• Portfolio
  • Blog
  • About
  • Raw DirectX 12
  • Introduction
  • Initialize the API
  • Raster Graphics Pipeline
  • Compute Pipeline
  • Ray Tracing Pipeline
  • More Resources

Raw DirectX 12 Book

Author Pic:Alain Galvan ·12/28/2021 @ 7:36 PM · Updated 1 year ago

A comprehensive introduction to DirectX 12 Ultimate, learn how to render meshes, ray trace scenes, and learn the latest techniques and best practices from industry and research.

The raster pipeline is the oldest and arguably most often used execution pipeline for GPUs. If you want to render primitives for meshes and UIs quickly and cheaply this is the way to do it. That being said, this may get phased out gradually in the distant future with the meshlet pipeline, though it's worth reviewing the pipeline that has made up the majority of all graphics cards over the past few decades:

Descriptor Heaps

Descriptor Heap Diagram

Descriptor heaps are objects that handle memory allocation required for storing the descriptions of objects that shaders reference.

ID3D12DescriptorHeap* renderTargetViewHeap; D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; rtvHeapDesc.NumDescriptors = backbufferCount; rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; ThrowIfFailed(device->CreateDescriptorHeap( &rtvHeapDesc, IID_PPV_ARGS(&renderTargetViewHeap)));

Root Signature

Root Signature Diagram

Root Signatures are objects that define what type of resources are accessible to your shaders, be it constant buffers, structured buffers, samplers, textures, structured buffers, etc.

// 👋 Declare Handles ID3D12RootSignature* rootSignature; // 🔎 Determine if we can get Root Signature Version 1.1: D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {}; featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1; if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData)))) { featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0; } // 📂 Individual GPU Resources D3D12_DESCRIPTOR_RANGE1 ranges[1]; ranges[0].BaseShaderRegister = 0; ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; ranges[0].NumDescriptors = 1; ranges[0].RegisterSpace = 0; ranges[0].OffsetInDescriptorsFromTableStart = 0; ranges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE; //🗄️ Groups of GPU Resources D3D12_ROOT_PARAMETER1 rootParameters[1]; rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; rootParameters[0].DescriptorTable.NumDescriptorRanges = 1; rootParameters[0].DescriptorTable.pDescriptorRanges = ranges; // 🏢 Overall Layout D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc; rootSignatureDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1; rootSignatureDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; rootSignatureDesc.Desc_1_1.NumParameters = 1; rootSignatureDesc.Desc_1_1.pParameters = rootParameters; rootSignatureDesc.Desc_1_1.NumStaticSamplers = 0; rootSignatureDesc.Desc_1_1.pStaticSamplers = nullptr; ID3DBlob* signature; ID3DBlob* error; try { // 🌱 Create the root signature ThrowIfFailed(D3D12SerializeVersionedRootSignature(&rootSignatureDesc, &signature, &error)); ThrowIfFailed(device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature))); rootSignature->SetName(L"Hello Triangle Root Signature"); } catch (std::exception e) { const char* errStr = (const char*)error->GetBufferPointer(); std::cout << errStr; error->Release(); error = nullptr; } if (signature) { signature->Release(); signature = nullptr; }

While these work well enough, using bindless resources is significantly more easy, Matt Pettineo (@MyNameIsMJP) wrote a blog post and chapter in Ray Tracing Gems 2 about this.

Heaps

Heap Diagram

Heaps are objects that encompass GPU memory. They can be used to upload resources like vertex buffers or textures to GPU exclusive memory.

// 🔼 Upload: // 👋 Declare Handles ID3D12Resource* uploadBuffer; std::vector<unsigned char> sourceData; D3D12_HEAP_PROPERTIES uploadHeapProps = {D3D12_HEAP_TYPE_UPLOAD, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1u, 1u}; D3D12_RESOURCE_DESC uploadBufferDesc = {D3D12_RESOURCE_DIMENSION_BUFFER, 65536ull, 65536ull, 1u, 1, 1, DXGI_FORMAT_UNKNOWN, {1u, 0u}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE}; result = device->CreateCommittedResource( &uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadBufferDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, __uuidof(ID3D12Resource), ((void**)&uploadBuffer)); uint8_t* data = nullptr; D3D12_RANGE range{0, SIZE_T(chunkSize)}; auto hr = spStaging -> Map(0, &range, reinterpret_cast<void**>(&data)); if (FAILED(hr)) { std::cout << "Could not map resource"; } // Copy data if (resourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) { memcpy(data, sourceData.data(), sourceData.size()); } // 📖 Readback: // 👋 Declare Handles ID3D12Resource* readbackBuffer; D3D12_HEAP_PROPERTIES heapPropsRead = {D3D12_HEAP_TYPE_READBACK, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1u, 1u}; D3D12_RESOURCE_DESC resourceDescDimBuffer = { D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT, 2725888ull, 1u, 1, 1, DXGI_FORMAT_UNKNOWN, {1u, 0u}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE}; result = device->CreateCommittedResource( &heapPropsRead, D3D12_HEAP_FLAG_NONE, &resourceDescDimBuffer, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, __uuidof(ID3D12Resource), ((void**)&readbackBuffer));

