DirectX Core Concepts

Introduction

DirectX 12 provides low‑level access to modern GPUs, offering developers fine‑grained control over rendering and compute workloads. Understanding its core concepts is essential for building high‑performance graphics applications on Windows.

Device (ID3D12Device)

The ID3D12Device object represents a virtual adapter and is the entry point for creating all other DirectX 12 objects.

#include <d3d12.h>
ID3D12Device* device = nullptr;
HRESULT hr = D3D12CreateDevice(
    nullptr,                    // default adapter
    D3D_FEATURE_LEVEL_12_1,    // feature level
    IID_PPV_ARGS(&device));

Key responsibilities:

  • Resource allocation and management.
  • Creation of command queues, descriptor heaps, and pipeline state objects.
  • Querying feature support.

Command Queue (ID3D12CommandQueue)

Command queues submit command lists to the GPU. DirectX 12 defines three types: Direct, Compute, and Copy.

ID3D12CommandQueue* queue = nullptr;
D3D12_COMMAND_QUEUE_DESC desc = {};
desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
device->CreateCommandQueue(&desc, IID_PPV_ARGS(&queue));

Typical usage sequence:

  1. Create a command allocator.
  2. Create a command list.
  3. Record rendering commands.
  4. Close the command list.
  5. Execute the command list on the queue.
  6. Signal a fence for synchronization.

Swap Chain (IDXGISwapChain3)

The swap chain presents rendered images to the screen. It works in concert with a command queue and render target views (RTVs).

IDXGIFactory4* factory = nullptr;
CreateDXGIFactory2(0, IID_PPV_ARGS(&factory));
DXGI_SWAP_CHAIN_DESC1 scDesc = {};
scDesc.Width = 0; // use window width
scDesc.Height = 0;
scDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scDesc.BufferCount = 2;
scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
scDesc.SampleDesc.Count = 1;

IDXGISwapChain1* tempSwapChain = nullptr;
factory->CreateSwapChainForHwnd(
    queue, hwnd, &scDesc, nullptr, nullptr, &tempSwapChain);
tempSwapChain->QueryInterface(IID_PPV_ARGS(&swapChain));

Remember to enable DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING on supported hardware for fullscreen‑free presentations.

Resources

Resources include buffers and textures. They are created via ID3D12Device::CreateCommittedResource or CreatePlacedResource.

D3D12_RESOURCE_DESC texDesc = {};
texDesc.Width = 1024;
texDesc.Height = 1024;
texDesc.DepthOrArraySize = 1;
texDesc.MipLevels = 1;
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texDesc.SampleDesc.Count = 1;
texDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
texDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;

D3D12_HEAP_PROPERTIES heapProps = {};
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;

ID3D12Resource* texture = nullptr;
device->CreateCommittedResource(
    &heapProps,
    D3D12_HEAP_FLAG_NONE,
    &texDesc,
    D3D12_RESOURCE_STATE_COPY_DEST,
    nullptr,
    IID_PPV_ARGS(&texture));

Descriptor heaps (CBV/SRV/UAV, RTV, DSV) are required to bind resources to shaders.

Pipeline State Object (PSO)

A PSO encapsulates the entire fixed‑function and programmable state of the GPU pipeline.

D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShaderBlob);
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShaderBlob);
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
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;

ID3D12PipelineState* pso = nullptr;
device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pso));

All immutable state must be defined at PSO creation; dynamic state is set via command list calls.

Synchronization

Fences (ID3D12Fence) and events synchronize CPU/GPU work.

ID3D12Fence* fence = nullptr;
device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
HANDLE fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
UINT64 fenceValue = 1;
queue->Signal(fence, fenceValue);
if (fence->GetCompletedValue() < fenceValue) {
    fence->SetEventOnCompletion(fenceValue, fenceEvent);
    WaitForSingleObject(fenceEvent, INFINITE);
}
CloseHandle(fenceEvent);

Double‑buffering and triple‑buffering strategies typically combine fences with swap‑chain present calls.

Debug Layer

Enabling the DirectX 12 debug layer helps detect API misuse early.

#if defined(_DEBUG)
    ID3D12Debug* debugController = nullptr;
    if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) {
        debugController->EnableDebugLayer();
    }
#endif

The DXGI and D3D12 debug SDK layers provide detailed messages in Visual Studio's output window.