VK_AMD_shader_early_and_late_fragment_tests

本文档描述了一个关于新的 SPIR-V 执行模式的提案,该模式允许片段着色器被早期片段操作丢弃,即使它们包含对存储资源的写入或其他副作用。

1. 问题陈述

大多数图形设备能够在片段着色器避免某些操作时利用早期片段操作 - 例如,写入深度或模板,或写入存储资源,在大多数情况下提供显著的性能优势。这在可能的情况下隐式启用,并且可以通过在 SPIR-V 中指定 EarlyFragmentTests 执行模式来显式启用。但是,EarlyFragmentTests 执行模式使得从着色器写入片段深度无效。

某些实现可以在片段着色之前之后执行深度测试,允许保守的早期测试丢弃大多数片段,并允许晚期测试以更高的精度丢弃片段。GL_ARB_conservative_depth 添加了一种方法,即使深度是由片段着色器写入的,也可以启用此优化,从而允许在某些条件下实现进一步的优化。但是,如果着色器也写入存储资源,则由于规范的可预测性要求,无法进行此类优化。在应用程序不关心丢弃时是否执行片段着色器存储写入的情况下,可以在某些控制台平台上使用此功能来显著提高性能,但到目前为止,Vulkan 没有机制来做到这一点。对于某些应用程序,这可能意味着不必要的性能损失,而这种损失应该相对容易解决。

2. 解决方案空间

这个问题实际上只有一个解决方案,即以某种方式向应用程序公开此功能。主要问题是如何公开此功能,以什么粒度公开,以及我们是否应该提供任何保证。最终,它应该相对容易地打开/关闭,并且理想情况下应该以某种形式在每个片段着色器中设置。

表示开关的主要选项是

  1. 管线创建标志

  2. SPIR-V 执行模式

  3. 非语义指令

如果它成为管线创建标志,则很容易在每个管线的基础上打开/关闭。但是,是否可以丢弃写入的知识通常是片段着色器代码本身中编写的任何算法的属性,这意味着此属性必须在两个位置指定。从这个角度来看,在着色器代码本身中包含一些东西是有意义的。

SPIR-V 执行模式是表达此功能的一种直接方式,并且与 SPIR-V 中表达保守深度的方式一致(例如,DepthGreater 执行模式)。使用本质上是优化提示的执行模式的缺点是,驱动程序必须实现该扩展才能使 SPIR-V 有效;而实际上在所有情况下都可以安全地忽略它。

非语义指令似乎提供了一种指定行为的方式,而无需驱动程序实现任何新东西。不幸的是,由于诱导的行为违反了当前的 Vulkan 规范,因此它不适合此用例,因为它不能安全地添加到所有着色器。

如果没有像非语义扩展那样更广泛的“可以忽略但可以改变行为”的扩展,SPIR-V 执行模式可能是最合适的选择。应用程序、面向应用程序的库或 Vulkan 软件层可以用来在不支持时自动删除执行模式。如果实现不具备所需的功能,也应该最终能够支持该执行模式作为空操作。

3. 提案

3.1. 新的 Vulkan 功能

typedef struct VkPhysicalDeviceShaderEarlyAndLateFragmentTestsFeaturesAMD {
    VkStructureType    sType;
    void*              pNext;
    VkBool32           shaderEarlyAndLateFragmentTests;
} VkPhysicalDeviceShaderEarlyAndLateFragmentTestsFeaturesAMD;

此功能允许在实现使用的 SPIR-V 着色器中使用新的执行模式。

3.2. 新的 SPIR-V 执行模式

引入了一种新的执行模式,允许在执行深度和模板写入时,结合深度优化,同时执行提前和延迟的深度和模板测试。为了允许使用此新执行模式进行模板参考写入,提供了类似的模板参考写入优化。

执行模式 额外操作数 启用功能

5017

