struct texture_data { uint64_t id; rhi_texture_format GpuTextureFormat; rhi_texture Texture; rhi_sampled_texture_view TextureView; uint32_t width; uint32_t height; uint32_t channels; }; struct model_data { uint64_t id; uint32_t IndexCount; uint64_t IndexBufferOffset; uint64_t VertexBufferOffset; }; enum sampler_type { Sampler_LinearClamp = 0, Sampler_LinearRepeat = 1, Sampler_NearestClamp = 2, Sampler_NearestRepeat = 3, }; enum input_interface_type { Input_Keyboard, Input_Mouse, Input_Gamepad, }; enum class input_id : uint8_t { None = 0, MoveForward = 1, MoveBackward, MoveLeft, MoveRight, }; struct input_state { uint64_t pressedAt; uint32_t code; input_interface_type type; bool released; }; struct renderer_scene { float4x4 vp; float4x4 invVp; }; struct renderer_entity { float4x4 model; uint32_t srvTextureIndex; uint32_t samplerIndex; }; struct renderer_draw_call { renderer_entity Entity; uint64_t MeshId; }; struct game_state { alloc FrameAlloc; bool vsync; bool shouldExit; float dt; uint64_t dtUs; uint64_t frameStartUs; uint32_t fps; uint64_t targetFrameDurationUs; uint64_t lastFrameStartUs; uint32_t dtUsAccum; uint32_t framesCounted; input_state inputStates[0xFF]; int32_t mouseX; int32_t mouseY; bool mouseButtons[MouseButton_Count]; window_event frameEvents[64]; uint32_t frameEventsCount; rhi_buffer stagingBuffer; uint64_t stagingBufferAt; uint64_t staticVertexBufferSize; uint64_t staticVertexBufferAt; rhi_buffer staticVertexBuffer; uint64_t staticIndexBufferSize; uint64_t staticIndexBufferAt; rhi_buffer staticIndexBuffer; texture_data Textures[128]; uint64_t TexturesCount; model_data Models[128]; uint64_t ModelsCount; float4x4 View; float4x4 Proj; rhi_graphics_pipeline Pipeline; renderer_draw_call DrawQueue[256]; uint64_t DrawQueueCount; struct { rhi_texture_format Format; rhi_texture Texture; rhi_render_target_view View; } ColorTarget; struct { rhi_texture_format Format; rhi_texture Texture; rhi_depth_stencil_view View; } DepthTarget; uint32_t CachedWidth; uint32_t CachedHeight; }; game_state* Game; #include "gpus.cc" #include "texture.cc" #include "models.cc" static texture_data* GetTexture(uint64_t TextureId) { for (uint64_t lo = 0, hi = Game->TexturesCount; lo < hi; ) { uint64_t mid = lo + (hi - lo) / 2; if (Game->Textures[mid].id == TextureId) return &Game->Textures[mid]; if (Game->Textures[mid].id < TextureId) lo = mid + 1; else hi = mid; } return 0; } static model_data* GetModel(uint64_t ModelId) { for (uint64_t lo = 0, hi = Game->ModelsCount; lo < hi; ) { uint64_t mid = lo + (hi - lo) / 2; if (Game->Models[mid].id == ModelId) return &Game->Models[mid]; if (Game->Models[mid].id < ModelId) lo = mid + 1; else hi = mid; } return 0; } static void SetOrthoProjection(float metersOnScreen) { rhi_texture_info rtInfo = Rhi_GetTextureInfo(Game->ColorTarget.Texture); uint32_t width = rtInfo.width; uint32_t height = rtInfo.height; float worldW; float worldH; float base = metersOnScreen; float aspect = ((float)width) / ((float)height); worldH = base; worldW = base * aspect; Game->Proj = OrthoProjection(-worldW * .5f, worldW * .5f, worldH * .5f, -worldH * .5f, 0.f, 1.f); } static void SetPerspectiveProjection(uint32_t width, uint32_t height, float fovDegrees, float nearPlane, float farPlane) { const float aspect = (float)width / (float)height; const float fovRadians = fovDegrees * (PI / 180.0f); Game->Proj = PerspectiveProjection(fovRadians, aspect, nearPlane, farPlane); } static void MapInput(input_id input, input_interface_type type, uint32_t code) { input_state* State = &Game->inputStates[(uint8_t)input]; State->type = type; State->code = code; } static void HandlePressed(input_interface_type type, uint32_t code, uint64_t timestamp) { for (uint32_t i = 0; i < COUNT(Game->inputStates); ++i) { input_state* State = &Game->inputStates[i]; if (State->type == type && State->code == code) { State->pressedAt = MAX(State->pressedAt, timestamp); return; } } } static void HandleReleased(input_interface_type type, uint32_t code) { for (uint32_t i = 0; i < COUNT(Game->inputStates); ++i) { input_state* State = &Game->inputStates[i]; if (State->type == type && State->code == code) { State->released = true; return; } } } static bool IsPressed(input_id input) { input_state* State = &Game->inputStates[(uint8_t)input]; return State->pressedAt; } static bool IsReleased(input_id input) { input_state* State = &Game->inputStates[(uint8_t)input]; return State->released; } static bool IsMouseButtonDown(mouse_button button) { if (button >= 0 && button < MouseButton_Count) { return Game->mouseButtons[button]; } return false; } static void Game_Bootstrap() { Game = PUSH_ALLOC(&PermanentAlloc, game_state, 1); Game->FrameAlloc = AllocAlloc(16 << 20); uint32_t maxFps = 144; Game->targetFrameDurationUs = 1'000'000ull / maxFps; Game->lastFrameStartUs = GetMonoTimeMicros(); WinOpen(); rhi_init_desc RhiConfig{}; RhiConfig.flags = Flag_VSync; RhiConfig.limits = DefaultGameRhiLimits(); Rhi_Bootstrap(&Game->FrameAlloc, &RhiConfig); Lua_Bootstrap(); { Game->stagingBufferAt = 0; Game->stagingBuffer = Rhi_CreateBuffer("StagingBuffer", GPU_MAX_PER_FRAME_UPLOAD_SIZE, BufferUsage_CopySrc, MemoryUsage_CpuToGpu); Game->staticVertexBufferSize = 1 << 30; Game->staticVertexBufferAt = 0; Game->staticVertexBuffer = Rhi_CreateBuffer("StaticVertexBuffer", Game->staticVertexBufferSize, (rhi_buffer_usage)(BufferUsage_Vertex | BufferUsage_CopyDst), MemoryUsage_GpuOnly); Game->staticIndexBufferSize = 256 << 20; Game->staticIndexBufferAt = 0; Game->staticIndexBuffer = Rhi_CreateBuffer("StaticIndexBuffer", Game->staticIndexBufferSize, (rhi_buffer_usage)(BufferUsage_Index | BufferUsage_CopyDst), MemoryUsage_GpuOnly); } { #define X(filter, addressMode) Rhi_CreateSampler("Sampler_" #filter, filter, filter, addressMode, addressMode, addressMode); X(FilterLinear, AddressClampToEdge); // Sampler_LinearClamp X(FilterLinear, AddressRepeat); // Sampler_LinearRepeat X(FilterNearest, AddressClampToEdge); // Sampler_NearestClamp X(FilterNearest, AddressRepeat); // Sampler_NearestRepeat #undef X } { Lua_BeginExport(); auto load = [](lua_State* state) { LoadTexture(rhi->graphicsQueue.cmd, (const char*)Lua_GetByteview(state, 1).data, (const char*)Lua_GetByteview(state, 2).data); return 0; }; Lua_ExportFn(load, "Load"); Lua_EndExport("Texture"); } { Lua_BeginExport(); auto load = [](lua_State* state) { LoadModel(rhi->graphicsQueue.cmd, (const char*)Lua_GetByteview(state, 1).data, Lua_GetByteview(state, 2), Lua_GetInteger(state, 3), Lua_GetByteview(state, 4), Lua_GetByteview(state, 5), Lua_GetByteview(state, 6)); return 0; }; Lua_ExportFn(load, "Load"); Lua_EndExport("Model"); } { rhi_vertex_attribute_desc vertexAttributes[3] { rhi_vertex_attribute_desc { .binding = 0, .semantic = "POSITION0"_s, .format = R32G32B32Float, .offset = 0, }, rhi_vertex_attribute_desc { .binding = 0, .semantic = "NORMAL0"_s, .format = R32G32B32Float, .offset = 12, }, rhi_vertex_attribute_desc { .binding = 0, .semantic = "TEXCOORD0"_s, .format = R32G32Float, .offset = 24, }, }; rhi_vertex_binding_desc vertexBinding { .binding = 0, .stride = 32, .inputRate = PerVertex, }; rhi_texture_format colorFormat = rhi_texture_format::B8G8R8A8_sRGB; rhi_shader vs; { FILE *file = fopen("shaders/build/renderer.vs.hlsl.spv", "rb"); ASSERT(file); fseek(file, 0, SEEK_END); uint64_t size = ftell(file); rewind(file); uint8_t* data = PUSH_ALLOC(&Game->FrameAlloc, uint8_t, size); fread(data, 1, size, file); fclose(file); vs = Rhi_CreateShader(Shader_Vertex, { data, size }); } rhi_shader ps; { FILE *file = fopen("shaders/build/renderer.ps.hlsl.spv", "rb"); ASSERT(file); fseek(file, 0, SEEK_END); uint64_t size = ftell(file); rewind(file); uint8_t* data = PUSH_ALLOC(&Game->FrameAlloc, uint8_t, size); fread(data, 1, size, file); fclose(file); ps = Rhi_CreateShader(Shader_Pixel, { data, size }); } Game->Pipeline = Rhi_CreateGraphicsPipeline(&Game->FrameAlloc, "Renderer", vs, ps, &vertexBinding, 1, vertexAttributes, COUNT(vertexAttributes), &colorFormat, 1, (rhi_pipeline_flags)(Pipeline_CCW | Pipeline_CullBack | Pipeline_DepthWrite | Pipeline_DepthTest), D32_Float_S8_UINT); } Game->ColorTarget.Format = rhi_texture_format::B8G8R8A8_sRGB; Game->DepthTarget.Format = rhi_texture_format::D32_Float_S8_UINT; // MapInput(input_id::MoveRight, Input_Keyboard, Key_ArrowRight); MapInput(input_id::MoveLeft, Input_Keyboard, Key_ArrowLeft); } static void UpdateRenderTargets(uint32_t width, uint32_t height) { if (width != Game->CachedWidth || height != Game->CachedHeight) { if (Game->ColorTarget.View) { Rhi_DestroyRenderTargetView(Game->ColorTarget.View); Game->ColorTarget.View = nullptr; } if (Game->ColorTarget.Texture) { Rhi_DestroyTexture(Game->ColorTarget.Texture); Game->ColorTarget.Texture = nullptr; } if (Game->DepthTarget.View) { Rhi_DestroyDepthStencilView(Game->DepthTarget.View); Game->DepthTarget.View = nullptr; } if (Game->DepthTarget.Texture) { Rhi_DestroyTexture(Game->DepthTarget.Texture); Game->DepthTarget.Texture = nullptr; } Game->CachedWidth = width; Game->CachedHeight = height; } if (!Game->ColorTarget.View) { Game->ColorTarget.Texture = Rhi_CreateTexture("ColorTarget", width, height, MemoryUsage_GpuOnly, (rhi_texture_usage)(TextureUsage_ColorTarget | TextureUsage_TransferSrc), Game->ColorTarget.Format); Game->ColorTarget.View = Rhi_CreateRenderTargetView("ColorTargetView", Game->ColorTarget.Texture, (rhi_texture_format)0); } if (!Game->DepthTarget.View) { Game->DepthTarget.Texture = Rhi_CreateTexture("DepthTarget", width, height, MemoryUsage_GpuOnly, (rhi_texture_usage)(TextureUsage_DepthStencil), Game->DepthTarget.Format); Game->DepthTarget.View = Rhi_CreateDepthStencilView("DepthTargetView", Game->DepthTarget.Texture, (rhi_texture_format)0); } } static void FrameBegin(bool rendering) { Game->FrameAlloc.at = Game->FrameAlloc.begin; Game->stagingBufferAt = 0; Rhi_FrameBegin(&Game->FrameAlloc); Game->frameStartUs = GetMonoTimeMicros(); Game->dtUs = Game->frameStartUs - Game->lastFrameStartUs; Game->lastFrameStartUs = Game->frameStartUs; Game->dt = (float)Game->dtUs * 1e-6f; Game->dtUsAccum += (uint32_t)Game->dtUs; ++Game->framesCounted; if (Game->dtUsAccum >= 500'000u) { double seconds = (double)Game->dtUsAccum / 1'000'000.0; Game->fps = (uint32_t)LRound((double)Game->framesCounted / seconds); if (false) { LOG("FPS=%d, framesCount=%d, dt=%f", Game->fps, Game->framesCounted, Game->dt); } Game->dtUsAccum = 0; Game->framesCounted = 0; } Game->frameEventsCount = 0; while (!Game->shouldExit) { window_event event; if (!WinPollEvent(&event)) break; if (Game->frameEventsCount < COUNT(Game->frameEvents)) { Game->frameEvents[Game->frameEventsCount++] = event; } switch (event.type) { case WindowEvent_KeyDown: { HandlePressed(Input_Keyboard, event.Key, Game->frameStartUs); } break; case WindowEvent_KeyUp: { HandleReleased(Input_Keyboard, event.Key); } break; case WindowEvent_MouseDown: { if (event.Mouse.Button < MouseButton_Count) { Game->mouseButtons[event.Mouse.Button] = true; } Game->mouseX = event.Mouse.x; Game->mouseY = event.Mouse.y; HandlePressed(Input_Mouse, (uint32_t)event.Mouse.Button, Game->frameStartUs); } break; case WindowEvent_MouseUp: { if (event.Mouse.Button < MouseButton_Count) { Game->mouseButtons[event.Mouse.Button] = false; } Game->mouseX = event.Mouse.x; Game->mouseY = event.Mouse.y; HandleReleased(Input_Mouse, (uint32_t)event.Mouse.Button); } break; case WindowEvent_MouseMove: { Game->mouseX = event.Mouse.x; Game->mouseY = event.Mouse.y; } break; case WindowEvent_Resize: { Rhi_TriggerSwapchainRecreate(); } break; case WindowEvent_Quit: { Game->shouldExit = true; } break; } } rhi_texture backbuffer = Rhi_GetTextureFromRender(Rhi_GetBackbufferView()); rhi_texture_info backbufferInfo = Rhi_GetTextureInfo(backbuffer); UpdateRenderTargets(backbufferInfo.width, backbufferInfo.height); if (rendering) { Rhi_CmdTransitionTexture(rhi->graphicsQueue.cmd, Game->ColorTarget.Texture, TextureState_ColorTarget); Rhi_CmdTransitionTexture(rhi->graphicsQueue.cmd, Game->DepthTarget.Texture, TextureState_DepthTarget); Rhi_PassBegin(Game->ColorTarget.View, Game->DepthTarget.View); } } static void FrameEnd(bool rendering) { if (rendering) { Rhi_CmdBindGraphicsPipeline(rhi->graphicsQueue.cmd, Game->Pipeline); float4x4 vp = Game->Proj * Game->View; float4x4 invVp; Invert(&vp, &invVp); renderer_scene scene = { .vp = vp, .invVp = invVp, }; Rhi_SetPassConstant(rhi->graphicsQueue.cmd, BYTEVIEW(&scene)); for (uint32_t i = 0; i < Game->DrawQueueCount; ++i) { renderer_draw_call* DrawCall = &Game->DrawQueue[i]; Rhi_SetDrawConstant(rhi->graphicsQueue.cmd, BYTEVIEW(&DrawCall->Entity)); ModelCmdBind(&Game->FrameAlloc, rhi->graphicsQueue.cmd, DrawCall->MeshId); ModelCmdDraw(rhi->graphicsQueue.cmd, DrawCall->MeshId); } Game->DrawQueueCount = 0; Rhi_PassEnd(); } rhi_texture BackBuffer = Rhi_GetTextureFromRender(Rhi_GetBackbufferView()); Rhi_CmdTransitionTexture(rhi->graphicsQueue.cmd, Game->ColorTarget.Texture, TextureState_TransferSrc); Rhi_CmdTransitionTexture(rhi->graphicsQueue.cmd, BackBuffer, TextureState_TransferDst); Rhi_CmdCopyTexture(rhi->graphicsQueue.cmd, BackBuffer, Game->ColorTarget.Texture); Rhi_CmdTransitionTexture(rhi->graphicsQueue.cmd, BackBuffer, TextureState_Present); Rhi_FrameEnd(&Game->FrameAlloc); for (input_state& state : Game->inputStates) { if (state.released) { state.pressedAt = 0; state.released = false; } } uint64_t frameEndUs = GetMonoTimeMicros(); uint64_t frameDurationUs = frameEndUs - Game->lastFrameStartUs; if (Game->targetFrameDurationUs > frameDurationUs) { uint64_t sleepForMillis = (Game->targetFrameDurationUs - frameDurationUs) / 1000; if (sleepForMillis) Sleep(sleepForMillis); } } static void RenderModel(float3 pos, float3 scale, uint64_t MeshId, uint64_t TextureId) { texture_data *Texture = GetTexture(TextureId); if (Texture->TextureView) { renderer_draw_call* DrawCall = &Game->DrawQueue[Game->DrawQueueCount++]; MEM_ZERO(DrawCall, 0); DrawCall->MeshId = MeshId; DrawCall->Entity.model = Identity4x4; DrawCall->Entity.model *= TranslateTransform({ pos.x, pos.y, pos.z, 1.f }); DrawCall->Entity.model *= ScaleTransform({ scale.x, scale.y, scale.z, 1.f }); DrawCall->Entity.srvTextureIndex = Texture->TextureView - rhi->SampledTextureViews; DrawCall->Entity.samplerIndex = (uint32_t)Sampler_NearestClamp; } }