You can be even more granular with your memory management by creating your own heaps. It can be a handful to manage heaps though, so you could use a memory allocation library instead.

Vertex Buffer

Vertex Buffer Diagram

A Vertex Buffer stores the per vertex information available as attributes in your Vertex Shader. All buffers are ID3D12Resource objects in DirectX 12, be it Vertex Buffers, Index Buffers, Constant Buffers, etc.

// 💾 Declare Data struct Vertex { float position[3]; float color[3]; }; Vertex vertexBufferData[3] = {{{1.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, {{-1.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, {{0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}}; // 👋 Declare Handles ID3D12Resource* vertexBuffer; D3D12_VERTEX_BUFFER_VIEW vertexBufferView; const UINT vertexBufferSize = sizeof(vertexBufferData); D3D12_HEAP_PROPERTIES heapProps; heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heapProps.CreationNodeMask = 1; heapProps.VisibleNodeMask = 1; D3D12_RESOURCE_DESC vertexBufferResourceDesc; vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; vertexBufferResourceDesc.Alignment = 0; vertexBufferResourceDesc.Width = vertexBufferSize; vertexBufferResourceDesc.Height = 1; vertexBufferResourceDesc.DepthOrArraySize = 1; vertexBufferResourceDesc.MipLevels = 1; vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN; vertexBufferResourceDesc.SampleDesc.Count = 1; vertexBufferResourceDesc.SampleDesc.Quality = 0; vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ThrowIfFailed(device->CreateCommittedResource( &heapProps, D3D12_HEAP_FLAG_NONE, &vertexBufferResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&vertexBuffer))); // 📄 Copy the triangle data to the vertex buffer. UINT8* pVertexDataBegin; // 🙈 We do not intend to read from this resource on the CPU. D3D12_RANGE readRange; readRange.Begin = 0; readRange.End = 0; ThrowIfFailed(vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin))); memcpy(pVertexDataBegin, vertexBufferData, sizeof(vertexBufferData)); vertexBuffer->Unmap(0, nullptr); // 👀 Initialize the vertex buffer view. vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress(); vertexBufferView.StrideInBytes = sizeof(Vertex); vertexBufferView.SizeInBytes = vertexBufferSize;

Index Buffer

Index Buffer Diagram

An Index Buffer contains the individual indices of each triangle/line/point that you intend to draw.

// 💾 Declare Data uint32_t indexBufferData[3] = {0, 1, 2}; // 👋 Declare Handles ID3D12Resource* indexBuffer; D3D12_INDEX_BUFFER_VIEW indexBufferView; const UINT indexBufferSize = sizeof(indexBufferData); D3D12_HEAP_PROPERTIES heapProps; heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heapProps.CreationNodeMask = 1; heapProps.VisibleNodeMask = 1; D3D12_RESOURCE_DESC vertexBufferResourceDesc; vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; vertexBufferResourceDesc.Alignment = 0; vertexBufferResourceDesc.Width = indexBufferSize; vertexBufferResourceDesc.Height = 1; vertexBufferResourceDesc.DepthOrArraySize = 1; vertexBufferResourceDesc.MipLevels = 1; vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN; vertexBufferResourceDesc.SampleDesc.Count = 1; vertexBufferResourceDesc.SampleDesc.Quality = 0; vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ThrowIfFailed(device->CreateCommittedResource( &heapProps, D3D12_HEAP_FLAG_NONE, &vertexBufferResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&indexBuffer))); // 📄 Copy data to DirectX 12 driver memory: UINT8* pVertexDataBegin; // 🙈 We do not intend to read from this resource on the CPU. D3D12_RANGE readRange; readRange.Begin = 0; readRange.End = 0; ThrowIfFailed(indexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin))); memcpy(pVertexDataBegin, indexBufferData, sizeof(indexBufferData)); indexBuffer->Unmap(0, nullptr); // 👀 Initialize the index buffer view. indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress(); indexBufferView.Format = DXGI_FORMAT_R32_UINT; indexBufferView.SizeInBytes = indexBufferSize;

