aboutsummaryrefslogtreecommitdiff
path: root/dxg/src/imm.c
diff options
context:
space:
mode:
Diffstat (limited to 'dxg/src/imm.c')
-rw-r--r--dxg/src/imm.c368
1 files changed, 368 insertions, 0 deletions
diff --git a/dxg/src/imm.c b/dxg/src/imm.c
new file mode 100644
index 0000000..28baa99
--- /dev/null
+++ b/dxg/src/imm.c
@@ -0,0 +1,368 @@
1/* Immediate-mode renderer.
2
3Geometry is given by client code and buffered in an upload-heap buffer stored
4in host memory.
5When the buffer fills up or the client is done, a draw call is issued. The draw
6call reads directly from the buffer in host memory; there is no intermediate
7buffer copy.
8The renderer double-buffers two host-side buffers so that the client can
9continue specifying more data into a second buffer while the contents of the
10first buffer are rendered.
11If the first buffer is still being rendered while the client loops around, then
12the client must wait before issuing further geometry.
13Once the render of the first buffer completes, the process starts again,
14ping-ponging between the two buffers.*/
15#include <dxg/imm.h>
16#include <dxg/dxcommon.h>
17
18#include <imm_vs.h> // generated
19#include <imm_ps.h> // generated
20
21#define WIN32_LEAN_AND_MEAN
22#include <Windows.h> // OutputDebugStringA
23
24#include <stdint.h>
25#include <stdlib.h>
26
27static ID3D12Resource* create_buffer(ID3D12Device* pDevice, size_t size) {
28 assert(pDevice);
29 const D3D12_HEAP_PROPERTIES props = {
30 .Type = D3D12_HEAP_TYPE_UPLOAD,
31 .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
32 .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
33 .CreationNodeMask = 0,
34 .VisibleNodeMask = 0
35 };
36 const D3D12_RESOURCE_DESC desc = {
37 .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
38 .Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT,
39 .Width = size,
40 .Height = 1,
41 .DepthOrArraySize = 1,
42 .MipLevels = 1,
43 .Format = DXGI_FORMAT_UNKNOWN,
44 .SampleDesc = {.Count = 1, .Quality = 0},
45 .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
46 .Flags = D3D12_RESOURCE_FLAG_NONE
47 };
48 ID3D12Resource* pBuffer = NULL;
49 TrapIfFailed(pDevice->lpVtbl->CreateCommittedResource(
50 pDevice,
51 &props,
52 D3D12_HEAP_FLAG_NONE,
53 &desc,
54 D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
55 NULL,
56 &IID_ID3D12Resource,
57 &pBuffer));
58 return pBuffer;
59}
60
61typedef struct GraphicsState {
62 D3D12_VIEWPORT viewport;
63 D3D12_CPU_DESCRIPTOR_HANDLE hBackBufferView;
64 D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilView;
65} GraphicsState;
66
67// Set of per-draw resources. The renderer cycles between sets per draw.
68typedef struct ResourceSet {
69 ID3D12Resource* pVertexBuffer;
70 CommandRecorder cmdRec;
71} ResourceSet;
72
73typedef struct DxgImm {
74 ID3D12Device* pDevice;
75 ID3D12CommandQueue* pCmdQueue;
76 ID3D12PipelineState* pPipelineState;
77 ID3D12RootSignature* pRootSignature;
78 GraphicsState graphicsState;
79 ResourceSet resources[2];
80 int cur; // Index to current resource set. New geometry written here.
81 float* pCurrentBufferData; // Mapped region of current buffer.
82 size_t bufferSizeVerts; // Num verts per buffer.
83 ID3D12Fence* pFence;
84 HANDLE fenceEvent;
85 uint64_t fenceValue;
86 size_t vertsWritten; // Verts written to current buffer.
87 bool wait; // Whether the next draw should wait.
88} DxgImm;
89
90static inline size_t vertex_size_bytes() {
91 return 3 * sizeof(float);
92}
93
94static inline size_t verts_byte_count(size_t numVerts) {
95 return numVerts * vertex_size_bytes();
96}
97
98static inline size_t dxg_imm_verts_left(const DxgImm* imm) {
99 assert(imm);
100 assert(imm->bufferSizeVerts >= imm->vertsWritten);
101 return imm->bufferSizeVerts - imm->vertsWritten;
102}
103
104static void dxg_imm_copy_verts(DxgImm* imm, const float* pVerts, size_t count) {
105 assert(imm);
106 assert(pVerts);
107 assert(count <= dxg_imm_verts_left(imm));
108 memcpy(&imm->pCurrentBufferData[imm->vertsWritten], pVerts, verts_byte_count(count));
109 imm->vertsWritten += count;
110}
111
112// Set up the current resource set for drawing.
113static void dxg_imm_set_up_resource_set(DxgImm* imm) {
114 assert(imm);
115 ResourceSet* const pResources = &imm->resources[imm->cur];
116 TrapIfFailed(pResources->pVertexBuffer->lpVtbl->Map(
117 pResources->pVertexBuffer, 0, NULL, &imm->pCurrentBufferData));
118 dxg_cmdrec_reset(&pResources->cmdRec);
119}
120
121// Move on to the next resource set.
122static ID3D12Resource* dxg_imm_next_resource_set(DxgImm* imm) {
123 assert(imm);
124 ResourceSet* const pResources = &imm->resources[imm->cur];
125 // Unmap the current buffer.
126 // TODO: Do we actually need to do this or can we leave it mapped? If the
127 // latter, then we could just map both buffers and let them be.
128 pResources->pVertexBuffer->lpVtbl->Unmap(pResources->pVertexBuffer, 0, NULL);
129 // Move on to the next resource set.
130 imm->cur = (imm->cur + 1) & 1;
131 imm->vertsWritten = 0;
132 // Set up the new resource set.
133 dxg_imm_set_up_resource_set(imm);
134}
135
136// Wait for the current buffer to be available for writing.
137static void dxg_imm_wait(DxgImm* imm) {
138 assert(imm);
139 assert(imm->wait);
140 // We only need to wait upon the first round around both buffers.
141 // First Signal is on fence value 1, 0 is not actually Signaled.
142 if (imm->fenceValue > 2) { // TODO: Do we need this check?
143 // The last buffer (not current) was Signaled with fenceValue - 1.
144 // The current buffer was therefore Signaled two fence values ago, or
145 // fenceValue - 2.
146 dxg_wait(imm->pFence, imm->fenceEvent, imm->fenceValue - 2);
147 }
148 imm->wait = false;
149}
150
151// Draw the current buffer.
152static void dxg_imm_draw(DxgImm* imm) {
153 assert(imm);
154 ResourceSet* const pResourceSet = &imm->resources[imm->cur];
155 ID3D12Resource* const pCurrentBuffer = pResourceSet->pVertexBuffer;
156 ID3D12GraphicsCommandList* const pCmdList = pResourceSet->cmdRec.pCmdList;
157 const D3D12_VIEWPORT* const pViewport = &imm->graphicsState.viewport;
158 const D3D12_RECT scissor = {
159 .bottom = pViewport->Height,
160 .left = 0,
161 .right = pViewport->Width,
162 .top = 0,
163 };
164 const D3D12_VERTEX_BUFFER_VIEW vertexBufferView = {
165 .BufferLocation = pCurrentBuffer->lpVtbl->GetGPUVirtualAddress(pCurrentBuffer),
166 .SizeInBytes = verts_byte_count(imm->vertsWritten),
167 .StrideInBytes = vertex_size_bytes(),
168 };
169 pCmdList->lpVtbl->RSSetViewports(pCmdList, 1, pViewport);
170 pCmdList->lpVtbl->RSSetScissorRects(pCmdList, 1, &scissor);
171 pCmdList->lpVtbl->OMSetRenderTargets(
172 pCmdList, 1, &imm->graphicsState.hBackBufferView, false, &imm->graphicsState.hDepthStencilView);
173 pCmdList->lpVtbl->SetPipelineState(pCmdList, imm->pPipelineState);
174 pCmdList->lpVtbl->SetGraphicsRootSignature(pCmdList, imm->pRootSignature);
175 pCmdList->lpVtbl->IASetPrimitiveTopology(pCmdList, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
176 pCmdList->lpVtbl->IASetVertexBuffers(pCmdList, 0, 1, &vertexBufferView);
177 pCmdList->lpVtbl->DrawInstanced(pCmdList, imm->vertsWritten, 1, 0, 0);
178 pCmdList->lpVtbl->Close(pCmdList);
179 ID3D12CommandList* const cmdLists[] = {(ID3D12CommandList*)pCmdList};
180 ID3D12CommandQueue* const pCmdQueue = imm->pCmdQueue;
181 pCmdQueue->lpVtbl->ExecuteCommandLists(pCmdQueue, 1, cmdLists);
182}
183
184DxgImm* dxg_imm_init(ID3D12Device* pDevice, ID3D12CommandQueue* pCmdQueue, DXGI_FORMAT swapChainRtvFormat, DXGI_SAMPLE_DESC swapChainSampleDesc, size_t bufferSizeVerts) {
185 assert(pDevice);
186 assert(pCmdQueue);
187
188 DxgImm* imm = calloc(1, sizeof(DxgImm));
189 if (!imm) {
190 return 0;
191 }
192
193 imm->pDevice = pDevice;
194 imm->pCmdQueue = pCmdQueue;
195 imm->bufferSizeVerts = bufferSizeVerts;
196 imm->fenceValue = 0;
197
198 // TODO: Move this to the application side.
199 const D3D_SHADER_MODEL model = D3D_SHADER_MODEL_6_5;
200 D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = { model };
201 HRESULT result = pDevice->lpVtbl->CheckFeatureSupport(
202 pDevice, D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel));
203 if (FAILED(result) || (shaderModel.HighestShaderModel < model)) {
204 DEBUG_PRINT("ERROR: Shader Model 6.5 is not supported!\n");
205 TrapIfFailed(result);
206 }
207
208 const D3D12_SHADER_BYTECODE vs_bytecode = {
209 .pShaderBytecode = imm_vs,
210 .BytecodeLength = sizeof(imm_vs)
211 };
212
213 const D3D12_SHADER_BYTECODE ps_bytecode = {
214 .pShaderBytecode = imm_ps,
215 .BytecodeLength = sizeof(imm_ps)
216 };
217
218 // TODO: Find out how many root parameters to use.
219 // Let's do bindless rendering to keep things flexible.
220 const D3D12_ROOT_SIGNATURE_DESC rootsig_desc = {
221 .NumParameters = 0,
222 .pParameters = NULL,
223 .NumStaticSamplers = 0,
224 .pStaticSamplers = NULL,
225 .Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
226 };
227
228 ID3DBlob* pRootSignature = NULL;
229 ID3DBlob* pErrors = NULL;
230 result = D3D12SerializeRootSignature(
231 &rootsig_desc,
232 D3D_ROOT_SIGNATURE_VERSION_1,
233 &pRootSignature,
234 &pErrors);
235 if (FAILED(result)) {
236 if (pErrors) {
237 DEBUG_PRINT(pErrors->lpVtbl->GetBufferPointer(pErrors));
238 }
239 TrapIfFailed(result);
240 }
241
242 TrapIfFailed(imm->pDevice->lpVtbl->CreateRootSignature(
243 imm->pDevice,
244 0,
245 pRootSignature->lpVtbl->GetBufferPointer(pRootSignature),
246 pRootSignature->lpVtbl->GetBufferSize(pRootSignature),
247 &IID_ID3D12RootSignature,
248 &imm->pRootSignature));
249
250 const D3D12_INPUT_ELEMENT_DESC input_layout[] = {
251 { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
252 };
253 const D3D12_INPUT_LAYOUT_DESC input_layout_desc = {
254 .pInputElementDescs = input_layout,
255 .NumElements = COUNTOF(input_layout)
256 };
257
258 const D3D12_GRAPHICS_PIPELINE_STATE_DESC gpso = {
259 .pRootSignature = imm->pRootSignature,
260 .VS = vs_bytecode,
261 .PS = ps_bytecode,
262 .BlendState = CD3DX12_BLEND_DESC_DEFAULT(),
263 .SampleMask = PointSampling,
264 .RasterizerState = CD3DX12_RASTERIZER_DESC_DEFAULT(),
265 .InputLayout = input_layout_desc,
266 .PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
267 .NumRenderTargets = 1,
268 .RTVFormats = {swapChainRtvFormat},
269 .SampleDesc = swapChainSampleDesc
270 };
271 TrapIfFailed(imm->pDevice->lpVtbl->CreateGraphicsPipelineState(
272 imm->pDevice, &gpso, &IID_ID3D12PipelineState, &imm->pPipelineState));
273
274 const size_t bufferSize = verts_byte_count(bufferSizeVerts);
275 for (int i = 0; i < 2; ++i) {
276 imm->resources[i].pVertexBuffer = create_buffer(pDevice, bufferSize);
277 if (!imm->resources[i].pVertexBuffer) {
278 dxg_imm_destroy(&imm);
279 }
280 TrapIfFailed(dxg_cmdrec_init(&imm->resources[i].cmdRec, pDevice));
281 }
282 imm->cur = 0;
283 dxg_imm_set_up_resource_set(imm);
284
285 TrapIfFailed(pDevice->lpVtbl->CreateFence(
286 pDevice, imm->fenceValue, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, &imm->pFence));
287
288 if ((imm->fenceEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) {
289 TrapIfFailed(HRESULT_FROM_WIN32(GetLastError()));
290 }
291
292 return imm;
293}
294
295void dxg_imm_destroy(DxgImm** ppImm) {
296 assert(ppImm);
297 DxgImm* imm = *ppImm;
298 if (imm) {
299 for (int i = 0; i < 2; ++i) {
300 SafeRelease(imm->resources[i].pVertexBuffer);
301 dxg_cmdrec_destroy(&imm->resources[i].cmdRec);
302 }
303 SafeRelease(imm->pRootSignature);
304 SafeRelease(imm->pPipelineState);
305 SafeRelease(imm->pFence);
306 if (imm->fenceEvent != NULL) {
307 CloseHandle(imm->fenceEvent);
308 }
309 free(imm);
310 *ppImm = 0;
311 }
312}
313
314void dxg_imm_set_graphics_state(
315 DxgImm* imm,
316 const D3D12_VIEWPORT* pViewport,
317 D3D12_CPU_DESCRIPTOR_HANDLE hBackBufferView,
318 D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilView) {
319 assert(imm);
320 assert(pViewport);
321 assert(hBackBufferView.ptr);
322 assert(hDepthStencilView.ptr);
323 imm->graphicsState = (GraphicsState) {
324 .viewport = *pViewport,
325 .hBackBufferView = hBackBufferView,
326 .hDepthStencilView = hDepthStencilView,
327 };
328}
329
330void dxg_imm_flush(DxgImm* imm) {
331 assert(imm);
332 if (imm->vertsWritten > 0) {
333 dxg_imm_draw(imm);
334 // Signal the fence so that the current buffer can be reused once the
335 // draw has finished.
336 ID3D12CommandQueue* pCmdQueue = imm->pCmdQueue;
337 imm->fenceValue++;
338 pCmdQueue->lpVtbl->Signal(pCmdQueue, imm->pFence, imm->fenceValue);
339 // Next draw should Wait for the next buffer. Wait lazily on the next
340 // draw to avoid a stall here.
341 imm->wait = true;
342 dxg_imm_next_resource_set(imm);
343 }
344}
345
346void dxg_imm_draw_triangles(DxgImm* imm, const float* pVerts, size_t numTris) {
347 assert(imm);
348 assert(pVerts);
349 // TODO: This could be a loop to handle the case where the max buffer
350 // capacity cannot hold numTris. Or maybe we should rely on the caller
351 // to specify a big enough capacity, but that makes the API less
352 // friendly.
353 size_t triCapacity = dxg_imm_verts_left(imm) / 3;
354 if (triCapacity == 0) {
355 dxg_imm_flush(imm);
356 }
357 // If we just flushed the previous buffer, then we have to wait on the next
358 // one. The wait is done here, and not inside the branch above, because the
359 // client code can also flush the buffer.
360 if (imm->wait) {
361 dxg_imm_wait(imm);
362 }
363 // Re-evaluate capacity. It must be >0 now.
364 triCapacity = dxg_imm_verts_left(imm) / 3;
365 assert(triCapacity > 0);
366 const size_t numVerts = MIN(triCapacity, numTris) * 3;
367 dxg_imm_copy_verts(imm, pVerts, numVerts);
368}