D3D12

渲染流程

初始化阶段

初始化D3D

首先是初始化DirectX的东西,每个版本基本都有,即 Factory、Adapter、Device

  • Factory 是 DirectX 12 API 的入口,可以查找 Adapter 等。
  • Adapter 提供了有关 DirectX 设备物理属性的信息。你可以查询当前 GPU 的名称、制造商、显存容量以及其它更多信息。
  • Device 则是 DirectX 12 API 的主要入口点,可用来访问 API 的内部。这是访问重要数据结构和函数(如管线、着色器 blob、渲染状态、资源屏障等)的关键。

接着通过 Device 去初始化 SwapChain,以及 CommandQueue、commandAllocator、commandList,还有最后同步要用的 Fence 等。

创建渲染目标视图(RTV)和深度模板视图(DSV)

交换链的后台缓冲区需要绑定为渲染目标,同时创建深度模板缓冲区(可选,但建议创建)。这些都对应成 DescriptorHeap。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 1. 创建RTV堆(存储渲染目标视图)
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = 2; // 双缓冲对应2个RTV
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvHeap));

// 2. 为每个后台缓冲区创建RTV
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeap->GetCPUDescriptorHandleForHeapStart());
UINT rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

for (UINT n = 0; n < 2; n++)
{
    ComPtr<ID3D12Resource> backBuffer;
    swapChain->GetBuffer(n, IID_PPV_ARGS(&backBuffer));
    device->CreateRenderTargetView(backBuffer.Get(), nullptr, rtvHandle);
    rtvHandle.Offset(1, rtvDescriptorSize);
    backBuffers[n] = backBuffer; // 保存后台缓冲区引用
}

// 3. 创建深度模板缓冲区和DSV(可选,三角形渲染可省略,但建议加)
ComPtr<ID3D12Resource> depthStencilBuffer;
D3D12_RESOURCE_DESC depthDesc = CD3DX12_RESOURCE_DESC::Tex2D(
    DXGI_FORMAT_D32_FLOAT, 800, 600, 1, 1, 1, 0,
    D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL
);
D3D12_CLEAR_VALUE depthClearValue = { DXGI_FORMAT_D32_FLOAT, 1.0f, 0 };
device->CreateCommittedResource(
    &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
    D3D12_HEAP_FLAG_NONE,
    &depthDesc,
    D3D12_RESOURCE_STATE_DEPTH_WRITE,
    &depthClearValue,
    IID_PPV_ARGS(&depthStencilBuffer)
);

// 创建DSV堆和视图
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
device->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&dsvHeap));
CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(dsvHeap->GetCPUDescriptorHandleForHeapStart());
device->CreateDepthStencilView(depthStencilBuffer.Get(), nullptr, dsvHandle);

资产准备阶段

定义顶点数据,创建顶点缓冲区(VB),创建顶点BufferView

在DX12中,ID3D12Resource是所有资源类型的代表,无论是Swapchain,还是Buffer,对API来说都只有一个ID3D12Resource。

对于Buffer而言,分为 UploadBuffer(CPU可见)、DefaultBuffer 和负责回读到CPU的ReadbackBuffer。

我们拷贝顶点数据到上传堆,再将上传堆的数据拷贝到默认堆,通过 BufferView 来标记 Buffer,绑定到根签名RootSignature,来让 Shader 知道从哪读数据。

管线准备阶段

编译Shader到中间码

HLSL 编译成 DXIL(DirectX Intermediate Language),这种中间码保存到了Blob中,所谓Blob基本可以认为是一个std::vectorstd::byte一样的容器,原理简单。

创建RootSignature

RootSignature,描述了shader要使用哪些类型的资源,以及这些资源将会被绑定到哪些register space的registers上。比如绑定之前上传堆的 BufferView

https://zhuanlan.zhihu.com/p/388534044

创建PSO

有了中间码后,就需要提交给驱动,让它编译成机器码,这一步就是 CreateGraphicsPipelineState 这个函数。

PSO 是 DX12 的核心,封装顶点着色器、像素着色器、光栅化等所有渲染状态。

录制命令阶段

每次渲染帧都需要录制命令列表,包括绑定资源、设置管线、绘制三角形。

Command Allocator用来给CommandList分配内存,CommandList把提交指令录制下来并一次性提交给CommandQueue,CommandQueue每次接到一个CommandList都会马上开始执行这个CommandList中的命令。

这里 CommandList 就可以指定我们之前创建的 DescriptorHeap、RootSignature 等:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 设置渲染要用的root signature,由步骤3中的代码创建
_commandList->SetGraphicsRootSignature(_rootSignature.Get());

// 设置/绑定索引为1的root paramter描述的资源,即每个pass用到的数据。
// 5. 每个渲染pass使用的constant buffer,被绑定到space0下的b1虚拟寄存器上。
ID3D12Resource * passCB = _curFrameData->PassCB->Resource();
_commandList->SetGraphicsRootConstantBufferView(1, passCB->GetGPUVirtualAddress());

// 绑定root parameter描述的资源,即材质数据数组
// 2. 材质数据Buffer,其占用space1下从register t0 开始的register。
ID3D12Resource * materialBuffer = _curFrameData->MaterialBuffer->Resource();
_commandList->SetGraphicsRootShaderResourceView(2, materialBuffer->GetGPUVirtualAddress());

// 绑定4张纹理
// 1. 声明一个纹理数组,其中有4张纹理。
_commandList->SetGraphicsRootDescriptorTable(3, mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());

D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress() + renderItem->ObjCBIndex * objCBByteSize;
// 设置索引为0的root paramter描述的资源,即每个Object对应的数据
// 4. 每个Object独有的constant buffer,被绑定到space0下的b0虚拟寄存器上
cmdList->SetGraphicsRootConstantBufferView(0, objCBAddress);

CommandList 应该与 PSO 绑定:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState1.Get());
/* …使用m_pipelineState1绘制物体… */

// 更改PSO
m_commandList->SetPipelineState(m_pipelineState2.Get());
/* …使用m_pipelineState2绘制物体… */

// 更改PSO
m_commandList->SetPipelineState(m_pipelineState3.Get());
/* …使用m_pipelineState3绘制物体… */

提交执行与呈现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 1. 提交命令列表到命令队列
ID3D12CommandList* ppCommandLists[] = {commandList};
commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

// 2. 呈现画面(交换前后缓冲区)
swapchain->Present(1, 0);

// 3. 等待GPU完成,避免帧重叠(可选,建议加)
const UINT64 fence = fenceValue;
ThrowIfFailed(commandQueue->Signal(fence, fence));
fenceValue++;

流程图

参考

https://zhuanlan.zhihu.com/p/388534044

https://zhuanlan.zhihu.com/p/457348124

https://zhuanlan.zhihu.com/p/601720224

https://www.zhihu.com/column/c_1268850893845594112

https://zhuanlan.zhihu.com/p/519905706