Direct3D 12 呈现通道

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

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

场景

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

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

这些是呈现传递旨在提供值的场景。

允许应用程序避免在 Tile-Based 延迟渲染(TBDR)体系结构上从/向主内存加载/存储不必要的资源

呈现传递的价值主张之一是,它提供了一个中心位置来指示应用程序的一组呈现作的数据依赖项。 这些数据依赖项允许显示驱动程序在绑定/屏障时间检查此数据,并发出将资源负载/存储从/到主内存最小化的说明。

允许 TBDR 体系结构跨呈现通道在芯片上缓存中以机会方式持久化资源(即使在单独的命令列表中)

注意

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

一种常见的呈现模式是,即使并行生成呈现命令,应用程序也会在多个命令列表之间呈现到相同的呈现目标(s)。 在此方案中使用呈现传递允许以某种方式(因为应用程序知道它将在立即成功的命令列表上恢复呈现),显示驱动程序可以避免刷新到命令列表边界上的主内存。

应用程序的责任

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

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

使用呈现传递功能

什么是 呈现传递

呈现传递由这些元素定义。

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

声明输出绑定

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

在调用 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 结构的第一个字段设置为与一个或多个呈现目标视图(RTV)对应的 CPU 描述符句柄。 同样,D3D12_RENDER_PASS_DEPTH_STENCIL_DESC 包含对应于深度模具视图(DSV)的 CPU 描述符句柄。 这些 CPU 描述符句柄与传递给 ID3D12GraphicsCommandList::OMSetRenderTargets的相同句柄。 而且,与 OMSetRenderTargets一样,CPU 描述符在调用 BeginRenderPass从各自的(CPU 描述符)堆 贴靠。

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

呈现传递和工作负荷

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

从呈现通道中执行的写入作不会 有效的 供你读取,直到后续呈现传递为止。 这排除了呈现传递中的某些类型的屏障,例如,从 RENDER_TARGET 屏障到当前绑定的呈现目标上的 SHADER_RESOURCE。 有关详细信息,请参阅下面的 呈现通道和资源屏障部分。

刚才提到的写读约束的一个例外涉及在深度测试和呈现目标混合过程中发生的隐式读取。 因此,在呈现通道中不允许这些 API(如果录制期间调用其中任何 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 绑定(通过根表或根描述符)继承为呈现通道,并传播出呈现通道。

暂停传递和恢复传递

可以将整个呈现传递指示为暂停传递和/或恢复传递。 暂停后跟恢复对在传递之间必须具有相同的视图/访问标志,并且可能没有任何干预 GPU作(例如,绘图、调度、放弃、清除、复制、更新磁贴映射、写入缓冲区-即时、查询、查询解析)之间的挂起呈现传递和恢复呈现传递。

预期用例是多线程呈现,其中假设有四个命令列表(每个命令列表都有自己的呈现通道)可以面向相同的呈现目标。 当跨命令列表挂起/恢复呈现传递时,必须在对 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 枚举。