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