DirectX 12 is Microsoft's latest graphics API library, which is only supported on Win10 system (if it is not Win10, it must be upgraded to Win10). My biggest feeling about this API is that it is particularly complex, because it exposes many underlying methods. Even though I have learned DX9, I still feel confused when facing DX12, not only because there are too many methods exposed in DX12, but also because of the way it encapsulates them. Well, that's all. I'll introduce the DX12 drawing process with my understanding and give a set of executable code.
The drawing process of DX12 is as follows:
First of all, painting requires a painting environment. Drawing environment is an abstraction of hardware environment in the code layer. All drawing is done in this drawing environment. It can be said that the drawing environment is our "hardware". The abstraction of DX12 for drawing environment is ID3D12Device.
Then, draw the natural needs of resources. Resources here include buffers for staging rendering results, including render buffers and depth buffers.
Finally, drawing requires drawing instructions. GPU must know how we want to draw.
The idea is so simple, but how to realize the drawing idea of DX12 is the focus we care about!
Drawing environment
The COM technology used by DX12, what is COM technology, is simply understood as a subclass inherited from a unified common base class. The greatest advantage of this technology is that the allocation and release of resources can be done without too much concern. Detailed content readers can baidu.
When creating a Device, we need to do two things. One is to directly create the Device of the default display adapter. The other is to create a Device of the software adapter if the Device of the default display adapter cannot be created. The code implementation is as follows:
/* * DirectX 12 */ ComPtr<IDXGIFactory4> g_pDXGIFactory = nullptr; // dxgi factory ComPtr<ID3D12Device> g_pDevice = nullptr; // Pointer to Device D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS g_msQualityLevels; // Multisampling support void CreateDevice() { // Create a hardware based Device HRESULT hardwareResult = D3D12CreateDevice( nullptr, // Which display adapter? nullptr represents the default display adapter D3D_FEATURE_LEVEL_11_0, // Feature set you want to support IID_PPV_ARGS(&g_pDevice)); // Output comptr < id3d12device > object // If you can't create a hardware based Device, create a // Device of WAP (Windows advanced rasterization platform) software adapter // DXGI is a basic library provided by Microsoft. It provides the functions related to graphic API but not suitable for drawing API // For example, enumerate the display adapter, display, supported display resolution refresh rate and so on in the system. // An important interface we use here is IDXGIFactory4. Because of the long-standing relationship, Microsoft has to use this interface // Magic number. This interface can enumerate display adapters. Similarly, it is its function to create a switch chain. ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&g_pDXGIFactory))); if (FAILED(hardwareResult)) { ComPtr<IDXGIAdapter> pWarpAdapter; // Abstraction of display adapter ThrowIfFailed(g_pDXGIFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter))); // Enumerate display adapters // Create an adapter related Device ThrowIfFailed(D3D12CreateDevice( pWarpAdapter.Get(), // Get the original pointer of COM D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&g_pDevice))); // Get the address of the COM pointer } // Check quality level of MSAA supporting 4X // All Direct3D11 compatible devices support 4X MSAA, so we just need to check the level of support g_msQualityLevels.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // Buffer format g_msQualityLevels.SampleCount = 4; // Sampling number g_msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE; // g_msQualityLevels.NumQualityLevels = 0; ThrowIfFailed(g_pDevice->CheckFeatureSupport( D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &g_msQualityLevels, sizeof (g_msQualityLevels))); }
In order to enumerate all the adapters owned by the machine, we need to use the Microsoft DXGI (DirectX Graphics Infrastructure) library, which contains the functions needed for graphics programming but not suitable for inclusion in DirextX, such as enumerate display adapters, displays, resolution supported by the machine, refresh rate, etc. The variable type of gdxgifactory is comptr < idxgifactory4 > and the creation function name is CreateDXGIFactory1, which is embarrassing. In the process of continuous development, even Microsoft has to use this magic number to distinguish versions.
CheckFeatureSupport is used to check the multisampling level supported by the device. The maximum number of multisampling is 32, which is defined by the d3d11 "Max" multisample "sample" count macro. It is usually set to 4 or 8 for performance reasons. The reason to check this is that you also need this data when creating render and depth buffers.
Create command queues and command lists
Unlike the previous DX, DX12 exposes a lot of the rendering process. Command Queue and Command List are among them. The main purpose of the Command Queue is to make full use of the computing power of CPU and GPU, so that they can be in the computing at the same time, reducing the waiting time. The Command Queue is mainly responsible for receiving the command (list) generated by CPU, and then notifying GPU to execute. The Command List is mainly used to let the CPU generate commands and save them in. When it is saved to a certain extent, it can be put into the Command Queue for GPU to execute (not necessarily after all commands are generated).
ComPtr<ID3D12CommandQueue> g_pCommandQueue; // Pointer to the command queue ComPtr<ID3D12CommandAllocator> g_pCommandAllocator; // Pointer to the command allocator (buffer used to allocate commands) ComPtr<ID3D12GraphicsCommandList> g_pCommandList; // Save the list of commands that will be sent to GPU for execution void CreateCommandObjects() { // To create a command queue, a description structure is needed to define the command queue D3D12_COMMAND_QUEUE_DESC queueDesc = {}; queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; ThrowIfFailed(g_pDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&g_pCommandQueue))); // The creation of command list requires a front Allocator, which is mainly for resource reuse // Multiple command lists can be associated with the same allocator, but only one command list can operate on one allocator at the same time ThrowIfFailed(g_pDevice->CreateCommandAllocator( D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(g_pCommandAllocator.GetAddressOf()))); ThrowIfFailed(g_pDevice->CreateCommandList( 0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_pCommandAllocator.Get(), // Relevant distributor nullptr, // Initial pipeline state object IID_PPV_ARGS(g_pCommandList.GetAddressOf()))); // Close command list // When the command list is created or reset, the status of the command list is Open, but the status of the Open command list is Open // Can't be executed, so it's a good habit to set the command list to Close after the operation g_pCommandList->Close(); }
Create exchange chain
The exchange chain is a kind of resource, but it is quite special. It is the storage place for the data currently being displayed and the data to be displayed in the next frame. In modern rendering, there are usually two buffers in a switching chain, i.e. the front buffer and the back buffer. The data in the front buffer is the data currently displayed (can be seen), and the data in the back buffer is the data to be displayed in the next frame. When it is necessary to display the data in the back buffer, it is OK to set the back buffer to the "front" buffer, which is very special Smart setting.
The difficulty in creating an exchange chain is to describe what kind of exchange chain to create. Many members of this description structure are very complicated. Please refer to the official [description document]( DXGI_SWAP_CHAIN_DESC ) The process of creating the exchange chain is very simple, with only three steps:
1. Release the existing exchange chain.
2. Fill the description structure to indicate what kind of exchange chain to create.
3. Call the CreateSwapChain function of Factory to create the exchange chain.
See the following code for details:
ComPtr<IDXGISwapChain> g_pSwapChain = nullptr; // Pointer to the exchange chain interface void CreateSwapChain() { // Release the exchange chain, we need to regenerate. g_pSwapChain.Reset(); // The exchange chain describes the structure used to define what kind of exchange chain to create. DXGI_SWAP_CHAIN_DESC sd; sd.BufferDesc.Width = g_ScreenWidth; sd.BufferDesc.Height = g_ScreenHeight; sd.BufferDesc.RefreshRate.Numerator = 60; // Refresh rate, molecular sd.BufferDesc.RefreshRate.Denominator = 1; // Refresh rate, denominator sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // DXGI_FORMAT enumeration, used to define the display format, here represents a 32-bit unsigned normalized integer format, each channel has 8 bits, including transparent channels sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; // The DXGI? Mode? Scanline? Order enumeration is used to define the scanline drawing format. In this case, the scanline drawing format is not specified. sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // DXGI? Mode? Scaling enumeration to indicate how the image is stretched to fit the display resolution. It is also not specified here. sd.SampleDesc.Count = 1; // Multiple samples can only be 1, otherwise they cannot match DXGI? Swap? Effect? Flip? Discard sd.SampleDesc.Quality = g_msQualityLevels.NumQualityLevels - 1; // Quality level of sampling sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // What does cache do sd.BufferCount = g_FrameCount; // Cache quantity sd.OutputWindow = g_hWnd; // associated window sd.Windowed = true; // Windowing or not sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // Indicates that it is a flip display model. After IDXGISwapChain1::Present1 is called, the cache data is discarded sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // The behavior flag of the exchange chain. Allows the application to toggle the window mode, and the display mode (display resolution) matches the size of the window. // Exchange chain needs command queue to refresh ThrowIfFailed(g_pDXGIFactory->CreateSwapChain( g_pCommandQueue.Get(), &sd, g_pSwapChain.GetAddressOf())); }
Create resource descriptor
In DX 12, Microsoft abstracts back buffer, depth / template buffer into resources. When we submit a drawing command, we need to bind the resource to the rendering pipeline so that the GPU can get the resource. However, GPU resources are not bound directly, but through a way of reference. This resource descriptor is the tool used to reference resources. We can think of it as a lightweight structure, describing what resources are used for GPU.
Why is there such an extra middle layer? The official explanation is that GPU resources are originally memory blocks, which can be accessed at different stages of the rendering pipeline in this original way. From the perspective of GPU, resources are data in a block of memory, and only descriptors know what this memory is for, whether it is rendering target or deep cache.
For historical reasons, descriptor s and views are usually equivalent, so you often see some functions or variables named view.
ComPtr<ID3D12Fence> g_pFence; // fence pointer uint32_t g_rtvDescriptorSize = 0; uint32_t g_dsvDescriptorSize = 0; void CreateDescriptorHeaps() { // The function of fence is to control the synchronization between CPU and GPU. // When the GPU executes the fence command, the CPU can know to prevent the CPU from operating the resources GPU is using. ThrowIfFailed(g_pDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_pFence))); // RTV for Render Target View // This shows the equivalence of view and descriptor, that is, they are the same thing. // Gets the size of the handle increment for the given descriptor heap type. This value is used to offset the resource handle. g_rtvDescriptorSize = g_pDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); // Descriptor heap, which can be simply understood as an array of descriptors. // To create a descriptor heap, you need to first explain what the heap is for. We are here to use it as a Render Target View D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc; rtvHeapDesc.NumDescriptors = g_FrameCount; // Number of descriptors in the heap rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; // Type of descriptor heap (RTV) rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; // Flag for descriptor heap (NONE indicates default usage) rtvHeapDesc.NodeMask = 0; // Single adapter set to 0 // Create descriptor heap ThrowIfFailed(g_pDevice->CreateDescriptorHeap( &rtvHeapDesc, IID_PPV_ARGS(g_pRtvHeap.GetAddressOf()))); // DSV for depth stencil view D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc; dsvHeapDesc.NumDescriptors = 1; dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; // Type of descriptor heap dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; // Descriptor heap flag (or default) dsvHeapDesc.NodeMask = 0; // Create descriptor heap ThrowIfFailed(g_pDevice->CreateDescriptorHeap( &dsvHeapDesc, IID_PPV_ARGS(g_pDsvHeap.GetAddressOf()))); }
Create buffer
Resource descriptors only describe what resources a block of memory is, but we still need to allocate this memory. This is the real resource. Buffer is a kind of resource. We put the creation buffer in a separate function.
uint32_t g_currentBackBuffer = 0; ComPtr<ID3D12Resource> g_SwapChainBuffer[g_FrameCount]; // Render buffer, there are 2 render buffers, one before and one after ComPtr<ID3D12Resource> g_DepthStencilBuffer; // Depth / template buffer void CreateBuffers() { // Release the buffer resources and we will recreate them for (int i = 0; i < g_FrameCount; ++i) g_SwapChainBuffer[i].Reset(); g_DepthStencilBuffer.Reset(); // Reallocate buffer size based on window size ThrowIfFailed(g_pSwapChain->ResizeBuffers( g_FrameCount, // Number of buffers g_ScreenWidth, g_ScreenHeight, // Width and height of window DXGI_FORMAT_R8G8B8A8_UNORM, // Buffer format DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH)); // Behavior specification of the exchange chain. Allows apps to switch modes through ResizeTarget. When switching from window to full screen // The display mode (display resolution) matches the size of the window. g_currentBackBuffer = 0; // Current back buffer remade to 0 /** Create render target view */ // In order to bind the post buffer to the output merge phase of the pipeline, we need to create a render target view for the post buffer. // Gets the descriptor handle at the start of the heap to use the descriptor heap. CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(g_pRtvHeap->GetCPUDescriptorHandleForHeapStart()); for (int i = 0; i < g_FrameCount; ++i) { // To get buffer resources and save them to global variables, you need to get them from the exchange chain. ThrowIfFailed(g_pSwapChain->GetBuffer(i, IID_PPV_ARGS(&g_SwapChainBuffer[i]))); // Create a Render Target View for the buffer, which is a resource that needs to be associated with a descriptor g_pDevice->CreateRenderTargetView(g_SwapChainBuffer[i].Get(), nullptr, rtvHeapHandle); // Next resource descriptor entry on heap rtvHeapHandle.Offset(1, g_rtvDescriptorSize); } /** Create depth / template buffers and views */ // Description of the depth / template buffer, defining the resource format created. D3D12_RESOURCE_DESC depthStencilDesc; depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; // Buffer dimension (1D texture or 2D texture?) depthStencilDesc.Alignment = 0; // alignment depthStencilDesc.Width = g_ScreenWidth; // Buffer width depthStencilDesc.Height = g_ScreenHeight; // Buffer height depthStencilDesc.DepthOrArraySize = 1; // The depth of the texture, in pixels; or the size of the texture array depthStencilDesc.MipLevels = 1; // Multi level texture series depthStencilDesc.Format = DXGI_FORMAT_R24G8_TYPELESS; // Grain format depthStencilDesc.SampleDesc.Count = 1; // Multiple samples depthStencilDesc.SampleDesc.Quality = 0; // Multisampling sampling quality depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; // Texture layout, do not care for the moment. depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; // Other resource flags. Allow depth / template views to be created as assets, // And the resource is allowed to convert to d3d12 ﹣ resource ﹣ state ﹣ depth ﹣ write or d3d12 ﹣ resource ﹣ state ﹣ depth ﹣ read state // Default when clearing cache D3D12_CLEAR_VALUE optClear; optClear.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; // Cache data format optClear.DepthStencil.Depth = 1.0f; // Depth value optClear.DepthStencil.Stencil = 0; // Template value // Create and submit a resource to a specific heap. This resource property is specified by us. ThrowIfFailed(g_pDevice->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), // Properties of a specific heap. This means that the heap is accessed by the GPU. D3D12_HEAP_FLAG_NONE, // Heap option, indicating whether the heap contains textures and whether resources are shared by multiple adapters. NONE means no additional functionality is required. &depthStencilDesc, // Resource descriptor. Generate this resource. D3D12_RESOURCE_STATE_COMMON, // The initial state of the resource. How do you want to use this resource? // The official explanation for this enumeration is that resources must be transitioned to this state if they are to be accessed across graph engine types. But there is no detailed explanation. // It is also pointed out that the texture must be in the COMMON state for the CPU to access. Does this conflict with the first parameter? &optClear, // Scavenging value IID_PPV_ARGS(g_DepthStencilBuffer.GetAddressOf()))); // COM address of the resource // Create a depth / template view, using a format that matches the previously created asset D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc; dsvDesc.Flags = D3D12_DSV_FLAG_NONE; // Options for depth / template view, which is represented by the default view dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; // How to get resources? Get as 2D texture dsvDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; // Resource data formats, including full type and untype formats. 24 bit depth 8-bit template. dsvDesc.Texture2D.MipSlice = 0; // Layer 1 of multi-level texture // Create depth / template view, associate resources and descriptors g_pDevice->CreateDepthStencilView(g_DepthStencilBuffer.Get(), &dsvDesc, g_pDsvHeap->GetCPUDescriptorHandleForHeapStart()); // Depth / template buffer resource state converted to depth write g_pCommandList->ResourceBarrier( 1, &CD3DX12_RESOURCE_BARRIER::Transition( g_DepthStencilBuffer.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE)); }
Let the CPU wait until the GPU is drawn. This is a very inefficient operation, but it is necessary for synchronization.
uint32_t g_CurrentFence = 0; // Current fence value void FlushCommandQueue() { // Increase the value of fence to ensure that it is executed. g_CurrentFence++; // Add an instruction to the command queue to set a new fence value. // GPU can not reach fence until all instructions are processed. ThrowIfFailed(g_pCommandQueue->Signal(g_pFence.Get(), g_CurrentFence)); // Until the GPU reaches the fence point. if (g_pFence->GetCompletedValue() < g_CurrentFence) { // Create an event HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS); // Set the event to fire when the fence object reaches the fence point ThrowIfFailed(g_pFence->SetEventOnCompletion(g_CurrentFence, eventHandle)); // Wait for the event to be fired WaitForSingleObject(eventHandle, INFINITE); CloseHandle(eventHandle); } }
Reassign size
When the window size changes, we have to reallocate the buffer size. The original resources must be released, and then resources can be created innovatively. Is there any way to reuse the previously written code? Of course, the previous function of creating buffer can be reused, so the code is much less. At the same time, we need to save the location of the viewport and crop region.
void OnResize() { // Buffer size can only be reallocated after Device, switch chain and command allocator are all created if (g_pDevice == nullptr || g_pSwapChain == nullptr || g_pCommandAllocator == nullptr) { return; } // Make sure that all resources are no longer in use. FlushCommandQueue(); ThrowIfFailed(g_pCommandList->Reset(g_pCommandAllocator.Get(), nullptr)); // Create buffer CreateBuffers(); // Execute recreate command ThrowIfFailed(g_pCommandList->Close()); ID3D12CommandList* cmdLists[] = {g_pCommandList.Get()}; g_pCommandQueue->ExecuteCommandLists(_countof(cmdLists), cmdLists); // Make sure that the instructions created are complete FlushCommandQueue(); // Update viewport changes g_ScreenViewport.TopLeftX = 0; g_ScreenViewport.TopLeftY = 0; g_ScreenViewport.Width = static_cast<float>(g_ScreenWidth); g_ScreenViewport.Height = static_cast<float>(g_ScreenHeight); g_ScreenViewport.MinDepth = 0.0f; g_ScreenViewport.MaxDepth = 1.0f; g_ScissorRect = {0, 0, static_cast<LONG>(g_ScreenWidth), static_cast<LONG>(g_ScreenHeight)}; }
Draw
Here, we simply fill the whole window with one color. As an entry-level project, this effect is more than enough.
void Draw() { // Reuse memory blocks that record commands. // The Reset function of Allocator can only be called after the GPU has executed the draw command. ThrowIfFailed(g_pCommandAllocator->Reset()); // The command list can be reset after submission, without waiting for the command to finish executing. // Reuse the memory block of the command list. ThrowIfFailed(g_pCommandList->Reset(g_pCommandAllocator.Get(), nullptr)); // Switch the resource state of the back buffer to Render Target. // The ResourceBarrier function creates a notification driven command to synchronize resource access, which is simply to switch the state of the resource. g_pCommandList->ResourceBarrier(1, // D3d12 ﹣ resource ﹣ barrier is a resource fence (temporarily Translated) used to represent the operation on resources. // Cd3dx12 ﹣ resource ﹣ barrier class is an auxiliary class of d3d12 ﹣ resource ﹣ barrier structure, providing more convenient // Use the interface. The function of Transition is the same as its function name, which creates the operation of resource state Transition and its return value // It is cd3dx12 "resource" barrier. &CD3DX12_RESOURCE_BARRIER::Transition(g_SwapChainBuffer[g_currentBackBuffer].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET)); // Set up the viewport and crop region. The viewport and crop region are reset as the command list resets. g_pCommandList->RSSetViewports(1, &g_ScreenViewport); g_pCommandList->RSSetScissorRects(1, &g_ScissorRect); // Clear the post cache and deep cache. g_pCommandList->ClearRenderTargetView( // Similarly, the cd3dx12 CPU controller handle class is also a helper class, which supports the d3d12 CPU controller handle structure // To access resources, we must pass d3d12 CPU descriptor handle, which is the handle of resources. // Here we pass in the id of the current post buffer and the size of the rendering view descriptor to get the handle of the corresponding resource. CD3DX12_CPU_DESCRIPTOR_HANDLE( g_pRtvHeap->GetCPUDescriptorHandleForHeapStart(), g_currentBackBuffer, g_rtvDescriptorSize), Colors::LightSteelBlue, // Steel blue 0, // Number of empty areas nullptr); // Empty area rectangle array g_pCommandList->ClearDepthStencilView( g_pDsvHeap->GetCPUDescriptorHandleForHeapStart(), // Handle to depth / template descriptor D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, // Clear the flags, we need to clear the depth and template cache 1.0f, // Depth value 0, // Template value 0, // Again, the number of empty areas nullptr); // Empty area rectangle array // Set CPU resource descriptor handle, including render target and depth / template cache g_pCommandList->OMSetRenderTargets(1, // Descriptor number &CD3DX12_CPU_DESCRIPTOR_HANDLE( g_pRtvHeap->GetCPUDescriptorHandleForHeapStart(), g_currentBackBuffer, g_rtvDescriptorSize), // Got handle to rendering object descriptor true, &g_pDsvHeap->GetCPUDescriptorHandleForHeapStart()); // Handle to template descriptor // Switch the back buffer to the PRESENT state. g_pCommandList->ResourceBarrier( 1, &CD3DX12_RESOURCE_BARRIER::Transition( g_SwapChainBuffer[g_currentBackBuffer].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT)); // After the command is saved, close the command list. ThrowIfFailed(g_pCommandList->Close()); // Add a list of commands to the queue and execute. ID3D12CommandList* cmdsLists[] = { g_pCommandList.Get()}; g_pCommandQueue->ExecuteCommandLists(_countof (cmdsLists), cmdsLists); // Buffer before and after swap ThrowIfFailed(g_pSwapChain->Present(0, 0)); g_currentBackBuffer = (g_currentBackBuffer + 1) % g_FrameCount; // Force refresh to have GPU execute command. This is not funny, but for a simple example // That's enough. FlushCommandQueue(); }
The main functions have been completed, and we need to assemble the code. Here I do not provide the code for creating Windows on Windows, because it has nothing to do with DX12. The relevant window creation code can be directly Baidu. There are many examples, so don't worry. To put it bluntly, the process of assembling code is equivalent to a process of sorting out the drawing process of DX12:
void InitDirect3D12() { CreateDevice(); CreateCommandObjects(); CreateSwapChain(); CreateDescriptorHeaps(); OnResize(); }
Yes, that's the process. The beginning is Device, the "hardware" of our code. Then there are command objects, including command list and command queue, which are created here because command queue is used when creating exchange chain. The next step is to create the swap chain, followed by the descriptor heap, including rendering and depth, although the depth is only one element. Finally, the size is reallocated, because there is the process of creating buffer, which needs to match the resource descriptor when creating buffer. As for where the drawing is called, it is naturally in the message loop:
int Run() { MSG msg = {}; while(msg.message != WM_QUIT) { if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { Draw(); // Draw interface when not handling messages } } return msg.wParam; }
Please refer to:
reference material:
Introduction to 3D Game Programming With DirectX 12
Zhihu article: Chen Wenli: knock the next generation game engine from scratch (15)
Supporting code of Longshu: d3dcoder/d3d12book