Constant Buffer

Constant Buffer Diagram

A Constant Buffer describes data that we'll be sending to shader stages when drawing. Normally you would put Model View Projection Matrices or any specific variable data like colors, sliders, etc. here.

// 💾 Declare Data struct { glm::mat4 projectionMatrix; glm::mat4 modelMatrix; glm::mat4 viewMatrix; } cbVS; // 👋 Declare Handles ID3D12Resource* constantBuffer; ID3D12DescriptorHeap* constantBufferHeap; UINT8* mappedConstantBuffer; // 🧊 Create the Constant Buffer D3D12_HEAP_PROPERTIES heapProps; heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heapProps.CreationNodeMask = 1; heapProps.VisibleNodeMask = 1; D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {}; heapDesc.NumDescriptors = 1; heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; ThrowIfFailed(device->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&constantBufferHeap))); D3D12_RESOURCE_DESC cbResourceDesc; cbResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; cbResourceDesc.Alignment = 0; cbResourceDesc.Width = (sizeof(cbVS) + 255) & ~255; cbResourceDesc.Height = 1; cbResourceDesc.DepthOrArraySize = 1; cbResourceDesc.MipLevels = 1; cbResourceDesc.Format = DXGI_FORMAT_UNKNOWN; cbResourceDesc.SampleDesc.Count = 1; cbResourceDesc.SampleDesc.Quality = 0; cbResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; cbResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ThrowIfFailed(device->CreateCommittedResource( &heapProps, D3D12_HEAP_FLAG_NONE, &cbResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&constantBuffer))); constantBufferHeap->SetName(L"Constant Buffer Upload Resource Heap"); // 👓 Create our Constant Buffer View D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {}; cbvDesc.BufferLocation = constantBuffer->GetGPUVirtualAddress(); cbvDesc.SizeInBytes = (sizeof(cbVS) + 255) & ~255; // CB size is required to be 256-byte aligned. D3D12_CPU_DESCRIPTOR_HANDLE cbvHandle(constantBufferHeap->GetCPUDescriptorHandleForHeapStart()); cbvHandle.ptr = cbvHandle.ptr + device->GetDescriptorHandleIncrementSize( D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) * 0; device->CreateConstantBufferView(&cbvDesc, cbvHandle); // 🙈 We do not intend to read from this resource on the CPU. D3D12_RANGE readRange; readRange.Begin = 0; readRange.End = 0; ThrowIfFailed(constantBuffer->Map( 0, &readRange, reinterpret_cast<void**>(&mappedConstantBuffer))); memcpy(mappedConstantBuffer, &cbVS, sizeof(cbVS)); constantBuffer->Unmap(0, &readRange);

Vertex Shader

Vertex Shader Diagram

Vertex Shaders execute per vertex, and are perfect for transforming a given object, performing per vertex animations with blend shapes, GPU skinning, etc.

cbuffer cb : register(b0) { row_major float4x4 projectionMatrix : packoffset(c0); row_major float4x4 modelMatrix : packoffset(c4); row_major float4x4 viewMatrix : packoffset(c8); }; struct VertexInput { float3 inPos : POSITION; float3 inColor : COLOR; }; struct VertexOutput { float3 color : COLOR; float4 position : SV_Position; }; VertexOutput main(VertexInput vertexInput) { float3 inColor = vertexInput.inColor; float3 inPos = vertexInput.inPos; float3 outColor = inColor; float4 position = mul(float4(inPos, 1.0f), mul(modelMatrix, mul(viewMatrix, projectionMatrix))); VertexOutput output; output.position = position; output.color = outColor; return output; }

You could compile shaders using the legacy DirectX Shader Compiler bundled with the DirectX 11/12 API, but it's best to use the newer official compiler.

dxc.exe -T lib_6_3 -Fo assets/triangle.vert.dxil assets/triangle.vert.hlsl

Then you could load the shader as a binary file:

