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; } }