aboutsummaryrefslogtreecommitdiff
path: root/triangle
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-02 16:39:36 -0800
committer3gg <3gg@shellblade.net>2025-12-02 16:39:36 -0800
commit6c8ae19be66cee247980a48e736a4e05d14de179 (patch)
treed860767907bf0cbe17ec66422e11bea700cf56d9 /triangle
parent8f594c8ebd11f0e5f8a0c6369c3fe7383d250cbe (diff)
Immediate-mode renderer, triangle demo, shader compilation in cmake, Agility SDKHEADmain
Diffstat (limited to 'triangle')
-rw-r--r--triangle/CMakeLists.txt12
-rw-r--r--triangle/main.c486
2 files changed, 498 insertions, 0 deletions
diff --git a/triangle/CMakeLists.txt b/triangle/CMakeLists.txt
new file mode 100644
index 0000000..1ba589d
--- /dev/null
+++ b/triangle/CMakeLists.txt
@@ -0,0 +1,12 @@
1cmake_minimum_required(VERSION 3.20)
2
3project(triangle)
4
5add_executable(triangle
6 main.c)
7
8target_link_libraries(triangle PRIVATE
9 app
10 dxg)
11
12install_agility_sdk(${CMAKE_CURRENT_BINARY_DIR})
diff --git a/triangle/main.c b/triangle/main.c
new file mode 100644
index 0000000..3413fdf
--- /dev/null
+++ b/triangle/main.c
@@ -0,0 +1,486 @@
1#include <dxg/dxcommon.h>
2#include <dxg/imm.h>
3#include <dxwindow.h>
4
5#include <assert.h>
6#include <stdio.h>
7
8#define WIDTH 1920
9#define HEIGHT 1200
10
11#define SWAP_CHAIN_BUFFER_COUNT 2 // Double-buffering.
12#define IMM_NUM_VERTS 1024
13
14typedef struct D3DSettings
15{
16 int width;
17 int height;
18} D3DSettings;
19
20typedef struct D3D {
21 Window* pWindow;
22 D3DSettings settings;
23
24 IDXGIFactory4* pDxgiFactory;
25 ID3D12Device* pDevice;
26
27 ID3D12CommandQueue* pCommandQueue;
28 CommandRecorder cmdRecPreamble;
29 CommandRecorder cmdRecPostamble;
30
31 IDXGISwapChain3* pSwapChain;
32
33 ID3D12DescriptorHeap* pRtvHeap;
34 ID3D12DescriptorHeap* pDsvHeap;
35
36 ID3D12Resource* pSwapChainBuffer[SWAP_CHAIN_BUFFER_COUNT];
37 ID3D12Resource* pDepthStencilBuffer;
38
39 ID3D12Fence* pFence;
40 HANDLE fence_event;
41 UINT64 fence_value;
42
43 UINT rtv_descriptor_size;
44 UINT dsv_descriptor_size;
45 UINT cbv_descriptor_size;
46
47 DxgImm* pImm;
48} D3D;
49
50static D3D12_CPU_DESCRIPTOR_HANDLE d3d_get_current_back_buffer_view(const D3D* d3d) {
51 assert(d3d);
52 assert(d3d->pSwapChain);
53 assert(d3d->pRtvHeap);
54 assert(d3d->rtv_descriptor_size > 0);
55 D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle;
56 d3d->pRtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pRtvHeap, &rtv_handle);
57 return CD3DX12_CPU_DESCRIPTOR_HANDLE(
58 rtv_handle,
59 d3d->pSwapChain->lpVtbl->GetCurrentBackBufferIndex(d3d->pSwapChain),
60 d3d->rtv_descriptor_size);
61}
62
63static ID3D12Resource* d3d_get_current_back_buffer(const D3D* d3d) {
64 assert(d3d);
65 assert(d3d->pSwapChain);
66 assert(d3d->pSwapChainBuffer);
67 return d3d->pSwapChainBuffer[d3d->pSwapChain->lpVtbl->GetCurrentBackBufferIndex(d3d->pSwapChain)];
68}
69
70static D3D12_CPU_DESCRIPTOR_HANDLE d3d_get_depth_stencil_view(D3D* d3d) {
71 assert(d3d);
72 assert(d3d->pDsvHeap);
73 D3D12_CPU_DESCRIPTOR_HANDLE handle;
74 d3d->pDsvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pDsvHeap, &handle);
75 return handle;
76}
77
78/// Creates the application's swap chain.
79///
80/// This method can be called multiple times to re-create the swap chain.
81static void d3d_create_swap_chain(D3D* d3d, DXGI_FORMAT swapChainRtvFormat, DXGI_SAMPLE_DESC swapChainSampleDesc) {
82 assert(d3d);
83 assert(d3d->pDxgiFactory);
84 assert(d3d->pCommandQueue);
85 assert(d3d->pWindow);
86
87 SafeRelease(d3d->pSwapChain);
88
89 DXGI_SWAP_CHAIN_DESC1 desc = (DXGI_SWAP_CHAIN_DESC1){
90 .Width = (UINT)(d3d->settings.width),
91 .Height = (UINT)(d3d->settings.height),
92 .Format = swapChainRtvFormat,
93 .SampleDesc = swapChainSampleDesc,
94 .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
95 .BufferCount = SWAP_CHAIN_BUFFER_COUNT,
96 .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
97 };
98 IDXGISwapChain1* pSwapChain = 0;
99 TrapIfFailed(d3d->pDxgiFactory->lpVtbl->CreateSwapChainForHwnd(
100 d3d->pDxgiFactory,
101 (IUnknown*)d3d->pCommandQueue, // Swap chain uses queue to perform flush.
102 window_handle(d3d->pWindow),
103 &desc,
104 /*pFullScreenDesc=*/0, // Running in windowed mode.
105 /*pRestrictToOutput=*/0,
106 &pSwapChain));
107 //TrapIfFailed(pSwapChain.As(&d3d->pSwapChain));
108 d3d->pSwapChain = (IDXGISwapChain3*)pSwapChain;
109}
110
111/// Creates RTVs for all of the swap chain's buffers.
112static void d3d_create_swap_chain_buffer_render_target_views(D3D* d3d) {
113 assert(d3d);
114 assert(d3d->pDevice);
115 assert(d3d->pSwapChain);
116 assert(d3d->pSwapChainBuffer);
117 assert(d3d->pRtvHeap);
118 assert(d3d->rtv_descriptor_size > 0);
119
120 // Create the new buffer views.
121 D3D12_CPU_DESCRIPTOR_HANDLE rtv_heap_handle;
122 d3d->pRtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pRtvHeap, &rtv_heap_handle);
123 for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
124 {
125 TrapIfFailed(d3d->pSwapChain->lpVtbl->GetBuffer(
126 d3d->pSwapChain, i, &IID_ID3D12Resource, &d3d->pSwapChainBuffer[i]));
127
128 d3d->pDevice->lpVtbl->CreateRenderTargetView(
129 d3d->pDevice, d3d->pSwapChainBuffer[i], /*pDesc=*/0, rtv_heap_handle);
130
131 rtv_heap_handle = OFFSET_HANDLE(rtv_heap_handle, 1, d3d->rtv_descriptor_size);
132 }
133}
134
135/// Creates a depth/stencil buffer and its view.
136static void d3d_create_depth_stencil_buffer_and_view(D3D* d3d) {
137 assert(d3d);
138 assert(d3d->pDevice);
139
140 const D3D12_RESOURCE_DESC depth_stencil_desc = (D3D12_RESOURCE_DESC){
141 .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
142 .Alignment = 0,
143 .Width = (UINT64)(d3d->settings.width),
144 .Height = (UINT)(d3d->settings.height),
145 .DepthOrArraySize = 1,
146 .MipLevels = 1,
147 .Format = DXGI_FORMAT_D24_UNORM_S8_UINT,
148 .SampleDesc = (DXGI_SAMPLE_DESC){
149 .Count = 1,
150 .Quality = 0,
151 },
152 .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
153 .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
154 };
155 const D3D12_CLEAR_VALUE opt_clear_value = (D3D12_CLEAR_VALUE){
156 .Format = depth_stencil_desc.Format,
157 .DepthStencil = (D3D12_DEPTH_STENCIL_VALUE){
158 .Depth = 1.0f,
159 .Stencil = 0,
160 },
161 };
162 const D3D12_HEAP_PROPERTIES depth_stencil_heap_properties = (D3D12_HEAP_PROPERTIES){
163 .Type = D3D12_HEAP_TYPE_DEFAULT,
164 .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
165 .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
166 .CreationNodeMask = 1,
167 .VisibleNodeMask = 1,
168 };
169 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommittedResource(
170 d3d->pDevice,
171 &depth_stencil_heap_properties,
172 D3D12_HEAP_FLAG_NONE,
173 &depth_stencil_desc,
174 D3D12_RESOURCE_STATE_COMMON,
175 &opt_clear_value,
176 &IID_ID3D12Resource,
177 &d3d->pDepthStencilBuffer));
178
179 d3d->pDevice->lpVtbl->CreateDepthStencilView(
180 d3d->pDevice,
181 d3d->pDepthStencilBuffer,
182 /*pDesc=*/0,
183 d3d_get_depth_stencil_view(d3d));
184}
185
186/// Creates RTV and DSV descriptor heaps.
187static void d3d_create_descriptor_heaps(D3D* d3d) {
188 assert(d3d);
189 assert(d3d->pDevice);
190
191 // The RTV heap must hold as many descriptors as we have buffers in the
192 // swap chain.
193 const D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = (D3D12_DESCRIPTOR_HEAP_DESC){
194 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
195 .NumDescriptors = SWAP_CHAIN_BUFFER_COUNT,
196 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
197 .NodeMask = 0,
198 };
199 TrapIfFailed(d3d->pDevice->lpVtbl->CreateDescriptorHeap(
200 d3d->pDevice, &rtv_heap_desc, &IID_ID3D12DescriptorHeap, &d3d->pRtvHeap));
201
202 // For the depth/stencil buffer, we just need one view.
203 const D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc = (D3D12_DESCRIPTOR_HEAP_DESC){
204 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
205 .NumDescriptors = 1,
206 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
207 .NodeMask = 0,
208 };
209 TrapIfFailed(d3d->pDevice->lpVtbl->CreateDescriptorHeap(
210 d3d->pDevice, &dsv_heap_desc, &IID_ID3D12DescriptorHeap, &d3d->pDsvHeap));
211}
212
213static void d3d_init(D3D* d3d, Window* pWindow, const D3DSettings* pSettings) {
214 assert(d3d);
215 assert(pWindow);
216 assert(pSettings);
217
218 d3d->pWindow = pWindow;
219 d3d->settings = *pSettings;
220
221 UINT dxgiFactoryFlags = 0;
222#ifndef NDEBUG
223 ID3D12Debug* pDebug = NULL;
224 TrapIfFailed(D3D12GetDebugInterface(&IID_ID3D12Debug, (&pDebug)));
225 pDebug->lpVtbl->EnableDebugLayer(pDebug);
226 dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
227#endif
228 TrapIfFailed(CreateDXGIFactory2(
229 dxgiFactoryFlags, &IID_IDXGIFactory4, &d3d->pDxgiFactory));
230
231 // Prevent Alt+Enter from going into fullscreen.
232 TrapIfFailed(d3d->pDxgiFactory->lpVtbl->MakeWindowAssociation(
233 d3d->pDxgiFactory,
234 window_handle(d3d->pWindow),
235 DXGI_MWA_NO_ALT_ENTER));
236
237 TrapIfFailed(D3D12CreateDevice(
238 /*pAdapter=*/0, // Default adapter.
239 D3D_FEATURE_LEVEL_11_0,
240 &IID_ID3D12Device,
241 &d3d->pDevice));
242
243 d3d->rtv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize(
244 d3d->pDevice,
245 D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
246 d3d->dsv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize(
247 d3d->pDevice,
248 D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
249 d3d->cbv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize(
250 d3d->pDevice,
251 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
252
253 const D3D12_COMMAND_QUEUE_DESC queue_desc = (D3D12_COMMAND_QUEUE_DESC){
254 .Type = D3D12_COMMAND_LIST_TYPE_DIRECT,
255 .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
256 };
257 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandQueue(
258 d3d->pDevice,
259 &queue_desc,
260 &IID_ID3D12CommandQueue,
261 &d3d->pCommandQueue));
262
263 TrapIfFailed(dxg_cmdrec_init(&d3d->cmdRecPreamble, d3d->pDevice));
264 TrapIfFailed(dxg_cmdrec_init(&d3d->cmdRecPostamble, d3d->pDevice));
265
266 d3d_create_descriptor_heaps(d3d);
267
268 const DXGI_FORMAT swapChainRtvFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
269 const DXGI_SAMPLE_DESC swapChainSampleDesc = {
270 .Count = 1,
271 .Quality = 0,
272 };
273
274 d3d_create_swap_chain(d3d, swapChainRtvFormat, swapChainSampleDesc);
275 d3d_create_swap_chain_buffer_render_target_views(d3d);
276 d3d_create_depth_stencil_buffer_and_view(d3d);
277
278 TrapIfFailed(d3d->pDevice->lpVtbl->CreateFence(
279 d3d->pDevice,
280 d3d->fence_value,
281 D3D12_FENCE_FLAG_NONE,
282 &IID_ID3D12Fence,
283 &d3d->pFence));
284
285 if ((d3d->fence_event = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL) {
286 TrapIfFailed(HRESULT_FROM_WIN32(GetLastError()));
287 }
288
289 d3d->pImm = dxg_imm_init(
290 d3d->pDevice, d3d->pCommandQueue, swapChainRtvFormat, swapChainSampleDesc, IMM_NUM_VERTS);
291}
292
293void d3d_destroy(D3D* d3d) {
294 assert(d3d);
295 dxg_imm_destroy(&d3d->pImm);
296 dxg_cmdrec_destroy(&d3d->cmdRecPreamble);
297 dxg_cmdrec_destroy(&d3d->cmdRecPostamble);
298 SafeRelease(d3d->pCommandQueue);
299 // Swap chain buffers are owned by the swap chain.
300 SafeRelease(d3d->pDepthStencilBuffer);
301 SafeRelease(d3d->pSwapChain);
302 SafeRelease(d3d->pRtvHeap);
303 SafeRelease(d3d->pDsvHeap);
304 SafeRelease(d3d->pFence);
305 SafeRelease(d3d->pDevice);
306 SafeRelease(d3d->pDxgiFactory);
307 CloseHandle(d3d->fence_event);
308}
309
310static void d3d_populate_command_lists(D3D* d3d) {
311 assert(d3d);
312 assert(d3d->cmdRecPreamble.pCmdAllocator);
313 assert(d3d->cmdRecPreamble.pCmdList);
314 assert(d3d->cmdRecPostamble.pCmdAllocator);
315 assert(d3d->cmdRecPostamble.pCmdList);
316
317 dxg_cmdrec_reset(&d3d->cmdRecPreamble);
318 dxg_cmdrec_reset(&d3d->cmdRecPostamble);
319 ID3D12GraphicsCommandList* pCmdListPre = d3d->cmdRecPreamble.pCmdList;
320 ID3D12GraphicsCommandList* pCmdListPost = d3d->cmdRecPostamble.pCmdList;
321
322 // Indicate that we intend to use the back buffer as a render target and
323 // depth/stencil for writing.
324 const D3D12_RESOURCE_BARRIER render_barriers[] = {
325 CD3DX12_RESOURCE_BARRIER_Transition(
326 d3d_get_current_back_buffer(d3d),
327 D3D12_RESOURCE_STATE_PRESENT,
328 D3D12_RESOURCE_STATE_RENDER_TARGET),
329 CD3DX12_RESOURCE_BARRIER_Transition(
330 d3d->pDepthStencilBuffer,
331 D3D12_RESOURCE_STATE_PRESENT,
332 D3D12_RESOURCE_STATE_DEPTH_WRITE)
333 };
334 pCmdListPre->lpVtbl->ResourceBarrier(pCmdListPre, COUNTOF(render_barriers), render_barriers);
335
336 // Clear the render target.
337 const float clear_colour[] = { 0.0f, 0.502f, 0.494f, 0.0f };
338 pCmdListPre->lpVtbl->ClearRenderTargetView(
339 pCmdListPre,
340 d3d_get_current_back_buffer_view(d3d),
341 clear_colour,
342 0, // Number of rectangles in the following array.
343 0); // No rectangles; clear the entire resource.
344
345 // Clear the depth/stencil buffer.
346 pCmdListPre->lpVtbl->ClearDepthStencilView(
347 pCmdListPre,
348 d3d_get_depth_stencil_view(d3d),
349 D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
350 1.0f, // Depth.
351 0, // Stencil.
352 0, // Number of rectangles in the following array.
353 0); // No rectangles; clear the entire resource view.
354
355 // Indicate that we now intend to use the back buffer and depth/stencil
356 // buffer to present.
357 const D3D12_RESOURCE_BARRIER present_barriers[] = {
358 CD3DX12_RESOURCE_BARRIER_Transition(
359 d3d_get_current_back_buffer(d3d),
360 D3D12_RESOURCE_STATE_RENDER_TARGET,
361 D3D12_RESOURCE_STATE_PRESENT),
362 CD3DX12_RESOURCE_BARRIER_Transition(
363 d3d->pDepthStencilBuffer,
364 D3D12_RESOURCE_STATE_DEPTH_WRITE,
365 D3D12_RESOURCE_STATE_PRESENT)
366 };
367 pCmdListPost->lpVtbl->ResourceBarrier(pCmdListPost, COUNTOF(present_barriers), present_barriers);
368
369 // A command list must be closed before it can be executed.
370 TrapIfFailed(pCmdListPre->lpVtbl->Close(pCmdListPre));
371 TrapIfFailed(pCmdListPost->lpVtbl->Close(pCmdListPost));
372}
373
374static void d3d_wait_for_previous_frame(D3D* d3d) {
375 assert(d3d);
376 assert(d3d->pFence);
377 assert(d3d->pCommandQueue);
378
379 // Advance the fence value to mark commands up to this fence point.
380 d3d->fence_value++;
381
382 // The command queue will signal the new fence value when all commands up to
383 // this point have finished execution.
384 TrapIfFailed(d3d->pCommandQueue->lpVtbl->Signal(
385 d3d->pCommandQueue, d3d->pFence, d3d->fence_value));
386
387 // Wait for commands to finish execution.
388 // It is possible that execution has already finished by the time we
389 // get here, so first check the fence's completed value.
390 if (d3d->pFence->lpVtbl->GetCompletedValue(d3d->pFence) < d3d->fence_value) {
391 // Commands are still being executed. Configure a Windows event
392 // and wait for it. The event fires when the commands have finished
393 // execution.
394
395 // Indicate that |fence_event| is to be fired when |fence|
396 // reaches the new fence value.
397 TrapIfFailed(d3d->pFence->lpVtbl->SetEventOnCompletion(
398 d3d->pFence, d3d->fence_value, d3d->fence_event));
399
400 // Will wake up when the fence takes on the new fence value.
401 WaitForSingleObject(d3d->fence_event, INFINITE);
402 }
403}
404
405static void d3d_render(D3D* d3d) {
406 assert(d3d);
407 assert(d3d->pCommandQueue);
408 assert(d3d->pSwapChain);
409
410 const D3D12_VIEWPORT viewport = {
411 .TopLeftX = 0,
412 .TopLeftY = 0,
413 .Width = WIDTH,
414 .Height = HEIGHT,
415 .MinDepth = 0.0f,
416 .MaxDepth = 1.0f
417 };
418 const D3D12_RECT scissor = {
419 .bottom = HEIGHT,
420 .left = 0,
421 .right = WIDTH,
422 .top = 0,
423 };
424 const D3D12_CPU_DESCRIPTOR_HANDLE hBackBufferView = d3d_get_current_back_buffer_view(d3d);
425 const D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilView = d3d_get_depth_stencil_view(d3d);
426
427 d3d_populate_command_lists(d3d); // Preamble and postamble.
428
429 // Preamble.
430 ID3D12CommandList* preamble[] = { (ID3D12CommandList*)d3d->cmdRecPreamble.pCmdList };
431 d3d->pCommandQueue->lpVtbl->ExecuteCommandLists(
432 d3d->pCommandQueue, COUNTOF(preamble), preamble);
433
434 // Draw.
435 const float o = 0.1f;
436 const float triangle[] = {
437 -1+o, -1+o, 0,
438 0, 1-o, 0,
439 +1-o, -1+o, 0,
440 };
441 dxg_imm_set_graphics_state(d3d->pImm, &viewport, hBackBufferView, hDepthStencilView);
442 dxg_imm_draw_triangles(d3d->pImm, triangle, 1);
443 dxg_imm_flush(d3d->pImm);
444
445 // Postamble.
446 ID3D12CommandList* postamble[] = { (ID3D12CommandList*)d3d->cmdRecPostamble.pCmdList };
447 d3d->pCommandQueue->lpVtbl->ExecuteCommandLists(
448 d3d->pCommandQueue, COUNTOF(postamble), postamble);
449
450 TrapIfFailed(d3d->pSwapChain->lpVtbl->Present(
451 d3d->pSwapChain, /*SyncInterval=*/1, /*Flags=*/0));
452
453 // It is not efficient to wait for the frame to complete here, but it
454 // is simple and sufficient for this application.
455 d3d_wait_for_previous_frame(d3d);
456}
457
458int main(int argc, const char** argv) {
459 const D3DSettings settings = (D3DSettings){
460 .width = WIDTH,
461 .height = HEIGHT,
462 };
463
464 if (!window_global_init()) {
465 TRAP("Failed to initialise the window subsystem");
466 }
467
468 Window* window = window_init(settings.width, settings.height, "D3D Application");
469 if (!window) {
470 TRAP(window_get_error());
471 }
472
473 D3D d3d = {0};
474 d3d_init(&d3d, window, &settings);
475
476 while (!window_should_close(window))
477 {
478 window_update(window);
479 d3d_render(&d3d);
480 Sleep(10);
481 }
482
483 d3d_destroy(&d3d);
484 window_global_quit();
485 return 0;
486}