• 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.

In order to use DirectX 12 you will need to create certain data structures that correspond to parts of the GPU, let's review each:

Factory

Factory Diagram

Factories are the entry point to the DirectX 12 API. They can be used to find adapters which can then create devices and other important data structures.

You can also create a Debug Controller alongside the factory which can enable API usage validation, though this should only be used in `debug builds.

  • Reference: IDXGIFactory7

// 👋 Declare DirectX 12 Handles IDXGIFactory4* factory; ID3D12Debug1* debugController; // 🏭 Create Factory UINT dxgiFactoryFlags = 0; #if defined(_DEBUG) // 🐛 Create a Debug Controller to track errors ID3D12Debug* dc; ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&dc))); ThrowIfFailed(dc->QueryInterface(IID_PPV_ARGS(&debugController))); debugController->EnableDebugLayer(); debugController->SetEnableGPUBasedValidation(true); dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG; dc->Release(); dc = nullptr; #endif HRESULT result = CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory));

Adapter

Adapter Diagram

An Adapter provides information on the physical properties of a given DirectX device. You can query your current GPU's name, manufacturer, how much memory it has, and much more.

There's 2 categories for adapters, software and hardware adapters. Microsoft Windows always includes a software based DirectX implementation that can be used in the event of there being no dedicated hardware such as a discrete or integrated GPU.

  • Reference: IDXGIAdapter4

// 👋 Declare Handles IDXGIAdapter1* adapter; // 🔌 Create Adapter for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != factory->EnumAdapters1(adapterIndex, &adapter); ++adapterIndex) { DXGI_ADAPTER_DESC1 desc; adapter->GetDesc1(&desc); // ❌ Don't select the Basic Render Driver adapter. if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { continue; } // ✔️ Check if the adapter supports Direct3D 12, and use that for the rest // of the application if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0, _uuidof(ID3D12Device), nullptr))) { break; } // ❌ Else we won't use this iteration's adapter, so release it adapter->Release(); }

Device

Device Diagram

A Device allows you to create key data structures such as command queues, allocators, resources like pipelines, buffers, buffer views, shader blobs, heaps, and synchronization primitives.

  • Reference: IDXGIDevice4

// 👋 Declare Handles ID3D12Device* device; // 💻 Create Device ID3D12Device* pDev = nullptr; ThrowIfFailed(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device)));

A Debug Device allows you to rely on DirectX 12's debug mode. It can be difficult to keep track of data structures created with DirectX. With this you'll be able to prevent data leaks or verify if you're using the API correctly.

  • Reference: ID3D12DebugDevice

// 👋 Declare Handles ID3D12DebugDevice* debugDevice; #if defined(_DEBUG) // 💻 Get debug device ThrowIfFailed(device->QueryInterface(&debugDevice)); #endif

Command Queue

Command Queue Diagram

A Command Queue allows you to submit groups of commands, known as command lists, together to execute in order, thus allowing a GPU to stay busy and optimize its execution speed. These commands can be to specify a buffer needs a barrier, to wait for an upload to the GPU to finish, to execute the raster or compute pipeline, and much more.

  • Reference: ID3D12CommandQueue

// 👋 Declare Handles ID3D12CommandQueue* commandQueue; // 📦 Create Command Queue D3D12_COMMAND_QUEUE_DESC queueDesc = {}; queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; ThrowIfFailed(device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue)));

Command Allocator

Command Allocator Diagram

A Command Allocator allows you to create command lists where you can define the functions you want the GPU to execute for that allocator.

  • Reference: ID3D12CommandAllocator

// 👋 Declare Handles ID3D12CommandAllocator* commandAllocator; // 🎅 Create Command Allocator ThrowIfFailed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator)));

Synchronization

Semaphore and Fence Diagram

DirectX 12 features a number of synchronization primitives that can help the driver know how resources will be used in the future, know when tasks have been completed by the GPU, etc.

A Fence lets your program know when certain tasks have been executed by the GPU, be it uploads to GPU exclusive memory, or when you've finished presenting to the screen.

  • Reference: ID3D12Fence1

// 👋 Declare handles UINT frameIndex; HANDLE fenceEvent; ID3D12Fence* fence; UINT64 fenceValue; // 🚧 Create fence ThrowIfFailed(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));

A Barrier lets the driver know how a resource should be used in upcoming commands. This can be useful if say, you're writing to a texture, and you want to copy that texture to another texture (such as the swapchain's render attachment).

// 👋 Declare handles ID3D12GraphicsCommandList* commandList; // 🔮 Create Barrier D3D12_RESOURCE_BARRIER barrier = {}; result.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; result.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; barrier.Transition.pResource = texResource; barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; commandList->ResourceBarrier(1, &barrier);

Swapchain

Swapchain Diagram

Swapchains handle swapping and allocating back buffers to display what you're rendering to a given window.

  • Reference: IDXGISwapChain4

// 💾 Declare Data unsigned width = 640; unsigned height = 640; // 👋 Declare Handles static const UINT backbufferCount = 2; UINT currentBuffer; ID3D12DescriptorHeap* renderTargetViewHeap; ID3D12Resource* renderTargets[backbufferCount]; UINT rtvDescriptorSize; // ⛓️ Swapchain IDXGISwapChain3* swapchain; D3D12_VIEWPORT viewport; D3D12_RECT surfaceSize; surfaceSize.left = 0; surfaceSize.top = 0; surfaceSize.right = static_cast<LONG>(width); surfaceSize.bottom = static_cast<LONG>(height); viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; viewport.Width = static_cast<float>(width); viewport.Height = static_cast<float>(height); viewport.MinDepth = .1f; viewport.MaxDepth = 1000.f; if (swapchain != nullptr) { // Create Render Target Attachments from swapchain swapchain->ResizeBuffers(backbufferCount, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0); } else { // ⛓️ Create swapchain DXGI_SWAP_CHAIN_DESC1 swapchainDesc = {}; swapchainDesc.BufferCount = backbufferCount; swapchainDesc.Width = width; swapchainDesc.Height = height; swapchainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swapchainDesc.SampleDesc.Count = 1; IDXGISwapChain1* newSwapchain = xgfx::createSwapchain(window, factory, commandQueue, &swapchainDesc); HRESULT swapchainSupport = swapchain->QueryInterface( __uuidof(IDXGISwapChain3), (void**)&newSwapchain); if (SUCCEEDED(swapchainSupport)) { swapchain = (IDXGISwapChain3*)newSwapchain; } } frameIndex = swapchain->GetCurrentBackBufferIndex(); // Describe and create a render target view (RTV) descriptor heap. 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))); rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); // 🎞️ Create frame resources D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle(renderTargetViewHeap->GetCPUDescriptorHandleForHeapStart()); // Create a RTV for each frame. for (UINT n = 0; n < backbufferCount; n++) { ThrowIfFailed(swapchain->GetBuffer(n, IID_PPV_ARGS(&renderTargets[n]))); device->CreateRenderTargetView(renderTargets[n], nullptr, rtvHandle); rtvHandle.ptr += (1 * rtvDescriptorSize); }

Tags: booksopengltrianglehellotheorydiagramsdirectxvulkan

GitHub Comments