跳到主要内容

WGSL 特性

WGSL 强制执行一些 GLSL 中没有的规则。本页介绍为 WebGPU 编写自定义着色器或代码块时最常遇到的规则。

受设备能力限制的可选 WGSL 功能,例如半精度类型和语言扩展,参见 WGSL 能力。引擎要求的属性、varying 和输出约定,参见 WGSL 顶点和片元着色器。

导数与一致控制流​

GPU 以称为 quad 的 2x2 像素块执行片元着色器,通过相邻调用之间的差值计算导数。只有块内每次调用都同时到达着色器的相同位置才有效,这称为一致控制流。如果部分调用进入其他分支,就没有相邻值可供求差,结果也没有意义。

因此,在创建着色器时,WGSL 会拒绝任何无法证明在一致控制流中执行的、依赖导数的内置函数调用:

  • 导数内置函数:dpdx、dpdy、fwidth 及其 Coarse / Fine 变体。
  • textureSample
  • textureSampleBias
  • textureSampleCompare

当控制流依赖 WGSL 无法证明在所有调用间相同的值时,就不一致,例如插值 varying、position 等内置值、纹理或存储缓冲区读数,或可变的模块作用域变量。根据 uniform 缓冲区值分支是安全的,#if / #ifdef 也安全,因为预处理器在编译前已处理它们。

错误类似如下:

error: 'textureSample' must only be called from uniform control flow
note: control flow depends on possibly non-uniform value
note: user-defined input 'uv' of 'fragmentMain' may be non-uniform

诊断附注比错误本身更有帮助:它会指出导致控制流不一致的值,通常是最快找到修复方法的线索。

修复方法​

**1. 重构以让调用处于一致控制流。**通常开销最低。将采样移到分支外,或移除导致 quad 分离的提前退出:

// rejected: the sample is only reached by some invocations
if (mask > 0.5) {
color = textureSample(colorMap, colorMapSampler, uv);
}

// accepted: every invocation samples, the branch only selects
let sampled = textureSample(colorMap, colorMapSampler, uv);
color = select(color, sampled, mask > 0.5);

单独 discard 没问题,因为之后控制流会重新汇合;加入 return 则不同,后续代码只会被部分调用执行:

// accepted: no early return, so later sampling is still uniform
if (alpha < threshold) {
discard;
}

// rejected: everything after this is non-uniform
if (alpha < threshold) {
discard;
return output;
}

2. 显式指定层级或梯度采样。textureSampleLevel、textureSampleGrad 和 textureSampleCompareLevel 不需要隐式导数,因此可在任何位置使用。引擎代码块也这样处理:阴影采样、cookie 图集、光照 LUT 和后处理都指定明确层级,因为这些纹理本来就没有可选择的 mipmap。

// legal in any control flow
color = textureSampleLevel(colorMap, colorMapSampler, uv, 0.0);

确实需要 mip 层级时,可在一致控制流中计算一次并传入;沿纹理步进的循环通常本来就需要固定层级:

let sizeInTexels = vec2f(textureDimensions(heightMap, 0));
let uvTexels = uv * sizeInTexels;
let dx = dpdx(uvTexels);
let dy = dpdy(uvTexels);
let lod = max(0.0, 0.5 * log2(max(dot(dx, dx), dot(dy, dy))));

for (var i = 0.0; i < steps; i += 1.0) {
let h = textureSampleLevel(heightMap, heightMapSampler, marchUv, lod).x;
// ...
}

**3. 抑制诊断。**仅作为最后手段,见下文。

derivative_uniformity 诊断​

derivative_uniformity 是两个可过滤的 WGSL 诊断规则之一,默认严重性为 error。诊断过滤器可改变严重性,使上述代码能够编译。

注意

不同浏览器的过滤器支持差异很大,属性形式在部分浏览器中会导致着色器创建错误。目前只有模块作用域指令可移植,使用前先阅读浏览器支持。

过滤器可通过指令应用于整个模块,也可通过属性应用于函数、语句或块:

// module scope - portable. Must precede every declaration in the module.
diagnostic(off, derivative_uniformity);

// function scope - rejected by Safari
@diagnostic(off, derivative_uniformity)
fn readHeight(uv: vec2f) -> f32 {
return textureSample(heightMap, heightMapSampler, uv).x;
}

// statement scope - rejected by Safari and Firefox
@diagnostic(off, derivative_uniformity) if (mask > 0.5) {
color = textureSample(colorMap, colorMapSampler, uv);
}

