【知乎】DirectX 12 簡明入門教程
【譯】DirectX 12 簡明入門教程

RYHarrison
編程/攝影/翻譯
原文鏈接:https://alain.xyz/blog/raw-directx12
作者:Alain Galvan
本文將介紹如何編寫一個(gè)簡單的 Hello Triangle DirectX 12 應(yīng)用程序。DirectX 12 是最新的底層圖形 API,用來編寫 Windows 應(yīng)用程序,支持光線追蹤、計(jì)算、光柵化等特性。
Github倉庫
下載范例
書

DirectX 12是微軟專有計(jì)算機(jī)圖形 API 的最新版本,用于 Windows 和 Xbox 平臺(tái)。與 Vulkan、Apple Metal 和 WebGPU 等較新的圖形 API 一樣,DirectX 12旨在讓開發(fā)者可以更接近 GPU 的底層架構(gòu),從而能開發(fā)出不再復(fù)雜、運(yùn)行更快的驅(qū)動(dòng)程序。這意味著應(yīng)用程序開發(fā)者能直接創(chuàng)建管線狀態(tài),指定使用隊(duì)列執(zhí)行哪些命令,并能選擇使用多線程來構(gòu)建這些數(shù)據(jù)結(jié)構(gòu)。

DirectX 專注于實(shí)時(shí)渲染,因此專為游戲開發(fā)人員和計(jì)算機(jī)輔助設(shè)計(jì)(CAD)軟件工程師而設(shè)計(jì)。作為計(jì)算機(jī)圖形 API 的行業(yè)標(biāo)準(zhǔn),幾乎所有兼容的硬件都能對(duì) DirectX 提供強(qiáng)大支持,使它成為商業(yè)項(xiàng)目的標(biāo)準(zhǔn)。
從?Marmoset Toolbag、Adobe PhotoShop、Unreal Engine?等三維或圖像創(chuàng)作軟件,再到像 Activision Blizzard 的?OverWatch?和?Call of Duty、Epic 的?Fortnite?的商業(yè)游戲、以及 Valve 的?Steam?上的絕大多數(shù)游戲等等,DirectX 這個(gè)圖形 API 是最受歡迎并且無處不在,盡管它對(duì)某些平臺(tái)不兼容。
當(dāng)然,這并不是說 DirectX 在 3D 渲染之外的領(lǐng)域沒有用途。Google Chrome?使用 DirectX 渲染 HTML 和 SVG 元素。Tensorflow 采用了?DirectX 12 后端為使用 DirectML 執(zhí)行機(jī)器學(xué)習(xí)提供支持。采用計(jì)算管線(compute pipeline)的 GPGPU 計(jì)算可以執(zhí)行各種工作負(fù)載,包括物理仿真、構(gòu)造 BVH 等等。
DirectX 12 目前支持以下設(shè)備:
Windows 7 - 11(Windows 7 可以通過微軟的?D3D12 On 7?部分支持DirectX 12)
?? Xbox One - Xbox Series X/S
雖然 DirectX 12 的官方語言或許是 C 和 C++,但還有不少語言同樣支持 DirectX 12:
C
C++
C#
Rust(借助?dx-sys)
我已經(jīng)準(zhǔn)備了一個(gè)Github倉庫,其中包含入門所需的一切內(nèi)容。我將用 C++ 介紹一個(gè) Hello Triangle 應(yīng)用程序,這個(gè)程序使用了光柵圖形管線創(chuàng)建三角形并將它渲染到屏幕上。
安裝
首先安裝:
Git
CMake
集成開發(fā)環(huán)境(IDE),比如 Visual Studio、XCode,或者是編譯器,比如 GCC
然后在任何終端(如 VS Code 的集成終端)中,輸入以下內(nèi)容。
# ? Clone 代碼倉git clone https://github.com/alaingalvan/directx12-seed --recurse-submodules# ? 定位至文件夾cd directx12-seed# ? 如果你忘記了 `recurse-submodules` ,你始終可以運(yùn)行:git submodule update --init# ? 創(chuàng)建一個(gè) build 文件夾,并將你的項(xiàng)目文件存放在里面:# ?? 在 Windows 上構(gòu)建 Visual Studio 64位版本的解決方案cmake . -B build -A x64# ? 在 Mac OS 上構(gòu)建 XCode 項(xiàng)目cmake . -B build -G Xcode# ? 在 Linux 上構(gòu)建 .make 文件cmake . -B build# ? 適用在任何平臺(tái)上進(jìn)行構(gòu)建cmake --build build
概述
DirectX 12 的開發(fā)文檔建議使用ComPtr<T>
來作為std::shared_ptr<T>
的替代,這樣做的好處是可以更好地調(diào)試,更容易初始化 DirectX 12 的數(shù)據(jù)結(jié)構(gòu)。
無論你是否選擇使用ComPtr<T>
,使用 DirectX 12 渲染光柵圖形的步驟與其他現(xiàn)代圖形 API?非常相似:
初始化 API:創(chuàng)建
IDXGIFactory
?、IDXGIAdapter
、ID3D12Device
、ID3D12CommandQueue
、ID3D12CommandAllocator
、ID3D12GraphicsCommandList
?。設(shè)置幀后臺(tái)緩沖:為后臺(tái)緩沖區(qū)創(chuàng)建
IDXGISwapChain
、ID3D12DescriptorHeap
,為ID3D12Resource
后臺(tái)緩沖區(qū)渲染目標(biāo)視圖,創(chuàng)建ID3D12Fence
以檢測(cè)幀何時(shí)完成渲染。初始化資源:創(chuàng)建三角形數(shù)據(jù),例如
ID3D12Resource
頂點(diǎn)緩沖區(qū)、ID3D12Resource
索引緩沖區(qū)等,創(chuàng)建ID3D12Fence
以檢測(cè)上傳到 GPU 內(nèi)存的時(shí)間。加載著色器ID3DBlob
、常量緩沖區(qū)ID3D12Resource
及其ID3D12DescriptorHeap
,描述使用ID3D12RootSignature
可以訪問哪些資源,并構(gòu)建ID3D12PipelineState
編碼指令:向
ID3D12GraphicsCommandList
編寫你希望執(zhí)行的管線指令,確保將ResourceBarrier
放在適當(dāng)?shù)奈恢谩?/p>渲染:更新 GPU 常量緩沖區(qū)數(shù)據(jù)(Uniforms),使用
ExecuteCommandLists
將命令提交至ID3D12CommandQueue
,Present
交換鏈,然后等待下一幀。銷毀:使用
Release()
來銷毀正在使用的數(shù)據(jù)結(jié)構(gòu),或者使用ComPtr<T>
對(duì)數(shù)據(jù)結(jié)構(gòu)解除分配。
下面我將對(duì)?Github 倉庫中代碼片段進(jìn)行解釋,省略了某些部分,并且成員變量 (mMemberVariable
) 內(nèi)聯(lián)聲明,不帶前綴m
,以便你可以更容易地分辨它們的類型,這里給出的示例代碼可以獨(dú)立運(yùn)行。
創(chuàng)建窗口(Window Creation)
我們使用?CrossWindow?(譯者注:原文給出的鏈接失效了,可以從這個(gè)?github倉庫?來訪問)來創(chuàng)建支持跨平臺(tái)的窗口。創(chuàng)建 Win32 窗口并對(duì)其進(jìn)行更新,操作非常簡單:
#include "CrossWindow/CrossWindow.h"#include "Renderer.h"#include <iostream>void xmain(int argc, const char** argv){
? ?// ?? 創(chuàng)建窗口 ? ?xwin::WindowDesc wdesc;
? ?wdesc.title = "DirectX 12 Seed";
? ?wdesc.name = "MainWindow";
? ?wdesc.visible = true;
? ?wdesc.width = 640;
? ?wdesc.height = 640;
? ?wdesc.fullscreen = false;
? ?xwin::Window window;
? ?xwin::EventQueue eventQueue;
? ?if (!window.create(wdesc, eventQueue))
? ?{ return; };
? ?// ? 創(chuàng)建一個(gè)渲染器 ? ?Renderer renderer(window);
? ?// ? 引擎循環(huán) ? ?bool isRunning = true;
? ?while (isRunning)
? ? ? ?{
? ? ? ? ? ?bool shouldRender = true;
? ? ? ? ? ?// ?? 更新事件隊(duì)列 ? ? ? ? ? ?eventQueue.update();
? ? ? ? ? ?// ? 事件迭代: ? ? ? ? ? ?while (!eventQueue.empty())
? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ?// 更新事件 ? ? ? ? ? ? ? ? ? ?const xwin::Event& event = eventQueue.front();
? ? ? ? ? ? ? ? ? ?// ? 響應(yīng)窗口改變大小: ? ? ? ? ? ? ? ? ? ?if (event.type == xwin::EventType::Resize)
? ? ? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ? ? ?const xwin::ResizeData data = event.data.resize;
? ? ? ? ? ? ? ? ? ? ? ?renderer.resize(data.width, data.height);
? ? ? ? ? ? ? ? ? ? ? ?shouldRender = false;
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?// ? 響應(yīng)窗口關(guān)閉: ? ? ? ? ? ? ? ? ? ?if (event.type == xwin::EventType::Close)
? ? ? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ? ? ?window.close();
? ? ? ? ? ? ? ? ? ? ? ?shouldRender = false;
? ? ? ? ? ? ? ? ? ? ? ?isRunning = false;
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?eventQueue.pop();
? ? ? ? ? ? ? ?}
? ? ? ? ? ?// ? 更新可視化部分 ? ? ? ? ? ?if (shouldRender)
? ? ? ? ? ?{
? ? ? ? ? ? ? ?renderer.render();
? ? ? ? ? ?}
? ? ? ?}}
如果你想替代 CrossWindow,可以使用其它庫,比如 GLFW,SFML,SDL,QT,或者直接調(diào)用 Win32 或 UWP API 接口。
初始化 API Initialize API)
工廠(Factory)

