HLSL 可用性诊断

简介

当使用不可用的着色器 API 时,HLSL 可用性诊断会发出错误或警告。不可用的着色器 API 是在 HLSL 代码中暴露但目标着色器阶段或着色器模型版本中不可用的 API。

HLSL 可用性诊断有三种模式

  1. 默认模式 - 当在从着色器入口点函数或导出库函数(在编译着色器库时)可访问的代码中找到不可用的 API 时,编译器会发出错误

  2. 放松模式 - 与默认模式相同,但编译器发出警告。此模式由 -Wno-error=hlsl-availability 启用。

  3. 严格模式 - 当在解析的代码中找到不可用的 API 时,编译器会发出错误,无论它是否可以从着色器入口点或导出函数访问。此模式由 -fhlsl-strict-availability 启用。

实现细节

环境参数

为了根据着色器模型版本和着色器模型阶段对 API 可用性进行编码,在现有的 Clang availability 属性中添加了一个新的 environment 参数。

此参数允许的值是作为 llvm::Triple 环境组件允许的值的子集。如果存在环境参数,则声明的可用性属性仅适用于具有相同平台和环境的目标。

默认和放松诊断模式

此模式在 DiagnoseHLSLAvailability 类中实现,该类在 SemaHLSL.cpp 中,并在解析完整个翻译单元后调用(从 Sema::ActOnEndOfTranslationUnit)。实现遍历翻译单元中的所有着色器入口点和导出库函数,并对每个函数体执行 AST 遍历。

当找到对另一个函数或成员方法的引用(DeclRefExprMemberExpr)并且它有主体时,也会扫描引用的函数的 AST。此 AST 遍历链将访问所有可从初始着色器入口点或导出库函数访问的代码,避免了生成调用图的必要性。

所有着色器 API 都具有可用性属性,该属性指定此 API 首次引入的着色器模型版本(以及环境,如果适用)。当找到对没有定义的函数的引用并且它具有可用性属性时,将检查属性的版本与目标着色器模型版本和着色器阶段(如果已知着色器阶段上下文),并根据需要生成适当的诊断。

所有着色器入口函数都具有 HLSLShaderAttr 属性,该属性指定此函数表示的着色器类型。但是,对于导出库函数,目标着色器阶段未知,因此在这种情况下,HLSL API 可用性将仅针对着色器模型版本进行检查。这意味着对于导出库函数,具有针对着色器阶段的可用性的 API 的诊断将推迟到 DXIL 链接时。

为了避免重复扫描和诊断,会保留一个已扫描函数的列表(参见 DiagnoseHLSLAvailability::ScannedDecls)。可能发生这种情况,一个着色器库可能有多个针对不同着色器阶段的着色器入口点,这些入口点都调用同一个共享函数。因此,重要的是不仅要记录已扫描函数,还要记录其扫描的着色器阶段上下文。这是通过使用 llvm::DenseMap 完成的,该映射将 FunctionDecl * 映射到一个 unsigned 位图,该位图表示函数已扫描的着色器阶段(或环境)集。集合中的第 N 位如果函数已在 HLSLShaderAttr::ShaderType 整数值等于 N 的着色器环境中扫描,则该位被设置。

发出的诊断消息属于 hlsl-availability 诊断组,默认情况下被报告为错误。使用 -Wno-error=hlsl-availability 标志,它们将变为警告,从而使 HLSL 诊断模式变得放松。

严格诊断模式

当启用严格 HLSL 可用性诊断模式时,编译器必须报告所有 HLSL API 可用性问题,无论代码是否可访问。此模式的实现利用了 DiagnoseUnguardedAvailability 类中已有的诊断扫描,该扫描在解析函数体后立即遍历每个函数的 AST。对于 HLSL,此步骤仅略微修改,例如确保诊断消息在 hlsl-availability 组中,并且如果未知着色器阶段上下文,则不包含基于着色器阶段的可用性检查。

如果编译目标是着色器库,则仅可以在此扫描期间诊断基于着色器模型版本的可用性。要诊断基于着色器阶段的可用性,编译器需要在翻译单元结束时运行 DiagnoseHLSLAvailability 中实现的 AST 遍历,如上所述。

因此,基于特定着色器阶段的可用性将仅在可从着色器入口点或库导出函数访问的代码中诊断。这也意味着函数体可能会被多次扫描。发生这种情况时,应注意不要产生重复的诊断。

示例

注意 对于以下示例,WaveActiveCountBits API 函数在着色器模型 6.0 中可用,而 WaveMultiPrefixSum 在着色器模型 6.5 中可用。

ddx 函数的可用性取决于着色器阶段。它在着色器模型 2.1 及更高版本的像素着色器中可用,在着色器模型 6.6 及更高版本的计算、网格和放大着色器中可用。对于任何其他着色器阶段,它都不可用。

计算着色器示例

float unusedFunction(float f) {
  return ddx(f);
}

[numthreads(4, 4, 1)]
void main(uint3 threadId : SV_DispatchThreadId) {
  float f1 = ddx(threadId.x);
  float f2 = WaveActiveCountBits(threadId.y == 1.0);
}

当作为着色器模型版本 5.0 的计算着色器编译时,Clang 将默认发出以下错误

<>:7:13: error: 'ddx' is only available in compute shader environment on Shader Model 6.6 or newer
<>:8:13: error: 'WaveActiveCountBits' is only available on Shader Model 6.5 or newer

在放松诊断模式下,这些错误将变为警告。

在严格诊断模式下,除了上面的两个错误之外,Clang 还会在 unusedFunction 中针对 ddx 调用发出错误。

<>:2:9: error: 'ddx' is only available in compute shader environment on Shader Model 6.5 or newer
<>:7:13: error: 'ddx' is only available in compute shader environment on Shader Model 6.5 or newer
<>:7:13: error: 'WaveActiveCountBits' is only available on Shader Model 6.5 or newer

着色器库示例

float myFunction(float f) {
  return ddx(f);
}

float unusedFunction(float f) {
  return WaveMultiPrefixSum(f, 1.0);
}

[shader("compute")]
[numthreads(4, 4, 1)]
void main(uint3 threadId : SV_DispatchThreadId) {
   float f = 3;
   float e = myFunction(f);
}

[shader("pixel")]
void main() {
   float f = 3;
   float e = myFunction(f);
}

当作为着色器模型版本 6.4 的着色器库编译时,Clang 将默认发出以下错误

<>:2:9: error: 'ddx' is only available in compute shader environment on Shader Model 6.5 or newer

在放松诊断模式下,这些错误将变为警告。

在严格诊断模式下,Clang 还会针对任何入口点都不使用的代码中的可用性问题发出错误

<>2:9: error: 'ddx' is only available in compute shader environment on Shader Model 6.6 or newer
<>:6:9: error: 'WaveActiveCountBits' is only available on Shader Model 6.5 or newer

请注意,myFunction 可从像素和计算着色器入口点访问,因此被扫描两次 - 每个上下文一次。诊断仅针对计算着色器上下文发出。