From e29c576356bbdf47846ce755a5ec76faa953392f Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 14 Jun 2026 16:54:05 +1000 Subject: [PATCH] pixel rendering functional --- .gitignore | 1 + .gitmodules | 3 + .ignore | 1 + build.d | 143 ++++++++++++++++++++++++++ dlib | 1 + shaders/pixel.comp.glsl | 38 +++++++ source/app.d | 216 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 403 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .ignore create mode 100755 build.d create mode 160000 dlib create mode 100644 shaders/pixel.comp.glsl create mode 100644 source/app.d diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..90a4d81 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dlib"] + path = dlib + url = https://git.sleepy.day/sleepy-day/dlib.git diff --git a/.ignore b/.ignore new file mode 100644 index 0000000..6b8710a --- /dev/null +++ b/.ignore @@ -0,0 +1 @@ +.git diff --git a/build.d b/build.d new file mode 100755 index 0000000..8e803f7 --- /dev/null +++ b/build.d @@ -0,0 +1,143 @@ +#!/usr/bin/env rdmd + +import std.stdio; +import std.file; +import std.array; +import std.process; +import core.stdc.stdlib : exit; + +enum Mode +{ + Debug, + Release, +}; + +enum Compiler +{ + LDC, + DMD, +}; + +// Config +Mode mode; +Compiler compiler; + +int COMPILER = 0; +int VERSION = 1; +int CFLAG = 2; +int OUT = 3; + +// General globals +string[] ARGS; + +bool HasFlag(string flag) +{ + bool result; + + foreach(arg; ARGS) + { + if(arg == flag) + { + result = true; + break; + } + } + + return result; +} + +void main(string[] args) +{ + ARGS = args; + + string out_name = "build/Pixel"; + + if(HasFlag("release")) mode = Mode.Release; + if(HasFlag("dmd")) compiler = Compiler.DMD; + + enum string[string] DMD_LOOKUP = [ + "compiler": "dmd", + "version": "-version=", + "cflag": "-P=", + "out": "-of=", + "include": "-I=", + ]; + + enum string[string] LDC_LOOKUP = [ + "compiler": "ldc2", + "version": "--d-version=", + "cflag": "--Xcc=", + "out": "--of=", + "include": "-I", + ]; + + auto lookup = compiler == Compiler.DMD ? DMD_LOOKUP : LDC_LOOKUP; + + string[] libs = ["SDL3", "vulkan", "X11", "Xfixes", "stdc++"]; + string[] dirs = ["dlib/dlib", "dlib/vulkan", "source"]; + string[] files = ["dlib/build/libvma.a"]; + string[] cflags = [ + "-DBUILD_VULKAN", + "-Idlib/external", + ]; + string[] versions = ["BUILD_VULKAN"]; + string[] scripts = [ + "./dlib/build.sh", + "glslang --target-env vulkan1.2 -gVS -o build/pixel.comp.spv shaders/pixel.comp.glsl" + ]; + string[] flags = [ + "-Jbuild", + "-Jdlib/build", + lookup["out"] ~ out_name, + lookup["include"] ~ "dlib/dlib", + ]; + + if(HasFlag("-v")) flags ~= "-v"; + + final switch(mode) + { + case Mode.Debug: + { + versions ~= "VULKAN_DEBUG"; + } break; + case Mode.Release: + { + + } break; + } + + execute(["mkdir", "-p"]); + + foreach(path; dirs) + { + foreach(string file_name; dirEntries(path, "*.{d,c}", SpanMode.depth)) + { + files ~= file_name; + } + } + + foreach(lib; libs) + { + flags ~= "-L-l" ~ lib; + } + + foreach(cflag; cflags) + { + flags ~= lookup["cflag"] ~ cflag; + } + + foreach(ver; versions) + { + flags ~= lookup["version"] ~ ver; + } + + foreach(script; scripts) + { + execute(script.split(" ")); + } + + auto result = execute([lookup["compiler"]] ~ files ~ flags); + + writeln(result.output); + exit(result.status); +} diff --git a/dlib b/dlib new file mode 160000 index 0000000..c65b540 --- /dev/null +++ b/dlib @@ -0,0 +1 @@ +Subproject commit c65b5401aba77d11562b544964d8db62cb65bb72 diff --git a/shaders/pixel.comp.glsl b/shaders/pixel.comp.glsl new file mode 100644 index 0000000..8d3e06d --- /dev/null +++ b/shaders/pixel.comp.glsl @@ -0,0 +1,38 @@ +#version 460 + +#define u32 uint +#define f32 float + +layout(local_size_x = 32, local_size_y = 32) in; + +layout(set = 1, binding = 0, rgba32f) uniform image2D DrawImage; + +layout(set = 1, binding = 1) buffer display_buffer +{ + u32 pixels[]; +}; + +layout(push_constant) uniform PushConst +{ + uvec2 src_resolution; +} PC; + +void main() +{ + uvec2 src_resolution = PC.src_resolution; + uvec2 dst_resolution = uvec2(imageSize(DrawImage).xy); + + uvec2 scale = dst_resolution/src_resolution; + uvec2 pos = uvec2(gl_GlobalInvocationID.xy); + + if(pos.x < dst_resolution.x && pos.y < dst_resolution.y) + { + uvec2 scaled_pos = pos/scale; + + u32 index = scaled_pos.y*src_resolution.x + scaled_pos.x; + + vec4 color = bool(pixels[index]) ? vec4(1.0) : vec4(0.0); + imageStore(DrawImage, ivec2(pos.xy), color); + } +} + diff --git a/source/app.d b/source/app.d new file mode 100644 index 0000000..48b4e0d --- /dev/null +++ b/source/app.d @@ -0,0 +1,216 @@ +import std.stdio; + +import dlib.aliases; +import dlib.util; + +import dlibincludes; +import vulkan; + +enum SDLWindowFlags : u64 +{ + FULLSCREEN = 0x0000000000000001, /**< window is in fullscreen mode */ + OPENGL = 0x0000000000000002, /**< window usable with OpenGL context */ + OCCLUDED = 0x0000000000000004, /**< window is occluded */ + HIDDEN = 0x0000000000000008, /**< window is neither mapped onto the desktop nor shown in the taskbar/dock/window list; SDL_ShowWindow() is required for it to become visible */ + BORDERLESS = 0x0000000000000010, /**< no window decoration */ + RESIZABLE = 0x0000000000000020, /**< window can be resized */ + MINIMIZED = 0x0000000000000040, /**< window is minimized */ + MAXIMIZED = 0x0000000000000080, /**< window is maximized */ + MOUSE_GRABBED = 0x0000000000000100, /**< window has grabbed mouse input */ + INPUT_FOCUS = 0x0000000000000200, /**< window has input focus */ + MOUSE_FOCUS = 0x0000000000000400, /**< window has mouse focus */ + EXTERNAL = 0x0000000000000800, /**< window not created by SDL */ + MODAL = 0x0000000000001000, /**< window is modal */ + HIGH_PIXEL_DENSITY = 0x0000000000002000, /**< window uses high pixel density back buffer if possible */ + MOUSE_CAPTURE = 0x0000000000004000, /**< window has mouse captured (unrelated to MOUSE_GRABBED) */ + MOUSE_RELATIVE_MODE = 0x0000000000008000, /**< window has relative mode enabled */ + ALWAYS_ON_TOP = 0x0000000000010000, /**< window should always be above others */ + UTILITY = 0x0000000000020000, /**< window should be treated as a utility window, not showing in the task bar and window list */ + TOOLTIP = 0x0000000000040000, /**< window should be treated as a tooltip and does not get mouse or keyboard focus, requires a parent window */ + POPUP_MENU = 0x0000000000080000, /**< window should be treated as a popup menu, requires a parent window */ + KEYBOARD_GRABBED = 0x0000000000100000, /**< window has grabbed keyboard input */ + FILL_DOCUMENT = 0x0000000000200000, /**< window is in fill-document mode (Emscripten only), since SDL 3.4.0 */ + VULKAN = 0x0000000010000000, /**< window usable for Vulkan surface */ + METAL = 0x0000000020000000, /**< window usable for Metal view */ + TRANSPARENT = 0x0000000040000000, /**< window with transparent buffer */ + NOT_FOCUSABLE = 0x0000000080000000, /**< window should not be focusable */ +} + +alias SDL_WINDOW = SDLWindowFlags; + +struct PxDisplay +{ + MappedBuffer!(u32) mapped_buffer; + u32[] buffer; + UVec2 resolution; + Descriptor descriptor; +} + +const u32 RESOLUTION_X = 640; +const u32 RESOLUTION_Y = 360; +const u32 BUFFER_SIZE = RESOLUTION_X * RESOLUTION_Y; +const UVec2 RESOLUTION = UVec2(RESOLUTION_X, RESOLUTION_Y); + +struct PxWork +{ + SDL_Window* window; + PxDisplay[FRAME_OVERLAP] displays; + Descriptor[FRAME_OVERLAP] draw_images; + PushConst pc; + DescSetLayout desc_set_layout; + DescSet[FRAME_OVERLAP] desc_sets; + PipelineLayout pipeline_layout; + Pipeline pipeline; + UVec2 dst_resolution; + bool[FRAME_OVERLAP] reload_image; +} + +struct PushConst +{ + UVec2 src_resolution; +} + +PxWork px; +const u8[] SHADER_SRC = import("pixel.comp.spv"); + +void +CreateDrawImage(Descriptor* desc, UVec2 resolution) +{ + CreateDescriptor( + desc, + DescInfo( + type: DT.StorageImage, + format: FMT.RGBA_F32, + usage: IU.Convert, + w: resolution.x, + h: resolution.y, + ch: 4, + binding: 0, + ) + ); +} + +void main() +{ + if(!SDL_Init(SDL_INIT_VIDEO)) + { + Errf("Failed to init SDL [%s]", SDL_GetError()); + assert(false); + } + + px.window = SDL_CreateWindow("Video Game", 1920, 1080, SDL_WINDOW.VULKAN); + if(!px.window) + { + Errf("Failed to create SDL window [%s]", SDL_GetError()); + assert(false); + } + + VulkanBuildInfo build_info = { + application_name: "Video Game", + engine_name: "PxWork", + platform_handles: { + sdl: true, + sdl_window: px.window, + }, + }; + + Init(build_info, MB(16), MB(8)); + + DescLayoutBinding[2] layout_bindings = [ + { binding: 0, descriptorType: DT.StorageImage, descriptorCount: 1, stageFlags: SS.All }, + { binding: 1, descriptorType: DT.Storage, descriptorCount: 1, stageFlags: SS.All }, + ]; + + px.desc_set_layout = CreateDescSetLayout(layout_bindings); + px.pipeline_layout = CreatePipelineLayout(px.desc_set_layout, PushConst.sizeof, true); + + px.dst_resolution = UVec2(GetExtent()); + px.pc.src_resolution = RESOLUTION; + + foreach(i; 0 .. FRAME_OVERLAP) + { + px.displays[i].mapped_buffer = CreateMappedBuffer!(u32)(BT.Storage, BUFFER_SIZE); + px.displays[i].buffer = px.displays[i].mapped_buffer.data; + px.displays[i].resolution = RESOLUTION; + + px.displays[i].descriptor.buffer = px.displays[i].mapped_buffer.base; + px.displays[i].descriptor.type = DT.Storage; + px.displays[i].descriptor.binding = 1; + + CreateDrawImage(&px.draw_images[i], px.dst_resolution); + + px.desc_sets[i] = AllocDescSet(px.desc_set_layout); + + Write(px.desc_sets[i], &px.draw_images[i]); + Write(px.desc_sets[i], &px.displays[i].descriptor); + } + + CompPipelineInfo pipeline_info = { + shader: cast(u8[])SHADER_SRC, + layout: px.pipeline_layout, + }; + + px.pipeline = CreateComputePipeline(&pipeline_info); + + SetClearColors([0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]); + + foreach(i; 0 .. FRAME_OVERLAP) + { + foreach(j; 0 .. RESOLUTION.y) + { + u32 cmp = j%2 == 0 ? 0 : 1; + foreach(k; 0 .. RESOLUTION.x) + { + px.displays[i].buffer[j*RESOLUTION.x + k] = k%2 == cmp; + } + } + } + + u32 index = 0; + SDL_Event event; + bool run = true; + while(run) + { + while(SDL_PollEvent(&event)) + { + switch(event.type) + { + case SDL_EVENT_QUIT: + { + run = false; + } break; + default: break; + } + } + + BeginFrame(); + + UVec2 extent = UVec2(GetExtent()); + if(extent.v != px.dst_resolution.v) + { + px.dst_resolution = extent; + foreach(i; 0 .. FRAME_OVERLAP) + { + px.reload_image[index] = true; + } + } + + if(px.reload_image[index]) + { + Destroy(&px.draw_images[index]); + CreateDrawImage(&px.draw_images[index], px.dst_resolution); + Write(px.desc_sets[index], &px.draw_images[index]); + + px.reload_image[index] = false; + } + + PrepComputeImage(&px.draw_images[index]); + Bind(px.pipeline, px.desc_sets[index]); + PushConstants(px.pipeline, &px.pc); + Dispatch(); + + SubmitAndPresent(&px.draw_images[index]); + + index = (index+1)%FRAME_OVERLAP; + } +}