SleakEngine 0.1.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
DirectX12Renderer.cpp
Go to the documentation of this file.
2#ifdef PLATFORM_WIN
3
4#include <SDL3/SDL_system.h>
5#include <d3dcompiler.h>
6
7#include <Core/Logger.hpp>
8#include <Core/Window.hpp>
12#include <Runtime/MeshData.hpp>
13#include <codecvt>
14#include <cstdint>
15#include <cstring>
16#include <locale>
17#include <stdexcept>
18#include <string>
19
21
22namespace Sleak {
23namespace RenderEngine {
24
25namespace {
26const D3D12_INPUT_ELEMENT_DESC kVertexInputLayout[] = {
27 {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,
28 static_cast<UINT>(offsetof(Sleak::Vertex, px)),
29 D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
30 {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,
31 static_cast<UINT>(offsetof(Sleak::Vertex, nx)),
32 D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
33 {"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0,
34 static_cast<UINT>(offsetof(Sleak::Vertex, tx)),
35 D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
36 {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0,
37 static_cast<UINT>(offsetof(Sleak::Vertex, r)),
38 D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
39 {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0,
40 static_cast<UINT>(offsetof(Sleak::Vertex, u)),
41 D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
42};
43
44uint64_t HashBytecode(ID3DBlob* blob) {
45 if (!blob) return 0;
46 const auto* bytes = static_cast<const uint8_t*>(blob->GetBufferPointer());
47 uint64_t hash = 14695981039346656037ull;
48 for (SIZE_T i = 0; i < blob->GetBufferSize(); ++i) {
49 hash ^= bytes[i];
50 hash *= 1099511628211ull;
51 }
52 return hash;
53}
54
55Microsoft::WRL::ComPtr<ID3DBlob> CompileStage(const wchar_t* path,
56 const char* entry,
57 const char* profile) {
58 Microsoft::WRL::ComPtr<ID3DBlob> blob, errorBlob;
59 HRESULT hr = D3DCompileFromFile(path, nullptr, nullptr, entry, profile, 0,
60 0, &blob, &errorBlob);
61 if (FAILED(hr)) {
62 if (errorBlob) {
63 SLEAK_ERROR("{} ({}) compile error: {}", entry, profile,
64 (char*)errorBlob->GetBufferPointer());
65 }
66 return nullptr;
67 }
68 return blob;
69}
70} // namespace
71
72/// Registers this backend's buffer/shader/texture factories with ResourceManager.
73DirectX12Renderer::DirectX12Renderer(Window* window) : window(window) {
74 fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
75 if (fenceEvent == nullptr) {
76 throw std::runtime_error("Failed to create fence event.");
77 }
78 this->Type = RendererType::DirectX12;
79 m_lightData.assign(sizeof(RenderEngine::ShadowLightUBO), 0);
80
81 ResourceManager::RegisterCreateBuffer(
82 this, &DirectX12Renderer::CreateBuffer);
83 ResourceManager::RegisterCreateShader(
84 this, &DirectX12Renderer::CreateShader);
85 ResourceManager::RegisterCreateTexture(
86 this, &DirectX12Renderer::CreateTexture);
87 ResourceManager::RegisterCreateCubemapTexture(
88 this, &DirectX12Renderer::CreateCubemapTexture);
89 ResourceManager::RegisterCreateCubemapTextureFromPanorama(
90 this, &DirectX12Renderer::CreateCubemapTextureFromPanorama);
91 ResourceManager::RegisterCreateTextureFromMemory(
92 [this](const void* data, uint32_t w, uint32_t h, TextureFormat fmt,
93 uint32_t) -> Texture* {
94 auto* tex = new DirectX12Texture(device.Get(), commandQueue.Get(),
95 commandList.Get(), &m_uploader);
96 if (!tex->LoadFromMemory(data, w, h, fmt)) {
97 delete tex;
98 return nullptr;
99 }
100 UINT slot = AllocateSRVSlot();
101 tex->CreateSRVIntoHandle(DXGI_FORMAT_R8G8B8A8_UNORM,
102 GetSharedSrvCPUHandle(slot));
103 tex->SetSharedSrvGPUHandle(GetSharedSrvGPUHandle(slot));
104 return tex;
105 });
106}
107
108DirectX12Renderer::~DirectX12Renderer() {
109 WaitForGPU();
110 delete m_defaultTexture;
111 m_defaultTexture = nullptr;
112 Cleanup();
113 if (fenceEvent) {
114 CloseHandle(fenceEvent);
115 fenceEvent = nullptr;
116 }
117}
118
119bool DirectX12Renderer::Initialize() {
120 if (m_Initialized) return true;
121
122 if (!CreateDevice()) return false;
123 if (!CreateCommandQueue()) return false;
124 if (!CreateCommandAllocatorAndList()) return false;
125 if (!CreateSwapChain()) return false;
126 if (!CreateRenderTargetViews()) return false;
127 if (!CreateDepthStencilView()) return false;
128 if (!CreateFence()) return false;
129 m_uploadRing.Initialize(device.Get(), FrameCount, 64 * 1024);
130 if (!m_uploader.Initialize(device.Get(), commandQueue.Get())) return false;
131 if (!CreateRootSignature()) return false;
132 if (!CreateSharedSrvHeap()) return false;
133 // PSO is created lazily when CreateShader() is called
134
135 // Create a 1x1 white default texture so the SRV table is always valid
136 {
137 uint32_t whitePixel = 0xFFFFFFFF; // RGBA(255,255,255,255)
138 m_defaultTexture = new DirectX12Texture(
139 device.Get(), commandQueue.Get(), commandList.Get(), &m_uploader);
140 if (!m_defaultTexture->LoadFromMemory(
141 &whitePixel, 1, 1, TextureFormat::RGBA8)) {
142 SLEAK_WARN("Failed to create default white texture for DX12");
143 delete m_defaultTexture;
144 m_defaultTexture = nullptr;
145 } else {
146 UINT slot = AllocateSRVSlot();
147 m_defaultTexture->CreateSRVIntoHandle(DXGI_FORMAT_R8G8B8A8_UNORM,
148 GetSharedSrvCPUHandle(slot));
149 m_defaultTexture->SetSharedSrvGPUHandle(GetSharedSrvGPUHandle(slot));
150 }
151 }
152
153 // Set initial viewport
154 int w = Window::GetWidth();
155 int h = Window::GetHeight();
156 viewport.TopLeftX = 0.0f;
157 viewport.TopLeftY = 0.0f;
158 viewport.Width = static_cast<float>(w);
159 viewport.Height = static_cast<float>(h);
160 viewport.MinDepth = 0.0f;
161 viewport.MaxDepth = 1.0f;
162
163 scissorRect.left = 0;
164 scissorRect.top = 0;
165 scissorRect.right = w;
166 scissorRect.bottom = h;
167
168 SetPerformanceCounter(true);
169
170 m_Initialized = true;
171 SLEAK_INFO("DirectX 12 has been initialized successfully!");
172
173 return true;
174}
175
176bool DirectX12Renderer::CreateDevice() {
177 // Try feature level 12.0 first
178 if (SUCCEEDED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0,
179 IID_PPV_ARGS(&device)))) {
180 SLEAK_INFO("DirectX 12 device created with feature level 12.0");
181 return true;
182 }
183
184 // Fall back to 11.0
185 if (SUCCEEDED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0,
186 IID_PPV_ARGS(&device)))) {
187 SLEAK_INFO("DirectX 12 device created with feature level 11.0");
188 return true;
189 }
190
191 SLEAK_ERROR("Failed to create DirectX 12 device!");
192 return false;
193}
194
195bool DirectX12Renderer::CreateCommandQueue() {
196 D3D12_COMMAND_QUEUE_DESC queueDesc = {};
197 queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
198 queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
199
200 if (FAILED(device->CreateCommandQueue(&queueDesc,
201 IID_PPV_ARGS(&commandQueue)))) {
202 SLEAK_ERROR("Failed to create command queue!");
203 return false;
204 }
205 return true;
206}
207
208bool DirectX12Renderer::CreateCommandAllocatorAndList() {
209 // Create one command allocator per frame-in-flight so the CPU can
210 // record frame N+1 while the GPU is still executing frame N.
211 for (UINT i = 0; i < FrameCount; i++) {
212 if (FAILED(device->CreateCommandAllocator(
213 D3D12_COMMAND_LIST_TYPE_DIRECT,
214 IID_PPV_ARGS(&commandAllocators[i])))) {
215 SLEAK_ERROR("Failed to create command allocator {}!", i);
216 return false;
217 }
218 }
219
220 if (FAILED(device->CreateCommandList(
221 0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocators[0].Get(),
222 nullptr, IID_PPV_ARGS(&commandList)))) {
223 SLEAK_ERROR("Failed to create command list!");
224 return false;
225 }
226
227 // Close the command list (it starts in an open state)
228 commandList->Close();
229 return true;
230}
231
232bool DirectX12Renderer::CreateSwapChain() {
233 HWND hwnd = (HWND)SDL_GetPointerProperty(
234 SDL_GetWindowProperties(window->GetSDLWindow()),
235 SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
236
237 DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
238 swapChainDesc.BufferCount = FrameCount;
239 swapChainDesc.Width = 0;
240 swapChainDesc.Height = 0;
241 swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
242 swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
243 swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
244 swapChainDesc.SampleDesc.Count = 1;
245
246 Microsoft::WRL::ComPtr<IDXGIFactory4> factory;
247 if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&factory)))) {
248 SLEAK_ERROR("Failed to create DXGI factory!");
249 return false;
250 }
251
252 HRESULT hr = factory->CreateSwapChainForHwnd(
253 commandQueue.Get(), hwnd, &swapChainDesc, nullptr, nullptr,
254 reinterpret_cast<IDXGISwapChain1**>(swapChain.GetAddressOf()));
255
256 if (FAILED(hr)) {
257 SLEAK_ERROR("Failed to create swap chain!");
258 return false;
259 }
260
261 frameIndex = swapChain->GetCurrentBackBufferIndex();
262
263 EnumerateDevices(factory);
264
265 return true;
266}
267
268bool DirectX12Renderer::CreateRenderTargetViews() {
269 D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
270 rtvHeapDesc.NumDescriptors = FrameCount;
271 rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
272 rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
273
274 if (FAILED(device->CreateDescriptorHeap(&rtvHeapDesc,
275 IID_PPV_ARGS(&rtvHeap)))) {
276 SLEAK_ERROR("Failed to create RTV descriptor heap!");
277 return false;
278 }
279
280 rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(
281 D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
282
283 D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle =
284 rtvHeap->GetCPUDescriptorHandleForHeapStart();
285
286 for (UINT i = 0; i < FrameCount; i++) {
287 if (FAILED(swapChain->GetBuffer(i,
288 IID_PPV_ARGS(&renderTargets[i])))) {
289 SLEAK_ERROR("Failed to get swap chain buffer {}!", i);
290 return false;
291 }
292 device->CreateRenderTargetView(renderTargets[i].Get(), nullptr,
293 rtvHandle);
294 rtvHandle.ptr += rtvDescriptorSize;
295 }
296
297 return true;
298}
299
300bool DirectX12Renderer::CreateDepthStencilView() {
301 // Create DSV descriptor heap
302 D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
303 dsvHeapDesc.NumDescriptors = 1;
304 dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
305 dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
306
307 if (FAILED(device->CreateDescriptorHeap(&dsvHeapDesc,
308 IID_PPV_ARGS(&dsvHeap)))) {
309 SLEAK_ERROR("Failed to create DSV descriptor heap!");
310 return false;
311 }
312
313 // Get swap chain dimensions
314 DXGI_SWAP_CHAIN_DESC1 scDesc;
315 swapChain->GetDesc1(&scDesc);
316 UINT width = scDesc.Width;
317 UINT height = scDesc.Height;
318
319 // Create depth stencil buffer
320 D3D12_RESOURCE_DESC depthDesc = {};
321 depthDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
322 depthDesc.Width = width;
323 depthDesc.Height = height;
324 depthDesc.DepthOrArraySize = 1;
325 depthDesc.MipLevels = 1;
326 depthDesc.Format = DXGI_FORMAT_D32_FLOAT;
327 depthDesc.SampleDesc.Count = 1;
328 depthDesc.SampleDesc.Quality = 0;
329 depthDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
330
331 D3D12_CLEAR_VALUE clearValue = {};
332 clearValue.Format = DXGI_FORMAT_D32_FLOAT;
333 clearValue.DepthStencil.Depth = 1.0f;
334 clearValue.DepthStencil.Stencil = 0;
335
336 D3D12_HEAP_PROPERTIES heapProps = {};
337 heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
338
339 if (FAILED(device->CreateCommittedResource(
340 &heapProps, D3D12_HEAP_FLAG_NONE, &depthDesc,
341 D3D12_RESOURCE_STATE_DEPTH_WRITE, &clearValue,
342 IID_PPV_ARGS(&depthStencilBuffer)))) {
343 SLEAK_ERROR("Failed to create depth stencil buffer!");
344 return false;
345 }
346
347 // Create depth stencil view
348 D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
349 dsvDesc.Format = DXGI_FORMAT_D32_FLOAT;
350 dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
351 dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
352
353 device->CreateDepthStencilView(
354 depthStencilBuffer.Get(), &dsvDesc,
355 dsvHeap->GetCPUDescriptorHandleForHeapStart());
356
357 return true;
358}
359
360bool DirectX12Renderer::CreateFence() {
361 if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE,
362 IID_PPV_ARGS(&fence)))) {
363 SLEAK_ERROR("Failed to create fence!");
364 return false;
365 }
366 m_fenceValue = 0;
367 for (UINT i = 0; i < FrameCount; i++)
368 fenceValues[i] = 0;
369 return true;
370}
371
372bool DirectX12Renderer::CreateRootSignature() {
373 // Parameter 0: CBV at register(b0) — transform (vertex shader)
374 // Parameter 1: CBV at register(b1) — material (all shaders)
375 // Parameter 2: SRV descriptor table at register(t0) — texture (pixel shader)
376 // Parameter 3: CBV at register(b2) — lighting/fog (all shaders, VS needs LightVP)
377 // Parameter 4: SRV descriptor table at register(t3) — shadow map (pixel shader)
378 D3D12_ROOT_PARAMETER rootParams[5] = {};
379 rootParams[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
380 rootParams[0].Descriptor.ShaderRegister = 0;
381 rootParams[0].Descriptor.RegisterSpace = 0;
382 rootParams[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
383
384 rootParams[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
385 rootParams[1].Descriptor.ShaderRegister = 1;
386 rootParams[1].Descriptor.RegisterSpace = 0;
387 rootParams[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
388
389 D3D12_DESCRIPTOR_RANGE srvRange = {};
390 srvRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
391 srvRange.NumDescriptors = 1;
392 srvRange.BaseShaderRegister = 0;
393 srvRange.RegisterSpace = 0;
394 srvRange.OffsetInDescriptorsFromTableStart =
395 D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
396
397 rootParams[2].ParameterType =
398 D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
399 rootParams[2].DescriptorTable.NumDescriptorRanges = 1;
400 rootParams[2].DescriptorTable.pDescriptorRanges = &srvRange;
401 rootParams[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
402
403 rootParams[3].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
404 rootParams[3].Descriptor.ShaderRegister = 2;
405 rootParams[3].Descriptor.RegisterSpace = 0;
406 rootParams[3].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
407
408 D3D12_DESCRIPTOR_RANGE shadowSrvRange = {};
409 shadowSrvRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
410 shadowSrvRange.NumDescriptors = 1;
411 shadowSrvRange.BaseShaderRegister = 3;
412 shadowSrvRange.RegisterSpace = 0;
413 shadowSrvRange.OffsetInDescriptorsFromTableStart =
414 D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
415
416 rootParams[4].ParameterType =
417 D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
418 rootParams[4].DescriptorTable.NumDescriptorRanges = 1;
419 rootParams[4].DescriptorTable.pDescriptorRanges = &shadowSrvRange;
420 rootParams[4].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
421
422 // Static samplers
423 D3D12_STATIC_SAMPLER_DESC staticSamplers[3] = {};
424
425 // s0: ANISOTROPIC 16x — surface textures (high quality filtering)
426 staticSamplers[0].Filter = D3D12_FILTER_ANISOTROPIC;
427 staticSamplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
428 staticSamplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
429 staticSamplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
430 staticSamplers[0].MipLODBias = 0.0f;
431 staticSamplers[0].MaxAnisotropy = 16;
432 staticSamplers[0].ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
433 staticSamplers[0].BorderColor =
434 D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
435 staticSamplers[0].MinLOD = 0.0f;
436 staticSamplers[0].MaxLOD = D3D12_FLOAT32_MAX;
437 staticSamplers[0].ShaderRegister = 0;
438 staticSamplers[0].RegisterSpace = 0;
439 staticSamplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
440
441 // s1: LINEAR/WRAP — skybox cubemap (smooth filtering)
442 staticSamplers[1].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
443 staticSamplers[1].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
444 staticSamplers[1].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
445 staticSamplers[1].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
446 staticSamplers[1].MipLODBias = 0.0f;
447 staticSamplers[1].MaxAnisotropy = 1;
448 staticSamplers[1].ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
449 staticSamplers[1].BorderColor =
450 D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
451 staticSamplers[1].MinLOD = 0.0f;
452 staticSamplers[1].MaxLOD = D3D12_FLOAT32_MAX;
453 staticSamplers[1].ShaderRegister = 1;
454 staticSamplers[1].RegisterSpace = 0;
455 staticSamplers[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
456
457 // s3: COMPARISON sampler — shadow map PCF
458 staticSamplers[2].Filter = D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT;
459 staticSamplers[2].AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
460 staticSamplers[2].AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
461 staticSamplers[2].AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
462 staticSamplers[2].MipLODBias = 0.0f;
463 staticSamplers[2].MaxAnisotropy = 1;
464 staticSamplers[2].ComparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
465 staticSamplers[2].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE;
466 staticSamplers[2].MinLOD = 0.0f;
467 staticSamplers[2].MaxLOD = 0.0f;
468 staticSamplers[2].ShaderRegister = 3;
469 staticSamplers[2].RegisterSpace = 0;
470 staticSamplers[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
471
472 D3D12_ROOT_SIGNATURE_DESC rootSigDesc = {};
473 rootSigDesc.NumParameters = 5;
474 rootSigDesc.pParameters = rootParams;
475 rootSigDesc.NumStaticSamplers = 3;
476 rootSigDesc.pStaticSamplers = staticSamplers;
477 rootSigDesc.Flags =
478 D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
479
480 Microsoft::WRL::ComPtr<ID3DBlob> serializedRootSig;
481 Microsoft::WRL::ComPtr<ID3DBlob> errorBlob;
482
483 HRESULT hr = D3D12SerializeRootSignature(
484 &rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
485 &serializedRootSig, &errorBlob);
486
487 if (FAILED(hr)) {
488 if (errorBlob) {
489 SLEAK_ERROR("Root signature serialization error: {}",
490 (char*)errorBlob->GetBufferPointer());
491 }
492 return false;
493 }
494
495 hr = device->CreateRootSignature(
496 0, serializedRootSig->GetBufferPointer(),
497 serializedRootSig->GetBufferSize(),
498 IID_PPV_ARGS(&rootSignature));
499
500 if (FAILED(hr)) {
501 SLEAK_ERROR("Failed to create root signature!");
502 return false;
503 }
504
505 return true;
506}
507
508ID3D12PipelineState* DirectX12Renderer::GetPipeline(const PipelineKey& key,
509 ID3DBlob* vs,
510 ID3DBlob* ps) {
511 for (auto& [cachedKey, pso] : m_pipelineCache) {
512 if (cachedKey == key) return pso.Get();
513 }
514 if (!vs || key.vertexFormat != 0) return nullptr;
515
516 D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
517 psoDesc.InputLayout = {kVertexInputLayout, _countof(kVertexInputLayout)};
518 psoDesc.pRootSignature = rootSignature.Get();
519 psoDesc.VS = {vs->GetBufferPointer(), vs->GetBufferSize()};
520 if (ps) psoDesc.PS = {ps->GetBufferPointer(), ps->GetBufferSize()};
521
522 D3D12_RASTERIZER_DESC rasterDesc = {};
523 rasterDesc.FillMode = D3D12_FILL_MODE_SOLID;
524 rasterDesc.CullMode = key.cullMode;
525 rasterDesc.FrontCounterClockwise = FALSE;
526 rasterDesc.DepthBias = key.depthBias;
527 rasterDesc.DepthBiasClamp = key.depthBiasClamp;
528 rasterDesc.SlopeScaledDepthBias = key.slopeScaledDepthBias;
529 rasterDesc.DepthClipEnable = key.depthClip;
530 rasterDesc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
531 psoDesc.RasterizerState = rasterDesc;
532
533 const bool hasColor = key.rtvFormat != DXGI_FORMAT_UNKNOWN;
534 D3D12_RENDER_TARGET_BLEND_DESC rtBlend = {};
535 rtBlend.BlendEnable = key.blendEnable;
536 rtBlend.SrcBlend =
537 key.blendEnable ? D3D12_BLEND_SRC_ALPHA : D3D12_BLEND_ONE;
538 rtBlend.DestBlend =
539 key.blendEnable ? D3D12_BLEND_INV_SRC_ALPHA : D3D12_BLEND_ZERO;
540 rtBlend.BlendOp = D3D12_BLEND_OP_ADD;
541 rtBlend.SrcBlendAlpha = D3D12_BLEND_ONE;
542 rtBlend.DestBlendAlpha =
543 key.blendEnable ? D3D12_BLEND_INV_SRC_ALPHA : D3D12_BLEND_ZERO;
544 rtBlend.BlendOpAlpha = D3D12_BLEND_OP_ADD;
545 rtBlend.LogicOp = D3D12_LOGIC_OP_NOOP;
546 rtBlend.RenderTargetWriteMask = hasColor ? D3D12_COLOR_WRITE_ENABLE_ALL : 0;
547 for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
548 psoDesc.BlendState.RenderTarget[i] = rtBlend;
549
550 psoDesc.DepthStencilState.DepthEnable = TRUE;
551 psoDesc.DepthStencilState.DepthWriteMask = key.depthWrite;
552 psoDesc.DepthStencilState.DepthFunc = key.depthFunc;
553 psoDesc.DepthStencilState.StencilEnable = FALSE;
554
555 psoDesc.SampleMask = UINT_MAX;
556 psoDesc.PrimitiveTopologyType = key.topology;
557 psoDesc.NumRenderTargets = hasColor ? 1 : 0;
558 psoDesc.RTVFormats[0] = key.rtvFormat;
559 psoDesc.DSVFormat = key.dsvFormat;
560 psoDesc.SampleDesc.Count = 1;
561
562 Microsoft::WRL::ComPtr<ID3D12PipelineState> pso;
563 HRESULT hr =
564 device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pso));
565 if (FAILED(hr)) {
566 SLEAK_ERROR("Failed to create pipeline state! HRESULT: 0x{:08X}",
567 static_cast<unsigned int>(hr));
568 return nullptr;
569 }
570
571 m_pipelineCache.emplace_back(key, pso);
572 return pso.Get();
573}
574
575bool DirectX12Renderer::CreateShadowPassPSO() {
576 if (m_shadowPassPSO) return true;
577
578 if (!m_shadowVSCompiled) {
579 m_shadowVSCompiled = true;
580 m_shadowVSBlob = CompileStage(
581 L"assets/shaders/default_shader_dx12.hlsl", "VS_Main", "vs_5_0");
582 }
583 ID3DBlob* vs = m_shadowVSBlob ? m_shadowVSBlob.Get() : m_cachedVSBlob.Get();
584 if (!vs) {
585 SLEAK_WARN("CreateShadowPassPSO: no vertex shader available yet");
586 return false;
587 }
588
589 PipelineKey key;
590 key.vsHash = HashBytecode(vs);
591 key.cullMode = D3D12_CULL_MODE_NONE;
592 key.depthBias = 1000;
593 key.depthBiasClamp = 0.01f;
594 key.slopeScaledDepthBias = 2.0f;
595 key.rtvFormat = DXGI_FORMAT_UNKNOWN;
596
597 m_shadowPassPSO = GetPipeline(key, vs, nullptr);
598 if (!m_shadowPassPSO) return false;
599 SLEAK_INFO("D3D12 shadow pass PSO created");
600 return true;
601}
602
603void DirectX12Renderer::BeginRender() {
604 frameIndex = swapChain->GetCurrentBackBufferIndex();
605
606 // Commit staged lightVP before shadow pass so shadow + main agree.
607 if (m_hasPendingLightVP) {
608 memcpy(m_lightVP, m_pendingLightVP, sizeof(m_lightVP));
609 }
610
611 // Only the frame that last recorded into this slot has to be finished.
612 // Everything the CPU rewrites per frame lives in per-slot memory.
613 const UINT64 slotFence = fenceValues[frameIndex];
614 if (fence->GetCompletedValue() < slotFence) {
615 fence->SetEventOnCompletion(slotFence, fenceEvent);
616 WaitForSingleObject(fenceEvent, INFINITE);
617 }
618
619 DirectX12Buffer::ProcessDeferredCleanup(fence->GetCompletedValue());
620 m_uploader.Retire();
621 m_uploader.Submit();
622 m_uploadRing.BeginFrame(frameIndex);
623 ++m_frameSerial;
624 m_frameActive = true;
625 m_shaderPipeline = nullptr;
626 m_passPipeline = nullptr;
627
628 // Reset the command allocator and command list for this frame
629 commandAllocators[frameIndex]->Reset();
630 commandList->Reset(commandAllocators[frameIndex].Get(),
631 m_defaultPipelineState);
632
633 if (!m_defaultPipelineState) {
634 SLEAK_WARN("BeginRender: no pipeline state yet, nothing will draw");
635 }
636
637 // Set root signature and viewport/scissor
638 commandList->SetGraphicsRootSignature(rootSignature.Get());
639
640 // Bind the shared SRV heap ONCE for the entire frame — never switch heaps
641 ID3D12DescriptorHeap* srvHeaps[] = {m_sharedSrvHeap.Get()};
642 commandList->SetDescriptorHeaps(1, srvHeaps);
643
644 commandList->RSSetViewports(1, &viewport);
645 commandList->RSSetScissorRects(1, &scissorRect);
646
647 // Transition back buffer to render target state
648 D3D12_RESOURCE_BARRIER barrier = {};
649 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
650 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
651 barrier.Transition.pResource = renderTargets[frameIndex].Get();
652 barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
653 barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
654 barrier.Transition.Subresource =
655 D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
656 commandList->ResourceBarrier(1, &barrier);
657
658 // Clear render target
659 const float clearColor[] = {0.39f, 0.58f, 0.93f, 1.0f};
660 D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle =
661 rtvHeap->GetCPUDescriptorHandleForHeapStart();
662 rtvHandle.ptr += frameIndex * rtvDescriptorSize;
663
664 D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle =
665 dsvHeap->GetCPUDescriptorHandleForHeapStart();
666
667 commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
668 commandList->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH,
669 1.0f, 0, 0, nullptr);
670
671 // Shadow pass before main rendering
672 if (m_shadowPassEnabled) {
673 RenderShadowPass();
674 if (m_defaultPipelineState)
675 commandList->SetPipelineState(m_defaultPipelineState);
676 }
677
678 // Set render targets
679 commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);
680 // Restore viewport/scissor after shadow pass
681 commandList->RSSetViewports(1, &viewport);
682 commandList->RSSetScissorRects(1, &scissorRect);
683
684 // Set primitive topology
685 commandList->IASetPrimitiveTopology(
686 D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
687
688 // Re-set root signature after shadow pass (SetGraphicsRootSignature
689 // invalidates all root parameter bindings, so we must rebind everything).
690 commandList->SetGraphicsRootSignature(rootSignature.Get());
691 {
692 ID3D12DescriptorHeap* heaps[] = {m_sharedSrvHeap.Get()};
693 commandList->SetDescriptorHeaps(1, heaps);
694 }
695
696 // Root param 2: SRV table at t0 — default white texture
697 if (m_defaultTexture) {
698 m_defaultTexture->Bind(0);
699 }
700
701 // Root param 3: CBV at b2, light/shadow constants
702 BindLightConstants();
703
704 // Root param 4: SRV table at t3 — shadow map
705 if (m_shadowMapCreated) {
706 commandList->SetGraphicsRootDescriptorTable(
707 4, GetSharedSrvGPUHandle(m_shadowSrvIndex));
708 }
709
710 bImFrameActive = false;
711 if (bImInitialized) {
712 ImGui_ImplDX12_NewFrame();
713 ImGui_ImplSDL3_NewFrame();
714 // Guard against first frames before SDL has reported a valid window size
715 auto& io = ImGui::GetIO();
716 if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f) {
717 ImGui::NewFrame();
718 bImFrameActive = true;
719 }
720 }
721}
722
723void DirectX12Renderer::EndRender() {
724 if (bImFrameActive) {
725 ImGui::Render();
726 // Shared SRV heap is already bound from BeginRender — no heap switch needed
727 ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(),
728 commandList.Get());
729 }
730
731 // Transition back buffer to present state
732 D3D12_RESOURCE_BARRIER barrier = {};
733 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
734 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
735 barrier.Transition.pResource = renderTargets[frameIndex].Get();
736 barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
737 barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
738 barrier.Transition.Subresource =
739 D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
740 commandList->ResourceBarrier(1, &barrier);
741
742 // Uploads recorded this frame must land before the draws that use them
743 commandList->Close();
744 m_uploader.Submit();
745 ID3D12CommandList* ppCommandLists[] = {commandList.Get()};
746 commandQueue->ExecuteCommandLists(_countof(ppCommandLists),
747 ppCommandLists);
748
749 swapChain->Present(m_vsync ? 1 : 0, 0);
750
751 // No wait here: BeginRender waits for this value only when the slot
752 // comes around again.
753 fenceValues[frameIndex] = ++m_fenceValue;
754 commandQueue->Signal(fence.Get(), m_fenceValue);
755 DirectX12Buffer::TagDeferredCleanup(m_fenceValue);
756 m_frameActive = false;
757
758 UpdateFrameMetrics();
759}
760
761void DirectX12Renderer::WaitForGPU() {
762 if (!commandQueue || !fence || !fenceEvent) return;
763
764 m_uploader.Submit();
765 const UINT64 waitValue = ++m_fenceValue;
766 commandQueue->Signal(fence.Get(), waitValue);
767
768 if (fence->GetCompletedValue() < waitValue) {
769 fence->SetEventOnCompletion(waitValue, fenceEvent);
770 WaitForSingleObject(fenceEvent, INFINITE);
771 }
772 m_uploader.Retire();
773}
774
775bool DirectX12Renderer::CreateSharedSrvHeap() {
776 D3D12_DESCRIPTOR_HEAP_DESC heapDesc{};
777 heapDesc.NumDescriptors = MAX_SRV_DESCRIPTORS;
778 heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
779 heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
780
781 HRESULT hr = device->CreateDescriptorHeap(
782 &heapDesc, IID_PPV_ARGS(&m_sharedSrvHeap));
783 if (FAILED(hr)) {
784 SLEAK_ERROR("Failed to create shared SRV descriptor heap!");
785 return false;
786 }
787 m_srvDescriptorSize = device->GetDescriptorHandleIncrementSize(
788 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
789 m_nextSrvSlot = 0;
790 return true;
791}
792
793UINT DirectX12Renderer::AllocateSRVSlot() {
794 if (m_nextSrvSlot >= MAX_SRV_DESCRIPTORS) {
795 SLEAK_ERROR("Shared SRV heap full! Increase MAX_SRV_DESCRIPTORS.");
796 return 0;
797 }
798 return m_nextSrvSlot++;
799}
800
801D3D12_CPU_DESCRIPTOR_HANDLE DirectX12Renderer::GetSharedSrvCPUHandle(UINT slot) const {
802 D3D12_CPU_DESCRIPTOR_HANDLE handle = m_sharedSrvHeap->GetCPUDescriptorHandleForHeapStart();
803 handle.ptr += static_cast<SIZE_T>(slot) * m_srvDescriptorSize;
804 return handle;
805}
806
807D3D12_GPU_DESCRIPTOR_HANDLE DirectX12Renderer::GetSharedSrvGPUHandle(UINT slot) const {
808 D3D12_GPU_DESCRIPTOR_HANDLE handle = m_sharedSrvHeap->GetGPUDescriptorHandleForHeapStart();
809 handle.ptr += static_cast<UINT64>(slot) * m_srvDescriptorSize;
810 return handle;
811}
812
813void DirectX12Renderer::Cleanup() {
814 if (!m_Initialized) return;
815
816 WaitForGPU();
817 DirectX12Buffer::TagDeferredCleanup(m_fenceValue);
818 DirectX12Buffer::ProcessDeferredCleanup(UINT64_MAX);
819
820 if (bImInitialized) {
821 ImGui_ImplDX12_Shutdown();
822 ImGui_ImplSDL3_Shutdown();
823 ImGui::DestroyContext();
824 bImInitialized = false;
825 }
826 m_uploader.Release();
827 m_uploadRing.Release();
828
829 imguiSrvHeap.Reset();
830 m_sharedSrvHeap.Reset();
831
832 m_skyboxPipelineState = nullptr;
833 m_debugLinePipelineState = nullptr;
834 m_shadowPassPSO = nullptr;
835 m_defaultPipelineState = nullptr;
836 m_shaderPipeline = nullptr;
837 m_passPipeline = nullptr;
838 m_pipelineCache.clear();
839 rootSignature.Reset();
840 depthStencilBuffer.Reset();
841 dsvHeap.Reset();
842 for (auto& rt : renderTargets) {
843 rt.Reset();
844 }
845 rtvHeap.Reset();
846 fence.Reset();
847 commandList.Reset();
848 for (UINT i = 0; i < FrameCount; i++)
849 commandAllocators[i].Reset();
850 commandQueue.Reset();
851 swapChain.Reset();
852 device.Reset();
853
854 m_Initialized = false;
855}
856
857void DirectX12Renderer::Resize(uint32_t width, uint32_t height) {
858 if (!m_Initialized || !swapChain) return;
859
860 WaitForGPU();
861
862 // Release render target references
863 for (auto& rt : renderTargets) {
864 rt.Reset();
865 }
866 depthStencilBuffer.Reset();
867 dsvHeap.Reset();
868 rtvHeap.Reset();
869
870 // Resize swap chain buffers
871 HRESULT hr = swapChain->ResizeBuffers(FrameCount, width, height,
872 DXGI_FORMAT_R8G8B8A8_UNORM, 0);
873 if (FAILED(hr)) {
874 SLEAK_ERROR("Failed to resize swap chain buffers!");
875 return;
876 }
877
878 frameIndex = swapChain->GetCurrentBackBufferIndex();
879
880 // Recreate render target views and depth stencil
881 if (!CreateRenderTargetViews() || !CreateDepthStencilView()) {
882 SLEAK_ERROR("Failed to recreate views after resize!");
883 return;
884 }
885
886 // Update viewport and scissor
887 viewport.Width = static_cast<float>(width);
888 viewport.Height = static_cast<float>(height);
889 scissorRect.right = width;
890 scissorRect.bottom = height;
891
892 SLEAK_INFO("DirectX 12 resized to {}x{}", width, height);
893}
894
895void DirectX12Renderer::Draw(uint32_t vertexCount) { Draw(vertexCount, 0); }
896
897void DirectX12Renderer::Draw(uint32_t vertexCount, uint32_t firstVertex) {
898 commandList->DrawInstanced(vertexCount, 1, firstVertex, 0);
899 DrawnVertices += vertexCount;
900 DrawnTriangles += vertexCount / 3;
901}
902
903void DirectX12Renderer::DrawIndexed(uint32_t indexCount) {
904 DrawIndexed(indexCount, 0, 0);
905}
906
907void DirectX12Renderer::DrawIndexed(uint32_t indexCount, uint32_t firstIndex,
908 int32_t baseVertex) {
909 commandList->DrawIndexedInstanced(indexCount, 1, firstIndex, baseVertex, 0);
910 DrawnVertices += indexCount;
911 DrawnTriangles += indexCount / 3;
912}
913
914void DirectX12Renderer::DrawInstance(uint32_t instanceCount,
915 uint32_t vertexPerInstance) {
916 commandList->DrawInstanced(vertexPerInstance, instanceCount, 0, 0);
917 DrawnVertices += vertexPerInstance * instanceCount;
918 DrawnTriangles += (vertexPerInstance / 3) * instanceCount;
919}
920
921void DirectX12Renderer::DrawIndexedInstance(uint32_t instanceCount,
922 uint32_t indexPerInstance) {
923 DrawIndexedInstance(instanceCount, indexPerInstance, 0, 0);
924}
925
926void DirectX12Renderer::DrawIndexedInstance(uint32_t instanceCount,
927 uint32_t indexPerInstance,
928 uint32_t firstIndex,
929 int32_t baseVertex) {
930 commandList->DrawIndexedInstanced(indexPerInstance, instanceCount,
931 firstIndex, baseVertex, 0);
932 DrawnVertices += indexPerInstance * instanceCount;
933 DrawnTriangles += (indexPerInstance / 3) * instanceCount;
934}
935
936void DirectX12Renderer::SetRenderFace(RenderFace face) {
937 Face = face;
938 ConfigureRenderFace();
939}
940
941void DirectX12Renderer::SetRenderMode(RenderMode mode) {
942 Mode = mode;
943 ConfigureRenderMode();
944}
945
946void DirectX12Renderer::SetViewport(float x, float y, float width,
947 float height, float minDepth,
948 float maxDepth) {
949 viewport.TopLeftX = x;
950 viewport.TopLeftY = y;
951 viewport.Width = width;
952 viewport.Height = height;
953 viewport.MinDepth = minDepth;
954 viewport.MaxDepth = maxDepth;
955
956 scissorRect.left = static_cast<LONG>(x);
957 scissorRect.top = static_cast<LONG>(y);
958 scissorRect.right = static_cast<LONG>(x + width);
959 scissorRect.bottom = static_cast<LONG>(y + height);
960
961 if (commandList) {
962 commandList->RSSetViewports(1, &viewport);
963 commandList->RSSetScissorRects(1, &scissorRect);
964 }
965}
966
967void DirectX12Renderer::ClearRenderTarget(float r, float g, float b,
968 float a) {
969 const float clearColor[] = {r, g, b, a};
970 D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle =
971 rtvHeap->GetCPUDescriptorHandleForHeapStart();
972 rtvHandle.ptr += frameIndex * rtvDescriptorSize;
973 commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
974}
975
976void DirectX12Renderer::ClearDepthStencil(bool clearDepth,
977 bool clearStencil, float depth,
978 uint8_t stencil) {
979 D3D12_CLEAR_FLAGS flags = {};
980 if (clearDepth)
981 flags = static_cast<D3D12_CLEAR_FLAGS>(
982 flags | D3D12_CLEAR_FLAG_DEPTH);
983 if (clearStencil)
984 flags = static_cast<D3D12_CLEAR_FLAGS>(
985 flags | D3D12_CLEAR_FLAG_STENCIL);
986
987 if (flags && dsvHeap) {
988 D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle =
989 dsvHeap->GetCPUDescriptorHandleForHeapStart();
990 commandList->ClearDepthStencilView(dsvHandle, flags, depth,
991 stencil, 0, nullptr);
992 }
993}
994
995void DirectX12Renderer::BindVertexBuffer(RefPtr<BufferBase> buffer,
996 uint32_t slot) {
997 auto* dx12Buf = static_cast<DirectX12Buffer*>(buffer.get());
998 if (!dx12Buf) return;
999
1000 D3D12_VERTEX_BUFFER_VIEW vbView = {};
1001 vbView.BufferLocation =
1002 dx12Buf->GetD3DBuffer()->GetGPUVirtualAddress();
1003 vbView.StrideInBytes = sizeof(Sleak::Vertex);
1004 vbView.SizeInBytes = static_cast<UINT>(dx12Buf->GetSize());
1005
1006 commandList->IASetVertexBuffers(slot, 1, &vbView);
1007}
1008
1009void DirectX12Renderer::BindIndexBuffer(RefPtr<BufferBase> buffer,
1010 uint32_t slot) {
1011 auto* dx12Buf = static_cast<DirectX12Buffer*>(buffer.get());
1012 if (!dx12Buf) return;
1013
1014 D3D12_INDEX_BUFFER_VIEW ibView = {};
1015 ibView.BufferLocation =
1016 dx12Buf->GetD3DBuffer()->GetGPUVirtualAddress();
1017 ibView.Format = DXGI_FORMAT_R32_UINT; // IndexType is uint32_t
1018 ibView.SizeInBytes = static_cast<UINT>(dx12Buf->GetSize());
1019
1020 commandList->IASetIndexBuffer(&ibView);
1021}
1022
1023void DirectX12Renderer::BindConstantBuffer(RefPtr<BufferBase> buffer,
1024 uint32_t slot) {
1025 auto* dx12Buf = static_cast<DirectX12Buffer*>(buffer.get());
1026 if (!dx12Buf) return;
1027
1028 // Shadow pass: use a dedicated shadow CB with LightVP*World for slot 0
1029 if (m_inShadowPass && slot == 0 && dx12Buf->GetCPUShadowCopySize() >= 128) {
1030 const float* srcWorld = reinterpret_cast<const float*>(
1031 static_cast<const char*>(dx12Buf->GetCPUShadowCopy()) + 64);
1032
1033 float shadowPC[32];
1034 for (int r = 0; r < 4; ++r) {
1035 for (int c = 0; c < 4; ++c) {
1036 float sum = 0.0f;
1037 for (int k = 0; k < 4; ++k) {
1038 sum += srcWorld[r * 4 + k] * m_lightVP[k * 4 + c];
1039 }
1040 shadowPC[r * 4 + c] = sum;
1041 }
1042 }
1043 memcpy(&shadowPC[16], srcWorld, sizeof(float) * 16);
1044
1045 DirectX12UploadRing::Allocation alloc;
1046 if (!m_uploadRing.Allocate(sizeof(shadowPC), alloc)) return;
1047 memcpy(alloc.cpu, shadowPC, sizeof(shadowPC));
1048 commandList->SetGraphicsRootConstantBufferView(slot, alloc.gpu);
1049 return;
1050 }
1051
1052 // Constant contents are copied into this frame's ring slice on the
1053 // first bind after an update, so frames in flight keep their own copy.
1054 if (const void* data = dx12Buf->GetConstantData()) {
1055 D3D12_GPU_VIRTUAL_ADDRESS address =
1056 dx12Buf->GetFrameAddress(m_frameSerial);
1057 if (!address) {
1058 const size_t size = dx12Buf->GetConstantDataSize();
1059 DirectX12UploadRing::Allocation alloc;
1060 if (m_uploadRing.Allocate(size, alloc)) {
1061 memcpy(alloc.cpu, data, size);
1062 address = alloc.gpu;
1063 dx12Buf->SetFrameAddress(m_frameSerial, address);
1064 }
1065 }
1066 if (address) {
1067 commandList->SetGraphicsRootConstantBufferView(slot, address);
1068 return;
1069 }
1070 }
1071
1072 if (!dx12Buf->GetD3DBuffer()) return;
1073 commandList->SetGraphicsRootConstantBufferView(
1074 slot, dx12Buf->GetD3DBuffer()->GetGPUVirtualAddress());
1075}
1076
1077BufferBase* DirectX12Renderer::CreateBuffer(BufferType Type, uint32_t size,
1078 void* data) {
1079 assert(size > 0);
1080 auto* buffer = new DirectX12Buffer(device.Get(), commandQueue.Get(), size,
1081 Type, &m_uploader);
1082 if (!buffer->Initialize(data)) {
1083 delete buffer;
1084 return nullptr;
1085 }
1086 return buffer;
1087}
1088
1089Shader* DirectX12Renderer::CreateShader(const std::string& shaderSource) {
1090 auto* shader = new DirectX12Shader(device.Get());
1091 if (!shader->compile(shaderSource)) {
1092 delete shader;
1093 return nullptr;
1094 }
1095
1096 ID3DBlob* vs = shader->getVertexShaderBlob();
1097 ID3DBlob* ps = shader->getPixelShaderBlob();
1098 PipelineKey key;
1099 key.vsHash = HashBytecode(vs);
1100 key.psHash = HashBytecode(ps);
1101 if (ID3D12PipelineState* pso = GetPipeline(key, vs, ps)) {
1102 shader->SetPipelineState(
1103 Microsoft::WRL::ComPtr<ID3D12PipelineState>(pso));
1104 if (!m_defaultPipelineState) {
1105 m_defaultPipelineState = pso;
1106 m_cachedVSBlob = vs;
1107 }
1108 }
1109 shader->SetCommandList(commandList.Get());
1110 shader->SetRenderer(this);
1111 return shader;
1112}
1113
1114Texture* DirectX12Renderer::CreateTexture(
1115 const std::string& TexturePath) {
1116 auto* texture = new DirectX12Texture(device.Get(), commandQueue.Get(),
1117 commandList.Get(), &m_uploader);
1118 if (!texture->LoadFromFile(TexturePath)) {
1119 delete texture;
1120 return nullptr;
1121 }
1122 // Place the SRV into the shared heap so we never switch heaps at draw time
1123 UINT slot = AllocateSRVSlot();
1124 texture->CreateSRVIntoHandle(
1125 texture->GetFormat() == TextureFormat::RGBA8 ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R8G8B8A8_UNORM,
1126 GetSharedSrvCPUHandle(slot));
1127 texture->SetSharedSrvGPUHandle(GetSharedSrvGPUHandle(slot));
1128 return texture;
1129}
1130
1131Texture* DirectX12Renderer::CreateTextureFromData(uint32_t width,
1132 uint32_t height,
1133 void* data) {
1134 auto* texture = new DirectX12Texture(device.Get(), commandQueue.Get(),
1135 commandList.Get(), &m_uploader);
1136 if (!texture->LoadFromMemory(data, width, height,
1137 TextureFormat::RGBA8)) {
1138 delete texture;
1139 return nullptr;
1140 }
1141 // Place the SRV into the shared heap
1142 UINT slot = AllocateSRVSlot();
1143 texture->CreateSRVIntoHandle(DXGI_FORMAT_R8G8B8A8_UNORM,
1144 GetSharedSrvCPUHandle(slot));
1145 texture->SetSharedSrvGPUHandle(GetSharedSrvGPUHandle(slot));
1146 return texture;
1147}
1148
1149Texture* DirectX12Renderer::CreateCubemapTexture(
1150 const std::array<std::string, 6>& facePaths) {
1151 auto* texture = new DirectX12CubemapTexture(
1152 device.Get(), commandQueue.Get(), &m_uploader);
1153 if (!texture->LoadCubemap(facePaths)) {
1154 delete texture;
1155 return nullptr;
1156 }
1157 UINT slot = AllocateSRVSlot();
1158 texture->CreateSRVIntoHandle(GetSharedSrvCPUHandle(slot));
1159 texture->SetSharedSrvGPUHandle(GetSharedSrvGPUHandle(slot));
1160 return texture;
1161}
1162
1163Texture* DirectX12Renderer::CreateCubemapTextureFromPanorama(
1164 const std::string& panoramaPath) {
1165 auto* texture = new DirectX12CubemapTexture(
1166 device.Get(), commandQueue.Get(), &m_uploader);
1167 if (!texture->LoadEquirectangular(panoramaPath)) {
1168 delete texture;
1169 return nullptr;
1170 }
1171 UINT slot = AllocateSRVSlot();
1172 texture->CreateSRVIntoHandle(GetSharedSrvCPUHandle(slot));
1173 texture->SetSharedSrvGPUHandle(GetSharedSrvGPUHandle(slot));
1174 return texture;
1175}
1176
1177void DirectX12Renderer::BindTexture(RefPtr<Sleak::Texture> texture,
1178 uint32_t slot) {
1179 if (!texture.IsValid() || !commandList) return;
1180
1181 if (texture->GetType() == TextureType::TextureCube) {
1182 static_cast<DirectX12CubemapTexture*>(texture.get())
1183 ->BindToCommandList(commandList.Get(), 2);
1184 } else {
1185 static_cast<DirectX12Texture*>(texture.get())
1186 ->BindToCommandList(commandList.Get(), 2);
1187 }
1188}
1189
1190void DirectX12Renderer::BindTextureRaw(Sleak::Texture* texture, uint32_t slot) {
1191 if (!texture || !commandList) return;
1192
1193 if (texture->GetType() == TextureType::TextureCube) {
1194 static_cast<DirectX12CubemapTexture*>(texture)
1195 ->BindToCommandList(commandList.Get(), 2);
1196 return;
1197 }
1198
1199 auto* dx12Tex = static_cast<DirectX12Texture*>(texture);
1200 if (dx12Tex) {
1201 dx12Tex->BindToCommandList(commandList.Get(), 2);
1202 }
1203}
1204
1205void DirectX12Renderer::BindLightConstants() {
1206 DirectX12UploadRing::Allocation alloc;
1207 if (!m_uploadRing.Allocate(m_lightData.size(), alloc)) return;
1208 memcpy(alloc.cpu, m_lightData.data(), m_lightData.size());
1209 commandList->SetGraphicsRootConstantBufferView(3, alloc.gpu);
1210}
1211
1212void DirectX12Renderer::UpdateShadowLightUBO(const void* data, uint32_t size) {
1213 if (!data) return;
1214
1215 size_t copySize = size;
1216 if (copySize > m_lightData.size()) copySize = m_lightData.size();
1217 memcpy(m_lightData.data(), data, copySize);
1218
1219 // The ring slice bound at BeginRender holds last frame's values
1220 if (m_frameActive) BindLightConstants();
1221}
1222
1223void DirectX12Renderer::SetLightVP(const float* mat) {
1224 if (mat) {
1225 memcpy(m_pendingLightVP, mat, sizeof(m_pendingLightVP));
1226 m_hasPendingLightVP = true;
1227 }
1228}
1229
1230bool DirectX12Renderer::CreateShadowMapResources() {
1231 if (m_shadowMapCreated) return true;
1232
1233 // Create shadow DSV heap
1234 D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
1235 dsvHeapDesc.NumDescriptors = 1;
1236 dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
1237 dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
1238 if (FAILED(device->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&m_shadowDsvHeap)))) {
1239 SLEAK_ERROR("Failed to create shadow DSV heap!");
1240 return false;
1241 }
1242 m_shadowDsvHandle = m_shadowDsvHeap->GetCPUDescriptorHandleForHeapStart();
1243
1244 // Create shadow depth texture
1245 D3D12_RESOURCE_DESC depthDesc = {};
1246 depthDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
1247 depthDesc.Width = m_shadowMapResolution;
1248 depthDesc.Height = m_shadowMapResolution;
1249 depthDesc.DepthOrArraySize = 1;
1250 depthDesc.MipLevels = 1;
1251 depthDesc.Format = DXGI_FORMAT_R32_TYPELESS;
1252 depthDesc.SampleDesc.Count = 1;
1253 depthDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
1254
1255 D3D12_CLEAR_VALUE clearValue = {};
1256 clearValue.Format = DXGI_FORMAT_D32_FLOAT;
1257 clearValue.DepthStencil.Depth = 1.0f;
1258
1259 D3D12_HEAP_PROPERTIES heapProps = {};
1260 heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
1261
1262 if (FAILED(device->CreateCommittedResource(
1263 &heapProps, D3D12_HEAP_FLAG_NONE, &depthDesc,
1264 D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, &clearValue,
1265 IID_PPV_ARGS(&m_shadowDepthBuffer)))) {
1266 SLEAK_ERROR("Failed to create shadow depth buffer!");
1267 return false;
1268 }
1269
1270 // Create DSV
1271 D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
1272 dsvDesc.Format = DXGI_FORMAT_D32_FLOAT;
1273 dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
1274 device->CreateDepthStencilView(m_shadowDepthBuffer.Get(), &dsvDesc, m_shadowDsvHandle);
1275
1276 // Create SRV in shared heap
1277 m_shadowSrvIndex = AllocateSRVSlot();
1278 D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
1279 srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
1280 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
1281 srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
1282 srvDesc.Texture2D.MipLevels = 1;
1283 device->CreateShaderResourceView(m_shadowDepthBuffer.Get(), &srvDesc,
1284 GetSharedSrvCPUHandle(m_shadowSrvIndex));
1285
1286 m_shadowMapCreated = true;
1287 m_shadowResourcesCreated = true;
1288 SLEAK_INFO("D3D12 shadow map resources created ({}x{})", m_shadowMapResolution, m_shadowMapResolution);
1289 return true;
1290}
1291
1292void DirectX12Renderer::RenderShadowPass() {
1293 // Skip if no cached draws — preserve previous frame's shadow map
1294 auto* queue = RenderCommandQueue::GetInstance();
1295 if (!queue || !queue->HasCachedShadowDraws()) return;
1296
1297 if (!m_shadowMapCreated) {
1298 if (!CreateShadowMapResources()) return;
1299 }
1300 if (!CreateShadowPassPSO()) return;
1301
1302 // Transition shadow buffer to depth write
1303 D3D12_RESOURCE_BARRIER barrier = {};
1304 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
1305 barrier.Transition.pResource = m_shadowDepthBuffer.Get();
1306 barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
1307 barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_DEPTH_WRITE;
1308 barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
1309 commandList->ResourceBarrier(1, &barrier);
1310
1311 // Set shadow DSV (no RTV)
1312 commandList->OMSetRenderTargets(0, nullptr, FALSE, &m_shadowDsvHandle);
1313 commandList->ClearDepthStencilView(m_shadowDsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
1314
1315 // Set shadow viewport
1316 D3D12_VIEWPORT shadowViewport = {};
1317 shadowViewport.Width = static_cast<float>(m_shadowMapResolution);
1318 shadowViewport.Height = static_cast<float>(m_shadowMapResolution);
1319 shadowViewport.MinDepth = 0.0f;
1320 shadowViewport.MaxDepth = 1.0f;
1321 commandList->RSSetViewports(1, &shadowViewport);
1322
1323 D3D12_RECT shadowScissor = {};
1324 shadowScissor.right = m_shadowMapResolution;
1325 shadowScissor.bottom = m_shadowMapResolution;
1326 commandList->RSSetScissorRects(1, &shadowScissor);
1327
1328 // Depth-only shadow PSO (no PS, no RTV, CULL_NONE + depth bias)
1329 commandList->SetPipelineState(m_shadowPassPSO);
1330 commandList->SetGraphicsRootSignature(rootSignature.Get());
1331 commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
1332
1333 // Re-bind SRV heap
1334 ID3D12DescriptorHeap* srvHeaps[] = {m_sharedSrvHeap.Get()};
1335 commandList->SetDescriptorHeaps(1, srvHeaps);
1336
1337 // Bind default texture so draws with texture lookups don't crash
1338 if (m_defaultTexture) {
1339 m_defaultTexture->Bind(0);
1340 }
1341 BindLightConstants();
1342
1343 // Execute shadow draw commands
1344 m_inShadowPass = true;
1345 if (queue) {
1346 queue->ExecuteShadowPass(this);
1347 }
1348 m_inShadowPass = false;
1349
1350 // Transition back to shader resource
1351 barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_DEPTH_WRITE;
1352 barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
1353 commandList->ResourceBarrier(1, &barrier);
1354}
1355
1356bool DirectX12Renderer::CreateSkyboxPipelineState() {
1357 if (m_skyboxPipelineState) return true;
1358 if (m_skyboxPipelineFailed) return false;
1359 m_skyboxPipelineFailed = true;
1360
1361 auto vs =
1362 CompileStage(L"assets/shaders/skybox_dx12.hlsl", "VS_Main", "vs_5_0");
1363 auto ps =
1364 CompileStage(L"assets/shaders/skybox_dx12.hlsl", "PS_Main", "ps_5_0");
1365 if (!vs || !ps) return false;
1366
1367 // No culling, depth write off with LEQUAL, and depth clip off so the
1368 // .xyww trick at z=1.0 is not clipped
1369 PipelineKey key;
1370 key.vsHash = HashBytecode(vs.Get());
1371 key.psHash = HashBytecode(ps.Get());
1372 key.cullMode = D3D12_CULL_MODE_NONE;
1373 key.depthClip = FALSE;
1374 key.depthWrite = D3D12_DEPTH_WRITE_MASK_ZERO;
1375 key.depthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
1376
1377 m_skyboxPipelineState = GetPipeline(key, vs.Get(), ps.Get());
1378 if (!m_skyboxPipelineState) return false;
1379
1380 m_skyboxPipelineFailed = false;
1381 SLEAK_INFO("DirectX 12 skybox pipeline state created successfully");
1382 return true;
1383}
1384
1385void DirectX12Renderer::BindShaderPipeline(ID3D12PipelineState* pso) {
1386 if (!pso || !commandList) return;
1387 m_shaderPipeline = pso;
1388 if (m_passPipeline) return;
1389 commandList->SetPipelineState(pso);
1390}
1391
1392void DirectX12Renderer::RestoreShaderPipeline() {
1393 ID3D12PipelineState* pso =
1394 m_shaderPipeline ? m_shaderPipeline : m_defaultPipelineState;
1395 if (pso) commandList->SetPipelineState(pso);
1396}
1397
1398void DirectX12Renderer::BeginSkyboxPass() {
1399 if (!CreateSkyboxPipelineState()) {
1400 SLEAK_WARN("BeginSkyboxPass: failed to create skybox PSO");
1401 return;
1402 }
1403 m_passPipeline = m_skyboxPipelineState;
1404 commandList->SetPipelineState(m_skyboxPipelineState);
1405}
1406
1407void DirectX12Renderer::EndSkyboxPass() {
1408 m_passPipeline = nullptr;
1409 RestoreShaderPipeline();
1410}
1411
1412bool DirectX12Renderer::CreateDebugLinePipelineState() {
1413 if (m_debugLinePipelineState) return true;
1414 if (m_debugLinePipelineFailed) return false;
1415 m_debugLinePipelineFailed = true;
1416
1417 auto vs = CompileStage(L"assets/shaders/debug_line_dx12.hlsl", "VS_Main",
1418 "vs_5_0");
1419 auto ps = CompileStage(L"assets/shaders/debug_line_dx12.hlsl", "PS_Main",
1420 "ps_5_0");
1421 if (!vs || !ps) return false;
1422
1423 PipelineKey key;
1424 key.vsHash = HashBytecode(vs.Get());
1425 key.psHash = HashBytecode(ps.Get());
1426 key.cullMode = D3D12_CULL_MODE_NONE;
1427 key.depthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
1428 key.topology = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;
1429
1430 m_debugLinePipelineState = GetPipeline(key, vs.Get(), ps.Get());
1431 if (!m_debugLinePipelineState) return false;
1432
1433 m_debugLinePipelineFailed = false;
1434 SLEAK_INFO("DirectX 12 debug line pipeline state created successfully");
1435 return true;
1436}
1437
1438void DirectX12Renderer::BeginDebugLinePass() {
1439 if (!CreateDebugLinePipelineState()) {
1440 SLEAK_WARN("BeginDebugLinePass: failed to create debug line PSO");
1441 return;
1442 }
1443 m_passPipeline = m_debugLinePipelineState;
1444 commandList->SetPipelineState(m_debugLinePipelineState);
1445 commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_LINELIST);
1446}
1447
1448void DirectX12Renderer::EndDebugLinePass() {
1449 m_passPipeline = nullptr;
1450 RestoreShaderPipeline();
1451 commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
1452}
1453
1454void DirectX12Renderer::ConfigureRenderMode() {
1455 // In DX12, fill mode and topology are baked into the PSO.
1456 // For now, we only support the default solid fill + triangle list.
1457 // A full implementation would cache multiple PSO variants.
1458}
1459
1460void DirectX12Renderer::ConfigureRenderFace() {
1461 // In DX12, cull mode is baked into the PSO.
1462 // A full implementation would cache multiple PSO variants.
1463}
1464
1465void DirectX12Renderer::EnumerateDevices(
1466 Microsoft::WRL::ComPtr<IDXGIFactory4> factory) {
1467 UINT adapter_index = 0;
1468 IDXGIAdapter1* tempadapter;
1469 while (factory->EnumAdapters1(adapter_index, &tempadapter) !=
1470 DXGI_ERROR_NOT_FOUND) {
1471 DXGI_ADAPTER_DESC1 desc;
1472 tempadapter->GetDesc1(&desc);
1473
1474#ifdef _DEBUG
1475 std::wstring wgpu_name = L"Found GPU: ";
1476 wgpu_name += desc.Description;
1477 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
1478 std::string gpu_name = converter.to_bytes(wgpu_name);
1479 SLEAK_INFO(gpu_name);
1480#endif
1481
1482 if (adapter == nullptr &&
1483 (desc.VendorId == 0x10DE || desc.VendorId == 0x1002)) {
1484 adapter.Attach(tempadapter);
1485 } else {
1486 tempadapter->Release();
1487 }
1488
1489 adapter_index++;
1490 }
1491
1492 SLEAK_INFO("Found {} GPUs", adapter_index);
1493}
1494
1495bool DirectX12Renderer::IsSupport() {
1496 Microsoft::WRL::ComPtr<ID3D12Device> testDevice;
1497 HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0,
1498 IID_PPV_ARGS(&testDevice));
1499 return SUCCEEDED(hr);
1500}
1501
1502bool DirectX12Renderer::CreateImGUI() {
1503 if (!device || !commandQueue || !m_sharedSrvHeap)
1504 return false;
1505
1506 // Allocate a slot in the shared SRV heap for ImGui's font texture
1507 UINT imguiSlot = AllocateSRVSlot();
1508 D3D12_CPU_DESCRIPTOR_HANDLE imguiCpuHandle = GetSharedSrvCPUHandle(imguiSlot);
1509 D3D12_GPU_DESCRIPTOR_HANDLE imguiGpuHandle = GetSharedSrvGPUHandle(imguiSlot);
1510
1511 IMGUI_CHECKVERSION();
1512 ImGui::CreateContext();
1513 ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
1514 ImGui::StyleColorsDark();
1515
1516 if (!ImGui_ImplSDL3_InitForD3D(window->GetSDLWindow()))
1517 return false;
1518
1519 ImGui_ImplDX12_InitInfo imguiInit;
1520 imguiInit.Device = device.Get();
1521 imguiInit.CommandQueue = commandQueue.Get();
1522 imguiInit.NumFramesInFlight = FrameCount;
1523 imguiInit.RTVFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
1524 imguiInit.DSVFormat = DXGI_FORMAT_D32_FLOAT;
1525 imguiInit.SrvDescriptorHeap = m_sharedSrvHeap.Get();
1526 imguiInit.LegacySingleSrvCpuDescriptor = imguiCpuHandle;
1527 imguiInit.LegacySingleSrvGpuDescriptor = imguiGpuHandle;
1528
1529 if (!ImGui_ImplDX12_Init(&imguiInit)) return false;
1530
1531 bImInitialized = true;
1532 return true;
1533}
1534
1535} // namespace RenderEngine
1536} // namespace Sleak
1537
1538#endif
int width
int height
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
#define SLEAK_WARN(...)
Definition Logger.hpp:21
virtual TextureType GetType() const =0
Backend-facing rendering layer shared by the four graphics backends.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
class ENGINE_API Window