工廠是 DirectX 12 API 的入口,它允許你查找那些支持 DirectX 12 命令的適配器(譯者注:可以理解為顯卡)。
你還可以在工廠附近創(chuàng)建一個(gè)調(diào)試控制器(Debug Controller),它可以啟用 API 使用情況驗(yàn)證。這只能在 debug 模式中使用。
參考:IDXGIFactory7
// ? 聲明 DirectX 12 句柄(Handle)IDXGIFactory4* factory;ID3D12Debug1* debugController;// ? 創(chuàng)建 FactoryUINT dxgiFactoryFlags = 0;#if defined(_DEBUG)// ? 創(chuàng)建一個(gè) Debug Controller 來追蹤錯(cuò)誤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;#endifHRESULT result = CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory));
適配器(Adapter)

Adapter 提供了有關(guān) DirectX 設(shè)備物理屬性的信息。你可以查詢當(dāng)前 GPU 的名稱、制造商、顯存容量以及其它更多信息。
有兩種類型的適配器:軟件適配器和硬件適配器。微軟 Windows 系統(tǒng)總是會(huì)包含一個(gè)基于軟件層面的DirectX 實(shí)現(xiàn),讓你可以在沒有專用硬件(如獨(dú)立或集成 GPU )的情況下使用 API 。
參考:IDXGIAdapter4
// ? 聲明句柄IDXGIAdapter1* adapter;// ? 創(chuàng)建 Adapterfor (UINT adapterIndex = 0;
? ? DXGI_ERROR_NOT_FOUND != factory->EnumAdapters1(adapterIndex, &adapter);
? ? ++adapterIndex){
? ?DXGI_ADAPTER_DESC1 desc;
? ?adapter->GetDesc1(&desc);
? ?// ? 不要選用 Basic Render Driver adapter. ? ?if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
? ?{
? ? ? ?continue;
? ?}
? ?// ?? 檢查適配器是否支持 Direct3D 12, 如果支持就選擇它來供程序運(yùn)行并返回 ? ?if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?_uuidof(ID3D12Device), nullptr)))
? ?{
? ? ? ?break;
? ?}
? ?// ? 否則的話,不再使用這個(gè)迭代器 adapter,所以要進(jìn)行釋放 ? ?adapter->Release();}
設(shè)備(Device)

設(shè)備是 DirectX 12 API 的主要入口點(diǎn),可用來訪問 API 的內(nèi)部。這是訪問重要數(shù)據(jù)結(jié)構(gòu)和函數(shù)(如管線、著色器 blob、渲染狀態(tài)、資源屏障等)的關(guān)鍵。
參考:IDXGIDevice4
// ? 聲明句柄ID3D12Device* device;// ? 創(chuàng)建 DeviceThrowIfFailed(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?IID_PPV_ARGS(&device)));
調(diào)試 DirectX 創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)可能非常困難。而一個(gè) Debug Device 對(duì)象允許你使用 DirectX 12 的調(diào)試模式。這樣,你將能夠防止數(shù)據(jù)泄露,或者能驗(yàn)證程序是否正確創(chuàng)建或使用 API。
參考:ID3D12DebugDevice
// ? 聲明句柄ID3D12DebugDevice* debugDevice;#if defined(_DEBUG)// ? 獲取 debug deviceThrowIfFailed(device->QueryInterface(&debugDevice));#endif
命令隊(duì)列(Command Queue)