EarlyAndLateFragmentTestsAMD
可以在片段着色器执行之前和之后执行片段测试,其中后面的测试会考虑写入 FragDepthFragStencilRefEXT 的值。不保证提前测试,但保证延迟测试。+
如果未指定 ExecutionModeDepthReplacingExecutionModeStencilRefReplacingEXT 中的任何一个,则其功能与 EarlyFragmentTests 相同。
如果同时指定了此项和 ExecutionModeStencilRefReplacingEXT,则还必须指定 StencilRefGreaterAMDStencilRefLessAMDStencilRefUnchangedAMD 中的一个。
如果同时指定了此项和 ExecutionModeDepthReplacing,则还必须指定 DepthGreaterDepthLessDepthUnchanged 中的一个。

仅对片段 执行模型有效。
有关片段操作的详细信息,请参阅客户端 API。

着色器

5079

StencilRefUnchangedFrontAMD
表示早期逐片段测试可以假设着色器写入的任何内置修饰值 FragStencilRefEXT 都等于客户端 API 中为正面设置的模板参考值(在掩码之后)。后期逐片段测试将照常使用写入的值。

仅对片段 执行模型有效。
最多可以指定 StencilRefGreaterAMDStencilRefLessAMDStencilRefUnchangedAMD 中的一个。

StencilExportEXT

5080

StencilRefGreaterFrontAMD
表示早期逐片段测试可以假设着色器写入的任何内置修饰值 FragStencilRefEXT 都大于或等于客户端 API 中为正面设置的模板参考值(在掩码之后)。后期逐片段测试将照常使用写入的值。

仅对片段 执行模型有效。
最多可以指定 StencilRefGreaterAMDStencilRefLessAMDStencilRefUnchangedAMD 中的一个。

StencilExportEXT

5081

StencilRefLessFrontAMD
表示早期逐片段测试可以假设着色器写入的任何内置修饰值 FragStencilRefEXT 都小于或等于客户端 API 中为正面设置的模板参考值(在掩码之后)。后期逐片段测试将照常使用写入的值。

仅对片段 执行模型有效。
最多可以指定 StencilRefGreaterAMDStencilRefLessAMDStencilRefUnchangedAMD 中的一个。

StencilExportEXT

5082

StencilRefUnchangedBackAMD
表示早期逐片段测试可以假设着色器写入的任何内置修饰值 FragStencilRefEXT 都等于客户端 API 中为背面设置的模板参考值(在掩码之后)。后期逐片段测试将照常使用写入的值。

仅对片段 执行模型有效。
最多可以指定 StencilRefGreaterAMDStencilRefLessAMDStencilRefUnchangedAMD 中的一个。

StencilExportEXT

5083

StencilRefGreaterBackAMD
表示早期逐片段测试可以假设着色器写入的任何内置修饰值 FragStencilRefEXT 都大于或等于客户端 API 中为背面设置的模板参考值(在掩码之后)。后期逐片段测试将照常使用写入的值。

仅对片段 执行模型有效。
最多可以指定 StencilRefGreaterAMDStencilRefLessAMDStencilRefUnchangedAMD 中的一个。

StencilExportEXT

5084

StencilRefLessBackAMD
表示早期逐片段测试可以假设着色器写入的任何内置修饰值 FragStencilRefEXT 都小于或等于客户端 API 中为背面设置的模板参考值(在掩码之后)。后期逐片段测试将照常使用写入的值。

仅对片段 执行模型有效。
最多可以指定 StencilRefGreaterAMDStencilRefLessAMDStencilRefUnchangedAMD 中的一个。

StencilExportEXT

这允许实现显式执行提前和延迟测试。

3.3. 新的 GLSL 布局限定符

以下新的布局限定符被添加到 GLSL 中

片段着色器允许以下独立的声明

__early_and_late_fragment_testsAMD

请求在片段着色器执行之前和之后执行某些片段测试,如 Vulkan 1.2 规范的“片段操作”章节中所述。此声明必须单独出现在一行中。

可以指定以下附加的独立声明

