SleakEngine 0.1.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
DirectX12Buffer.cpp
Go to the documentation of this file.
2
4#include <cassert>
5#include <cstring>
6
8
9namespace Sleak {
10namespace RenderEngine {
11
13 ID3D12CommandQueue* queue, size_t size,
14 BufferType type,
15 DirectX12UploadContext* uploader)
16 : BufferBase() {
17 assert(device != nullptr);
18 m_device = device;
19 m_commandQueue = queue;
20 m_uploader = uploader;
21 Size = size;
22 Type = type; // Set the buffer type in the base class
23 ConfigureFromBufferType(type);
24}
25
26DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, size_t size,
27 D3D12_HEAP_TYPE heapType,
28 D3D12_RESOURCE_STATES resourceState)
29 : BufferBase(), m_heapType(heapType), m_resourceState(resourceState) {
30 assert(device != nullptr);
31 m_device = device;
32 Size = size;
33}
34
36 : BufferBase(std::move(other)),
37 m_device(std::move(other.m_device)),
38 m_commandQueue(other.m_commandQueue),
39 m_uploader(other.m_uploader),
40 m_buffer(std::move(other.m_buffer)),
41 m_heapType(other.m_heapType),
42 m_resourceState(other.m_resourceState),
43 m_currentState(other.m_currentState),
44 m_mappedData(other.m_mappedData),
45 m_constantData(std::move(other.m_constantData)),
46 m_frameSerial(other.m_frameSerial),
47 m_frameAddress(other.m_frameAddress) {
48 other.m_commandQueue = nullptr;
49 other.m_uploader = nullptr;
50 other.m_mappedData = nullptr;
51 other.m_frameSerial = 0;
52 other.m_frameAddress = 0;
53}
54
56{
57 if (this != &other) {
58 Cleanup();
59
60 BufferBase::operator=(std::move(other));
61 m_device = std::move(other.m_device);
62 m_commandQueue = other.m_commandQueue;
63 m_uploader = other.m_uploader;
64 m_buffer = std::move(other.m_buffer);
65 m_heapType = other.m_heapType;
66 m_resourceState = other.m_resourceState;
67 m_currentState = other.m_currentState;
68 m_mappedData = other.m_mappedData;
69 m_constantData = std::move(other.m_constantData);
70 m_frameSerial = other.m_frameSerial;
71 m_frameAddress = other.m_frameAddress;
72
73 other.m_commandQueue = nullptr;
74 other.m_uploader = nullptr;
75 other.m_mappedData = nullptr;
76 other.m_frameSerial = 0;
77 other.m_frameAddress = 0;
78 }
79 return *this;
80}
81
86
87void DirectX12Buffer::ConfigureFromBufferType(BufferType type)
88{
89 switch (type) {
91 m_heapType = D3D12_HEAP_TYPE_DEFAULT;
92 m_resourceState = D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
93 break;
94
96 m_heapType = D3D12_HEAP_TYPE_DEFAULT;
97 m_resourceState = D3D12_RESOURCE_STATE_INDEX_BUFFER;
98 break;
99
101 m_heapType = D3D12_HEAP_TYPE_UPLOAD;
102 m_resourceState = D3D12_RESOURCE_STATE_GENERIC_READ;
103 break;
104
106 m_heapType = D3D12_HEAP_TYPE_DEFAULT;
107 m_resourceState = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
108 break;
109
110 default:
111 m_heapType = D3D12_HEAP_TYPE_DEFAULT;
112 m_resourceState = D3D12_RESOURCE_STATE_COMMON;
113 break;
114 }
115}
116
117D3D12_RESOURCE_DESC DirectX12Buffer::CreateBufferDesc() const
118{
119 D3D12_RESOURCE_DESC desc = {};
120 desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
121 desc.Alignment = 0;
122 desc.Width = Size;
123 desc.Height = 1;
124 desc.DepthOrArraySize = 1;
125 desc.MipLevels = 1;
126 desc.Format = DXGI_FORMAT_UNKNOWN;
127 desc.SampleDesc.Count = 1;
128 desc.SampleDesc.Quality = 0;
129 desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
130 desc.Flags = D3D12_RESOURCE_FLAG_NONE;
131 return desc;
132}
133
135 return Initialize(data, Size);
136}
137
138bool DirectX12Buffer::Initialize(const void* data, size_t size)
139{
140 if (!m_device || Size == 0)
141 return false;
142
143 if (m_buffer)
144 Cleanup();
145
146 D3D12_HEAP_PROPERTIES heapProps = {};
147 heapProps.Type = m_heapType;
148 heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
149 heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
150 heapProps.CreationNodeMask = 1;
151 heapProps.VisibleNodeMask = 1;
152
153 auto desc = CreateBufferDesc();
154 // When we have initial data for a DEFAULT heap buffer, create in
155 // COPY_DEST state so the upload copy can succeed. The post-copy
156 // barrier in UploadToDefaultHeap transitions to m_resourceState.
157 D3D12_RESOURCE_STATES initialState =
158 (data && m_heapType == D3D12_HEAP_TYPE_DEFAULT)
159 ? D3D12_RESOURCE_STATE_COPY_DEST
160 : m_resourceState;
161 m_currentState = initialState;
162 HRESULT hr = m_device->CreateCommittedResource(
163 &heapProps,
164 D3D12_HEAP_FLAG_NONE,
165 &desc,
166 initialState,
167 nullptr,
168 IID_PPV_ARGS(&m_buffer));
169
170 if (FAILED(hr)) {
171 // Error handling code here
172 return false;
173 }
174
175 if (Type == BufferType::Constant) m_constantData.assign(Size, 0);
176
177 if (data) {
178 // If we have initial data, update the buffer
179 if (m_heapType == D3D12_HEAP_TYPE_DEFAULT) {
180 // For GPU-only buffers, we need to stage the data
181 UploadToDefaultHeap(data, size);
182 } else {
183 // For CPU-accessible buffers, we can directly map and update
184 Update(const_cast<void*>(data), size);
185 }
186 }
187
188 bIsInitialized = true;
189 return true;
190}
191
192bool DirectX12Buffer::UploadToDefaultHeap(const void* data, size_t dataSize) {
194 DirectX12UploadContext* uploader = m_uploader;
195 if (!uploader) {
196 if (!local.Initialize(m_device.Get(), m_commandQueue)) return false;
197 uploader = &local;
198 }
199
200 ID3D12Resource* staging = nullptr;
201 void* mapped = uploader->AllocateStaging(dataSize, &staging);
202 if (!mapped) return false;
203 memcpy(mapped, data, dataSize);
204
205 ID3D12GraphicsCommandList* commandList = uploader->GetCommandList();
206
207 // If the buffer is NOT in COPY_DEST state (i.e. it was already uploaded
208 // once and transitioned to its target state), transition it back to
209 // COPY_DEST so the copy can succeed.
210 if (m_currentState != D3D12_RESOURCE_STATE_COPY_DEST) {
211 D3D12_RESOURCE_BARRIER barrier = {};
212 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
213 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
214 barrier.Transition.pResource = m_buffer.Get();
215 barrier.Transition.StateBefore = m_currentState;
216 barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
217 barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
218
219 commandList->ResourceBarrier(1, &barrier);
220 }
221
222 commandList->CopyBufferRegion(m_buffer.Get(), 0, staging, 0, dataSize);
223
224 // Transition resource to its target state
225 if (m_resourceState != D3D12_RESOURCE_STATE_COPY_DEST) {
226 D3D12_RESOURCE_BARRIER barrier = {};
227 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
228 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
229 barrier.Transition.pResource = m_buffer.Get();
230 barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
231 barrier.Transition.StateAfter = m_resourceState;
232 barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
233
234 commandList->ResourceBarrier(1, &barrier);
235 }
236
237 m_currentState = m_resourceState;
238
239 if (uploader == &local) local.Flush();
240 return true;
241}
242
244 // Binding happens through the renderer's command list.
245}
246
248{
249 if (bIsMapped) {
250 Unmap();
251 }
252
253 // Defer GPU buffer destruction — may still be referenced by in-flight
254 // commands.
255 if (m_buffer) {
256 DeferCleanup(std::move(m_buffer));
257 m_buffer = nullptr;
258 }
259
260 m_constantData.clear();
261 m_frameSerial = 0;
262 m_frameAddress = 0;
263
264 bIsInitialized = false;
265 Size = 0;
266 Data = nullptr;
267 bIsMapped = false;
268}
269
271{
272 if (!m_buffer || bIsMapped)
273 return false;
274
275 // Only upload heaps can be mapped
276 if (m_heapType != D3D12_HEAP_TYPE_UPLOAD && m_heapType != D3D12_HEAP_TYPE_READBACK)
277 return false;
278
279 HRESULT hr = m_buffer->Map(0, nullptr, &m_mappedData);
280
281 if (FAILED(hr)) {
282 // Error handling
283 return false;
284 }
285
286 Data = m_mappedData;
287 bIsMapped = true;
288 return true;
289}
290
292{
293 if (m_buffer && bIsMapped) {
294 m_buffer->Unmap(0, nullptr);
295 Data = nullptr;
296 m_mappedData = nullptr;
297 bIsMapped = false;
298 }
299}
300
301void DirectX12Buffer::Update(void* data, size_t size)
302{
303 if (!m_buffer || size > Size || !data)
304 return;
305
306 if (Type == BufferType::Constant) {
307 // Store CPU shadow copy for transform CBs (needed by shadow pass)
308 if (size <= 128) StoreCPUShadowCopy(data, size);
309
310 if (m_constantData.size() < size) m_constantData.resize(size);
311 memcpy(m_constantData.data(), data, size);
312 m_frameSerial = 0;
313 m_frameAddress = 0;
314 return;
315 }
316
317 if (m_heapType == D3D12_HEAP_TYPE_UPLOAD) {
318 if (!bIsMapped && !Map()) return;
319 memcpy(m_mappedData, data, size);
320 } else {
321 UploadToDefaultHeap(data, size);
322 }
323}
324
326 return nullptr;
327}
328
329namespace {
330struct DeferredObject {
331 Microsoft::WRL::ComPtr<ID3D12Pageable> object;
332 uint64_t fenceValue = 0;
333};
334
335std::vector<Microsoft::WRL::ComPtr<ID3D12Pageable>> s_untagged;
336std::vector<DeferredObject> s_tagged;
337} // namespace
338
340 Microsoft::WRL::ComPtr<ID3D12Pageable> object) {
341 if (object) s_untagged.push_back(std::move(object));
342}
343
344void DirectX12Buffer::TagDeferredCleanup(uint64_t fenceValue) {
345 for (auto& object : s_untagged)
346 s_tagged.push_back({std::move(object), fenceValue});
347 s_untagged.clear();
348}
349
350void DirectX12Buffer::ProcessDeferredCleanup(uint64_t completedValue) {
351 std::erase_if(s_tagged, [completedValue](const DeferredObject& d) {
352 return d.fenceValue <= completedValue;
353 });
354}
355
356} // namespace RenderEngine
357} // namespace Sleak
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.
DirectX12Buffer(ID3D12Device *device, ID3D12CommandQueue *queue, size_t size, BufferType type, DirectX12UploadContext *uploader=nullptr)
Uploads go through the shared uploader when given, else they block.
static void TagDeferredCleanup(uint64_t fenceValue)
Stamps everything queued since the last call with its retire value.
bool Map() override
Maps the buffer for CPU writes (upload-heap buffers only).
static void DeferCleanup(Microsoft::WRL::ComPtr< ID3D12Pageable > object)
Queues a GPU object for release once the frame that used it is done.
static void ProcessDeferredCleanup(uint64_t completedValue)
Releases every queued object whose fence value has completed.
DirectX12Buffer & operator=(const DirectX12Buffer &)=delete
void * AllocateStaging(uint64_t size, ID3D12Resource **resource)
Returns a mapped upload buffer that lives until the open batch is done.
ID3D12GraphicsCommandList * GetCommandList()
Returns the open copy command list, starting a new batch if needed.
bool Initialize(ID3D12Device *device, ID3D12CommandQueue *queue)
Creates the fence used to track submitted batches.
void Flush()
Submits and blocks until every batch has finished on the GPU.
Backend-facing rendering layer shared by the four graphics backends.
BufferType
GPU buffer usage kind, drives backend binding flags and layout.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10