aboutsummaryrefslogtreecommitdiff
path: root/hello/main.cc
diff options
context:
space:
mode:
Diffstat (limited to 'hello/main.cc')
-rw-r--r--hello/main.cc439
1 files changed, 439 insertions, 0 deletions
diff --git a/hello/main.cc b/hello/main.cc
new file mode 100644
index 0000000..1b94cf6
--- /dev/null
+++ b/hello/main.cc
@@ -0,0 +1,439 @@
1#include <dxcommon.h>
2#include <dxwindow.h>
3
4#include <d3d12.h>
5#include <dxgi1_4.h>
6#include <directx/d3dx12.h>
7
8#include <cassert>
9#include <cstdio>
10
11using namespace dx;
12
13struct D3DSettings
14{
15 int width = 0;
16 int height = 0;
17};
18
19class D3D
20{
21public:
22 void Initialise(Window* window, const D3DSettings& settings)
23 {
24 m_window = window;
25 m_settings = settings;
26
27 UINT dxgiFactoryFlags = 0;
28#ifdef DEBUG
29 {
30 ComPtr<ID3D12Debug> debug;
31 D3D12GetDebugInterface(IID_PPV_ARGS(&debug));
32 debug->EnableDebugLayer();
33 dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
34 }
35#endif
36 ThrowIfFailed(CreateDXGIFactory2(
37 dxgiFactoryFlags, IID_PPV_ARGS(&m_dxgi_factory)));
38
39 // Prevent Alt+Enter from going into fullscreen.
40 ThrowIfFailed(m_dxgi_factory->MakeWindowAssociation(
41 m_window->GetWindowHandle(),
42 DXGI_MWA_NO_ALT_ENTER));
43
44 ThrowIfFailed(D3D12CreateDevice(
45 /*pAdapter=*/nullptr, // Default adapter.
46 D3D_FEATURE_LEVEL_11_0,
47 IID_PPV_ARGS(&m_device)));
48
49 m_rtv_descriptor_size = m_device->GetDescriptorHandleIncrementSize(
50 D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
51 m_dsv_descriptor_size = m_device->GetDescriptorHandleIncrementSize(
52 D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
53 m_cbv_descriptor_size = m_device->GetDescriptorHandleIncrementSize(
54 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
55
56 const D3D12_COMMAND_QUEUE_DESC queue_desc =
57 {
58 .Type = D3D12_COMMAND_LIST_TYPE_DIRECT,
59 .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
60 };
61 ThrowIfFailed(m_device->CreateCommandQueue(
62 &queue_desc,
63 IID_PPV_ARGS(&m_command_queue)));
64
65 ThrowIfFailed(m_device->CreateCommandAllocator(
66 queue_desc.Type,
67 IID_PPV_ARGS(&m_command_allocator)));
68
69 // The command allocator is the memory backing for the command list.
70 // It is in the allocator's memory where the commands are stored.
71 ThrowIfFailed(m_device->CreateCommandList(
72 /*nodeMask=*/0,
73 queue_desc.Type,
74 m_command_allocator.Get(),
75 /*pInitialState=*/nullptr, // Pipeline state.
76 IID_PPV_ARGS(&m_command_list)));
77
78 // Command lists are in the "open" state after they are created. It is
79 // easier to assume that they start in the "closed" state at each
80 // iteration of the main loop, however. The Reset() method, which we'll
81 // use later, also expects the command list to be closed.
82 ThrowIfFailed(m_command_list->Close());
83
84 CreateDescriptorHeaps();
85
86 CreateSwapChain();
87 CreateSwapChainBufferRenderTargetViews();
88 CreateDepthStencilBufferAndView();
89
90 ThrowIfFailed(m_device->CreateFence(
91 /*InitialValue=*/m_fence_value,
92 D3D12_FENCE_FLAG_NONE,
93 IID_PPV_ARGS(&m_fence)));
94
95 if ((m_fence_event = CreateEvent(
96 /*lpEventAttributes=*/nullptr,
97 /*bManualReset=*/FALSE,
98 /*bInitialState=*/FALSE,
99 /*lpName=*/nullptr)) == 0)
100 {
101 ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
102 }
103 }
104
105 void Render()
106 {
107 PopulateCommandList();
108
109 ID3D12CommandList* command_lists[] = { m_command_list.Get() };
110 m_command_queue->ExecuteCommandLists(
111 _countof(command_lists), command_lists);
112
113 ThrowIfFailed(m_swap_chain->Present(/*SyncInterval=*/1, /*Flags=*/0));
114 m_current_back_buffer = m_swap_chain->GetCurrentBackBufferIndex();
115
116 // It is not efficient to wait for the frame to complete here, but it
117 // is simple and sufficient for this application.
118 WaitForPreviousFrame();
119 }
120
121private:
122 void PopulateCommandList()
123 {
124 /// Note that we skip the following two items:
125 ///
126 /// 1. RSSetViewports()
127 /// 2. OMSetRenderTargets()
128 ///
129 /// This application does not render anything useful, it simply clears
130 /// the back buffer and depth/stencil view. Clearing both resources
131 /// does not require a viewport to be set or the OM (output-merger
132 /// stage) to be configured.
133
134 // A command allocator can only be reset when its associated command
135 // lists are finished executing on the GPU. This requires
136 // synchronisation.
137 ThrowIfFailed(m_command_allocator->Reset());
138
139 // A command list can be reset as soon as it is executed with
140 // ExecuteCommandList(). Reset() does require that the command list is
141 // in a "closed" state, however, which is why we close it right away
142 // after creation.
143 ThrowIfFailed(m_command_list->Reset(
144 m_command_allocator.Get(),
145 /*pInitialState=*/nullptr));
146
147 // Indicate that we intend to use the back buffer as a render target.
148 const auto render_barrier = CD3DX12_RESOURCE_BARRIER::Transition(
149 GetCurrentBackBuffer(),
150 D3D12_RESOURCE_STATE_PRESENT,
151 D3D12_RESOURCE_STATE_RENDER_TARGET);
152 m_command_list->ResourceBarrier(1, &render_barrier);
153
154 // Record commands.
155 const float clear_colour[] = { 0.0f, 0.0f, 0.0f, 0.0f };
156 m_command_list->ClearRenderTargetView(
157 GetCurrentBackBufferView(),
158 clear_colour,
159 0, // Number of rectangles in the following array.
160 nullptr); // No rectangles; clear the entire resource.
161
162 m_command_list->ClearDepthStencilView(
163 GetDepthStencilView(),
164 D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
165 1.0f, // Depth.
166 0, // Stencil.
167 0, // Number of rectangles in the following array.
168 nullptr); // No rectangles; clear the entire resource view.
169
170 // Indicate that we now intend to use the back buffer to present.
171 const auto present_barrier = CD3DX12_RESOURCE_BARRIER::Transition(
172 GetCurrentBackBuffer(),
173 D3D12_RESOURCE_STATE_RENDER_TARGET,
174 D3D12_RESOURCE_STATE_PRESENT);
175 m_command_list->ResourceBarrier(1, &present_barrier);
176
177 ThrowIfFailed(m_command_list->Close());
178 }
179
180 void WaitForPreviousFrame()
181 {
182 // Advance the fence value to mark commands up to this fence point.
183 m_fence_value++;
184
185 // The command queue will signal the new fence value when all commands
186 // up to this point have finished execution.
187 ThrowIfFailed(m_command_queue->Signal(m_fence.Get(), m_fence_value));
188
189 // Wait for commands to finish execution.
190 // It is possible that execution has already finished by the time we
191 // get here, so first check the fence's completed value.
192 if (m_fence->GetCompletedValue() < m_fence_value)
193 {
194 // Commands are still being executed. Configure a Windows event
195 // and wait for it. The event fires when the commands have finished
196 // execution.
197
198 // Indicate that |m_fence_event| is to be fired when |m_fence|
199 // reaches the |fence| value.
200 ThrowIfFailed(m_fence->SetEventOnCompletion(
201 m_fence_value, m_fence_event));
202
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 m_current_back_buffer = m_swap_chain->GetCurrentBackBufferIndex();
271 }
272
273 /// Creates RTVs for all of the swap chain's buffers.
274 void CreateSwapChainBufferRenderTargetViews()
275 {
276 assert(m_device);
277 assert(m_swap_chain);
278 assert(m_rtv_heap);
279
280 // Create the new buffer views.
281 CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_heap_handle(
282 m_rtv_heap->GetCPUDescriptorHandleForHeapStart());
283 for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
284 {
285 ThrowIfFailed(m_swap_chain->GetBuffer(
286 i, IID_PPV_ARGS(&m_swap_chain_buffer[i])));
287
288 m_device->CreateRenderTargetView(
289 m_swap_chain_buffer[i].Get(), /*pDesc=*/nullptr, rtv_heap_handle);
290
291 rtv_heap_handle.Offset(1, m_rtv_descriptor_size);
292 }
293 }
294
295 /// Creates a depth/stencil buffer and its view.
296 void CreateDepthStencilBufferAndView()
297 {
298 assert(m_device);
299
300 const D3D12_RESOURCE_DESC depth_stencil_desc =
301 {
302 .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
303 .Alignment = 0,
304 .Width = static_cast<UINT64>(m_settings.width),
305 .Height = static_cast<UINT>(m_settings.height),
306 .DepthOrArraySize = 1,
307 .MipLevels = 1,
308 .Format = DXGI_FORMAT_D24_UNORM_S8_UINT,
309 .SampleDesc = DXGI_SAMPLE_DESC
310 {
311 .Count = 1,
312 .Quality = 0,
313 },
314 .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
315 .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
316 };
317 const D3D12_CLEAR_VALUE opt_clear_value =
318 {
319 .Format = depth_stencil_desc.Format,
320 .DepthStencil = D3D12_DEPTH_STENCIL_VALUE
321 {
322 .Depth = 1.0f,
323 .Stencil = 0,
324 },
325 };
326 const CD3DX12_HEAP_PROPERTIES depth_stencil_heap_properties(
327 D3D12_HEAP_TYPE_DEFAULT);
328 ThrowIfFailed(m_device->CreateCommittedResource(
329 &depth_stencil_heap_properties,
330 D3D12_HEAP_FLAG_NONE,
331 &depth_stencil_desc,
332 D3D12_RESOURCE_STATE_COMMON,
333 &opt_clear_value,
334 IID_PPV_ARGS(&m_depth_stencil_buffer)));
335
336 m_device->CreateDepthStencilView(
337 m_depth_stencil_buffer.Get(),
338 /*pDesc=*/nullptr,
339 GetDepthStencilView());
340 }
341
342 ID3D12Resource* GetCurrentBackBuffer() const
343 {
344 return m_swap_chain_buffer[m_current_back_buffer].Get();
345 }
346
347 D3D12_CPU_DESCRIPTOR_HANDLE GetCurrentBackBufferView() const
348 {
349 assert(m_rtv_heap);
350 assert(m_rtv_descriptor_size > 0);
351 return CD3DX12_CPU_DESCRIPTOR_HANDLE(
352 m_rtv_heap->GetCPUDescriptorHandleForHeapStart(),
353 m_current_back_buffer,
354 m_rtv_descriptor_size);
355 }
356
357 D3D12_CPU_DESCRIPTOR_HANDLE GetDepthStencilView() const
358 {
359 assert(m_dsv_heap);
360 return m_dsv_heap->GetCPUDescriptorHandleForHeapStart();
361 }
362
363private:
364 static constexpr int SWAP_CHAIN_BUFFER_COUNT = 2; // Double-buffering.
365
366 Window* m_window = nullptr;
367 D3DSettings m_settings;
368
369 ComPtr<IDXGIFactory4> m_dxgi_factory;
370 ComPtr<ID3D12Device> m_device;
371
372 ComPtr<ID3D12CommandQueue> m_command_queue;
373 ComPtr<ID3D12CommandAllocator> m_command_allocator;
374 ComPtr<ID3D12GraphicsCommandList> m_command_list;
375
376 ComPtr<IDXGISwapChain3> m_swap_chain;
377
378 ComPtr<ID3D12DescriptorHeap> m_rtv_heap;
379 ComPtr<ID3D12DescriptorHeap> m_dsv_heap;
380
381 ComPtr<ID3D12Resource> m_swap_chain_buffer[SWAP_CHAIN_BUFFER_COUNT];
382 ComPtr<ID3D12Resource> m_depth_stencil_buffer;
383
384 ComPtr<ID3D12Fence> m_fence;
385 HANDLE m_fence_event = 0;
386 UINT64 m_fence_value = 0;
387
388 // Index to the buffer in the RTV descriptor heap that represents the
389 // current back buffer.
390 int m_current_back_buffer = 0;
391
392 UINT m_rtv_descriptor_size = 0;
393 UINT m_dsv_descriptor_size = 0;
394 UINT m_cbv_descriptor_size = 0;
395};
396
397int main()
398{
399 try
400 {
401 const D3DSettings settings =
402 {
403 // TODO: use 960x600 or 1920x1200 depending on native resolution.
404 .width = 1920,
405 .height = 1200,
406 };
407
408 if (!WindowInitialise())
409 {
410 THROW("Failed to initialise the window subsystem");
411 }
412 {
413 Window window;
414 if (!window.Initialise(
415 settings.width, settings.height, /*title=*/"D3D Application"))
416 {
417 THROW(GetWindowError());
418 }
419
420 D3D d3d;
421 d3d.Initialise(&window, settings);
422
423 while (!window.ShouldClose())
424 {
425 window.Update();
426 d3d.Render();
427 Sleep(10);
428 }
429 }
430 WindowTerminate();
431
432 return 0;
433 }
434 catch (const std::exception& e)
435 {
436 fprintf(stderr, "Exception caught: %s\n", e.what());
437 return 1;
438 }
439}