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.