aboutsummaryrefslogtreecommitdiff
path: root/hello
diff options
context:
space:
mode:
Diffstat (limited to 'hello')
-rw-r--r--hello/CMakeLists.txt8
-rw-r--r--hello/main.c430
-rw-r--r--hello/main.cc433
3 files changed, 434 insertions, 437 deletions
diff --git a/hello/CMakeLists.txt b/hello/CMakeLists.txt
index 2804b24..b3c1507 100644
--- a/hello/CMakeLists.txt
+++ b/hello/CMakeLists.txt
@@ -1,10 +1,10 @@
1cmake_minimum_required(VERSION 3.25) 1cmake_minimum_required(VERSION 3.20)
2 2
3project(hello) 3project(hello)
4 4
5add_executable(hello 5add_executable(hello
6 main.cc) 6 main.c)
7 7
8target_link_libraries(hello PRIVATE 8target_link_libraries(hello PRIVATE
9 dxg 9 app
10 dxwindow) 10 dxg)
diff --git a/hello/main.c b/hello/main.c
new file mode 100644
index 0000000..738f5c0
--- /dev/null
+++ b/hello/main.c
@@ -0,0 +1,430 @@
1#include <dxg/dxcommon.h>
2#include <dxwindow.h>
3
4#include <assert.h>
5#include <stdio.h>
6
7#define SWAP_CHAIN_BUFFER_COUNT 2 // Double-buffering.
8
9typedef struct D3DSettings
10{
11 int width;
12 int height;
13} D3DSettings;
14
15typedef struct D3D {
16 Window* pWindow;
17 D3DSettings settings;
18
19 IDXGIFactory4* pDxgiFactory;
20 ID3D12Device* pDevice;
21
22 ID3D12CommandQueue* pCommandQueue;
23 ID3D12CommandAllocator* pCommandAllocator;
24 ID3D12GraphicsCommandList* pCommandList;
25
26 IDXGISwapChain3* pSwapChain;
27
28 ID3D12DescriptorHeap* pRtvHeap;
29 ID3D12DescriptorHeap* pDsvHeap;
30
31 ID3D12Resource* pSwapChainBuffer[SWAP_CHAIN_BUFFER_COUNT];
32 ID3D12Resource* pDepthStencilBuffer;
33
34 ID3D12Fence* pFence;
35 HANDLE fence_event;
36 UINT64 fence_value;
37
38 UINT rtv_descriptor_size;
39 UINT dsv_descriptor_size;
40 UINT cbv_descriptor_size;
41} D3D;
42
43/// Creates the application's swap chain.
44///
45/// This method can be called multiple times to re-create the swap chain.
46static void d3d_create_swap_chain(D3D* d3d) {
47 assert(d3d);
48 assert(d3d->pDxgiFactory);
49 assert(d3d->pCommandQueue);
50
51 SafeRelease(d3d->pSwapChain);
52
53 DXGI_SWAP_CHAIN_DESC1 desc = (DXGI_SWAP_CHAIN_DESC1){
54 .Width = (UINT)(d3d->settings.width),
55 .Height = (UINT)(d3d->settings.height),
56 .Format = DXGI_FORMAT_R8G8B8A8_UNORM,
57 .SampleDesc = (DXGI_SAMPLE_DESC){
58 .Count = 1,
59 .Quality = 0,
60 },
61 .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
62 .BufferCount = SWAP_CHAIN_BUFFER_COUNT,
63 .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
64 };
65 IDXGISwapChain1* pSwapChain = 0;
66 TrapIfFailed(d3d->pDxgiFactory->lpVtbl->CreateSwapChainForHwnd(
67 d3d->pDxgiFactory,
68 (IUnknown*)d3d->pCommandQueue, // Swap chain uses queue to perform flush.
69 window_handle(d3d->pWindow),
70 &desc,
71 /*pFullScreenDesc=*/0, // Running in windowed mode.
72 /*pRestrictToOutput=*/0,
73 &pSwapChain));
74 //TrapIfFailed(pSwapChain.As(&d3d->pSwapChain));
75 d3d->pSwapChain = (IDXGISwapChain3*)pSwapChain;
76}
77
78/// Creates RTVs for all of the swap chain's buffers.
79static void d3d_create_swap_chain_buffer_render_target_views(D3D* d3d) {
80 assert(d3d);
81 assert(d3d->pDevice);
82 assert(d3d->pSwapChain);
83 assert(d3d->pRtvHeap);
84
85 // Create the new buffer views.
86 D3D12_CPU_DESCRIPTOR_HANDLE rtv_heap_handle;
87 d3d->pRtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pRtvHeap, &rtv_heap_handle);
88 for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
89 {
90 TrapIfFailed(d3d->pSwapChain->lpVtbl->GetBuffer(
91 d3d->pSwapChain, i, &IID_ID3D12Resource, &d3d->pSwapChainBuffer[i]));
92
93 d3d->pDevice->lpVtbl->CreateRenderTargetView(
94 d3d->pDevice, d3d->pSwapChainBuffer[i], /*pDesc=*/0, rtv_heap_handle);
95
96 rtv_heap_handle = OFFSET_HANDLE(rtv_heap_handle, 1, d3d->rtv_descriptor_size);
97 }
98}
99
100static D3D12_CPU_DESCRIPTOR_HANDLE d3d_get_depth_stencil_view(D3D* d3d) {
101 assert(d3d);
102 assert(d3d->pDsvHeap);
103 D3D12_CPU_DESCRIPTOR_HANDLE handle;
104 d3d->pDsvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pDsvHeap, &handle);
105 return handle;
106}
107
108/// Creates a depth/stencil buffer and its view.
109static void d3d_create_depth_stencil_buffer_and_view(D3D* d3d) {
110 assert(d3d);
111 assert(d3d->pDevice);
112
113 const D3D12_RESOURCE_DESC depth_stencil_desc = (D3D12_RESOURCE_DESC){
114 .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
115 .Alignment = 0,
116 .Width = (UINT64)(d3d->settings.width),
117 .Height = (UINT)(d3d->settings.height),
118 .DepthOrArraySize = 1,
119 .MipLevels = 1,
120 .Format = DXGI_FORMAT_D24_UNORM_S8_UINT,
121 .SampleDesc = (DXGI_SAMPLE_DESC){
122 .Count = 1,
123 .Quality = 0,
124 },
125 .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
126 .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
127 };
128 const D3D12_CLEAR_VALUE opt_clear_value = (D3D12_CLEAR_VALUE){
129 .Format = depth_stencil_desc.Format,
130 .DepthStencil = (D3D12_DEPTH_STENCIL_VALUE){
131 .Depth = 1.0f,
132 .Stencil = 0,
133 },
134 };
135 const D3D12_HEAP_PROPERTIES depth_stencil_heap_properties = (D3D12_HEAP_PROPERTIES){
136 .Type = D3D12_HEAP_TYPE_DEFAULT,
137 .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
138 .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
139 .CreationNodeMask = 1,
140 .VisibleNodeMask = 1,
141 };
142 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommittedResource(
143 d3d->pDevice,
144 &depth_stencil_heap_properties,
145 D3D12_HEAP_FLAG_NONE,
146 &depth_stencil_desc,
147 D3D12_RESOURCE_STATE_COMMON,
148 &opt_clear_value,
149 &IID_ID3D12Resource,
150 &d3d->pDepthStencilBuffer));
151
152 d3d->pDevice->lpVtbl->CreateDepthStencilView(
153 d3d->pDevice,
154 d3d->pDepthStencilBuffer,
155 /*pDesc=*/0,
156 d3d_get_depth_stencil_view(d3d));
157}
158
159/// Creates RTV and DSV descriptor heaps.
160static void d3d_create_descriptor_heaps(D3D* d3d) {
161 assert(d3d);
162 assert(d3d->pDevice);
163
164 // The RTV heap must hold as many descriptors as we have buffers in the
165 // swap chain.
166 const D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = (D3D12_DESCRIPTOR_HEAP_DESC){
167 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
168 .NumDescriptors = SWAP_CHAIN_BUFFER_COUNT,
169 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
170 .NodeMask = 0,
171 };
172 TrapIfFailed(d3d->pDevice->lpVtbl->CreateDescriptorHeap(
173 d3d->pDevice, &rtv_heap_desc, &IID_ID3D12DescriptorHeap, &d3d->pRtvHeap));
174
175 // For the depth/stencil buffer, we just need one view.
176 const D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc = (D3D12_DESCRIPTOR_HEAP_DESC){
177 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
178 .NumDescriptors = 1,
179 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
180 .NodeMask = 0,
181 };
182 TrapIfFailed(d3d->pDevice->lpVtbl->CreateDescriptorHeap(
183 d3d->pDevice, &dsv_heap_desc, &IID_ID3D12DescriptorHeap, &d3d->pDsvHeap));
184}
185
186static void d3d_init(D3D* d3d, Window* pWindow, const D3DSettings* pSettings) {
187 assert(d3d);
188 assert(pWindow);
189 assert(pSettings);
190
191 d3d->pWindow = pWindow;
192 d3d->settings = *pSettings;
193
194 UINT dxgiFactoryFlags = 0;
195#ifdef DEBUG
196 ID3D12Debug* debug = 0;
197 D3D12GetDebugInterface(&IID_ID3D12Debug, (&debug));
198 debug->EnableDebugLayer();
199 dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
200#endif
201 TrapIfFailed(CreateDXGIFactory2(
202 dxgiFactoryFlags, &IID_IDXGIFactory4, &d3d->pDxgiFactory));
203
204 // Prevent Alt+Enter from going into fullscreen.
205 TrapIfFailed(d3d->pDxgiFactory->lpVtbl->MakeWindowAssociation(
206 d3d->pDxgiFactory,
207 window_handle(d3d->pWindow),
208 DXGI_MWA_NO_ALT_ENTER));
209
210 TrapIfFailed(D3D12CreateDevice(
211 /*pAdapter=*/0, // Default adapter.
212 D3D_FEATURE_LEVEL_11_0,
213 &IID_ID3D12Device,
214 &d3d->pDevice));
215
216 d3d->rtv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize(
217 d3d->pDevice,
218 D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
219 d3d->dsv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize(
220 d3d->pDevice,
221 D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
222 d3d->cbv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize(
223 d3d->pDevice,
224 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
225
226 const D3D12_COMMAND_QUEUE_DESC queue_desc = (D3D12_COMMAND_QUEUE_DESC){
227 .Type = D3D12_COMMAND_LIST_TYPE_DIRECT,
228 .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
229 };
230 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandQueue(
231 d3d->pDevice,
232 &queue_desc,
233 &IID_ID3D12CommandQueue,
234 &d3d->pCommandQueue));
235
236 // The command allocator is the memory backing for the command list.
237 // It is in the allocator's memory where the commands are stored.
238 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandAllocator(
239 d3d->pDevice,
240 queue_desc.Type,
241 &IID_ID3D12CommandAllocator,
242 &d3d->pCommandAllocator));
243
244 TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandList(
245 d3d->pDevice,
246 /*nodeMask=*/0,
247 queue_desc.Type,
248 d3d->pCommandAllocator,
249 /*pInitialState=*/0, // Pipeline state.
250 &IID_ID3D12CommandList,
251 &d3d->pCommandList));
252
253 // Command lists are in the "open" state after they are created. It is
254 // easier to assume that they start in the "closed" state at each
255 // iteration of the main loop, however. The Reset() method, which we'll
256 // use later, also expects the command list to be closed.
257 TrapIfFailed(d3d->pCommandList->lpVtbl->Close(d3d->pCommandList));
258
259 d3d_create_descriptor_heaps(d3d);
260
261 d3d_create_swap_chain(d3d);
262 d3d_create_swap_chain_buffer_render_target_views(d3d);
263 d3d_create_depth_stencil_buffer_and_view(d3d);
264
265 TrapIfFailed(d3d->pDevice->lpVtbl->CreateFence(
266 d3d->pDevice,
267 d3d->fence_value,
268 D3D12_FENCE_FLAG_NONE,
269 &IID_ID3D12Fence,
270 &d3d->pFence));
271
272 if ((d3d->fence_event = CreateEvent(0, FALSE, FALSE, 0)) == 0) {
273 TrapIfFailed(HRESULT_FROM_WIN32(GetLastError()));
274 }
275}
276
277static D3D12_CPU_DESCRIPTOR_HANDLE d3d_get_current_back_buffer_view(const D3D* d3d) {
278 assert(d3d);
279 assert(d3d->pRtvHeap);
280 assert(d3d->rtv_descriptor_size > 0);
281 D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle;
282 d3d->pRtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pRtvHeap, &rtv_handle);
283 return CD3DX12_CPU_DESCRIPTOR_HANDLE(
284 rtv_handle,
285 d3d->pSwapChain->lpVtbl->GetCurrentBackBufferIndex(d3d->pSwapChain),
286 d3d->rtv_descriptor_size);
287}
288
289static ID3D12Resource* d3d_get_current_back_buffer(const D3D* d3d) {
290 assert(d3d);
291 return d3d->pSwapChainBuffer[d3d->pSwapChain->lpVtbl->GetCurrentBackBufferIndex(d3d->pSwapChain)];
292}
293
294static void d3d_populate_command_list(D3D* d3d) {
295 assert(d3d);
296
297 /// Note that we skip the following two items:
298 ///
299 /// 1. RSSetViewports()
300 /// 2. OMSetRenderTargets()
301 ///
302 /// This application does not render anything useful, it simply clears
303 /// the back buffer and depth/stencil view. Clearing both resources
304 /// does not require a viewport to be set or the OM (output-merger
305 /// stage) to be configured.
306
307 // A command allocator can only be reset when its associated command
308 // lists are finished executing on the GPU. This requires
309 // synchronisation.
310 TrapIfFailed(d3d->pCommandAllocator->lpVtbl->Reset(d3d->pCommandAllocator));
311
312 // A command list can be reset as soon as it is executed with
313 // ExecuteCommandList(). Reset() does require that the command list is
314 // in a "closed" state, however, which is why we Close() it right away
315 // after creation.
316 TrapIfFailed(d3d->pCommandList->lpVtbl->Reset(
317 d3d->pCommandList,
318 d3d->pCommandAllocator,
319 /*pInitialState=*/0));
320
321 // Indicate that we intend to use the back buffer as a render target.
322 const D3D12_RESOURCE_BARRIER render_barrier = CD3DX12_RESOURCE_BARRIER_Transition(
323 d3d_get_current_back_buffer(d3d),
324 D3D12_RESOURCE_STATE_PRESENT,
325 D3D12_RESOURCE_STATE_RENDER_TARGET);
326 d3d->pCommandList->lpVtbl->ResourceBarrier(d3d->pCommandList, 1, &render_barrier);
327
328 // Record commands.
329 const float clear_colour[] = { 0.0f, 0.502f, 0.494f, 0.0f };
330 d3d->pCommandList->lpVtbl->ClearRenderTargetView(
331 d3d->pCommandList,
332 d3d_get_current_back_buffer_view(d3d),
333 clear_colour,
334 0, // Number of rectangles in the following array.
335 0); // No rectangles; clear the entire resource.
336
337 d3d->pCommandList->lpVtbl->ClearDepthStencilView(
338 d3d->pCommandList,
339 d3d_get_depth_stencil_view(d3d),
340 D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
341 1.0f, // Depth.
342 0, // Stencil.
343 0, // Number of rectangles in the following array.
344 0); // No rectangles; clear the entire resource view.
345
346 // Indicate that we now intend to use the back buffer to present.
347 const D3D12_RESOURCE_BARRIER present_barrier = CD3DX12_RESOURCE_BARRIER_Transition(
348 d3d_get_current_back_buffer(d3d),
349 D3D12_RESOURCE_STATE_RENDER_TARGET,
350 D3D12_RESOURCE_STATE_PRESENT);
351 d3d->pCommandList->lpVtbl->ResourceBarrier(d3d->pCommandList, 1, &present_barrier);
352
353 // A command list must be closed before it can be executed.
354 TrapIfFailed(d3d->pCommandList->lpVtbl->Close(d3d->pCommandList));
355}
356
357static void d3d_wait_for_previous_frame(D3D* d3d) {
358 assert(d3d);
359
360 // Advance the fence value to mark commands up to this fence point.
361 d3d->fence_value++;
362
363 // The command queue will signal the new fence value when all commands
364 // up to this point have finished execution.
365 TrapIfFailed(d3d->pCommandQueue->lpVtbl->Signal(
366 d3d->pCommandQueue, d3d->pFence, d3d->fence_value));
367
368 // Wait for commands to finish execution.
369 // It is possible that execution has already finished by the time we
370 // get here, so first check the fence's completed value.
371 if (d3d->pFence->lpVtbl->GetCompletedValue(d3d->pFence) < d3d->fence_value) {
372 // Commands are still being executed. Configure a Windows event
373 // and wait for it. The event fires when the commands have finished
374 // execution.
375
376 // Indicate that |fence_event| is to be fired when |fence|
377 // reaches the new fence value.
378 TrapIfFailed(d3d->pFence->lpVtbl->SetEventOnCompletion(
379 d3d->pFence, d3d->fence_value, d3d->fence_event));
380
381 // Will wake up when the fence takes on the new fence value.
382 WaitForSingleObject(d3d->fence_event, INFINITE);
383 }
384}
385
386static void d3d_render(D3D* d3d) {
387 assert(d3d);
388
389 d3d_populate_command_list(d3d);
390
391 ID3D12CommandList* command_lists[] = { (ID3D12CommandList*)d3d->pCommandList };
392 d3d->pCommandQueue->lpVtbl->ExecuteCommandLists(
393 d3d->pCommandQueue, _countof(command_lists), command_lists);
394
395 TrapIfFailed(d3d->pSwapChain->lpVtbl->Present(
396 d3d->pSwapChain, /*SyncInterval=*/1, /*Flags=*/0));
397
398 // It is not efficient to wait for the frame to complete here, but it
399 // is simple and sufficient for this application.
400 d3d_wait_for_previous_frame(d3d);
401}
402
403int main(int argc, const char** argv) {
404 const D3DSettings settings = (D3DSettings){
405 .width = 1920,
406 .height = 1200,
407 };
408
409 if (!window_global_init()) {
410 TRAP("Failed to initialise the window subsystem");
411 }
412
413 Window* window = window_init(settings.width, settings.height, "D3D Application");
414 if (!window) {
415 TRAP(window_get_error());
416 }
417
418 D3D d3d = {0};
419 d3d_init(&d3d, window, &settings);
420
421 while (!window_should_close(window))
422 {
423 window_update(window);
424 d3d_render(&d3d);
425 Sleep(10);
426 }
427
428 window_global_quit();
429 return 0;
430}
diff --git a/hello/main.cc b/hello/main.cc
deleted file mode 100644
index d9040b4..0000000
--- a/hello/main.cc
+++ /dev/null
@@ -1,433 +0,0 @@
1#include <dxcommon.h>
2
3#include <cassert>
4#include <cstdio>
5
6import dxcommon;
7import dxwindow;
8
9using namespace dx;
10using Microsoft::WRL::ComPtr;
11
12struct D3DSettings
13{
14 int width = 0;
15 int height = 0;
16};
17
18class D3D
19{
20public:
21 void Initialise(Window* window, const D3DSettings& settings)
22 {
23 m_window = window;
24 m_settings = settings;
25
26 UINT dxgiFactoryFlags = 0;
27#ifdef DEBUG
28 {
29 ComPtr<ID3D12Debug> debug;
30 D3D12GetDebugInterface(IID_PPV_ARGS(&debug));
31 debug->EnableDebugLayer();
32 dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
33 }
34#endif
35 ThrowIfFailed(CreateDXGIFactory2(
36 dxgiFactoryFlags, IID_PPV_ARGS(&m_dxgi_factory)));
37
38 // Prevent Alt+Enter from going into fullscreen.
39 ThrowIfFailed(m_dxgi_factory->MakeWindowAssociation(
40 m_window->GetWindowHandle(),
41 DXGI_MWA_NO_ALT_ENTER));
42
43 ThrowIfFailed(D3D12CreateDevice(
44 /*pAdapter=*/nullptr, // Default adapter.
45 D3D_FEATURE_LEVEL_11_0,
46 IID_PPV_ARGS(&m_device)));
47
48 m_rtv_descriptor_size = m_device->GetDescriptorHandleIncrementSize(
49 D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
50 m_dsv_descriptor_size = m_device->GetDescriptorHandleIncrementSize(
51 D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
52 m_cbv_descriptor_size = m_device->GetDescriptorHandleIncrementSize(
53 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
54
55 const D3D12_COMMAND_QUEUE_DESC queue_desc
56 {
57 .Type = D3D12_COMMAND_LIST_TYPE_DIRECT,
58 .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
59 };
60 ThrowIfFailed(m_device->CreateCommandQueue(
61 &queue_desc,
62 IID_PPV_ARGS(&m_command_queue)));
63
64 // The command allocator is the memory backing for the command list.
65 // It is in the allocator's memory where the commands are stored.
66 ThrowIfFailed(m_device->CreateCommandAllocator(
67 queue_desc.Type,
68 IID_PPV_ARGS(&m_command_allocator)));
69
70 ThrowIfFailed(m_device->CreateCommandList(
71 /*nodeMask=*/0,
72 queue_desc.Type,
73 m_command_allocator.Get(),
74 /*pInitialState=*/nullptr, // Pipeline state.
75 IID_PPV_ARGS(&m_command_list)));
76
77 // Command lists are in the "open" state after they are created. It is
78 // easier to assume that they start in the "closed" state at each
79 // iteration of the main loop, however. The Reset() method, which we'll
80 // use later, also expects the command list to be closed.
81 ThrowIfFailed(m_command_list->Close());
82
83 CreateDescriptorHeaps();
84
85 CreateSwapChain();
86 CreateSwapChainBufferRenderTargetViews();
87 CreateDepthStencilBufferAndView();
88
89 ThrowIfFailed(m_device->CreateFence(
90 /*InitialValue=*/m_fence_value,
91 D3D12_FENCE_FLAG_NONE,
92 IID_PPV_ARGS(&m_fence)));
93
94 if ((m_fence_event = CreateEvent(
95 /*lpEventAttributes=*/nullptr,
96 /*bManualReset=*/FALSE,
97 /*bInitialState=*/FALSE,
98 /*lpName=*/nullptr)) == 0)
99 {
100 ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
101 }
102 }
103
104 void Render()
105 {
106 PopulateCommandList();
107
108 ID3D12CommandList* command_lists[] { m_command_list.Get() };
109 m_command_queue->ExecuteCommandLists(
110 _countof(command_lists), command_lists);
111
112 ThrowIfFailed(m_swap_chain->Present(/*SyncInterval=*/1, /*Flags=*/0));
113
114 // It is not efficient to wait for the frame to complete here, but it
115 // is simple and sufficient for this application.
116 WaitForPreviousFrame();
117 }
118
119private:
120 void PopulateCommandList()
121 {
122 /// Note that we skip the following two items:
123 ///
124 /// 1. RSSetViewports()
125 /// 2. OMSetRenderTargets()
126 ///
127 /// This application does not render anything useful, it simply clears
128 /// the back buffer and depth/stencil view. Clearing both resources
129 /// does not require a viewport to be set or the OM (output-merger
130 /// stage) to be configured.
131
132 // A command allocator can only be reset when its associated command
133 // lists are finished executing on the GPU. This requires
134 // synchronisation.
135 ThrowIfFailed(m_command_allocator->Reset());
136
137 // A command list can be reset as soon as it is executed with
138 // ExecuteCommandList(). Reset() does require that the command list is
139 // in a "closed" state, however, which is why we Close() it right away
140 // after creation.
141 ThrowIfFailed(m_command_list->Reset(
142 m_command_allocator.Get(),
143 /*pInitialState=*/nullptr));
144
145 // Indicate that we intend to use the back buffer as a render target.
146 const auto render_barrier = CD3DX12_RESOURCE_BARRIER::Transition(
147 GetCurrentBackBuffer(),
148 D3D12_RESOURCE_STATE_PRESENT,
149 D3D12_RESOURCE_STATE_RENDER_TARGET);
150 m_command_list->ResourceBarrier(1, &render_barrier);
151
152 // Record commands.
153 const float clear_colour[] { 0.0f, 0.502f, 0.494f, 0.0f };
154 m_command_list->ClearRenderTargetView(
155 GetCurrentBackBufferView(),
156 clear_colour,
157 0, // Number of rectangles in the following array.
158 nullptr); // No rectangles; clear the entire resource.
159
160 m_command_list->ClearDepthStencilView(
161 GetDepthStencilView(),
162 D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
163 1.0f, // Depth.
164 0, // Stencil.
165 0, // Number of rectangles in the following array.
166 nullptr); // No rectangles; clear the entire resource view.
167
168 // Indicate that we now intend to use the back buffer to present.
169 const auto present_barrier = CD3DX12_RESOURCE_BARRIER::Transition(
170 GetCurrentBackBuffer(),
171 D3D12_RESOURCE_STATE_RENDER_TARGET,
172 D3D12_RESOURCE_STATE_PRESENT);
173 m_command_list->ResourceBarrier(1, &present_barrier);
174
175 // A command list must be closed before it can be executed.
176 ThrowIfFailed(m_command_list->Close());
177 }
178
179 void WaitForPreviousFrame()
180 {
181 // Advance the fence value to mark commands up to this fence point.
182 m_fence_value++;
183
184 // The command queue will signal the new fence value when all commands
185 // up to this point have finished execution.
186 ThrowIfFailed(m_command_queue->Signal(m_fence.Get(), m_fence_value));
187
188 // Wait for commands to finish execution.
189 // It is possible that execution has already finished by the time we
190 // get here, so first check the fence's completed value.
191 if (m_fence->GetCompletedValue() < m_fence_value)
192 {
193 // Commands are still being executed. Configure a Windows event
194 // and wait for it. The event fires when the commands have finished
195 // execution.
196
197 // Indicate that |m_fence_event| is to be fired when |m_fence|
198 // reaches the new fence value.
199 ThrowIfFailed(m_fence->SetEventOnCompletion(
200 m_fence_value, m_fence_event));
201
202 // Will wake up when the fence takes on the new fence value.
203 WaitForSingleObject(m_fence_event, INFINITE);
204 }
205 }
206
207 /// Creates RTV and DSV descriptor heaps.
208 void CreateDescriptorHeaps()
209 {
210 assert(m_device);
211
212 // The RTV heap must hold as many descriptors as we have buffers in the
213 // swap chain.
214 const D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc
215 {
216 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
217 .NumDescriptors = SWAP_CHAIN_BUFFER_COUNT,
218 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
219 .NodeMask = 0,
220 };
221 ThrowIfFailed(m_device->CreateDescriptorHeap(
222 &rtv_heap_desc, IID_PPV_ARGS(&m_rtv_heap)));
223
224 // For the depth/stencil buffer, we just need one view.
225 const D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc
226 {
227 .Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
228 .NumDescriptors = 1,
229 .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
230 .NodeMask = 0,
231 };
232 ThrowIfFailed(m_device->CreateDescriptorHeap(
233 &dsv_heap_desc, IID_PPV_ARGS(&m_dsv_heap)));
234 }
235
236 /// Creates the application's swap chain.
237 ///
238 /// This method can be called multiple times to re-create the swap chain.
239 void CreateSwapChain()
240 {
241 assert(m_dxgi_factory);
242 assert(m_command_queue);
243
244 SafeRelease(m_swap_chain);
245
246 DXGI_SWAP_CHAIN_DESC1 desc
247 {
248 .Width = static_cast<UINT>(m_settings.width),
249 .Height = static_cast<UINT>(m_settings.height),
250 .Format = DXGI_FORMAT_R8G8B8A8_UNORM,
251 .SampleDesc = DXGI_SAMPLE_DESC
252 {
253 .Count = 1,
254 .Quality = 0,
255 },
256 .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
257 .BufferCount = SWAP_CHAIN_BUFFER_COUNT,
258 .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
259 };
260 ComPtr<IDXGISwapChain1> swap_chain;
261 ThrowIfFailed(m_dxgi_factory->CreateSwapChainForHwnd(
262 m_command_queue.Get(), // Swap chain uses queue to perform flush.
263 m_window->GetWindowHandle(),
264 &desc,
265 /*pFullScreenDesc=*/nullptr, // Running in windowed mode.
266 /*pRestrictToOutput=*/nullptr,
267 &swap_chain));
268 ThrowIfFailed(swap_chain.As(&m_swap_chain));
269 }
270
271 /// Creates RTVs for all of the swap chain's buffers.
272 void CreateSwapChainBufferRenderTargetViews()
273 {
274 assert(m_device);
275 assert(m_swap_chain);
276 assert(m_rtv_heap);
277
278 // Create the new buffer views.
279 CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_heap_handle(
280 m_rtv_heap->GetCPUDescriptorHandleForHeapStart());
281 for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
282 {
283 ThrowIfFailed(m_swap_chain->GetBuffer(
284 i, IID_PPV_ARGS(&m_swap_chain_buffer[i])));
285
286 m_device->CreateRenderTargetView(
287 m_swap_chain_buffer[i].Get(), /*pDesc=*/nullptr, rtv_heap_handle);
288
289 rtv_heap_handle.Offset(1, m_rtv_descriptor_size);
290 }
291 }
292
293 /// Creates a depth/stencil buffer and its view.
294 void CreateDepthStencilBufferAndView()
295 {
296 assert(m_device);
297
298 const D3D12_RESOURCE_DESC depth_stencil_desc
299 {
300 .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
301 .Alignment = 0,
302 .Width = static_cast<UINT64>(m_settings.width),
303 .Height = static_cast<UINT>(m_settings.height),
304 .DepthOrArraySize = 1,
305 .MipLevels = 1,
306 .Format = DXGI_FORMAT_D24_UNORM_S8_UINT,
307 .SampleDesc = DXGI_SAMPLE_DESC
308 {
309 .Count = 1,
310 .Quality = 0,
311 },
312 .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
313 .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
314 };
315 const D3D12_CLEAR_VALUE opt_clear_value
316 {
317 .Format = depth_stencil_desc.Format,
318 .DepthStencil = D3D12_DEPTH_STENCIL_VALUE
319 {
320 .Depth = 1.0f,
321 .Stencil = 0,
322 },
323 };
324 const CD3DX12_HEAP_PROPERTIES depth_stencil_heap_properties(
325 D3D12_HEAP_TYPE_DEFAULT);
326 ThrowIfFailed(m_device->CreateCommittedResource(
327 &depth_stencil_heap_properties,
328 D3D12_HEAP_FLAG_NONE,
329 &depth_stencil_desc,
330 D3D12_RESOURCE_STATE_COMMON,
331 &opt_clear_value,
332 IID_PPV_ARGS(&m_depth_stencil_buffer)));
333
334 m_device->CreateDepthStencilView(
335 m_depth_stencil_buffer.Get(),
336 /*pDesc=*/nullptr,
337 GetDepthStencilView());
338 }
339
340 ID3D12Resource* GetCurrentBackBuffer() const
341 {
342 return m_swap_chain_buffer[m_swap_chain->GetCurrentBackBufferIndex()].Get();
343 }
344
345 D3D12_CPU_DESCRIPTOR_HANDLE GetCurrentBackBufferView() const
346 {
347 assert(m_rtv_heap);
348 assert(m_rtv_descriptor_size > 0);
349 return CD3DX12_CPU_DESCRIPTOR_HANDLE(
350 m_rtv_heap->GetCPUDescriptorHandleForHeapStart(),
351 m_swap_chain->GetCurrentBackBufferIndex(),
352 m_rtv_descriptor_size);
353 }
354
355 D3D12_CPU_DESCRIPTOR_HANDLE GetDepthStencilView() const
356 {
357 assert(m_dsv_heap);
358 return m_dsv_heap->GetCPUDescriptorHandleForHeapStart();
359 }
360
361private:
362 static constexpr int SWAP_CHAIN_BUFFER_COUNT = 2; // Double-buffering.
363
364 Window* m_window = nullptr;
365 D3DSettings m_settings;
366
367 ComPtr<IDXGIFactory4> m_dxgi_factory;
368 ComPtr<ID3D12Device> m_device;
369
370 ComPtr<ID3D12CommandQueue> m_command_queue;
371 ComPtr<ID3D12CommandAllocator> m_command_allocator;
372 ComPtr<ID3D12GraphicsCommandList> m_command_list;
373
374 ComPtr<IDXGISwapChain3> m_swap_chain;
375
376 ComPtr<ID3D12DescriptorHeap> m_rtv_heap;
377 ComPtr<ID3D12DescriptorHeap> m_dsv_heap;
378
379 ComPtr<ID3D12Resource> m_swap_chain_buffer[SWAP_CHAIN_BUFFER_COUNT];
380 ComPtr<ID3D12Resource> m_depth_stencil_buffer;
381
382 ComPtr<ID3D12Fence> m_fence;
383 HANDLE m_fence_event = 0;
384 UINT64 m_fence_value = 0;
385
386 UINT m_rtv_descriptor_size = 0;
387 UINT m_dsv_descriptor_size = 0;
388 UINT m_cbv_descriptor_size = 0;
389};
390
391int main()
392{
393 try
394 {
395 const D3DSettings settings =
396 {
397 // TODO: use 960x600 or 1920x1200 depending on native resolution.
398 .width = 1920,
399 .height = 1200,
400 };
401
402 if (!WindowInitialise())
403 {
404 THROW("Failed to initialise the window subsystem");
405 }
406 {
407 Window window;
408 if (!window.Initialise(
409 settings.width, settings.height, /*title=*/"D3D Application"))
410 {
411 THROW(GetWindowError());
412 }
413
414 D3D d3d;
415 d3d.Initialise(&window, settings);
416
417 while (!window.ShouldClose())
418 {
419 window.Update();
420 d3d.Render();
421 Sleep(10);
422 }
423 }
424 WindowTerminate();
425
426 return 0;
427 }
428 catch (const std::exception& e)
429 {
430 fprintf(stderr, "Exception caught: %s\n", e.what());
431 return 1;
432 }
433}