命令隊(duì)列讓你可以一次性提交多組 draw call(稱為命令列表 command lists)來按順序來執(zhí)行命令,從而讓 GPU 保持充分工作并優(yōu)化執(zhí)行速度。
參考:ID3D12CommandQueue
// ? 聲明句柄ID3D12CommandQueue* commandQueue;// ? 創(chuàng)建 Command QueueD3D12_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)

命令分配器讓你可以創(chuàng)建命令列表,你可以在其中定義希望 GPU 執(zhí)行的功能。
參考:ID3D12CommandAllocator
// ? 聲明句柄ID3D12CommandAllocator* commandAllocator;// ? 創(chuàng)建命令分配器ThrowIfFailed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? IID_PPV_ARGS(&commandAllocator)));
同步(Synchronization)

DirectX 12 具有大量同步圖元(synchronization primitives),可以告知驅(qū)動(dòng)程序如何使用資源、GPU 何時(shí)完成任務(wù)等。
圍欄(Fence)能讓程序知道 GPU 在什么時(shí)候執(zhí)行了哪些特定任務(wù),無論是上傳了哪些資源到 GPU 專用內(nèi)存,還是程序什么時(shí)候完成向屏幕的提交(present),都能獲取到這些信息。
參考:ID3D12Fence1
// ? 聲明句柄UINT frameIndex;HANDLE fenceEvent;ID3D12Fence* fence;UINT64 fenceValue;// ? 創(chuàng)建 fenceThrowIfFailed(device->CreateFence(0, D3D12_FENCE_FLAG_NONE,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?IID_PPV_ARGS(&fence)));
屏障(Barrier)能讓驅(qū)動(dòng)程序知道如何在即將提交的命令中使用資源。比如說,程序正在寫入紋理,并且想要將這個(gè)紋理復(fù)制到另一個(gè)紋理(例如交換鏈的渲染附件),這會(huì)很有用。
// ? 聲明句柄ID3D12GraphicsCommandList* commandList;// ? 創(chuàng)建 BarrierD3D12_RESOURCE_BARRIER barrier = {};barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;barrier.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);
交換鏈(Swap Chain)

