· 4 years ago · Feb 15, 2021, 09:32 AM
1// Left todo:
2// 1. Wireframe rendering of the blue triangle
3// 2. Change vertex buffer so that we render multiple triangles
4// (I added vertices so that we render two triangles but we aren't using index buffer for it)
5// 3. Use constant buffer for change of position instead of color.
6
7#pragma region INCLUDES
8#define WIN32_LEAN_AND_MEAN // This minimizes the number of header files included from Windows.h
9#include <Windows.h> // Used for creating windows etc.
10#include <wincodec.h> // Used for digital images. Supports BMP, GIF, ICO, JPEG, PNG, TIFF and DDS files.
11
12// The min/max macros conflict with like-named member functions.
13// Only use std::min and std::max defined in <algorithm>
14#if defined(min)
15#undef min
16#endif
17
18#if defined(max)
19#undef max
20#endif
21
22// In order to define a function called CreateWindow, the Windows
23// macro needs to be undefined. This is fine since a version called
24// CreateWindowExW is used to create the OS window.
25#if defined(CreateWindow)
26#undef CreateWindow
27#endif
28
29// Windows Runtime Library. Needed for Microsoft::WRL::ComPtr<> template class.
30// Provides smart pointers to COM objects which DirectX 12 uses.
31#include <wrl.h>
32using namespace Microsoft::WRL;
33
34// DirectX 12 specific headers:
35#include <d3d12.h> // Contains Direct3D 12 objects (device, CommandQueue, CommandList, etc...)
36#include <dxgi1_6.h> // Microsoft DirectX Graphics Infrastructure is used to manage low-level tasks
37// such as enumerating GPU adapters, presenting the rendered image and handling full screen transitions.
38#include <d3dcompiler.h> // Contains functions to compile HLSL shaders.
39#include <DirectXMath.h> // Math library
40
41// D3D12 extension library:
42#include "d3dx12.h" // Helper code basically.
43
44#pragma comment(lib, "d3d12.lib")
45#pragma comment(lib, "DXGI.lib")
46#pragma comment(lib, "d3dcompiler.lib")
47#pragma comment(lib, "dxguid.lib") // Included so that we can use UpdateSubresources.
48
49// STL Headers:
50#include <algorithm> // Contains std::min and std::max among other things
51#include <cassert> // Assert macro
52#include <chrono> // Contains high_resolution_clock for calculating FPS
53
54// Helper functions:
55#include "Helpers.h"
56#include <iostream>
57#pragma endregion
58
59#pragma region VARIABLES
60const uint8_t g_NumFrames = 3; // Number of swap chain back buffers. 2 or more.
61bool g_UseWarp = false; // Use of WARP (Windows Advanced Rasterization Platform) adapter.
62// Use it if the full set of rendering features can be used (not an old GPU)...
63uint32_t g_ClientWidth = 1280; // Window width.
64uint32_t g_ClientHeight = 720; // Window height.
65bool g_IsInitialized = false; // Set to true once the DX12 objects have been initialized.
66
67HWND g_Hwnd; // Window handle.
68RECT g_WindowRect; // Window rectangle (used to toggle fullscreen state). Holds previous window size to change
69// between windowed and full screen mode.
70
71// Added texcoords
72struct Vertex
73{
74 float x, y, z; // Position
75 float r, g, b; // Color
76 float u, v; // Texture coordinates
77};
78
79// DirectX 12 Objects:
80ComPtr<ID3D12Device2> g_Device; // DirectX12 device. !! There is far newer versions of this. Might use them instead?
81ComPtr<ID3D12CommandQueue> g_CommandQueue; // Use one queue for now. Executes what a pen wrote on a paper.
82ComPtr<IDXGISwapChain4> g_SwapChain; // Back buffer management. Presents the image in the end.
83ComPtr<ID3D12Resource> g_BackBuffers[g_NumFrames]; // !! There is far newer versions of this. Might use them instead?
84ComPtr<ID3D12GraphicsCommandList> g_CommandList; // !! There is far newer versions of this. Might use them instead?
85// This was "the pen" which "creates" tasks. Could use multiple to run multithreading.
86ComPtr<ID3D12CommandAllocator> g_CommandAllocators[g_NumFrames]; // This was the "paper" upon which we create tasks.
87// All commands within an allocator has to be finished before reusing it. At least one is used per back buffer of the swap chain.
88ComPtr<ID3D12DescriptorHeap> g_RTVDescriptorHeap; // Render target view. Describes the location, dimensions
89// and format of the texture resource in GPU. Stored in descriptor heaps which is an array of descriptors (views).
90// A view means a resource in GPU memory.
91UINT g_RTVDescriptorSize; // This is vendor specific and therefore needs to be queried during initialization.
92UINT g_CurrentBackBufferIndex;
93
94// "Stefan-objekt" (Sista 6 stegen):
95D3D12_VIEWPORT gViewport = {};
96D3D12_RECT gScissorRect = {};
97
98ID3D12RootSignature* g_RootSignature = nullptr;
99ID3D12PipelineState* g_PipelineState = nullptr;
100
101ID3D12Resource1* g_VertexBufferResource = nullptr;
102D3D12_VERTEX_BUFFER_VIEW g_VertexBufferView = {};
103
104// Texture-related variables:
105ComPtr<ID3D12Resource> g_TextureBuffer; // The resource heap containing our texture
106ComPtr<ID3D12Resource> g_TextureBufferUploadHeap;
107ComPtr<ID3D12DescriptorHeap> g_TextureDescriptorHeap;
108UINT g_TextureDescriptorHeapSize;
109D3D12_RESOURCE_DESC g_TextureDesc;
110int g_ImageBytesPerRow = 0;
111BYTE* g_ImageData;
112int g_ImageSize = 0;
113
114struct ConstantBuffer
115{
116 float colorChannel[4];
117};
118
119// Descriptor heaps that isn't for render targets:
120ID3D12DescriptorHeap* g_DescriptorHeap[g_NumFrames] = {};
121ID3D12Resource1* g_ConstantBufferResource[g_NumFrames] = {};
122ConstantBuffer g_ConstantBufferCPU = {};
123
124// Synchronization objects:
125ComPtr<ID3D12Fence> g_Fence; // !! There exists a newer version, might use that.
126uint64_t g_FenceValue = 0;
127uint64_t g_FrameFenceValues[g_NumFrames] = {}; // All frames need to be synchrnized so that no data is overwritten before finalized.
128HANDLE g_FenceEvent; // Used to check wether a fence has reached a specific value.
129
130// Swap chain related variables:
131bool g_VSync = true; // By default V-Sync will be on. Can be toggled with the V key. Forces framerate to match the screens.
132bool g_TearingSupported = false;
133bool 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.
134#pragma endregion
135
136#pragma region FUNCTION DECLARATIONS
137// Window callback function:
138LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
139
140void EnableDebugLayer()
141{
142#if defined(_DEBUG)
143 // Allways enable the debug layer before doing anything DX12 related
144 // so all possible errors generated while creating DX12 obhects are caught.
145 ComPtr<ID3D12Debug3> debugInterface;
146 if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugInterface))))
147 {
148 debugInterface->EnableDebugLayer();
149 debugInterface->SetEnableGPUBasedValidation(true);
150 }
151#endif
152}
153
154void RegisterWindowClass(HINSTANCE hInst, const wchar_t* windowClassName)
155{
156 // Register a window class for creating our render window with.
157 WNDCLASSEXW windowClass = {};
158
159 windowClass.cbSize = sizeof(WNDCLASSEXW); // The size in bytes of this structure.
160 windowClass.style = CS_HREDRAW | CS_VREDRAW; // Class style, CS_HREDRAW and CS_VREDRAW indicates the window should be redrawn if the window is resized.
161 windowClass.lpfnWndProc = &WndProc; // Pointer to the windows procedure. Will handle messages for windows created using this class.
162 windowClass.cbClsExtra = 0; // Extra bytes allocated following the window class structure.
163 windowClass.cbWndExtra = 0; // Extra bytes allocated following the window instance.
164 windowClass.hInstance = hInst; // A handle to the instance that contains the window procedure for the class.
165 windowClass.hIcon = ::LoadIconW(hInst, NULL); // Used to load a specific icon to the window.
166 windowClass.hCursor = ::LoadCursor(NULL, IDC_ARROW); // Using default cursor.
167 windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // Could be a brush or a color.
168 windowClass.lpszMenuName = NULL; // Resource name of the class menu.
169 windowClass.lpszClassName = windowClassName; // Name that uniquely identifies this window class.
170 windowClass.hIconSm = ::LoadIconW(hInst, NULL); // Small version of earlier icon.
171
172 static ATOM atom = ::RegisterClassExW(&windowClass);
173 assert(atom > 0);
174}
175
176HWND CreateWindow(const wchar_t* windowClassName, HINSTANCE hInst, const wchar_t* windowTitle, uint32_t width, uint32_t height)
177{
178 int screenWidth = ::GetSystemMetrics(SM_CXSCREEN); // Width of primary display monitor
179 int screenHeight = ::GetSystemMetrics(SM_CYSCREEN); // Height of primary display monitor
180
181 RECT windowRect = { 0, 0, static_cast<LONG>(width), static_cast<LONG>(height) };
182 ::AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE); // VS_OVERLAPPPEDWINDOW can be minimized, maximized and has a thick window frame.
183
184 int windowWidth = windowRect.right - windowRect.left;
185 int windowHeight = windowRect.bottom - windowRect.top;
186
187 // Center the window within the screen. Clamp to 0, 0 for the top-left corner.
188 int windowX = std::max<int>(0, (screenWidth - windowWidth) / 2);
189 int windowY = std::max<int>(0, (screenHeight - windowHeight) / 2);
190
191 HWND hWnd = ::CreateWindowExW(
192 NULL,
193 windowClassName, // Window class name
194 windowTitle, // Window name
195 WS_OVERLAPPEDWINDOW, // Window style
196 windowX, // Window position
197 windowY,
198 windowWidth, // Window size
199 windowHeight,
200 NULL, // Parent of this window
201 NULL, // Handle to a menu
202 hInst, // The instance
203 nullptr
204 );
205
206 assert(hWnd && "Failed to create window");
207
208 return hWnd;
209}
210
211ComPtr<IDXGIAdapter4> GetAdapter(bool useWarp) // (TODO: NEED COMMENTS!)
212{
213 // Before creating DX12 device we need a compatible adapter on the user's PC.
214 ComPtr<IDXGIFactory4> dxgiFactory; // !! Newer versions exist.
215 UINT createFactoryFlags = 0;
216#if defined(_DEBUG)
217 createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG; // Gives potential error messages.
218#endif
219
220 ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory)));
221
222 ComPtr<IDXGIAdapter1> dxgiAdapter1;
223 ComPtr<IDXGIAdapter4> dxgiAdapter4;
224
225 if (useWarp) // Good GPU:
226 {
227 ThrowIfFailed(dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&dxgiAdapter1)));
228 ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4)); // Typecasting
229 }
230 else // Not the best GPU:
231 {
232 SIZE_T maxDedicatedVideoMemory = 0;
233 // EnumAdapters finds the amount of GPU adapters in the system.
234 for (UINT i = 0; dxgiFactory->EnumAdapters1(i, &dxgiAdapter1) != DXGI_ERROR_NOT_FOUND; ++i)
235 {
236 DXGI_ADAPTER_DESC1 dxgiAdapterDesc1; // !! Newer versions exist?
237 dxgiAdapter1->GetDesc1(&dxgiAdapterDesc1);
238
239 // Check to see if the adapter can create a D3D12 device without actually
240 // creating it. The adapter with the largest dedicated video memory
241 // is favored...
242 // We only want hardware adapters and therefore DXGI_ADAPTER_FLAG_SOFTWARE adapters should be ignored.
243 // If D3D12CreateDevice generate a S_OK the adapter is D3D12 compatible.
244 // Finally we want the adapter with the largest video memory since it is generally
245 // a good indication of it's performance.
246 if ((dxgiAdapterDesc1.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 &&
247 SUCCEEDED(D3D12CreateDevice(dxgiAdapter1.Get(),
248 D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), nullptr)) &&
249 dxgiAdapterDesc1.DedicatedVideoMemory > maxDedicatedVideoMemory)
250 {
251 maxDedicatedVideoMemory = dxgiAdapterDesc1.DedicatedVideoMemory;
252 ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4));
253 }
254 }
255 }
256
257 // After this we can finally create the DX12 device! :)
258 return dxgiAdapter4;
259}
260
261ComPtr<ID3D12Device2> CreateDevice(ComPtr<IDXGIAdapter4> adapter) // !! Newer device versions exist!
262{
263 // The device is used to allocate resources such as textures.
264 // If destroyed all associated resources are invalid as well.
265
266 // We create the device and stores it.
267 // Parameters for create device is an adapter, feature level, identifier for the device interface and a memory block if specified.
268 ComPtr<ID3D12Device2> d3d12Device2; // !! Up to device8 exist!
269 ThrowIfFailed(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&d3d12Device2)));
270 // Newer feature levels exists as well.
271
272 // To make debugging easier we enable the ID3D12InfoQueue and different types of messages to be passed:
273#if defined(_DEBUG)
274 ComPtr<ID3D12InfoQueue> pInfoQueue;
275 if (SUCCEEDED(d3d12Device2.As(&pInfoQueue)))
276 {
277 pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE);
278 pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
279 pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE);
280
281 // Some warnings can be skipped if wanted:
282 // Suppress whole categories of messages:
283 // D3D12_MESSAGE_CATEGORY Categories[] = {};
284
285 // Suppress messages based on their level of severity:
286 D3D12_MESSAGE_SEVERITY Severities[] =
287 {
288 D3D12_MESSAGE_SEVERITY_INFO // This is only information messages and are therefore suppressed
289 };
290
291 // Suppress individual messages by their ID:
292 D3D12_MESSAGE_ID DenyIds[] =
293 {
294 D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE, // This warning occurs when clearing a render target with an unoptimized color.
295 D3D12_MESSAGE_ID_MAP_INVALID_NULLRANGE, // This and the next warning occurs when a frame is captured using the Visual Studio graphics debugger.
296 D3D12_MESSAGE_ID_UNMAP_INVALID_NULLRANGE
297 };
298
299 // Creates a filter for the messages to be debugged.
300 D3D12_INFO_QUEUE_FILTER NewFilter = {};
301 // NewFilter.DenyList.NumCategories = _countof(Categories);
302 // NewFilter.DenyList.pCategoryList = Categories;
303 NewFilter.DenyList.NumSeverities = _countof(Severities);
304 NewFilter.DenyList.pSeverityList = Severities;
305 NewFilter.DenyList.NumIDs = _countof(DenyIds);
306 NewFilter.DenyList.pIDList = DenyIds;
307
308 ThrowIfFailed(pInfoQueue->PushStorageFilter(&NewFilter));
309 }
310#endif
311
312 return d3d12Device2;
313}
314
315ComPtr<ID3D12CommandQueue> CreateCommandQueue(ComPtr<ID3D12Device2> device, D3D12_COMMAND_LIST_TYPE type) // !! Device exists up to device8!
316{
317 ComPtr<ID3D12CommandQueue> d3d12CommandQueue;
318
319 D3D12_COMMAND_QUEUE_DESC desc = {};
320 desc.Type = type; // Type of command queue: DIRECT, COMPUTE or COPY
321 desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; // Priority: Normal, high or global realtime
322 desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; // Flags for the command queue
323 desc.NodeMask = 0; // If multiple GPU nodes set this mask to the device's physical adapter this queue applies to.
324
325 ThrowIfFailed(device->CreateCommandQueue(&desc, IID_PPV_ARGS(&d3d12CommandQueue)));
326
327 return d3d12CommandQueue;
328}
329
330bool CheckTearingSupport()
331{
332 // Check for screen tearing support.
333 // The feature DXGI_FEATURE_PRESENT_ALLOW_TEARING must be present.
334 // If so, DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING must be specified when creating the swap chain.
335 // Furthermore, the DXGI_PRESENT_ALLOW_TEARING flag must be used when presenting with a sync-interval of 0.
336
337 BOOL allowTearing = false;
338 // Rather than create the DXGI 1.5 factory interface directly, we create the
339 // DXGI 1.4 interface and query for the 1.5 interface. This is to enable the
340 // graphics debugging tools which will not support the 1.5 factory interface
341 // until a future update. !! These might be present now though since this is
342 // a tutorial with a few years behind it...
343 ComPtr<IDXGIFactory4> factory4; // !! Factory7 exist!
344 if (SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&factory4)))) // !! Newer versions of createDXGI exist
345 {
346 ComPtr<IDXGIFactory5> factory5; // !! Up to 7 exist
347 if (SUCCEEDED(factory4.As(&factory5)))
348 {
349 if (FAILED(factory5->CheckFeatureSupport(
350 DXGI_FEATURE_PRESENT_ALLOW_TEARING, // Feature
351 &allowTearing, sizeof(allowTearing)))) // Buffer to be filled which describes the feature support and the size.
352 {
353 allowTearing = FALSE;
354 }
355 }
356 }
357
358 return allowTearing == TRUE;
359}
360
361ComPtr<IDXGISwapChain4> CreateSwapChain(HWND hWnd, ComPtr<ID3D12CommandQueue> commandQueue,
362 uint32_t width, uint32_t height, uint32_t bufferCount)
363{
364 // The primary purpose of the swap chain is to present the rendered image to screen.
365 // The swap chain stores two or more buffers that are swapped between to present the image.
366 // The buffer that is currently being rendered to is called the back buffer, the buffer that is
367 // on screen is called the front buffer. They swap places using the IDXGISwapChain::Present function.
368 // To swap between buffers DX12 uses a flip effect which can either be DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
369 // or _DISCARD (at the end). The difference is that with discard you discard the contents of the back buffer
370 // when present has been called and this makes it so multisampling and partial presentation works.
371 // SEQUENTIAL makes it so that the contents of the back buffer persist even after a present call.
372 // Multisampling does not work with sequential. DISCARD is generally faster too since it just pushes
373 // the backbuffer to screen and not to a queue.
374
375 ComPtr<IDXGISwapChain4> dxgiSwapChain4;
376 ComPtr<IDXGIFactory4> dxgiFactory4; // !! Factory7 exist!
377 UINT createFactoryFlags = 0;
378#if defined(_DEBUG)
379 createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG;
380#endif
381
382 ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory4)));
383
384 DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
385 swapChainDesc.Width = width; // Resolution width
386 swapChainDesc.Height = height; // Resolution height. This and width would get the output window's size if CreateSwapChainForHwnd and 0 is used.
387 swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // Display format
388 swapChainDesc.Stereo = FALSE; // Is the full-screen display and swap-chain back buffer stereo?
389 swapChainDesc.SampleDesc = { 1, 0 }; // Describes multisampling parameters. When flip is used, this has to be { 1, 0 }
390 swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // The surface usage of the back buffer, either shader input or render target
391 swapChainDesc.BufferCount = bufferCount; // Number of buffers in the swap chain.
392 swapChainDesc.Scaling = DXGI_SCALING_STRETCH; // Resize behavior of the back buffer. Specifies what happens when the render target is of different size.
393 swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // Previously mentioned swap effect/presentation model. Sequential or discard.
394 swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; // Transparency behavior of the back buffer
395 // It is recommended to always allow tearing if tearing is supported
396 swapChainDesc.Flags = CheckTearingSupport() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
397
398 ComPtr<IDXGISwapChain1> swapChain1; // !! Up to 4 exist!
399 ThrowIfFailed(dxgiFactory4->CreateSwapChainForHwnd( // Used to create a swap chain associated with a specific window handle.
400 commandQueue.Get(), // D3D12 direct command queue. Cannot be NULL.
401 hWnd, // Window handle.
402 &swapChainDesc, // Swap chain descriptor.
403 nullptr, // NULL = windowed swap chain. Can be full screen as well.
404 nullptr, // This target can be set to a specific output but in this case we don't need to.
405 &swapChain1 // The actual swap chain.
406 ));
407
408 // Disable the Alt+Enter fullscreen toggle feature. Switching to fullscreen will be handled manually.
409 ThrowIfFailed(dxgiFactory4->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER));
410
411 ThrowIfFailed(swapChain1.As(&dxgiSwapChain4));
412
413 return dxgiSwapChain4;
414
415 // To render to the swap chain's back buffers we need a RTV, render target views (views = resources) which
416 // needs to be created for each buffer. Next we will create a descriptor heap which will contain descriptors
417 // that contain the buffers.
418}
419
420ComPtr<ID3D12DescriptorHeap> CreateDescriptorHeap(ComPtr<ID3D12Device2> device, // !! Newer devices exist!
421 D3D12_DESCRIPTOR_HEAP_TYPE type, uint32_t numDescriptors)
422{
423 // A descriptor heap can be considered an array of resource views/descriptors (Render Target Views, Shader Resource Views,
424 // Unordered Access Views or Constant Buffer Views). Before these views are created we need a descriptor heap.
425 // CBVs, SRVs and UAVs can be in the same descriptor heap but not RTVs and Sampler views.
426 // Here we create a descriptor heap to store the render target views for the swap chain buffers.
427
428 ComPtr<ID3D12DescriptorHeap> descriptorHeap;
429
430 // The following description could also contain flags (for visible to GPU) and node mask (for multiple adapters).
431 D3D12_DESCRIPTOR_HEAP_DESC desc = {};
432 desc.NumDescriptors = numDescriptors; // Number of descriptors in the heap.
433 desc.Type = type; // Could be:
434 // D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV - For constant buffers, shader resources and unordered access view.
435 // D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER - For textures I guess
436 // D3D12_DESCRIPTOR_HEAP_TYPE_RTV - Render target
437 // D3D12_DESCRIPTOR_HEAP_TYPE_DSV - Depth stencil view
438
439 // If we have a sampler or constant buffer/shader resource/unordered access view we need to
440 // be able to read it from shader:
441 if (type != D3D12_DESCRIPTOR_HEAP_TYPE_RTV && type != D3D12_DESCRIPTOR_HEAP_TYPE_DSV)
442 desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
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
480// Translation of image formats from WIC to DXGI
481DXGI_FORMAT GetDXGIFormatFromWICFormat(WICPixelFormatGUID& wicFormatGUID)
482{
483 if (wicFormatGUID == GUID_WICPixelFormat128bppRGBAFloat) return DXGI_FORMAT_R32G32B32A32_FLOAT;
484 else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBAHalf) return DXGI_FORMAT_R16G16B16A16_FLOAT;
485 else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBA) return DXGI_FORMAT_R16G16B16A16_UNORM;
486 else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBA) return DXGI_FORMAT_R8G8B8A8_UNORM;
487 else if (wicFormatGUID == GUID_WICPixelFormat32bppBGRA) return DXGI_FORMAT_B8G8R8A8_UNORM;
488 else if (wicFormatGUID == GUID_WICPixelFormat32bppBGR) return DXGI_FORMAT_B8G8R8X8_UNORM;
489 else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBA1010102XR) return DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM;
490
491 else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBA1010102) return DXGI_FORMAT_R10G10B10A2_UNORM;
492 else if (wicFormatGUID == GUID_WICPixelFormat16bppBGRA5551) return DXGI_FORMAT_B5G5R5A1_UNORM;
493 else if (wicFormatGUID == GUID_WICPixelFormat16bppBGR565) return DXGI_FORMAT_B5G6R5_UNORM;
494 else if (wicFormatGUID == GUID_WICPixelFormat32bppGrayFloat) return DXGI_FORMAT_R32_FLOAT;
495 else if (wicFormatGUID == GUID_WICPixelFormat16bppGrayHalf) return DXGI_FORMAT_R16_FLOAT;
496 else if (wicFormatGUID == GUID_WICPixelFormat16bppGray) return DXGI_FORMAT_R16_UNORM;
497 else if (wicFormatGUID == GUID_WICPixelFormat8bppGray) return DXGI_FORMAT_R8_UNORM;
498 else if (wicFormatGUID == GUID_WICPixelFormat8bppAlpha) return DXGI_FORMAT_A8_UNORM;
499
500 else return DXGI_FORMAT_UNKNOWN;
501}
502
503// Get a DXGI compatible WIC format from another WIC format:
504WICPixelFormatGUID GetConvertToWICFormat(WICPixelFormatGUID& wicFormatGUID)
505{
506 if (wicFormatGUID == GUID_WICPixelFormatBlackWhite) return GUID_WICPixelFormat8bppGray;
507 else if (wicFormatGUID == GUID_WICPixelFormat1bppIndexed) return GUID_WICPixelFormat32bppRGBA;
508 else if (wicFormatGUID == GUID_WICPixelFormat2bppIndexed) return GUID_WICPixelFormat32bppRGBA;
509 else if (wicFormatGUID == GUID_WICPixelFormat4bppIndexed) return GUID_WICPixelFormat32bppRGBA;
510 else if (wicFormatGUID == GUID_WICPixelFormat8bppIndexed) return GUID_WICPixelFormat32bppRGBA;
511 else if (wicFormatGUID == GUID_WICPixelFormat2bppGray) return GUID_WICPixelFormat8bppGray;
512 else if (wicFormatGUID == GUID_WICPixelFormat4bppGray) return GUID_WICPixelFormat8bppGray;
513 else if (wicFormatGUID == GUID_WICPixelFormat16bppGrayFixedPoint) return GUID_WICPixelFormat16bppGrayHalf;
514 else if (wicFormatGUID == GUID_WICPixelFormat32bppGrayFixedPoint) return GUID_WICPixelFormat32bppGrayFloat;
515 else if (wicFormatGUID == GUID_WICPixelFormat16bppBGR555) return GUID_WICPixelFormat16bppBGRA5551;
516 else if (wicFormatGUID == GUID_WICPixelFormat32bppBGR101010) return GUID_WICPixelFormat32bppRGBA1010102;
517 else if (wicFormatGUID == GUID_WICPixelFormat24bppBGR) return GUID_WICPixelFormat32bppRGBA;
518 else if (wicFormatGUID == GUID_WICPixelFormat24bppRGB) return GUID_WICPixelFormat32bppRGBA;
519 else if (wicFormatGUID == GUID_WICPixelFormat32bppPBGRA) return GUID_WICPixelFormat32bppRGBA;
520 else if (wicFormatGUID == GUID_WICPixelFormat32bppPRGBA) return GUID_WICPixelFormat32bppRGBA;
521 else if (wicFormatGUID == GUID_WICPixelFormat48bppRGB) return GUID_WICPixelFormat64bppRGBA;
522 else if (wicFormatGUID == GUID_WICPixelFormat48bppBGR) return GUID_WICPixelFormat64bppRGBA;
523 else if (wicFormatGUID == GUID_WICPixelFormat64bppBGRA) return GUID_WICPixelFormat64bppRGBA;
524 else if (wicFormatGUID == GUID_WICPixelFormat64bppPRGBA) return GUID_WICPixelFormat64bppRGBA;
525 else if (wicFormatGUID == GUID_WICPixelFormat64bppPBGRA) return GUID_WICPixelFormat64bppRGBA;
526 else if (wicFormatGUID == GUID_WICPixelFormat48bppRGBFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
527 else if (wicFormatGUID == GUID_WICPixelFormat48bppBGRFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
528 else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBAFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
529 else if (wicFormatGUID == GUID_WICPixelFormat64bppBGRAFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
530 else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf;
531 else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBHalf) return GUID_WICPixelFormat64bppRGBAHalf;
532 else if (wicFormatGUID == GUID_WICPixelFormat48bppRGBHalf) return GUID_WICPixelFormat64bppRGBAHalf;
533 else if (wicFormatGUID == GUID_WICPixelFormat128bppPRGBAFloat) return GUID_WICPixelFormat128bppRGBAFloat;
534 else if (wicFormatGUID == GUID_WICPixelFormat128bppRGBFloat) return GUID_WICPixelFormat128bppRGBAFloat;
535 else if (wicFormatGUID == GUID_WICPixelFormat128bppRGBAFixedPoint) return GUID_WICPixelFormat128bppRGBAFloat;
536 else if (wicFormatGUID == GUID_WICPixelFormat128bppRGBFixedPoint) return GUID_WICPixelFormat128bppRGBAFloat;
537 else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBE) return GUID_WICPixelFormat128bppRGBAFloat;
538 else if (wicFormatGUID == GUID_WICPixelFormat32bppCMYK) return GUID_WICPixelFormat32bppRGBA;
539 else if (wicFormatGUID == GUID_WICPixelFormat64bppCMYK) return GUID_WICPixelFormat64bppRGBA;
540 else if (wicFormatGUID == GUID_WICPixelFormat40bppCMYKAlpha) return GUID_WICPixelFormat64bppRGBA;
541 else if (wicFormatGUID == GUID_WICPixelFormat80bppCMYKAlpha) return GUID_WICPixelFormat64bppRGBA;
542
543#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE)
544 else if (wicFormatGUID == GUID_WICPixelFormat32bppRGB) return GUID_WICPixelFormat32bppRGBA;
545 else if (wicFormatGUID == GUID_WICPixelFormat64bppRGB) return GUID_WICPixelFormat64bppRGBA;
546 else if (wicFormatGUID == GUID_WICPixelFormat64bppPRGBAHalf) return GUID_WICPixelFormat64bppRGBAHalf;
547#endif
548
549 else return GUID_WICPixelFormatDontCare;
550}
551
552// Get the number of bits per pixel for a DXGI format:
553int GetDXGIFormatBitsPerPixel(DXGI_FORMAT& dxgiFormat)
554{
555 if (dxgiFormat == DXGI_FORMAT_R32G32B32A32_FLOAT) return 128;
556 else if (dxgiFormat == DXGI_FORMAT_R16G16B16A16_FLOAT) return 64;
557 else if (dxgiFormat == DXGI_FORMAT_R16G16B16A16_UNORM) return 64;
558 else if (dxgiFormat == DXGI_FORMAT_R8G8B8A8_UNORM) return 32;
559 else if (dxgiFormat == DXGI_FORMAT_B8G8R8A8_UNORM) return 32;
560 else if (dxgiFormat == DXGI_FORMAT_B8G8R8X8_UNORM) return 32;
561 else if (dxgiFormat == DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM) return 32;
562
563 else if (dxgiFormat == DXGI_FORMAT_R10G10B10A2_UNORM) return 32;
564 else if (dxgiFormat == DXGI_FORMAT_B5G5R5A1_UNORM) return 16;
565 else if (dxgiFormat == DXGI_FORMAT_B5G6R5_UNORM) return 16;
566 else if (dxgiFormat == DXGI_FORMAT_R32_FLOAT) return 32;
567 else if (dxgiFormat == DXGI_FORMAT_R16_FLOAT) return 16;
568 else if (dxgiFormat == DXGI_FORMAT_R16_UNORM) return 16;
569 else if (dxgiFormat == DXGI_FORMAT_R8_UNORM) return 8;
570 else if (dxgiFormat == DXGI_FORMAT_A8_UNORM) return 8;
571}
572
573// Load and decode an image from file:
574int LoadImageDataFromFile(BYTE** imageData, D3D12_RESOURCE_DESC& resourceDescription, LPCWSTR filename, int& bytesPerRow)
575{
576 HRESULT hr;
577
578 // We only need one instance of the imaging factory to create decoders and frames
579 static IWICImagingFactory* wicFactory;
580
581 // Reset decoder, frame and converter since these will be different for each image we load
582 IWICBitmapDecoder* wicDecoder = NULL;
583 IWICBitmapFrameDecode* wicFrame = NULL;
584 IWICFormatConverter* wicConverter = NULL;
585
586 bool imageConverted = false;
587
588 if (wicFactory == NULL)
589 {
590 // Inititalize the COM library if it is the first time we read a texture:
591 CoInitialize(NULL);
592
593 // Create the WIC factory:
594 hr = CoCreateInstance(
595 CLSID_WICImagingFactory,
596 NULL,
597 CLSCTX_INPROC_SERVER,
598 IID_PPV_ARGS(&wicFactory)
599 );
600 if (FAILED(hr)) return 0;
601 }
602
603 // Load a decoder for the image
604 hr = wicFactory->CreateDecoderFromFilename(
605 filename, // Image we want to load in
606 NULL, // This is a vendor ID, we do not prefer a specific one so set to null
607 GENERIC_READ, // We want to read from this file
608 WICDecodeMetadataCacheOnLoad, // We will cache the metadata right away, rather than when needed, which might be unknown
609 &wicDecoder // The WIC decoder to be created
610 );
611 if (FAILED(hr)) return 0;
612
613 // Get image from decoder (this will decode the "frame")
614 hr = wicDecoder->GetFrame(0, &wicFrame); // 0 indicates first frame. If we have a GIF it contains multiple frames.
615 if (FAILED(hr)) return 0;
616
617 // Get WIC pixel format of image
618 WICPixelFormatGUID pixelFormat;
619 hr = wicFrame->GetPixelFormat(&pixelFormat);
620 if (FAILED(hr)) return 0;
621
622 // Get size of image
623 UINT textureWidth, textureHeight;
624 hr = wicFrame->GetSize(&textureWidth, &textureHeight);
625 if (FAILED(hr)) return 0;
626
627 // Convert WIC pixel format to dxgi pixel format
628 DXGI_FORMAT dxgiFormat = GetDXGIFormatFromWICFormat(pixelFormat);
629
630 // If the format of the image is not a supported dxgi format, try to convert it
631 if (dxgiFormat == DXGI_FORMAT_UNKNOWN)
632 {
633 // Get a dxgi compatible wic format from the current image format
634 WICPixelFormatGUID convertToPixelFormat = GetConvertToWICFormat(pixelFormat);
635
636 // Return if no dxgi compatible format was found
637 if (convertToPixelFormat == GUID_WICPixelFormatDontCare) return 0;
638
639 // Set the dxgi format
640 dxgiFormat = GetDXGIFormatFromWICFormat(convertToPixelFormat);
641
642 // Create the format converter
643 hr = wicFactory->CreateFormatConverter(&wicConverter);
644 if (FAILED(hr)) return 0;
645
646 // Make sure we can convert to the dxgi compatible format
647 BOOL canConvert = FALSE;
648 hr = wicConverter->CanConvert(pixelFormat, convertToPixelFormat, &canConvert);
649 if (FAILED(hr) || !canConvert) return 0;
650
651 // Do the conversion (wicConverter will contain the converted image)
652 hr = wicConverter->Initialize(wicFrame, convertToPixelFormat, WICBitmapDitherTypeErrorDiffusion, 0, 0, WICBitmapPaletteTypeCustom);
653 if (FAILED(hr)) return 0;
654
655 // This is so we know to get the image data from the wicConverter (otherwise we will get from wicFrame)
656 imageConverted = true;
657 }
658
659 int bitsPerPixel = GetDXGIFormatBitsPerPixel(dxgiFormat); // Number of bits per pixel
660 bytesPerRow = (textureWidth * bitsPerPixel) / 8; // Number of bytes in each row of the image data
661 int imageSize = bytesPerRow * textureHeight; // Total image size in bytes
662
663 // Allocate enough memory for the raw image data, and set imageData to point to that memory
664 *imageData = (BYTE*)malloc(imageSize);
665
666 // Copy (decoded) raw image data into the newly allocated memory (imageData)
667 if (imageConverted)
668 {
669 // If image format needed to be converted the wic converter will contain the converted image
670 hr = wicConverter->CopyPixels(0, bytesPerRow, imageSize, *imageData);
671 if (FAILED(hr)) return 0;
672 }
673 else
674 {
675 // No need to convert, just copy data from the wic frame
676 hr = wicFrame->CopyPixels(0, bytesPerRow, imageSize, *imageData);
677 if (FAILED(hr)) return 0;
678 }
679
680 // Now we describe the texture with the information we have obtained from the image
681 resourceDescription = {};
682 resourceDescription.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
683 resourceDescription.Alignment = 0; // May be 0, 4KB, 64KB or 4MB. 0 will let runtime decide between 64KB and 4MB (4MB for multi-sampled textures)
684 resourceDescription.Width = textureWidth; // Width of the texture.
685 resourceDescription.Height = textureHeight; // Height of the texture.
686 resourceDescription.DepthOrArraySize = 1; // If 3d image, depth of 3d image. Otherwise an array of 1D or 2D textures (we only have one image, so we set 1)
687 resourceDescription.MipLevels = 1; // Number of mipmaps. We are not generating mipmaps for this texture, so we have only one level
688 resourceDescription.Format = dxgiFormat; // This is the dxgi format of the image (format of the pixels)
689 resourceDescription.SampleDesc.Count = 1; // This is the number of samples per pixel, we just want 1 sample
690 resourceDescription.SampleDesc.Quality = 0; // The quality level of the samples. Higher is better quality, but worse performance
691 resourceDescription.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; // The arrangement of the pixels. Setting to unknown lets the driver choose the most efficient one
692 resourceDescription.Flags = D3D12_RESOURCE_FLAG_NONE; // No flags
693
694 // Return the size of the image. Remember to delete the image once your done with it.
695 return imageSize;
696}
697
698void CreateViewportAndScissorRect()
699{
700 gViewport.TopLeftX = 0.0f;
701 gViewport.TopLeftY = 0.0f;
702 gViewport.Width = (float)g_ClientWidth;
703 gViewport.Height = (float)g_ClientHeight;
704 gViewport.MinDepth = 0.0f;
705 gViewport.MaxDepth = 1.0f;
706
707 gScissorRect.left = (long)0;
708 gScissorRect.right = (long)g_ClientWidth;
709 gScissorRect.top = (long)0;
710 gScissorRect.bottom = (long)g_ClientHeight;
711}
712
713void CreateConstantBufferResources()
714{
715 for (int i = 0; i < g_NumFrames; i++)
716 {
717 D3D12_DESCRIPTOR_HEAP_DESC descriptorHeapDesc = {};
718 descriptorHeapDesc.NumDescriptors = 1;
719 descriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
720 descriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
721 ThrowIfFailed(g_Device->CreateDescriptorHeap(&descriptorHeapDesc, IID_PPV_ARGS(&g_DescriptorHeap[i])));
722 }
723
724 UINT cbSizeAligned = (sizeof(ConstantBuffer) + 255) & ~255; // 256-byte aligned CB.
725
726 D3D12_HEAP_PROPERTIES heapProperties = {};
727 heapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;
728 heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
729 heapProperties.CreationNodeMask = 1; //used when multi-gpu
730 heapProperties.VisibleNodeMask = 1; //used when multi-gpu
731 heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
732
733 D3D12_RESOURCE_DESC resourceDesc = {};
734 resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
735 resourceDesc.Width = cbSizeAligned;
736 resourceDesc.Height = 1;
737 resourceDesc.DepthOrArraySize = 1;
738 resourceDesc.MipLevels = 1;
739 resourceDesc.SampleDesc.Count = 1;
740 resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
741
742 //Create a resource heap, descriptor heap, and pointer to cbv for each frame
743 for (int i = 0; i < g_NumFrames; i++)
744 {
745 g_Device->CreateCommittedResource(
746 &heapProperties,
747 D3D12_HEAP_FLAG_NONE,
748 &resourceDesc,
749 D3D12_RESOURCE_STATE_GENERIC_READ,
750 nullptr,
751 IID_PPV_ARGS(&g_ConstantBufferResource[i])
752 );
753
754 g_ConstantBufferResource[i]->SetName(L"CB Heap");
755
756 D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
757 cbvDesc.BufferLocation = g_ConstantBufferResource[i]->GetGPUVirtualAddress();
758 cbvDesc.SizeInBytes = cbSizeAligned;
759 g_Device->CreateConstantBufferView(&cbvDesc, g_DescriptorHeap[i]->GetCPUDescriptorHandleForHeapStart());
760 }
761}
762
763void CreateRootSignature()
764{
765 // Here we have some new stuff
766 // Constant buffers, textures and samplers are located in different registers
767 // and register spaces. Buffers go in b0, b1..., textures t0, t1... and samplers
768 // in s0, s1...
769
770 // Descriptor for our constant buffer which will be the first root parameter:
771 D3D12_ROOT_DESCRIPTOR rootCBVDescriptor;
772 rootCBVDescriptor.RegisterSpace = 0; // b0
773 rootCBVDescriptor.ShaderRegister = 0; // b+
774
775 // Define descriptor range(s), only one range since constant buffer is a root
776 // constant. Only shader resource in this range.
777 D3D12_DESCRIPTOR_RANGE dtRanges[1] = {};
778
779 dtRanges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
780 dtRanges[0].NumDescriptors = 1; // Only one CB in this case
781 dtRanges[0].BaseShaderRegister = 0; // Register t0
782 dtRanges[0].RegisterSpace = 0; // Register (t0)
783 dtRanges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
784
785 // Create a descriptor table
786 D3D12_ROOT_DESCRIPTOR_TABLE dt = {};
787 dt.NumDescriptorRanges = ARRAYSIZE(dtRanges); // 1 in this case.
788 dt.pDescriptorRanges = dtRanges; // Pointer to Descriptor range.
789
790 // Create root parameter(s)
791 D3D12_ROOT_PARAMETER rootParam[2] = {};
792 rootParam[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
793 rootParam[0].Descriptor = rootCBVDescriptor;
794 rootParam[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
795
796 rootParam[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
797 rootParam[1].DescriptorTable = dt;
798 rootParam[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
799
800 // Create a static sampler
801 // This describes wrap mode (if we index outside of UV coordinates) etc...
802 D3D12_STATIC_SAMPLER_DESC sampler = {};
803 sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
804 sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
805 sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
806 sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
807 sampler.MipLODBias = 0;
808 sampler.MaxAnisotropy = 0;
809 sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
810 sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
811 sampler.MinLOD = 0.0f;
812 sampler.MaxLOD = D3D12_FLOAT32_MAX;
813 sampler.ShaderRegister = 0; // s0
814 sampler.RegisterSpace = 0; // s0
815 sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
816
817 D3D12_ROOT_SIGNATURE_DESC rsDesc = {};
818 rsDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
819 rsDesc.NumParameters = ARRAYSIZE(rootParam);
820 rsDesc.pParameters = rootParam;
821 rsDesc.NumStaticSamplers = 1;
822 rsDesc.pStaticSamplers = &sampler;
823
824 ID3DBlob* sBlob;
825 D3D12SerializeRootSignature(
826 &rsDesc,
827 D3D_ROOT_SIGNATURE_VERSION_1,
828 &sBlob,
829 nullptr
830 );
831
832 g_Device->CreateRootSignature(
833 0,
834 sBlob->GetBufferPointer(),
835 sBlob->GetBufferSize(),
836 IID_PPV_ARGS(&g_RootSignature)
837 );
838}
839
840void CreateShadersAndPipelineState()
841{
842 // Shader compiles:
843 ID3DBlob* vertexBlob;
844 D3DCompileFromFile(
845 L"VertexShader.hlsl", // Filename
846 nullptr, // optional macros
847 nullptr, // optional include files
848 "VS_main", // entry point
849 "vs_5_0", // shader model (target)
850 0, // shader compile options // here DEBUGGING OPTIONS
851 0, // effect compile options
852 &vertexBlob, // double pointer to ID3DBlob
853 nullptr // pointer for Error Blob messages.
854 // how to use the Error blob, see here
855 // https://msdn.microsoft.com/en-us/library/windows/desktop/hh968107(v=vs.85).aspx
856 );
857
858 ID3DBlob* pixelBlob;
859 D3DCompileFromFile(
860 L"PixelShader.hlsl",
861 nullptr,
862 nullptr,
863 "PS_main",
864 "ps_5_0",
865 0,
866 0,
867 &pixelBlob,
868 nullptr
869 );
870
871 // Input layout:
872 D3D12_INPUT_ELEMENT_DESC inputElementDesc[] = {
873 // SemanticName, SemanticIndex, Format, InputSlot, AlignedByteOffset, InputSlotClass, InstanceDataStepRate
874 { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
875 { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
876 { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
877 };
878
879 D3D12_INPUT_LAYOUT_DESC inputLayoutDesc = {};
880 inputLayoutDesc.pInputElementDescs = inputElementDesc; // Pointer to attribute description
881 inputLayoutDesc.NumElements = ARRAYSIZE(inputElementDesc); // Number of attributes per vertex
882
883 // Pipeline state:
884 D3D12_GRAPHICS_PIPELINE_STATE_DESC gpsd = {};
885
886 // Specify pipeline stages:
887 gpsd.pRootSignature = g_RootSignature;
888 gpsd.InputLayout = inputLayoutDesc;
889 gpsd.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
890 gpsd.VS.pShaderBytecode = reinterpret_cast<void*>(vertexBlob->GetBufferPointer());
891 gpsd.VS.BytecodeLength = vertexBlob->GetBufferSize();
892 gpsd.PS.pShaderBytecode = reinterpret_cast<void*>(pixelBlob->GetBufferPointer());
893 gpsd.PS.BytecodeLength = pixelBlob->GetBufferSize();
894
895 // Specify render target and depth stencil usage:
896 gpsd.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
897 gpsd.NumRenderTargets = 1;
898
899 gpsd.SampleDesc.Count = 1;
900 gpsd.SampleMask = UINT_MAX;
901
902 // Specify rasterizer behaviour:
903 gpsd.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
904 gpsd.RasterizerState.CullMode = D3D12_CULL_MODE_BACK; // Back face culling I pressume
905
906 // Specify blend descriptions:
907 D3D12_RENDER_TARGET_BLEND_DESC defaultRTdesc = {
908 false,
909 false,
910 D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
911 D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
912 D3D12_LOGIC_OP_NOOP, D3D12_COLOR_WRITE_ENABLE_ALL
913 };
914
915 for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
916 {
917 gpsd.BlendState.RenderTarget[i] = defaultRTdesc;
918 }
919
920 g_Device->CreateGraphicsPipelineState(&gpsd, IID_PPV_ARGS(&g_PipelineState));
921}
922
923void CreateTriangleData()
924{
925 Vertex triangleVertices[] =
926 {
927 // Added texcoords...
928 0.0f, 0.5f, 0.0f, //v0 pos
929 1.0f, 0.0f, 0.0f, //v0 color
930 0.5f, 0.0f, //v0 texcoord
931
932 0.5f, -0.5f, 0.0f, //v1
933 0.0f, 1.0f, 0.0f, //v1 color
934 1.0f, 1.0f, //v1 texcoord
935
936 -0.5f, -0.5f, 0.0f, //v2
937 0.0f, 0.0f, 1.0f, //v2 color
938 0.0f, 1.0f, //v2 texcoord
939
940 0.0f, 1.5f, 0.0f, //v0 pos
941 1.0f, 0.0f, 0.0f, //v0 color
942 0.5f, 0.0f, //v0 texcoord
943
944 0.5f, 0.5f, 0.0f, //v1
945 0.0f, 1.0f, 0.0f, //v1 color
946 1.0f, 1.0f, //v1 texcoord
947
948 -0.5f, 0.5f, 0.0f, //v2
949 0.0f, 0.0f, 1.0f, //v2 color
950 0.0f, 1.0f, //v2 texcoord
951 };
952
953 // Note: using upload heaps to transfer static data like vert buffers is not
954 // recommended. Every time the GPU needs it, the upload heap will be marshalled
955 // over. Please read up on Default Heap usage. An upload heap is used here for
956 // code simplicity and because there are very few vertices to actually transfer.
957 D3D12_HEAP_PROPERTIES hp = {};
958 hp.Type = D3D12_HEAP_TYPE_UPLOAD;
959 hp.CreationNodeMask = 1;
960 hp.VisibleNodeMask = 1;
961
962 D3D12_RESOURCE_DESC rd = {};
963 rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
964 rd.Width = sizeof(triangleVertices);
965 rd.Height = 1;
966 rd.DepthOrArraySize = 1;
967 rd.MipLevels = 1;
968 rd.SampleDesc.Count = 1;
969 rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
970
971 // Creates both a resource and an implicit heap, such that the heap is big enough
972 // to contain the entire resource and the resource is mapped to the heap.
973 g_Device->CreateCommittedResource(
974 &hp,
975 D3D12_HEAP_FLAG_NONE,
976 &rd,
977 D3D12_RESOURCE_STATE_GENERIC_READ,
978 nullptr,
979 IID_PPV_ARGS(&g_VertexBufferResource)
980 );
981
982 // Copy the triangle data to the vertex buffer:
983 void* dataBegin = nullptr;
984 D3D12_RANGE range = { 0, 0 }; // We do not intend to read this resource on the CPU.
985 g_VertexBufferResource->Map(0, &range, &dataBegin);
986 memcpy(dataBegin, triangleVertices, sizeof(triangleVertices));
987 g_VertexBufferResource->Unmap(0, nullptr);
988
989 // Inititialize vertex buffer view, used in render call:
990 g_VertexBufferView.BufferLocation = g_VertexBufferResource->GetGPUVirtualAddress();
991 g_VertexBufferView.StrideInBytes = sizeof(Vertex);
992 g_VertexBufferView.SizeInBytes = sizeof(triangleVertices);
993}
994
995void CreateTexture()
996{
997 // !! TODO: Might need to do this for the number of frames. See ConstantBuffer creation.
998
999 // Loads image from a WIC compatible file
1000 // Returns the size in bytes and sets the data of in parameters
1001 g_ImageSize = LoadImageDataFromFile(&g_ImageData, g_TextureDesc, L"../Textures/fatboy.png", g_ImageBytesPerRow);
1002 char buffer[100]; // Debug text buffer
1003
1004 // Check that the texture has been read
1005 if (g_ImageSize <= 0)
1006 {
1007 sprintf_s(buffer, 100, "Error reading texture!\n");
1008 OutputDebugStringA(buffer);
1009 }
1010 else
1011 {
1012 sprintf_s(buffer, 100, "Succesfully read texture!\n");
1013 OutputDebugStringA(buffer);
1014 }
1015
1016 // Default resource heap:
1017 auto defaultHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
1018
1019 // Creates a default resource heap for and sets it to the texture buffer
1020 ThrowIfFailed(g_Device->CreateCommittedResource(
1021 &defaultHeapProperties,
1022 D3D12_HEAP_FLAG_NONE,
1023 &g_TextureDesc,
1024 D3D12_RESOURCE_STATE_COPY_DEST,
1025 nullptr,
1026 IID_PPV_ARGS(&g_TextureBuffer)
1027 ));
1028 g_TextureBuffer->SetName(L"Texture Buffer Resource Heap");
1029
1030 // Upload resource heap:
1031 // This is the size that is to be uploaded which is set to textureUploadBufferSize
1032 UINT64 textureUploadBufferSize;
1033 g_Device->GetCopyableFootprints(&g_TextureDesc, 0, 1, 0, nullptr, nullptr, nullptr, &textureUploadBufferSize);
1034
1035 // Here we need a resource upload heap. Using the help library we create a description
1036 // of a buffer of size textureUploadBufferSize
1037 auto uploadHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
1038 auto resourceDesc = CD3DX12_RESOURCE_DESC::Buffer(textureUploadBufferSize);
1039
1040 // Creates resource upload heap.
1041 ThrowIfFailed(g_Device->CreateCommittedResource(
1042 &uploadHeapProperties,
1043 D3D12_HEAP_FLAG_NONE,
1044 &resourceDesc,
1045 D3D12_RESOURCE_STATE_GENERIC_READ,
1046 nullptr,
1047 IID_PPV_ARGS(&g_TextureBufferUploadHeap)
1048 ));
1049 g_TextureBufferUploadHeap->SetName(L"Texture Buffer Upload Resource Heap");
1050
1051 // Format of and pointer to image data
1052 D3D12_SUBRESOURCE_DATA textureData = {};
1053 textureData.pData = &g_ImageData[0];
1054 textureData.RowPitch = g_ImageBytesPerRow;
1055 textureData.SlicePitch = g_ImageBytesPerRow * g_TextureDesc.Height;
1056
1057 // Upload the texture:
1058 UpdateSubresources(g_CommandList.Get(), g_TextureBuffer.Get(), g_TextureBufferUploadHeap.Get(), 0, 0, 1, &textureData);
1059
1060 // We need to transition the texture buffer from a copy destination to a pixel shader resource
1061 auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(g_TextureBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
1062 g_CommandList->ResourceBarrier(1, &barrier);
1063
1064 // We create the descriptor heap/memory block for the texture
1065 g_TextureDescriptorHeap = CreateDescriptorHeap(g_Device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 1);
1066 g_TextureDescriptorHeapSize = g_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
1067
1068 // Creates the shader resource which will be read in shader
1069 D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
1070 srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
1071 srvDesc.Format = g_TextureDesc.Format;
1072 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
1073 srvDesc.Texture2D.MipLevels = 1;
1074 g_Device->CreateShaderResourceView(g_TextureBuffer.Get(), &srvDesc, g_TextureDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
1075}
1076
1077void SetResourceTransitionBarrier(ID3D12GraphicsCommandList* commandList, ID3D12Resource* resource,
1078 D3D12_RESOURCE_STATES StateBefore, D3D12_RESOURCE_STATES StateAfter)
1079{
1080 D3D12_RESOURCE_BARRIER barrierDesc = {};
1081
1082 barrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
1083 barrierDesc.Transition.pResource = resource;
1084 barrierDesc.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
1085 barrierDesc.Transition.StateBefore = StateBefore;
1086 barrierDesc.Transition.StateAfter = StateAfter;
1087
1088 commandList->ResourceBarrier(1, &barrierDesc);
1089}
1090
1091ComPtr<ID3D12CommandAllocator> CreateCommandAllocator(ComPtr<ID3D12Device2> device, D3D12_COMMAND_LIST_TYPE type) // !! Newer devices exist!
1092{
1093 // A command allocator is the backing memory used by a command list. A command allocator does not provide any
1094 // functionality and must be accessed indirectly by a command list. A command allocator can only be used by a
1095 // single command list at a time but can be reused onces all commands that were recorded in to a command list
1096 // are finished executing on the GPU. The memory allocated by a command allocator is reclaimed by using the
1097 // Reset function. Use a GPU fence to check the status of the associated command list.
1098 // To maximize frame-rates one command allocator per operating command list should be created.
1099
1100 ComPtr<ID3D12CommandAllocator> commandAllocator;
1101 ThrowIfFailed(device->CreateCommandAllocator(type, IID_PPV_ARGS(&commandAllocator)));
1102 // Type indicates which command list type it is: DIRECT, BUNDLE, COMPUTE or COPY.
1103 // Second parameter is the unique identifier of our allocator.
1104 // Finally we can provide a third parameter if we wish to specify a memory block.
1105 return commandAllocator;
1106
1107 // This creates one allocator but can be used multiple times to create more.
1108}
1109
1110ComPtr<ID3D12GraphicsCommandList> CreateCommandList(ComPtr<ID3D12Device2> device, // !! Newer command lists and devices exist!
1111 ComPtr<ID3D12CommandAllocator> commandAllocator, D3D12_COMMAND_LIST_TYPE type)
1112{
1113 // A command list is used for recording commands that are to be executed on the GPU. These are deferred.
1114 // That means that none of the commands on the list is executed until the command list is sent to the command queue.
1115 // Unlike the command allocator, the command list can be used immidiately after it has been executed on the command
1116 // queue. The only restriction is that it has to be reset first.
1117
1118 ComPtr<ID3D12GraphicsCommandList> commandList; // !! Newer command lists exists.
1119 ThrowIfFailed(device->CreateCommandList(0, type, commandAllocator.Get(), nullptr, IID_PPV_ARGS(&commandList)));
1120 // Parameters:
1121 // 1: If multiple physical adapters set this to the specific adapter it applies to
1122 // 2: The type: DIRECT, BUNDLE, COMPUTE or COPY.
1123 // 3: The command allocator which the device create command lists from
1124 // 4: Pipeline state object
1125 // 5: Unique identifier of the command list
1126 // (Optional) 6: Specific memory block
1127
1128 // !! Used to close the commandList here but don't since we upload texture data then close it
1129 // The command list should immidiately be closed since no commands are to be recorded as of yet.
1130 // ThrowIfFailed(commandList->Close());
1131
1132 return commandList;
1133}
1134
1135ComPtr<ID3D12Fence> CreateFence(ComPtr<ID3D12Device2> device) // !! Newer fences and devices exist!
1136{
1137 // Fences is an interface for synchronization between the CPU and GPU. Internally it stores a single 64-bit integer
1138 // value which is initialized once the fence is created. The CPU can update said value by using ID3D12Fence::Signal function
1139 // and the GPU uses the command queue's signal function.
1140 // To wait for a specific value on the CPU use ID3D12Fence::SetEventOnCompletion followed by WaitForSingleObject.
1141 // The GPU uses the command queue's wait function.
1142 // Generally the fence object should be initialized with a value of zero and only be allowed to increase.
1143 // Each thread or GPU queue should have at least one fence object and a corresponding fence value.
1144 // More than one thread should no signal that fence value but could wait for it.
1145
1146 ComPtr<ID3D12Fence> fence;
1147
1148 ThrowIfFailed(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));
1149 // Parameters:
1150 // 1: Initial value, generally 0
1151 // 2: Flags, could be NONE, SHARED, SHARED_CROSS_ADAPTER, NON_MONITORED
1152 // 3: Unique identifier
1153 // (Optional) 4: Block of memory
1154
1155 return fence;
1156}
1157
1158HANDLE CreateEventHandle()
1159{
1160 // This function is used to create an event that block any further processing until a fence has been signaled.
1161 HANDLE fenceEvent;
1162
1163 fenceEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
1164 // Parameters:
1165 // 1: A pointer to a SECURITY_ATTRIBUTES structure. If null the handle cant be inherited by child processes.
1166 // 2: If true the event has to be manually reset using the ResetEvent function.
1167 // 3: If true the initial state of the event object is signaled otherwise not.
1168 // 4: The name of the event object.
1169
1170 // This OS event is used to cause a CPU thread to stall using the WaitForSingleObject function.
1171
1172 assert(fenceEvent && "Failed to create fence event");
1173
1174 return fenceEvent;
1175}
1176
1177uint64_t Signal(ComPtr<ID3D12CommandQueue> commandQueue, ComPtr<ID3D12Fence> fence, // !! Newer fences exist
1178 uint64_t& fenceValue)
1179{
1180 // This function is used to update the fence from the GPU.
1181 // The signal gets executed after existing commands in the command queue are finalized.
1182 uint64_t fenceValueForSignal = ++fenceValue;
1183 ThrowIfFailed(commandQueue->Signal(fence.Get(), fenceValueForSignal));
1184 // Parameters of Signal is the fence to update and the value to update it to.
1185
1186 // The returned value is the value the CPU should wait for before reusing any "in-flight" resources on the GPU.
1187 return fenceValueForSignal;
1188}
1189
1190void WaitForFenceValue(ComPtr<ID3D12Fence> fence, uint64_t fenceValue, HANDLE fenceEvent, // !! Newer fences exist
1191 std::chrono::milliseconds duration = std::chrono::milliseconds::max())
1192{
1193 // Write to objects such as RTV buffers need to be waited upon before reused by another command queue.
1194 // Textures that we just read from however don't need this function or double buffering.
1195
1196 // This function is used to stall the CPU thread if the fence has not yet reached (been signaled with)
1197 // a specific value. The function will wait for a duration specified by chrono (about 584 million years :O)
1198 // or until an event object signals the expected value.
1199 if (fence->GetCompletedValue() < fenceValue)
1200 {
1201 ThrowIfFailed(fence->SetEventOnCompletion(fenceValue, fenceEvent));
1202 ::WaitForSingleObject(fenceEvent, static_cast<DWORD>(duration.count()));
1203 }
1204}
1205
1206void Flush(ComPtr<ID3D12CommandQueue> commandQueue, ComPtr<ID3D12Fence> fence, // !! Newer fences exist
1207 uint64_t& fenceValue, HANDLE fenceEvent)
1208{
1209 // The flush function is used to ensure that any commands previously executed on the GPU have
1210 // finished executing before the CPU thread is allowed to continue processing. This is useful for ensuring
1211 // that any back buffer resources being referenced by a command that is currently "in-flight" on the GPU have
1212 // finished executing before being resized. It is also good to flush the GPU command queue before releasing
1213 // any resources that might be referenced by a command list that is in flight on the command queue.
1214 // One such example could be before closing the application.
1215
1216 // We signal the fence on the GPU:
1217 uint64_t fenceValueForSignal = Signal(commandQueue, fence, fenceValue);
1218 // We wait until the fence is signaled after the commandQueue is finalized.
1219 WaitForFenceValue(fence, fenceValueForSignal, fenceEvent);
1220}
1221
1222void Update()
1223{
1224 static uint64_t frameCounter = 0; // Number of frames passed since last update.
1225 static double elapsedSeconds = 0.0; // Time elapsed since last update.
1226 static std::chrono::high_resolution_clock clock;
1227 static auto t0 = clock.now(); // Initital time.
1228
1229 frameCounter++; // Keeps track of the amount of times we have rendered the screen since last time.
1230 auto t1 = clock.now();
1231 auto deltaTime = t1 - t0; // Time passed.
1232 t0 = t1;
1233
1234 elapsedSeconds += deltaTime.count() * 1e-9;
1235 if (elapsedSeconds > 1.0) // We only print once per every second
1236 {
1237 char buffer[500]; // Text buffer for fps
1238 auto fps = frameCounter / elapsedSeconds;
1239 sprintf_s(buffer, 500, "FPS: %f\n", fps);
1240 OutputDebugStringA(buffer);
1241
1242 frameCounter = 0;
1243 elapsedSeconds = 0.0;
1244 }
1245
1246 // Updating colors:
1247 for (int i = 0; i < 3; i++)
1248 {
1249 g_ConstantBufferCPU.colorChannel[i] += 0.0001f * (i + 1);
1250 if (g_ConstantBufferCPU.colorChannel[i] > 1)
1251 {
1252 g_ConstantBufferCPU.colorChannel[i] = 0;
1253 }
1254 }
1255
1256 // Update GPU memory:
1257 void* mappedMem = nullptr;
1258 D3D12_RANGE readRange = { 0, 0 };
1259 if (SUCCEEDED(g_ConstantBufferResource[g_CurrentBackBufferIndex]->Map(0, &readRange, &mappedMem)))
1260 {
1261 memcpy(mappedMem, &g_ConstantBufferCPU, sizeof(ConstantBuffer));
1262
1263 D3D12_RANGE writeRange = { 0, sizeof(ConstantBuffer) };
1264 g_ConstantBufferResource[g_CurrentBackBufferIndex]->Unmap(0, &writeRange);
1265 }
1266}
1267
1268void Render()
1269{
1270 // For this case the Render function is in charge of:
1271 // 1. Clear the back buffer
1272 // 2. Present the rendered frame
1273
1274 // We are in charge that resources are in the correct state before using them.
1275 // Resource barriers are used to transition between states.
1276 // One example: Before using the swap chain's back buffer as a render target it must be in th
1277 // RENDER_TARGET state and to present it it has to be in the PRESENT state. Makes sense...
1278 // Here are some different kinds of resource barriers:
1279 // 1. Transition - transitions a (sub)resource to a particular state before using it.
1280 //For example, before a texture can be used in a pixel shader we have to put it in
1281 // the PIXEL_SHADER_RESOURCE state
1282 // 2. Aliasing - Specifies that a resource is used in a placed or reserved heap when that
1283 // resource is aliased with another resource in the same heap.
1284 // 3. UAV - Indicates that all UAV accesses to a particular resource have completed before
1285 // any future UAV access can begin. This is necessary when the UAV is transitioned for:
1286 // Read > Write: Guarantees that all previous read operations on the UAV have completed before being written to in another shader.
1287 // Write > Read: Guarantees that all previous write operations on the UAV have completed before being read from in another shader.
1288 // Write > Write: Avoids race conditions that could be caused by different shaders in a different draw or dispatch trying to write
1289 // to the same resource(does not avoid race conditions that could be caused in the same draw or dispatch call).
1290 // A UAV barrier is not needed if the resource is being used as a read - only(Read > Read) resource between draw or dispatches.
1291
1292 // Before any commands can be recorded into the command list we reset the command allocator and list.
1293 auto commandAllocator = g_CommandAllocators[g_CurrentBackBufferIndex];
1294 auto backBuffer = g_BackBuffers[g_CurrentBackBufferIndex];
1295
1296 commandAllocator->Reset();
1297 // PipelineState used to be nullptr here:
1298 g_CommandList->Reset(commandAllocator.Get(), g_PipelineState);
1299
1300 // NEW WAY:
1301 // Constant buffer doesn't need to be set here, uploaded in Update()
1302 // ID3D12DescriptorHeap* descriptorHeaps[] = { g_DescriptorHeap[g_CurrentBackBufferIndex] };
1303 // The texture's descriptor heap has to be set however:
1304 ID3D12DescriptorHeap* descriptorHeaps[] = { g_TextureDescriptorHeap.Get() };
1305 g_CommandList->SetDescriptorHeaps(ARRAYSIZE(descriptorHeaps), descriptorHeaps);
1306
1307 // Set root signature:
1308 g_CommandList->SetGraphicsRootSignature(g_RootSignature);
1309
1310 // Since we updated the root signature the constant buffer is at index 0 of the root parameters
1311 g_CommandList->SetGraphicsRootConstantBufferView(
1312 0,
1313 g_ConstantBufferResource[g_CurrentBackBufferIndex]->GetGPUVirtualAddress()
1314 );
1315 // And descriptor table is at index 1
1316 g_CommandList->SetGraphicsRootDescriptorTable(
1317 1,
1318 g_TextureDescriptorHeap->GetGPUDescriptorHandleForHeapStart()
1319 );
1320
1321 // Set necessary states:
1322 g_CommandList->RSSetViewports(1, &gViewport);
1323 g_CommandList->RSSetScissorRects(1, &gScissorRect);
1324
1325 // Indicate that the back buffer will be used as render target:
1326 SetResourceTransitionBarrier(
1327 g_CommandList.Get(),
1328 g_BackBuffers[g_CurrentBackBufferIndex].Get(),
1329 D3D12_RESOURCE_STATE_PRESENT, // state before
1330 D3D12_RESOURCE_STATE_RENDER_TARGET // state after
1331 );
1332
1333 // Record commands:
1334 // Get the handle for the current render target used as back buffer:
1335 D3D12_CPU_DESCRIPTOR_HANDLE cdh = g_RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
1336 cdh.ptr += (size_t)g_RTVDescriptorSize * (size_t)g_CurrentBackBufferIndex;
1337
1338 g_CommandList->OMSetRenderTargets(1, &cdh, true, nullptr);
1339
1340 FLOAT clearColor[] = { 0.4f, 0.6f, 0.9f, 1.0f };
1341 g_CommandList->ClearRenderTargetView(cdh, clearColor, 0, nullptr);
1342
1343 g_CommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
1344 g_CommandList->IASetVertexBuffers(0, 1, &g_VertexBufferView);
1345
1346 g_CommandList->DrawInstanced(3, 1, 0, 0);
1347 // g_CommandList->DrawInstanced(3, 1, 3, 0);
1348
1349 // Indicate that the back buffer will now be used to present:
1350 SetResourceTransitionBarrier(
1351 g_CommandList.Get(),
1352 g_BackBuffers[g_CurrentBackBufferIndex].Get(),
1353 D3D12_RESOURCE_STATE_RENDER_TARGET, // state before
1354 D3D12_RESOURCE_STATE_PRESENT // state after
1355 );
1356
1357 // Close the list to prepare it for execution:
1358 g_CommandList->Close();
1359
1360 // Execute the command list:
1361 ID3D12CommandList* listsToExecute[] = { g_CommandList.Get() };
1362 g_CommandQueue->ExecuteCommandLists(ARRAYSIZE(listsToExecute), listsToExecute);
1363
1364 // Present the frame:
1365 UINT syncInterval = g_VSync ? 1 : 0;
1366 UINT presentFlags = g_TearingSupported && !g_VSync ? DXGI_PRESENT_ALLOW_TEARING : 0;
1367
1368 DXGI_PRESENT_PARAMETERS pp = {};
1369 g_SwapChain->Present1(syncInterval, presentFlags, &pp);
1370
1371 // Signal fence:
1372 g_FrameFenceValues[g_CurrentBackBufferIndex] = Signal(g_CommandQueue, g_Fence, g_FenceValue);
1373
1374 g_CurrentBackBufferIndex = g_SwapChain->GetCurrentBackBufferIndex();
1375
1376 WaitForFenceValue(g_Fence, g_FrameFenceValues[g_CurrentBackBufferIndex], g_FenceEvent);
1377}
1378
1379void Resize(uint32_t width, uint32_t height)
1380{
1381 if (g_ClientWidth != width || g_ClientHeight != height)
1382 {
1383 // Don't allow 0 size swap chain back buffers
1384 g_ClientWidth = std::max(1u, width);
1385 g_ClientHeight = std::max(1u, height);
1386
1387 // Flush the GPU queue to make sure the swap chain's back buffers are not being referenced by
1388 // an in-flight command list
1389 Flush(g_CommandQueue, g_Fence, g_FenceValue, g_FenceEvent);
1390
1391 for (int i = 0; i < g_NumFrames; ++i)
1392 {
1393 // Any references to the back buffers must be released before the swap chain can be resized.
1394 g_BackBuffers[i].Reset();
1395 g_FrameFenceValues[i] = g_FrameFenceValues[g_CurrentBackBufferIndex];
1396 }
1397
1398 DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
1399 ThrowIfFailed(g_SwapChain->GetDesc(&swapChainDesc));
1400 ThrowIfFailed(g_SwapChain->ResizeBuffers(g_NumFrames, g_ClientWidth, g_ClientHeight,
1401 swapChainDesc.BufferDesc.Format, swapChainDesc.Flags));
1402
1403 g_CurrentBackBufferIndex = g_SwapChain->GetCurrentBackBufferIndex();
1404
1405 UpdaterRenderTargetViews(g_Device, g_SwapChain, g_RTVDescriptorHeap);
1406 }
1407}
1408
1409void SetFullscreen(bool fullscreen)
1410{
1411 if (g_Fullscreen != fullscreen)
1412 {
1413 g_Fullscreen = fullscreen;
1414
1415 if (g_Fullscreen) // Switching to fullscreen:
1416 {
1417 // Store the current window dimensions so that they can be restored
1418 // when switching out of fullscreen state.
1419 ::GetWindowRect(g_Hwnd, &g_WindowRect);
1420
1421 // Set the window style to a borderless window so the client area fills
1422 // the entire screen.
1423 UINT windowStyle = WS_OVERLAPPEDWINDOW & ~(WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
1424
1425 ::SetWindowLongW(g_Hwnd, GWL_STYLE, windowStyle);
1426
1427 // Query the name of the nearest display device for the window.
1428 // This is required to set the fullscreen dimensions of the window
1429 // when using a multi-monitor setup.
1430 HMONITOR hMonitor = ::MonitorFromWindow(g_Hwnd, MONITOR_DEFAULTTONEAREST);
1431 MONITORINFOEX monitorInfo = {};
1432 monitorInfo.cbSize = sizeof(MONITORINFOEX);
1433 ::GetMonitorInfo(hMonitor, &monitorInfo);
1434
1435 ::SetWindowPos(
1436 g_Hwnd, // Handle to the window
1437 HWND_TOP, // Where to place the window, in this case on top of other processes
1438 monitorInfo.rcMonitor.left, // New x position
1439 monitorInfo.rcMonitor.top, // New y position
1440 monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left, // New width
1441 monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top, // New height
1442 SWP_FRAMECHANGED | SWP_NOACTIVATE); // Window flags
1443
1444 ::ShowWindow(g_Hwnd, SW_MAXIMIZE);
1445 }
1446 else
1447 {
1448 // Restore al the window decorators
1449 ::SetWindowLong(g_Hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
1450
1451 ::SetWindowPos(g_Hwnd, HWND_NOTOPMOST,
1452 g_WindowRect.left,
1453 g_WindowRect.top,
1454 g_WindowRect.right - g_WindowRect.left,
1455 g_WindowRect.bottom - g_WindowRect.top,
1456 SWP_FRAMECHANGED | SWP_NOACTIVATE);
1457
1458 ::ShowWindow(g_Hwnd, SW_NORMAL);
1459 }
1460 }
1461}
1462
1463LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
1464{
1465 // This function handles messages to the window.
1466 if (g_IsInitialized) // DX12 has to be initialized first.
1467 {
1468 switch (message)
1469 {
1470 case WM_PAINT: // Repaint a portion of the application's window contents
1471 Update();
1472 Render();
1473 break;
1474 case WM_SYSKEYDOWN:
1475 case WM_KEYDOWN:
1476 {
1477 bool alt = (::GetAsyncKeyState(VK_MENU) & 0x8000) != 0;
1478
1479 switch (wParam)
1480 {
1481 case 'V':
1482 g_VSync = !g_VSync;
1483 break;
1484 case VK_ESCAPE:
1485 ::PostQuitMessage(0);
1486 break;
1487 case VK_RETURN:
1488 if (alt)
1489 {
1490 case VK_F11:
1491 SetFullscreen(!g_Fullscreen);
1492 }
1493 break;
1494 }
1495 }
1496 break;
1497 // The default window procedure will play a system notification sound
1498 // when pressing the Alt+Enter keyboard combination if this message is
1499 // not handled.
1500 case WM_SYSCHAR:
1501 break;
1502 case WM_SIZE:
1503 {
1504 RECT clientRect = {};
1505 ::GetClientRect(g_Hwnd, &clientRect);
1506
1507 int width = clientRect.right - clientRect.left;
1508 int height = clientRect.bottom - clientRect.top;
1509
1510 Resize(width, height);
1511 }
1512 break;
1513 case WM_DESTROY:
1514 ::PostQuitMessage(0);
1515 break;
1516 default:
1517 return ::DefWindowProcW(hwnd, message, wParam, lParam);
1518 }
1519 }
1520 else
1521 {
1522 return ::DefWindowProcW(hwnd, message, wParam, lParam);
1523 }
1524
1525 return 0;
1526}
1527#pragma endregion
1528
1529#pragma region MAIN
1530int CALLBACK wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow)
1531{
1532 // Windows 10 Creators update adds Per Monitor V2 DPI awareness context.
1533 // Using this awareness context allows the client area of the window
1534 // to achieve 100% scaling while still allowing non-client window content to
1535 // be rendered in a DPI sensitive fashion.
1536 SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
1537
1538 // Window class name. Used for registering / creating the window.
1539 const wchar_t* windowClassName = L"DX12WindowClass";
1540
1541 EnableDebugLayer();
1542
1543 g_TearingSupported = CheckTearingSupport();
1544
1545 RegisterWindowClass(hInstance, windowClassName);
1546 g_Hwnd = CreateWindow(windowClassName, hInstance, L"DirectX 12", g_ClientWidth, g_ClientHeight);
1547
1548 // Initialize the global window rect variable.
1549 ::GetWindowRect(g_Hwnd, &g_WindowRect);
1550
1551 ComPtr<IDXGIAdapter4> dxgiAdapter4 = GetAdapter(g_UseWarp);
1552
1553 g_Device = CreateDevice(dxgiAdapter4);
1554
1555 g_CommandQueue = CreateCommandQueue(g_Device, D3D12_COMMAND_LIST_TYPE_DIRECT);
1556
1557 g_SwapChain = CreateSwapChain(g_Hwnd, g_CommandQueue, g_ClientWidth, g_ClientHeight, g_NumFrames);
1558
1559 g_CurrentBackBufferIndex = g_SwapChain->GetCurrentBackBufferIndex();
1560
1561 g_RTVDescriptorHeap = CreateDescriptorHeap(g_Device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, g_NumFrames);
1562 g_RTVDescriptorSize = g_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
1563
1564 UpdaterRenderTargetViews(g_Device, g_SwapChain, g_RTVDescriptorHeap);
1565
1566 for (int i = 0; i < g_NumFrames; ++i)
1567 {
1568 g_CommandAllocators[i] = CreateCommandAllocator(g_Device, D3D12_COMMAND_LIST_TYPE_DIRECT);
1569 }
1570
1571 g_CommandList = CreateCommandList(g_Device, g_CommandAllocators[g_CurrentBackBufferIndex], D3D12_COMMAND_LIST_TYPE_DIRECT);
1572
1573 g_Fence = CreateFence(g_Device);
1574 g_FenceEvent = CreateEventHandle();
1575
1576 CreateViewportAndScissorRect();
1577
1578 CreateRootSignature();
1579
1580 CreateShadersAndPipelineState();
1581
1582 CreateConstantBufferResources();
1583
1584 CreateTriangleData();
1585
1586 CreateTexture();
1587
1588 // Execute the instructions that have been uploaded so far, mainly uploading of the texture to VRAM
1589 g_CommandList->Close();
1590 ID3D12CommandList* commandListsToExecute[] = { g_CommandList.Get() };
1591 g_CommandQueue->ExecuteCommandLists(ARRAYSIZE(commandListsToExecute), commandListsToExecute);
1592
1593 // Wait for GPU:
1594 g_FrameFenceValues[g_CurrentBackBufferIndex] = Signal(g_CommandQueue, g_Fence, g_FenceValue);
1595 g_CurrentBackBufferIndex = g_SwapChain->GetCurrentBackBufferIndex();
1596 WaitForFenceValue(g_Fence, g_FrameFenceValues[g_CurrentBackBufferIndex], g_FenceEvent);
1597
1598 // Removing image that is allocated in CreateTexture() after it has been uploaded to GPU.
1599 delete g_ImageData;
1600
1601 g_IsInitialized = true;
1602
1603 ::ShowWindow(g_Hwnd, SW_SHOW);
1604
1605 MSG msg = {};
1606 while (msg.message != WM_QUIT)
1607 {
1608 if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
1609 {
1610 ::TranslateMessage(&msg);
1611 ::DispatchMessage(&msg);
1612 }
1613 }
1614
1615 // Make sure the command queue has finished all commands before closing:
1616 Flush(g_CommandQueue, g_Fence, g_FenceValue, g_FenceEvent);
1617
1618 ::CloseHandle(g_FenceEvent);
1619
1620 return 0;
1621}
1622#pragma endregion