// Helper to read a binary file: inline std::vector<char> readFile(const std::string& filename) { std::ifstream file(filename, std::ios::ate | std::ios::binary); bool exists = (bool)file; if (!exists || !file.is_open()) { throw std::runtime_error("failed to open file!"); } size_t fileSize = (size_t)file.tellg(); std::vector<char> buffer(fileSize); file.seekg(0); file.read(buffer.data(), fileSize); file.close(); return buffer; }; // 👋 Declare handles D3D12_SHADER_BYTECODE vsBytecode; std::string compiledPath; std::vector<char> vsBytecodeData = readFile(compCompiledPath); vsBytecode.pShaderBytecode = vsBytecodeData.data(); vsBytecode.BytecodeLength = vsBytecodeData.size();

Pixel Shader

Pixel Shader Diagram

Pixel Shaders execute per each pixel of your output, including the other attachments that correspond to that pixel coordinate.

struct PixelInput { float3 color : COLOR; }; struct PixelOutput { float4 attachment0 : SV_Target0; }; PixelOutput main(PixelInput pixelInput) { float3 inColor = pixelInput.color; PixelOutput output; output.attachment0 = float4(inColor, 1.0f); return output; }

Pipeline State

Raster Pipeline Diagram

The Pipeline State describes everything necessary to execute a given raster based draw call.

// 👋 Declare handles ID3D12PipelineState* pipelineState; // ⚗️ Define the Graphics Pipeline D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; // 🔣 Input Assembly D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, {"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}}; psoDesc.InputLayout = {inputElementDescs, _countof(inputElementDescs)}; // 🦄 Resources psoDesc.pRootSignature = rootSignature; // 🔺 Vertex Shader D3D12_SHADER_BYTECODE vsBytecode; vsBytecode.pShaderBytecode = vertexShaderBlob->GetBufferPointer(); vsBytecode.BytecodeLength = vertexShaderBlob->GetBufferSize(); psoDesc.VS = vsBytecode; // 🖌️ Pixel Shader D3D12_SHADER_BYTECODE psBytecode; psBytecode.pShaderBytecode = pixelShaderBlob->GetBufferPointer(); psBytecode.BytecodeLength = pixelShaderBlob->GetBufferSize(); psoDesc.PS = psBytecode; // 🟨 Rasterization D3D12_RASTERIZER_DESC rasterDesc; rasterDesc.FillMode = D3D12_FILL_MODE_SOLID; rasterDesc.CullMode = D3D12_CULL_MODE_NONE; rasterDesc.FrontCounterClockwise = FALSE; rasterDesc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS; rasterDesc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; rasterDesc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; rasterDesc.DepthClipEnable = TRUE; rasterDesc.MultisampleEnable = FALSE; rasterDesc.AntialiasedLineEnable = FALSE; rasterDesc.ForcedSampleCount = 0; rasterDesc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; psoDesc.RasterizerState = rasterDesc; psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; // 🌀 Color/Blend D3D12_BLEND_DESC blendDesc; blendDesc.AlphaToCoverageEnable = FALSE; blendDesc.IndependentBlendEnable = FALSE; const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = { FALSE, FALSE, D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, D3D12_LOGIC_OP_NOOP, D3D12_COLOR_WRITE_ENABLE_ALL, }; for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) blendDesc.RenderTarget[i] = defaultRenderTargetBlendDesc; psoDesc.BlendState = blendDesc; // 🌑 Depth/Stencil State psoDesc.DepthStencilState.DepthEnable = FALSE; psoDesc.DepthStencilState.StencilEnable = FALSE; psoDesc.SampleMask = UINT_MAX; // 🖼️ Output psoDesc.NumRenderTargets = 1; psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; psoDesc.SampleDesc.Count = 1; // 🌟 Create the raster pipeline state try { ThrowIfFailed(device->CreateGraphicsPipelineState( &psoDesc, IID_PPV_ARGS(&pipelineState))); } catch (std::exception e) { std::cout << "Failed to create Graphics Pipeline!"; }

Encoding Commands

Encoding Commands Diagram

In order to execute draw calls, you're going to need a place to write commands. A Command List is an object that can encode a number of commands to be executed by the GPU, be it configuring barriers, setting root signatures, etc.