交換鏈能處理交換和分配后臺(tái)緩沖區(qū),用來顯示正在渲染到給定窗口的內(nèi)容。
// ? 聲明數(shù)據(jù)變量unsigned width = 640;unsigned height = 640;// ? 聲明句柄static const UINT backbufferCount = 2;UINT currentBuffer;ID3D12DescriptorHeap* renderTargetViewHeap;ID3D12Resource* renderTargets[backbufferCount];UINT rtvDescriptorSize;// ?? 聲明交換鏈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){
? ?// 通過交換鏈創(chuàng)建渲染目標(biāo)附件(Render Target Attachments) ? ?swapchain->ResizeBuffers(backbufferCount, width, height,
? ? ? ? ? ? ? ? ? ? ? ? ? ? DXGI_FORMAT_R8G8B8A8_UNORM, 0);}else{
? ?// ?? 創(chuàng)建交換鏈 ? ?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();// 描述并創(chuàng)建渲染目標(biāo)視圖(render target view, RTV) 描述符堆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);// ?? 創(chuàng)建幀資源D3D12_CPU_DESCRIPTOR_HANDLE
? ?rtvHandle(renderTargetViewHeap->GetCPUDescriptorHandleForHeapStart());// 為每幀創(chuàng)建RTVfor (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);}
初始化資源(Initialize Resources)
描述符堆(Descriptor Heaps)

描述符堆是用來處理內(nèi)存分配的,這些內(nèi)存存儲(chǔ)著色器引用的對(duì)象描述。
參考:ID3D12DescriptorHeap
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)

