Direct3D 12 呈现通道

呈现传递功能是 Windows 10 版本 1809 (10.0 的新增功能;内部版本 17763) ,它引入了 Direct3D 12 呈现通道的概念。 呈现传递由记录到命令列表中的命令子集组成。

若要声明每个呈现通道的开始和结束位置,请将属于该传递的命令嵌套在 对 ID3D12GraphicsCommandList4::BeginRenderPassEndRenderPass 的调用中。 因此,任何命令列表都包含零个、一个或多个呈现通道。

方案

如果呈现器基于Tile-Based延迟渲染 (TBDR) 以及其他技术,则呈现器传递可以提高呈现器的性能。 更具体地说,该技术通过使应用程序能够更好地识别资源呈现顺序要求和数据依赖项,减少传入/流出片外内存的内存流量,从而帮助呈现器提高 GPU 效率。

为利用呈现传递功能而明确编写的显示驱动程序可提供最佳结果。 但是,呈现传递 API 甚至可以在预先存在的驱动程序上运行, (尽管不一定) 性能改进。

在这些方案中,呈现传递旨在提供值。

允许应用程序避免在Tile-Based延迟呈现 (TBDR) 体系结构上main内存中不必要的资源加载/存储

呈现传递的价值主张之一是,它提供一个中心位置来指示应用程序对一组呈现操作的数据依赖关系。 这些数据依赖项允许显示驱动程序在绑定/屏障时检查此数据,并发出指令,以尽量减少资源加载/存储main内存。

允许 TBDR 体系结构在多个呈现传递 (片上缓存中以机会方式持久保存资源,即使在单独的命令列表中)

注意

具体而言,此方案仅限于要跨多个命令列表写入同一个呈现目标 () 的情况。

一种常见的呈现模式是让应用程序以串行方式跨多个命令列表呈现到同一呈现目标 () ,即使呈现命令是并行生成的。 在此方案中使用呈现传递允许以 (方式将这些传递组合在一起,因为应用程序知道它将恢复在直接成功的命令列表上呈现) 显示驱动程序可以避免刷新到命令列表边界上的main内存。

应用程序的责任

即使使用呈现传递功能,Direct3D 12 运行时和显示驱动程序都不会承担重新排序/避免加载和存储的推导机会的责任。 为了正确利用呈现传递功能,应用程序需要承担这些责任。

  • 正确标识其操作的数据/排序依赖项。
  • 以尽量减少刷新 (的方式对提交进行排序,因此,请尽量减少) 使用 _PRESERVE 标志。
  • 正确利用资源屏障,并跟踪资源状态。
  • 避免不需要的副本/清除。 为了帮助识别这些问题,可以使用 Windows 上的 PIX 工具中的自动性能警告。

使用呈现传递功能

什么是 呈现通道

呈现通道由这些元素定义。

  • 在呈现传递期间固定的一组输出绑定。 这些绑定 (RTV) 和/或深度模具视图 (DSV) 的一个或多个呈现目标视图。
  • 面向该组输出绑定的 GPU 操作的列表。
  • 描述呈现传递目标的所有输出绑定的加载/存储依赖项的元数据。

声明输出绑定

在呈现阶段开始时,将绑定声明到呈现器目标 () 和/或深度/模具缓冲区。 绑定到 () 呈现目标是可选的,也可以绑定到深度/模具缓冲区。 但必须至少绑定到两者中的一个,在下面的代码示例中,我们将绑定到这两者。

在调用 ID3D12GraphicsCommandList4::BeginRenderPass 时声明这些绑定。