layout-qualifier-id:
    __stencil_ref_unchanged_frontAMD
    __stencil_ref_less_frontAMD
    __stencil_ref_greater_frontAMD
    __stencil_ref_unchanged_backAMD
    __stencil_ref_less_backAMD
    __stencil_ref_greater_backAMD

这些声明必须各自单独出现在一行中。只能指定一个 stencil_ref_*frontAMD 和一个 stencil_ref*_backAMD 声明。每个声明都约束任何着色器调用写入的 gl_FragStencilRefARB 的最终值的意图。如果与声明一致的所有 gl_FragStencilRefARB 值都将失败(或通过),则允许实现执行优化,假设给定片段的模板测试失败(或通过)。这可能包括如果片段被遮挡并且着色器没有副作用而导致片段被丢弃时跳过着色器执行。如果 gl_FragStencilRefARB 的最终值与着色多边形的朝向的声明不一致,则相应片段的模板测试结果是未定义的。如果模板测试通过且启用了模板写入,则写入模板缓冲区的值始终是 gl_FragStencilRefARB 的值,无论它是否与布局限定符一致。

以上每个限定符都直接映射到等效命名的 spir-v 执行模式。

3.4. 新的 HLSL 属性

添加了以下新的 Vulkan 特定属性

  • early_and_late_tests:将入口点标记为启用提前和延迟深度测试。如果通过 SV_Depth 写入深度,则还必须指定 depth_unchanged(可以自由写入 SV_DepthLess 和 SV_DepthGreater)。如果通过 SV_StencilRef 写入模板参考值,则必须指定 stencil_ref_unchanged_frontstencil_ref_greater_equal_frontstencil_ref_less_equal_front 中的一个,以及 stencil_ref_unchanged_backstencil_ref_greater_equal_backstencil_ref_less_equal_back 中的一个。

  • depth_unchanged:指定写入 SV_Depth 的任何深度都不会使早期深度测试的结果无效。在 SPIR-V 中设置 DepthUnchanged 执行模式。

  • stencil_ref_unchanged_front:指定当片段是正面时,写入 SV_StencilRef 的任何模板引用都不会使早期模板测试的结果无效。在 SPIR-V 中设置 StencilRefUnchangedFrontAMD 执行模式。

  • stencil_ref_greater_equal_front:指定当片段是正面时,写入 SV_StencilRef 的任何模板引用值将大于或等于 API 设置的模板参考值。在 SPIR-V 中设置 StencilRefGreaterFrontAMD 执行模式。

  • stencil_ref_less_equal_front:指定当片段是正面时,写入 SV_StencilRef 的任何模板引用值将小于或等于 API 设置的模板参考值。在 SPIR-V 中设置 StencilRefLessFrontAMD 执行模式。

  • stencil_ref_unchanged_back:指定当片段是背面时,写入 SV_StencilRef 的任何模板引用值不会使早期模板测试的结果无效。在 SPIR-V 中设置 StencilRefUnchangedBackAMD 执行模式。

  • stencil_ref_greater_equal_back:指定当片段是背面时,写入 SV_StencilRef 的任何模板引用值将大于或等于 API 设置的模板参考值。在 SPIR-V 中设置 StencilRefGreaterBackAMD 执行模式。

  • stencil_ref_less_equal_back:指定当片段是背面时,写入 SV_StencilRef 的任何模板引用值将小于或等于 API 设置的模板参考值。在 SPIR-V 中设置 StencilRefLessBackAMD 执行模式。

着色器不能指定多个 stencil_ref_unchanged_frontstencil_ref_greater_equal_frontstencil_ref_less_equal_front 中的一个。着色器不能指定多个 stencil_ref_unchanged_backstencil_ref_greater_equal_backstencil_ref_less_equal_back 中的一个。

4. 问题

4.1. 未解决:我们是否应该公开一个特性/属性来指示实现是否真的会执行早期和晚期测试?

如果最终所有实现都能提供此特性,并在相关情况下将其视为无操作,这将很有用。但是,如果某些实现无法从中获得任何优势,那么公开一个指示此情况的属性可能是合理的。