根簽名定義了著色器可以訪問資源類型的對(duì)象,包括常量緩沖區(qū)、結(jié)構(gòu)化緩沖區(qū)、采樣器、紋理、結(jié)構(gòu)化緩沖區(qū),等等。
參考:ID3D12RootSignature
// ? 聲明句柄ID3D12RootSignature* rootSignature;// ? 判斷是否能得到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;}// ? 私有的GPU資源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;// ? GPU資源組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;// ? ?所有布局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{
? ?// ? 創(chuàng)建根簽名 ? ?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;}
雖然程序運(yùn)行起來沒有問題,但如果使用無綁定資源(bindless resources),那么開發(fā)起來要容易得多,Matt Pettineo(@MyNameIsMJP)在《Ray Tracing Gems II》中寫過關(guān)于這方面的內(nèi)容。
堆(Heaps)

堆是 GPU 顯存中的對(duì)象。你可以使用堆將頂點(diǎn)緩沖區(qū)或紋理等資源上傳到 GPU 顯存中。
參考:ID3D12Resource
// ? 上傳:// ? 聲明句柄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";}// 拷貝數(shù)據(jù)if (resourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER){
? ?memcpy(data, sourceData.data(), sourceData.size());}// ? 回讀:// ? 聲明句柄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));
通過創(chuàng)建自己的堆,你可以更加精細(xì)地管理內(nèi)存。不過,可管理的堆可能很少,因此你可以改用內(nèi)存分配庫。
頂點(diǎn)緩沖區(qū)(Vertex Buffer)

頂點(diǎn)緩沖區(qū)將每個(gè)頂點(diǎn)信息作為屬性存儲(chǔ)在頂點(diǎn)著色器中。所有緩沖區(qū)都是 DirectX 12 中的ID3D12Resource
對(duì)象,無論是頂點(diǎn)緩沖區(qū)、索引緩沖區(qū)、常量緩沖區(qū)等。
參考:D3D12_VERTEX_BUFFER_VIEW
// ? 聲明數(shù)據(jù)結(jié)構(gòu)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}}};// ? 聲明句柄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)));// ? 向頂點(diǎn)緩沖區(qū)拷貝三角形數(shù)據(jù)UINT8* pVertexDataBegin;// ? 不會(huì)在 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);// ? 初始化頂點(diǎn)緩沖視圖.vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();vertexBufferView.StrideInBytes = sizeof(Vertex);vertexBufferView.SizeInBytes = vertexBufferSize;
索引緩沖區(qū)(Index Buffer)

索引緩沖區(qū)包含要繪制的每個(gè)三角形、線、點(diǎn)的各個(gè)索引。
參考:D3D12_INDEX_BUFFER_VIEW
// ? 聲明數(shù)組uint32_t indexBufferData[3] = {0, 1, 2};// ? 聲明句柄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)));// ? 向 DirectX 12 顯存拷貝數(shù)據(jù):UINT8* pVertexDataBegin;// ? 不會(huì)在 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);// ? 初始化索引緩沖區(qū)視圖indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress();indexBufferView.Format = DXGI_FORMAT_R32_UINT;indexBufferView.SizeInBytes = indexBufferSize;
常量緩沖區(qū)(Constant Buffer)

常量緩沖區(qū)描述了在程序繪制時(shí)將發(fā)送到著色器階段的數(shù)據(jù)。通常,你在這里存放著模型視圖投影矩陣或任何特定的變量數(shù)據(jù),如顏色。
// ? 聲明數(shù)據(jù)結(jié)構(gòu)struct{
? ?glm::mat4 projectionMatrix;
? ?glm::mat4 modelMatrix;
? ?glm::mat4 viewMatrix;} cbVS;// ? 聲明句柄ID3D12Resource* constantBuffer;ID3D12DescriptorHeap* constantBufferHeap;UINT8* mappedConstantBuffer;// ? 創(chuàng)建常量緩沖區(qū)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");// ? 創(chuàng)建常量緩沖區(qū)視圖(CBV)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);// ? 不會(huì)在 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);
頂點(diǎn)著色器(Vertex Shader)