void render_passes(::ID3D12GraphicsCommandList4 * pIGCL4,
    D3D12_CPU_DESCRIPTOR_HANDLE const& rtvCPUDescriptorHandle,
    D3D12_CPU_DESCRIPTOR_HANDLE const& dsvCPUDescriptorHandle)
{
    const float clearColor4[]{ 0.f, 0.f, 0.f, 0.f };
    CD3DX12_CLEAR_VALUE clearValue{ DXGI_FORMAT_R32G32B32_FLOAT, clearColor4 };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessClear{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, { clearValue } };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessPreserve{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {} };
    D3D12_RENDER_PASS_RENDER_TARGET_DESC renderPassRenderTargetDesc{ rtvCPUDescriptorHandle, renderPassBeginningAccessClear, renderPassEndingAccessPreserve };

    D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessNoAccess{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessNoAccess{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, {} };
    D3D12_RENDER_PASS_DEPTH_STENCIL_DESC renderPassDepthStencilDesc{ dsvCPUDescriptorHandle, renderPassBeginningAccessNoAccess, renderPassBeginningAccessNoAccess, renderPassEndingAccessNoAccess, renderPassEndingAccessNoAccess };

    pIGCL4->BeginRenderPass(1, &renderPassRenderTargetDesc, &renderPassDepthStencilDesc, D3D12_RENDER_PASS_FLAG_NONE);
    // Record command list.
    pIGCL4->EndRenderPass();
    // Begin/End further render passes and then execute the command list(s).
}

D3D12_RENDER_PASS_RENDER_TARGET_DESC 结构的第一个字段设置为 CPU 描述符句柄,该句柄对应于一个或多个呈现目标视图 (RTV) 。 同样, D3D12_RENDER_PASS_DEPTH_STENCIL_DESC 包含与深度模具视图相对应的 CPU 描述符句柄 (DSV) 。 这些 CPU 描述符句柄与以其他方式传递给 ID3D12GraphicsCommandList::OMSetRenderTargets 的句柄相同。 并且,与 OMSetRenderTargets 一样,在调用 BeginRenderPass 时,CPU 描述符从各自的 (CPU 描述符) 堆进行贴靠

RTV 和 DSV 不会继承到呈现通道中。 相反,必须设置它们。 BeginRenderPass 中声明的 RTV 和 DSV 也不会传播到命令列表。 相反,它们在呈现传递之后处于未定义状态。

呈现传递和工作负载

不能嵌套呈现通道,并且不能让呈现通道跨多个命令列表, (它们必须在录制到单个命令列表) 时开始和结束。 下面的呈现传递标志部分讨论了旨在实现高效多线程生成呈现 通道的优化。

在后续呈现阶段之前,从呈现传递中执行写入操作无效。 这排除了呈现通道中的某些类型的屏障,例如,在当前绑定的呈现器目标上 RENDER_TARGETSHADER_RESOURCE 。 有关详细信息,请参阅下面的 呈现传递和资源屏障部分。

刚才提到的写入-读取约束的一个例外涉及作为深度测试和呈现目标混合的一部分发生的隐式读取。 因此,如果在录制) (核心运行时删除命令列表,则呈现通道中不允许使用这些 API。

呈现传递和资源屏障

不能读取或使用同一呈现通道中发生的写入。 某些障碍不符合此约束,例如,从当前绑定的呈现器目标 (D3D12_RESOURCE_STATE_RENDER_TARGET*_SHADER_RESOURCE ,调试层将错误达到该效果) 。 但是,在当前呈现通道 外部 写入的呈现目标上的相同屏障是一致的,因为写入操作将在当前呈现通道开始之前完成。 了解显示驱动程序在这方面可以进行的某些优化可能会受益匪浅。 给定符合的工作负载,显示驱动程序可能会将呈现通道中遇到的任何障碍移动到呈现通道的开头。 在那里,它们可以 (合并,并且不会干扰任何平铺/装箱操作) 。 这是一项有效的优化,前提是所有写入操作在当前呈现阶段开始之前都已完成。

下面是一个更完整的驱动程序优化示例,该示例假定你有一个呈现引擎,该引擎具有预 Direct3D 12 样式的资源绑定设计-根据资源绑定方式 按需 执行障碍。 当写入到无序访问视图中时, (UAV) 帧 (在以下帧) 的末尾处使用,引擎可能会在帧结束时将资源保留为 D3D12_RESOURCE_STATE_UNORDERED_ACCESS 状态。 在随后的帧中,当引擎将资源作为着色器资源视图绑定 (SRV) 时,会发现资源状态不正确,并且会发出从 D3D12_RESOURCE_STATE_UNORDERED_ACCESSD3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE的屏障。 如果此屏障发生在呈现通道内,则显示驱动程序假设所有写入操作都已在此当前呈现通道 之外 发生,因此 (,下面是优化的来源) 显示驱动程序可能会 将屏障向上移动到 呈现通道的开头。 同样,只要代码符合本节和上一节中所述的写-读取约束,这一点就有效。

这些是符合性障碍的示例。

  • D3D12_RESOURCE_STATE_UNORDERED_ACCESSD3D12_RESOURCE_STATE_INDIRECT_ARGUMENT。
  • D3D12_RESOURCE_STATE_COPY_DEST * _SHADER_RESOURCE

以下是不合规障碍的示例。

  • D3D12_RESOURCE_STATE_RENDER_TARGET 当前绑定的 RTV/DSV 上的任何读取状态。
  • D3D12_RESOURCE_STATE_DEPTH_WRITE 当前绑定的 RTV/DSV 上的任何读取状态。
  • 任何别名屏障。
  • 无序访问视图 (UAV) 障碍。 

资源访问声明

BeginRenderPass 时间,以及声明在该传递中充当 RTV 和/或 DSV 的所有资源,还必须指定其开始和结束 访问 特征。 如上面的 声明输出绑定 部分的代码示例所示,可以使用 D3D12_RENDER_PASS_RENDER_TARGET_DESCD3D12_RENDER_PASS_DEPTH_STENCIL_DESC 结构执行此操作。

有关详细信息,请参阅 D3D12_RENDER_PASS_BEGINNING_ACCESSD3D12_RENDER_PASS_ENDING_ACCESS 结构,以及 D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPED3D12_RENDER_PASS_ENDING_ACCESS_TYPE 枚举。

呈现传递标志

最后一个传递给 BeginRenderPass 的参数是呈现传递标志, (来自 D3D12_RENDER_PASS_FLAGS 枚举) 的值。

enum D3D12_RENDER_PASS_FLAGS
{
    D3D12_RENDER_PASS_FLAG_NONE = 0,
    D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES = 0x1,
    D3D12_RENDER_PASS_FLAG_SUSPENDING_PASS = 0x2,
    D3D12_RENDER_PASS_FLAG_RESUMING_PASS = 0x4
};

UAV 在呈现通道内写入

允许在呈现通道内进行无序访问视图 (UAV) 写入,但必须明确指示将通过指定 D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES在呈现通道中发出 UAV 写入,以便显示驱动程序可以在必要时选择退出平铺。

UAV 访问必须遵循上述写入-读取约束, (在后续呈现传递) 之前,呈现传递中的写入将无效读取。 呈现通道中不允许使用 UAV 屏障。

通过根表或根描述符 (的 UAV 绑定) 继承到呈现通道中,并传播出呈现通道。

Suspending-pass 和 resuming-pass

可以将整个呈现通道指示为挂起传递和/或恢复传递。 挂起-后跟-by-a-a-resuming 对之间必须具有相同的视图/访问标志,并且可能没有任何干预 GPU 操作 (例如,绘制、调度、放弃、清除、复制、update-tile-mappings、write-buffer-immediates、查询、查询解析) 挂起呈现传递和恢复呈现传递之间的) 。

