· 4 years ago · Feb 09, 2021, 03:12 PM
1#pragma region INCLUDES
2#define WIN32_LEAN_AND_MEAN // This minimizes the number of header files included from Windows.h
3#include <Windows.h> // Used for creating windows etc.
4#include <shellapi.h> // Used for CommandLineToArgvW, is used to parse command-line arguments. (TODO: Delete?)
5
6// The min/max macros conflict with like-named member functions.
7// Only use std::min and std::max defined in <algorithm>
8#if defined(min)
9#undef min
10#endif
11
12#if defined(max)
13#undef max
14#endif
15
16// In order to define a function called CreateWindow, the Windows
17// macro needs to be undefined. This is fine since a version called
18// CreateWindowExW is used to create the OS window.
19#if defined(CreateWindow)
20#undef CreateWindow
21#endif
22
23// Windows Runtime Library. Needed for Microsoft::WRL::ComPtr<> template class.
24// Provides smart pointers to COM objects which DirectX 12 uses.
25#include <wrl.h>
26using namespace Microsoft::WRL;
27
28// DirectX 12 specific headers:
29#include <d3d12.h> // Contains Direct3D 12 objects (device, CommandQueue, CommandList, etc...)
30#include <dxgi1_6.h> // Microsoft DirectX Graphics Infrastructure is used to manage low-level tasks
31// such as enumerating GPU adapters, presenting the rendered image and handling full screen transitions.
32#include <d3dcompiler.h> // Contains functions to compile HLSL shaders.
33#include <DirectXMath.h> // Math library
34
35// D3D12 extension library:
36#include "d3dx12.h" // Helper code basically.
37
38#pragma comment(lib, "d3d12.lib")
39#pragma comment(lib, "DXGI.lib")
40#pragma comment(lib, "d3dcompiler.lib")
41
42// STL Headers:
43#include <algorithm> // Contains std::min and std::max among other things
44#include <cassert> // Assert macro
45#include <chrono> // Contains high_resolution_clock for calculating FPS
46
47// Helper functions:
48#include "Helpers.h"
49
50// Includes for printing (TODO: Delete?):
51#include <iostream>
52using namespace std;
53#pragma endregion
54
55#pragma region VARIABLES
56const uint8_t g_NumFrames = 3; // Number of swap chain back buffers. 2 or more.
57bool g_UseWarp = false; // Use of WARP (Windows Advanced Rasterization Platform) adapter.
58// Use it if the full set of rendering features can be used (not an old GPU)...
59uint32_t g_ClientWidth = 1280; // Window width.
60uint32_t g_ClientHeight = 720; // Window height.
61bool g_IsInitialized = false; // Set to true once the DX12 objects have been initialized.
62
63HWND g_Hwnd; // Window handle.
64RECT g_WindowRect; // Window rectangle (used to toggle fullscreen state). Holds previous window size to change
65// between windowed and full screen mode.
66
67struct Vertex
68{
69 float x, y, z; // Position
70 float r, g, b; // Color
71};
72
73// DirectX 12 Objects:
74ComPtr<ID3D12Device2> g_Device; // DirectX12 device. !! There is far newer versions of this. Might use them instead?
75ComPtr<ID3D12CommandQueue> g_CommandQueue; // Use one queue for now. Executes what a pen wrote on a paper.
76ComPtr<IDXGISwapChain4> g_SwapChain; // Back buffer management. Presents the image in the end.
77ComPtr<ID3D12Resource> g_BackBuffers[g_NumFrames]; // !! There is far newer versions of this. Might use them instead?
78ComPtr<ID3D12GraphicsCommandList> g_CommandList; // !! There is far newer versions of this. Might use them instead?
79// This was "the pen" which "creates" tasks. Could use multiple to run multithreading.
80ComPtr<ID3D12CommandAllocator> g_CommandAllocators[g_NumFrames]; // This was the "paper" upon which we create tasks.
81// All commands within an allocator has to be finished before reusing it. At least one is used per back buffer of the swap chain.
82ComPtr<ID3D12DescriptorHeap> g_RTVDescriptorHeap; // Render target view. Describes the location, dimensions
83// and format of the texture resource in GPU. Stored in descriptor heaps which is an array of descriptors (views).
84// A view means a resource in GPU memory.
85UINT g_RTVDescriptorSize; // This is vendor specific and therefore needs to be queried during initialization.
86UINT g_CurrentBackBufferIndex;
87
88// "Stefan-objekt" (Sista 6 stegen):
89D3D12_VIEWPORT gViewport = {};
90D3D12_RECT gScissorRect = {};
91
92ID3D12RootSignature* g_RootSignature = nullptr;
93ID3D12PipelineState* g_PipelineState = nullptr;
94
95ID3D12Resource1* g_VertexBufferResource = nullptr;
96D3D12_VERTEX_BUFFER_VIEW g_VertexBufferView = {};
97
98struct ConstantBuffer
99{
100 float colorChannel[4];
101};
102
103// Descriptor heaps that isn't for render targets:
104ID3D12DescriptorHeap* g_DescriptorHeap[g_NumFrames] = {};
105ID3D12Resource1* g_ConstantBufferResource[g_NumFrames] = {};
106ConstantBuffer g_ConstantBufferCPU = {};
107
108// Synchronization objects:
109ComPtr<ID3D12Fence> g_Fence; // !! There exists a newer version, might use that.
110uint64_t g_FenceValue = 0;
111uint64_t g_FrameFenceValues[g_NumFrames] = {}; // All frames need to be synchrnized so that no data is overwritten before finalized.
112HANDLE g_FenceEvent; // Used to check wether a fence has reached a specific value.
113
114// Swap chain related variables:
115bool g_VSync = false; // By default V-Sync will be on. Can be toggled with the V key. Forces framerate to match the screens.
116bool g_TearingSupported = false;
117bool g_Fullscreen = false; // By default we use windowed screen. Toggle with Alt+Enter or F11. Alt+Enter is disbled however when we create the swap chain.
118#pragma endregion
119
120#pragma region FUNCTION DECLARATIONS
121// Window callback function:
122LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
123
124void ParseCommandLineArguments()
125{
126 int argc;
127 // :: indicate system functions defined in global scope.
128 wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
129
130 for (size_t i = 0; i < argc; ++i)
131 {
132 if (::wcscmp(argv[i], L"-w") == 0 || ::wcscmp(argv[i], L"--width") == 0)
133 g_ClientWidth = ::wcstol(argv[++i], nullptr, 10);
134 if (::wcscmp(argv[i], L"-h") == 0 || ::wcscmp(argv[i], L"--height") == 0)
135 g_ClientHeight = ::wcstol(argv[++i], nullptr, 10);
136 if (::wcscmp(argv[i], L"-warp") == 0 || ::wcscmp(argv[i], L"--warp") == 0)
137 g_UseWarp = true;
138 }
139 // If we want to specify in command-line any further things, such as window mode, this function can be exstended.
140
141 // Free memory allocated by CommandLineToArgvW
142 ::LocalFree(argv);
143}
144
145void EnableDebugLayer()
146{
147#if defined(_DEBUG)
148 // Allways enable the debug layer before doing anything DX12 related
149 // so all possible errors generated while creating DX12 obhects are caught.
150 ComPtr<ID3D12Debug3> debugInterface;
151 if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugInterface))))
152 {
153 debugInterface->EnableDebugLayer();
154 debugInterface->SetEnableGPUBasedValidation(true);
155 }
156#endif
157}
158
159void RegisterWindowClass(HINSTANCE hInst, const wchar_t* windowClassName)
160{
161 // Register a window class for creating our render window with.
162 WNDCLASSEXW windowClass = {};
163
164 windowClass.cbSize = sizeof(WNDCLASSEXW); // The size in bytes of this structure.
165 windowClass.style = CS_HREDRAW | CS_VREDRAW; // Class style, CS_HREDRAW and CS_VREDRAW indicates the window should be redrawn if the window is resized.
166 windowClass.lpfnWndProc = &WndProc; // Pointer to the windows procedure. Will handle messages for windows created using this class.
167 windowClass.cbClsExtra = 0; // Extra bytes allocated following the window class structure.
168 windowClass.cbWndExtra = 0; // Extra bytes allocated following the window instance.
169 windowClass.hInstance = hInst; // A handle to the instance that contains the window procedure for the class.
170 windowClass.hIcon = ::LoadIconW(hInst, NULL); // Used to load a specific icon to the window.
171 windowClass.hCursor = ::LoadCursor(NULL, IDC_ARROW); // Using default cursor.
172 windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // Could be a brush or a color.
173 windowClass.lpszMenuName = NULL; // Resource name of the class menu.
174 windowClass.lpszClassName = windowClassName; // Name that uniquely identifies this window class.
175 windowClass.hIconSm = ::LoadIconW(hInst, NULL); // Small version of earlier icon.
176
177 static ATOM atom = ::RegisterClassExW(&windowClass);
178 assert(atom > 0);
179}
180
181HWND CreateWindow(const wchar_t* windowClassName, HINSTANCE hInst, const wchar_t* windowTitle, uint32_t width, uint32_t height)
182{
183 int screenWidth = ::GetSystemMetrics(SM_CXSCREEN); // Width of primary display monitor
184 int screenHeight = ::GetSystemMetrics(SM_CYSCREEN); // Height of primary display monitor
185
186 RECT windowRect = { 0, 0, static_cast<LONG>(width), static_cast<LONG>(height) };
187 ::AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE); // VS_OVERLAPPPEDWINDOW can be minimized, maximized and has a thick window frame.
188
189 int windowWidth = windowRect.right - windowRect.left;
190 int windowHeight = windowRect.bottom - windowRect.top;
191
192 // Center the window within the screen. Clamp to 0, 0 for the top-left corner.
193 int windowX = std::max<int>(0, (screenWidth - windowWidth) / 2);
194 int windowY = std::max<int>(0, (screenHeight - windowHeight) / 2);
195
196 HWND hWnd = ::CreateWindowExW(
197 NULL,
198 windowClassName, // Window class name
199 windowTitle, // Window name
200 WS_OVERLAPPEDWINDOW, // Window style
201 windowX, // Window position
202 windowY,
203 windowWidth, // Window size
204 windowHeight,
205 NULL, // Parent of this window
206 NULL, // Handle to a menu
207 hInst, // The instance
208 nullptr
209 );
210
211 assert(hWnd && "Failed to create window");
212
213 return hWnd;
214}
215
216ComPtr<IDXGIAdapter4> GetAdapter(bool useWarp) // (TODO: NEED COMMENTS!)
217{
218 // Before creating DX12 device we need a compatible adapter on the user's PC.
219 ComPtr<IDXGIFactory4> dxgiFactory; // !! Newer versions exist.
220 UINT createFactoryFlags = 0;
221#if defined(_DEBUG)
222 createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG; // Gives potential error messages.
223#endif
224
225 ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory)));
226
227 ComPtr<IDXGIAdapter1> dxgiAdapter1;
228 ComPtr<IDXGIAdapter4> dxgiAdapter4;
229
230 if (useWarp) // Good GPU:
231 {
232 ThrowIfFailed(dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&dxgiAdapter1)));
233 ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4)); // Typecasting
234 }
235 else // Not the best GPU:
236 {
237 SIZE_T maxDedicatedVideoMemory = 0;
238 // EnumAdapters finds the amount of GPU adapters in the system.
239 for (UINT i = 0; dxgiFactory->EnumAdapters1(i, &dxgiAdapter1) != DXGI_ERROR_NOT_FOUND; ++i)
240 {
241 DXGI_ADAPTER_DESC1 dxgiAdapterDesc1; // !! Newer versions exist?
242 dxgiAdapter1->GetDesc1(&dxgiAdapterDesc1);
243
244 // Check to see if the adapter can create a D3D12 device without actually
245 // creating it. The adapter with the largest dedicated video memory
246 // is favored...
247 // We only want hardware adapters and therefore DXGI_ADAPTER_FLAG_SOFTWARE adapters should be ignored.
248 // If D3D12CreateDevice generate a S_OK the adapter is D3D12 compatible.
249 // Finally we want the adapter with the largest video memory since it is generally
250 // a good indication of it's performance.
251 if ((dxgiAdapterDesc1.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 &&
252 SUCCEEDED(D3D12CreateDevice(dxgiAdapter1.Get(),
253 D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), nullptr)) &&
254 dxgiAdapterDesc1.DedicatedVideoMemory > maxDedicatedVideoMemory)
255 {
256 maxDedicatedVideoMemory = dxgiAdapterDesc1.DedicatedVideoMemory;
257 ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4));
258 }
259 }
260 }
261
262 // After this we can finally create the DX12 device! :)
263 return dxgiAdapter4;
264}
265
266ComPtr<ID3D12Device2> CreateDevice(ComPtr<IDXGIAdapter4> adapter) // !! Newer device versions exist!
267{
268 // The device is used to allocate resources such as textures.
269 // If destroyed all associated resources are invalid as well.
270
271 // We create the device and stores it.
272 // Parameters for create device is an adapter, feature level, identifier for the device interface and a memory block if specified.
273 ComPtr<ID3D12Device2> d3d12Device2; // !! Up to device8 exist!
274 ThrowIfFailed(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&d3d12Device2)));
275 // Newer feature levels exists as well.
276
277 // To make debugging easier we enable the ID3D12InfoQueue and different types of messages to be passed:
278#if defined(_DEBUG)
279 ComPtr<ID3D12InfoQueue> pInfoQueue;
280 if (SUCCEEDED(d3d12Device2.As(&pInfoQueue)))
281 {
282 pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE);
283 pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
284 pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE);
285
286 // Some warnings can be skipped if wanted:
287 // Suppress whole categories of messages:
288 // D3D12_MESSAGE_CATEGORY Categories[] = {};
289
290 // Suppress messages based on their level of severity:
291 D3D12_MESSAGE_SEVERITY Severities[] =
292 {
293 D3D12_MESSAGE_SEVERITY_INFO // This is only information messages and are therefore suppressed
294 };
295
296 // Suppress individual messages by their ID:
297 D3D12_MESSAGE_ID DenyIds[] =
298 {
299 D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE, // This warning occurs when clearing a render target with an unoptimized color.
300 D3D12_MESSAGE_ID_MAP_INVALID_NULLRANGE, // This and the next warning occurs when a frame is captured using the Visual Studio graphics debugger.
301 D3D12_MESSAGE_ID_UNMAP_INVALID_NULLRANGE
302 };
303
304 // Creates a filter for the messages to be debugged.
305 D3D12_INFO_QUEUE_FILTER NewFilter = {};
306 // NewFilter.DenyList.NumCategories = _countof(Categories);
307 // NewFilter.DenyList.pCategoryList = Categories;
308 NewFilter.DenyList.NumSeverities = _countof(Severities);
309 NewFilter.DenyList.pSeverityList = Severities;
310 NewFilter.DenyList.NumIDs = _countof(DenyIds);
311 NewFilter.DenyList.pIDList = DenyIds;
312
313 ThrowIfFailed(pInfoQueue->PushStorageFilter(&NewFilter));
314 }
315#endif
316
317 return d3d12Device2;
318}
319
320ComPtr<ID3D12CommandQueue> CreateCommandQueue(ComPtr<ID3D12Device2> device, D3D12_COMMAND_LIST_TYPE type) // !! Device exists up to device8!
321{
322 ComPtr<ID3D12CommandQueue> d3d12CommandQueue;
323
324 D3D12_COMMAND_QUEUE_DESC desc = {};
325 desc.Type = type; // Type of command queue: DIRECT, COMPUTE or COPY
326 desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; // Priority: Normal, high or global realtime
327 desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; // Flags for the command queue
328 desc.NodeMask = 0; // If multiple GPU nodes set this mask to the device's physical adapter this queue applies to.
329
330 ThrowIfFailed(device->CreateCommandQueue(&desc, IID_PPV_ARGS(&d3d12CommandQueue)));
331
332 return d3d12CommandQueue;
333}
334
335bool CheckTearingSupport()
336{
337 // Check for screen tearing support.
338 // The feature DXGI_FEATURE_PRESENT_ALLOW_TEARING must be present.
339 // If so, DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING must be specified when creating the swap chain.
340 // Furthermore, the DXGI_PRESENT_ALLOW_TEARING flag must be used when presenting with a sync-interval of 0.
341
342 BOOL allowTearing = false;
343 // Rather than create the DXGI 1.5 factory interface directly, we create the
344 // DXGI 1.4 interface and query for the 1.5 interface. This is to enable the
345 // graphics debugging tools which will not support the 1.5 factory interface
346 // until a future update. !! These might be present now though since this is
347 // a tutorial with a few years behind it...
348 ComPtr<IDXGIFactory4> factory4; // !! Factory7 exist!
349 if (SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&factory4)))) // !! Newer versions of createDXGI exist
350 {
351 ComPtr<IDXGIFactory5> factory5; // !! Up to 7 exist
352 if (SUCCEEDED(factory4.As(&factory5)))
353 {
354 if (FAILED(factory5->CheckFeatureSupport(
355 DXGI_FEATURE_PRESENT_ALLOW_TEARING, // Feature
356 &allowTearing, sizeof(allowTearing)))) // Buffer to be filled which describes the feature support and the size.
357 {
358 allowTearing = FALSE;
359 }
360 }
361 }
362
363 return allowTearing == TRUE;
364}
365
366ComPtr<IDXGISwapChain4> CreateSwapChain(HWND hWnd, ComPtr<ID3D12CommandQueue> commandQueue,
367 uint32_t width, uint32_t height, uint32_t bufferCount)
368{
369 // The primary purpose of the swap chain is to present the rendered image to screen.
370 // The swap chain stores two or more buffers that are swapped between to present the image.
371 // The buffer that is currently being rendered to is called the back buffer, the buffer that is
372 // on screen is called the front buffer. They swap places using the IDXGISwapChain::Present function.
373 // To swap between buffers DX12 uses a flip effect which can either be DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
374 // or _DISCARD (at the end). The difference is that with discard you discard the contents of the back buffer
375 // when present has been called and this makes it so multisampling and partial presentation works.
376 // SEQUENTIAL makes it so that the contents of the back buffer persist even after a present call.
377 // Multisampling does not work with sequential. DISCARD is generally faster too since it just pushes
378 // the backbuffer to screen and not to a queue.
379
380 ComPtr<IDXGISwapChain4> dxgiSwapChain4;
381 ComPtr<IDXGIFactory4> dxgiFactory4; // !! Factory7 exist!
382 UINT createFactoryFlags = 0;
383#if defined(_DEBUG)
384 createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG;
385#endif
386
387 ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory4)));
388
389 DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
390 swapChainDesc.Width = width; // Resolution width
391 swapChainDesc.Height = height; // Resolution height. This and width would get the output window's size if CreateSwapChainForHwnd and 0 is used.
392 swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // Display format
393 swapChainDesc.Stereo = FALSE; // Is the full-screen display and swap-chain back buffer stereo?
394 swapChainDesc.SampleDesc = { 1, 0 }; // Describes multisampling parameters. When flip is used, this has to be { 1, 0 }
395 swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // The surface usage of the back buffer, either shader input or render target
396 swapChainDesc.BufferCount = bufferCount; // Number of buffers in the swap chain.
397 swapChainDesc.Scaling = DXGI_SCALING_STRETCH; // Resize behavior of the back buffer. Specifies what happens when the render target is of different size.
398 swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // Previously mentioned swap effect/presentation model. Sequential or discard.
399 swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; // Transparency behavior of the back buffer
400 // It is recommended to always allow tearing if tearing is supported
401 swapChainDesc.Flags = CheckTearingSupport() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
402
403 ComPtr<IDXGISwapChain1> swapChain1; // !! Up to 4 exist!
404 ThrowIfFailed(dxgiFactory4->CreateSwapChainForHwnd( // Used to create a swap chain associated with a specific window handle.
405 commandQueue.Get(), // D3D12 direct command queue. Cannot be NULL.
406 hWnd, // Window handle.
407 &swapChainDesc, // Swap chain descriptor.
408 nullptr, // NULL = windowed swap chain. Can be full screen as well.
409 nullptr, // This target can be set to a specific output but in this case we don't need to.
410 &swapChain1 // The actual swap chain.
411 ));
412
413 // Disable the Alt+Enter fullscreen toggle feature. Switching to fullscreen will be handled manually.
414 ThrowIfFailed(dxgiFactory4->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER));
415
416 ThrowIfFailed(swapChain1.As(&dxgiSwapChain4));
417
418 return dxgiSwapChain4;
419
420 // To render to the swap chain's back buffers we need a RTV, render target views (views = resources) which
421 // needs to be created for each buffer. Next we will create a descriptor heap which will contain descriptors
422 // that contain the buffers.
423}
424
425ComPtr<ID3D12DescriptorHeap> CreateDescriptorHeap(ComPtr<ID3D12Device2> device, // !! Newer devices exist!
426 D3D12_DESCRIPTOR_HEAP_TYPE type, uint32_t numDescriptors)
427{
428 // A descriptor heap can be considered an array of resource views/descriptors (Render Target Views, Shader Resource Views,
429 // Unordered Access Views or Constant Buffer Views). Before these views are created we need a descriptor heap.
430 // CBVs, SRVs and UAVs can be in the same descriptor heap but not RTVs and Sampler views.
431 // Here we create a descriptor heap to store the render target views for the swap chain buffers.
432
433 ComPtr<ID3D12DescriptorHeap> descriptorHeap;
434
435 // The following description could also contain flags (for visible to GPU) and node mask (for multiple adapters).
436 D3D12_DESCRIPTOR_HEAP_DESC desc = {};
437 desc.NumDescriptors = numDescriptors; // Number of descriptors in the heap.
438 desc.Type = type; // Could be:
439 // D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV - For constant buffers, shader resources and unordered access view.
440 // D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER - For textures I guess
441 // D3D12_DESCRIPTOR_HEAP_TYPE_RTV - Render target
442 // D3D12_DESCRIPTOR_HEAP_TYPE_DSV - Depth stencil view
443
444 ThrowIfFailed(device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&descriptorHeap)));
445
446 return descriptorHeap;
447}
448
449void UpdaterRenderTargetViews(ComPtr<ID3D12Device2> device, ComPtr<IDXGISwapChain4> swapChain, // !! Newer devices exist!
450 ComPtr<ID3D12DescriptorHeap> descriptorHeap)
451{
452 // A render target view (RTV) is a resourve that can be attached to a slot of the output merger stage,
453 // on of the later stages of the rendering pipeline. It can both contain a specific part of the vertices
454 // present on an image such as position, normals and albedo color but here it will be the color of said pixel.
455
456 // Size of a specfic descriptor is vendor specific and here we get the size of the local machine
457 // to take care of the offset in the heap:
458 auto rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
459
460 // We need to start from the first descriptor so here we get the start address:
461 CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(descriptorHeap->GetCPUDescriptorHandleForHeapStart());
462
463 for (int i = 0; i < g_NumFrames; ++i)
464 {
465 ComPtr<ID3D12Resource> backBuffer; // !! Resource2 exist!
466 // We get the buffers from our swap chain:
467 ThrowIfFailed(swapChain->GetBuffer(i, IID_PPV_ARGS(&backBuffer)));
468
469 // And attach a render target to said buffer:
470 device->CreateRenderTargetView(backBuffer.Get(), nullptr, rtvHandle);
471
472 // Add it to our array of buffers:
473 g_BackBuffers[i] = backBuffer;
474
475 // Step one descriptor forward:
476 rtvHandle.Offset(rtvDescriptorSize);
477 }
478}
479
480void CreateViewportAndScissorRect()
481{
482 gViewport.TopLeftX = 0.0f;
483 gViewport.TopLeftY = 0.0f;
484 gViewport.Width = g_ClientWidth;
485 gViewport.Height = g_ClientHeight;
486 gViewport.MinDepth = 0.0f;
487 gViewport.MaxDepth = 1.0f;
488
489 gScissorRect.left = (long)0;
490 gScissorRect.right = (long)g_ClientWidth;
491 gScissorRect.top = (long)0;
492 gScissorRect.bottom = (long)g_ClientHeight;
493}
494
495void CreateConstantBufferResources()
496{
497 for (int i = 0; i < g_NumFrames; i++)
498 {
499 D3D12_DESCRIPTOR_HEAP_DESC descriptorHeapDesc = {};
500 descriptorHeapDesc.NumDescriptors = 1;
501 descriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
502 descriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
503 ThrowIfFailed(g_Device->CreateDescriptorHeap(&descriptorHeapDesc, IID_PPV_ARGS(&g_DescriptorHeap[i])));
504 }
505
506 UINT cbSizeAligned = (sizeof(ConstantBuffer) + 255) & ~255; // 256-byte aligned CB.
507
508 D3D12_HEAP_PROPERTIES heapProperties = {};
509 heapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;
510 heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
511 heapProperties.CreationNodeMask = 1; //used when multi-gpu
512 heapProperties.VisibleNodeMask = 1; //used when multi-gpu
513 heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
514
515 D3D12_RESOURCE_DESC resourceDesc = {};
516 resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
517 resourceDesc.Width = cbSizeAligned;
518 resourceDesc.Height = 1;
519 resourceDesc.DepthOrArraySize = 1;
520 resourceDesc.MipLevels = 1;
521 resourceDesc.SampleDesc.Count = 1;
522 resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
523
524 //Create a resource heap, descriptor heap, and pointer to cbv for each frame
525 for (int i = 0; i < g_NumFrames; i++)
526 {
527 g_Device->CreateCommittedResource(
528 &heapProperties,
529 D3D12_HEAP_FLAG_NONE,
530 &resourceDesc,
531 D3D12_RESOURCE_STATE_GENERIC_READ,
532 nullptr,
533 IID_PPV_ARGS(&g_ConstantBufferResource[i])
534 );
535
536 g_ConstantBufferResource[i]->SetName(L"CB Heap");
537
538 D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
539 cbvDesc.BufferLocation = g_ConstantBufferResource[i]->GetGPUVirtualAddress();
540 cbvDesc.SizeInBytes = cbSizeAligned;
541 g_Device->CreateConstantBufferView(&cbvDesc, g_DescriptorHeap[i]->GetCPUDescriptorHandleForHeapStart());
542 }
543}
544
545void CreateRootSignature()
546{
547 // Define descriptor range(s)
548 D3D12_DESCRIPTOR_RANGE dtRanges[1];
549 dtRanges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
550 dtRanges[0].NumDescriptors = 1; // Only one CB in this case
551 dtRanges[0].BaseShaderRegister = 0; // Register b0
552 dtRanges[0].RegisterSpace = 0; // Register (b0, space0)
553 dtRanges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
554
555 // Create a descriptor table
556 D3D12_ROOT_DESCRIPTOR_TABLE dt;
557 dt.NumDescriptorRanges = ARRAYSIZE(dtRanges); // 1 in this case.
558 dt.pDescriptorRanges = dtRanges; // Pointer to Descriptor range.
559
560 // Create root parameter
561 D3D12_ROOT_PARAMETER rootParam[1];
562 rootParam[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
563 rootParam[0].DescriptorTable = dt;
564 rootParam[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
565
566 D3D12_ROOT_SIGNATURE_DESC rsDesc;
567 rsDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
568 rsDesc.NumParameters = ARRAYSIZE(rootParam);
569 rsDesc.pParameters = rootParam;
570 rsDesc.NumStaticSamplers = 0;
571 rsDesc.pStaticSamplers = nullptr;
572
573 ID3DBlob* sBlob;
574 D3D12SerializeRootSignature(
575 &rsDesc,
576 D3D_ROOT_SIGNATURE_VERSION_1,
577 &sBlob,
578 nullptr
579 );
580
581 g_Device->CreateRootSignature(
582 0,
583 sBlob->GetBufferPointer(),
584 sBlob->GetBufferSize(),
585 IID_PPV_ARGS(&g_RootSignature)
586 );
587}
588
589void CreateShadersAndPipelineState()
590{
591 // Shader compiles:
592 ID3DBlob* vertexBlob;
593 D3DCompileFromFile(
594 L"VertexShader.hlsl", // Filename
595 nullptr, // optional macros
596 nullptr, // optional include files
597 "VS_main", // entry point
598 "vs_5_0", // shader model (target)
599 0, // shader compile options // here DEBUGGING OPTIONS
600 0, // effect compile options
601 &vertexBlob, // double pointer to ID3DBlob
602 nullptr // pointer for Error Blob messages.
603 // how to use the Error blob, see here
604 // https://msdn.microsoft.com/en-us/library/windows/desktop/hh968107(v=vs.85).aspx
605 );
606
607 ID3DBlob* pixelBlob;
608 D3DCompileFromFile(
609 L"PixelShader.hlsl",
610 nullptr,
611 nullptr,
612 "PS_main",
613 "ps_5_0",
614 0,
615 0,
616 &pixelBlob,
617 nullptr
618 );
619
620 // Input layout:
621 D3D12_INPUT_ELEMENT_DESC inputElementDesc[] = {
622 // SemanticName, SemanticIndex, Format, InputSlot, AlignedByteOffset, InputSlotClass, InstanceDataStepRate
623 { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
624 { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
625 };
626
627 D3D12_INPUT_LAYOUT_DESC inputLayoutDesc;
628 inputLayoutDesc.pInputElementDescs = inputElementDesc; // Pointer to attribute description
629 inputLayoutDesc.NumElements = ARRAYSIZE(inputElementDesc); // Number of attributes per vertex
630
631 // Pipeline state:
632 D3D12_GRAPHICS_PIPELINE_STATE_DESC gpsd = {};
633
634 // Specify pipeline stages:
635 gpsd.pRootSignature = g_RootSignature;
636 gpsd.InputLayout = inputLayoutDesc;
637 gpsd.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
638 gpsd.VS.pShaderBytecode = reinterpret_cast<void*>(vertexBlob->GetBufferPointer());
639 gpsd.VS.BytecodeLength = vertexBlob->GetBufferSize();
640 gpsd.PS.pShaderBytecode = reinterpret_cast<void*>(pixelBlob->GetBufferPointer());
641 gpsd.PS.BytecodeLength = pixelBlob->GetBufferSize();
642
643 // Specify render target and depth stencil usage:
644 gpsd.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
645 gpsd.NumRenderTargets = 1; // TODO: 3?
646
647 gpsd.SampleDesc.Count = 1;
648 gpsd.SampleMask = UINT_MAX;
649
650 // Specify rasterizer behaviour:
651 gpsd.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
652 gpsd.RasterizerState.CullMode = D3D12_CULL_MODE_BACK; // Back face culling I pressume
653
654 // Specify blend descriptions:
655 D3D12_RENDER_TARGET_BLEND_DESC defaultRTdesc = {
656 false,
657 false,
658 D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
659 D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
660 D3D12_LOGIC_OP_NOOP, D3D12_COLOR_WRITE_ENABLE_ALL
661 };
662
663 for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
664 {
665 gpsd.BlendState.RenderTarget[i] = defaultRTdesc;
666 }
667
668 g_Device->CreateGraphicsPipelineState(&gpsd, IID_PPV_ARGS(&g_PipelineState));
669}
670
671void CreateTriangleData()
672{
673 Vertex triangleVertices[3] =
674 {
675 0.0f, 0.5f, 0.0f, //v0 pos
676 1.0f, 0.0f, 0.0f, //v0 color
677
678 0.5f, -0.5f, 0.0f, //v1
679 0.0f, 1.0f, 0.0f, //v1 color
680
681 -0.5f, -0.5f, 0.0f, //v2
682 0.0f, 0.0f, 1.0f //v2 color
683 };
684
685 // Note: using upload heaps to transfer static data like vert buffers is not
686 // recommended. Every time the GPU needs it, the upload heap will be marshalled
687 // over. Please read up on Default Heap usage. An upload heap is used here for
688 // code simplicity and because there are very few vertices to actually transfer.
689 D3D12_HEAP_PROPERTIES hp = {};
690 hp.Type = D3D12_HEAP_TYPE_UPLOAD;
691 hp.CreationNodeMask = 1;
692 hp.VisibleNodeMask = 1;
693
694 D3D12_RESOURCE_DESC rd = {};
695 rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
696 rd.Width = sizeof(triangleVertices);
697 rd.Height = 1;
698 rd.DepthOrArraySize = 1;
699 rd.MipLevels = 1;
700 rd.SampleDesc.Count = 1;
701 rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
702
703 // Creates both a resource and an implicit heap, such that the heap is big enough
704 // to contain the entire resource and the resource is mapped to the heap.
705 g_Device->CreateCommittedResource(
706 &hp,
707 D3D12_HEAP_FLAG_NONE,
708 &rd,
709 D3D12_RESOURCE_STATE_GENERIC_READ,
710 nullptr,
711 IID_PPV_ARGS(&g_VertexBufferResource)
712 );
713
714 // Copy the triangle data to the vertex buffer:
715 void* dataBegin = nullptr;
716 D3D12_RANGE range = { 0, 0 }; // We do not intend to read this resource on the CPU.
717 g_VertexBufferResource->Map(0, &range, &dataBegin);
718 memcpy(dataBegin, triangleVertices, sizeof(triangleVertices));
719 g_VertexBufferResource->Unmap(0, nullptr);
720
721 // Inititialize vertex buffer view, used in render call:
722 g_VertexBufferView.BufferLocation = g_VertexBufferResource->GetGPUVirtualAddress();
723 g_VertexBufferView.StrideInBytes = sizeof(Vertex);
724 g_VertexBufferView.SizeInBytes = sizeof(triangleVertices);
725}
726
727void SetResourceTransitionBarrier(ID3D12GraphicsCommandList* commandList, ID3D12Resource* resource,
728 D3D12_RESOURCE_STATES StateBefore, D3D12_RESOURCE_STATES StateAfter)
729{
730 D3D12_RESOURCE_BARRIER barrierDesc = {};
731
732 barrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
733 barrierDesc.Transition.pResource = resource;
734 barrierDesc.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
735 barrierDesc.Transition.StateBefore = StateBefore;
736 barrierDesc.Transition.StateAfter = StateAfter;
737
738 commandList->ResourceBarrier(1, &barrierDesc);
739}
740
741ComPtr<ID3D12CommandAllocator> CreateCommandAllocator(ComPtr<ID3D12Device2> device, D3D12_COMMAND_LIST_TYPE type) // !! Newer devices exist!
742{
743 // A command allocator is the backing memory used by a command list. A command allocator does not provide any
744 // functionality and must be accessed indirectly by a command list. A command allocator can only be used by a
745 // single command list at a time but can be reused onces all commands that were recorded in to a command list
746 // are finished executing on the GPU. The memory allocated by a command allocator is reclaimed by using the
747 // Reset function. Use a GPU fence to check the status of the associated command list.
748 // To maximize frame-rates one command allocator per operating command list should be created.
749
750 ComPtr<ID3D12CommandAllocator> commandAllocator;
751 ThrowIfFailed(device->CreateCommandAllocator(type, IID_PPV_ARGS(&commandAllocator)));
752 // Type indicates which command list type it is: DIRECT, BUNDLE, COMPUTE or COPY.
753 // Second parameter is the unique identifier of our allocator.
754 // Finally we can provide a third parameter if we wish to specify a memory block.
755 return commandAllocator;
756
757 // This creates one allocator but can be used multiple times to create more.
758}
759
760ComPtr<ID3D12GraphicsCommandList> CreateCommandList(ComPtr<ID3D12Device2> device, // !! Newer command lists and devices exist!
761 ComPtr<ID3D12CommandAllocator> commandAllocator, D3D12_COMMAND_LIST_TYPE type)
762{
763 // A command list is used for recording commands that are to be executed on the GPU. These are deferred.
764 // That means that none of the commands on the list is executed until the command list is sent to the command queue.
765 // Unlike the command allocator, the command list can be used immidiately after it has been executed on the command
766 // queue. The only restriction is that it has to be reset first.
767
768 ComPtr<ID3D12GraphicsCommandList> commandList; // !! Newer command lists exists.
769 ThrowIfFailed(device->CreateCommandList(0, type, commandAllocator.Get(), nullptr, IID_PPV_ARGS(&commandList)));
770 // Parameters:
771 // 1: If multiple physical adapters set this to the specific adapter it applies to
772 // 2: The type: DIRECT, BUNDLE, COMPUTE or COPY.
773 // 3: The command allocator which the device create command lists from
774 // 4: Pipeline state object
775 // 5: Unique identifier of the command list
776 // (Optional) 6: Specific memory block
777
778 // The command list should immidiately be closed since no commands are to be recorded as of yet.
779 ThrowIfFailed(commandList->Close());
780
781 return commandList;
782}
783
784ComPtr<ID3D12Fence> CreateFence(ComPtr<ID3D12Device2> device) // !! Newer fences and devices exist!
785{
786 // Fences is an interface for synchronization between the CPU and GPU. Internally it stores a single 64-bit integer
787 // value which is initialized once the fence is created. The CPU can update said value by using ID3D12Fence::Signal function
788 // and the GPU uses the command queue's signal function.
789 // To wait for a specific value on the CPU use ID3D12Fence::SetEventOnCompletion followed by WaitForSingleObject.
790 // The GPU uses the command queue's wait function.
791 // Generally the fence object should be initialized with a value of zero and only be allowed to increase.
792 // Each thread or GPU queue should have at least one fence object and a corresponding fence value.
793 // More than one thread should no signal that fence value but could wait for it.
794
795 ComPtr<ID3D12Fence> fence;
796
797 ThrowIfFailed(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));
798 // Parameters:
799 // 1: Initial value, generally 0
800 // 2: Flags, could be NONE, SHARED, SHARED_CROSS_ADAPTER, NON_MONITORED
801 // 3: Unique identifier
802 // (Optional) 4: Block of memory
803
804 return fence;
805}
806
807HANDLE CreateEventHandle()
808{
809 // This function is used to create an event that block any further processing until a fence has been signaled.
810 HANDLE fenceEvent;
811
812 fenceEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
813 // Parameters:
814 // 1: A pointer to a SECURITY_ATTRIBUTES structure. If null the handle cant be inherited by child processes.
815 // 2: If true the event has to be manually reset using the ResetEvent function.
816 // 3: If true the initial state of the event object is signaled otherwise not.
817 // 4: The name of the event object.
818
819 // This OS event is used to cause a CPU thread to stall using the WaitForSingleObject function.
820
821 assert(fenceEvent && "Failed to create fence event");
822
823 return fenceEvent;
824}
825
826uint64_t Signal(ComPtr<ID3D12CommandQueue> commandQueue, ComPtr<ID3D12Fence> fence, // !! Newer fences exist
827 uint64_t& fenceValue)
828{
829 // This function is used to update the fence from the GPU.
830 // The signal gets executed after existing commands in the command queue are finalized.
831 uint64_t fenceValueForSignal = ++fenceValue;
832 ThrowIfFailed(commandQueue->Signal(fence.Get(), fenceValueForSignal));
833 // Parameters of Signal is the fence to update and the value to update it to.
834
835 // The returned value is the value the CPU should wait for before reusing any "in-flight" resources on the GPU.
836 return fenceValueForSignal;
837}
838
839void WaitForFenceValue(ComPtr<ID3D12Fence> fence, uint64_t fenceValue, HANDLE fenceEvent, // !! Newer fences exist
840 std::chrono::milliseconds duration = std::chrono::milliseconds::max())
841{
842 // Write to objects such as RTV buffers need to be waited upon before reused by another command queue.
843 // Textures that we just read from however don't need this function or double buffering.
844
845 // This function is used to stall the CPU thread if the fence has not yet reached (been signaled with)
846 // a specific value. The function will wait for a duration specified by chrono (about 584 million years :O)
847 // or until an event object signals the expected value.
848 if (fence->GetCompletedValue() < fenceValue)
849 {
850 ThrowIfFailed(fence->SetEventOnCompletion(fenceValue, fenceEvent));
851 ::WaitForSingleObject(fenceEvent, static_cast<DWORD>(duration.count()));
852 }
853}
854
855void Flush(ComPtr<ID3D12CommandQueue> commandQueue, ComPtr<ID3D12Fence> fence, // !! Newer fences exist
856 uint64_t& fenceValue, HANDLE fenceEvent)
857{
858 // The flush function is used to ensure that any commands previously executed on the GPU have
859 // finished executing before the CPU thread is allowed to continue processing. This is useful for ensuring
860 // that any back buffer resources being referenced by a command that is currently "in-flight" on the GPU have
861 // finished executing before being resized. It is also good to flush the GPU command queue before releasing
862 // any resources that might be referenced by a command list that is in flight on the command queue.
863 // One such example could be before closing the application.
864
865 // We signal the fence on the GPU:
866 uint64_t fenceValueForSignal = Signal(commandQueue, fence, fenceValue);
867 // We wait until the fence is signaled after the commandQueue is finalized.
868 WaitForFenceValue(fence, fenceValueForSignal, fenceEvent);
869}
870
871void Update()
872{
873 static uint64_t frameCounter = 0; // Number of frames passed since last update.
874 static double elapsedSeconds = 0.0; // Time elapsed since last update.
875 static std::chrono::high_resolution_clock clock;
876 static auto t0 = clock.now(); // Initital time.
877
878 frameCounter++; // Keeps track of the amount of times we have rendered the screen since last time.
879 auto t1 = clock.now();
880 auto deltaTime = t1 - t0; // Time passed.
881 t0 = t1;
882
883 elapsedSeconds += deltaTime.count() * 1e-9;
884 if (elapsedSeconds > 1.0) // We only print once per every second
885 {
886 char buffer[500]; // Text buffer for fps
887 auto fps = frameCounter / elapsedSeconds;
888 sprintf_s(buffer, 500, "FPS: %f\n", fps);
889 OutputDebugStringA(buffer);
890
891 frameCounter = 0;
892 elapsedSeconds = 0.0;
893 }
894
895 // Updating colors:
896 for (int i = 0; i < 3; i++)
897 {
898 g_ConstantBufferCPU.colorChannel[i] += 0.0001f * (i + 1);
899 if (g_ConstantBufferCPU.colorChannel[i] > 1)
900 {
901 g_ConstantBufferCPU.colorChannel[i] = 0;
902 }
903 }
904
905 // Update GPU memory:
906 void* mappedMem = nullptr;
907 D3D12_RANGE readRange = { 0, 0 };
908 if (SUCCEEDED(g_ConstantBufferResource[g_CurrentBackBufferIndex]->Map(0, &readRange, &mappedMem)))
909 {
910 memcpy(mappedMem, &g_ConstantBufferCPU, sizeof(ConstantBuffer));
911
912 D3D12_RANGE writeRange = { 0, sizeof(ConstantBuffer) };
913 g_ConstantBufferResource[g_CurrentBackBufferIndex]->Unmap(0, &writeRange);
914 }
915}
916
917void Render()
918{
919 // For this case the Render function is in charge of:
920 // 1. Clear the back buffer
921 // 2. Present the rendered frame
922
923 // We are in charge that resources are in the correct state before using them.
924 // Resource barriers are used to transition between states.
925 // One example: Before using the swap chain's back buffer as a render target it must be in th
926 // RENDER_TARGET state and to present it it has to be in the PRESENT state. Makes sense...
927 // Here are some different kinds of resource barriers:
928 // 1. Transition - transitions a (sub)resource to a particular state before using it.
929 //For example, before a texture can be used in a pixel shader we have to put it in
930 // the PIXEL_SHADER_RESOURCE state
931 // 2. Aliasing - Specifies that a resource is used in a placed or reserved heap when that
932 // resource is aliased with another resource in the same heap.
933 // 3. UAV - Indicates that all UAV accesses to a particular resource have completed before
934 // any future UAV access can begin. This is necessary when the UAV is transitioned for:
935 // Read > Write: Guarantees that all previous read operations on the UAV have completed before being written to in another shader.
936 // Write > Read: Guarantees that all previous write operations on the UAV have completed before being read from in another shader.
937 // Write > Write: Avoids race conditions that could be caused by different shaders in a different draw or dispatch trying to write
938 // to the same resource(does not avoid race conditions that could be caused in the same draw or dispatch call).
939 // A UAV barrier is not needed if the resource is being used as a read - only(Read > Read) resource between draw or dispatches.
940
941 // Before any commands can be recorded into the command list we reset the command allocator and list.
942 auto commandAllocator = g_CommandAllocators[g_CurrentBackBufferIndex];
943 auto backBuffer = g_BackBuffers[g_CurrentBackBufferIndex];
944
945 commandAllocator->Reset();
946 // PipelineState used to be nullptr here:
947 g_CommandList->Reset(commandAllocator.Get(), g_PipelineState);
948
949 // NEW WAY:
950 // Set constant buffer descriptor heap
951 ID3D12DescriptorHeap* descriptorHeaps[] = { g_DescriptorHeap[g_CurrentBackBufferIndex] };
952 g_CommandList->SetDescriptorHeaps(ARRAYSIZE(descriptorHeaps), descriptorHeaps);
953
954 // Set root signature:
955 g_CommandList->SetGraphicsRootSignature(g_RootSignature);
956
957 // Set root descriptor table to index 0 in previously set root signature:
958 g_CommandList->SetGraphicsRootDescriptorTable(
959 0,
960 g_DescriptorHeap[g_CurrentBackBufferIndex]->GetGPUDescriptorHandleForHeapStart()
961 );
962
963 // Set necessary states:
964 g_CommandList->RSSetViewports(1, &gViewport);
965 g_CommandList->RSSetScissorRects(1, &gScissorRect);
966
967 // Indicate that the back buffer will be used as render target:
968 SetResourceTransitionBarrier(
969 g_CommandList.Get(),
970 g_BackBuffers[g_CurrentBackBufferIndex].Get(),
971 D3D12_RESOURCE_STATE_PRESENT, // state before
972 D3D12_RESOURCE_STATE_RENDER_TARGET // state after
973 );
974
975 // Record commands:
976 // Get the handle for the current render target used as back buffer:
977 D3D12_CPU_DESCRIPTOR_HANDLE cdh = g_RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
978 cdh.ptr += g_RTVDescriptorSize * g_CurrentBackBufferIndex;
979
980 g_CommandList->OMSetRenderTargets(1, &cdh, true, nullptr);
981
982 FLOAT clearColor[] = { 0.4f, 0.6f, 0.9f, 1.0f };
983 g_CommandList->ClearRenderTargetView(cdh, clearColor, 0, nullptr);
984
985 g_CommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
986 g_CommandList->IASetVertexBuffers(0, 1, &g_VertexBufferView);
987
988 g_CommandList->DrawInstanced(3, 1, 0, 0);
989
990 // Indicate that the back buffer will now be used to present:
991 SetResourceTransitionBarrier(
992 g_CommandList.Get(),
993 g_BackBuffers[g_CurrentBackBufferIndex].Get(),
994 D3D12_RESOURCE_STATE_RENDER_TARGET, // state before
995 D3D12_RESOURCE_STATE_PRESENT // state after
996 );
997
998 // Close the list to prepare it for execution:
999 g_CommandList->Close();
1000
1001 // Execute the command list:
1002 ID3D12CommandList* listsToExecute[] = { g_CommandList.Get() };
1003 g_CommandQueue->ExecuteCommandLists(ARRAYSIZE(listsToExecute), listsToExecute);
1004
1005 // Present the frame:
1006 UINT syncInterval = g_VSync ? 1 : 0;
1007 UINT presentFlags = g_TearingSupported && !g_VSync ? DXGI_PRESENT_ALLOW_TEARING : 0;
1008
1009 DXGI_PRESENT_PARAMETERS pp = {};
1010 g_SwapChain->Present1(syncInterval, presentFlags, &pp);
1011
1012 // Signal fence:
1013 g_FrameFenceValues[g_CurrentBackBufferIndex] = Signal(g_CommandQueue, g_Fence, g_FenceValue);
1014
1015 g_CurrentBackBufferIndex = g_SwapChain->GetCurrentBackBufferIndex();
1016
1017 WaitForFenceValue(g_Fence, g_FrameFenceValues[g_CurrentBackBufferIndex], g_FenceEvent);
1018
1019 // OLD WAY:
1020 //// Usually the first thing we do to a RTV is to clear it but before that we need to set it to the correct state.
1021 //// Clear
1022 //{
1023 // CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( // We want to transition between states
1024 // backBuffer.Get(), // Resource
1025 // D3D12_RESOURCE_STATE_PRESENT, // State before
1026 // D3D12_RESOURCE_STATE_RENDER_TARGET // State after
1027 // );
1028
1029 // g_CommandList->ResourceBarrier(1, &barrier);
1030
1031 // FLOAT clearColor[] = { 0.4f, 0.6f, 0.9f, 1.0f };
1032 // CD3DX12_CPU_DESCRIPTOR_HANDLE rtv(g_RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
1033 // g_CurrentBackBufferIndex, g_RTVDescriptorSize);
1034
1035 // g_CommandList->ClearRenderTargetView(rtv, clearColor, 0, nullptr);
1036 //}
1037
1038 //// Present
1039 //{
1040 // // Transition state from render target to present
1041 // CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
1042 // backBuffer.Get(),
1043 // D3D12_RESOURCE_STATE_RENDER_TARGET,
1044 // D3D12_RESOURCE_STATE_PRESENT
1045 // );
1046
1047 // g_CommandList->ResourceBarrier(1, &barrier);
1048
1049 // ThrowIfFailed(g_CommandList->Close());
1050
1051 // ID3D12CommandList* const commandLists[] = {
1052 // g_CommandList.Get()
1053 // };
1054
1055 // g_CommandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
1056
1057 // UINT syncInterval = g_VSync ? 1 : 0;
1058 // UINT presentFlags = g_TearingSupported && !g_VSync ? DXGI_PRESENT_ALLOW_TEARING : 0;
1059 // ThrowIfFailed(g_SwapChain->Present(syncInterval, presentFlags));
1060
1061 // g_FrameFenceValues[g_CurrentBackBufferIndex] = Signal(g_CommandQueue, g_Fence, g_FenceValue);
1062
1063 // g_CurrentBackBufferIndex = g_SwapChain->GetCurrentBackBufferIndex();
1064
1065 // WaitForFenceValue(g_Fence, g_FrameFenceValues[g_CurrentBackBufferIndex], g_FenceEvent);
1066 //}
1067}
1068
1069void Resize(uint32_t width, uint32_t height)
1070{
1071 if (g_ClientWidth != width || g_ClientHeight != height)
1072 {
1073 // Don't allow 0 size swap chain back buffers
1074 g_ClientWidth = std::max(1u, width);
1075 g_ClientHeight = std::max(1u, height);
1076
1077 // Flush the GPU queue to make sure the swap chain's back buffers are not being referenced by
1078 // an in-flight command list
1079 Flush(g_CommandQueue, g_Fence, g_FenceValue, g_FenceEvent);
1080
1081 for (int i = 0; i < g_NumFrames; ++i)
1082 {
1083 // Any references to the back buffers must be released before the swap chain can be resized.
1084 g_BackBuffers[i].Reset();
1085 g_FrameFenceValues[i] = g_FrameFenceValues[g_CurrentBackBufferIndex];
1086 }
1087
1088 DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
1089 ThrowIfFailed(g_SwapChain->GetDesc(&swapChainDesc));
1090 ThrowIfFailed(g_SwapChain->ResizeBuffers(g_NumFrames, g_ClientWidth, g_ClientHeight,
1091 swapChainDesc.BufferDesc.Format, swapChainDesc.Flags));
1092
1093 g_CurrentBackBufferIndex = g_SwapChain->GetCurrentBackBufferIndex();
1094
1095 UpdaterRenderTargetViews(g_Device, g_SwapChain, g_RTVDescriptorHeap);
1096 }
1097}
1098
1099void SetFullscreen(bool fullscreen)
1100{
1101 if (g_Fullscreen != fullscreen)
1102 {
1103 g_Fullscreen = fullscreen;
1104
1105 if (g_Fullscreen) // Switching to fullscreen:
1106 {
1107 // Store the current window dimensions so that they can be restored
1108 // when switching out of fullscreen state.
1109 ::GetWindowRect(g_Hwnd, &g_WindowRect);
1110
1111 // Set the window style to a borderless window so the client area fills
1112 // the entire screen.
1113 UINT windowStyle = WS_OVERLAPPEDWINDOW & ~(WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
1114
1115 ::SetWindowLongW(g_Hwnd, GWL_STYLE, windowStyle);
1116
1117 // Query the name of the nearest display device for the window.
1118 // This is required to set the fullscreen dimensions of the window
1119 // when using a multi-monitor setup.
1120 HMONITOR hMonitor = ::MonitorFromWindow(g_Hwnd, MONITOR_DEFAULTTONEAREST);
1121 MONITORINFOEX monitorInfo = {};
1122 monitorInfo.cbSize = sizeof(MONITORINFOEX);
1123 ::GetMonitorInfo(hMonitor, &monitorInfo);
1124
1125 ::SetWindowPos(
1126 g_Hwnd, // Handle to the window
1127 HWND_TOP, // Where to place the window, in this case on top of other processes
1128 monitorInfo.rcMonitor.left, // New x position
1129 monitorInfo.rcMonitor.top, // New y position
1130 monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left, // New width
1131 monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top, // New height
1132 SWP_FRAMECHANGED | SWP_NOACTIVATE); // Window flags
1133
1134 ::ShowWindow(g_Hwnd, SW_MAXIMIZE);
1135 }
1136 else
1137 {
1138 // Restore al the window decorators
1139 ::SetWindowLong(g_Hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
1140
1141 ::SetWindowPos(g_Hwnd, HWND_NOTOPMOST,
1142 g_WindowRect.left,
1143 g_WindowRect.top,
1144 g_WindowRect.right - g_WindowRect.left,
1145 g_WindowRect.bottom - g_WindowRect.top,
1146 SWP_FRAMECHANGED | SWP_NOACTIVATE);
1147
1148 ::ShowWindow(g_Hwnd, SW_NORMAL);
1149 }
1150 }
1151}
1152
1153LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
1154{
1155 // This function handles messages to the window.
1156 if (g_IsInitialized) // DX12 has to be initialized first.
1157 {
1158 switch (message)
1159 {
1160 case WM_PAINT: // Repaint a portion of the application's window contents
1161 Update();
1162 Render();
1163 break;
1164 case WM_SYSKEYDOWN:
1165 case WM_KEYDOWN:
1166 {
1167 bool alt = (::GetAsyncKeyState(VK_MENU) & 0x8000) != 0;
1168
1169 switch (wParam)
1170 {
1171 case 'V':
1172 g_VSync = !g_VSync;
1173 break;
1174 case VK_ESCAPE:
1175 ::PostQuitMessage(0);
1176 break;
1177 case VK_RETURN:
1178 if (alt)
1179 {
1180 case VK_F11:
1181 SetFullscreen(!g_Fullscreen);
1182 }
1183 break;
1184 }
1185 }
1186 break;
1187 // The default window procedure will play a system notification sound
1188 // when pressing the Alt+Enter keyboard combination if this message is
1189 // not handled.
1190 case WM_SYSCHAR:
1191 break;
1192 case WM_SIZE:
1193 {
1194 RECT clientRect = {};
1195 ::GetClientRect(g_Hwnd, &clientRect);
1196
1197 int width = clientRect.right - clientRect.left;
1198 int height = clientRect.bottom - clientRect.top;
1199
1200 Resize(width, height);
1201 }
1202 break;
1203 case WM_DESTROY:
1204 ::PostQuitMessage(0);
1205 break;
1206 default:
1207 return ::DefWindowProcW(hwnd, message, wParam, lParam);
1208 }
1209 }
1210 else
1211 {
1212 return ::DefWindowProcW(hwnd, message, wParam, lParam);
1213 }
1214
1215 return 0;
1216}
1217#pragma endregion
1218
1219#pragma region MAIN
1220int CALLBACK wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow)
1221{
1222 // Windows 10 Creators update adds Per Monitor V2 DPI awareness context.
1223 // Using this awareness context allows the client area of the window
1224 // to achieve 100% scaling while still allowing non-client window content to
1225 // be rendered in a DPI sensitive fashion.
1226 SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
1227
1228 // Window class name. Used for registering / creating the window.
1229 const wchar_t* windowClassName = L"DX12WindowClass";
1230 ParseCommandLineArguments();
1231
1232 EnableDebugLayer();
1233
1234 g_TearingSupported = CheckTearingSupport();
1235
1236 RegisterWindowClass(hInstance, windowClassName);
1237 g_Hwnd = CreateWindow(windowClassName, hInstance, L"DirectX 12", g_ClientWidth, g_ClientHeight);
1238
1239 // Initialize the global window rect variable.
1240 ::GetWindowRect(g_Hwnd, &g_WindowRect);
1241
1242 ComPtr<IDXGIAdapter4> dxgiAdapter4 = GetAdapter(g_UseWarp);
1243
1244 g_Device = CreateDevice(dxgiAdapter4);
1245
1246 g_CommandQueue = CreateCommandQueue(g_Device, D3D12_COMMAND_LIST_TYPE_DIRECT);
1247
1248 g_SwapChain = CreateSwapChain(g_Hwnd, g_CommandQueue, g_ClientWidth, g_ClientHeight, g_NumFrames);
1249
1250 g_CurrentBackBufferIndex = g_SwapChain->GetCurrentBackBufferIndex();
1251
1252 g_RTVDescriptorHeap = CreateDescriptorHeap(g_Device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, g_NumFrames);
1253 g_RTVDescriptorSize = g_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
1254
1255 UpdaterRenderTargetViews(g_Device, g_SwapChain, g_RTVDescriptorHeap);
1256
1257 for (int i = 0; i < g_NumFrames; ++i)
1258 {
1259 g_CommandAllocators[i] = CreateCommandAllocator(g_Device, D3D12_COMMAND_LIST_TYPE_DIRECT);
1260 }
1261
1262 g_CommandList = CreateCommandList(g_Device, g_CommandAllocators[g_CurrentBackBufferIndex], D3D12_COMMAND_LIST_TYPE_DIRECT);
1263
1264 g_Fence = CreateFence(g_Device);
1265 g_FenceEvent = CreateEventHandle();
1266
1267 CreateViewportAndScissorRect();
1268
1269 CreateRootSignature();
1270
1271 CreateShadersAndPipelineState();
1272
1273 CreateConstantBufferResources();
1274
1275 CreateTriangleData();
1276
1277 // Wait for GPU:
1278 g_FrameFenceValues[g_CurrentBackBufferIndex] = Signal(g_CommandQueue, g_Fence, g_FenceValue);
1279 g_CurrentBackBufferIndex = g_SwapChain->GetCurrentBackBufferIndex();
1280 WaitForFenceValue(g_Fence, g_FrameFenceValues[g_CurrentBackBufferIndex], g_FenceEvent);
1281
1282 g_IsInitialized = true;
1283
1284 ::ShowWindow(g_Hwnd, SW_SHOW);
1285
1286 MSG msg = {};
1287 while (msg.message != WM_QUIT)
1288 {
1289 if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
1290 {
1291 ::TranslateMessage(&msg);
1292 ::DispatchMessage(&msg);
1293 }
1294 }
1295
1296 // Make sure the command queue has finished all commands before closing:
1297 Flush(g_CommandQueue, g_Fence, g_FenceValue, g_FenceEvent);
1298
1299 ::CloseHandle(g_FenceEvent);
1300
1301 return 0;
1302}
1303#pragma endregion