// 👋 Declare handles ID3D12CommandAllocator* commandAllocator; ID3D12PipelineState* initialPipelineState; ID3D12GraphicsCommandList* commandList; // 📃 Create the command list. ThrowIfFailed(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator, initialPipelineState, IID_PPV_ARGS(&commandList)));

Later, encode those commands and submit them:

// 🚿 Reset the command list and add new commands. ThrowIfFailed(commandAllocator->Reset()); // 🖌️ Begin using the Raster Graphics Pipeline ThrowIfFailed(commandList->Reset(commandAllocator, pipelineState)); // 🔳 Setup Resources commandList->SetGraphicsRootSignature(rootSignature); ID3D12DescriptorHeap* pDescriptorHeaps[] = {constantBufferHeap}; commandList->SetDescriptorHeaps(_countof(pDescriptorHeaps), pDescriptorHeaps); D3D12_GPU_DESCRIPTOR_HANDLE cbvHandle(constantBufferHeap->GetGPUDescriptorHandleForHeapStart()); commandList->SetGraphicsRootDescriptorTable(0, cbvHandle); // 🖼️ Indicate that the back buffer will be used as a render target. D3D12_RESOURCE_BARRIER renderTargetBarrier; renderTargetBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; renderTargetBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; renderTargetBarrier.Transition.pResource = renderTargets[frameIndex]; renderTargetBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; renderTargetBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; renderTargetBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; commandList->ResourceBarrier(1, &renderTargetBarrier); D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeap->GetCPUDescriptorHandleForHeapStart()); rtvHandle.ptr = rtvHandle.ptr + (frameIndex * rtvDescriptorSize); commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); // 🎥 Record raster commands. const float clearColor[] = {0.2f, 0.2f, 0.2f, 1.0f}; commandList->RSSetViewports(1, &viewport); commandList->RSSetScissorRects(1, &surfaceSize); commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); commandList->IASetVertexBuffers(0, 1, &vertexBufferView); commandList->IASetIndexBuffer(&indexBufferView); commandList->DrawIndexedInstanced(3, 1, 0, 0, 0); // 🖼️ Indicate that the back buffer will now be used to present. D3D12_RESOURCE_BARRIER presentBarrier; presentBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; presentBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; presentBarrier.Transition.pResource = renderTargets[frameIndex]; presentBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; presentBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; presentBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; commandList->ResourceBarrier(1, &presentBarrier); ThrowIfFailed(commandList->Close());

Render

Triangle Raster Gif

Rendering in DirectX 12 is a simple matter of changing any constant buffer data you intend to update, submitting your command lists to be executed, presenting the swapchain so your Win32 or UWP window updates, and signaling your application that you've finished rendering.

// 👋 declare handles std::chrono::time_point<std::chrono::steady_clock> tStart, tEnd; float elapsedTime = 0.0f; void render() { // ⌚ Frame limit set to 60 fps tEnd = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration<float, std::milli>(tEnd - tStart).count(); if (time < (1000.0f / 60.0f)) { return; } tStart = std::chrono::high_resolution_clock::now(); // 🎛️ Update Uniforms elapsedTime += 0.001f * time; elapsedTime = fmodf(elapsedTime, 6.283185307179586f); cbVS.modelMatrix = Matrix4::rotationY(elapsedTime); D3D12_RANGE readRange; readRange.Begin = 0; readRange.End = 0; ThrowIfFailed(constantBuffer->Map( 0, &readRange, reinterpret_cast<void**>(&mappedConstantBuffer))); memcpy(mappedConstantBuffer, &cbVS, sizeof(cbVS)); constantBuffer->Unmap(0, &readRange); setupCommands(); ID3D12CommandList* ppCommandLists[] = {commandList}; commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); // 🎥 Present, then wait till finished to continue execution swapchain->Present(1, 0); const UINT64 fence = fenceValue; ThrowIfFailed(commandQueue->Signal(fence, fence)); fenceValue++; if (fence->GetCompletedValue() < fence) { ThrowIfFailed(fence->SetEventOnCompletion(fence, fenceEvent)); WaitForSingleObject(fenceEvent, INFINITE); } frameIndex = swapchain->GetCurrentBackBufferIndex(); }

Destroy Handles

If you're using ComPtr<T> data structures, then just like with shared pointers, you don't need to worry about destroying any handles you create. If you aren't, you can call the Release() function built into every DirectX data structure.

Tags: booksopengltrianglehellotheorydiagramsdirectxvulkan

GitHub Comments