頂點(diǎn)著色器是逐頂點(diǎn)執(zhí)行的,它非常適合對(duì)給定對(duì)象進(jìn)行轉(zhuǎn)換處理,比如根據(jù)混合形狀執(zhí)行每個(gè)頂點(diǎn)動(dòng)畫、GPU 蒙皮,等等。
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;}
你可以使用傳統(tǒng)的 DirectX 著色器編譯器(已包含在 DirectX 11/12 API 中)來編譯 shader ,但最好使用較新的官方編譯器。
dxc.exe -T lib_6_3 -Fo assets/triangle.vert.dxil assets/triangle.vert.hlsl
然后,你就能以為二進(jìn)制文件形式加載著色器:
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;};// ? 聲明句柄D3D12_SHADER_BYTECODE vsBytecode;std::string compiledPath;std::vector<char> vsBytecodeData = readFile(compCompiledPath);vsBytecode.pShaderBytecode = vsBytecodeData.data();vsBytecode.BytecodeLength = vsBytecodeData.size();
像素著色器(Pixel Shader)

像素著色器是按輸出的每個(gè)像素來執(zhí)行的,包括與這個(gè)像素坐標(biāo)對(duì)應(yīng)的其他附件。
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;}
管線狀態(tài)(Pipeline State)

管線狀態(tài)描述了執(zhí)行給定的光柵繪制指令所用到的全部內(nèi)容。
參考:ID3D12GraphicsCommandList5
// ? 聲明句柄ID3D12PipelineState* pipelineState;// ?? 定義圖形管線D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};// ? 輸入裝配布局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)};// ? 資源psoDesc.pRootSignature = rootSignature;// ? 頂點(diǎn)著色器D3D12_SHADER_BYTECODE vsBytecode;vsBytecode.pShaderBytecode = vertexShaderBlob->GetBufferPointer();vsBytecode.BytecodeLength = vertexShaderBlob->GetBufferSize();psoDesc.VS = vsBytecode;// ?? 像素著色器D3D12_SHADER_BYTECODE psBytecode;psBytecode.pShaderBytecode = pixelShaderBlob->GetBufferPointer();psBytecode.BytecodeLength = pixelShaderBlob->GetBufferSize();psoDesc.PS = psBytecode;// ? 光柵化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;// ? 顏色/混合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;// ? 深度/緩沖狀態(tài)psoDesc.DepthStencilState.DepthEnable = FALSE;psoDesc.DepthStencilState.StencilEnable = FALSE;psoDesc.SampleMask = UINT_MAX;// ?? 輸出psoDesc.NumRenderTargets = 1;psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;psoDesc.SampleDesc.Count = 1;// ? 創(chuàng)建光柵管線狀態(tài)try{
? ?ThrowIfFailed(device->CreateGraphicsPipelineState(
? ? ? ?&psoDesc, IID_PPV_ARGS(&pipelineState)));}catch (std::exception e){
? ?std::cout << "Failed to create Graphics Pipeline!";}
編碼指令(Encoding Commands)

