从 Direct3D 11 移植到 Direct3D 12
本部分提供有关从自定义的 Direct3D 11 图形引擎移植到 Direct3D 12 的一些指导。
设备创建
Direct3D 11 和 Direct3D 12 共享类似的设备创建模式。 现有的 Direct3D 12 驱动程序都是 D3D_FEATURE_LEVEL_11_0 或更高版本,因此可以忽略较旧的功能级别和相关限制。
另请记住,使用 Direct3D 12 时,应使用 DXGI 接口显式枚举设备信息。 在 Direct3D 11 中,可以从 Direct3D 设备链接到 DXGI 设备,Direct3D 12 不支持此操作。
通过提供从 IDXGIFactory4::EnumWarpAdapter 获取的显式适配器,在 Direct3D 12 上创建 WARP 软件设备。 Direct3D 12 的 WARP 设备仅在启用了图形工具可选功能的系统上可用。
注意
没有等效于 D3D11CreateDeviceAndSwapChain。 即使使用 Direct3D 11,我们也不鼓励使用此函数,因为最好在不同的步骤中创建设备和交换链。
提交的资源
使用 Direct3D 11 中以下接口创建的对象将转换为 Direct3D 12 中称作“提交的资源”的对象。 提交的资源是指具有关联的虚拟地址空间和物理页面的资源。 这是 Direct3D 12 所基于的 Microsoft Windows 设备驱动程序 2 (WDD2) 内存模型中的一个概念。
Direct3D 11 资源:
- ID3D11Resource
- ID3D11Buffer 和 ID3D11Device::CreateBuffer
- ID3D11Texture1D 和 ID3D11Device:CreateTexture1D
- ID3D11Texture2D 和 ID3D11Device::CreateTexture2D
- ID3D11Texture3D 和 ID3D11Device::CreateTexture3D
在 Direct3D 12 中,所有这些资源由 ID3D12Resource 和 ID3D12Device::CreateCommittedResource 表示。
预留的资源
预留的资源是仅分配了虚拟地址空间的资源,在调用 ID3D12Device::CreateHeap 之前,不会分配物理内存。 这实质上与 Direct3D 11 中“图块化资源”的概念相同。
Direct3D 11 中使用的标志(D3D11_RESOURCE_MISC_FLAG),用于设置平铺资源,然后将其映射到物理内存。
- D3D11_RESOURCE_MISC_TILED
- D3D11_RESOURCE_MISC_TILE_POOL
上传数据
在 Direct3D 11 中,存在单个时间线的外观(按序列调用(如使用D3D11_SUBRESOURCE_DATA初始化的数据),然后对 ID3D11DeviceContext::UpdateSubresource 进行调用,然后调用 ID3D11DeviceContext::Map。 Direct3D 11 开发人员无法清楚地看到所创建的数据副本数。
Direct3D 12 中有两条时间线:GPU 时间线(通过从可映射内存调用 CopyTextureRegion 和 CopyBufferRegion 来设置) 和 CPU 时间线(通过调用 Map 来确定)。 提供使用共享时间线的名为 Updatesubresources 的帮助器函数(在 d3dx12.h 文件中)。 此帮助器函数有多个变体,其中一个变体使用 ID3D12Device::GetCopyableFootprints,另一个变体使用堆分配机制,还有一个变体使用堆栈分配机制。 这些帮助器函数通过内存的中间暂存区域将资源复制到 GPU 和 CPU。
通常,GPU 和 CPU 都具有自身的资源副本,该副本与其自身的时间线相关联。 类似地,共享时间线方法维护两个副本。
着色器和着色器对象
Direct3D 11 中存在许多着色器和状态对象创建创建,以及使用 ID3D11Device 创建方法和 ID3D11DeviceContext 设置方法设置这些对象的状态的操作。 通常会对这些方法发出大量的调用,在绘制时,驱动程序会合并这些调用以设置正确的管道状态。
在 Direct3D 12 中,此管道状态设置已合并成单个对象(计算引擎的 CreateComputePipelineState,以及图形引擎的 CreateGraphicsPipelineState),然后,在通过调用 SetPipelineState 发出绘制调用之前附加到命令列表。
这些调用将替换 Direct3D 11 中设置着色器、输入布局、混合状态、光栅器状态、深度模具状态等的所有单个调用
- 设备 11 方法:
CreateInputLayout
、CreateXShader
和CreateDepthStencilState
CreateRasterizerState
。 - 设备上下文 11 方法:
IASetInputLayout
、xxSetShader
、OMSetBlendState
和OMSetDepthStencilState
RSSetState
。
虽然 Direct3D 12 可以支持较旧的已编译着色器 Blob,但着色器应使用着色器模型 5.1 和 FXC/D3DCompile API 生成,或使用 DXIL DXC 编译器使用着色器模型 6。 应使用 CheckFeatureSupport 和 D3D12_FEATURE_SHADER_MODEL 验证着色器模型 6 支持。
将工作提交到 GPU
在 Direct3D 11 中,几乎无法控制工作的实际提交方式,此操作在很大程度上由驱动程序处理,不过,可以通过 ID3D11DeviceContext::Flush 和 IDXGISwapChain1::Present1 调用来启用某种控制。
在 Direct3D 12 中,工作提交是非常明确的,并受应用的控制。 用于提交工作的主要构造是 ID3D12GraphicsCommandList,它用于记录所有应用命令(在概念上非常类似于 ID3D11 中的延迟上下文)。 命令列表的后备存储由 ID3D12CommandAllocator 提供,它可以让应用通过实际公开 Direct3D 12 驱动程序用于存储命令列表的内存,来管理命令列表的内存利用率。
最后,ID3D12CommandQueue 是一个先入先出队列,可存储要提交到 GPU 的命令列表的正确顺序。 仅当一个命令列表已 GPU 上完成执行时,驱动程序才会提交队列中的下一个命令列表。
在 Direct3D 11 中,命令队列没有明确的概念。 在 Direct3D 12 的常见设置中,当前帧的当前打开 D3D12_COMMAND_LIST_TYPE_DIRECT 命令列表可以被视为类似于 Direct3D 11 即时上下文。 这提供了许多相同的函数。
D3D11DeviceContext | ID3D12GraphicsCommand List |
---|---|
ClearDepthStencilView | ClearDepthStencilView |
ClearRenderTargetView | ClearRenderTargetView |
ClearUnorderedAccess* | ClearUnorderedAccess* |
Draw、DrawInstanced | DrawInstanced |
DrawIndexed、DrawIndexedInstanced | DrawIndexedInstanced |
Dispatch | Dispatch |
IASetInputLayout、xxSetShader 等。 | SetPipelineState |
OMSetBlendState | OMSetBlendFactor |
OMSetDepthStencilState | OMSetStencilRef |
OMSetRenderTargets | OMSetRenderTargets |
RSSetViewports | RSSetViewports |
RSSetScissorRects | RSSetScissorRects |
IASetPrimitiveTopology | IASetPrimitiveTopology |
IASetVertexBuffers | IASetVertexBuffers |
IASetIndexBuffer | IASetIndexBuffer |
ResolveSubresource | ResolveSubresource |
CopySubresourceRegion | CopyBufferRegion |
UpdateSubresource | CopyTextureRegion |
CopyResource | CopyResource |
注意
使用 D3D12_COMMAND_LIST_TYPE_BUNDLE 创建的命令列表对延迟上下文而言比较简单。 Direct3D 12 还支持通过D3D12_COMMAND_LIST_TYPE_COPY和D3D12_COMMAND_LIST_TYPE_COMPUTE命令列表类型同时访问即时上下文的某些功能来呈现。
CPU/GPU 同步
在 Direct3D 11 中,CPU/GPU 同步在很大程度上是自动化的,应用无需维护物理内存的状态。
在 Direct3D 12 中,应用必须显式管理两条时间线(CPU 和 GPU)。 因此,应用需要维护有关 GPU 需要哪些资源,以及需要使用多长时间的信息。 这也意味着,应用需负责确保资源内容(例如提交的资源、堆、命令分配器)在 GPU 用完它们之前不会更改。
用于同步时间线的主要对象是 ID3D12Fence 对象。 围栏的操作相当简单,它们使 GPU 能够在任务完成时发出信号。 GPU 和 CPU 都可发出信号,并且都会等待围栏。
常用的方法是,在提交某个命令列表供执行时,GPU 会在完成时(读完数据时)传输围栏信号,使 CPU 能够重复使用或销毁资源。
在 Direct3D 11 中, ID3D11DeviceContext::Map 标志D3D11_MAP_WRITE_DISCARD基本上将每个资源视为应用可以写入的无休止的内存供应(称为“重命名”的进程)。 在 Direct3D 12 中,该进程同样很明确:需要分配额外的内存,并且应该使用围栏来同步操作。 环形缓冲区(由较大的缓冲区组成)可能是解决此问题的适当方法,具体请参阅基于围栏的资源管理中的环形缓冲区方案。
资源绑定
Direct3D 11 中的视图(着色器资源视图、渲染器目标视图等)基本上已被 Direct3D 12 中的描述符概念取代。 Direct3D 12 中仍然存在创建方法(例如 CreateShaderResourceView 和 CreateRenderTargetView),创建描述符堆后会调用这些方法,以在堆中写入数据。 Direct3D 12 中的绑定现在由根签名中描述的描述符句柄处理,使用 SetGraphicsRootDescriptorTable 或 SetComputeRootDescriptorTable 方法提交。
根签名详细描述根签名槽数与描述符表之间的映射,其中,描述符表可以包含对顶点着色器、像素着色器和其他着色器(例如常量缓冲区、着色器资源视图和采样器)可用资源的引用。 这种灵活性可将 HLSL 寄存器空间从 Direct3D 12 中的 API 绑定空间断开连接。Direct3D 11 则与此不同,其中的这些资源存在一对一的映射。
此系统的影响之一在于,应用负责重命名描述符表,使开发人员能够了解即使在执行每个绘制调用时更改一个描述符的性能开销比。
Direct3D 12 的一项新功能是,应用可以控制哪些描述符在哪些着色器阶段之间共享。 在 Direct3D 11 中,UAV 等资源在所有着色器阶段之间共享。 通过启用要对某些着色器阶段禁用的描述符,已禁用的描述符使用的寄存器可供为特定着色器阶段启用的描述符使用。
下表显示了一个示例根签名。
根参数槽 | 描述符表条目 |
---|---|
0 | VS 描述符范围 b0-b13 |
1 | VS 描述符范围 t0-t127 |
2 | VS 描述符范围 s0-s16 |
3 | PS 描述符范围 b0-b13 |
... | |
14 | DS 描述符范围 s0-16 |
15 | 共享描述符范围 u0-u63 |
资源状态
在 Direct3D 11 中,资源状态由驱动程序维护,而不是由应用维护。
在 Direct3D 12 中,资源状态的维护由应用负责,以便在记录命令列表时实现完全并行度:应用必须处理命令列表的记录时间线(可以并行进行),而执行时间线必须是有序的。
资源状态转换由 ResourceBarrier 方法处理。 从根本上讲,当资源使用率发生变化时,应用必须通知驱动程序。 例如,如果将某个资源用作渲染器目标,然后在下一次执行绘制调用时又将它用作顶点着色器的输入,则可能需要短暂停滞 GPU 操作,以便在处理顶点着色器之前先完成渲染器目标操作。
此系统可以实现图形管道的粒度级同步(GPU 停滞),以及缓存刷新和可能的某些内存布局更改(例如渲染器目标视图到深度模具视图的解压缩)。
这称为“转换屏障”。 Direct3D 11 中还有其他类型的屏障,两个不同的图块化资源可以通过 ID3D11DeviceContext2::TiledResourceBarrier 使用相同的物理内存。 在 Direct3D 12 中,这称为“失真屏障”。 可对 Direct3D 12 中的图块和定位资源使用失真屏障。 此外还有 UAV 屏障。 在 Direct3D 11 中,所有 UAV 调度和绘制操作都需要序列化,即使这些操作可以构成管道或并行工作。 在 Direct3D 12 中,已通过添加 UAV 屏障消除了此限制。 UAV 屏障确保 UAV 操作是有序的,因此,如果第二个操作要求第一个操作先完成,则添加屏障会强制要求第二个操作等待第一个操作完成。 UAV 的默认操作仅仅是让操作尽快继续。
很明显,如果可以并行化工作负荷,则性能将会提升。
交换链
DXGI 交换链是 Direct3D 11 和 12 中的交换链的基础。 Direct3D 11 中存在一些细微差异,三种类型的交换链是顺序、放弃和FLIP_SEQUENTIAL。 对于 Direct3D 12,只有两种类型:FLIP_SEQUENTIAL和FLIP_DISCARD。 如上所述,应通过 IDXGIFactory4 或更高版本显式创建交换链,并对任何适配器枚举使用相同的接口。
在 Direct3D 11 中存在自动反向缓冲区轮转:反向缓冲区 0 只需一个渲染器目标视图。 在 Direct3D 12 中,缓冲区轮转是显式的,每个反向缓冲区都需要一个渲染器目标视图。 使用 IDXGISwapChain3::GetCurrentBackBufferIndex 方法选择要渲染的缓冲区。 同样,更大的这种灵活性可以实现更高的并行度。
注意
虽然有许多方法可以设置应用程序,但通常应用程序每个交换链缓冲区都有一个 ID3D12CommandAllocator 。 这样,应用程序就可以继续为下一帧生成一组命令,同时 GPU 呈现上一帧。
已修复的函数渲染
在 Direct3D 11 中,有些方法简化了各种更高级别的操作,例如 GenerateMips(创建完整的 mip 链)和 DrawAuto(使用流输出作为着色器输入,且无需应用进一步提供输入)。 这些方法在 Direct3D 12 中不可用,应用需要通过创建执行这些操作的着色器来处理这些操作。
杂项
下表显示了在 Direct3D 11 和 12 中类似的,但不完全相同的一些功能。
Direct3D 11 | Direct3D 12 |
---|---|
ID3D11Query | ID3D12QueryHeap 允许将查询分组在一起,以降低成本。 |
ID3D11Predicate | 现在,可以通过在完全透明的缓冲区中存储数据,来实现断言。 Direct3D 11 ID3D11Predicate 对象已由 ID3D12Resource::Map 取代,后者必须在调用 ResolveQueryData 并完成使用围栏等待数据准备就绪的 GPU 同步操作之后执行。 请参阅断言。 |
UAV/SO 隐藏计数器 | 应用负责分配和管理 SO/UAV 计数器。 请参阅流输出计数器和 UAV 计数器。 |
资源动态 MinLOD(最低详细程度) | 此功能已过渡到 SRV 描述符静态 MinLOD。 |
Draw*Indirect/DispatchIndirect | 绘制间接方法已全部合并成一个 ExecuteIndirect 方法。 |
DepthStencil 格式是交错式 | DepthStencil 格式是平面式。 例如, 24 位深度、8 位模具的格式在 Direct3D 11 中将以 24/8/24/8... 等格式存储,但在 Direct3D 12 中将以 24/24/24... 后接 8/8/8... 的格式存储。 请注意,每个平面在 D3D12 中具有自身的子资源(请参阅子资源)。 |
ResizeTilePool | 保留的资源可映射到多个堆。 如果图块池已在 D3D11 中增大,可以改为在 D3D12 中分配附加的堆。 |