预期的用例是多线程呈现,其中四个命令列表 (每个命令列表都有自己的呈现传递) 可以针对相同的呈现目标。 当跨命令列表挂起/恢复呈现传递时,必须在对 ID3D12CommandQueue::ExecuteCommandLists 的同一调用中执行命令列表。

呈现通道可以是恢复和挂起。 在刚刚给出的多线程示例中,命令列表 2 和 3 将分别从 1 和 2 恢复。 同时,2和3将分别暂停到3和4。

查询呈现传递功能支持

可以调用 ID3D12Device::CheckFeatureSupport 来查询设备驱动程序和/或硬件有效支持呈现传递的程度。

D3D12_RENDER_PASS_TIER get_render_passes_tier(::ID3D12Device * pIDevice)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS5 featureSupport{};
    winrt::check_hresult(
        pIDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &featureSupport, sizeof(featureSupport))
    );
    return featureSupport.RenderPassesTier;
}
...
    D3D12_RENDER_PASS_TIER renderPassesTier{ get_render_passes_tier(pIDevice) };

由于运行时的映射逻辑,呈现传递始终有效。 但是,根据功能支持,它们并不总是提供好处。 可以使用类似于上述代码示例的代码来确定在呈现传递时发出命令是否值得/何时发出命令,以及当它肯定不是一个好处 (即,当运行时只是映射到现有 API 图面) 。 如果使用 D3D11On12) ,则执行此检查尤其重要。

有关三个支持层的说明,请参阅 D3D12_RENDER_PASS_TIER 枚举。