WGSL 函数参考(WGSL Function Reference)
原作者:webgpufundamentals Contributors
原始链接:https://webgpufundamentals.org/webgpu/lessons/webgpu-wgsl-function-reference.html
导言
本文对 W3C 官方 WGSL 函数参考进行深度解读,帮助 WebGPU 开发者在编写着色器时快速查阅和理解内置函数。与 GLSL 相比,WGSL 的函数体系更加严格和模块化,尤其强调类型安全、内存访问修饰符以及未定义行为的明确界定。本文将按类别梳理核心函数,保留关键代码签名,并提炼出三个与性能优化密切相关的要点。
一、函数分类与核心代码
WGSL 内置函数共分为以下几大类,每类中选取高频使用的函数进行解读。
1. 比特位重解释(Bit Reinterpretation)
用于在不同数值类型之间重新解释底层二进制位,常见于打包数据、浮点位操作等场景。
fn bitcast<T>(e : T) -> T // 恒等变换(无类型变化)
fn bitcast<T>(e : S) -> T // S 与 T 为 i32/u32/f32 之一,且不同
fn bitcast<vecN<T>>(e : vecN<S>) -> vecN<T> // 向量逐分量重解释
优化提示:bitcast 通常不会被编译为真实指令,但滥用可能影响着色器可移植性,应确保目标类型位数匹配。
2. 逻辑与数组函数
fn all(e: vecN<bool>) -> bool
fn any(e: vecN<bool>) -> bool
fn select(f: T, t: T, cond: bool) -> T // 三元选择
fn select(f: vecN<T>, t: vecN<T>, cond: vecN<bool>) -> vecN<T>
fn arrayLength(p: ptr<storage, array<E>, AM>) -> u32
select 对应 GLSL 中的 mix 或条件运算符,但 WGSL 要求条件类型严格匹配。
3. 数值函数(Numeric Built-in Functions)
包含数学运算、位操作、插值等大量函数。以下仅列出最具代表性且与性能相关的几个:
fn clamp(e: T, low: T, high: T) -> T
fn mix(e1: T, e2: T, e3: T) -> T // 线性混合
fn smoothstep(edge0: T, edge1: T, x: T) -> T
fn dot(e1: vecN<T>, e2: vecN<T>) -> T
fn cross(e1: vec3<T>, e2: vec3<T>) -> vec3<T>
fn length(e: T) -> S
fn normalize(e: vecN<T>) -> vecN<T>
fn pow(e1: T, e2: T) -> T
fn exp2(e: T) -> T
fn log2(e: T) -> T
fn abs(e: T) -> T
fn floor(e: T) -> T
fn ceil(e: T) -> T
fn trunc(e: T) -> T
fn fract(e: T) -> T
fn frexp(e: T) -> __frexp_result_f32
fn ldexp(e1: T, e2: I) -> T
fn extractBits(e: T, offset: u32, count: u32) -> T
fn insertBits(e: T, newbits: T, offset: u32, count: u32) -> T
fn countLeadingZeros(e: T) -> T
fn countOneBits(e: T) -> T
fn reverseBits(e: T) -> T
注意:pow 的底数为负数时结果未定义,这在跨平台开发中是常见的坑。
4. 导数函数(Derivative Built-in Functions)
仅在片元着色阶段可用,用于计算屏幕空间导数,常用于纹理过滤和边缘检测。
fn dpdx(e: T) -> T
fn dpdy(e: T) -> T
fn fwidth(e: T) -> T
fn dpdxCoarse(e: T) -> T
fn dpdyFine(e: T) -> T
术语对照:这些函数与 GLSL 中的 dFdx、dFdy、fwidth 直接对应。
5. 纹理函数(Texture Built-in Functions)
纹理操作是 WebGPU 着色器的核心,函数众多,按功能分为查询、采样、加载、存储四类。
查询类
fn textureDimensions(t: T) -> u32 / vec2<u32> / vec3<u32>
fn textureNumLayers(t: T) -> u32
fn textureNumLevels(t: T) -> u32
fn textureNumSamples(t: T) -> u32
采样类
fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>) -> vec4<f32>
fn textureSampleBias(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, bias: f32) -> vec4<f32>
fn textureSampleLevel(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, level: f32) -> vec4<f32>
fn textureSampleGrad(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32>
fn textureSampleCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32
fn textureGather(t: texture_2d<ST>, s: sampler, coords: vec2<f32>) -> vec4<ST>
fn textureGatherCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> vec4<f32>
加载与存储类
fn textureLoad(t: texture_2d<ST>, coords: vec2<C>, level: L) -> vec4<ST>
fn textureStore(t: texture_storage_2d<F, AM>, coords: vec2<C>, value: vec4<CF>)
关键区别:textureSample 会自动计算导数,因此不能在非均匀控制流中使用;而 textureSampleLevel 和 textureLoad 则没有此限制。
6. 原子操作(Atomic Built-in Functions)
用于内存共享场景,需在 storage 或 workgroup 地址空间中配合 atomic< T > 类型使用。
fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>
7. 数据打包与解包(Pack / Unpack)
用于高效利用带宽,将多个小数据类型压缩进 32 位整数。
fn pack4x8snorm(e: vec4<f32>) -> u32
fn pack4x8unorm(e: vec4<f32>) -> u32
fn pack2x16float(e: vec2<f32>) -> u32
fn unpack4x8snorm(e: u32) -> vec4<f32>
fn unpack4x8unorm(e: u32) -> vec4<f32>
fn unpack2x16float(e: u32) -> vec2<f32>
这类函数在优化顶点数据存储、纹理数据压缩时非常有用。
8. 同步与子组(Synchronization & Subgroup)
用于 workgroup 内协作和高级并行模式。
fn workgroupBarrier()
fn storageBarrier()
fn textureBarrier()
fn workgroupUniformLoad(p: ptr<workgroup, T>) -> T
fn subgroupAdd(e: T) -> T
fn subgroupBroadcast(e: T, id: I) -> T
fn subgroupShuffle(e: T, id: I) -> T
fn subgroupBallot(pred: bool) -> vec4<u32>
9. 四边形操作(Quad Operations)
与片元着色器中的 2x2 像元块相关,可实现如纹理 LOD 计算等操作。
fn quadBroadcast(e: T, id: I) -> T
fn quadSwapX(e: T) -> T
fn quadSwapY(e: T) -> T
fn quadSwapDiagonal(e: T) -> T
二、三个核心优化要点
从以上函数参考中可以提炼出以下三个与 WebGPU 性能密切相关的优化建议:
1. 避免未定义行为,提升跨设备稳定性
WGSL 明确列出了许多函数的未定义输入范围,例如 pow(x,y) 当 x<0 或 x==0 && y<=0,log(x) 当 x<=0,smoothstep(edge0, edge1, x) 当 edge0>=edge1 等。这些未定义行为在不同硬件上可能产生截然不同的结果,导致着色器在用户设备上出现难以复现的渲染错误。优化要点:在着色器代码中主动钳制输入值,确保始终位于合法区间,这不仅能避免视觉瑕疵,还能减少因驱动差异导致的性能波动。
2. 选择合适的纹理采样函数
textureSample 会隐式计算屏幕空间导数,因此它在非均匀控制流(如分支内)中无法使用,且会触发 uniform 性检查。相比之下:
textureSampleLevel使用显式 LOD,无导数计算,可用于任意着色阶段,适合在计算着色器中采样纹理。textureLoad直接读取单个纹素,不做任何过滤,是最快且最可控的纹理访问方式。textureSampleGrad允许手动提供导数,在需要自定义过滤或实现高级效果时非常有用。 优化要点:在计算着色器或不需要过滤的场合,优先使用textureLoad或textureSampleLevel,避免不必要的导数计算和依赖。
3. 利用数据打包和子组操作减少内存带宽
pack4x8unorm、pack2x16float 等函数可以将多个数据分量压缩进单个 32 位值,从而显著减少缓冲区或纹理的存储和传输带宽。同时,subgroup 系列操作(如 subgroupAdd、subgroupBroadcast)允许在 GPU 的子组内快速进行归约和广播,比使用 workgroup 共享内存的 workgroupBarrier 方案通常更高效。优化要点:在顶点数据、粒子属性、颜色等场景中积极使用打包数据类型,并在并行归约、前缀和等算法中优先考虑子组操作,以减少全局内存访问和同步开销。
三、GLSL / WebGL 专业术语检查
尽管本文是 WGSL 函数参考,但许多概念与 GLSL 有对应关系。以下是常见术语对照,帮助有 GLSL 背景的开发者快速迁移:
| WGSL 术语 | GLSL 对应 | 说明 |
|---|---|---|
bitcast< T >(e) | intBitsToFloat / floatBitsToInt / uintBitsToFloat | 类型比特位重解释 |
select(f, t, cond) | mix(f, t, cond) 或 cond ? t : f | 条件选择 |
textureSample | texture | 带导数纹理采样 |
textureSampleLevel | textureLod | 显式 LOD 纹理采样 |
textureLoad | texelFetch | 直接读取纹素 |
textureGather | textureGather | 收集四个纹素分量 |
dpdx / dpdy | dFdx / dFdy | 片元着色器导数 |
fwidth | fwidth | 导数绝对值之和 |
subgroupAdd / subgroupBroadcast | GLSL 的 subgroupAdd / subgroupBroadcast(需扩展) | 子组操作 |
workgroupBarrier | barrier() | 同步屏障,但作用域和内存模型更明确 |
atomicAdd / atomicMax | atomicAdd / atomicMax(针对 atomic_uint 等) | 原子操作函数 |
pack4x8unorm | packUnorm4x8 | 数据打包 |
unpack4x8unorm | unpackUnorm4x8 | 数据解包 |
注意事项:
- WGSL 使用指针参数
ptr<AS, atomic<T>, read_write>进行原子操作,而 GLSL 通过特殊类型atomic_uint和布局限定符实现。 - WGSL 的内存同步函数区分
storageBarrier、workgroupBarrier和textureBarrier,比 GLSL 的单一barrier()更精确,有助于编译优化。 - WGSL 中的向量类型为
vecN<T>(如vec3<f32>),而 GLSL 使用vec3等简写形式,两者在语法上略有差异。
结语
WGSL 函数参考是 WebGPU 开发必不可少的查阅文档。通过理解各类内置函数的适用场景和未定义行为,并合理选择采样、打包和并行操作,可以显著提升着色器的性能与可移植性。希望本深度解读能帮助你在实际项目中更高效地使用 WGSL。