為了執(zhí)行 draw call,你需要一個(gè)編寫命令的地方。命令列表(Command List)可以對(duì) GPU 要執(zhí)行的許多命令進(jìn)行編碼,包括設(shè)置屏障(barrier)、設(shè)置根簽名等等。
參考:ID3D12GraphicsCommandList5
// ? 聲明句柄ID3D12CommandAllocator* commandAllocator;ID3D12PipelineState* initialPipelineState;ID3D12GraphicsCommandList* commandList;// ? 創(chuàng)建命令列表ThrowIfFailed(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?commandAllocator, initialPipelineState,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?IID_PPV_ARGS(&commandList)));
然后,對(duì)這些命令進(jìn)行編碼并提交:
// ? 重置命令列表并添加新的命令ThrowIfFailed(commandAllocator->Reset());// ?? 開始調(diào)用光柵圖形管線ThrowIfFailed(commandList->Reset(commandAllocator, pipelineState));// ? 設(shè)置資源commandList->SetGraphicsRootSignature(rootSignature);ID3D12DescriptorHeap* pDescriptorHeaps[] = {constantBufferHeap};commandList->SetDescriptorHeaps(_countof(pDescriptorHeaps), pDescriptorHeaps);D3D12_GPU_DESCRIPTOR_HANDLE
? ?cbvHandle(constantBufferHeap->GetGPUDescriptorHandleForHeapStart());commandList->SetGraphicsRootDescriptorTable(0, cbvHandle);// ?? 指派back buffer,用作render targetD3D12_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);// ? 記錄光柵命令.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);// ?? 指派back buffer 隨即提交(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)
在 DirectX 12 中渲染非常簡單:你需要在刷新時(shí)改動(dòng)常量緩沖區(qū)數(shù)據(jù)、提交要執(zhí)行的命令列表、提交交換鏈來更新 Win32 或 UWP 窗口,同時(shí)向程序發(fā)出已完成提交的信號(hào)。
// ? 聲明句柄std::chrono::time_point<std::chrono::steady_clock> tStart, tEnd;float elapsedTime = 0.0f;void render(){
? ?// ? 將幀率鎖定至60fps ? ?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();
? ?// ?? 更新 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);
? ?// ? 提交,然后等待GPU執(zhí)行 ? ?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)
如果你使用的是ComPtr<T>
數(shù)據(jù)結(jié)構(gòu),那么就像使用共享指針一樣,你無需擔(dān)心什么時(shí)候銷毀已創(chuàng)建的句柄。如果沒使用ComPtr<T>
,那你可以調(diào)用內(nèi)置在每個(gè) DirectX 數(shù)據(jù)結(jié)構(gòu)中的Release()
函數(shù)進(jìn)行銷毀。
結(jié)論
DirectX 12 是一個(gè)功能豐富、性能強(qiáng)大的計(jì)算機(jī)圖形 API,非常適合商業(yè)項(xiàng)目。它在設(shè)計(jì)上遵循現(xiàn)代圖形 API,同時(shí)它也是硬件驅(qū)動(dòng)程序工程師和商業(yè)項(xiàng)目工程師主要在維護(hù)的 API。這篇文章回顧了基于光柵的繪圖,但沒有涉及到更多方面,而它們同樣值得探討,例如:
DirectML:硬件加速機(jī)器學(xué)習(xí)模型。
DirectX Raytracing:硬件加速光線追蹤和場(chǎng)景遍歷。
計(jì)算著色器(Compute Shader):基于 GPGPU 執(zhí)行任意任務(wù),例如圖像處理、物理等。
網(wǎng)格著色器(Mesh Shader):一種替代傳統(tǒng)基于光柵渲染技術(shù)構(gòu)建基元的方法。
可變速率著色(Variable Rate Shading,VRS):對(duì)給定命令集的著色速率進(jìn)行更精細(xì)的控制。
我還寫了更多關(guān)于 DirectX 的文章:
Raw DirectX 12 book?介紹了計(jì)算管線、光線追蹤管線等。
Raw DirectX 11 blog post?回顧了早期的 DirectX 11,并且可以把這篇文章作為學(xué)習(xí) DirectX 很不錯(cuò)的入門材料,因?yàn)檫@里面的許多概念都沿用到了 DirectX 12。
更多資源
你一定不要錯(cuò)過下面這些文章、工具和項(xiàng)目:
文章
在微軟 DirectX 12文檔中有一個(gè)頁面,包含了初始化 DirectX 的所用到的全部數(shù)據(jù)結(jié)構(gòu);
微軟在這個(gè)鏈接里有關(guān)于 D3D12 和 11 的規(guī)范文檔;
Jendrik Illner(@jendrikillner)寫了一篇?DirectX 12 學(xué)習(xí)計(jì)劃;
Braynzarsoft,一個(gè) DirectX 教程社區(qū);
Riko Ophorst?的一篇?DirectX 12 光線追蹤論文;
Riccardo Loggini 的 D3D12 博客文章;
英特爾關(guān)于 D3D12 博客文章;
NVIDIA 關(guān)于 D3D12 和相關(guān)主題的博客文章;
Diligent Graphics (@diligentengine) 寫了篇關(guān)于 DirectX 12 的文章;
Jeremiah van Oosten的?DirectX 12 系列教程;
Alex Tardif?(@longbool)的?A Gentle Introduction to D3D12;
范例
微軟官方的?DirectX 12 范例代碼倉庫;
英特爾官方的?GitHub Organization Game Tech Dev;
NVIDIA 的官方?GitHub Organization GameWorks;
Matth?us G. Chajdas(@NIV_Anteru)發(fā)布的?HelloD3D12,它是 AMD 的 GPUOpen 庫和 SDK 的一部分;
工具
由 Adam Sawicki(@Reg__)開發(fā)的?D3D12 Memory Allocator?,他同樣還是 Vulkan Memory Allocator 的開發(fā)者;
Tim Jones(@tim_jones) 發(fā)布了一個(gè)VS Code 插件?HLSL Tools,可讓你更輕松地對(duì)編寫 shader;
你可以在這個(gè)Github倉庫中找到本篇文章提到的所有源代碼。
發(fā)布于 2023-01-29 19:41?IP 屬地上海?directx 12是什么意思?directx12有什么功能和效果?
來源: 酷狗科技網(wǎng)
時(shí)間:2022-08-09 15:57:54
directx 12是什么意思?
DirectX全稱為Direct eXtension,是由微軟公司創(chuàng)建的多媒體編程接口。
DirectX由C++編程語言實(shí)現(xiàn),遵循COM。被廣泛使用于Microsoft Windows、Microsoft Xbox和Microsoft Xbox 360電子游戲開發(fā),并且只能支持這些平臺(tái),DirectX 12是目前DirectX最新的版本,大家熟悉的XP系統(tǒng)內(nèi)置的版本為DirectX 9.0,Win7/8系統(tǒng)則內(nèi)置版本為DirectX 11,而Win10正式版中,將內(nèi)置最新的DirectX12,而且DX12只會(huì)支持Windows10,
微軟在2014年的GDC上正式發(fā)布。全球首款支持DirectX 12的游戲是奇點(diǎn)灰燼。
directx12有什么功能和效果?
像之前的DirectX規(guī)范一樣,DX12實(shí)際上也可以分為多個(gè)功能不同的功能層(Feature Level),不過DX12這次還多了一個(gè)底層優(yōu)化,所以DX12規(guī)范可以視作三個(gè)不同層級(jí):
·D3D 12 Low Level API:這部分實(shí)際上是見諸報(bào)道最多的一部分,DX12相比DX11性能大提升就是底層優(yōu)化的功勞,這也是DX12最吸引人的一點(diǎn),不論是對(duì)游戲開發(fā)者還是對(duì)游戲玩家來說都是如此。底層優(yōu)化部分包括Low Overhead(低開銷)、更多的控制及異步計(jì)算(Async Compute)等多個(gè)部分,低開銷類似AMD提出的Mantle優(yōu)化,后者也是大幅改善了游戲的多線程效率,降低了驅(qū)動(dòng)層開銷,現(xiàn)在這部分已經(jīng)可以使用3DMark的Driver Overhead做測(cè)試了。
·DX12 Feature Level 12_0:前面的底層優(yōu)化部分實(shí)際上是幫助DX12打通了“經(jīng)脈”,提高了開發(fā)者的潛力,但那些并不涉及具體的招式——Feature Level 12_0這部分開始涉及更新的3D渲染方法,包括平鋪資源(Tiled Resoure)、歸類UAV訪問、無綁定(Bindless)等等,其中多項(xiàng)功能實(shí)際上DX11.1中就有了,不過DX11中多是T1級(jí)別的,現(xiàn)在的則是T2級(jí)別的。
·DX12高級(jí)功能Feature Level 12_1:跟以往的DX11.1/11.2一樣,DX12還有比Feature Level 12_0更高級(jí)的Feature Level 12_1功能,包括立體平鋪資源(Volume Tiled Resources)、保守光柵(Conservative Rasterization)、光柵順序視圖(Raster Order Views)等,這些功能通常屬于可選支持,但它們可以更好地提升開發(fā)者的效率或者游戲畫質(zhì),同時(shí)對(duì)顯卡的要求也更高。
以上三部分是DX12規(guī)范的主要內(nèi)容,但這些還不是DX12的全部功能,還記得之前曝光過的DX12黑科技——A、N顯卡混合交火嗎?微軟確實(shí)在DX12中嘗試了不同顯卡的混搭技術(shù),該技術(shù)名為Muti-Adapter(多顯卡適配器),它就可以把不同架構(gòu)的GPU聯(lián)合起來渲染。
directx12官方版擁有更加強(qiáng)效的驅(qū)動(dòng)效率,可以允許游戲開發(fā)者對(duì)特定的硬件進(jìn)行優(yōu)化,能夠?yàn)?Windows 系統(tǒng)提供高性能的硬件加速多煤體支持,讓游戲最大程度的與電腦硬件相配合。