Write a Direct3D12 application (1)
First, to build a Direct3D12 app, you need to include the Direct3D12 header files and link the library files. In the past, you could download something called the DirectX SDK and write the include and link settings in VisualStudio, Now it seems that the DirectX SDK is gone and is included in the Windows SDK.
So where is the Windows SDK? You can run an app called Visual Studio Installer, select it from the VisualStudio components, and install it.
There are Windows 10 SDK and Windows 11 SDK, multiple versions, and you may not know which one to choose. If you are just building your own application, you only need to install the latest version. If you are building someone else’s program, it is safer to include the older version.
Now that you can build it, I wrote a program that draws only one triangle; I wrote it while listening to ChatGPT, so it may contain some useless code.
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <d3dcompiler.h>
#include <DirectXColors.h>
#include <wrl.h>
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")
using namespace Microsoft::WRL;
using namespace DirectX;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_DESTROY) {
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
struct Vertex {
XMFLOAT3 position;
};
const char* vertexShaderSource = R"(
struct VSInput {
float3 position : POSITION;
};
struct PSInput {
float4 position : SV_POSITION;
};
PSInput VSMain(VSInput input) {
PSInput output;
output.position = float4(input.position, 1.0f);
return output;
}
)";
const char* pixelShaderSource = R"(
float4 PSMain() : SV_TARGET {
return float4(1.0f, 0.0f, 0.0f, 1.0f);
}
)";
// entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// Window Class Registration
WNDCLASSEX wcex = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, hInstance, nullptr, nullptr, nullptr, nullptr, "D3D12SampleWindowClass", nullptr };
RegisterClassEx(&wcex);
HWND hWnd = CreateWindow(wcex.lpszClassName, "Direct3D 12 Sample", WS_OVERLAPPEDWINDOW, 100, 100, 800, 600, nullptr, nullptr, wcex.hInstance, nullptr);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// Direct3D 12 Create device and command queue
ComPtr<ID3D12Device> device;
ComPtr<IDXGISwapChain3> swapChain;
ComPtr<ID3D12CommandQueue> commandQueue;
ComPtr<ID3D12DescriptorHeap> rtvHeap;
ComPtr<ID3D12Resource> renderTargets[2];
ComPtr<ID3D12CommandAllocator> commandAllocator;
ComPtr<ID3D12GraphicsCommandList> commandList;
// Swap chain-related settings
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = 2;
swapChainDesc.Width = 800;
swapChainDesc.Height = 600;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
// DXGICreating Factories
ComPtr<IDXGIFactory4> factory;
CreateDXGIFactory1(IID_PPV_ARGS(&factory));
// Device Creation
D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device));
// Command Queue Creation
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue));
// Creating a swap chain
ComPtr<IDXGISwapChain1> tempSwapChain;
factory->CreateSwapChainForHwnd(commandQueue.Get(), hWnd, &swapChainDesc, nullptr, nullptr, &tempSwapChain);
tempSwapChain.As(&swapChain);
// Create RTV (render target view) for drawing
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = 2;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvHeap));
// RTVGet a handle on
UINT rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = rtvHeap->GetCPUDescriptorHandleForHeapStart();
// Get render target from swap chain
for (UINT i = 0; i < 2; ++i) {
swapChain->GetBuffer(i, IID_PPV_ARGS(&renderTargets[i]));
device->CreateRenderTargetView(renderTargets[i].Get(), nullptr, rtvHandle);
rtvHandle.ptr += rtvDescriptorSize;
}
// Creating Command Allocators and Command Lists
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator));
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), nullptr, IID_PPV_ARGS(&commandList));
// Vertex buffer data
Vertex triangleVertices[] = {
{ { 0.0f, 0.5f, 0.0f } },
{ { 0.5f, -0.5f, 0.0f } },
{ { -0.5f, -0.5f, 0.0f } }
};
// Vertex buffer creation
D3D12_HEAP_PROPERTIES heapProps = {};
heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
D3D12_RESOURCE_DESC bufferDesc = {};
bufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
bufferDesc.Width = sizeof(triangleVertices);
bufferDesc.Height = 1;
bufferDesc.DepthOrArraySize = 1;
bufferDesc.MipLevels = 1;
bufferDesc.Format = DXGI_FORMAT_UNKNOWN;
bufferDesc.SampleDesc.Count = 1;
bufferDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
ComPtr<ID3D12Resource> vertexBuffer;
device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&vertexBuffer));
// Copy data to vertex buffer
void* pVertexDataBegin;
D3D12_RANGE readRange = { 0, 0 };
vertexBuffer->Map(0, &readRange, &pVertexDataBegin);
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
vertexBuffer->Unmap(0, nullptr);
// Creating a vertex buffer view
D3D12_VERTEX_BUFFER_VIEW vertexBufferView = {};
vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexBufferView.SizeInBytes = sizeof(triangleVertices);
vertexBufferView.StrideInBytes = sizeof(Vertex);
// shader
ComPtr<ID3DBlob> vertexShader;
ComPtr<ID3DBlob> pixelShader;
ComPtr<ID3DBlob> errorBlob;
D3DCompile(vertexShaderSource, strlen(vertexShaderSource), nullptr, nullptr, nullptr, "VSMain", "vs_5_0", 0, 0, &vertexShader, nullptr);
D3DCompile(pixelShaderSource, strlen(pixelShaderSource), nullptr, nullptr, nullptr, "PSMain", "ps_5_0", 0, 0, &pixelShader, nullptr);
// input element
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
// Root Signature
D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error);
ComPtr<ID3D12RootSignature> rootSignature;
device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature));
// pipeline state object
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) }; // Input Layout
psoDesc.pRootSignature = rootSignature.Get(); // Root Signature
psoDesc.VS = { vertexShader->GetBufferPointer(), vertexShader->GetBufferSize() }; // Vertex shaders
psoDesc.PS = { pixelShader->GetBufferPointer(), pixelShader->GetBufferSize() }; // Pixel shaders
// Default rasterizer settings
D3D12_RASTERIZER_DESC rasterizerDesc = {};
rasterizerDesc.FillMode = D3D12_FILL_MODE_SOLID;
rasterizerDesc.CullMode = D3D12_CULL_MODE_BACK;
rasterizerDesc.FrontCounterClockwise = FALSE;
rasterizerDesc.DepthClipEnable = TRUE;
psoDesc.RasterizerState = rasterizerDesc;
// Default blend settings
D3D12_BLEND_DESC blendDesc = {};
blendDesc.RenderTarget[0].BlendEnable = FALSE;
blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
psoDesc.BlendState = blendDesc;
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
ComPtr<ID3D12PipelineState> pipelineState;
device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState));
// Viewport and scissor rectangle settings
D3D12_VIEWPORT viewport = {};
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = 800;
viewport.Height = 600;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
D3D12_RECT scissorRect = {};
scissorRect.left = 0;
scissorRect.top = 0;
scissorRect.right = 800;
scissorRect.bottom = 600;
// main loop
MSG msg = {};
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
// Recording and executing command lists
commandAllocator->Reset();
commandList->Reset(commandAllocator.Get(), pipelineState.Get());
// Barrier Transition
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = renderTargets[swapChain->GetCurrentBackBufferIndex()].Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
commandList->ResourceBarrier(1, &barrier);
rtvHandle = rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += swapChain->GetCurrentBackBufferIndex() * rtvDescriptorSize;
// RenderTargetSet and clear the
commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
commandList->ClearRenderTargetView(rtvHandle, DirectX::Colors::Black, 0, nullptr);
// Pipeline
commandList->SetPipelineState(pipelineState.Get());
commandList->SetGraphicsRootSignature(rootSignature.Get());
commandList->RSSetViewports(1, &viewport);
commandList->RSSetScissorRects(1, &scissorRect);
// Execution of drawing commands
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
commandList->IASetVertexBuffers(0, 1, &vertexBufferView);
commandList->DrawInstanced(3, 1, 0, 0);
// Barrier transition to present
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
commandList->ResourceBarrier(1, &barrier);
commandList->Close();
ID3D12CommandList* commandLists[] = { commandList.Get() };
commandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
// Swap Chain Display
swapChain->Present(1, 0);
}
}
UnregisterClass(wcex.lpszClassName, hInstance);
return 0;
}
I am sorry. I could not write short. It is long.
It might be shorter if I included d3dx12.h and used the utility, but d3dx12.h is not included in the Windows SDK, so I did not use it.
d3dx12.h is a utility library that makes it easy to configure d3d12, but Microsoft’s handling of it is not very clear, and I don’t know where to install it from, However, Microsoft’s handling of it is subtle, and it does not clearly state where to install it from. Moreover, the name of the folder where it is installed is different, and frankly speaking, it is difficult to handle. The library was originally designed for sample programs, but it is still used because d3d12.h alone is difficult to use. It is still used because it is difficult to use.
Incidentally, when I installed the package “Microsoft.Direct3D.D3D12” from “NuGet Package Management”, it installed Direct3D12 which also includes d3dx12.h. I don’t understand: even if I could install Direct3D12 by itself, it would conflict with the Windows SDK files. I wish nuget would allow me to install only d3dx12.
This is a bit long-winded, so I’ll stop here and explain the contents of this program in the next installment.