WGSL 特性
WGSL 强制执行一些 GLSL 中没有的规则。本页介绍为 WebGPU 编写自定义着色器或代码块时最常遇到的规则。
受设备能力限制的可选 WGSL 功能,例如半精度类型和语言扩展,参见 WGSL 能力。引擎要求的属性、varying 和输出约定,参见 WGSL 顶点和片元着色器。
导数与一致控制流
GPU 以称为 quad 的 2x2 像素块执行片元着色器,通过相邻调用之间的差值计算导数。只有块内每次调用都同时到达着色器的相同位置才有效,这称为一致控制流。如果部分调用进入其他分支,就没有相邻值可供求差,结果也没有意义。
因此,在创建着色器时,WGSL 会拒绝任何无法证明在一致控制流中执行的、依赖导数的内置函数调用:
- 导数内置函数:
dpdx、dpdy、fwidth及其Coarse/Fine变体。 textureSampletextureSampleBiastextureSampleCompare
当控制流依赖 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 视为错误。引擎自身代码块通过显式采样层级满足规则,而非抑制诊断,从而保持可移植性。