抑制诊断不会让导数变正确。WGSL 规范规定,不一致控制流中的导数内置函数返回“不确定值”,即由实现任意选择的值,对浮点类型可能是 NaN。它不会破坏内存或导致设备丢失,但 NaN 的 mip 层级或法线可能传播到帧缓冲区,产生随 GPU 而异的伪影。应优先重构着色器或指定明确采样层级。

浏览器支持​

以下于 2026 年 9 月,在 macOS / Apple GPU 上使用 Chrome 148、Firefox 154 和 Safari 26.6.2 实测:

Chrome(Dawn)Firefox(naga)Safari(WebKit)
强制执行 derivative_uniformity是,作为错误否否
模块作用域 diagnostic(…) 指令生效解析但无作用解析但无作用
函数上的 @diagnostic(…)生效解析但无作用着色器创建错误
语句或块上的 @diagnostic(…)生效着色器创建错误着色器创建错误
拒绝冲突指令是是否
无法识别的规则名警告,原规则仍生效静默接受静默接受

应考虑两个结果:

  • **目前只有 Chrome 强制执行此规则。**在不一致控制流中采样的着色器,在 Firefox 和 Safari 编译成功,却只在 Chrome 失败;若想尽早发现问题,应以 Chrome 开发验证。按最严格实现编写,因为规范要求报错,其他浏览器随时可能开始执行该规则。
  • **只有模块作用域指令可移植。**Firefox 直接拒绝语句作用域属性(@diagnostic(…) attribute(s) not yet implemented),Safari 拒绝语句和函数形式(invalid attribute for function declaration)。因此,即使函数级属性恰好符合规范建议的最小作用域原则,单个 @diagnostic 也足以让着色器在 Safari 上失效。

作用域规则​

以下规则描述唯一实际执行过滤器的 Chrome。它们仍很重要,因为你会在 Chrome 上看到错误。

过滤器必须覆盖内置函数调用位置,而非引起控制流不一致的分支位置,也不会传递给被调用函数。这是最常见的误区:

// does NOT work - the attribute is on the entry point, but the
// textureSample call is inside readHeight
@diagnostic(off, derivative_uniformity)
@fragment
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
if (input.mask > 0.5) {
h = readHeight(uv); // still an error, reported inside readHeight
}
// ...
}

// works - the attribute is on the function that contains the call
@diagnostic(off, derivative_uniformity)
fn readHeight(uv: vec2f) -> f32 {
return textureSample(heightMap, heightMapSampler, uv).x;
}

对于位于辅助函数内、该函数从不一致分支调用的 textureSample,结果如下:

属性放置位置Chrome 中的效果
包含采样调用的辅助函数上抑制诊断
仅入口函数上仍报错
调用辅助函数的 if 语句上仍报错
调用辅助函数的代码块上仍报错
辅助函数上,但采样调用还在更深一层仍报错

语句作用域属性可抑制直接写在其中的 textureSample,if、for 或普通块都可以。但 Firefox 和 Safari 均拒绝这种形式,因此不能用于发布的跨平台着色器。

其他行为​

  • **位置。**模块作用域 diagnostic(…) 必须位于所有模块声明之前,与 enable、requires 指令放在一起。指令之间顺序不限,三种浏览器都如此。引擎会将自己的 enable / requires 行置于源码之前,因此你的指令应放在代码块最顶部,早于任何 const、struct、var 或 fn。
  • **严重性。**可选 off、info、warning、error。Chrome 中,info 和 warning 允许编译,同时通过 getCompilationInfo() 报告,适合审计。其他两种浏览器不报告它们。
  • **重新启用仅适用于 Chrome。**在模块关闭规则后,更小作用域的 @diagnostic(error, derivative_uniformity) 可恢复错误,让大部分代码保留检查、只豁免一个函数。但这依赖函数属性形式,无法跨平台使用。
  • **冲突会导致失败。**同一模块中两条指令为同一规则指定不同严重性,在 Chrome 和 Firefox 中会导致创建错误。重复相同严重性在所有浏览器中都允许。将多个代码块组合时尤其需注意。
  • **只有 Chrome 明确报告拼写错误。**无法识别的规则名会产生警告并列出合法规则,原错误仍会触发。Firefox 和 Safari 静默接受拼错的规则名。
备注

PlayCanvas 不会向生成的 WGSL 注入任何 diagnostic(…),因此 Chrome 中所有着色器默认将 derivative_uniformity 视为错误。引擎自身代码块通过显式采样层级满足规则,而非抑制诊断,从而保持可移植性。