渲染流程
初始化阶段
初始化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