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