SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
DirectX12Buffer.cpp
Go to the documentation of this file.
3#include <cassert>
4
5namespace Sleak {
6namespace RenderEngine {
7
8DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, ID3D12CommandQueue* queue, size_t size, BufferType type)
9 : BufferBase()
10{
11 assert(device != nullptr);
12 m_device = device;
13 m_commandQueue = queue;
14 Size = size;
15 Type = type; // Set the buffer type in the base class
16 ConfigureFromBufferType(type);
17}
18
19DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, size_t size,
20 D3D12_HEAP_TYPE heapType, D3D12_RESOURCE_STATES resourceState)
21 : BufferBase(),
22 m_heapType(heapType),
23 m_resourceState(resourceState)
24{
25 assert(device != nullptr);
26 m_device = device;
27 Size = size;
28}
29
31 : BufferBase(std::move(other)),
32 m_device(std::move(other.m_device)),
33 m_commandQueue(other.m_commandQueue),
34 m_buffer(std::move(other.m_buffer)),
35 m_uploadBuffer(std::move(other.m_uploadBuffer)),
36 m_commandAllocator(std::move(other.m_commandAllocator)),
37 m_commandList(std::move(other.m_commandList)),
38 m_heapType(other.m_heapType),
39 m_resourceState(other.m_resourceState),
40 m_currentState(other.m_currentState),
41 m_mappedData(other.m_mappedData)
42{
43 other.m_commandQueue = nullptr;
44 other.m_mappedData = nullptr;
45}
46
48{
49 if (this != &other) {
50 Cleanup();
51
52 BufferBase::operator=(std::move(other));
53 m_device = std::move(other.m_device);
54 m_commandQueue = other.m_commandQueue;
55 m_buffer = std::move(other.m_buffer);
56 m_uploadBuffer = std::move(other.m_uploadBuffer);
57 m_commandAllocator = std::move(other.m_commandAllocator);
58 m_commandList = std::move(other.m_commandList);
59 m_heapType = other.m_heapType;
60 m_resourceState = other.m_resourceState;
61 m_currentState = other.m_currentState;
62 m_mappedData = other.m_mappedData;
63
64 other.m_commandQueue = nullptr;
65 other.m_mappedData = nullptr;
66 }
67 return *this;
68}
69
74
75void DirectX12Buffer::ConfigureFromBufferType(BufferType type)
76{
77 switch (type) {
79 m_heapType = D3D12_HEAP_TYPE_DEFAULT;
80 m_resourceState = D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
81 break;
82
84 m_heapType = D3D12_HEAP_TYPE_DEFAULT;
85 m_resourceState = D3D12_RESOURCE_STATE_INDEX_BUFFER;
86 break;
87
89 m_heapType = D3D12_HEAP_TYPE_UPLOAD;
90 m_resourceState = D3D12_RESOURCE_STATE_GENERIC_READ;
91 break;
92
94 m_heapType = D3D12_HEAP_TYPE_DEFAULT;
95 m_resourceState = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
96 break;
97
98 default:
99 m_heapType = D3D12_HEAP_TYPE_DEFAULT;
100 m_resourceState = D3D12_RESOURCE_STATE_COMMON;
101 break;
102 }
103}
104
105D3D12_RESOURCE_DESC DirectX12Buffer::CreateBufferDesc() const
106{
107 D3D12_RESOURCE_DESC desc = {};
108 desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
109 desc.Alignment = 0;
110 desc.Width = Size;
111 desc.Height = 1;
112 desc.DepthOrArraySize = 1;
113 desc.MipLevels = 1;
114 desc.Format = DXGI_FORMAT_UNKNOWN;
115 desc.SampleDesc.Count = 1;
116 desc.SampleDesc.Quality = 0;
117 desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
118 desc.Flags = D3D12_RESOURCE_FLAG_NONE;
119 return desc;
120}
121
123 return Initialize(data, Size);
124}
125
126bool DirectX12Buffer::Initialize(const void* data, size_t size)
127{
128 if (!m_device || Size == 0)
129 return false;
130
131 if (m_buffer)
132 Cleanup();
133
134 D3D12_HEAP_PROPERTIES heapProps = {};
135 heapProps.Type = m_heapType;
136 heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
137 heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
138 heapProps.CreationNodeMask = 1;
139 heapProps.VisibleNodeMask = 1;
140
141 auto desc = CreateBufferDesc();
142 // When we have initial data for a DEFAULT heap buffer, create in
143 // COPY_DEST state so the upload copy can succeed. The post-copy
144 // barrier in CreateUploadBuffer transitions to m_resourceState.
145 D3D12_RESOURCE_STATES initialState =
146 (data && m_heapType == D3D12_HEAP_TYPE_DEFAULT)
147 ? D3D12_RESOURCE_STATE_COPY_DEST
148 : m_resourceState;
149 m_currentState = initialState;
150 HRESULT hr = m_device->CreateCommittedResource(
151 &heapProps,
152 D3D12_HEAP_FLAG_NONE,
153 &desc,
154 initialState,
155 nullptr,
156 IID_PPV_ARGS(&m_buffer));
157
158 if (FAILED(hr)) {
159 // Error handling code here
160 return false;
161 }
162
163 if (data) {
164 // If we have initial data, update the buffer
165 if (m_heapType == D3D12_HEAP_TYPE_DEFAULT) {
166 // For GPU-only buffers, we need to create an upload buffer
167 CreateUploadBuffer(data, size);
168 } else {
169 // For CPU-accessible buffers, we can directly map and update
170 Update(const_cast<void*>(data), size);
171 }
172 }
173
174 bIsInitialized = true;
175 return true;
176}
177
178bool DirectX12Buffer::CreateUploadBuffer(const void* data, size_t dataSize)
179{
180 // Create upload heap for transferring data to the default heap
181 D3D12_HEAP_PROPERTIES uploadHeapProps = {};
182 uploadHeapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
183 uploadHeapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
184 uploadHeapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
185 uploadHeapProps.CreationNodeMask = 1;
186 uploadHeapProps.VisibleNodeMask = 1;
187
188 D3D12_RESOURCE_DESC uploadDesc = CreateBufferDesc();
189
190 HRESULT hr = m_device->CreateCommittedResource(
191 &uploadHeapProps,
192 D3D12_HEAP_FLAG_NONE,
193 &uploadDesc,
194 D3D12_RESOURCE_STATE_GENERIC_READ,
195 nullptr,
196 IID_PPV_ARGS(&m_uploadBuffer));
197
198 if (FAILED(hr)) {
199 // Error handling
200 return false;
201 }
202
203 // Map the upload buffer
204 void* mappedData = nullptr;
205 hr = m_uploadBuffer->Map(0, nullptr, &mappedData);
206
207 if (FAILED(hr)) {
208 // Error handling
209 return false;
210 }
211
212 // Copy data to the upload buffer
213 memcpy(mappedData, data, dataSize);
214 m_uploadBuffer->Unmap(0, nullptr);
215
216 // Initialize command objects
217 if (!m_commandAllocator) {
218 hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
219 IID_PPV_ARGS(&m_commandAllocator));
220 if (FAILED(hr))
221 return false;
222 } else {
223 // Allocator already exists from a previous upload — wait for the GPU
224 // to finish using it before resetting. Create a temporary fence.
225 WaitForUploadComplete();
226 hr = m_commandAllocator->Reset();
227 if (FAILED(hr))
228 return false;
229 }
230
231 if (!m_commandList) {
232 hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
233 m_commandAllocator.Get(), nullptr,
234 IID_PPV_ARGS(&m_commandList));
235 if (FAILED(hr))
236 return false;
237 } else {
238 // Command list already exists in CLOSED state — reset it
239 hr = m_commandList->Reset(m_commandAllocator.Get(), nullptr);
240 if (FAILED(hr))
241 return false;
242 }
243
244 // If the buffer is NOT in COPY_DEST state (i.e. it was already uploaded
245 // once and transitioned to its target state), transition it back to
246 // COPY_DEST so the copy can succeed.
247 if (m_currentState != D3D12_RESOURCE_STATE_COPY_DEST) {
248 D3D12_RESOURCE_BARRIER barrier = {};
249 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
250 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
251 barrier.Transition.pResource = m_buffer.Get();
252 barrier.Transition.StateBefore = m_currentState;
253 barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
254 barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
255
256 m_commandList->ResourceBarrier(1, &barrier);
257 }
258
259 // Copy data from upload buffer to default buffer
260 m_commandList->CopyBufferRegion(m_buffer.Get(), 0, m_uploadBuffer.Get(), 0, dataSize);
261
262 // Transition resource to its target state
263 if (m_resourceState != D3D12_RESOURCE_STATE_COPY_DEST) {
264 D3D12_RESOURCE_BARRIER barrier = {};
265 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
266 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
267 barrier.Transition.pResource = m_buffer.Get();
268 barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
269 barrier.Transition.StateAfter = m_resourceState;
270 barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
271
272 m_commandList->ResourceBarrier(1, &barrier);
273 }
274
275 m_currentState = m_resourceState;
276
277 hr = m_commandList->Close();
278 if (FAILED(hr))
279 return false;
280
281 return true;
282}
283
285 if (!m_commandList) {
286 // Constant buffers on UPLOAD heap don't have their own
287 // command list. They are bound through the renderer's
288 // command list during ExecuteCommands.
290 return;
291 SLEAK_ERROR("Command list is null for buffer Update!");
292 return;
293 }
294
295 switch (Type) {
297 SetAsVertexBuffer(m_commandList.Get(),0, sizeof(Sleak::Vertex));
298 break;
300 SetAsIndexBuffer(m_commandList.Get(),DXGI_FORMAT_R32_UINT);
301 break;
303 SetAsConstantBuffer(m_commandList.Get(),0);
304 break;
305 default:
306 SLEAK_ERROR("Unknown buffer type passed!");
307 }
308}
309
311{
312 if (bIsMapped) {
313 Unmap();
314 }
315
316 // Defer GPU buffer destruction — may still be referenced by in-flight commands.
317 // Upload resources can be freed immediately (copy already submitted).
319 if (m_buffer) {
320 DeferCleanup(std::move(m_buffer));
321 m_buffer = nullptr;
322 }
323
324 bIsInitialized = false;
325 Size = 0;
326 Data = nullptr;
327 bIsMapped = false;
328}
329
331 m_uploadBuffer.Reset();
332 m_commandList.Reset();
333 m_commandAllocator.Reset();
334}
335
337{
338 if (!m_buffer || bIsMapped)
339 return false;
340
341 // Only upload heaps can be mapped
342 if (m_heapType != D3D12_HEAP_TYPE_UPLOAD && m_heapType != D3D12_HEAP_TYPE_READBACK)
343 return false;
344
345 HRESULT hr = m_buffer->Map(0, nullptr, &m_mappedData);
346
347 if (FAILED(hr)) {
348 // Error handling
349 return false;
350 }
351
352 Data = m_mappedData;
353 bIsMapped = true;
354 return true;
355}
356
358{
359 if (m_buffer && bIsMapped) {
360 m_buffer->Unmap(0, nullptr);
361 Data = nullptr;
362 m_mappedData = nullptr;
363 bIsMapped = false;
364 }
365}
366
367void DirectX12Buffer::Update(void* data, size_t size)
368{
369 if (!m_buffer || size > Size || !data)
370 return;
371
372 // Store CPU shadow copy for transform CBs (needed by shadow pass)
373 if (Type == BufferType::Constant && size <= 128) {
374 StoreCPUShadowCopy(data, size);
375 }
376
377 if (m_heapType == D3D12_HEAP_TYPE_UPLOAD) {
378 // CPU-accessible buffer (like constant buffers)
379 if (!bIsMapped && !Map())
380 return;
381
382 memcpy(m_mappedData, data, size);
383 // Note: For constant buffers, we typically don't unmap until the buffer is destroyed
384 // If you need different behavior, you can adjust this
385 }
386 else {
387 // GPU-only buffer (like vertex/index buffers)
388 // Use an upload buffer and command list to update the buffer
389 if (CreateUploadBuffer(data, size) && m_commandQueue) {
390 ID3D12CommandList* ppCmdLists[] = {m_commandList.Get()};
391 m_commandQueue->ExecuteCommandLists(1, ppCmdLists);
392 WaitForUploadComplete();
393 }
394 }
395}
396
397void DirectX12Buffer::SetAsVertexBuffer(ID3D12GraphicsCommandList* commandList, UINT slot, UINT stride, UINT offset) {
398 D3D12_VERTEX_BUFFER_VIEW vbView;
399 vbView.BufferLocation = m_buffer->GetGPUVirtualAddress() + offset;
400 vbView.StrideInBytes = stride;
401 vbView.SizeInBytes = static_cast<UINT>(Size - offset);
402
403 commandList->IASetVertexBuffers(slot, 1, &vbView);
404}
405
406// New method to set index buffer in a command list
407void DirectX12Buffer::SetAsIndexBuffer(ID3D12GraphicsCommandList* commandList, DXGI_FORMAT format, UINT offset) {
408 D3D12_INDEX_BUFFER_VIEW ibView;
409 ibView.BufferLocation = m_buffer->GetGPUVirtualAddress() + offset;
410 ibView.Format = format; // Typically DXGI_FORMAT_R16_UINT or DXGI_FORMAT_R32_UINT
411 ibView.SizeInBytes = static_cast<UINT>(Size - offset);
412
413 commandList->IASetIndexBuffer(&ibView);
414}
415
416// New method to bind constant buffer to root signature slot
417void DirectX12Buffer::SetAsConstantBuffer(ID3D12GraphicsCommandList* commandList, UINT rootParameterIndex) {
418 commandList->SetGraphicsRootConstantBufferView(
419 rootParameterIndex,
420 m_buffer->GetGPUVirtualAddress()
421 );
422}
423
425 return nullptr;
426}
427
428void DirectX12Buffer::WaitForUploadComplete()
429{
430 if (!m_device || !m_commandQueue)
431 return;
432
433 Microsoft::WRL::ComPtr<ID3D12Fence> tempFence;
434 HRESULT hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&tempFence));
435 if (FAILED(hr)) return;
436
437 m_commandQueue->Signal(tempFence.Get(), 1);
438
439 HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr);
440 if (!ev) return;
441
442 if (tempFence->GetCompletedValue() < 1) {
443 tempFence->SetEventOnCompletion(1, ev);
444 WaitForSingleObject(ev, INFINITE);
445 }
446 CloseHandle(ev);
447}
448
449// Static deferred cleanup queue — buffers released after GPU fence wait
450static std::vector<Microsoft::WRL::ComPtr<ID3D12Resource>> s_deferredResources;
451static std::vector<Microsoft::WRL::ComPtr<ID3D12Resource>> s_pendingResources;
452
453void DirectX12Buffer::DeferCleanup(Microsoft::WRL::ComPtr<ID3D12Resource> resource) {
454 if (resource)
455 s_pendingResources.push_back(std::move(resource));
456}
457
459 // Release resources deferred from the PREVIOUS frame (GPU guaranteed done
460 // since BeginRender waits for all fences before calling this).
461 s_deferredResources.clear();
462 // Move current pending to deferred — will be released next frame
464}
465
466} // namespace RenderEngine
467} // namespace Sleak
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
Backend-agnostic GPU buffer: vertex, index, constant, or resource view target.
void StoreCPUShadowCopy(const void *data, size_t size)
Copies up to 128 bytes into the inline shadow-copy storage, clamping oversized input.
bool Initialize(void *Data) override
Creates the default-heap resource and uploads initial data via a staging buffer, if any.
DirectX12Buffer(ID3D12Device *device, ID3D12CommandQueue *queue, size_t size, BufferType type)
static void DeferCleanup(Microsoft::WRL::ComPtr< ID3D12Resource > resource)
bool Map() override
Maps the buffer for CPU writes (upload-heap buffers only).
DirectX12Buffer & operator=(const DirectX12Buffer &)=delete
Backend-facing rendering layer shared by the four graphics backends.
static std::vector< Microsoft::WRL::ComPtr< ID3D12Resource > > s_deferredResources
BufferType
GPU buffer usage kind, drives backend binding flags and layout.
static std::vector< Microsoft::WRL::ComPtr< ID3D12Resource > > s_pendingResources
Root namespace for everything the engine exposes.
Definition Camera.hpp:10