项目初始化
理论
Command List
命令列表(Command List),抽象为ID3D12CommandList。但该接口只是一个通用的抽象基类,实际包含的方法非常少,在应用时通常会使用更具体的接口。
ID3D12CommandList通常用于统一管理命令队列,由于它是父类,它的数组允许其元素是不同类型的命令队列。
通常,录制命令时使用更具体的CommandList类型指针(即CommandList的子类),提交命令时使用通用的ComandList指针。不需要显示转换,只需要把具体的CommandList指针加入通用的CommandList列表并提交到命令队列即可,编译器会自行处理。
当命令全部加入命令列表后,使用Close()方法关闭命令列表,并使用m_commandQueue.ExcuteCommandLists()提交到命令队列中。
GraphicsCommandList
API文档:ID3D12GraphicsCommandList (d3d12.h) - Win32 apps | Microsoft Learn
继承自CoomandList,封装了一系列图形渲染指令,这些方法都只是将命令加入命令列表,而不是立刻执行。
Command Queue
API文档:ID3D12CommandQueue (d3d12.h) - Win32 apps | Microsoft Learn
命令队列(Command Queue)本质是环形缓冲区,在dx12中CPU通过命令列表将命令提交到这个队列中,而命令不会在提交的同时立刻执行。
初始化
命令队列在DX12中被抽象为ID3D12CommandQueue接口,创建命令队列前需要先创建对应的描述符。
Command Queue 初始化流程
这里使用了IID_PPV_ARGS,这是WindowsSDK中一个宏,作用是将接口类型转换成GUID并传入接口地址。
常用方法
-
ExecuteCommandLists()
用于将命令列表数组里的命令添加到命令队列中,一次性可以添加多个命令列表,GPU会按数组中的索引开始顺序执行。。有如下参数:
- UINT Count :命令列表数组中命令列表的数量
- ID3D12CommandList *const *ppCommandLists:待执行的命令数组,指向命令列表数组的第一个元素。
Command Allocator
API文档:ID3D12CommandAllocator (d3d12.h) - Win32 apps | Microsoft Learn
命令分配器。存储命令的接口,可以与多个命令列表关联,但同一个命令分配器同一时间,只允许有一个命令列表处于录制状态且与该命令分配器关联。
逻辑关系上,可以把Coomand List视为笔,Command Allocatoe视为纸。
命令列表创建和重置时默认处于打开状态,因此不能连续创建命令列表并将其加入Allocator。
创建与销毁CommandAllocator的开销极为昂贵,通用的做法是创建与Swap Chain的缓冲区数量相同数量的Command Allocator,每个缓冲区的录制统一使用一个。在确认GPU执行完命令分配器中的命令后,可以通过Reset()方法重置Allocator。
初始化Command Allocator需要使用ID3D12Device。
初始化Command Allocator
其中:
-
type
指定与分配器关联的命令列表的类型,常用的有两种:
D3D12_COMMAND_LIST_TYPE_DIRECTD3D12_COMMAND_LIST_TYPE_BUNDLE
- rrid:接口的com id。
- ppCommandAllocator:输出指针
flushing the command queue &Fench
为了避免CPU和GPU在并行计算时不同步,导致CPU提前覆写GPU需要使用的数据,DX12使用Fence(围栏)来进行CPU与GPU之间的同步。
其思路是强制CPU进行等待,直到GPU执行完命令达到指定的fence point为止。
初始化fence
每个Fence对象维护一个UINT64类型的值InitialValue,用来标识Fence point。
创建Fence对象时,InitialValue应传入\(0\);每当需要标记一个新的Fence point时,InitialValue的值就加\(1\)。
资源转换 Resource Barrier
为了避免资源转换未完成时GPU就执行相关达到读写操作,DX12使用一系列状态标记资源。
需要注意资源转换和跟踪状态这一行为有一定开销,应该谨慎地使用。
命令与多线程
Command List、Command Allocator都不是线程自由对象,每个线程只能使用各自的对象。
Command Queue是线程自由对象,且多个线程可以同时向Command Queue提交Command List。
创建项目
D3D12项目通常是桌面程序,在新建项目时应该选择:

并选择“桌面应用程序(.exe)”,勾选”空项目“:

需要注意,对于桌面程序,其程序主入口通常是WINAPI WinMain而不是main:
支撑文件
WinInclude.h
设置一个头文件以引用在整个项目中都会引用到的依赖库,并在后续其它文件中都引用该头文件,以避免多次引用同一个库导致重复定义和其它异常。
在这里将其命名为WinInclude.h,放在/Support/下,通常一个D3D12项目的WinInclude.h文件至少需要包含以下内容:
支撑文件
项目属性
打开项目属性,分步进行以下设置:
依赖项
选择配置属性->链接器->输入->附加依赖项,增加d3d12.lib;dxgi.lib;dxguid.lib。

-
d3d12.lib
D3D12的核心静态库,提供与GPU交互的底层接口与函数,用于创建Deivce、Commond Queue、PSO和Assets(Buffer/Textrue)等。
常用于初始化d3d12设备,管理渲染管线、提交绘制命令、同步GPU和CPU等。
-
dxgi.lib
DXGI的静态库,负责图形的硬件抽象层,提供了与GPU、SwapChain、显示模式等的相关功能。
常用于枚举GPU适配器、创建SwapChain、查询显示器支持的显示模式等。
-
dxguid.lib
包含DXGI和Direct3D接口的GUID定义,用于唯一标识COM接口。
包含目录
选择配置属性->VC++目录->包含目录,添加当前项目的根目录,以便在项目中include。

浮点模型和SSE2
选择配置属性->C/C++->代码生成->浮点模型,选择快速(/fp:fast)。
同一页面找到启用增强指令集,选择流式处理SIMD拓展2(/arch:SSE2);这一步仅在x86平台是必须的,x64默认支持SSE2。

D3D初始化
总体流程:
- 创建
ID3D12Device实例 - 创建
ID3D12Fence实例,并查询Descriptor大小 - 检查设备对4X MSAA的支持情况
- 依次构建Command Queue、Command Allocator和Command List。
- 描述并创建Swap Chain。
- 创建Descriptor Heap。
- 调整后台缓冲区大小,创建RTV(渲染目标视图)
- 创建深度/模板缓冲区和关联视图
- 设置视口和裁剪矩形。