1. 简介
WebGPU 着色语言(WGSL)是 [WebGPU] 的着色器语言。 也就是说,使用 WebGPU API 的应用程序会用 WGSL 来编写在 GPU 上运行的程序(称为着色器)。
// 一个使用点光源对贴图几何体进行光照的片元着色器。 // 来自存储缓冲区绑定的光源。 struct PointLight { position: vec3f, color : vec3f, } struct LightStorage { pointCount : u32, point : array< PointLight > , } @group ( 0 ) @binding ( 0 ) var < storage> lights : LightStorage ; // 纹理和采样器。 @group ( 1 ) @binding ( 0 ) var baseColorSampler : sampler; @group ( 1 ) @binding ( 1 ) var baseColorTexture : texture_2d< f32> ; // 函数参数为顶点着色器传来的值。 @fragment fn fragmentMain ( @location ( 0 ) worldPos : vec3f, @location ( 1 ) normal : vec3f, @location ( 2 ) uv : vec2f) -> @location ( 0 ) vec4f{ // 从纹理中采样表面基础颜色。 let baseColor = textureSample ( baseColorTexture , baseColorSampler , uv ); let N = normalize ( normal ); var surfaceColor = vec3f( 0 ); // 遍历场景中的点光源。 for ( var i = 0u ; i < lights . pointCount ; i ++ ) { let worldToLight = lights . point [ i ]. position- worldPos ; let dist = length ( worldToLight ); let dir = normalize ( worldToLight ); // 计算该光源对表面颜色的贡献。 let radiance = lights . point [ i ]. color * ( 1 / pow ( dist , 2 )); let nDotL = max ( dot ( N , dir ), 0 ); // 累加光照对表面颜色的贡献。 surfaceColor += baseColor . rgb * radiance * nDotL ; } // 返回累加后的表面颜色。 return vec4( surfaceColor , baseColor . a ); }
1.1. 概述
WebGPU 以 GPU 命令 的形式向 GPU 下发一项工作任务。 WGSL 关注两种类型的 GPU 命令:
两种管线都使用 WGSL 编写的着色器。
着色器 是 WGSL 程序中在管线中执行 着色器阶段 的部分。 一个着色器包括:
-
从入口点出发,所有被调用函数的可传递闭包集合。 该集合包含 用户自定义 和 内建 函数。 (更严谨的定义见“着色器阶段中的函数”。)
-
所有这些函数静态访问的变量和常量的集合。
-
用于定义或分析上述函数、变量与常量的类型集合。
注意: WGSL 程序不要求有 入口点;但此类程序无法被 API 执行,因为创建
GPUProgrammableStage
时需要入口点。
执行着色器阶段时,具体实现会:
-
计算在 模块作用域 声明的常量的值。
-
为其他 模块作用域 变量分配内存,并用指定初始值填充该内存。
-
如有入口点形式参数,则用着色器阶段的输入填充。
-
如有,连接入口点 返回值 到着色器阶段输出。
-
随后调用入口点。
一个 WGSL 程序结构包括:
-
指令,指定模块级行为控制。
-
函数,指定执行行为。
-
语句,声明或可执行行为单元。
-
字面量,用于纯数学值的文本表示。
-
常量,为某一时刻计算的值提供名称。
-
变量,为存储值的内存提供名称。
-
表达式,组合一组值以产生结果值。
-
类型,描述:
-
值的集合。
-
对支持表达式的约束。
-
这些表达式的语义。
-
-
属性,用于修改对象以指定额外信息,例如:
注意: WGSL 程序目前由单个 WGSL 模块 组成。
WGSL 是命令式语言:行为通过要执行的语句序列来指定。 语句可以:
-
修改变量内容。
-
用结构化编程结构修改执行顺序:
-
计算表达式以作为上述行为的一部分获得值。
WGSL 是静态类型语言:特定表达式计算的每个值都属于特定类型,并且仅通过检查程序源码即可确定。
WGSL 有描述 布尔 和数值 (整数 和 浮点数)的类型。 这些类型可以聚合为 复合类型 (向量、矩阵、 数组 和 结构体)。 WGSL 还有特殊类型(例如 原子类型)提供独特操作。 WGSL 以 内存视图 形式描述可以存储在 内存 中的类型。 WGSL 以 纹理和采样器 形式提供常用渲染类型。 这些类型有相应的 内建函数 用于暴露 GPU 硬件通常提供的图形渲染能力。
WGSL 没有从具体 类型的隐式转换或提升,但允许从 抽象类型隐式转换和提升。 从一种具体数值或布尔类型 转换为另一种,需要显式转换、 值构造器, 或位重解释; 不过,WGSL 提供有限的从标量 类型提升到向量类型的能力。 这同样适用于复合类型。
着色器阶段的工作被划分为一个或多个 调用(invocation), 每个调用都会执行入口点,但条件略有不同。 同一着色器阶段的调用共享对某些变量的访问:
但各调用针对不同的着色器阶段输入(含提供唯一标识值的内建输入)进行操作。 每个调用都拥有独立的内存空间,即 private 和 function 地址空间中的变量。
同一着色器阶段内的调用并发执行,常常是并行运行的。 着色器作者需确保该阶段调用的动态行为:
-
满足某些原语操作(如纹理采样、控制同步)的一致性要求。
-
协调对共享变量的潜在冲突访问,避免数据竞争。
WGSL 某些特性允许多种行为实现。 这存在可移植性隐患,因为不同实现可能表现出不同行为。 WGSL 设计旨在最小化这种情况,但会受到可行性和跨设备高性能目标的约束。
行为要求 是实现处理或执行 WGSL 程序时将执行的动作。它们描述了 实现与程序员之间契约中的义务。 当义务不够明显时,规范会明确说明这些义务。
1.2. 语法表示法
下列语法表示法描述了 WGSL 句法语法的约定:
-
规则两侧的斜体文本表示语法规则。
-
规则右侧以单引号(')包裹的粗体等宽文本表示关键字和标记。
-
常规文本中的冒号(:)用于定义语法规则。
-
常规文本中的竖线(|)表示可选项。
-
常规文本中的问号(?)表示前面的关键字、标记、规则或分组出现零次或一次(即可选)。
-
常规文本中的星号(*)表示前面的关键字、标记、规则或分组出现零次或多次。
-
常规文本中的加号(+)表示前面的关键字、标记、规则或分组出现一次或多次。
-
常规文本中配对的左括号(()和右括号())表示元素分组。
1.3. 数学术语与记号
角度:
-
惯例上,角度以弧度为单位。
-
用于测量角度的参考射线是从原点 (0,0) 指向 (+∞,0) 的射线。
-
设 θ 为比较射线与参考射线夹的角度。随着比较射线逆时针旋转,θ 增大。
-
一个完整圆周为 2π 弧度。
-
示例:
-
角度 0 从原点向右指向 (1,0)
-
角度 2π 从原点向右指向 (1,0)
-
角度 π/4 从原点指向 (1,1)
-
角度 π/2 从原点指向 (0,1)
-
角度 π 从原点指向 (-1,0)
-
角度 (3/2)π 从原点指向 (0,-1)
-
双曲角是一个无量纲面积,不是传统意义上的角度。具体如下:
-
考虑双曲线 x2 - y2 = 1,x > 0。
-
设 R 为从原点指向双曲线上某点 (x, y) 的射线。
-
设 a 为由 R、x 轴和双曲线自身围成的面积的两倍。
-
当 R 在 x 轴上方时 a 为正,下方为负。
则面积 a 是一个双曲角,使得 x 是 a 的双曲余弦,y 是 a 的双曲正弦。
正无穷,记为 +∞,是严格大于所有实数的唯一值。
负无穷,记为 −∞,是严格小于所有实数的唯一值。
扩展实数(也称仿射扩展实数)是实数集与 +∞ 和 −∞ 的并集。 计算机用浮点类型近似表示扩展实数,包括两个无穷值。 参见§ 15.7 浮点数运算。
区间是有上下界的连续数集。根据上下文,可以是整数、浮点数、实数或扩展实数。
-
闭区间 [a,b] 是满足 a ≤ x ≤ b 的所有 x 的集合。
-
半开区间 [a,b) 是满足 a ≤ x < b 的所有 x 的集合。
-
半开区间 (a,b] 是满足 a < x ≤ b 的所有 x 的集合。
向下取整表达式对扩展实数 x 定义如下:
-
⌊ +∞ ⌋ = +∞
-
⌊ −∞ ⌋ = −∞
-
对实数 x,有 ⌊x⌋ = k,其中 k 是唯一满足 k ≤ x < k+1 的整数
向上取整表达式对扩展实数 x 定义如下:
-
⌈ +∞ ⌉ = +∞
-
⌈ −∞ ⌉ = −∞
-
对实数 x,有 ⌈x⌉ = k,其中 k 是唯一满足 k-1 < x ≤ k 的整数
截断函数对扩展实数 x 定义如下:
-
truncate(+∞) = +∞
-
truncate(−∞) = −∞
-
对实数 x,计算绝对值不超过 x 的最近整数:
-
若 x ≥ 0,truncate(x) = ⌊x⌋,若 x < 0,truncate(x) = ⌈x⌉。
-
roundUp 函数对正整数 k 和 n 定义为:
-
roundUp(k, n) = ⌈n ÷ k⌉ × k
转置 一个 c 列 r 行矩阵 A 的转置是 r 列 c 行矩阵 AT,由 A 的各行作为 AT 的各列:
-
transpose(A) = AT
-
transpose(A)i,j = Aj,i
列向量的转置可看作 1 行矩阵的转置,行向量的转置可看作 1 列矩阵的转置。
2. WGSL 模块
一个 WGSL 程序由一个 WGSL 模块组成。
模块是由可选的 指令,后跟 模块作用域 的 声明 和 断言 组成的序列。 一个模块结构包括:
-
指令,用于指定模块级行为控制。
-
函数,用于定义执行行为。
-
语句,声明或可执行行为单元。
-
字面量,纯数学值的文本表示。
-
变量,为存储值的内存提供名称。
-
常量,为某一时刻计算的值提供名称。
-
表达式,组合一组值以产生结果值。
-
类型,描述:
-
值的集合。
-
对支持表达式的约束。
-
这些表达式的语义。
-
-
属性,用于修改对象以指定额外信息,例如:
global_directive * ( global_decl | global_assert | ';' ) *
2.1. 着色器生命周期
WGSL 程序及其包含的着色器生命周期中有四个关键事件。 前两个对应于 WebGPU API 用于准备 WGSL 程序执行的方法。 后两个是着色器执行的开始和结束。
这些事件包括:
-
着色器模块创建
-
当调用 WebGPU
createShaderModule()方法时发生。 WGSL 程序的源文本在此时提供。
-
-
管线创建
-
当调用 WebGPU
createComputePipeline()方法 或 WebGPUcreateRenderPipeline()方法时发生。 这些方法会使用一个或多个之前创建的着色器模块,以及其它配置信息。 -
只有组成指定 入口点 的
GPUProgrammableStage的 着色器 代码会在管线创建期间被考虑。 即,与入口点无关的代码在编译前会被有效剔除。 -
注意: 每个 着色器阶段 都被认为单独编译,因此可能包含模块的不同部分。
-
-
着色器执行开始
-
当向 GPU 下发 draw 或 dispatch command,开始执行管线并调用 着色器阶段 的 入口点函数时发生。
-
-
着色器执行结束
事件顺序由以下原因决定:
-
数据依赖:着色器执行需要管线,管线需要着色器模块。
-
因果关系:着色器必须先开始执行,才能结束执行。
2.2. 错误
WebGPU 实现处理着色器失败有两种原因:
-
如果着色器不满足 WGSL 或 WebGPU 规范要求,则发生 程序错误。
-
即使所有 WGSL 和 WebGPU 要求都满足,仍然可能出现 未分类错误。 可能的原因包括:
-
着色器过于复杂,超出实现能力,但这种超出不易用规定的 限制 捕获。 简化着色器可能能规避问题。
-
WebGPU 实现存在缺陷。
-
着色器生命周期的三个阶段中可能出现处理错误:
-
着色器创建错误 指在 着色器模块创建 时可行检测到的错误。 检测仅依赖 WGSL 模块源文本及
createShaderModuleAPI 方法可用的其它信息。 本规范中要求程序 必须 做某事的描述,若被违反,通常会导致着色器创建错误。 -
管线创建错误 指在 管线创建 时可检测到的错误。 检测依赖 WGSL 模块源文本和具体管线创建 API 方法可用的其它信息。 这些错误只针对为
GPUProgrammableStage编译的 入口点 的 着色器 中出现的代码触发。 -
动态错误 指着色器执行期间发生的错误。 这些错误可能可检测,也可能不可检测。
注意: 例如,数据竞争可能无法检测到。
每项 行为要求 都会在最早的机会被检查。 即:
-
着色器创建时可检测的要求不满足时,会导致着色器创建错误。
-
管线创建时可检测、但之前无法检测的要求不满足时,会导致管线创建错误。
如上下文不明确,本规范会指明 未满足特定要求时,是着色器创建错误、管线创建错误还是动态错误。
错误的后果如下:
-
如果发生 动态错误:
2.3. 诊断
在着色器模块创建或管线创建期间,实现可以生成诊断。 诊断是实现为应用开发者产生的信息。
当满足特定条件时会创建或触发诊断, 该条件被称为触发规则。 在源文本中满足条件的位置(以点或区间表示)被称为 触发位置。
诊断具有以下属性:
诊断的严重级别有如下几种,由高到低:
- 错误(error)
- 警告(warning)
-
诊断描述了值得开发者关注的异常,但不是错误。
- 信息(info)
-
诊断描述了值得开发者关注的特殊情况,但不是错误或警告。
- 关闭(off)
-
诊断被禁用,不会传递给应用。
触发规则的名称可以是:
-
两个diagnostic_name_token,用句点
'.'(U+002E)分隔。
2.3.1. 诊断处理
已触发的诊断将按如下方式处理:
-
对于每个诊断 D,查找包含 D 的触发位置、且具有相同触发规则的最小影响范围的诊断过滤器。
-
如果存在过滤器,则应用于 D,更新 D 的严重级别。
-
否则 D 保持不变。
-
-
丢弃严重级别为off的诊断。
-
如果至少有一个剩余诊断 DI 严重级别为info,则:
-
具有相同触发规则的其他info诊断可被丢弃,仅保留原始诊断 DI。
-
-
如果至少有一个剩余诊断 DW 严重级别为warning,则:
-
如果至少有一个剩余诊断严重级别为error,则:
-
如在着色器模块创建期间处理,则剩余诊断填充 WebGPU
GPUCompilationInfo对象的messages成员。 -
如在管线创建期间处理,error诊断会导致校验
GPUProgrammableStage时 WebGPU 校验失败。
注意: 这些规则允许实现一旦检测到错误就停止处理 WGSL 模块。 此外,某类警告分析可在发现第一个警告时停止,某类 info 诊断分析可在第一次出现时停止。 WGSL 未规定不同类型分析的执行顺序,或单一分析内的顺序。 因此,对同一个 WGSL 模块,不同实现可能报告相同严重级别的不同诊断实例。
2.3.2. 可过滤触发规则
大多数诊断会无条件报告给 WebGPU 应用。 某些诊断类型可通过命名其触发规则等方式过滤。 下表列出了可过滤的标准触发规则集。
| 可过滤触发规则 | 默认严重级别 | 触发位置 | 描述 |
|---|---|---|---|
| derivative_uniformity | error | 计算导数的内建函数的调用点位置。 即对下列函数的调用位置: |
调用内建函数计算导数,但一致性分析无法证明调用发生在一致控制流中。
参见§ 15.2 一致性。 |
| subgroup_uniformity | error | subgroup 或 quad 内建函数的调用点位置。 |
调用 subgroup 或 quad 内建函数,但一致性分析无法证明调用发生在一致控制流中。
另外,当一致性分析无法证明如下参数值一致时:
参见§ 15.2 一致性。 |
使用单个diagnostic name-token组成的未识别触发规则时,应该触发用户代理的警告。
实现可以支持此处未指定的触发规则,只要它们按diagnostic_rule_name的多 token 形式拼写。 使用多 token 形式拼写的未识别触发规则本身可能触发诊断。
本规范的未来版本可能移除某一规则或降低其默认严重级别(即将当前默认替换为更低的级别),仍视为向后兼容。
例如,WGSL 的未来版本可能将derivative_uniformity的默认严重级别从error改为warning或info。
修改后,原先有效的程序仍将保持有效。
2.3.3. 诊断过滤
当带有可过滤触发规则的诊断被触发后,WGSL 提供机制来丢弃诊断或修改其严重级别。
诊断过滤器 DF 有三个参数:
对诊断 D 应用诊断过滤器 DF(AR,NS,TR) 具有如下效果:
区间诊断过滤器是诊断过滤器,其影响范围为指定的源文本区间。
区间诊断过滤器以 @diagnostic 属性形式出现在受影响的源区间起始处,具体见下表。
@diagnostic 属性不得出现在其他地方。
| 放置位置 | 影响范围 |
|---|---|
| 复合语句的开始位置。 | 该复合语句。 |
| 函数声明的开始位置。 | 该函数声明。 |
| if 语句的开始位置。 | 该 if 语句:if_clause 及所有相关的 else_if_clause 和 else_clause,包括所有控制条件表达式。 |
| switch 语句的开始位置。 | 该 switch 语句:选择器表达式和 switch_body。 |
| switch_body 的开始位置。 | 该 switch_body。 |
| loop 语句的开始位置。 | 该 loop 语句。 |
| while 语句的开始位置。 | 该 while 语句:条件表达式和循环体。 |
| for 语句的开始位置。 | 该 for 语句:for_header 和循环体。 |
紧邻 循环体的左大括号('{')之前,适用于 loop、while 或 for 循环。
| 该 循环体。 |
| continuing_compound_statement 的开始位置。 | 该 continuing_compound_statement。 |
注意: 下列也是复合语句:函数体、case 子句、default-alone 子句、 while 和 for 循环体, 以及 if_clause、else_if_clause、else_clause 的体。
var < private> d : f32; fn helper () -> vec4< f32> { // 在 "if" 体内禁用 derivative_uniformity 诊断。 if ( d < 0.5 ) @diagnostic ( off , derivative_uniformity ) { return textureSample ( t , s , vec2( 0 , 0 )); } return vec4( 0.0 ); }
全局诊断过滤器可用于将诊断过滤应用于整个 WGSL 模块。
diagnostic ( off , derivative_uniformity ); var < private> d : f32; fn helper () -> vec4< f32> { if ( d < 0.5 ) { // 这里通过全局诊断过滤器禁用了 derivative_uniformity 诊断 return textureSample ( t , s , vec2( 0 , 0 )); } else { // 这里 derivative_uniformity 诊断被设为 'warning' 级别。 @diagnostic ( warning , derivative_uniformity ) { return textureSample ( t , s , vec2( 0 , 0 )); } } return vec4( 0.0 ); }
两个诊断过滤器 DF(AR1,NS1,TR1) 和 DF(AR2,NS2,TR2) 当满足如下条件时冲突:
-
(AR1 = AR2),且
-
(TR1 = TR2),且
-
(NS1 ≠ NS2)。
WGSL 的诊断过滤器设计为其影响范围可完美嵌套。 如果 DF1 的影响范围与 DF2 重叠,则要么 DF1 的影响范围完全包含于 DF2,要么反之亦然。
对于源位置 L 和触发规则 TR,最近包含诊断过滤器(如存在)是满足以下条件的诊断过滤器 DF(AR,NS,TR):
-
L 落在影响范围 AR 内,且
-
如存在另一个过滤器 DF'(AR',NS',TR) 且 L 落在 AR' 内,则 AR 包含于 AR'。
由于影响范围嵌套,最近包含诊断过滤器:
2.4. 限制
WGSL 实现将会支持满足如下限制的着色器。 WGSL 实现也可以支持超出指定限制的着色器。
注意: 如果不支持超出指定限制的着色器,WGSL 实现应当报错。
| 限制 | 最小支持值 |
|---|---|
| 结构体类型的最大成员数 | 1023 |
| 复合类型的最大嵌套深度 | 15 |
| 函数内大括号包围语句的最大嵌套深度 | 127 |
| 函数的最大参数数 | 255 |
| switch 语句中 case 选择器值的最大数量。 对每个 case 语句,case 值数量之和,包括default 子句。 | 1023 |
| 单个着色器中在 private 地址空间内被静态访问的所有变量的总字节大小上限 | 8192 |
| 单个函数中在 function 地址空间声明的所有变量的总字节大小上限 | 8192 |
|
单个着色器中在
workgroup 地址空间内被静态访问的所有变量的总字节大小上限
对此限制,固定占用数组在替换 override 值时视为创建时固定占用数组处理。 此项将 WebGPU maxComputeWorkgroupStorageSize 限制映射为独立 WGSL 限制。 | 16384 |
|
一个立即数据
变量的最大字节大小。
这会将 WebGPU maxImmediateSize 限制 映射为一个独立的 WGSL 限制。 | 64 |
| 值构造器表达式中数组类型的最大元素数 | 2047 |
3. 文本结构
text/wgsl 媒体类型用于标识内容为 WGSL 模块。
参见附录 A:text/wgsl 媒体类型。
WGSL 模块是使用 UTF-8 编码的 Unicode 文本,不带字节顺序标记(BOM)。
WGSL 模块文本由一系列 Unicode 码点组成,这些码点被分组成连续且非空的集合,形成:
程序文本不得包含空码点(U+0000)。
3.1. 解析
解析 WGSL 模块的步骤如下:
去除注释:
用空格码点(
U+0020)替换第一个注释。重复此步骤,直到没有注释为止。
查找 模板列表, 使用 算法,见 § 3.9 模板列表。 此步骤用于区分
'<'(U+003C) 和'>'(U+003E) 代码点作为模板列表分隔符和其他用途(如比较运算符)之间的不同用法。解析整个文本,尝试匹配translation_unit 语法规则。 解析器使用 LALR(1) 解析器(单标记前瞻)[DeRemer1969],并有如下定制:
词法分析与语法分析交错进行,并且与上下文相关。 当解析器请求下一个标记时:
消费并忽略初始序列的空白符码点。
如果下一个码点是模板列表的开始,则消费它并返回_template_args_start。
如果下一个码点是模板列表的结束,则消费它并返回_template_args_end。
否则:
标记候选是指由剩余未消费码点的非空前缀形成的任何 WGSL 标记。
返回的标记是当前解析器状态下也是有效前瞻标记的最长标记候选。[VanWyk2007]
若出现以下情况,则会导致着色器创建错误:
-
整个源文本无法转换为有限序列的有效标记,或
-
translation_unit 语法规则不能匹配整个标记序列。
另外一种方法是将模板列表发现与分词过程交错进行。 在该方法中,会在语法规则的任意可能出现模板列表的位置插入一个合成的记号(_disambiguate_template)。 当扫描器尝试匹配 _disambiguate_template 记号时:
-
扫描器对剩余文本运行 模板列表发现算法,并记录模板列表分隔符的位置。
-
扫描器标记
_disambiguate_template记号成功匹配,且关联文本为空字符串。
后续分词步骤将用记录的模板列表分隔符位置来生成 _template_args_start 和 _template_args_end 记号。
这种替代方法并非规范性要求。 规范语法包含 _disambiguate_template 记号,是为采用该替代方法的实现提供帮助。 使用标准方法的解析器可以忽略该合成记号,或者等价地,都将其成功匹配为空字符串。
3.2. 空白与换行
空白符是指来自 Unicode Pattern_White_Space 属性的一个或多个码点的任意组合。 以下是 Pattern_White_Space 属性的码点集合:
-
空格(
U+0020) -
水平制表符(
U+0009) -
换行(
U+000A) -
垂直制表符(
U+000B) -
换页符(
U+000C) -
回车(
U+000D) -
下一行(
U+0085) -
从左到右标记(
U+200E) -
从右到左标记(
U+200F) -
行分隔符(
U+2028) -
段落分隔符(
U+2029)
换行是指一连串空白符码点,表示行的结束。 它被定义为信号“强制换行”的空白符,详见 UAX14 第 6.1 节 不可裁剪换行规则 LB4 和 LB5。 即,换行是下列之一:
-
换行(
U+000A) -
垂直制表符(
U+000B) -
换页符(
U+000C) -
回车(
U+000D),但后面没有跟随换行(U+000A) -
回车(
U+000D)后跟换行(U+000A) -
下一行(
U+0085) -
行分隔符(
U+2028) -
段落分隔符(
U+2029)
注意: 以行号报告源文本位置的诊断应使用换行来计数行。
3.3. 注释
注释是一段文本,不影响 WGSL 程序的合法性或语义,只是注释可以分隔标记。 着色器作者可以使用注释记录程序。
行尾注释是一种注释,
由两个码点 //(U+002F 和 U+002F)和后续码点组成,
直到但不包括:
-
下一个换行,或
-
程序结束。
块注释是一种注释,包括:
-
两个码点
/*(U+002F和U+002A) -
然后是任意序列:
-
一个块注释,或
-
不包含
*/(U+002A和U+002F) 或/*(U+002F和U+002A)的文本
-
-
然后是两个码点
*/(U+002A和U+002F)
注意: 块注释可以嵌套。 由于块注释要求匹配的起始和结束文本序列,并允许任意嵌套, 块注释无法用正则表达式识别。 这是正则语言抽水引理的结果。
3.4. 标记
标记是由码点组成的连续序列,其可以是:
3.5. 字面量
字面量是以下之一:
3.5.1. 布尔字面量
'true'
| 'false'
3.5.2. 数值字面量
数值字面量的形式通过模式匹配定义。
整数字面量是:
-
整数可指定为以下任一形式:
-
0 -
一串十进制数字,且首位不为
0。 -
0x或0X后跟一串十六进制数字。
-
-
随后可选
i或u后缀。
注意: 非零整数字面量前导零(如 012)是禁止的,以避免与其他语言的八进制表示法混淆。
/0[iu]?/
| /[1-9][0-9]*[iu]?/
/0[xX][0-9a-fA-F]+[iu]?/
浮点字面量有两个逻辑部分:用于表示分数的有效数位(significand),以及可选的指数部分。 大致来说,字面量的值等于有效数位乘以底数的指数幂。 当一个有效数位为非零时为有效,或其左右两侧均有非零有效数位。 有效数位自左至右计数:第 N 个有效数位左侧有 N-1 个有效数位。
十进制浮点字面量为:
-
有效数位,由一串数字组成,可在其中任意位置有一个小数点(
.)。 有效数位用十进制表示一个分数。 -
之后可选一个指数后缀,包括:
-
e或E。 -
后跟一个十进制整数指数,可有正负号(
+或-)。 -
后可选
f或h后缀。
-
/0[fh]/
| /[1-9][0-9]*[fh]/
| /[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?[fh]?/
| /[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?[fh]?/
| /[0-9]+[eE][+-]?[0-9]+[fh]?/
const a = 0.e+4f ; const b = 01. ; const c = .01 ; const d = 12.34 ; const f = .0f ; const g = 0h ; const h = 1e-3 ;
-
从 significand 计算 effective_significand:
-
若 significand 有 20 位或更少有效数位,则 effective_significand 为 significand。
-
否则:
-
truncated_significand 与 significand 相同,唯有第 20 位有效数位右侧的每一位都替换为 0。
-
truncated_significand_next 与 significand 相同,唯有:
-
第 20 位有效数位加 1,必要时向左进位,保证每位数字仍在 0~9 范围内,且
-
第 20 位有效数位右侧各位都替换为 0。
-
-
effective_significand 设为 truncated_significand 或 truncated_significand_next,由实现选择。
-
-
-
该字面量的数学值为 effective_significand 作为十进制分数的值,乘以 10 的指数次幂。若未指定指数,则指数为 0。
注意: 十进制有效数位截断为 20 位,保留约 log(10)/log(2)×20 ≈ 66.4 位有效二进制位。
十六进制浮点字面量为:
-
0x或0X前缀 -
随后一个有效数位,由一串十六进制数字组成,可在其中任意位置有一个十六进制点(
.)。 有效数位用十六进制表示一个分数。 -
之后可选一个指数后缀,包括:
-
p或P -
后跟一个十进制整数指数,可有正负号(
+或-)。 -
后可选
f或h后缀。
-
/0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+([pP][+-]?[0-9]+[fh]?)?/
| /0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*([pP][+-]?[0-9]+[fh]?)?/
| /0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fh]?/
const a = 0xa.fp+2 ; const b = 0x1P+4f ; const c = 0X.3 ; const d = 0x3p+2h ; const e = 0X1.fp-4 ; const f = 0x3.2p+2h ;
-
从 significand 计算 effective_significand:
-
若 significand 有 16 位或更少有效数位,则 effective_significand 为 significand。
-
否则:
-
truncated_significand 与 significand 相同,唯有第 16 位有效数位右侧的每一位都替换为 0。
-
truncated_significand_next 与 significand 相同,唯有:
-
第 16 位有效数位加 1,必要时向左进位,保证每位仍在 0~f 范围内,且
-
第 16 位有效数位右侧各位都替换为 0。
-
-
effective_significand 设为 truncated_significand 或 truncated_significand_next,由实现选择。
-
-
-
该字面量的数学值为 effective_significand 作为十六进制分数的值,乘以 2 的指数次幂。若未指定指数,则指数为 0。
注意: 十六进制有效数位截断为 16 位,保留约 4×16=64 位有效二进制位。
当数值字面量有后缀时,该字面量表示特定具体标量类型的值。 否则,字面量表示下方定义的抽象数值类型之一的值。 两种情况下,字面量所表示的值都是在转换到目标类型后,其数学值,转换规则见§ 15.7.6 浮点数转换。
| 数值字面量 | 后缀 | 类型 | 示例 |
|---|---|---|---|
| 整数字面量 | i
| i32 | 42i |
| 整数字面量 | u
| u32 | 42u |
| 整数字面量 | AbstractInt | 124 | |
| 浮点字面量 | f
| f32 | 42f 1e5f 1.2f 0x1.0p10f |
| 浮点字面量 | h
| f16 | 42h 1e5h 1.2h 0x1.0p10h |
| 浮点字面量 | AbstractFloat | 1e5 1.2 0x1.0p10 |
若出现以下情况,将导致着色器创建错误:
注意: 十六进制浮点值 0x1.00000001p0 需要 33 位有效数位才能精确表示,但f32 只有 23 位显式有效数位。
注意: 如果要通过 f
后缀强制十六进制浮点字面量为某类型,字面量还必须使用二进制指数。例如,写作 0x1p0f。相比之下,0x1f 是十六进制整数字面量。
3.6. 关键字
关键字是指代预定义语言概念的标记。 WGSL 关键字列表见§ 16.1 关键字总结。
3.7. 标识符
标识符是一种标记,用于作为名称。 见§ 5 声明与作用域。
WGSL 使用两个语法非终结符区分用法:
-
ident 用于声明对象的命名。
-
member_ident 用于命名结构体类型的成员。
标识符的形式基于 Unicode 标准附录 #31 Unicode 14.0.0 版, 并作如下扩展。
标识符使用如下 UAX31 语法描述的配置:
<Identifier> := <Start> <Continue>* (<Medial> <Continue>+)* <Start> := XID_Start + U+005F <Continue> := <Start> + XID_Continue <Medial> :=
这意味着包含非 ASCII 码点的下列标识符都是合法的:Δέλτα、réflexion、Кызыл、𐰓𐰏𐰇、
朝焼け、سلام、검정、שָׁלוֹם、गुलाबी、
փիրուզ。
有如下例外:
/([_\p{XID_Start}][\p{XID_Continue}]+)|([\p{XID_Start}])/u
Unicode 14.0.0 版字符数据库包含 所有有效码点的非规范性列表, 包括XID_Start 和 XID_Continue。
注意: 某些内建函数的返回类型为结构体类型,其名称不能在 WGSL 源中使用。
这些结构体类型被视为以双下划线开头的预声明类型。
可用类型推断将结果值保存至新声明的 let 或 var,或直接按成员名提取成员。见 frexp 和 modf
的示例用法。
3.7.1. 标识符比较
仅当两个 WGSL 标识符由相同序列的码点组成时它们才相同。
注意: 本规范不允许对比时进行 Unicode 归一化。 视觉和语义上相同但码点序列不同的值不会匹配。 内容作者应一致使用相同的编码序列,或在选择值时避免有潜在问题的字符。更多信息见[CHARMOD-NORM]。
注意: 当将 WGSL 模块中标识符的所有实例替换为同形异义词会改变其语义时,用户代理应向开发者发出可见警告。 (同形异义字是指一段码点序列在视觉上可与另一段码点序列混淆。 检测同形异义字的映射方法包括上文提到的转换、映射与匹配算法。若能通过反复替换子序列为其同形异义字将一个标识符变为另一个,则这两个序列为同形异义词。)
3.8. 上下文相关名称
上下文相关名称是只在特定语法环境下用于命名概念的标记。 该标记的拼写可能与标识符相同,但该标记不会解析为已声明对象。 本节列举了用作上下文相关名称的标记。 该标记不得是关键字或保留字。
3.8.1. 属性名称
见 § 12 属性。
属性名称包括:
3.8.2. 内建值名称
内建值名称包括:
3.8.3. 诊断规则名称
见 § 2.3 诊断。
预定义的诊断规则名称有:
3.8.4. 诊断严重级别控制名称
有效的诊断过滤器严重级别控制名称在§ 2.3 诊断中列出,形式与标识符相同:
诊断过滤器严重级别控制名称包括:
3.8.5. 扩展名称
有效的enable-extension名称在§ 4.1.1 启用扩展中列出,但通常形式与标识符相同:
enable-extension名称包括:
有效的语言扩展名称在§ 4.1.2 语言扩展中列出,但通常形式与标识符相同:
语言扩展名称包括:
3.8.6. 插值类型名称
插值类型名称标记是用于插值类型名称的标记,用于interpolate_type_name。
参见 § 13.3.1.4 插值。
插值类型名称包括:
3.8.7. 插值采样名称
参见 § 13.3.1.4 插值。
插值采样名称包括:
3.8.8. 分量调换名称
/[rgba]/
| /[rgba][rgba]/
| /[rgba][rgba][rgba]/
| /[rgba][rgba][rgba][rgba]/
| /[xyzw]/
| /[xyzw][xyzw]/
| /[xyzw][xyzw][xyzw]/
| /[xyzw][xyzw][xyzw][xyzw]/
3.9. 模板列表
模板参数化是一种指定用于修饰通用概念的参数的方法。 要编写模板参数化,先写通用概念,后跟一个模板列表。
模板参数的形式由下方模板列表发现算法隐式定义。一般来说,它们可以是名称、表达式或类型。
注意: 例如,短语 vec3<f32> 是一个模板参数化,其中
vec3 是被修饰的通用概念,
<f32> 是包含一个参数的模板列表,即 f32 类型。
合起来,vec3<f32> 表示一个具体的向量类型。
注意: 例如,短语
var<storage,read_write> 用模板参数 storage 和 read_write 修饰了通用
var 概念。
array<vec4<f32>> 有两个模板参数化:
-
vec4<f32>用模板参数f32修饰通用vec4概念。 -
array<vec4<f32>>用模板参数vec4<f32>修饰通用array概念。
界定模板列表的 '<'(U+003C)和 '>'(U+003E)码点也用于拼写:
该句法歧义优先解释为模板列表:
-
在稍后的词法分析阶段, 模板列表的初始
'<'(U+003C)被映射为_template_args_start 标记, 模板列表的终止'>'(U+003E)被映射为_template_args_end 标记。
模板列表发现算法如下。 它使用以下假设和属性:
-
表达式不包含
';'(U+003B)、'{'(U+007B)或':'(U+003A)码点。 -
表达式不包含赋值语句。
-
'='(U+003D)码点只会出现在比较运算中,即'<='、'>='、'=='或'!='之一。 否则,'='(U+003D)码点作为赋值出现。 -
模板列表分隔符会考虑由圆括号 '(... )' 和数组下标 '[...]' 形成的嵌套表达式。模板列表的开始和结束必须出现在同一级嵌套。
算法: 模板列表发现输入: 程序源文本。
记录类型:
令 UnclosedCandidate 为包含:
position,源文本中的位置
depth,整数,position 处的表达式嵌套深度
令 TemplateList 为包含:
start_position,以
'<'(U+003C)码点标记此模板列表起始的源位置end_position,以
'>'(U+003E)码点标记此模板列表结束的源位置输出: DiscoveredTemplateLists,一个 TemplateList 记录的列表。
过程:
初始化 DiscoveredTemplateLists 为空列表。
初始化 Pending 为空的 UnclosedCandidate 记录堆栈。
初始化 CurrentPosition 整数变量为 0,表示当前正在检查的码点位置(从文本开头起的码点数)。
该变量在算法执行时在文本中前进。到达文本末尾时,立即终止算法并返回 DiscoveredTemplateLists。
初始化 NestingDepth 整数变量为 0。
重复以下步骤:
如果 ident_pattern_token 匹配 CurrentPosition 处的文本,则:
将 CurrentPosition 前进到 ident_pattern_token 之后。
如有,继续前进到空白符和注释之后。
如果
'<'(U+003C)出现在 CurrentPosition,则:
注意: 此码点为模板列表起始候选。保存足够的状态,以便后续与终止
'>'(U+003E)匹配。将 UnclosedCandidate(position=CurrentPosition,depth=NestingDepth) 压入 Pending 堆栈。
将 CurrentPosition 前进到下一个码点。
如果
'<'(U+003C)再次出现在 CurrentPosition,则:
注意: 根据假设1,没有模板参数以
'<'(U+003C)开始,所以前一个码点不是模板列表起始。 因此当前和前一个码点必为'<<'运算符。弹出 Pending 堆栈顶。
将 CurrentPosition 前进到下一个码点,开始新一轮循环。
如果
'='(U+003D)出现在 CurrentPosition,则:
注意: 根据假设1,没有模板参数以
'='(U+003C)开始,所以前一个码点不是模板列表起始。 假定当前和前一个码点组成'<='比较运算符。 跳过'='(U+003D),以免后续误判为赋值。弹出 Pending 堆栈顶。
将 CurrentPosition 前进到下一个码点,开始新一轮循环。
开始下一轮循环。
如果
'>'(U+003E)出现在 CurrentPosition,则:
注意: 此码点为模板列表结束候选。
如 Pending 非空,令 T 为其栈顶,且 T.depth 等于 NestingDepth 时:
注意: 此码点结束了 T 记录的模板列表。
将 TemplateList(start_position=T.position, end_position=CurrentPosition) 加入 DiscoveredTemplateLists。
弹出 T。
将 CurrentPosition 前进到下一个码点,开始新一轮循环。
否则,此码点不结束模板列表:
将 CurrentPosition 前进到下一个码点。
如果
'='(U+003D)出现在 CurrentPosition,则:
注意: 假定当前和前一个码点组成
'>='比较运算符。 跳过'='(U+003D),以免后续误判为赋值。将 CurrentPosition 前进到下一个码点。
开始新一轮循环。
如果
'('(U+0028)或'['(U+005B)出现在 CurrentPosition,则:
注意: 进入嵌套表达式。
NestingDepth 加 1。
将 CurrentPosition 前进到下一个码点,开始新一轮循环。
如果
')'(U+0029)或']'(U+005D)出现在 CurrentPosition,则:
注意: 退出嵌套表达式。
弹出 Pending 栈,直到为空,或栈顶 depth < NestingDepth。
将 NestingDepth 设为 0 或 NestingDepth−1,以较大者为准。
将 CurrentPosition 前进到下一个码点,开始新一轮循环。
如果
'!'(U+0021)出现在 CurrentPosition,则:
将 CurrentPosition 前进到下一个码点。
如果
'='(U+003D)出现在 CurrentPosition,则:
注意: 假定当前和前一个码点组成
'!='比较运算符。 跳过'='(U+003D),以免后续误判为赋值。将 CurrentPosition 前进到下一个码点。
开始新一轮循环。
如果
'='(U+003D)出现在 CurrentPosition,则:
将 CurrentPosition 前进到下一个码点。
如果
'='(U+003D)再次出现在 CurrentPosition,则:
注意: 假定当前和前一个码点组成
'=='比较运算符。 跳过'='(U+003D),以免后续误判为赋值。将 CurrentPosition 前进到下一个码点,开始新一轮循环。
注意: 假定此码点为赋值一部分,赋值不能出现在表达式中,因此也不能出现在模板列表中。清空待关闭候选项。
将 NestingDepth 设为 0。
清空 Pending 堆栈。
将 CurrentPosition 前进到下一个码点,开始新一轮循环。
如果
';'(U+003B)、'{'(U+007B)或':'(U+003A)出现在 CurrentPosition,则:
注意: 这些字符不能出现在表达式中,因此也不能出现在模板列表中。清空待关闭候选项。
将 NestingDepth 设为 0。
清空 Pending 堆栈。
将 CurrentPosition 前进到下一个码点,开始新一轮循环。
如果
'&&'或'||'匹配 CurrentPosition 处的文本,则:
注意: 这些是优先级低于比较的运算符。丢弃当前表达式级别的待关闭候选项。
注意: 依据此规则,程序片段
a<b || c>d不会被识别为模板列表,而会被识别为两个比较的短路或。弹出 Pending,直到为空或栈顶 depth < NestingDepth。
将 CurrentPosition 前进两个码点,开始新一轮循环。
将 CurrentPosition 前进到下一个码点。
-
修改 UnclosedCandidate,添加以下字段:
-
parameters:模板参数的源码区间列表。
-
parameter_start_position:源码位置。
-
-
修改 TemplateList,添加字段:
-
parameters:模板参数的源码区间列表。
-
-
将新的 UnclosedCandidate 压入 Pending 堆栈时:
-
将其 parameters 字段设为空列表。
-
将 parameter_start_position 设为 CurrentPosition 后一个码点。
-
-
将 TemplateList TL 加入 DiscoveredTemplateLists 时:
-
令 T 为 Pending 栈顶,如原算法。
-
将从 T.parameter_start_position 到 CurrentPosition−1 的源码区间压入 T.parameters。
-
如原算法准备 TL。
-
将 TL.parameters 设为 T.parameters。
-
-
在主循环结束、即将前进到下一个码点前增加检查:
-
如
,(U+002C)出现在 CurrentPosition 且 Pending 非空:-
令 T 为 Pending 栈顶。
-
将从 T.parameter_start_position 到 CurrentPosition−1 的源码区间压入 T.parameters。
-
将 T.parameter_start_position 设为 CurrentPosition+1
-
-
注意: 该算法会显式跳过字面量,因为某些数字字面量以字母结尾,例如 1.0f。
末尾的 f 不应被误判为ident_pattern_token 的开头。
注意: 短语
A ( B < C, D > ( E ) ) 中,< C, D > 是模板列表。
注意: 算法遵循表达式嵌套:特定模板列表的起止不能出现在不同表达式嵌套层级。
例如 array<i32,select(2,3,a>b)>,该模板列表有三个参数,最后一个为 select(2,3,a>b)。
a>b 中的 '>' 不会终止模板列表,因为它包裹在 select 函数括号表达式中。
注意: 模板列表两端必须在同一索引表达式内。例如 a[b<d]>()
不包含有效模板列表。
注意: 短语 A<B<<C> 中,
B<<C 被解析为 B,左移运算符'<<',和 C。
模板发现算法从 B 后的 '<' 开始检查,发现下一个 '<' 不能作为模板参数开头,因此 B 后的
'<' 不是模板列表起始。
唯一的模板列表定界符是最外层 '<' 和 '>',参数为 B<<C。
注意: 短语 A<B<=C>
解析方式与前述类似,B<=C 被解析为 B,小于等于运算符'<='和
C。
算法检查 B 后 '<',发现下一个 '=' 不能作为模板参数开头,因此 B 后的
'<' 不是模板列表起始。
唯一的模板列表定界符为最外层 '<' 和 '>',参数为 B<=C。
注意: 检查短语
A<(B>=C)> 时,有一个模板列表,起于第一个 '<'(U+003C),止于最后一个 '>'(U+003E),参数为
B>=C。
检查 B 后第一个 '>'(U+003C)后,'=' 需特殊处理,避免被识别为赋值。
注意: 检查短语
A<(B!=C)> 时,有一个模板列表,起于第一个 '<'(U+003C),止于最后一个 '>'(U+003E),参数为
B!=C。
检查 'B' 后的 '!'(U+0021)后,'=' 需特殊处理,避免被识别为赋值。
注意: 检查短语
A<(B==C)> 时,有一个模板列表,起于第一个 '<'(U+003C),止于最后一个 '>'(U+003E),参数为
B==C。
检查 'B' 后第一个 '='(U+003D)后,第二个 '=' 需特殊处理,避免两者被识别为赋值。
在模板列表发现完成后, 解析将尝试将每个模板列表匹配到template_list语法规则。
_template_args_start template_arg_comma_list _template_args_end
template_arg_expression ( ',' template_arg_expression ) * ',' ?
4. 指令
指令是一组标记序列,用于修改 WebGPU 实现处理 WGSL 程序的方式。
指令是可选的。 如果存在,所有指令必须出现在任何声明或const 断言之前。
4.1. 扩展
WGSL 预计会随着时间演进。
扩展是对 WGSL 规范的一组相关修改的命名分组,由以下任意组合组成:
-
通过新语法增加新概念和行为,包括:
-
声明、语句、属性和内建函数。
-
-
移除当前规范或先前发布扩展中的限制。
-
用于收缩可允许行为集的语法。
-
用于限制程序某部分可用特性的语法。
-
描述扩展与现有规范及其他扩展(可选)之间的交互方式。
理论上,扩展可以:
-
增加数值标量类型,如不同位宽的整数。
-
增加用于约束浮点舍入方式的语法。
-
增加用于声明着色器不使用原子类型的语法。
-
增加新类型的语句。
-
增加新的内建函数。
-
增加用于约束着色器调用执行方式的语法。
-
增加新的着色器阶段。
扩展分为两类:enable-extensions 和 语言扩展。
4.1.1. 启用扩展
enable-extension 是一种扩展,其功能仅在以下条件下可用:
-
实现支持它,并且
-
着色器通过enable 指令显式请求,并且
-
创建
GPUDevice时,请求的功能中包含相应的 WebGPUGPUFeatureName。
enable-extensions 旨在暴露并非所有硬件都普遍支持的功能。
enable 指令是一种指令,用于开启对一个或多个 enable-extensions 的支持。 如果实现不支持所有列出的 enable-extensions,则会导致着色器创建错误。
'enable' enable_extension_list ';'
如同其它指令,如果存在enable 指令,必须出现在所有声明和const 断言之前。 扩展名不是标识符:它们不会解析为声明。
有效的enable-extensions如下表所列。
| WGSL enable-extension | WebGPU GPUFeatureName
| 说明 |
|---|---|---|
f16
| "shader-f16" | 在 WGSL 模块中可以使用 f16 类型。否则,直接或间接使用f16 会导致着色器创建错误。 |
clip_distances
| "clip-distances" | 在 WGSL 模块中可以使用内建变量clip_distances。否则,使用clip_distances会导致着色器创建错误。 |
dual_source_blending
| "dual-source-blending" | 在 WGSL 模块中可以使用属性blend_src。否则,使用 blend_src 会导致 着色器创建错误。 |
subgroups
| "subgroups" | 在 WGSL 模块中可以使用subgroup 内建值、subgroup 内建函数和quad 内建函数。 否则,任何使用都会导致着色器创建错误。 |
primitive_index
| "primitive-index"
| 内建变量 primitive_index 可以在 WGSL 模块中使用。否则,使用 primitive_index 会导致 着色器创建错误。 |
// 启用假设的任意精度浮点类型扩展。 enable arbitrary_precision_float ; enable arbitrary_precision_float ; // 重复 enable 指令是允许的。 // 启用假设的用于控制舍入模式的扩展。 enable rounding_mode ; // 假设 arbitrary_precision_float 启用后可使用: // - 类型 f<E,M> // - 作为函数返回、形参和 let 声明的类型 // - 从 AbstractFloat 构造值 // - 作为除法运算符 / 的操作数 // 假设 @rounding_mode 属性由 rounding_mode enable 指令启用。 @rounding_mode ( round_to_even ) fn halve_it ( x : f < 8 , 7 > ) -> f < 8 , 7 > { let two = f < 8 , 7 > ( 2 ); return x / 2 ; // 使用 round to even 舍入模式。 }
4.1.2. 语言扩展
语言扩展 是一种扩展,只要实现支持就会自动可用。 程序无需显式请求即可使用。
语言扩展 体现了任何 WebGPU 实现都合理支持的功能。 如果某个特性并非普遍可用,那是因为尚有部分 WebGPU 实现未实现它。
注意: 例如,do-while 循环可以作为一种语言扩展。
WebGPU wgslLanguageFeatures
成员会列出实现所支持的
语言扩展集合。
该成员属于 WebGPU GPU
对象。
requires 指令 是一种指令,用于声明 程序使用了哪些语言扩展。 它不会改变实现所暴露的功能。 如果实现不支持所需扩展之一,则会导致着色器创建错误。
WGSL 模块可以使用requires 指令来提示潜在的不移植性,并声明预期的最小可移植性要求。
注意: WebGPU 实现以外的工具可以检查程序用到的语言扩展是否都已在程序中通过requires 指令标明。
'requires' language_extension_list ';'
language_extension_name ( ',' language_extension_name ) * ',' ?
和其他指令一样,如有requires 指令,必须出现在所有声明和const 断言之前。 扩展名不是标识符:它们不会解析为声明。
| WGSL 语言扩展 | 说明 |
|---|---|
| readonly_and_readwrite_storage_textures | 允许 storage texture 使用 read 和 read_write 访问模式。 另外,增加 textureBarrier 内建函数。 |
| packed_4x8_integer_dot_product | 支持用 32 位整数打包 4 个 8 位整数分量向量作为 dot4U8Packed 和 dot4I8Packed 内建函数的点积输入。 另外,增加了用于 8 位整数打包/解包的内建函数:pack4xI8、pack4xU8、 pack4xI8Clamp、pack4xU8Clamp、 unpack4xI8 和 unpack4xU8。 |
| unrestricted_pointer_parameters | 移除了函数限制中关于用户自定义函数的如下限制: |
| pointer_composite_access |
支持复合值分解表达式,其根表达式为指针时,返回引用。
例如,若 同理,若 |
| uniform_buffer_standard_layout | 允许 uniform 地址空间中的缓冲区使用与其它地址空间相同的 内存布局约束。 |
| subgroup_id | 当启用 subgroups 扩展时,可以使用内建值 subgroup_id 和 num_subgroups。 |
| subgroup_uniformity |
为统一控制流
的 子组 和
四元 内置函数新增了作用域:subgroup,使所有调用都在同一个subgroup内。
注意:使用 subgroup 和 quad 内置函数需要启用 subgroups 扩展。 |
| texture_and_sampler_let | 允许let 声明的 effective-value-type 为texture 或 sampler 类型。 |
| texture_formats_tier1 | 支持更多元数据格式(texel formats): rgba16unorm、rgba16snorm、rg8unorm、 rg8snorm、rg8uint、rg8sint、 rg16unorm、rg16snorm、rg16uint、 rg16sint、rg16float、r8unorm、 r8snorm、r8uint、r8sint、 r16unorm、r16snorm、r16uint、 r16sint、r16float、rgb10a2unorm、 rgb10a2uint、rg11b10ufloat |
| linear_indexing | 支持 global_invocation_index 和 workgroup_index 内建值。 |
| immediate_data |
启用 immediate 地址空间,允许变量
用 var<immediate> 声明,并绑定到通过 WebGPU API 直接从命令编码器传入的少量频繁
更新的数据。
|
注意: 设计目标是 WGSL 随时间推移将语言扩展中业界常见的所有功能都定义为内置扩展。 在requires 指令中,这些扩展可以用作列出所有常用特性的简写。 它们代表不断增强的功能集合,也可视为某种语言版本。
4.2. 全局诊断过滤器
全局诊断过滤器是一种诊断过滤器,其影响范围为整个 WGSL 模块。
它是一条指令,因此出现在所有模块作用域声明之前。
它的拼写形式与属性类似,但没有前导 @(U+0040)码点,且以分号结尾。
'diagnostic' diagnostic_control ';'
5. 声明与作用域
声明将一个标识符与下列对象之一关联:
换句话说,声明为一个对象引入一个名称。
声明位于模块作用域,如果声明出现在程序源码中但不在其他声明的文本内部。
一个函数声明位于模块作用域。 函数声明包含形参声明(如果有的话),并且可以在其函数体内包含变量和常量声明。 这些包含的声明不属于模块作用域。
注意: 唯一能包含其他声明的声明类型是函数声明。
某些对象由 WebGPU 实现提供,视为在 WGSL 模块源码开始之前已经声明。 我们称这些对象为预声明对象。 例如,WGSL 预声明了:
-
内建函数,
-
如
array、ptr和texture_2d等内建类型生成器,以及 -
如 read_write、workgroup 和 rgba8unorm 等枚举项。
作用域是声明在程序源码中所声明标识符可能表示其关联对象的源码位置集合。 我们称这些源码位置的标识符为声明的在作用域内。
声明出现的位置决定了其作用域:
-
预声明对象和模块作用域声明在整个程序源码中在作用域内。
-
用户声明函数的每个形参在相应函数体内在作用域内。 见§ 11.1 用户自定义函数声明。
-
其他情况,作用域为声明结束后立即开始的一段文本。 详情见§ 7 变量和常量声明。
同一 WGSL 源程序中的两个声明不得同时:
-
引入同名标识符,并且
-
具有相同的作用域结束位置。
注意: 预声明对象在 WGSL 源码中没有声明。 因此,用户在模块作用域或函数内声明的对象可以与预声明对象同名。
标识符的用法如下,由语法环境区分:
-
匹配ident语法元素的标记:
-
在声明中用作被声明对象的名称,或
-
用作名称,表示在其他地方声明的对象。此为常见情况。
-
-
匹配member_ident语法元素的标记:
-
用作名称,表示结构体值的成员,或表示结构体成员的引用。见§ 8.5.4 结构体访问表达式。
当 ident 标记作为名称引用其他地方声明的对象时, 它必须对某个声明在作用域内。 该标识符标记所表示的对象按以下规则确定:
-
如果该标记属于至少一个非模块作用域声明的作用域, 则标记表示距离最近的这些声明所关联的对象。
注意: 最近的此类声明出现在标识符标记之前。
-
否则,如果存在同名的模块作用域声明,则该标记表示该声明的对象。
注意: 模块作用域声明可以出现在标识符标记之前或之后。
-
否则,如果存在同名的预声明对象,则该标记表示该对象。
当上述算法用于将标识符映射到声明时,我们称该标识符解析到该声明。 同样,我们也称该标识符解析到被声明对象。
如果任何模块作用域声明是递归的,将导致着色器创建错误。 即,声明之间不能形成环:
考虑如下有向图:
每个节点对应一个声明 D。
当声明 D 的定义中引用了解析到声明 T 的标识符时,从 D 指向 T 有一条边。
该图不得有环。
注意: 函数体是函数声明的一部分,因此函数也不能直接或间接递归。
注意: 非模块作用域的标识符声明必须出现在其使用之前。
// 合法,用户自定义变量可以与内建函数同名。 var < private> modf : f32= 0.0 ; // 合法,foo_1 在整个程序范围内有效。 var < private> foo : f32= 0.0 ; // foo_1 // 合法,bar_1 在整个程序范围内有效。 var < private> bar : u32= 0u ; // bar_1 // 合法,my_func_1 在整个程序范围内有效。 // 合法,foo_2 在函数末尾前有效。 fn my_func ( foo : f32) { // my_func_1, foo_2 // 对 'foo' 的任何引用都解析到函数参数。 // 非法,modf 解析为模块作用域变量。 let res = modf ( foo ); // 非法,foo_2 的作用域到函数末尾。 var foo : f32; // foo_3 // 合法,bar_2 在函数末尾前有效。 var bar : u32; // bar_2 // 对 'bar' 的引用解析为 bar_2 { // 合法,foo_4 在复合语句末尾前有效。 var foo : f32; // foo_4 // 合法,bar_3 在复合语句末尾前有效。 var bar : u32; // bar_3 // 对 'bar' 的引用解析为 bar_3 // 非法,bar_4 与 bar_3 作用域结束位置相同。 var bar : i32; // bar_4 // 合法,i_1 在 for 循环末尾前有效 for ( var i : i32= 0 ; i < 10 ; i ++ ) { // i_1 // 非法,i_2 与 i_1 作用域结束位置相同。 var i : i32= 1 ; // i_2. } } // 非法,bar_5 与 bar_2 作用域结束位置相同。 var bar : u32; // bar_5 // 合法,later_def,模块作用域声明,在整个程序范围内有效。 var early_use : i32= later_def ; } // 非法,bar_6 与 bar_1 作用域相同。 var < private> bar : u32= 1u ; // bar_6 // 非法,my_func_2 与 my_func_1 作用域结束位置相同。 fn my_func () { } // my_func_2 // 合法,my_foo_1 在整个程序范围内有效。 fn my_foo ( //my_foo_1 // 合法,my_foo_2 在函数末尾前有效。 my_foo : i32// my_foo_2 ) { } var < private> later_def : i32= 1 ;
// 此声明屏蔽了预声明的 'min' 内建函数。 // 由于该声明处于模块作用域,因此在整个源码中有效。 // 内建函数不再可访问。 fn min () -> u32{ return 0 ; } const rgba8unorm= 12 ; // 此处屏蔽了预声明的 'rgba8unorm' 枚举项。
6. 类型
程序计算值。
在 WGSL 中,类型是一组值,每个值仅属于一个类型。 值的类型决定了可以对该值执行的操作的语法和语义。
例如,数学数字 1 在 WGSL 中对应如下不同的值:
-
32 位有符号整数值
1i, -
32 位无符号整数值
1u, -
32 位浮点值
1.0f, -
如果启用了 f16 扩展,则为 16 位浮点值
1.0h, -
AbstractInt 值 1,和
-
AbstractFloat 值 1.0
WGSL 将这些值视为不同,因为它们的机器表示和操作不同。
某些类型通过模板参数化表示。
类型生成器是一个预声明对象,当与模板列表参数化时,表示一个类型。
例如,类型 atomic<u32> 结合了类型生成器 atomic 与模板列表 <u32>。
我们区分 WGSL 中类型的概念和表示该类型的语法。 在许多情况下,本规范中类型的拼写与其 WGSL 语法相同。 例如:
-
32 位无符号整数值集合在本规范中和 WGSL 模块中均拼写为
u32。 -
结构体类型或包含结构体的类型的拼写不同。
某些 WGSL 类型仅用于分析源程序和确定程序的运行时行为。 本规范将描述这些类型,但它们不会出现在 WGSL 源文本中。
注意: 引用类型不会出现在 WGSL 模块中。见 § 6.4.3 引用和指针类型。
6.1. 类型检查
WGSL 值通过求值表达式计算。
表达式是被解析为 WGSL
语法规则之一的源码段,这些规则的名称以 "expression" 结尾。
表达式 E 可以包含子表达式,即完整包含在外层表达式 E 内的表达式。
顶层表达式是自身不是子表达式的表达式。
见 § 8.18 表达式语法汇总。
表达式求值所产生的具体值依赖于:
-
静态上下文: 表达式周围的源码,和
-
动态上下文: 求值表达式的调用状态,以及调用运行时的执行上下文。
求值特定表达式产生的值总是属于特定 WGSL 类型,称为表达式的静态类型。 WGSL 的规则设计为表达式的静态类型仅依赖于表达式的静态上下文。
类型断言是 WGSL 源表达式到 WGSL 类型的映射。 表示法
e : T
是一条类型断言,表示 T 是 WGSL 表达式 e 的静态类型。
注意: 类型断言是关于程序文本事实的陈述。 它不是运行时检查。
语句通常使用表达式,并可能对这些表达式的静态类型施加要求。 例如:
对成功解析的 WGSL 模块进行类型检查是将每个表达式映射到其静态类型并验证每条语句的类型要求是否满足的过程。 如果类型检查失败,结果是一种称为类型错误的特殊着色器创建错误。
类型检查可以通过递归应用类型规则到语法短语来完成,其中语法短语可以是表达式或语句。 类型规则描述了静态上下文如何决定语法短语内表达式的静态类型。 一条类型规则有两个部分:
-
结论。
-
前提条件,包括:
类型规则的前提条件和结论中可能有类型参数。 当类型规则的结论或前提条件包含类型参数时,我们称其为参数化规则。 当它们不包含时,称其为完全展开规则。 我们可以通过为其每个类型参数替换一个类型,使参数化类型规则成为完全展开规则,在规则中每次出现的参数都使用相同类型。 给规则的类型参数分配类型称为替代。
例如,这里是逻辑取反(形式为 !e 的表达式)的类型规则:
| 前提条件 | 结论 |
|---|---|
| e: T T 是 bool 或 vecN<bool> | !e: T
|
这是一个参数化规则,因为它包含类型参数 T,可以表示四种类型之一 bool、vec2<bool>、
vec3<bool> 或 vec4<bool>。
应用将 T 映射为 vec3<bool> 的替代产生完全展开的类型规则:
| 前提条件 | 结论 |
|---|---|
e: vec3<bool> | !e: vec3<bool>
|
通过应用满足规则其他条件的替代,从参数化规则生成的每条完全展开规则称为该参数化规则的重载。 例如,布尔取反规则有四个重载,因为有四种可能的方式为其类型参数 T 分配类型。
注意: 换句话说,参数化类型规则提供了一个模式,用于生成一组完全展开的类型规则,每条规则由对参数化规则应用不同替代产生。
当以下条件满足时,类型规则适用于某语法短语:
-
规则的结论匹配语法短语的合法解析,并且
-
规则的前提条件满足。
如果存在某替代生成的完全展开类型规则适用于表达式,则参数化类型规则适用于该表达式。
考虑表达式 1u+2u。
它有两个字面量子表达式:1u 和 2u,均为 u32 类型。
顶层表达式是加法。
参考 § 8.7 算术表达式中的规则,加法的类型规则适用于该表达式,因为:
-
1u+2u匹配形式 e1+e2 的解析,e1 表示1u,e2 表示2u,并且 -
e1 为 u32 类型,且
-
e2 为 u32 类型,且
-
我们可以在类型规则中将类型参数 T 替换为 u32,从而生成适用于整个表达式的完全展开规则。
分析某语法短语时,可能出现以下三种情况:
-
没有类型规则适用于该表达式。结果是类型错误。
-
多条类型规则适用。即,多条重载的前提条件满足。 在这种情况下,使用§ 6.1.3 重载解析中描述的决策过程。
继续上述示例,仅有一条类型规则适用于表达式 1u+2u,因此类型检查接受该类型规则的结论,即 1u+2u 为 u32 类型。
当以下条件满足时,WGSL 源程序是类型正确的:
-
通过应用类型规则可以确定程序中每个表达式的静态类型,并且
-
每条语句的类型要求都满足。
否则会发生类型错误,程序不是有效的 WGSL 模块。
WGSL 是一种静态类型语言,因为对 WGSL 模块的类型检查将成功或发现类型错误,并且只需检查程序源码文本。
6.1.1. 类型规则表
WGSL 的表达式类型规则按照类型规则表组织, 每条类型规则对应表中的一行。
表达式的语义是对该表达式求值的效果,主要是产出一个结果值。 适用于某个表达式的类型规则的 Description(描述)列会指定该表达式的语义。 语义通常取决于类型规则参数的值,包括任何子表达式的假定值。 有时,表达式的语义还包括产生结果值之外的效果,如其子表达式的副作用。
fn foo ( p : ptr< function, i32> ) -> i32{ let x = * p ; * p += 1 ; return x ; } fn bar () { var a : i32; let x = foo ( & a ); // 调用 foo 返回一个值 // 同时更新了 a 的值 }
6.1.2. 转换等级
当类型断言 e:T 作为类型规则前提条件使用时,满足如下条件即可:
-
e 已经是类型 T,或者
-
e 是类型 S,且类型 S 可自动转换为类型 T,定义见下文。
该规则由 ConversionRank 函数对类型对进行编码,定义见下表。 ConversionRank 函数表达了从一个类型(Src)自动转换到另一个类型(Dest)的优先级和可行性。 等级越低越理想。
可行的自动转换指将值从类型 Src 转换为类型 Dest,且仅当 ConversionRank(Src,Dest) 有限时才允许。 此类转换是保值的,具体限制见 § 15.7 浮点数求值。
注意: 自动转换只发生在两种情形。 一是将常量表达式转换为 GPU 可用的对应类型数值。 二是在从内存引用加载时,产生存储在该内存中的值。
注意: 无限等级的转换不可行,即不允许。
注意: 未执行转换时,转换等级为零。
| Src | Dest | ConversionRank(Src,Dest) | 说明 |
|---|---|---|---|
| T | T | 0 | 恒等。未执行转换。 |
| ref<AS,T,AM> 其中 地址空间为 AS, 且访问模式 AM 为 read 或 read_write。 | T | 0 | 对内存引用应用加载规则。 |
| AbstractFloat | f32 | 1 | 见 § 15.7.6 浮点数转换 |
| AbstractFloat | f16 | 2 | 见 § 15.7.6 浮点数转换 |
| AbstractInt | i32 | 3 | 若值在 i32 范围内则恒等,否则产生着色器创建错误。 |
| AbstractInt | u32 | 4 | 若值在 u32 范围内则恒等,否则产生着色器创建错误。 |
| AbstractInt | AbstractFloat | 5 | 见 § 15.7.6 浮点数转换 |
| AbstractInt | f32 | 6 | 等效于 AbstractInt 到 AbstractFloat,再到 AbstractFloat 到 f32 |
| AbstractInt | f16 | 7 | 等效于 AbstractInt 到 AbstractFloat,再到 AbstractFloat 到 f16 |
| vecN<S> | vecN<T> | ConversionRank(S,T) | 继承分量类型的转换等级。 |
| matCxR<S> | matCxR<T> | ConversionRank(S,T) | 继承分量类型的转换等级。 |
| array<S,N> | array<T,N> | ConversionRank(S,T) | 继承分量类型的转换等级。 注:只有定长数组可有抽象分量类型。 |
| __frexp_result_abstract | __frexp_result_f32 | 1 | |
| __frexp_result_abstract | __frexp_result_f16 | 2 | |
| __frexp_result_vecN_abstract | __frexp_result_vecN_f32 | 1 | |
| __frexp_result_vecN_abstract | __frexp_result_vecN_f16 | 2 | |
| __modf_result_abstract | __modf_result_f32 | 1 | |
| __modf_result_abstract | __modf_result_f16 | 2 | |
| __modf_result_vecN_abstract | __modf_result_vecN_f32 | 1 | |
| __modf_result_vecN_abstract | __modf_result_vecN_f16 | 2 | |
| S | T 如上无适用情况 | infinity | 不同类型间无自动转换。 |
如果:
-
T是具体类型,且 -
T不是引用类型,且 -
ConversionRank(
S,T) 有限,且 -
对于所有其他非引用类型
T2,ConversionRank(S,T2) > ConversionRank(S,T)。
则类型 T 是 S 的具体化。
e 类型为 T 的值的具体化是对 e 应用可行转换,将 T 映射到其具体化类型后的结果值。
注意:转换为 f32 总是优先于 f16,因此 自动转换只会在模块启用了 extension 的情况下才会生成 f16。
6.1.3. 重载解析
当多条类型规则适用于某个语法短语时,将使用一个决策流程来确定应采用哪一条规则。 该流程称为重载解析,并假定类型检查已成功为子表达式找到了静态类型。
设 语法短语 P,所有适用的类型规则。 重载解析算法将这些类型规则称为重载候选。 对每个候选项:
-
对每个候选 C,枚举语法短语中子表达式的转换等级。 由于候选的前提已满足,故对于 P 中第 i 个子表达式:
-
其静态类型已被计算。
-
存在从该表达式的静态类型到前提中对应类型断言的可行自动转换。 记 C.R(i) 为该转换的ConversionRank。
-
-
候选排序:给定两个重载候选 C1 和 C2,若满足如下条件,则 C1 优于 C2:
-
对 P 中每个表达式位置 i,有 C1.R(i) ≤ C2.R(i)。
-
即,应用 C1 所需的每个表达式转换至少与应用 C2 所需的对应表达式转换一样优。
-
-
至少存在一个表达式位置 i 使得 C1.R(i) < C2.R(i)。
-
即,应用 C1 至少有一个表达式转换比应用 C2 的对应转换更优。
-
-
-
如果存在唯一一个候选 C,其优于所有其他候选,则重载解析成功,结果为该类型规则 C。 否则,重载解析失败。
6.2. 普通类型
普通类型是指布尔值、数值、向量、矩阵或这些值的聚合在机器中的表示类型。
注意: WGSL 中的普通类型类似于 C++ 的 Plain-Old-Data 类型,但还包括原子类型和抽象数值类型。
6.2.1. 抽象数值类型
这些类型不能在 WGSL 源中书写。仅用于类型检查。
某些表达式在着色器创建时求值,其数值范围和精度可能大于 GPU 直接实现的范围。
WGSL 为这些求值定义了两种抽象数值类型:
在这些类型之一中对表达式的求值不得溢出,也不得产生无穷大或 NaN。
若某类型为抽象数值类型或包含抽象数值类型,则其为抽象类型。 若不是抽象类型,则为具体类型。
-
无
i或u后缀的整数字面量表示 AbstractInt 值。 -
无
f或h后缀的浮点字面量表示 AbstractFloat 值。
示例:表达式 log2(32) 的分析如下:
-
log2(32)被解析为对内建函数log2的调用,操作数为 AbstractInt 值 32。 -
不存在形参为整数标量的
log2重载。 -
-
AbstractInt 到 AbstractFloat。(转换等级 4)
-
AbstractInt 到 f32。(转换等级 5)
-
AbstractInt 到 f16。(转换等级 6)
-
-
最终计算以 AbstractFloat 进行(如
log2(32.0))。
示例:表达式 1 + 2.5 的分析如下:
-
1 + 2.5被解析为加法操作,子表达式为 AbstractInt 值 1 和 AbstractFloat 值 2.5。 -
不存在 e+f 其中 e 为整数类型、f 为浮点型的重载。
-
但通过可行自动转换,有三种可能的重载:
-
1转为 AbstractFloat 值1.0(等级 4),2.5保持为 AbstractFloat(等级 0)。
-
-
第一个重载为最优候选,类型检查成功。
-
最终计算以 AbstractFloat 执行
1.0 + 2.5。
示例:let x = 1 + 2.5;
-
此示例与上例类似,区别在于
x不能解析为抽象数值类型。 -
该声明效果等价于
let x : f32 = 1.0f + 2.5f;。
示例:1u + 2.5 会导致着色器创建错误:
-
1u是 u32 类型表达式。 -
2.5是 AbstractFloat 类型表达式。 -
不存在有效的重载候选:
示例:-1 * i32(-2147483648) 不会导致着色器创建错误:
-
-1是 AbstractInt 类型表达式。 -
i32(-2147483648)是 i32 类型表达式。 -
乘法运算符没有针对这两种类型的重载,并且 i32 无法上转换为 AbstractInt。
-
唯一可行的自动转换是将 AbstractInt 转为 i32,因此:
// 显式类型的无符号整数字面量。 var u32_1 = 1u ; // 变量为 u32 // 显式类型的有符号整数字面量。 var i32_1 = 1i ; // 变量为 i32 // 显式类型的浮点数字面量。 var f32_1 = 1f ; // 变量为 f32 // 显式类型的无符号整数字面量不能取负号。 var u32_neg = - 1u ; // 非法:u32 不支持一元负号 // 若需要具体类型,但语句或表达式无处强制具体类型,整数被解释为 i32: // let 声明的初值必须可构造(或为指针)。 // 从 AbstractInt 到可构造类型的最高优先级自动转换为 AbstractInt 到 i32(等级 2),因此 '1' 推断为 i32。 let some_i32 = 1 ; // 等价于 let some_i32: i32 = 1i; // 从声明类型推断。 var i32_from_type : i32= 1 ; // 变量为 i32。AbstractInt 到 i32,转换等级 2 var u32_from_type : u32= 1 ; // 变量为 u32。AbstractInt 到 u32,转换等级 3 // 无后缀整数字面量可按需转换为浮点: // 自动将 AbstractInt 转为 f32,转换等级 5。 var f32_promotion : f32= 1 ; // 变量为 f32 // 非法:浮点不能自动转换为整数 var i32_demotion : i32= 1.0 ; // 非法 // 从表达式推断。 var u32_from_expr = 1 + u32_1 ; // 变量为 u32 var i32_from_expr = 1 + i32_1 ; // 变量为 i32 // 值必须可表示。 let u32_too_large : u32= 1234567890123456890 ; // 非法,溢出 let i32_too_large : i32= 1234567890123456890 ; // 非法,溢出 let u32_large : u32= 2147483649 ; // 合法 let i32_large : i32= 2147483649 ; // 非法,溢出 let f32_out_of_range1 = 0x1p500 ; // 非法,超范围 let f32_hex_lost_bits = 0x1.0000000001p0 ; // 非法,f32 不能精确表示 // 最小整数:对 AbstractInt 取一元负号,然后推断 i32。 // AbstractInt 到可构造类型(最低转换等级)首选为 i32。 let i32_min = - 2147483648 ; // 类型为 i32 // 非法。选择 AbstractInt 到 i32,但值超范围。 let i32_too_large_2 = 2147483648 ; // 非法。 // 子表达式可解析为 AbstractInt 和 AbstractFloat。 // 下例均合法,变量值为 6u。 var u32_expr1 = ( 1 + ( 1 + ( 1 + ( 1 + 1 )))) + 1u ; var u32_expr2 = 1u + ( 1 + ( 1 + ( 1 + ( 1 + 1 )))); var u32_expr3 = ( 1 + ( 1 + ( 1 + ( 1u + 1 )))) + 1 ; var u32_expr4 = 1 + ( 1 + ( 1 + ( 1 + ( 1u + 1 )))); // 基于内建函数参数的推断。 // 最优候选是 clamp(i32,i32,i32)->i32 let i32_clamp = clamp ( 1 , - 5 , 5 ); // 最优候选是 clamp(u32,u32,u32)。 // 字面量用自动转换 AbstractInt 到 u32。 let u32_clamp = clamp ( 5 , 0 , u32_from_expr ); // 最优候选是 clamp(f32,f32,f32)->f32 // 字面量用自动转换 AbstractInt 到 f32。 let f32_clamp = clamp ( 0 , f32_1 , 1 ); // 下例均提升为 f32,初值为 10f。 let f32_promotion1 = 1.0 + 2 + 3 + 4 ; let f32_promotion2 = 2 + 1.0 + 3 + 4 ; let f32_promotion3 = 1f + (( 2 + 3 ) + 4 ); let f32_promotion4 = (( 2 + ( 3 + 1f )) + 4 ); // 类型规则违规。 // 非法,初值只能解析为 f32: // AbstractFloat 无法自动转换为 u32。 let mismatch : u32= 1.0 ; // 非法。clamp 不允许混合符号参数的重载。 let ambiguous_clamp = clamp ( 1u , 0 , 1i ); // 推断在语句级别完成。 // let 声明的初值必须可构造(或为指针)。 // AbstractInt 到可构造类型的最高优先级自动转换为 i32(等级 2),所以 '1' 推断为 i32。 let some_i32 = 1 ; // 等价于 let some_i32: i32 = 1i; let some_f32 : f32= some_i32 ; // 类型错误:i32 不能赋值给 f32 // 另一个溢出情况 let overflow_u32 = ( 1 - 2 ) + 1u ; // 非法,-1 不在 u32 范围内 // 理论值超出 32 位,但又被拉回范围 let out_and_in_again = ( 0x1ffffffff / 8 ); // 类似,但非法 let out_of_range = ( 0x1ffffffff / 8u ); // 需用 32 位计算,0x1ffffffff 超范围
6.2.2. 布尔类型
bool 类型包含 true 和
false 两个值。
| 前提条件 | 结论 | 说明 |
|---|---|---|
true: bool
| true 值。 | |
false: bool
| false 值。 |
6.2.3. 整数类型
u32 类型是一组 32 位无符号整数。
i32 类型是一组 32 位有符号整数。 采用二进制补码表示,符号位为最高位。
| 类型 | 最小值 | 最大值 |
|---|---|---|
| i32 | i32(-2147483648) | 2147483647i |
| i32(-0x80000000) | 0x7fffffffi | |
| u32 | 0u | 4294967295u |
| 0x0u | 0xffffffffu |
注意: AbstractInt 也是一种整数类型。
6.2.4. 浮点类型
f32 类型是一组 32 位浮点值,符合 IEEE-754 binary32(单精度)格式。 详情见 § 15.7 浮点数运算。
f16 类型是一组 16 位浮点值,符合
IEEE-754 binary16(半精度)格式。如果未包含
enable f16; 指令启用
f16 扩展,则使用 f16 类型会导致着色器创建错误。详情见 § 15.7 浮点数运算。
下表列出了浮点类型的部分极值。 每种类型都有对应的负极值。
| 类型 | 最小正非规格化数 | 最小正规格化数 | 最大正有限值 | 最大有限 2 的幂 |
|---|---|---|---|---|
| f32 | 1.40129846432481707092e-45f | 1.17549435082228750797e-38f | 3.40282346638528859812e+38f | 0x1p+127f |
| 0x1p-149f | 0x1p-126f | 0x1.fffffep+127f | ||
| f16 | 5.9604644775390625e-8h | 0.00006103515625h | 65504.0h | 0x1p+15h |
| 0x1p-24h | 0x1p-14h | 0x1.ffcp+15h |
注意: AbstractFloat 也是一种浮点类型。
6.2.5. 标量类型
标量类型包括 bool、AbstractInt、 AbstractFloat、i32、u32、f32 和 f16。
数值标量类型包括 AbstractInt、 AbstractFloat、i32、u32、f32 和 f16。
整数标量类型包括 AbstractInt、i32 和 u32。
标量转换 指将一个标量类型的值映射为另一标量类型的值。 通常,结果值会尽量接近原值,但受目标类型限制。 标量转换发生在:
6.2.6. 向量类型
向量是由 2、3 或 4 个标量分量组成的有序集合。
| 类型 | 说明 |
|---|---|
| vecN<T> | N 个 T 类型分量的向量。 N 必须为 {2, 3, 4},T必须是标量类型之一。 称 T 为该向量的分量类型。 |
如果一个向量的分量类型为数值标量,则称其为数值向量。
向量的常见用途包括:
-
表示方向和大小。
-
表示空间中的位置。
-
表示某种颜色空间下的颜色。 例如,分量可为红、绿、蓝的强度,第四分量可为 alpha(不透明度)值。
许多针对向量(和矩阵)的操作是按分量进行的,即结果通过对每个标量分量独立操作得到。
let x : vec3< f32> = a + b ; // a 和 b 都是 vec3<f32> // x[0] = a[0] + b[0] // x[1] = a[1] + b[1] // x[2] = a[2] + b[2]
| 预声明别名 | 原始类型 | 限制 |
|---|---|---|
| vec2i | vec2<i32> | |
| vec3i | vec3<i32> | |
| vec4i | vec4<i32> | |
| vec2u | vec2<u32> | |
| vec3u | vec3<u32> | |
| vec4u | vec4<u32> | |
| vec2f | vec2<f32> | |
| vec3f | vec3<f32> | |
| vec4f | vec4<f32> | |
| vec2h | vec2<f16> | 需要 f16 扩展。 |
| vec3h | vec3<f16> | |
| vec4h | vec4<f16> |
6.2.7. 矩阵类型
矩阵是一组 2、3 或 4 个浮点向量的组合序列。
| 类型 | 说明 |
|---|---|
| matCxR<T> | C 列 R 行、类型为 T 的矩阵,其中 C 和 R 都属于 {2, 3, 4},T 必须为 f32、f16 或 AbstractFloat。 换言之,可以视为 C 个类型为 vecR<T> 的列向量。 |
矩阵的主要用途是表达线性变换。 在这种解释下,矩阵的向量被视为列向量。
乘积操作符(*)用于:
-
通过标量大小对变换进行缩放。
-
将变换应用于向量。
-
与其他矩阵组合变换。
参见 § 8.7 算术表达式。
| 预声明别名 | 原始类型 | 限制 |
|---|---|---|
| mat2x2f | mat2x2<f32> | |
| mat2x3f | mat2x3<f32> | |
| mat2x4f | mat2x4<f32> | |
| mat3x2f | mat3x2<f32> | |
| mat3x3f | mat3x3<f32> | |
| mat3x4f | mat3x4<f32> | |
| mat4x2f | mat4x2<f32> | |
| mat4x3f | mat4x3<f32> | |
| mat4x4f | mat4x4<f32> | |
| mat2x2h | mat2x2<f16> | 需要 f16 扩展。 |
| mat2x3h | mat2x3<f16> | |
| mat2x4h | mat2x4<f16> | |
| mat3x2h | mat3x2<f16> | |
| mat3x3h | mat3x3<f16> | |
| mat3x4h | mat3x4<f16> | |
| mat4x2h | mat4x2<f16> | |
| mat4x3h | mat4x3<f16> | |
| mat4x4h | mat4x4<f16> |
6.2.8. 原子类型
| 类型 | 说明 |
|---|---|
| atomic<T> | 类型为 T 的原子。T必须是 u32 或 i32。 |
表达式不得求值为原子类型。
原子类型只能由 workgroup 地址空间的变量,或具有 read_write 访问模式的 存储缓冲区变量实例化。
该类型操作的内存作用域由其实例化的地址空间决定。
workgroup 地址空间中的原子类型内存作用域为 Workgroup,
storage
地址空间中的原子类型内存作用域为 QueueFamily。
原子修改是指对原子对象的任何操作,该操作设置对象内容。 即使新值与对象原有值相同,该操作也算作修改。
在 WGSL 中,原子修改在每个对象内是互相有序的。 即,在着色器阶段执行期间,对于每个原子对象 A,所有代理观察到的对 A 的修改操作顺序一致。 不同原子对象的排序可能无关,也不暗示因果关系。 注意,workgroup 空间的变量在同一 workgroup 内共享,但不同 workgroup 之间不共享。
6.2.9. 数组类型
数组是一组可索引的元素值序列。
| 类型 | 说明 |
|---|---|
| array<E,N> | 定长数组,包含
N 个 E 类型的元素。 N 称为该数组的元素数量。 |
| array<E> | 运行时大小数组,元素类型为 E。
仅可在特定上下文中出现。 |
数组的第一个元素索引为 0,后续元素依次为下一个整数索引。见 § 8.5.3 数组访问表达式。
表达式不得求值为运行时大小数组类型。
定长数组的元素数量表达式 N 须满足以下约束:
注意: 如果 N 依赖于任何override 声明,则元素数量值会在管线创建时确定,否则在着色器模块创建时确定。
注意: 若要类型等价,任何不是常量表达式的 override 表达式必须为标识符。 见 由可重写常量定长的 workgroup 变量
运行时大小数组的元素数量由与对应存储缓冲区变量关联的缓冲区绑定大小决定。 见 § 13.3.4 缓冲区绑定决定运行时大小数组元素数量。
数组元素类型必须是以下之一:
注意: 元素类型必须是普通类型。
只有在以下所有条件均为真时,两个数组类型才相同:
-
具有相同的元素类型。
-
元素数量说明匹配,即下列之一为真:
// array<f32,8> 和 array<i32,8> 是不同类型: // 元素类型不同 var < private> a : array< f32, 8 > ; var < private> b : array< i32, 8 > ; var < private> c : array< i32, 8u > ; // array<i32,8> 和 array<i32,8u> 是相同类型 const width = 8 ; const height = 8 ; // array<i32,8>、array<i32,8u> 和 array<i32,width> 是相同类型。 // 它们的元素数量均为 8。 var < private> d : array< i32, width > ; // array<i32,height> 和 array<i32,width> 是相同类型。 var < private> e : array< i32, width > ; var < private> f : array< i32, height > ;
注意: 由可重写常量定长的数组类型仅能用作memory view,且仅限于 workgroup 地址空间。 这包括 workgroup 变量的存储类型。 见 § 7 变量和常量声明。
override blockSize = 16 ; var < workgroup> odds : array< i32, blockSize > ; var < workgroup> evens : array< i32, blockSize > ; // 类型相同 // 以下均与 'odds' 和 'evens' 类型不同。 // 类型不同:不是标识符 'blockSize' var < workgroup> evens_0 : array< i32, 16 > ; // 类型不同:元素数量用表达式。 var < workgroup> evens_1 : array< i32,( blockSize * 2 / 2 ) > ; // 类型不同:用括号而非标识符。 var < workgroup> evens_2 : array< i32,( blockSize ) > ; // 非法示例:可重写元素数量只能出现在最外层。 // var<workgroup> both: array<array<i32,blockSize>,2>; // 非法示例:仅 workgroup 变量允许可重写元素数量。 // var<private> bad_address_space: array<i32,blockSize>;
6.2.10. 结构体类型
结构体是由若干具名成员值组成的具名分组。
| 类型 | 说明 |
|---|---|
struct AStructName {M1 : T1, ... MN : TN, } |
结构体类型声明,名称为标识符
AStructName,包含 N 个成员,
其中第 i 个成员名为标识符 Mi,类型为
Ti。
N 必须至少为 1。 同一结构体类型的两个成员不得重名。 |
结构体类型在模块作用域声明。 在程序源码其它地方,结构体类型用其标识符名称表示。 见 § 5 声明与作用域。
只有当且仅当结构体类型同名时,两结构体类型才相同。
结构体成员类型必须为以下之一:
注意: 所有用户声明的结构体类型都是具体类型。
注意: 每个成员类型必须为普通类型。
结构体成员和数组元素类型限制的部分后果:
// 一个拥有三个成员的结构体。 struct Data { a : i32, b : vec2< f32> , c : array< i32, 10 > , // 末尾逗号可选 } // 声明一个类型为 Data 的变量。 var < private> some_data : Data ;
'struct' ident struct_body_decl
'{' struct_member ( ',' struct_member ) * ',' ? '}'
以下属性可应用于结构体成员:
属性 builtin、location、blend_src、 interpolate、 invariant 都是IO 属性。 结构体 S 的成员带有 IO 属性 仅在 S 用作形参或返回类型(作为入口点)时生效。 见 § 13.3.1 阶段间输入输出接口。
属性 align 和 size 是布局属性, 若结构体类型用于定义uniform 缓冲区或storage 缓冲区时可能需要。 见 § 14.4 内存布局。
// 运行时数组 alias RTArr = array< vec4< f32>> ; struct S { a : f32, b : f32, data : RTArr } @group ( 0 ) @binding ( 0 ) var < storage> buffer : S ;
6.2.11. 复合类型
如果一个类型具有用其他类型组合表达的内部结构,则称为复合类型。 其内部各部分不重叠,称为组件。 复合值可以被分解为其组件。见 § 8.5 复合值分解表达式。
复合类型包括:
对于复合类型 T,其嵌套深度,记作 NestDepth(T),定义如下:
-
向量类型为 1
-
矩阵类型为 2
-
数组类型为 1 + NestDepth(E),其中 E 为元素类型
-
若 T 为结构体类型,成员类型为 M1,...,MN,则为 1 + max(NestDepth(M1),..., NestDepth(MN))
6.2.12. 可构造类型
许多类型的值可以被创建、加载、存储、传递进函数,也可以作为函数的返回值。 我们称为可构造类型。
如果类型属于以下之一,则称为可构造类型:
注意: 所有可构造类型都有创建时固定占用。
注意: 原子类型和运行时大小数组类型都不是可构造类型。 包含原子类型或运行时大小数组的复合类型也不是可构造类型。
6.2.13. 固定占用类型
内存占用是指变量用于存储内容所需的内存单元数。 变量的内存占用取决于其存储类型,并将在着色器生命周期的某个时刻最终确定。 大多数变量会在着色器创建时很早就定型。 有些变量可能在管线创建时定型,另一些则晚至着色器执行启动时。
如果某类型的具体化大小可在着色器创建时完全确定,则称其具有创建时固定占用。
如果类型的大小可在管线创建时完全确定,则称其具有固定占用。
注意: 所有具体的创建时固定占用和固定占用类型都是可存储类型。
注意: 管线创建依赖于着色器创建,因此具有创建时固定占用的类型也具有固定占用。
具有创建时固定占用的类型包括:
注意: 元素数量为override 表达式且不是常量表达式的定长数组,仅能用作 memory view,且仅限于 workgroup 地址空间。这包括 workgroup 变量的存储类型。
注意: 固定占用类型可以直接或间接包含原子类型,而可构造类型则不能。
注意: 固定占用类型不包括运行时大小数组,以及任何包含运行时大小数组的结构体。
6.3. 枚举类型
枚举类型是一组有限的具名值。 枚举用于区分某个特定概念的一组可能性,比如一组有效的纹素格式。
枚举项是某个枚举中的一个具名值。 每个枚举项都与其他所有枚举项、以及所有其他类型的值都不同。
WGSL 源码中没有声明新枚举项或新枚举类型的机制。
注意: 枚举项可用作模板参数。
6.3.1. 预声明枚举项
下表列出了 WGSL 中的 枚举类型、它们的 预声明 枚举元,以及这些 枚举 类型所需的 语言扩展。 这些枚举类型存在,但不能在 WGSL 源码中直接拼写。
6.4. 内存视图
除了对普通值进行计算外,WGSL 程序还经常通过内存访问操作,从内存读取值或向内存写入值。 每次内存访问都是通过内存视图进行的。
内存视图包含:
内存视图的访问模式必须由地址空间支持。见§ 7 变量和常量声明。
6.4.1. 可存储类型
变量中包含的值必须为可存储类型。 可存储类型可以由 WGSL 明确定义其表示方式(见 § 14.4.4 值的内部布局),也可以是透明类型,比如纹理和采样器。
如果类型既是具体类型又属于以下之一,则称为可存储类型:
注意: 即,可存储类型包含具体普通类型、纹理类型和采样器类型。
6.4.2. 主机可共享类型
主机可共享类型用于描述主机与 GPU 之间共享的缓冲区内容,或主机和 GPU 间无需格式转换即可拷贝的内容。 用于此目的时,该类型还可以按布局属性进行约束,见 § 14.4 内存布局。 如 § 7.3 var 声明所述,uniform 缓冲区和storage 缓冲区变量的存储类型 必须为主机可共享类型。
若类型既为具体类型,又属于以下之一,则称为主机可共享类型:
注意: 关于阶段间输入输出类型的限制见 § 13.3.1 阶段间输入输出接口及后续章节。 这些类型也有大小约束,但计数方式不同。
注意: 纹理和采样器也可以主机与 GPU 共享,但其内容是透明的。 本节中的主机可共享类型专用于storage 和 uniform 缓冲区。
6.4.3. 引用类型与指针类型
WGSL 有两种类型用于表示内存视图: 引用类型和指针类型。
| 约束 | 类型 | 说明 |
|---|---|---|
| AS 是地址空间, T 是可存储类型, AM 是访问模式 | ref<AS,T,AM> |
引用类型
标识一组内存视图,这些视图指向
AS 地址空间中存储 T 类型值的内存位置,
支持由 AM 描述的内存访问。
此处 T 为存储类型。 引用类型不会出现在 WGSL 源中,仅用于分析 WGSL 模块。 |
| AS 是地址空间, T 是可存储类型, AM 是访问模式 | ptr<AS,T,AM> |
指针类型
标识一组内存视图,这些视图指向
AS 地址空间中存储 T 类型值的内存位置,
支持由 AM 描述的内存访问。
此处 T 为存储类型。 指针类型可出现在 WGSL 源中。 |
只有地址空间、存储类型和访问模式均相同时,两个指针类型才相同。
在分析 WGSL 模块时,引用类型和指针类型都完全由地址空间、可存储类型和访问模式参数化。 在本规范的代码示例中,注释都会展示这种完全参数化的形式。
但在 WGSL 源代码文本中:
-
引用类型不得出现。
-
指针类型可以出现。
fn my_function ( /* 'ptr<function,i32,read_write>' 是指向 function 地址空间中存储 i32 值的指针类型。 这里 'i32' 是存储类型。 隐含访问模式为 'read_write'。 访问模式默认见“地址空间”章节。*/ ptr_int : ptr< function, i32> , // 'ptr<private,array<f32,50>,read_write>' 是指向 private 地址空间中存储 50 个 f32 元素数组的指针类型。 // 这里存储类型为 'array<f32,50>'。 // 隐含访问模式为 'read_write'。 // 访问模式默认见“地址空间”章节。 ptr_array : ptr< private, array< f32, 50 >> ) { }
引用类型和指针类型都是一组内存视图: 某个具体内存视图既关联唯一的引用值,也关联唯一的指针值:
每个类型为 ptr<AS,T,AM> 的指针值 p,都对应一个类型为 ref<AS,T,AM> 的唯一引用值 r,反之亦然。p 和 r 描述同一个内存视图。
6.4.4. 有效与无效内存引用
引用的形成方式详见 § 6.4.8 创建引用和指针值。 通常,有效引用的形成方式有:
通常,无效内存引用的形成方式有:
有效指针是指对应于有效引用的指针。 无效指针是指对应于无效内存引用的指针。
6.4.5. 起源变量
指针值的起源变量定义为其对应引用值的起源变量。
注意: 起源变量是一个动态概念。函数形参的起源变量取决于函数的调用点。 不同的调用点可能传入指向不同起源变量的指针。
有效引用总是对应于某变量的一部分或全部内存位置的非空内存视图。
在下例中,只有当 i 是 0 或 1 时,the_particle.position[i] 是有效引用。
当 i 为 2 时,该引用为无效内存引用,但它实际上会对应于 the_particle.color_index
的内存位置。
6.4.6. 越界访问
越界访问是程序缺陷,因为如果真的按原样执行,通常会:
因此,实现不会按原样执行该访问。 执行越界访问会产生动态错误。
注意: 错误解释存储类型的例子见
上一节的示例。
当 i 为 2 时,表达式 the_particle.velocity[i] 类型为
ref<storage,f32,read_write>,即为存储类型为 f32 的内存视图。
但实际内存位置分配给了 color_index 成员,因此实际存储值类型为 i32。
这些结果包括但不限于:
- Trap
-
着色器调用立即终止,着色器阶段输出被置为零值。
- 无效加载
-
从无效引用加载时,可能返回:
-
若起源变量是uniform 缓冲区或storage 缓冲区, 则返回绑定到起源变量的 WebGPU
GPUBuffer的任意内存位置的值 -
若起源变量不是 uniform 缓冲区或 storage 缓冲区, 则返回起源变量的任意内存位置的值
-
返回引用存储类型的零值
-
若加载值为向量,则为 (0, 0, 0, x),其中 x 为:
-
整型分量为 0、1 或最大正值
-
浮点分量为 0.0 或 1.0
-
-
- 无效存储
-
向无效引用写入时,可能:
-
若起源变量为storage 缓冲区, 则将值写入绑定到起源变量的 WebGPU
GPUBuffer的任意内存位置上 -
若起源变量不是storage 缓冲区, 则将值写入起源变量的任意内存位置上
-
不执行写入操作。
-
如果无效加载或写入被重定向到共享地址空间变量内部的不同位置,可能导致数据竞争。 例如,多个并发执行的调用可能都被重定向到数组的第一个元素。 如果至少有一个访问是写操作,且未同步,则结果是数据竞争,从而导致动态错误。
越界访问会破坏一致性分析的前提。 例如,若调用因越界访问提前终止,则无法再参与集体操作。 特别是,调用 workgroupBarrier 可能导致着色器挂起,求导操作可能产出无效结果。
6.4.7. 引用和指针的用例
引用和指针的区别体现在它们的用法:
-
变量的类型是引用类型。
-
取地址操作(一元
&)将引用值转换为其对应的指针值。 -
间接寻址操作(一元
*)将指针值转换为其对应的引用值。 -
let 声明可以是指针类型,但不能是引用类型。
-
形参可以为指针类型,但不能为引用类型。
-
加载规则(Load Rule):在函数内部,引用会自动解引用(读取),以满足类型规则:
-
在函数中,当引用表达式 r(存储类型为 T)在语句或表达式中被使用,且
-
r 的访问模式为read 或 read_write,并且
-
所有可能匹配的类型规则都要求 r 为类型 T 的值时,
-
则认为类型规则已满足,并且
-
在该上下文中对 r 求值的结果是其所引用内存位置中存储的 T 类型的值(求值时刻)。 即,执行读访问以产出结果值。
-
这样定义引用可简化变量的使用:
@compute @workgroup_size ( 1 ) fn main () { // 'i' 的引用类型为 ref<function,i32,read_write> // 'i' 的内存位置存储 i32 值 0。 var i : i32= 0 ; // 'i + 1' 只能匹配类型规则,要求 'i' 子表达式为 i32 类型。 // 所以 'i + 1' 表达式类型为 i32,求值时 'i' 子表达式为 'i' 当前内存存储的 i32 值。 let one : i32= i + 1 ; // 更新 'i' 所引用内存位置的值为 2。 i = one + 1 ; // 更新 'i' 所引用内存位置的值为 5。 // 右值先求值后再赋值。 i = i + 3 ; }
var < private> age : i32; fn get_age () -> i32{ // return 语句中的表达式类型必须为 'i32',需要与函数声明的返回类型一致。 // 'age' 表达式类型为 ref<private,i32,read_write>。 // 应用加载规则(Load Rule),因为引用存储类型与表达式要求类型一致,无其他类型规则匹配。 // 在此上下文对 'age' 求值即为当前 'age' 所引用内存中的 i32 值。 return age ; } fn caller () { age = 21 ; // copy_age 常量将获得 i32 值 21。 let copy_age : i32= get_age (); }
这样定义指针可实现两个关键用法:
struct Particle { position: vec3< f32> , velocity : vec3< f32> } struct System { active_index : i32, timestep : f32, particles : array< Particle , 100 > } @group ( 0 ) @binding ( 0 ) var < storage, read_write> system : System ; @compute @workgroup_size ( 1 ) fn main () { // 创建指向 storage 内存中特定 Particle 的指针。 let active_particle = & system . particles [ system . active_index ]; let delta_position : vec3< f32> = ( * active_particle ). velocity * system . timestep ; let current_position : vec3< f32> = ( * active_particle ). position; ( * active_particle ). position= delta_position + current_position ; }
fn add_one ( x : ptr< function, i32> ) { /* 更新 'x' 的内存位置为下一个更大的整数值(或溢出为 i32 的最大负值)。 左值一元 * 将指针转为可赋值的引用,默认访问模式为 read_write。 右值: - 一元 * 将指针转为引用,访问模式为 read_write。 - 唯一匹配的类型规则为加法(+),要求 '*x' 类型为 i32,即 '*x' 的存储类型。 - 应用加载规则,'*x' 求值为 '*x' 当前内存的值,即 i32 的 0。 - 加 1 得右值为 1。*/ 将 1 存入 '*x' 的内存。*/ * x = * x + 1 ; } @compute @workgroup_size ( 1 ) fn main () { var i : i32= 0 ; // 修改 'i' 的内容为 1。 // 用一元 & 获取 'i' 的指针值。 // 这明确表示被调函数拥有 'i' 的内存访问权,可以修改它。 add_one ( & i ); let one : i32= i ; // 'one' 的值为 1。 }
6.4.8. 引用和指针值的生成
引用值通过以下方式之一生成:
-
对指针使用间接寻址(一元
*)操作。 -
-
对于存储类型为向量的内存视图,追加单字母向量访问短语,得到该向量指定分量的引用。 见 § 8.5.1.3 向量内存视图的分量引用。
-
对于存储类型为结构体的内存视图,追加成员访问短语,得到该结构体指定成员的引用。 见 § 8.5.4 结构体访问表达式。
-
-
-
对于存储类型为向量的内存视图,追加数组索引访问短语,得到该向量指定分量的引用。 见 § 8.5.1.3 向量内存视图的分量引用。
-
对于存储类型为矩阵的内存视图,追加数组索引访问短语,得到该矩阵指定列向量的引用。 见 § 8.5.2 矩阵访问表达式。
-
对于存储类型为数组的内存视图,追加数组索引访问短语,得到该数组指定元素的引用。 见 § 8.5.3 数组访问表达式。
-
在所有情况下,结果的访问模式与原始引用的访问模式相同。
struct S { age : i32, weight : f32} var < private> person : S ; // 在其他地方,'person' 表示变量底层内存的引用,类型为 ref<private,S,read_write>。 fn f () { var uv : vec2< f32> ; // 该函数体的剩余部分中,'uv' 表示底层内存的引用,类型为 ref<function,vec2<f32>,read_write>。 // 赋值左值求值过程: // 对 'uv.x' 求值以获得引用: // 1. 首先对 'uv' 求值,得到 'uv' 变量内存的引用,类型为 ref<function,vec2<f32>,read_write>。 // 2. 然后应用 '.x' 向量访问短语,得到前一步引用的向量的第一个分量的内存引用。 // 类型为 ref<function,f32,read_write>。 // 赋值右值求值结果为 f32 值 1.0。 // 将 f32 值 1.0 存入 uv.x 的内存位置。 uv . x = 1.0 ; // 赋值左值求值过程: // 对 'uv[1]' 求值以获得引用: // 1. 首先对 'uv' 求值,得到 'uv' 变量内存的引用,类型为 ref<function,vec2<f32>,read_write>。 // 2. 然后应用 '[1]' 数组索引短语,得到前一步引用的向量的第二个分量的内存引用。 // 类型为 ref<function,f32,read_write>。 // 赋值右值求值结果为 f32 值 2.0。 // 将 f32 值 2.0 存入 uv[1] 的内存位置。 uv [ 1 ] = 2.0 ; var m : mat3x2< f32> ; // 对 'm[2]' 求值: // 1. 首先对 'm' 求值,得到 m 变量内存引用,类型为 ref<function,mat3x2<f32>,read_write>。 // 2. 应用 '[2]' 索引短语,得到第3列向量的内存引用,类型为 ref<function,vec2<f32>,read_write>。 // let 声明需要初值类型为 vec2<f32>,应用加载规则,初始化值为 'm[2]' 内存当前载入的 vec2<f32>。 let p_m_col2 : vec2< f32> = m [ 2 ]; var A : array< i32, 5 > ; // 对 'A[4]' 求值: // 1. 首先对 'A' 求值,得到 A 变量内存引用,类型为 ref<function,array<i32,5>,read_write>。 // 2. 应用 '[4]' 索引短语,得到数组第5个元素的内存引用,类型为 ref<function,i32,read_write>。 // let 声明要求右值类型为 i32,应用加载规则,初值为 'A[4]' 内存当前载入的 i32 值。 let A_4_value : i32= A [ 4 ]; // 对 'person.weight' 求值: // 1. 首先对 'person' 求值,得到模块作用域变量 'person' 的内存引用,类型为 ref<private,S,read_write>。 // 2. 应用 '.weight' 成员访问短语,得到第二个成员的内存引用,类型为 ref<private,f32,read_write>。 // let 声明要求右值类型为 f32,应用加载规则,初值为 'person.weight' 当前载入的 f32 值。 let person_weight : f32= person . weight ; // 也可以用指针生成引用,语法相同。 let uv_ptr = & uv ; // 该函数体剩余部分,'uv_ptr' 表示 'uv' 内存的指针,类型为 ptr<function,vec2<f32>,read_write>。 // 赋值左值求值: // 对 '*uv_ptr' 求值,得到引用: // 1. 对 'uv_ptr' 求值,得到 'uv' 变量内存的指针,类型为 ptr<function,vec2<f32>,read_write>。 // 2. 应用间接寻址表达式运算符,得到 'uv' 的内存引用。 // 右值为 vec2<f32> 值 (1.0, 2.0),写入 uv 的内存。 * uv_ptr = vec2f( 1.0 , 2.0 ); // 赋值左值求值: // 对 'uv_ptr.x' 求值,得到引用: // 1. 对 'uv_ptr' 求值,得到 'uv' 变量内存的指针,类型为 ptr<function,vec2<f32>,read_write>。 // 2. 应用 '.x' 向量访问短语,得到第一个分量内存引用,类型为 ref<function,f32,read_write>。 // 右值为 f32 值 1.0,写入 uv.x 的内存。 uv_ptr . x = 1.0 ; // 赋值左值求值: // 对 'uv_ptr[1]' 求值,得到引用: // 1. 对 'uv_ptr' 求值,得到 'uv' 变量内存的指针,类型为 ptr<function,vec2<f32>,read_write>。 // 2. 应用 '[1]' 索引短语,得到向量第二分量的内存引用,类型为 ref<function,f32,read_write>。 // 右值为 f32 值 2.0,写入 uv[1] 的内存。 uv_ptr [ 1 ] = 2.0 ; let m_ptr = & m ; // 对 'm_ptr[2]' 求值: // 1. 对 'm_ptr' 求值,得到 m 变量内存的指针,类型为 ptr<function,mat3x2<f32>,read_write>。 // 2. 应用 '[2]' 索引短语,得到第3列向量内存引用,类型为 ref<function,vec2<f32>,read_write>。 // let 声明需初值为 vec2<f32>,应用加载规则,初值为 m[2] 当前载入的 vec2<f32>。 let p_m_col2 : vec2< f32> = m_ptr [ 2 ]; let A_ptr = & A ; // 对 'A[4]' 求值: // 1. 对 'A_ptr' 求值,得到 A 变量内存的指针,类型为 ptr<function,array<i32,5>,read_write>。 // 2. 应用 '[4]' 索引短语,得到数组第5个元素内存引用,类型为 ref<function,i32,read_write>。 // let 声明需右值为 i32,应用加载规则,初值为 'A[4]' 当前载入的 i32 值。 let A_4_value : i32= A_ptr [ 4 ]; let person_ptr = & person ; // 对 'person.weight' 求值: // 1. 对 'person_ptr' 求值,得到模块作用域变量 'person' 的指针,类型为 ptr<private,S,read_write>。 // 2. 应用 '.weight' 成员访问短语,得到第二个成员的内存引用,类型为 ref<private,f32,read_write>。 // let 声明需右值为 f32,应用加载规则,初值为 'person.weight' 当前载入的 f32 值。 let person_weight : f32= person_ptr . weight ; }
指针值通过以下方式之一生成:
在所有情况下,结果的访问模式与原始指针的访问模式相同。
// 在 private 地址空间声明存储 f32 值的变量。 var < private> x : f32; fn f () { // 在 function 地址空间声明存储 i32 值的变量。 var y : i32; // 'x' 解析到模块作用域变量 'x',类型为 ref<private,f32,read_write>。 // 一元 & 操作将引用转换为指针,访问模式同原变量,完整类型为 ptr<private,f32,read_write>。 // read_write 为 function 地址空间默认访问模式,可省略。 let x_ptr : ptr< private, f32> = & x ; // 'y' 解析到函数作用域变量 'y',类型为 ref<private,i32,read_write>。 // 一元 & 操作转换为指针,访问模式默认 read_write。 let y_ptr : ptr< function, i32> = & y ; // 新变量,与模块作用域变量同名但不同实例。 var x : u32; // 此处 'x' 解析为上面声明的函数作用域变量,类型为 ref<function,u32,read_write>。 // 一元 & 操作转换为指针,访问模式默认 read_write。 let inner_x_ptr : ptr< function, u32> = & x ; }
6.4.9. 与其他语言中引用和指针的比较
本节为说明性内容,并非规范要求。
WGSL 中的引用和指针比其他语言受限得多。 具体包括:
-
在 WGSL 中,引用不能直接声明为其他引用或变量的别名,无论是变量还是形参。
-
在 WGSL 中,函数不得返回指针或引用。
-
在 WGSL 中,无法在整数值和指针值之间转换。
-
在 WGSL 中,无法强制将一个指针值的类型转换为另一个指针类型。
-
复合组件引用表达式不同: 它接受复合值的引用并产生该复合值内部某个组件或元素的引用。 在 WGSL 中,这些被认为是不同的引用,即使在底层实现抽象中它们可能具有相同的机器地址。
-
-
在 WGSL 中,无法强制将一个引用值的类型转换为另一个引用类型。
-
在 WGSL 中,无法更改指针或引用的访问模式。
-
相比之下,C++ 可自动将非 const 指针转换为 const 指针,并有
const_cast可将 const 值转换为非 const。
-
-
在 WGSL 中,无法从“堆”中分配新内存。
-
在 WGSL 中,无法显式销毁变量。 WGSL 变量的内存仅在变量超出作用域时变得不可访问。
注意: 根据上述规则,不可能形成“悬空”指针,即不再引用“活跃”起源变量内存的指针。 内存视图虽然可能是无效内存引用, 但它永远不会访问未关联于 起源变量或缓冲区的内存位置。
6.5. 纹理与采样器类型
纹素(texel)是用作纹理中最小独立访问单元的标量或向量。 texel 是 texture element 的缩写。
纹理是支持用于渲染的特殊操作的一组纹素。 在 WGSL 中,这些操作通过纹理内建函数进行调用。 完整列表见 § 17.7 纹理内建函数。
WGSL 的纹理对应于 WebGPU GPUTexture。
纹理具有以下特性:
- 纹素格式
-
每个纹素的数据表示。见 § 6.5.1 纹素格式。
- 维度
-
网格坐标的维数,以及坐标的解释方式。 维数为 1、2 或 3。 大多数纹理使用笛卡尔坐标。 立方体纹理有六个正方形面,采样时以三维坐标作为从原点指向以原点为中心立方体的方向向量。
- 尺寸
-
每个维度上网格坐标的范围。这是mip level的函数。
- mip 层级数
-
采样纹理和深度纹理的 mip 层级数至少为 1,存储纹理的值恒为 1。
Mip level 0 包含完整尺寸的纹理。 每个后续 mip 层级包含上一级经过滤波、尺寸减半(四舍五入)的版本。
采样时,使用显式或隐式计算的细节级别选择 mip 层级,然后通过滤波合成采样值。 - 数组化
-
纹理是否为数组。
-
非数组纹理是一个纹素网格。
-
数组纹理是一组同构的纹素网格数组。
-
- 数组大小
-
若纹理为数组,则为同构网格数量。
- 采样数
-
若纹理为多重采样,则为采样数。
纹理中的每个纹素都关联唯一的逻辑纹素地址, 它是一个整数元组,包括:
纹理的物理组织通常针对渲染操作做了优化。 因此,许多细节对程序员是隐藏的,包括数据布局、数据类型和着色器语言无法直接表达的内部操作。
因此,着色器无法直接访问纹理变量内的纹素内存。 只能通过不透明句柄访问:
-
在着色器中:
-
构造 WebGPU 管线时,纹理变量的存储类型与绑定必须与相应绑定组布局条目兼容。
这样,纹理类型支持的操作集由是否有带该纹理类型形参的内建函数决定。
注意: 纹理变量存储的句柄无法被着色器更改。 即使底层纹理可变(如写入型存储纹理),该变量也是只读的。
纹理类型为以下各节定义的类型集合:
采样器是不透明句柄,用于控制从采样纹理或深度纹理获取纹素的方式。
WGSL 采样器对应 WebGPU GPUSampler。
采样器通过多个属性控制纹素访问:
- 寻址模式
-
控制纹理边界和越界坐标的处理方式。 每个维度的寻址模式可独立设置。 见 WebGPU
GPUAddressMode。 - 滤波模式
-
控制采样时访问哪些纹素生成最终结果。 滤波可使用最近纹素或在多个纹素间插值。 多个滤波模式可独立设置。 见 WebGPU
GPUFilterMode。 - LOD 限定
-
控制可访问的细节级别最小和最大值。
- 比较
-
控制比较采样器的比较类型。 见 WebGPU
GPUCompareFunction。 - 最大各向异性
-
控制采样器使用的最大各向异性值。
采样器无法在 WGSL 模块中创建,其状态(如上述属性)在着色器内不可变,只能由 WebGPU API 设置。
如使用插值滤波的采样器(即任何插值滤波采样器)与非滤波格式纹理配合,则为管线创建错误。
注意: 采样器变量存储的句柄无法被着色器更改。
6.5.1. 纹素格式
在 WGSL 中,某些纹理类型以纹素格式为参数。
纹素格式的特征包括:
- 通道
-
每个通道包含一个标量。 一个纹素格式最多有四个通道:
r、g、b和a, 通常对应红、绿、蓝和 alpha 通道的概念。 - 通道格式
-
通道中位数,以及这些位的解释方式。
WGSL 中的每个纹素格式都对应一个同名的 WebGPU GPUTextureFormat。
仅有部分 texel 格式可用于 WGSL 源代码中。 用于定义这些 texel 格式的通道格式,列示在 通道格式 表中。 倒数第二列指定了从存储通道位到着色器中实际使用值的转换。 这也被称为 通道传递函数,或 CTF。 第三列指定了从着色器值到存储通道位的转换。 这也被称为 反通道传递函数,或 ICTF。 最后一列则指定了该 texel 格式所需的 语言扩展。
注意: 8unorm 的通道传递函数将 {0,...,255} 映射到浮点区间 [0.0, 1.0]。
注意: 8snorm 的通道传递函数将 {-128,...,127} 映射到浮点区间 [-1.0, 1.0]。
| 通道格式 | 存储位数 | 存储位解释 | 着色器类型 | 着色器值(通道传递函数) | 写入值 T(逆通道传递函数)
|
|---|---|---|---|---|---|
| 8unorm | 8 | 无符号整数 v ∈ {0,...,255} | f32 | v ÷ 255 | max(0, min(1, T))
|
| 8snorm | 8 | 有符号整数 v ∈ {-128,...,127} | f32 | v ÷ 127 | max(-1, min(1, T))
|
| 8uint | 8 | 无符号整数 v ∈ {0,...,255} | u32 | v | min(255, T)
|
| 8sint | 8 | 有符号整数 v ∈ {-128,...,127} | i32 | v | max(-128, min(127, T))
|
| 16unorm | 16 | 无符号整数 v ∈ {0,...,65535} | f32 | v ÷ 65535 | max(0, min(1, T))
|
| 16snorm | 16 | 有符号整数 v ∈ {-32768,...,32767} | f32 | v ÷ 32767 | max(-1, min(1, T))
|
| 16uint | 16 | 无符号整数 v ∈ {0,...,65535} | u32 | v | min(65535, T)
|
| 16sint | 16 | 有符号整数 v ∈ {-32768,...,32767} | i32 | v | max(-32768, min(32767, T))
|
| 16float | 16 | IEEE-754 binary16 16位浮点值 v | f32 | v | quantizeToF16(T)
|
| 32uint | 32 | 32位无符号整数值 v | u32 | v | T
|
| 32sint | 32 | 32位有符号整数值 v | i32 | v | T
|
| 32float | 32 | IEEE-754 binary32 32位浮点值 v | f32 | v | T
|
| 2unorm | 2 | 无符号整数 v ∈ {0,...,3} | f32 | v ÷ 3 | max(0, min(1, T))
|
| 2uint | 2 | 无符号整数 v ∈ {0,...,3} | u32 | v | min(3, T)
|
| 10unorm | 10 | 无符号整数 v ∈ {0,...,1023} | f32 | v ÷ 1023 | max(0, min(1, T))
|
| 10uint | 10 | 无符号整数 v ∈ {0,...,1023} | u32 | v | min(1023, T)
|
| 10float | 10 | 10位浮点值:5位偏置指数,5位小数 v | f32 | v | max(0, T)
|
| 11float | 11 | 11位浮点值:5位偏置指数,6位小数 v | f32 | v | max(0, T)
|
下表所列的纹素格式
存储纹理的纹素格式
对应于支持 WebGPU STORAGE_BINDING
用法(至少支持一种访问模式)的WebGPU 普通颜色格式。
这些纹素格式用于参数化 存储纹理类型,定义见 § 6.5.5 存储纹理类型。
当纹素格式不包含所有四个通道时:
-
-
若无绿色通道,则着色器值第二分量为 0。
-
若无蓝色通道,则着色器值第三分量为 0。
-
若无 alpha 通道,则着色器值第四分量为 1。
-
下表最后一列采用了通道传递函数,其定义见通道格式表。
| Texel 格式 | 通道格式 | 内存顺序中的通道 | 对应的着色器值 | 所需语言扩展 |
|---|---|---|---|---|
| rgba8unorm | 8unorm | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba8snorm | 8snorm | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba8uint | 8uint | r, g, b, a | vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba8sint | 8sint | r, g, b, a | vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba16unorm | 16unorm | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | texture_formats_tier1 |
| rgba16snorm | 16snorm | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | texture_formats_tier1 |
| rgba16uint | 16uint | r, g, b, a | vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba16sint | 16sint | r, g, b, a | vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba16float | 16float | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rg8unorm | 8unorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| rg8snorm | 8snorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| rg8uint | 8uint | r, g | vec4<u32>(CTF(r), CTF(g), 0u, 1u) | texture_formats_tier1 |
| rg8sint | 8sint | r, g | vec4<i32>(CTF(r), CTF(g), 0, 1) | texture_formats_tier1 |
| rg16unorm | 16unorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| rg16snorm | 16snorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| rg16uint | 16uint | r, g | vec4<u32>(CTF(r), CTF(g), 0u, 1u) | texture_formats_tier1 |
| rg16sint | 16sint | r, g | vec4<i32>(CTF(r), CTF(g), 0, 1) | texture_formats_tier1 |
| rg16float | 16float | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| r32uint | 32uint | r | vec4<u32>(CTF(r), 0u, 0u, 1u) | |
| r32sint | 32sint | r | vec4<i32>(CTF(r), 0, 0, 1) | |
| r32float | 32float | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | |
| rg32uint | 32uint | r, g | vec4<u32>(CTF(r), CTF(g), 0u, 1u) | |
| rg32sint | 32sint | r, g | vec4<i32>(CTF(r), CTF(g), 0, 1) | |
| rg32float | 32float | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | |
| rgba32uint | 32uint | r, g, b, a | vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba32sint | 32sint | r, g, b, a | vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba32float | 32float | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| bgra8unorm | 8unorm | b, g, r, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| r8unorm | 8unorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| r8snorm | 8snorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| r8uint | 8uint | r | vec4<u32>(CTF(r), 0u, 0u, 1u) | texture_formats_tier1 |
| r8sint | 8sint | r | vec4<i32>(CTF(r), 0, 0, 1) | texture_formats_tier1 |
| r16unorm | 16unorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| r16snorm | 16snorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| r16uint | 16uint | r | vec4<u32>(CTF(r), 0u, 0u, 1u) | texture_formats_tier1 |
| r16sint | 16sint | r | vec4<i32>(CTF(r), 0, 0, 1) | texture_formats_tier1 |
| r16float | 16float | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| rgb10a2unorm | r, g, b: 10unorm a: 2unorm | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | texture_formats_tier1 |
| rgb10a2uint | r, g, b: 10uint a: 2uint | r, g, b, a | vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a)) | texture_formats_tier1 |
| rg11b10ufloat | r, g: 11float b: 10float | r, g, b | vec4<f32>(CTF(r), CTF(g), CTF(b), 1.0) | texture_formats_tier1 |
6.5.2. 采样纹理类型
采样纹理可以结合采样器 进行访问。 也可以无需采样器进行访问。 采样纹理只允许读访问。
纹素格式是
format
属性,位于绑定到纹理变量的
GPUTexture
上。
WebGPU 校验纹理、本地绑定组布局的 sampleType
以及纹理变量的采样类型的兼容性。
纹理由采样类型参数化,且必须
是 f32、i32 或 u32。
| 类型 | 维度 | 数组化 |
|---|---|---|
| texture_1d<T> | 1D
| 否 |
| texture_2d<T> | 2D
| 否 |
| texture_2d_array<T> | 2D
| 是 |
| texture_3d<T> | 3D
| 否 |
| texture_cube<T> | Cube
| 否 |
| texture_cube_array<T> | Cube
| 是 |
-
T 是采样类型。
-
图像的参数化类型是采样转换后的类型。 例如,你可以有纹素为 8 位 unorm 分量的图像,但采样时得到 32 位浮点值(或 vec-of-f32)。
6.5.3. 多重采样纹理类型
多重采样纹理有一个 采样数,不少于 1。 尽管名为“多重采样”,它不能与采样器一起使用。 如果忽略采样索引,则每个逻辑纹素地址都有效存储多个纹素数据。
纹素格式是
format
属性,位于绑定到纹理变量的
GPUTexture
上。
WebGPU 校验纹理、本地绑定组布局的 sampleType
以及纹理变量的采样类型的兼容性。
texture_multisampled_2d 由采样类型参数化,且必须是 f32、i32 或 u32。
| 类型 | 维度 | 数组化 |
|---|---|---|
| texture_multisampled_2d<T> | 2D
| 否 |
| texture_depth_multisampled_2d | 2D
| 否 |
-
T 是采样类型。
6.5.4. 外部采样纹理类型
外部纹理是一种不透明的二维浮点采样纹理类型,类似于 texture_2d<f32>,但其底层表示可能不同。
可以通过 textureLoad 或 textureSampleBaseClampToEdge 内建函数读取,这些函数会处理不同的底层表示。
参见 WebGPU § 6.4 GPUExternalTexture。
| 类型 | 维度 | 数组化 |
|---|---|---|
| texture_external | 2D
| 否 |
6.5.5. 存储纹理类型
存储纹理支持无需采样器即可访问单个纹素值。
存储纹理类型必须以存储纹理的纹素格式之一为参数。 纹素格式决定了转换函数,详见 § 6.5.1 纹素格式。
写入存储纹理时,使用转换函数的逆将着色器值转换为存储纹素。
| 类型 | 维度 | 数组化 |
|---|---|---|
| texture_storage_1d<Format, Access> | 1D
| 否 |
| texture_storage_2d<Format, Access> | 2D
| 否 |
| texture_storage_2d_array<Format, Access> | 2D
| 是 |
| texture_storage_3d<Format, Access> | 3D
| 否 |
6.5.6. 深度纹理类型
深度纹理可以结合sampler_comparison进行访问。 也可以无需采样器进行访问。 深度纹理只允许读访问。
深度纹理的纹素格式定义在 GPUTextureBindingLayout
中。
| 类型 | 维度 | 数组化 |
|---|---|---|
| texture_depth_2d | 2D
| 否 |
| texture_depth_2d_array | 2D
| 是 |
| texture_depth_cube | Cube
| 否 |
| texture_depth_cube_array | Cube
| 是 |
6.5.7. 采样器类型
采样器类型包括:
| 类型 | 说明 |
|---|---|
| sampler | 采样器。协调对采样纹理的访问。 |
| sampler_comparison | 比较采样器。 协调对深度纹理的访问。 |
采样器在 WebGPU API 创建时被参数化。 WGSL 模块不能修改采样器。
采样器只能被纹理内建函数使用。
sampler sampler_comparison
6.6. AllTypes 类型
AllTypes 类型是所有 WGSL 类型的集合。
无法在 WGSL 源码中写出 AllTypes 类型。
见 § 6.9 预声明类型与类型生成器总览,其中列出了所有预声明类型和类型生成器。
AllTypes 类型的存在,是为了让类型检查规则适用于任何可能包含普通值的语句。 WGSL 通过把类型定义为一种值,并允许表达式表示类型,使得规则保持一致。
典型场景是模板参数,它在不同上下文中可表示多种事物,包括类型、枚举项或普通值。 特别是,template_arg_expression 语法规则会扩展为 expression 语法非终结符。
6.7. 类型别名
类型别名为某个已存在类型声明一个新名称。 声明必须出现在模块作用域,其作用域为整个程序。
当类型 T 被定义为结构体类型 S 的类型别名时, S 的所有成员属性(包括属性)都会传递到 T 的成员上。
注意:如果被别名的类型支持 值构造器 内建函数, 那么当类型别名 在作用域内并且调用该函数是合法的情况下,可以通过别名名来调用这些函数,而不是使用原始类型说明符名。
'alias' ident '=' type_specifier
alias Arr = array< i32, 5 > ; alias RTArr = array< vec4< f32>> ; alias single = f32; // 声明 f32 的别名 const pi_approx : single = 3.1415 ; fn two_pi () -> single { return single ( 2 ) * pi_approx ; }
6.8. 类型说明符语法
见 § 8.17 类型表达式。
注意: 表达式也可以表示类型,通过 primary_expression 语法规则扩展为 template_elaborated_ident, 也可以通过括号表达式。
6.9. 预声明类型与类型生成器总览
WGSL 还会为 frexp、 modf 以及 atomicCompareExchangeWeak 内建函数预声明返回类型, 但这些类型不能在 WGSL 源码中书写。
预声明的类型生成器如下表:
| 预声明类型生成器 | 交叉引用 |
|---|---|
| array | 见 § 6.2.9 数组类型 |
| atomic | 见 § 6.2.8 原子类型 |
| mat2x2 |
见 § 6.2.7 矩阵类型,其中也列出了矩阵类型的别名。
注意: 这些类型也用于值构造函数表达式中创建矩阵。 |
| mat2x3 | |
| mat2x4 | |
| mat3x2 | |
| mat3x3 | |
| mat3x4 | |
| mat4x2 | |
| mat4x3 | |
| mat4x4 | |
| ptr | 见 § 6.4.3 引用类型与指针类型 |
| texture_1d | 见 § 6.5.2 采样纹理类型 |
| texture_2d | |
| texture_2d_array | |
| texture_3d | |
| texture_cube | |
| texture_cube_array | |
| texture_multisampled_2d | 见 § 6.5.3 多重采样纹理类型 |
| texture_storage_1d | 见 § 6.5.5 存储纹理类型 |
| texture_storage_2d | |
| texture_storage_2d_array | |
| texture_storage_3d | |
| vec2 |
见 § 6.2.6 向量类型,其中也列出了向量类型的别名。
注意: 这些类型也用于值构造函数表达式中创建向量。 |
| vec3 | |
| vec4 |
7. 变量与值声明
值声明
创建一个值的名称,该值在声明后不可变。
有四种值声明:const、override、let 和形参声明,
详见下文(参见§ 7.2 值声明)。
变量声明为存储值的内存位置创建名称;
如果变量的read_write访问模式允许,存储的值可以被更新。
变量声明只有一种:var,但可以与地址空间和访问模式组合使用,详见下文(参见§ 7.3 var 声明)。
注意:值声明不关联内存位置。例如,WGSL 表达式无法形成指向该值的指针。
在任何函数定义之外出现的声明属于模块作用域。 其名称在整个程序中有效。
在函数定义内部出现的声明处于函数作用域。 该名称可在声明后紧接的语句及其所在的大括号语句块范围内被使用。 函数作用域声明属于动态上下文。
[...] 表示可选项,...*
表示前项可重复零次或多次,...+ 表示前项至少出现一次。具体语法规则见各元素对应章节。
// 具体的值声明。 const name [: type ] = initializer ; [ attribute ] * override name [: type ] [ = initializer ]; let name [: type ] = initializer ; // 一般变量形式。 [ attribute ] * var [ < address_space [, access_mode ] > ] name [: type ] [ = initializer ]; // 具体变量声明。 // 函数作用域。 var [ < function> ] name [: type ] [ = initializer ]; // 模块作用域。 var < private> name [: type ] [ = initializer ]; var < workgroup> name : type ; [ attribute ] + var < uniform> name : type ; [ attribute ] + var name : texture_type ; [ attribute ] + var name : sampler_type ; [ attribute ] + var < storage[, access_mode ] > name : type ;
每个这样的声明必须显式指定类型或初始值。 类型和初始值都可以同时指定。 每个这样的声明确定与之关联的数据值的类型,称为该声明的实际值类型(effective-value-type)。其规则如下:
-
如显式指定了类型,则类型即为实际值类型。
-
否则,若初始值表达式类型为
T:-
对于
const声明,实际值类型即为T。 -
对于
override、let或var声明,实际值类型为 T 的具体化。
-
每种值或变量声明可能对初始值表达式的形式(如存在)和实际值类型施加额外约束。
| 声明 | 可变性 | 作用域 | 有效值类型1 | 初始化器支持 | 初始化器表达式2 | 资源接口的一部分 |
|---|---|---|---|---|---|---|
| const | 不可变 | 模块或函数 | 可构造(具体或抽象) | 必需 | const-expression | 否 |
| override | 不可变 | 模块 | 具体标量 | 可选3 | const-expression 或 override-expression | 否4 |
| let | 不可变 | 函数 | 具体可构造或 指针类型。 此外,如果支持 texture_and_sampler_let 特性, 则还包括 纹理或采样器类型。 | 必需 | const-expression、override-expression,或 运行时 表达式 | 否 |
|
var<storage, read> var<storage> | 不可变 | 模块 | 具体宿主可共享 | 不允许 | 是。 storage buffer | |
| var<storage, read_write>5,6 | 可变 | 模块 | 具体宿主可共享 | 不允许 | 是。 storage buffer | |
| var<uniform> | 不可变 | 模块 | 具体可构造宿主可共享 | 不允许 | 是。 uniform buffer | |
| var<immediate> | 不可变 | 模块 | 具体可构造宿主可共享, 排除数组以及包含数组成员的结构 | 不允许 | 是。 立即 数据 | |
| var6 | 不可变7 | 模块 | 纹理 | 不允许 | 是。 纹理 资源 | |
| var | 不可变 | 模块 | 采样器 | 不允许 | 是。 采样器 资源 | |
| var<workgroup>6,8 | 可变 | 模块 | 具体普通类型,具有固定 占用空间9 | 不允许10 | 否 | |
| var<private> | 可变 | 模块 | 具体可构造 | 可选10 | const-expression 或 override-expression | 否 |
| var<function> var | 可变 | 函数 | 具体可构造 | 可选10 | const-expression、override-expression,或 运行时 表达式 | 否 |
-
若未指定初始值,则必须在管线创建时提供值。
-
override 声明属于着色器接口,但不是绑定资源。
-
存储缓冲区和 存储纹理(访问模式非read)不能在 顶点着色器阶段中静态访问。 详见 WebGPU
createBindGroupLayout()。 -
原子类型只能出现在可变存储缓冲区或 workgroup 变量中。
-
存储纹理中,访问模式为write或read_write 的访问模式数据是可变的,但只能通过textureStore内建函数修改。 变量本身不能被修改。
-
最外层数组的元素数量可以是override-expression。
-
若无初始值,变量会默认初始化。
7.1. 变量与值
变量声明是 WGSL 模块中唯一可变的数据。 值声明始终是不可变的。 变量可以作为引用类型和指针类型值的基础,因为变量有相关联的内存位置, 而值声明不能作为指针或引用值的基础。
通常,使用变量比使用值声明代价更高, 因为对变量的使用需要额外的操作来读取或 写入变量关联的内存位置。
一般来说,建议作者按如下顺序优先使用声明,最优先的在前:
这样通常能获得着色器的最佳整体性能。
7.2. 值声明
WGSL 提供多种值声明。 每种声明的值在着色器生命周期的不同阶段被固定。 不同种类的值声明及其值被固定的时机如下:
7.2.1. const 声明
const-declaration 为在着色器创建时固定的数据值 指定一个名称。 每个 const-declaration 都需要一个初始化器。 const-declaration 可以在模块或函数作用域中声明。 初始化器表达式必须是一个 const-expression。 const-declaration 的类型必须是具体或 抽象可构造类型。 const-declarations 是唯一一类其有效值类型 可以是抽象的声明。
注意: 由于抽象数值类型不能在 WGSL 中直接书写,只能通过类型推导使用。
const a = 4 ; // AbstractInt 值为 4。 const b : i32= 4 ; // i32 值为 4。 const c : u32= 4 ; // u32 值为 4。 const d : f32= 4 ; // f32 值为 4。 const e = vec3( a , a , a ); // vec3 的 AbstractInt,值为 (4, 4, 4)。 const f = 2.0 ; // AbstractFloat 值为 2。 const g = mat2x2( a , f , a , f ); // mat2x2 的 AbstractFloat,值为: // ((4.0, 2.0), (4.0, 2.0)). // AbstractInt a 自动转为 AbstractFloat。 // AbstractFloat 不能转为 AbstractInt。 const h = array( a , f , a , f ); // AbstractFloat 组成的数组,共 4 个分量: // (4.0, 2.0, 4.0, 2.0).
7.2.2. override 声明
override-declaration 为一个 可由管线覆盖的常量值指定一个名称。 override-declaration 必须只在 模块作用域中声明。 可由管线覆盖的常量的值固定于 管线创建 时。 如果指定了该值,则该值是由 WebGPU 管线创建方法提供的值; 否则是其初始化器表达式具体化后的值。 override-declaration 的有效值类型必须是具体 标量类型。
初始值表达式为可选项。 如指定,必须是override-expression,表示可管线覆盖常量的默认值。 若未指定初始值,则在管线创建时未提供值即为管线创建错误。
如声明应用了id属性, 该字面量操作数称为管线常量 ID,且必须为 0 至 65535 间唯一整数。 即,两个 override 声明不能使用相同的管线常量 ID。
应用可以在管线创建时为 override 声明指定自己的值。 管线创建 API 接受一个从可覆盖常量到其类型值的映射。 常量通过可管线覆盖常量标识字符串识别, 若指定了管线常量 ID则为其十进制表示,否则为常量声明的名称。
@id ( 0 ) override has_point_light : bool= true ; // 算法控制 @id ( 1200 ) override specular_param : f32= 2.3 ; // 数值控制 @id ( 1300 ) override gain : f32; // 必须覆盖 override width : f32= 0.0 ; // API 层通过名称 "width" 指定 // override depth : f32; // API 层通过名称 "depth" 指定 // 必须覆盖。 override height = 2 * depth ; // 默认值 // (如 API 层未设置), // 依赖于另一可覆盖常量。
7.2.3. let 声明
let-declaration 为一个在运行时每次执行该语句时固定的值指定一个名称。 let-declaration 必须只在函数作用域中声明,因此它处于 动态上下文中。 let-declaration 必须具有一个初始化器表达式。 该值是初始化器的具体化值。 let-declaration 的有效值类型必须是具体可构造类型或 指针类型。 如果支持 texture_and_sampler_let 特性,则 effective-value-type 也可以是纹理或采样器类型。
// 'blockSize' 表示 i32 类型值 1024。 let blockSize : i32= 1024 ; // 'row_size' 表示 u32 类型值 16u。类型会被自动推断。 let row_size = 16u ;
7.3. var 声明
变量是对内存的具名引用,可以包含某种可存储类型的值。
变量关联两种类型:其存储类型(可放入引用内存的值的类型)和引用类型(变量本身的类型)。
若变量的存储类型为 T,地址空间为 AS,访问模式为 AM,则其引用类型为 ref<AS,T,AM>。
变量的存储类型总是具体的。
变量声明:
-
指定变量的 名称。
-
决定变量的 地址空间、存储类型和访问模式。 这些共同组成变量的引用类型。
-
存储类型是变量声明的有效值类型。
-
-
确保执行环境为变量的生命周期,在指定的地址空间、支持指定访问模式下,为一个存储类型的值分配内存。
-
如果变量位于 private 或 function 地址空间,则可选地有初始化表达式。 如果有,初始化器必须求值为变量的存储类型。 如果有,private 变量的初始化器必须是 常量表达式或override 表达式。 除 function 和 private 以外的地址空间中的变量不能有初始化器。
当一个 标识符 解析为变量声明时, 标识符是表示变量内存的引用内存视图的表达式, 其类型为变量的引用类型。 见 § 8.11 变量标识符表达式。
如果变量声明的 地址空间 或 访问模式 在程序源码中被指定,
会作为var关键字之后的模板列表进行书写:
private、storage、 uniform、workgroup 和 handle 地址空间中的变量只能 在模块作用域中声明,而 function 地址空间中的变量只能 在函数作用域中声明。 除 handle 和 function 外,所有地址 空间必须指定地址空间。 handle 地址空间不得指定。 指定 function 地址空间是可选的。
访问模式始终有一个 默认值,并且除了 storage 地址空间中的变量外,在 WGSL 源中不得 指定访问模式。 见 § 14.3 地址空间。
uniform 地址空间中的变量是 uniform buffer 变量。 其存储类型必须是宿主可共享的可构造类型,并且 必须满足地址空间布局 约束。
storage 地址空间中的变量是 storage buffer 变量。 其存储类型必须是宿主可共享类型,并且 必须满足 地址空间布局约束。 该变量可以用 read 或 read_write 访问模式声明;默认值为 read。
immediate 地址空间中的变量是一个 立即数据变量。 其存储类型必须是宿主可共享的可构造类型, 但不包括数组以及包含数组成员的结构。 每个入口点必须至多静态访问 一个立即数据变量。 immediate 变量的值通过 WebGPU API 命令编码器记录的 setImmediates 命令设置, 并且在着色器执行期间保持不变。 变量大小受管线布局的 immediateSize 配置限制。
纹理资源是 一种变量,其有效值类型是纹理类型。 它在模块作用域中声明。 它保存一个不透明句柄,该句柄用于访问纹理中底层的纹素网格。 句柄本身位于 handle 地址空间中,并且始终为只读。 在许多情况下,底层纹素是只读的,我们称该纹理变量为不可变。 对于只写存储纹理和读写存储 纹理,底层纹素是可变的,按约定 我们称该纹理变量为可变。
采样器资源是 一种变量,其有效值类型是采样器类型。 它在模块作用域中声明, 存在于 handle 地址空间中, 并且是不可变的。
如 § 13.3.2 资源接口所述,uniform buffer、storage buffer、 纹理和采样器构成着色器的资源接口。
变量的生存期是 着色器执行期间内存位置与该变量关联的时期。 模块作用域 变量的生存期是整个 着色器阶段执行期间。 对于每次调用,private 和 function 地址空间中的变量都有一个独立版本。 函数作用域变量是 动态上下文。 函数作用域变量的生存期由其作用域决定:
两个资源变量可以有重叠的内存位置, 但如果其中任一变量是可变的,则是动态错误。 其他具有重叠生存期的变量将不会 具有重叠的内存位置。 当变量的生存期结束时,其内存可用于另一个变量。
注:WGSL 确保变量的内容只在 变量的生存期内可观察。
当 private、function 或 workgroup 地址空间中的变量被创建时,它将 具有一个初始值。 如果没有指定初始化器,则初始值为默认 初始值。 初始值按如下方式计算:
-
对于 function 地址空间中的变量:
-
对于 private 地址空间中的变量:
-
否则,它是求值具体化初始化器表达式所得的结果。 初始化器必须是一个 override-expression,因此其值 不晚于管线创建时固定。
-
对于 workgroup 地址空间中的变量:
其他地址空间中的变量是由绘制命令或分派命令中的绑定设置的 资源。
考虑以下 WGSL 片段:
var i : i32; // 初始值为 0。不推荐的风格。 loop { var twice : i32= 2 * i ; // 每次迭代都会重新求值。 i ++ ; if i == 5 { break ; } }
i 将取值 0、1、2、3、4、5,而变量 twice 将取
值 0、2、4、6 和 8。
考虑以下 WGSL 片段:
因为x 是一个变量,所以对它的所有访问都会转换为加载和存储操作。
不过,预期浏览器或驱动程序会优化此中间表示,
以消除冗余加载。
var < private> decibels : f32; var < workgroup> worklist : array< i32, 10 > ; struct Params { specular : f32, count : i32} // Uniform buffer。始终只读,并且具有更严格的布局规则。 @group ( 0 ) @binding ( 2 ) var < uniform> param : Params ; // 一个 uniform buffer // 一个 storage buffer,用于读取和写入 @group ( 0 ) @binding ( 0 ) var < storage, read_write> pbuf : array< vec2< f32>> ; // 纹理和采样器始终位于 "handle" 空间中。 @group ( 0 ) @binding ( 1 ) var filter_params : sampler;
// Storage buffers @group ( 0 ) @binding ( 0 ) var < storage, read> buf1 : Buffer ; // 可读,不能写。 @group ( 0 ) @binding ( 0 ) var < storage> buf2 : Buffer ; // 可读,不能写。 @group ( 0 ) @binding ( 1 ) var < storage, read_write> buf3 : Buffer ; // 既可读也可写。 struct ParamsTable { weight : f32} // Uniform buffer。始终只读,并且具有更严格的布局规则。 @group ( 0 ) @binding ( 2 ) var < uniform> params : ParamsTable ; // 可读,不能写。
fn f () { var < function> count : u32; // function 地址空间中的变量。 var delta : i32; // function 地址空间中的另一个变量。 var sum : f32= 0.0 ; // 带初始化器的 function 地址空间变量。 var pi = 3.14159 ; // 从初始化器推断 f32 存储类型。 }
7.4. 变量和值声明语法总结
| variable_decl '=' expression
| 'let' optionally_typed_ident '=' expression
| 'const' optionally_typed_ident '=' expression
'var' _disambiguate_template template_list ? optionally_typed_ident
ident ( ':' type_specifier ) ?
attribute * variable_decl ( '=' expression ) ?
'const' optionally_typed_ident '=' expression
| attribute * 'override' optionally_typed_ident ( '=' expression ) ?
8. 表达式
表达式用于指定值的计算方式。
不同类型的值表达式在求值时机与表达能力之间存在权衡。求值越早,可用的操作越受限,但值可用的场景也越多。这种权衡导致不同类型的值声明有不同的灵活性。 const-expression 和 override-expression 在 GPU 执行前就被求值,因此最终 GPU 代码中只需表达式的计算结果。 此外,由于const-expression在着色器创建时求值,使用场景多于 override-expression,如用于函数作用域 变量中的数组长度。 运行时表达式是既非 const-expression 也非 override-expression 的表达式。 运行时表达式在着色器执行时于 GPU 上计算。虽然可用语法元素较少,但可由更广泛的表达式组成,如其他运行时值。
8.1. 提前求值表达式
WGSL 定义了两类可在运行前求值的表达式:
8.1.1. const 表达式
可在着色器创建时求值的表达式称为 const-expression。 当且仅当表达式中所有标识符解析为以下内容时,该表达式为 const-expression:
const 表达式的类型必须解析为具备
创建时固定占用的类型。
注意: 抽象类型可以作为 const-expression 的推导类型。
const-expression E 会被求值,当且仅当:
-
E 是顶层表达式,
注意: 此求值规则意味着短路操作符 && 和
|| 会保护其右侧子表达式不被求值,除非有子表达式需要求值以确定静态类型。
const-expression 可由实现 WebGPU API 的 CPU 求值。 因此对 AbstractFloat 值操作的精度要求不会高于常见 WebGPU 运行环境(如 WebAssembly [WASM-CORE-2] 和 ECMAScript [ECMASCRIPT])的要求。 具体浮点类型(如 f32)的精度要求见 § 15.7.4.1 具体浮点表达式的精度。
示例:(42) 分析如下:
-
项
42是 AbstractInt 值 42。 -
加括号得到新表达式
(42),类型为 AbstractInt,值为 42。
示例:-5 分析如下:
-
项
5是 AbstractInt 值 5。 -
加上一元负号,得新表达式
-5,类型为 AbstractInt,值为 -5。
示例:-2147483648 分析如下:
-
项
2147483648是 AbstractInt 值 2147483648。 注意该值不能放入 32 位有符号整型。 -
加上一元负号,得新表达式
-2147483648,类型为 AbstractInt,值为 -2147483648。
示例:const minint = -2147483648; 分析如下:
-
如上,
-2147483648求值为 AbstractInt 值 -2147483648。 -
结果是
minint被声明为 AbstractInt 值 -2147483648。
示例:let minint = -2147483648; 分析如下:
-
如上所述,
-2147483648求值为 AbstractInt 值 -2147483648。 -
let-declaration 要求初始化器是具体可构造类型或指针类型。
-
该 let-declaration 没有显式类型,因此使用重载解析。 适用的重载候选使用从 AbstractInt 到 i32、u32 或 f32 的可行自动转换。 最低等级的那个是到 i32,因此 AbstractInt -2147483648 值被转换为 i32 值 -2147483648。
-
结果是,
minint被声明为 i32 值 -2147483648。
示例:false && (10i < i32(5 * 1000 * 1000 * 1000)) 分析如下:
-
整个表达式是一个 const-expression。
-
但是,
&&运算符的短路规则适用: 左侧求值为false,因此右侧不会被求值。 -
对 i32(5 * 1000 * 1000 * 1000) 求值本会导致着色器创建错误, 因为 AbstractInt 值 5000000000 溢出了 i32 类型。
示例:false && array<u32, 1 + 2>(0, 1, 2)[0] == 0
-
整个表达式是 const-expression。
-
类型检查要求
e1 : bool && e2 : bool:-
false是 bool 值。 -
类型检查会处理右侧,并最终对数组元素数表达式
1 + 2求值。
-
-
1 + 2求值为 i32 值3。-
数组类型为
array<u32, 3i>。
-
-
数组访问表达式和等号表达式均未被求值。
8.1.2. override 表达式
可在管线创建时求值的表达式称为 override-expression。 当且仅当表达式中所有标识符解析为以下内容时,表达式为 override-expression:
注意:所有const-expression也是 override-expression。
除 const-expression 外的 override-expression 只在管线创建期间校验或求值,且仅在 API 提供的值替换 override 声明后。 如果 override 声明通过 API 被赋值,则其初始值表达式(如有)不会被求值。 否则,override-expression E 会被求值,仅当:
-
E 属于 着色器,且为指定 入口点的
GPUProgrammableStage的一部分,并且: -
下列任意条件成立:
注意: 并非所有 override-expression 都可作为override 声明的初始值,因为此类初始值必须解析为具体的标量类型。
示例:override x = 42; 分析如下:
-
项
42是 AbstractInt 值 42。 -
override 声明要求具体标量类型。
示例:let y = x + 1; 分析如下:
-
如上,
x类型为i32。 -
表达式
x + 1是 override-expression,因为它由override 声明和整数字面量组成。
示例:vec3(x,x,x) 分析如下:
-
如上,
x是override 声明,类型为i32。 -
vec3(x,x,x)是 override-expression,因为其中所有标识符解析为 override 声明。
override a : i32= 0 ; override b = a / 0 ; // 着色器创建错误, // 无论是否尝试覆盖 c
override a : i32= 0 ; override b = 1 / a ; // b 属于 frag1 着色器的一部分。将 frag1 编译为管线时: // * 若 b 被覆盖,不会报错。 // * 若 a 被覆盖为非零值,不会报错。 // * 若 a 为 0 且 b 未被覆盖,则会产生管线创建错误。 @fragment fn frag1 () { _ = b ; } // b 不属于 frag2 着色器。将 frag2 编译为管线时: // 即使 b 未被覆盖且 a 为 0,也不会报错。 @fragment fn frag2 () { }
8.2. 不确定值
在有限情况下,运行时表达式的求值可能会用到其子表达式的不支持值。
在这种情况下,求值结果是该表达式静态类型的不确定值, 即静态类型的任意由实现选取的值。
对于表达式被求值的每个唯一动态上下文,可能会产生不同的不确定值。 例如,如果求值发生在循环的每次迭代中,每次迭代都可能计算出不同的值。
注意: 如果类型为浮点类型且实现支持 NaN 值,则运行时产生的不确定值可能为 NaN。
fn fun () { var extracted_values : array< i32, 2 > ; const v = vec2< i32> ( 0 , 1 ); for ( var i : i32= 0 ; i < 2 ; i ++ ) { // 在向量索引时使用运行时表达式且索引超界, // 会产生该向量分量类型的不确定值。 let extract = v [ i + 5 ]; // 此时 'extract' 为任意 i32 值。 // 保存以备后用。 extracted_values [ i ] = extract ; if extract == extract { // 此分支总会执行 } if extract < 2 { // 该分支可能执行,也可能不执行。 // 尽管原向量分量为 0 和 1, // 提取值可能不是这两个值。 } } if extracted_values [ 0 ] == extracted_values [ 1 ] { // 该分支可能执行,也可能不执行。 } } fn float_fun ( runtime_index : u32) { const v = vec2< f32> ( 0 , 1 ); // 浮点向量 // 和上例一样,'float_extract' 是不确定值。 // 因为是浮点类型,可能为 NaN。 let float_extract : f32= v [ runtime_index + 5 ]; if float_extract == float_extract { // 该分支可能不会执行,因为: // - 'float_extract' 可能为 NaN,且 // - NaN 从不等于任何浮点值,哪怕另一个 NaN。 } }
8.3. 字面值表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
true: bool
| true 布尔值。
| |
false: bool
| false 布尔值。
| |
| e 是无后缀整数字面量 | e: AbstractInt | 抽象整数字面值。 |
| e 是无后缀浮点字面量 | e: AbstractFloat | 抽象浮点字面值。 |
e 是带 i 后缀的整数字面量
| e: i32 | 32 位有符号整数字面值。 |
e 是带 u 后缀的整数字面量
| e: u32 | 32 位无符号整数字面值。 |
e 是带 f 后缀的浮点字面量
| e: f32 | 32 位浮点字面值。 |
e 是带 h 后缀的浮点字面量
| e: f16 | 16 位浮点字面值。 |
8.4. 括号表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e : T | ( e ) : T
| 结果为 e。 用括号将表达式与周围文本隔离。 |
8.5. 复合值分解表达式
本节描述获取组件的复合值的表达式, 以及从指向包含复合值的内存视图获取组件引用的表达式。 这里复合值或其内存视图统称为base。
有两种方式:
- 命名组件表达式
-
对base B 的表达式后紧跟一个句点
'.'(U+002D),再接组件名。 - 索引表达式
-
base 的表达式后接
'['(U+005B),再接索引表达式,最后']'(U+005D)。
语法上,这两种形式通过component_or_swizzle_specifier 语法规则体现。
索引值为非有效索引时为越界索引。越界索引通常为编程缺陷,通常会导致错误。详见下文。
此外,向量类型支持分量混合(swizzle)语法,可用另一个向量的分量创建新向量。
8.5.1. 向量分量访问表达式
可以通过以下方式访问向量的分量:
-
使用数组下标(如
v[2]),或 -
使用分量混合(swizzle)名称, 一种上下文相关名称,写作一组便捷名称序列,每个名称映射到源向量的某个分量。
-
颜色便捷名称集:
r、g、b、a,分别对应向量分量 0、1、2、3。 -
空间维度便捷名称集:
x、y、z、w,分别对应向量分量 0、1、2、3。
-
便捷名称使用 . 表示法访问。(例如 color.bgra)。
便捷字母形式不得混用。例如,你不能使用
.rybw。
便捷字母不得访问超出向量末尾的分量。
便捷字母形式可以按任意顺序应用,包括按需重复字母。 提供的字母数量必须介于 1 到 4 之间。 也就是说,使用便捷字母只能产生标量类型或有效的向量类型。
结果类型取决于所提供的字母数量。假设有一个 vec4<f32>
| 访问器 | 结果类型 |
|---|---|
| r | f32
|
| rg | vec2<f32>
|
| rgb | vec3<f32>
|
| rgba | vec4<f32>
|
var a : vec3< f32> = vec3< f32> ( 1. , 2. , 3. ); var b : f32= a . y ; // b = 2.0 var c : vec2< f32> = a . bb ; // c = (3.0, 3.0) var d : vec3< f32> = a . zyx ; // d = (3.0, 2.0, 1.0) var e : f32= a [ 1 ]; // e = 2.0
8.5.1.1. 向量单分量选择
| 前置条件 | 结论 | 描述 |
|---|---|---|
| e: vecN<T> |
e.x: Te .r: T
|
选择 e 的第一个分量
这是一个单字母重排。 |
| e: vecN<T> |
e.y: Te .g: T
|
选择 e 的第二个分量
这是一个单字母重排。 |
| e: vecN<T> N 为 3 或 4 |
e.z: Te .b: T
|
选择 e 的第三个分量
这是一个单字母重排。 |
| e: vec4<T> |
e.w: Te .a: T
|
选择 e 的第四个分量
这是一个单字母重排。 |
| e: vecN<T> i: i32 或 u32 T 是具体的 | e[i]: T |
选择向量的第 ith 个分量 第一个分量位于索引 i=0。 如果 i 在范围 [0,N-1] 之外:
|
| e: vecN<T> i: i32 或 u32 T 是抽象的 i 是一个const-expression | e[i]: T |
选择向量的第 ith 个分量 第一个分量位于索引 i=0。 如果 i 在范围 [0,N-1] 之外,则为着色器创建错误。 注:当抽象向量值 e 由一个不是const-expression的表达式索引时,该向量会在应用索引之前 被具体化。 |
8.5.1.2. 向量多分量选择
本节中的表达式均为多字母分量混合(swizzle)。 每个表达式都通过另一个向量的分量组成新的向量。
多字母swizzle不能出现在赋值语句左侧: 赋值语句左侧必须是引用类型, 而多字母混合表达式总是产生向量类型的值。
| 前置条件 | 结论 | 说明 |
|---|---|---|
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I 为 x、y、z 或 w 字母之一J 为 x、y、z 或 w 字母之一AM 为 read 或 read_write |
e.IJ: vec2<T> | 计算一个两分量向量,第一个分量为 e.I,第二个为 e.J。 字母 z 仅当 N 为 3 或 4 时有效。字母 w 仅当 N 为 4 时有效。若 e 是指针,先应用间接寻址,再应用加载规则。 |
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I 为 r、g、b 或 a 字母之一J 为 r、g、b 或 a 字母之一AM 为 read 或 read_write |
e.IJ: vec2<T> | 计算一个两分量向量,第一个分量为 e.I,第二个为 e.J。 字母 b 仅当 N 为 3 或 4 时有效。字母 a 仅当 N 为 4 时有效。若 e 是指针,先应用间接寻址,再应用加载规则。 |
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I、J、K 为 x、y、z 或
wAM 为 read 或 read_write |
e.IJK: vec3<T> | 计算一个三分量向量,分量依次为
e.I、e.J、e.K。 字母 z 仅当 N 为 3 或 4 时有效。字母 w 仅当 N 为 4 时有效。若 e 是指针,先应用间接寻址,再应用加载规则。 |
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I、J、K 为 r、g、b 或
aAM 为 read 或 read_write |
e.IJK: vec3<T> | 计算一个三分量向量,分量依次为
e.I、e.J、e.K。 字母 b 仅当 N 为 3 或 4 时有效。字母 a 仅当 N 为 4 时有效。若 e 是指针,先应用间接寻址,再应用加载规则。 |
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I、J、K、L 为 x、y、z 或 wAM 为 read 或 read_write |
e.IJKL:
vec4<T> | 计算一个四分量向量,分量依次为
e.I、e.J、e.K、e.L。 字母 z 仅当 N 为 3 或 4 时有效。字母 w 仅当 N 为 4 时有效。若 e 是指针,先应用间接寻址,再应用加载规则。 |
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I、J、K、L 为 r、g、b 或 aAM 为 read 或 read_write |
e.IJKL:
vec4<T> | 计算一个四分量向量,分量依次为
e.I、e.J、e.K、e.L。 字母 b 仅当 N 为 3 或 4 时有效。字母 a 仅当 N 为 4 时有效。若 e 是指针,先应用间接寻址,再应用加载规则。 |
8.5.1.3. 从向量内存视图获取分量引用
本节中的表达式通过向量整体的内存视图获取该向量单个分量的引用。
WGSL 类型规则意味着这些表达式可以出现在:
注意: 这意味着不同调用访问同一向量内存中不同分量时,若有写操作,必须同步。 参见 § 17.11 同步内建函数。
| 前置条件 | 结论 | 说明 |
|---|---|---|
| r: ref<AS,vecN<T>,AM> 或 ptr<AS,vecN<T>,AM> |
r.x: ref<AS,T,AM>r .r: ref<AS,T,AM> | 计算 内存视图
r 所引用向量的第一个分量的引用。 结果引用的原始变量与 r 相同。 |
| r: ref<AS,vecN<T>,AM> 或 ptr<AS,vecN<T>,AM> |
r.y: ref<AS,T,AM>r .g: ref<AS,T,AM> | 计算 内存视图
r 所引用向量的第二个分量的引用。 结果引用的原始变量与 r 相同。 |
| r: ref<AS,vecN<T>,AM> 或 ptr<AS,vecN<T>,AM> N 为 3 或 4 |
r.z: ref<AS,T,AM>r .b: ref<AS,T,AM> | 计算 内存视图
r 所引用向量的第三个分量的引用。 结果引用的原始变量与 r 相同。 |
| r: ref<AS,vec4<T>,AM> 或 ptr<AS,vec4<T>,AM> |
r.w: ref<AS,T,AM>r .a: ref<AS,T,AM> | 计算 内存视图
r 所引用向量的第四个分量的引用。 结果引用的原始变量与 r 相同。 |
| r: ref<AS,vecN<T>,AM> 或 ptr<AS,vecN<T>,AM> i: i32 或 u32 |
r[i] : ref<AS,T,AM> |
计算 内存视图 r
所引用向量的第 i 个分量的引用。
若 i 不在 [0,N-1] 范围内:
结果引用的原始变量与 r 相同。 |
8.5.2. 矩阵访问表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
|
e: matCxR<T> i: i32 或 u32 T 为具体 | e[i]: vecR<T> |
结果为 e 的第 i 个列向量。
若 i 不在 [0,C-1] 范围内:
|
|
e: matCxR<T> i: i32 或 u32 T 为抽象 i 为const-expression | e[i]: vecR<T> |
结果为 e 的第 i 个列向量。
若 i 不在 [0,C-1] 范围内,则为着色器创建错误。 注意: 当抽象矩阵值 e 由非常量表达式索引时,索引前会对矩阵进行具体化。 |
| 前置条件 | 结论 | 说明 |
|---|---|---|
|
r: ref<AS,matCxR<T>,AM>
或 ptr<AS,matCxR<T>,AM> i: i32 或 u32 | r[i] : ref<AS,vecR<T>,AM> |
计算 内存视图 r
所引用矩阵的第 i 个列向量的引用。
若 i 不在 [0,C-1] 范围内:
结果引用的原始变量与 r 相同。 |
8.5.3. 数组访问表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
|
e: array<T,N> i: i32 或 u32 T 为具体 | e[i] : T |
结果为数组值 e 的第 i 个元素的值。
若 i 不在 [0,N-1] 范围内:
|
|
e: array<T,N> i: i32 或 u32 T 为抽象 i 为const-expression | e[i] : T |
结果为数组值 e 的第 i 个元素的值。
若 i 不在 [0,N-1] 范围内,则为着色器创建错误。 注意: 当抽象数组值 e 由非常量表达式索引时,索引前会对数组进行具体化。 |
| 前置条件 | 结论 | 说明 |
|---|---|---|
|
r: ref<AS,array<T,N>,AM> 或 ptr<AS,array<T,N>,AM> i: i32 或 u32 | r[i] : ref<AS,T,AM> |
计算 内存视图 r
所引用数组的第 i 个元素的引用。
若 i 不在 [0,N-1] 范围内:
结果引用的原始变量与 r 相同。 |
| r: ref<AS,array<T>,AM> 或 ptr<AS,array<T>,AM> i: i32 或 u32 | r[i] : ref<AS,T,AM> |
计算 内存视图 r
所引用运行时长度数组的第 i 个元素的引用。
若运行时数组有 N 个元素,且 i 不在 [0,N-1] 范围内,则表达式结果为无效内存引用。 若 i 为有符号整数,且 i 小于 0:
结果引用的原始变量与 r 相同。 |
8.5.4. 结构体访问表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
|
S 为结构体类型 M 是 S 的成员标识符名,类型为 T e: S | e.M: T | 结果为结构体值 e 中名为 M 的成员的值。 |
| 前置条件 | 结论 | 说明 |
|---|---|---|
|
S 为结构体类型 M 是 S 的成员标识符名,类型为 T r: ref<AS,S,AM> 或 ptr<AS,S,AM> | r.M: ref<AS,T,AM> | 给定结构体的内存视图,结果为该结构体成员 M 的引用。 结果引用的原始变量与 r 相同。 |
8.6. 逻辑表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e: T T 为 bool 或 vecN<bool> | !e: T
| 逻辑非运算。
当 e 为 false 时结果为 true,e 为 true 时结果为
false。
若 T 是向量,则为分量级运算。
|
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e1: bool e2: bool | e1 || e2: bool
| 短路“或”。如果 e1 或 e2 为 true,则结果为 true;
仅当 e1 为 false 时才会对 e2 求值。
|
| e1: bool e2: bool | e1 && e2: bool
| 短路“与”。只有当 e1 和 e2 都为 true 时结果为 true;
仅当 e1 为 true 时才会对 e2 求值。
|
| e1: T e2: T T 为 bool 或 vecN<bool> | e1 | e2: T
| 逻辑“或”。若 T 是向量,则为分量级运算。对 e1 和 e2 都会求值。 |
| e1: T e2: T T 为 bool 或 vecN<bool> | e1 & e2: T
| 逻辑“与”。若 T 是向量,则为分量级运算。对 e1 和 e2 都会求值。 |
8.7. 算术表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e: T T 为 AbstractInt、AbstractFloat、i32、f32、f16、vecN<AbstractInt>、 vecN<AbstractFloat>、vecN<i32>、vecN<f32> 或 vecN<f16> | -e: T
| 取反。当 T 为向量时为分量级操作。 如果 T 是整型标量类型且 e 为最小负值,则结果为 e。 |
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e1 : T e2 : T S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 为 S 或 vecN<S> | e1 + e2 : T
|
加法。当 T 为向量时为分量级操作。
若 T 为浮点类型,标量定义域 是所有扩展实数对 (x,y),但不包括:
|
| e1 : T e2 : T S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 为 S 或 vecN<S> | e1 - e2 : T
|
减法。当 T 为向量时为分量级操作。
若 T 为浮点类型,标量定义域 是所有扩展实数对 (x,y),但不包括:
|
| e1 : T e2 : T S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 为 S 或 vecN<S> | e1 * e2 : T
|
乘法。当 T 为向量时为分量级操作。
若 T 为浮点类型,标量定义域 是所有扩展实数对 (x,y),但不包括:
|
| e1 : T e2 : T S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 为 S 或 vecN<S> | e1 / e2 : T
|
除法。当 T 为向量时为分量级操作。
若 T 为有符号整型标量类型,计算如下:
注意: 为保证截断行为,实现可能需要比无符号除法更多运算。 当两个操作数已知同号时,建议用无符号除法。 若 T 为无符号整型标量类型,计算如下:
若 T 为浮点类型,标量定义域 是所有扩展实数对 (x,y),但不包括:
|
| e1 : T e2 : T S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 为 S 或 vecN<S> | e1 % e2 : T
|
取余。当 T 为向量时为分量级操作。
若 T 为有符号整型标量类型,e1、e2 各求值一次,结果为:
注意: 非零时,结果与 e1 同号。 注意: 保证一致性可能使实现比无符号余数运算需要更多操作。 若 T 为无符号整型标量类型,结果为:
若 T 为浮点类型,结果等于: 若 T 为浮点类型,标量定义域 是所有扩展实数对 (x,y),但不包括:
|
| 前置条件 | 结论 | 语义 |
|---|---|---|
| S 为 AbstractInt、AbstractFloat、f32、f16、i32、u32 之一 V 为 vecN<S> es: S ev: V | ev + es: V
| ev + V(es)
|
es + ev: V
| V(es) + ev
| |
ev - es: V
| ev - V(es)
| |
es - ev: V
| V(es) - ev
| |
ev * es: V
| ev * V(es)
| |
es * ev: V
| V(es) * ev
| |
ev / es: V
| ev / V(es)
| |
es / ev: V
| V(es) / ev
| |
ev % es: V
| ev % V(es)
| |
es % ev: V
| V(es) % ev
|
| 前置条件 | 结论 | 语义 |
|---|---|---|
| e1、e2: matCxR<T> T 为 AbstractFloat、f32 或 f16 | e1 + e2: matCxR<T> | 矩阵加法:结果为分量级计算,结果第 i 列为 e1[i] + e2[i] |
e1 - e2: matCxR<T>
| 矩阵减法:结果为分量级计算,结果第 i 列为 e1[i] - e2[i] | |
| m: matCxR<T> s: T T 为 AbstractFloat、f32 或 f16 | m * s: matCxR<T> | 分量级缩放:(m * s)[i][j] 为
m[i][j] * s
|
s * m: matCxR<T> | 分量级缩放:(s * m)[i][j] 为
m[i][j] * s
| |
| m: matCxR<T> v: vecC<T> T 为 AbstractFloat、f32 或 f16 | m * v: vecR<T> | 线性代数矩阵-列向量积:
结果的第 i 分量为
dot(transpose(m)[i],v)
|
|
m: matCxR<T> v: vecR<T> T 为 AbstractFloat、f32 或 f16 | v * m: vecC<T> | 线性代数行向量-矩阵积: transpose(transpose(m) *
transpose(v))
|
| e1: matKxR<T> e2: matCxK<T> T 为 AbstractFloat、f32 或 f16 | e1 * e2: matCxR<T> | 线性代数矩阵乘积。 |
8.8. 比较表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e1: T e2: T S 为 AbstractInt、AbstractFloat、bool、i32、u32、f32 或 f16 T 为 S 或 vecN<S> TB 若 T 是向量,则为 vecN<bool>, 否则 TB 为 bool | e1 == e2: TB
| 等值。当 T 是向量时为分量级比较。 |
| e1: T e2: T S 为 AbstractInt、AbstractFloat、bool、i32、u32、f32 或 f16 T 为 S 或 vecN<S> TB 若 T 是向量,则为 vecN<bool>, 否则 TB 为 bool | e1 != e2: TB
| 不等值。当 T 是向量时为分量级比较。 |
| e1: T e2: T S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 为 S 或 vecN<S> TB 若 T 是向量,则为 vecN<bool>, 否则 TB 为 bool | e1 < e2: TB
| 小于。当 T 是向量时为分量级比较。 |
| e1: T e2: T S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 为 S 或 vecN<S> TB 若 T 是向量,则为 vecN<bool>, 否则 TB 为 bool | e1 <= e2: TB
| 小于等于。当 T 是向量时为分量级比较。 |
| e1: T e2: T S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 为 S 或 vecN<S> TB 若 T 是向量,则为 vecN<bool>, 否则 TB 为 bool | e1 > e2: TB
| 大于。当 T 是向量时为分量级比较。 |
| e1: T e2: T S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 为 S 或 vecN<S> TB 若 T 是向量,则为 vecN<bool>, 否则 TB 为 bool | e1 >= e2: TB
| 大于等于。当 T 是向量时为分量级比较。 |
8.9. 位运算表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e: T S 为 AbstractInt、i32 或 u32 T 为 S 或 vecN<S> | ~e : T
| 按位取反。 结果每一位都与 e 对应位相反。 若 T 是向量,则为分量级操作。 |
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e1: T e2: T S 为 AbstractInt、i32 或 u32 T 为 S 或 vecN<S> | e1 | e2: T
| 按位或。若 T 是向量,则为分量级操作。 |
| e1: T e2: T S 为 AbstractInt、i32 或 u32 T 为 S 或 vecN<S> | e1 & e2: T
| 按位与。若 T 是向量,则为分量级操作。 |
| e1: T e2: T S 为 AbstractInt、i32 或 u32 T 为 S 或 vecN<S> | e1 ^ e2: T
| 按位异或。若 T 是向量,则为分量级操作。 |
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e1: T e2: TS S 为 i32 或 u32 T 为 S 或 vecN<S> TS 若 T 为 S,则为 u32,否则为 vecN<u32> | e1 << e2: T
|
左移(被移值为具体类型):
将 e1 左移,在低位补零,高位溢出丢弃。 移位位数为 e2 的值,对 e1 位宽取模。
如 e1 与 e2 均在着色器执行开始前已知,结果不得溢出:
分量级操作,若 T 为向量。 |
| e1: T e2: TS T 为 AbstractInt 或 vecN<AbstractInt> TS 若 T 为 AbstractInt,则为 u32,否则为 vecN<u32> | e1 << e2: T
|
左移(被移值为抽象类型):
将 e1 左移,在低位补零,高位溢出丢弃。 移位位数为 e2 的值。 e1 的高 e2+1 位必须全相同。 否则会溢出。 注意: 该条件意味着所有被丢弃位必须与原值的符号位一致,且与结果符号位一致。 分量级操作,若 T 为向量。 |
| e1: T e2: TS S 为 i32 或 u32 T 为 S 或 vecN<S> TS 若 T 为 S,则为 u32,否则为 vecN<u32> | e1 >> e2: T |
右移(被移值为具体类型)。
将 e1 右移,低位丢弃。 若 S 为无符号类型,高位补零。 若 S 为有符号类型:
移位位数为 e2 的值,对 e1 位宽取模。 若 e2 大于等于 e1 位宽,则:
分量级操作,若 T 为向量。 |
| e1: T e2: TS T 为 AbstractInt 或 vecN<AbstractInt> TS 若 T 为 AbstractInt,则为 u32,否则为 vecN<u32> | e1 >> e2: T |
右移(抽象类型)。
将 e1 右移,低位丢弃。 若 e1 为负,高位补1,结果为负;否则高位补0。 移位位数为 e2 的值。 分量级操作,若 T 为向量。 |
8.10. 函数调用表达式
函数调用表达式会执行一次函数调用,被调用的函数具有返回类型。 如果被调用的函数没有返回值,应使用函数调用语句。 参见 § 9.5 函数调用语句。
8.11. 变量标识符表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| v 是标识符,解析为 在作用域内的变量, 该变量声明于地址空间 AS, 存储类型为T, 访问模式为AM | v: ref<AS,T,AM> | 结果是命名变量 v 内存的引用。 |
8.12. 形式参数表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| a 是标识符,解析为 在作用域内的形式参数声明,类型为 T | a: T | 结果是函数调用时在调用点传递给该参数的值。 |
8.13. 取地址表达式
取地址操作符将引用转换为其对应的指针。
| 前置条件 | 结论 | 说明 |
|---|---|---|
| r: ref<AS,T,AM> |
&r: ptr<AS,T,AM>
|
结果是与引用值 r 对应的
相同内存视图的指针值。
如果 r 是无效内存引用,则结果指针也为无效内存引用。 |
8.14. 间接寻址表达式
间接寻址操作符将指针转换为其对应的引用。
| 前置条件 | 结论 | 说明 |
|---|---|---|
| p: ptr<AS,T,AM> |
*p: ref<AS,T,AM>
|
结果是与指针值 p 对应的
相同内存视图的引用值。
如果 p 是无效内存引用,则结果引用也是无效内存引用。 |
8.15. 值声明的标识符表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| c 是标识符,解析为 在作用域内的const 声明,类型为 T | c: T | 结果为初始化表达式计算得到的值。 该表达式为const-expression,在着色器创建时求值。 |
| c 是标识符,解析为 在作用域内的override 声明,类型为 T | c: T |
如果管线创建时为常量 ID指定了值,
则结果为该值。
不同管线实例该值可能不同。
否则,结果为初始化表达式计算得到的值。 管线可重写常量出现在模块作用域,因此求值发生在着色器执行前。 注意: 若 API 调用未指定初始值且 |
| c 是标识符,解析为 在作用域内的let 声明,类型为 T | c: T | 结果为初始化表达式计算得到的值。
let
声明出现在函数体内,其初始值每次控制流到达声明处都会重新求值。 |
8.16. 枚举表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| e 是标识符,解析为预声明枚举项 属于枚举类型 E | e : E | 参见 § 6.3.1 预声明枚举项 |
8.17. 类型表达式
| 前置条件 | 结论 | 说明 |
|---|---|---|
| t 是标识符,解析为预声明类型 | t : AllTypes | 参见 § 6.9 预声明类型与类型生成器汇总 |
| a 是标识符,解析为类型别名。 | a : AllTypes | 此外,a 表示它所别名的类型。 |
| s 是标识符,解析为结构体类型的声明。 | s : AllTypes | 此外,s 表示该结构体类型。 |
|
tg 是标识符,解析为类型生成器
e1: T1 | tg _template_args_start e1, ..., eN _template_args_end : AllTypes |
每个类型生成器对其所需和可接受的模板参数有自己的要求,
并定义模板参数如何决定最终类型。
表达式 e1 到 eN 是该类型生成器的模板参数。 例如,类型表达式 见 § 6.9 预声明类型与类型生成器汇总 获取预声明类型生成器列表。 注意: 这两种写法仅区别于 eN 后是否有逗号。 |
| tg _template_args_start e1, ..., eN, _template_args_end : AllTypes |
8.18. 表达式语法总结
当标识符是call_phrase中的第一个词法单元时,其含义有:
声明与作用域规则保证这些名称总是唯一的。
expression ( ',' expression ) * ',' ?
'[' expression ']' component_or_swizzle_specifier ?
| multiplicative_expression multiplicative_operator unary_expression
| additive_expression additive_operator multiplicative_expression
| shift_expression _less_than shift_expression
| shift_expression _greater_than shift_expression
| shift_expression _less_than_equal shift_expression
| shift_expression _greater_than_equal shift_expression
binary_and_expression '&' unary_expression
| short_circuit_or_expression '||'
relational_expression
8.19. 运算符优先级与结合性
本小节全部为非规范性内容。
WGSL 右值表达式中的运算符优先级与结合性由其语法总结自然而然地体现出来。右值表达式将运算符分组以组织它们,具体见下图:
为了通过冗余提升可读性,以下分组不会与其他分组结合:
以下分组不会与自己结合:
上述分组若需结合,必须使用括号明确关系。以下示例注释中说明了这些规则导致表达式无效的情况:
let a = x & ( y ^ ( z | w )); // 无效:x & y ^ z | w let b = ( x + y ) << ( z >= w ); // 无效:x + y << z >= w let c = x < ( y > z ); // 无效:x < y > z let d = x && ( y || z ); // 无效:x && y || z
推导出的优先级决定了表达式中隐含括号的位置,优先级高的运算符会像被括号包围一样与优先级低的运算符结合。例如,乘法优先级高于加法,所以表达式 a + b * c 推导为
(a + (b * c))。类似地,结合性决定了这些隐含括号的方向。例如,左结合会把 a + b + c 推导为
((a + b) + c),而右结合会把 * * a 推导为 (* (* a))。
下表总结了运算符优先级、结合性和绑定关系,自上而下从强到弱排列。绑定列指明了该运算符可包含的更强表达式。例如,“All above” 表示该运算符可包含任意比其强的表达式;而“Unary”表示只有比一元更弱、但比本行更强的表达式需要括号与该运算符结合。该列用于线性列出运算符的必要性。
| 名称 | 运算符 | 结合性 | 绑定 |
|---|---|---|---|
| 括号 | (...)
| ||
| 主表达式 | a(), a[], a.b
| 左结合 | |
| 一元 | -a, !a, ~a, *a, &a
| 右结合 | 以上全部 |
| 乘法 | a*b, a/b, a%b
| 左结合 | 以上全部 |
| 加法 | a+b, a-b
| 左结合 | 以上全部 |
| 移位 | a<<b, a>>b
| 需要括号 | 一元 |
| 关系 | a<b, a>b, a<=b, a>=b,
a==b, a!=b
| 需要括号 | 以上全部 |
| 按位与 | a&b
| 左结合 | 一元 |
| 按位异或 | a^b
| 左结合 | 一元 |
| 按位或 | a|b
| 左结合 | 一元 |
| 短路与 | a&&b
| 左结合 | 关系 |
| 短路或 | a||b
| 左结合 | 关系 |
9. 语句
语句是一个控制执行的程序片段。 语句通常按顺序执行;但控制流语句可能导致程序以非顺序方式执行。
9.1. 复合语句
复合语句 是由大括号包围的零个或多个语句序列。 当声明是其中的一个语句时,其标识符 从下一个语句开始直到复合语句结束都在作用域内。
continuing_compound_statement 是复合语句的一种特殊形式, 作为continuing语句的主体,并允许在末尾有一个可选的break-if语句。
9.2. 赋值语句
赋值会计算一个表达式, 并可选地将其存储到内存中(从而更新变量的内容)。
lhs_expression ( '=' | compound_assignment_operator ) expression
| '_' '=' expression
运算符左边的文本称为左值(left-hand side), 运算符右边的表达式称为右值(right-hand side)。
9.2.1. 简单赋值
当赋值的
左值为表达式,且运算符为等号('=')时,就是简单赋值。
在这种情况下,右值的值会写入左值引用的内存中。
| 前置条件 | 语句 | 说明 |
|---|---|---|
| e: T, T 为具体可构造类型, r: ref<AS,T,AM>, AS 为可写地址空间, 访问模式 AM为write或read_write | r = e |
先计算r,再计算e,然后将e计算得到的值写入r引用的内存位置中。
注意: 如果该引用是无效内存引用,写操作可能不会执行,或者会写到意外的内存位置。 |
最简单的情况,左值是变量名。 其他情况参见§ 6.4.8 引用和指针值的形成。
struct S { age : i32, weight : f32} var < private> person : S ; fn f () { var a : i32= 20 ; a = 30 ; // 用 30 替换 'a' 的内容。 person . age = 31 ; // 将 31 写入 person 变量的 age 字段。 var uv : vec2< f32> ; uv . y = 1.25 ; // 把 1.25 放到 uv 的第二个分量。 let uv_x_ptr : ptr< function, f32> = & uv . x ; * uv_x_ptr = 2.5 ; // 把 2.5 放到 uv 的第一个分量。 var sibling : S ; // 把 'person' 变量的内容拷贝到 'sibling' 变量中。 sibling = person ; }
9.2.2. 占位赋值(Phony Assignment)
当赋值的
左值是下划线('_')时,就是占位赋值。
此时右值会被计算,然后被忽略。
| 前置条件 | 语句 | 说明 |
|---|---|---|
| e: T, T 是可构造类型,指针类型,纹理类型或采样器类型 | _ = e |
计算e。
注意: 结果值不会被存储。
|
占位赋值常用于:
-
调用返回值不需要的函数,但明确表示结果未被使用。
-
注意: 缓冲区变量的存储类型可能不是可构造类型,例如包含原子类型或运行时大小数组。 这种情况下,应使用指向变量内容的指针。
var < private> counter : i32; fn increment_and_yield_previous () -> i32{ let previous = counter ; counter = counter + 1 ; return previous ; } fn user () { // 递增计数器,但不关心返回值。 _ = increment_and_yield_previous (); }
struct BufferContents { counter : atomic< u32> , data : array< vec4< f32>> } @group ( 0 ) @binding ( 0 ) var < storage> buf : BufferContents ; @group ( 0 ) @binding ( 1 ) var t : texture_2d< f32> ; @group ( 0 ) @binding ( 2 ) var s : sampler; @fragment fn shade_it () -> @location ( 0 ) vec4< f32> { // 用占位赋值声明 buf、t、s 是着色器接口的一部分,但不做任何操作。 _ = & buf ; _ = t ; _ = s ; return vec4< f32> (); }
9.2.3. 复合赋值
当赋值的 左值是表达式,且运算符为复合赋值运算符之一时,就是复合赋值。
每个语句的类型要求、语义和行为如表所示地展开,区别在于:
-
引用表达式 e1 只会被计算一次,并且
-
e1 的引用类型 必须具有read_write的访问模式。
| 语句 | 展开 |
|---|---|
| e1 += e2 | e1 = e1 + (e2) |
| e1 -= e2 | e1 = e1 - (e2) |
| e1 *= e2 | e1 = e1 * (e2) |
| e1 /= e2 | e1 = e1 / (e2) |
| e1 %= e2 | e1 = e1 % (e2) |
| e1 &= e2 | e1 = e1 & (e2) |
| e1 |= e2 | e1 = e1 | (e2) |
| e1 ^= e2 | e1 = e1 ^ (e2) |
| e1 >>= e2 | e1 = e1 >> (e2) |
| e1 <<= e2 | e1 = e1 << (e2) |
注意: 即使引用 e1 只计算一次,其底层内存被访问两次: 首先是读访问获得旧值,然后写访问存储新值。
var < private> next_item : i32= 0 ; fn advance_item () -> i32{ next_item += 1 ; // 给 next_item 加 1。 return next_item - 1 ; } fn bump_item () { var data : array< f32, 10 > ; next_item = 0 ; // 给 data[0] 加 5.0,并且 advance_item() 只调用一次。 data [ advance_item ()] += 5.0 ; // 此处 next_item 为 1。 } fn precedence_example () { var value = 1 ; // 复合赋值语句右侧是一个完整的表达式。 value *= 2 + 3 ; // 等价于 value = value * (2 + 3); // 'value' 此时为 5。 }
e1+=e2;
可以重写为
其中标识符{ let p = &(e1); *p = *p + (e2); }
p 要与程序中其它标识符不同。
ev可以重写为[c] +=e2;
其中{ let p = &(ev); let c0 =c; (*p)[c0] = (*p)[c0] + (e2); }
c0 和 p 也要与其他标识符不同。
9.3. 自增和自减语句
自增语句将变量的内容加 1。 自减语句将变量的内容减 1。
表达式必须求值为一个引用,该引用具有具体整数标量存储类型和 read_write 访问模式。
| 前置条件 | 语句 | 描述 |
|---|---|---|
| r : ref<AS,T,read_write>, T 是具体整数标量 | r++
| 将 r 所引用的内存内容加 1。
等同于 r += T(1) |
| r : ref<AS,T,read_write>, T 是具体整数标量 | r--
| 将 r 所引用的内存内容减 1。
等同于 r -= T(1) |
9.4. 控制流
控制流语句可能导致程序以非顺序方式执行。
9.4.1. If 语句
if 语句根据条件表达式的结果,有条件地执行最多一个复合语句。
一个 if 语句包含一个 if 子句,后面跟零个或多个 else if 子句,最后可选跟一个 else 子句。
'else' 'if' expression compound_statement
'else' compound_statement
类型规则前置条件:
每个 if 和 else if 子句中的表达式必须为bool类型。
if 语句的执行流程如下:
-
首先计算
if子句的条件。 如果结果为true,则控制流转到第一个复合语句(紧跟在条件表达式之后)。 -
否则,按文本顺序计算下一个
else if子句的条件(如存在)。 如果结果为true,则控制流转到关联的复合语句。-
对所有
else if子句重复上述行为,直到某个条件为true。
-
-
如果没有任何条件为
true,则控制流转到else子句关联的复合语句(若存在)。
9.4.2. Switch 语句
switch 语句根据选择器表达式的结果,将控制流转移到一组case 子句之一,或default 子句。
attribute * 'switch' expression switch_body
attribute * '{' switch_clause + '}'
'case' case_selectors ':' ? compound_statement
'default' ':' ? compound_statement
case_selector ( ',' case_selector ) * ',' ?
'default'
case 子句是'case'关键字后跟逗号分隔的选择器列表和一个复合语句主体。
default-alone
子句是'default'关键字后跟一个复合语句主体。
default 子句可以是:
每个 switch 语句必须恰好有一个default 子句。
'default'
token 不得在单个
case_selector
列表中出现超过一次。
类型规则 前置条件: 对于单个 switch 语句,选择器表达式和所有 case 选择器表达式必须具有 相同的具体整数标量类型。
case_selectors 中的表达式必须 是 const-expressions。
同一个 switch 语句中的两个不同 case 选择器表达式不得具有相同的值。
如果选择器值等于某个case_selector列表中的表达式值, 则控制流转到该case 子句的主体。 如果选择器值与所有 case 选择器值都不相等,则控制流转到default 子句的主体。
当控制流到达某个子句主体末尾时,控制流转到 switch 语句后的第一个语句。
当子句主体中的某个语句是声明时, 它遵循作用域和生命周期的正常规则。 即,主体是语句序列,如果其中有声明,则该声明的作用域从下一个语句开始直到主体结束。 声明在执行时创建一个新的变量或值实例,并初始化它。
var a : i32; let x : i32= generateValue (); switch x { case 0 : { // 冒号可选 a = 1 ; } default { // default 不必须写在最后 a = 2 ; } case 1 , 2 , { // 可使用多个选择器值 a = 3 ; } case 3 , { // 结尾逗号可选 a = 4 ; } case 4 { a = 5 ; } }
const c = 2 ; var a : i32; let x : i32= generateValue (); switch x { case 0 : { a = 1 ; } case 1 , c { // case 选择器可用常量表达式 a = 3 ; } case 3 , default { // default 关键字可与其他选择器同用 a = 4 ; } }
9.4.3. 循环语句
loop 语句会重复执行一次循环体; 循环体由复合语句指定。 每次循环体执行称为一次迭代。
循环体的最后一条语句可以可选为 continuing 语句。
如果loop会执行无限多次迭代,则会发生动态错误。 这可能导致循环提前终止、其它非局部影响,甚至设备丢失。
若循环体中的某条语句是声明, 则遵循作用域和生命周期的常规规则。 即,循环体是语句序列,如其中有声明,则声明的作用域从下一个语句起至循环体结束。 每次到达声明时都会执行,因而每次新迭代都创建新的变量或值实例,并重新初始化。
注意: loop 语句是一种专用结构,你可能更想用 for
或 while 语句。loop 语句是与其他着色器语言最大不同之处之一。
此设计直接表达了编译代码中常见的循环习惯用法。 特别是,将循环更新语句放在循环体结尾,可以自然地使用循环体中定义的值。
-
<1> 初始化在循环之前列出。
var a : i32= 2 ; let step : i32= 1 ; for ( var i : i32= 0 ; i < 4 ; i += step ) { if ( i % 2 == 0 ) { continue ; } a *= 2 ; }
var a : i32= 2 ; var i : i32= 0 ; loop { if i >= 4 { break ; } let step : i32= 1 ; i = i + step ; if i % 2 == 0 { continue ; } a = a * 2 ; }
var a : i32= 2 ; var i : i32= 0 ; loop { if i >= 4 { break ; } let step : i32= 1 ; if i % 2 == 0 { continue ; } a = a * 2 ; continuing { // <2> i = i + step ; } }
-
<2>
continue结构放在loop的结尾。
9.4.4. For 语句
attribute * 'for' '(' for_header ')' compound_statement
for_init ? ';' expression ? ';' for_update ?
for 语句是 复合语句 的语法糖,复合语句中包含一个 循环 语句。
通常,for 语句的形式如下
for (initializer;condition;update_part) {body}
当存在条件表达式时,for 语句会被转换为如下形式的循环:
{当条件表达式缺失时,
initializer ;
loop {
if !(condition) { break; }
body
continuing { update_part }
}
}
for 语句会被转换为如下形式的循环:
{
initializer ;
loop {
body
continuing { update_part }
}
}
另外:
-
如果
initializer非空,则会在第一次 迭代 前,在额外的 作用域 中执行。 初始化器中的声明,其作用域会一直延伸到循环体的末尾。 -
类型规则前置条件:如果条件非空,其值 必须 是 bool 类型的表达式。
-
如果存在条件表达式,则会在每次执行 for 循环体前立即对其求值。 如果条件为假,则会执行 § 9.4.6 Break 语句,结束循环的执行。 该检查会在每次循环迭代开始时进行。
-
-
如果
update_part非空,则会在循环构造的末尾成为一个 continuing 语句。 -
转换语法糖时,会 重新命名
body中声明的标识符,以确保update_part中的所有标识符依然 解析 到和转换前相同的声明。
for 循环的 initializer 会在执行循环之前执行一次。
当 声明 出现在初始化器中时,它的 标识符 会 在作用域内 直到 body 末尾。
与 body 中的声明不同,初始化器中的声明不会在每次迭代时被重新初始化。
condition、body 和 update_part 会按此顺序执行,形成一次循环 迭代。
body 是 复合语句 的一种特殊形式。
在 body 中声明的标识符 在作用域内,从下一个语句开始直到 body 结束。
该声明每次被执行时,都会创建一个新的变量或常量实例,并重新初始化。
var a : i32= 2 ; for ( var i : i32= 0 ; i < 4 ; i ++ ) { if a == 0 { continue ; } a = a + 2 ; }
转换为:
var a : i32= 2 ; { // 为循环变量 i 引入新作用域 var i : i32= 0 ; loop { if ! ( i < 4 ) { break ; } if a == 0 { continue ; } a = a + 2 ; continuing { i ++ ; } } }
var a : i32= 2 ; for ( var i : i32= 0 ; ; i ++ ) { if a == 0 { continue ; } if i == 4 { break ; } a = a + 2 ; }
转换为:
var a : i32= 2 ; { // 为循环变量 i 引入新作用域 var i : i32= 0 ; loop { // 注意:转换语法糖不会在这里引入 if 分支。 if a == 0 { continue ; } if i == 4 { break ; } a = a + 2 ; continuing { i ++ ; } } }
如果 for 循环会执行无限次 迭代,会发生 运行时错误。 这可能导致循环提前终止、产生其他非局部影响,甚至 丢失设备。
9.4.5. While 语句
attribute * 'while' expression compound_statement
while 语句是一种带条件的循环。 每次循环迭代开始时,会计算一个布尔条件。 如果条件为假,则 while 循环结束执行。 否则,执行本次迭代剩余部分。
while 循环可视为loop或for语句的语法糖。 下列三种写法等价:
-
whilecondition{body_statements} -
loop { if !condition{break;}body_statements} -
for (;condition;) {body_statements}
如果while循环会执行无限多次迭代,则会发生动态错误。 这可能导致循环提前终止、其它非局部影响,甚至设备丢失。
9.4.6. Break 语句
'break'
break 语句将控制流转移到最近包裹的循环体或switch语句体之后,从而结束循环或 switch 语句的执行。
break 语句只能用于loop、for、while和switch语句内。
break 语句不能放在会跳出循环continuing语句的位置。
应使用break-if语句代替。
var a : i32= 2 ; var i : i32= 0 ; loop { let step : i32= 1 ; if i % 2 == 0 { continue ; } a = a * 2 ; continuing { i = i + step ; if i >= 4 { break ; } // 非法,需用 break-if。 } }
9.4.7. Break-If 语句
'break' 'if' expression ';'
break-if 语句会计算一个布尔条件; 如果条件为 true,则控制流转到最近包裹的loop 语句体之后,结束该循环的执行。
注意: break-if 语句只能作为continuing 语句体的最后一条语句出现。
var a : i32= 2 ; var i : i32= 0 ; loop { let step : i32= 1 ; if i % 2 == 0 { continue ; } a = a * 2 ; continuing { i = i + step ; break if i >= 4 ; } }
9.4.8. Continue 语句
'continue'
continue 语句在最近包裹的loop中:
-
如果存在,在该循环体末尾向前跳转到continuing语句。
-
否则向后跳转到循环体的第一条语句,开始下一次迭代。
continue语句必须只用于loop、for或while语句中。
continue语句不得被放置在会将控制流跳转到外层continuing语句的位置。
(跳转到continuing语句时,这是一种向前分支。)
continue语句不得被放置在会将控制流穿越某个在目标continuing语句中使用的声明的位置。
注意:只有在continuing语句中用于控制流在其它嵌套于该continuing语句内的循环时,才能使用continue。也就是说,continue不能用于将控制流直接跳转到当前正在执行的continuing语句的开头。
var i : i32= 0 ; loop { if i >= 4 { break ; } if i % 2 == 0 { continue ; } // <3> let step : i32= 2 ; continuing { i = i + step ; } }
-
<3>
continue是无效的,因为它会绕过在continuing结构中使用的step的声明。
9.4.9. Continuing 语句
'continuing' continuing_compound_statement
continuing 语句指定每次循环迭代结束时要执行的复合语句。 该结构是可选的。
9.4.10. Return 语句
'return' expression ?
return 语句结束当前函数的执行。 如果该函数是一个入口点, 则当前着色器调用 被终止。 否则,求值将在当前函数调用的调用点 求值之后的下一个表达式或语句处继续。
如果函数没有返回 类型,则 return 语句是 可选的。如果为这样的函数提供了 return 语句,则它不得 提供值。 否则,表达式必须存在,并称为返回值。 在这种情况下,此函数调用的调用点求值为返回值。 返回值的类型必须匹配函数的返回类型。
9.4.11. Discard 语句
discard 语句会将当前调用转换为辅助调用并丢弃片元。
discard 语句只能用于fragment着色器阶段。
更准确地说,执行 discard 语句将会:
-
将当前调用转换为辅助调用,并
-
阻止当前片元在GPURenderPipeline中被进一步处理。
只有在 discard 语句之前执行的语句会产生可观察到的效果。
注意: discard 语句可以被 fragment
阶段的任意函数执行,其效果相同:该片元会被丢弃。
@group ( 0 ) @binding ( 0 ) var < storage, read_write> will_emit_color : u32; fn discard_if_shallow ( pos : vec4< f32> ) { if pos . z < 0.001 { // 如果执行到这里,will_emit_color 变量将不会被设置为 1,因为辅助调用不会写入共享内存。 discard ; } will_emit_color = 1 ; } @fragment fn main ( @builtin ( position) coord_in : vec4< f32> ) -> @location ( 0 ) vec4< f32> { discard_if_shallow ( coord_in ); // 只有未被 discard 的片元才会设置 will_emit_color = 1 并输出红色。 will_emit_color = 1 ; return vec4< f32> ( 1.0 , 0.0 , 0.0 , 1.0 ); }
9.5. 函数调用语句
函数调用语句会执行一次函数调用。
如果被调用函数带有must_use属性,则会导致着色器创建错误。
注意: 如果函数有返回值, 且未带有must_use属性, 则该返回值会被忽略。
9.6. 语句语法总结
statement 规则匹配可以在函数体内大多数位置使用的语句。
此外,某些语句仅能用于特定上下文:
9.7. 语句行为分析
9.7.1. 规则
一些影响控制流的语句只在特定语境下有效。例如,continue 在 loop、 for 或 while 之外是无效的。 此外,统一性分析(见 § 15.2 Uniformity)需要知道控制流何时可能以多种方式退出语句。
这两个目标都通过一个用于总结语句执行行为的系统实现。行为分析会将每个语句映射到语句执行完成后可能继续执行的方式集合。与值和表达式的类型分析类似,行为分析是自底向上的:先确定某些基本语句的行为,然后通过组合规则确定更高层结构的行为。
行为是一个集合,其元素可以是:
-
Return
-
Break
-
Continue
-
Next
每一种都对应复合语句的一种退出方式:要么通过关键字,要么通过“Next”进入下一语句。
我们用“s: B”表示 s 遵循行为相关规则,并具有行为 B。
对于每个函数:
我们为每个函数分配一个行为:它 是其主体的行为(将 主体视为普通语句),并将任何 "Return" 替换为 "Next"。 作为上述规则的结果,函数行为始终是 {} 或 {Next} 之一。
| 语句 | 前置条件 | 产生的行为 |
|---|---|---|
| 空语句 | {Next} | |
| {s} | s: B | B |
|
s1 s2
注: s1 通常以分号结尾。 | s1: B1 Next 在 B1 s2: B2 | (B1∖{Next}) ∪ B2 |
| s1: B1 Next 不在 B1 s2: B2 | B1 | |
| var x:T; | {Next} | |
| let x = e; | {Next} | |
| var x = e; | {Next} | |
| x = e; | {Next} | |
| _ = e; | {Next} | |
| f(e1, ..., en); | f 有行为 B | B |
| return; | {Return} | |
| return e; | {Return} | |
| discard; | {Next} | |
| break; | {Break} | |
| break if e; | {Break, Next} | |
| continue; | {Continue} | |
| const_assert e; | {Next} | |
| if e s1 else s2 |
s1: B1 s2: B2 | B1 ∪ B2 |
| loop {s1 continuing {s2}} |
s1: B1 s2: B2 B1 = {Return} {Continue, Return} 都不在 B2 | {Return} |
|
s1: B1 s2: B2 B1 ≠ {Return} {Continue, Return} 都不在 B2 Break 不在 (B1 ∪ B2) | (B1 ∪ B2)∖{Continue, Next} | |
|
s1: B1 s2: B2 B1 ≠ {Return} {Continue, Return} 都不在 B2 Break 在 (B1 ∪ B2) 内 | (B1 ∪ B2 ∪ {Next})∖{Break, Continue} | |
| switch e {case c1: s1 ... case cn: sn} |
s1: B1 ... sn: Bn Break 不在 (B1 ∪ ... ∪ Bn) 内 | B1 ∪ ... ∪ Bn |
|
s1: B1 ... sn: Bn Break 在 (B1 ∪ ... ∪ Bn) 内 | (B1 ∪ ... ∪ Bn ∪ {Next})∖Break |
注:∪ 是集合并集运算,∖ 是集合差集运算。
注:空语句的情况出现在 loop 体为空,或 for
循环缺少初始化或更新语句时。
为分析目的:
-
for循环会被降糖处理(见 § 9.4.4 For 语句) -
while循环会被降糖处理(见 § 9.4.5 While 语句) -
loop {s}等价于loop {s continuing {}} -
没有
else分支的if语句等价于以else {}结束;这样会在其 行为 中添加 Next -
带
else if分支的if语句视为嵌套的简单if/else语句 -
以
default开头的 switch_clause 与以case _:开头的 switch_clause 行为一致
每个 内置函数 的 行为 都是 {Next}。 任何上表未列出的运算符应用,其 行为 与同操作数函数调用,且函数行为为 {Next} 时一致。
函数的行为必须满足上述规则。
注:上述规则意味着循环的行为是 {Next}、{Return} 或 {Next,Return}。
注:无需分析表达式的行为,因为它们始终为 {Next},或者之前分析过的函数会产生错误。
9.7.2. 说明
本节为信息性内容,非规范性。
行为分析会导致程序因以下原因被拒绝(重述上文要求):
-
函数体(作为普通语句处理)具有不属于 {Next, Return} 的行为。
-
带返回类型的函数体行为不是 {Return}。
-
continuing 块的行为包含 Continue 或 Return。
-
某些明显的无限循环行为集合为空,因此无效。
该分析可线性时间完成,自下而上分析调用图(因为函数调用的行为可依赖于函数的实际代码)。
9.7.3. 示例
以下是行为分析实际应用的一些示例:
fn simple () -> i32{ var a : i32; return 0 ; // 行为: {Return} a = 1 ; // 有效,静态不可达代码。 // 该语句行为: {Next} // 总体行为(因顺序语句规则): {Return} return 2 ; // 有效,静态不可达代码。行为: {Return} } // 函数行为: {Return}
fn nested () -> i32{ var a : i32; { // 复合语句开始 a = 2 ; // 行为: {Next} return 1 ; // 行为: {Return} } // 该复合语句整体行为: {Return} a = 1 ; // 有效,静态不可达代码。 // 该语句行为: {Next} // 总体行为: {Return} return 2 ; // 有效,静态不可达代码。行为: {Return} }
fn if_example () { var a : i32= 0 ; loop { if a == 5 { break ; // 行为: {Break} } // 整个 if 复合语句行为: {Break, Next}, // 因为 if 有隐式空 else a = a + 1 ; // 有效,因前一语句行为包含 Next } }
fn if_example () { var a : i32= 0 ; loop { if a == 5 { break ; // 行为: {Break} } else { continue ; // 行为: {Continue} } // 整个 if 复合语句行为: {Break, Continue} a = a + 1 ; // 有效,静态不可达代码。 // 该语句行为: {Next} // 总体行为: {Break, Continue} } }
fn if_example () { var a : i32= 0 ; loop { // if e1 s1 else if e2 s2 else s3 // 等价于 // if e1 else { if e2 s2 else s3 } if a == 5 { break ; // 行为: {Break} } else if a == 42 { continue ; // 行为: {Continue} } else { return ; // 行为: {Return} } // 整个 if 复合语句行为: // {Break, Continue, Return} } // 整个 loop 复合语句行为 {Next, Return} } // 整个函数行为 {Next}
fn switch_example () { var a : i32= 0 ; switch a { default : { break ; // 行为: {Break} } } // 行为: {Next},因为 switch 会用 Next 替换 Break a = 5 ; // 有效,因为前一语句行为包含 Next }
fn invalid_infinite_loop () { loop { discard ; // 行为 { Next }。 } // 无效,整个 loop 行为为 { }。 }
fn conditional_continue () { var a : i32; loop { if a == 5 { break ; } // 行为: {Break, Next} if a % 2 == 1 { // 有效,因前一语句行为包含 Next continue ; // 行为: {Continue} } // 行为: {Continue, Next} a = a * 2 ; // 有效,因前一语句行为包含 Next continuing { // 有效,continuing 行为为 {Next} // 不包含 {Break, Continue, Return} a = a + 1 ; } } // loop 整体行为 {Next}, // 吸收 "Continue" 和 "Next", // 然后将 "Break" 替换成 "Next" }
fn redundant_continue_with_continuing () { var a : i32; loop { if a == 5 { break ; } continue ; // 有效。此为冗余,直接跳到下条语句。 continuing { a = a + 1 ; } } }
fn continue_end_of_loop_body () { for ( var i : i32= 0 ; i < 5 ; i ++ ) { continue ; // 有效。此为冗余, // 跳转到循环体末尾。 } // 行为: {Next}, // 因为循环吸收 "Continue", // 且 for 循环始终加上 "Next" }
for 循环会解糖为带条件 break 的 loop。如前例所示,条件 break 的 行为为 {Break, Next},从而导致为循环加上 "Next" 行为。
fn missing_return () -> i32{ var a : i32= 0 ; if a == 42 { return a ; // 行为: {Return} } // 行为: {Next, Return} } // 错误:Next 不允许出现在带返回类型函数体 // 中
fn continue_out_of_loop () { var a : i32= 0 ; if a > 0 { continue ; // 行为: {Continue} } // 行为: {Next, Continue} } // 错误:Continue 在函数体中无效
continue 换成 break,该例同样无效。
10. 断言
断言是用于确保布尔条件成立的检查。
'const_assert' expression
10.1. 常量断言语句
const assertion 语句是一种断言,如果表达式求值为
false,则会产生着色器创建错误。
该表达式必须是一个 const-expression。
该语句可以满足着色器中的静态访问条件,
但除此之外不会对编译后的着色器产生影响。
const assertion 可以出现在
模块作用域,也可以作为函数作用域语句出现。
const x = 1 ; const y = 2 ; const_assert x < y ; // 在模块作用域有效。 const_assert ( y != 0 ); // 括号可选。 fn foo () { const z = x + y - 2 ; const_assert z > 0 ; // 在函数内有效。 let a = 3 ; const_assert a != 0 ; // 无效,表达式必须为常量表达式。 }
11. 函数
函数在被调用时执行计算任务。
函数可以通过以下方式之一被调用:
-
通过计算函数调用表达式。参见 § 8.10 函数调用表达式。
-
通过执行函数调用语句。参见 § 9.5 函数调用语句。
WGSL 中的函数可以以任意顺序定义,包括可以在用到它之后才定义。 因此不需要函数原型或前置声明,也没有办法做前置声明。
函数分为两类:
11.1. 声明用户自定义函数
函数声明用于创建用户自定义函数,需要指定:
函数声明必须只出现在模块作用域。 函数名在整个程序中都在作用域内。
形式参数声明为一个在调用函数时必须提供的值 指定一个标识符名称和类型。 形式参数可以具有属性。 见 § 11.2 函数调用。 标识符的作用域是函数主体。 给定函数的两个形式参数不得具有相同名称。
注:某些内建函数可以允许参数为 抽象数值 类型; 但是,此功能目前不支持用户声明的 函数。
WGSL 定义了以下可应用于函数声明的属性:
WGSL 定义了以下可用于函数参数和返回类型的属性:
'fn' ident '(' param_list ? ')' ( '->' attribute * template_elaborated_ident ) ?
// 声明 add_two 函数。 // 有两个形式参数:i 和 b。 // 返回类型为 i32。 // 函数体包含 return 语句。 fn add_two ( i : i32, b : f32) -> i32{ return i + 2 ; // 形式参数可在函数体内使用。 } // 一个计算着色器入口点函数,名为 main。 // 没有指定返回类型。 // 调用 add_two 函数,并将结果赋值给名为 six 的值。 @compute @workgroup_size ( 1 ) fn main () { let six : i32= add_two ( 4 , 5.0 ); }
11.2. 函数调用
函数调用是调用函数的语句或表达式。
包含函数调用的函数称为调用方函数,或调用者。 被调用的函数称为被调用方函数,或被调用者。
函数调用:
-
指定被调用方函数的名称,以及
-
提供一个用括号括起来、逗号分隔的实参值表达式列表。
函数调用必须提供与被调用方函数的形式参数数量一致的实参值。 每个实参值必须按顺序与对应形式参数类型相同。
总结,调用函数时:
被调用方函数返回的方式如下:
具体来说,执行函数调用时会发生以下步骤:
-
计算函数调用的实参值,求值顺序为从左到右。
-
如果被调用方函数是用户自定义函数, 则为其函数作用域变量分配内存。
-
初始化方式见§ 7.3 var 声明。
-
-
为被调用方函数的形式参数赋值, 按顺序与调用点的实参一一对应。例如,被调用方函数的第一个形式参数取调用点第一个实参的值。
-
执行被调用方函数,直到其返回。
-
控制流回到调用方函数,被调用方函数的执行结束挂起。 如果被调用方函数返回一个值,该值将作为函数调用表达式的结果。
函数调用的位置称为调用点,具体为解析出的call_phrase语法规则实例的第一个token所在位置。 调用点属于动态上下文。 因此,相同文本位置可能对应多个调用点。
注意: 如果 fragment 着色器中的所有 quad 调用都被 discard,则有可能某个函数调用永远不会返回。在这种情况下,控制流不会回到调用方函数。
11.3. const 函数
带有const属性声明的函数可以在着色器创建时求值。 这些函数称为常量函数。 对这些函数的调用可以作为常量表达式的一部分。
如果该函数包含任何不是常量表达式的表达式,或包含任何不是const 声明的声明,则会导致着色器创建错误。
注意: const 属性不能用于用户自定义函数。
const first_one = firstLeadingBit ( 1234 + 4567 ); // 求值为 12 // first_one 类型为 i32,因为 // firstLeadingBit 不能操作 AbstractInt @id ( 1 ) override x : i32; override y = firstLeadingBit ( x ); // 常量表达式可用于 override 表达式 // firstLeadingBit(x) 在此不是常量表达式 fn foo () { var a : array< i32, firstLeadingBit ( 257 ) > ; // 若所有参数为常量表达式,常量函数可用于常量表达式 }
11.4. 函数限制
-
-
可构造类型
-
指针类型
-
纹理类型
-
采样器类型
-
-
每个函数调用实参必须求值为对应 函数参数的类型。
注意: 不允许递归,因为各类声明之间不允许有环。
fn bar ( p : ptr< function, f32> ) { } fn baz ( p : ptr< private, i32> ) { } fn bar2 ( p : ptr< function, f32> ) { let a = &*&* ( p ); bar ( p ); // 有效 bar ( a ); // 有效 } fn baz2 ( p : ptr< storage, f32> ) { } struct S { x : i32} @group ( 0 ) @binding ( 0 ) var < storage> ro_storage : f32; @group ( 0 ) @binding ( 1 ) var < storage, read_write> rw_storage : f32; var usable_priv : i32; var unusable_priv : array< i32, 4 > ; fn foo () { var usable_func : f32; var unusable_func : S ; var i32_func : i32; let a_priv = & usable_priv ; let b_priv = a_priv ; let c_priv = &*& usable_priv ; let d_priv = & ( unusable_priv . x ); let e_priv = d_priv ; let a_func = & usable_func ; let b_func = & unusable_func ; let c_func = & ( * b_func )[ 0 ]; let d_func = c_func ; let e_func = &* a_func ; baz ( & usable_priv ); // 有效,变量取地址。 baz ( a_priv ); // 有效,本质上是变量取地址。 baz ( b_priv ); // 有效,本质上是变量取地址。 baz ( c_priv ); // 有效,本质上是变量取地址。 baz ( d_priv ); // 有效,内存视图已变。 baz ( e_priv ); // 有效,内存视图已变。 baz ( & i32_func ); // 无效,地址空间不匹配。 bar ( & usable_func ); // 有效,变量取地址。 bar ( c_func ); // 有效,内存视图已变。 bar ( d_func ); // 有效,内存视图已变。 bar ( e_func ); // 有效,本质上是变量取地址。 baz2 ( & ro_storage ); // 有效,变量取地址。 baz2 ( & rw_storage ); // 无效,访问模式不匹配。 }
11.4.1. 别名分析
11.4.1.1. 根标识符
内存位置可以通过内存视图在函数执行期间被访问。 在一个函数内部,每个内存视图都有一个特定的根标识符,该标识符命名了首次在该函数中提供访问该内存的变量或形式参数。
本地派生的引用类型或 指针类型的表达式可能为某个根标识符引入新名字, 但每个表达式都有静态可确定的根标识符。
给定一个指针或引用类型的表达式E,其 根标识符为如下找到的原始变量或指针类型形式参数:
-
如果E是一个标识符,解析为变量,则根标识符为该变量。
-
如果E是一个标识符,解析为指针类型的形式参数,则根标识符为该形式参数。
-
如果E的形式为
(E2)、&E2、*E2或 E2[Ei],那么根标识符为E2的根标识符。 -
如果E是形如E2.member_name的结构体访问表达式,则根标识符为E2的根标识符。
11.4.1.2. 别名分析
虽然根标识符的起源变量是一个动态概念, 取决于该函数的调用点, 但可以对 WGSL 模块进行静态分析,以确定每个根标识符的所有可能起源变量集合。
当两个根标识符具有相同的 起源 变量时,它们别名。 WGSL 函数的执行不得可能 通过别名根标识符访问内存,其中一次访问是写入, 另一次访问是读取或写入。 这是通过从调用图的叶子向上(即按拓扑顺序)分析程序来确定的。 对于每个函数,分析会记录以下集合:
在函数的每个调用点, 如果发生以下任一情况,则为着色器创建错误:
-
两个指针类型实参具有相同的根标识符,并且任一 对应参数在被写参数集合中。
-
一个指针类型实参,其根标识符是模块作用域变量,其中:
-
对应的指针参数在被写指针参数集合中,并且
-
该模块作用域变量在被调用函数的读取集合中。
-
-
一个指针类型实参,其根标识符是模块作用域变量,其中:
-
对应的指针参数在被写指针参数集合中,并且
-
该模块作用域变量在被调用函数的写入集合中。
-
-
一个指针类型实参,其根标识符是模块作用域变量,其中:
-
对应的指针参数在读取指针参数集合中,并且
-
该模块作用域变量在被调用函数的写入集合中。
-
var < private> x : i32= 0 ; fn f1 ( p1 : ptr< function, i32> , p2 : ptr< function, i32> ) { * p1 = * p2 ; } fn f2 ( p1 : ptr< function, i32> , p2 : ptr< function, i32> ) { f1 ( p1 , p2 ); } fn f3 () { var a : i32= 0 ; f2 ( & a , & a ); // 无效。不能传递两个拥有相同根标识符的指针参数, // 当其中一个或多个被写(即使在子函数中)。 } fn f4 ( p1 : ptr< function, i32> , p2 : ptr< function, i32> ) -> i32{ return * p1 + * p2 ; } fn f5 () { var a : i32= 0 ; let b = f4 ( & a , & a ); // 有效。f4 的 p1 和 p2 都只读。 } fn f6 ( p : ptr< private, i32> ) { x = * p ; } fn f7 ( p : ptr< private, i32> ) -> i32{ return x + * p ; } fn f8 () { let a = f6 ( & x ); // 无效。x 作为全局变量被写入,又作为参数被读取。 // let b = f7 ( & x ); // 有效。x 仅以参数和变量身份被读取。 }
12. 属性
属性用于修饰对象。 WGSL 提供统一的属性应用语法。 属性被用于各种目的,比如指定与 API 的接口。
通常从语言角度来说,属性在类型和语义检查时可以被忽略。 此外,属性名是上下文相关名称, 某些属性参数也是上下文相关名称。
'@' ident_pattern_token
argument_expression_list ?
| id_attr
除非某属性描述中明确允许,否则一个属性不能在同一对象或类型上指定多次。
12.1. align
'@' 'align' '(' expression ',' ?
')'
| 描述 |
约束结构体成员在内存中的位置。
此属性影响封闭结构体类型的值在内存中的出现方式: 它约束了结构体自身及其成员在内存中的字节地址。
如果
align(n) 应用于类型为 T 的 S 的成员,
且 S 可以作为地址空间 AS 中变量的 存储类型,
其中 AS 不是 uniform,
那么 n 必须满足:
n = k × RequiredAlignOf(T,AS) 其中 k 是某个正整数。 对齐和大小的规则是相互递归的。 但上述约束是定义良好的,因为它依赖于 嵌套类型的所需对齐,并且类型有有限的嵌套深度。 参见§ 14.4 内存布局。 |
| 参数 | 必须是常量表达式,解析为i32或u32类型。 必须为正数。 必须为2的幂。 |
12.2. binding
'@' 'binding' '(' expression ',' ?
')'
| 描述 | 指定绑定组中资源的绑定编号。 见 § 13.3.2 资源接口。 |
| 参数 | 必须是一个const-expression,其解析为 i32 或 u32。 必须为非负。 |
12.3. blend_src
'@' 'blend_src' '(' expression ',' ?
')'
| 描述 |
当启用特性 dual_source_blending
时,指定片段输出的一部分。
见 § 13.3.1.3 输入输出位置。
必须只应用于具有 location 属性的结构类型的成员。 必须只应用于具有数值标量 或数值 向量类型的对象声明。 不得包含在着色器 阶段输入中。 不得包含在着色器阶段输出中, 但片段 着色器阶段除外。 |
| 参数 | 必须是一个const-expression,其解析为值为 0 或
1 的 i32 或 u32。
|
12.4. builtin
'@' 'builtin' '(' builtin_value_name ',' ? ')'
| 描述 | 指定关联对象是一个内建值,由指定的token表示。 见 § 13.3.1.1 内建输入和输出。 |
| 参数 | 必须是内建值 名称 token,用于一个内建值。 |
12.5. const
'@' 'const'
| 描述 |
指定该函数可以用作 const-function。
此属性不得应用于
用户定义函数。
必须只应用于函数声明。 注:此属性用作一种 表记约定,用来描述哪些 内建函数可以在 const-expressions 中使用。 |
| 参数 | 无 |
12.6. diagnostic
'@' 'diagnostic' diagnostic_control
'(' severity_control_name ',' diagnostic_rule_name ',' ? ')'
| 描述 |
指定一个范围诊断过滤器。参见§ 2.3 诊断。
同一个语法结构可以指定多个diagnostic属性, 但它们必须指定不同的触发规则。 |
| 参数 |
第一个参数是severity_control_name。
第二个参数是diagnostic_rule_name token, 指定一个触发规则。 |
12.7. group
'@' 'group' '(' expression ',' ?
')'
| 描述 | 指定资源的绑定组。 见 § 13.3.2 资源接口。 |
| 参数 | 必须是一个const-expression,其解析为 i32 或 u32。 必须为非负。 |
12.8. id
'@' 'id' '(' expression ',' ?
')'
| 描述 |
指定一个数值标识符,作为
可由管线覆盖的常量的替代名称。
必须只应用于标量类型的override-declaration。 |
| 参数 | 必须是一个const-expression,其解析为 i32 或 u32。 必须为非负。 |
12.9. interpolate
'@' 'interpolate' '(' interpolate_type_name ',' ? ')'
| '@' 'interpolate' '(' interpolate_type_name ',' interpolate_sampling_name ',' ? ')'
| 描述 | 指定用户定义 IO 必须如何插值。 该属性只对用户定义的vertex输出 和fragment输入有意义。 见 § 13.3.1.4 插值。 |
| 参数 |
第一个参数必须是一个
插值类型名称 token,用于一种插值类型。
第二个参数如果存在,则必须是 插值采样名称 token,用于 插值采样。 |
12.10. invariant
'@' 'invariant'
| 描述 |
当应用于顶点着色器的position 内建输出值
时,其结果的计算在不同程序和相同入口点的不同调用间保持不变。
即,如果两个入口点的 position 输出数据和控制流一致,则结果值必然相同。
对 position 内建输入值无影响。
注意: 此属性对应 HLSL 的 |
| 参数 | 无 |
12.11. location
'@' 'location' '(' expression ',' ?
')'
| 描述 |
指定入口点的用户定义 IO 的一部分。
见 § 13.3.1.3 输入输出位置。
必须只应用于入口点 函数参数、入口点 返回类型,或结构类型的成员。 必须只应用于具有数值标量 或数值 向量类型的对象声明。 不得包含在compute 着色器 阶段输入中。 |
| 参数 | 必须是一个const-expression,其解析为 i32 或 u32。 必须为非负。 |
12.12. must_use
'@' 'must_use'
| 描述 |
指定对此函数的调用必须用作一个
表达式。
也就是说,对此函数的调用不得构成整个函数调用语句。
注:许多函数返回一个值且
没有副作用。
将此类函数作为函数
调用语句中唯一的内容来调用,通常是一种编程缺陷。
具有这些属性的内建函数会声明为 |
| 参数 | 无 |
12.13. size
'@' 'size' '(' expression ',' ?
')'
| 描述 |
指定为结构成员保留的字节数。
如果 见 § 14.4 内存布局。 |
| 参数 | 必须是一个const-expression,其解析为 i32 或 u32。 必须为正。 |
12.14. workgroup_size
'@' 'workgroup_size' '(' expression ',' ? ')'
| '@' 'workgroup_size' '(' expression ',' expression ',' ?
')'
| '@' 'workgroup_size' '(' expression ',' expression ','
expression ',' ?
')'
| 描述 |
指定计算着色器的工作组网格的
x、y 和 z 维度。
第一个参数指定 x 维度。 第二个参数如果提供,则指定 y 维度,否则假定为 1。 第三个参数如果提供,则指定 z 维度,否则假定为 1。 |
| 参数 |
接受一个、两个或三个参数。
每个参数必须是一个 const-expression 或一个override-expression。 所有参数必须为相同类型,即 i32 或 u32。 如果任何指定的参数是一个 const-expression,且求值为非正 值,则会导致着色器创建错误。 如果任何指定参数求值为非正值,或超过 WebGPU API 指定的上限,或者参数值的乘积超过 WebGPU API 指定的上限 (见 WebGPU § 3.6.2 限制),则会导致管线创建错误。 |
12.15. 着色器阶段属性
以下着色器阶段 属性 将一个函数指定为特定着色器阶段的入口 点。 这些属性必须只应用于函数声明, 并且给定函数上最多只能存在一个。 它们不接受参数。
12.15.1. vertex
'@' 'vertex'
vertex 属性声明该函数为渲染
管线的 vertex 着色器
阶段的入口点。
12.15.2. fragment
'@' 'fragment'
fragment 属性声明该函数为渲染
管线的 fragment 着色器
阶段的入口点。
12.15.3. compute
'@' 'compute'
compute 属性声明该函数为计算管线的 compute 着色器
阶段的入口点。
13. 入口点
入口点是一个用户自定义函数,用于 执行特定着色器阶段的工作。
13.1. 着色器阶段
WebGPU 以 draw 或 dispatch 命令的形式向 GPU 下达工作。 这些命令会在含有一组着色器阶段输入、输出,以及附加的 资源的上下文中执行一个管线。
管线描述了要在 GPU 上执行的工作,是各阶段的序列,其中有些阶段是可编程的。 在 WebGPU 中,管线会在调度 draw 或 dispatch 命令执行前创建。 管线分为两类:GPUComputePipeline 和 GPURenderPipeline。
dispatch 命令使用 GPUComputePipeline 以可控并行度在逻辑点网格上运行 计算着色器阶段, 同时读取并可能写入缓冲区和图像资源。
draw 命令使用 GPURenderPipeline 运行多阶段流程, 其中包含两个可编程阶段以及其他固定功能阶段:
-
顶点着色器阶段:将单个顶点的输入属性映射为该顶点的输出属性。
-
固定功能阶段将顶点映射为图元(如三角形),然后光栅化为片元。
-
片元着色器阶段:处理每个片元,可能产生片元输出。
-
固定功能阶段消费片元输出,可能更新外部状态(如颜色附件、深度和模板缓冲区)。
WebGPU 规范对管线有更详细描述。
WGSL 定义了三种着色器阶段,对应管线的可编程部分:
-
计算
-
顶点
-
片元
每个着色器阶段都有各自的特性和约束,详见其他章节。
13.2. 入口点声明
要创建一个入口点,请声明 一个带有着色器阶段属性的用户定义 函数。
在 WebGPU API 中配置管线时,
入口点的函数名会映射到 WebGPU GPUProgrammableStage
对象的 entryPoint 属性。
入口点的形式 参数表示该阶段的着色器阶段输入。 可以使用结构类型将用户定义输入彼此分组,并可选地与内建 输入分组。 每个参数必须要么是着色器阶段输入, 要么必须 声明为结构类型,并且 每个结构成员都是着色器阶段输入。
如果指定,入口点的返回 类型及其属性表示该阶段的着色器阶段输出。 可以使用结构类型将用户定义输出彼此分组,并可选地与内建 输出分组。 如果存在,返回类型及其属性 必须是 着色器阶段输出, 要么它必须 是结构类型,并且每个结构成员都是着色器阶段输出。
注意: 计算入口点不能有返回类型。
@vertex fn vert_main () -> @builtin ( position) vec4< f32> { return vec4< f32> ( 0.0 , 0.0 , 0.0 , 1.0 ); } @fragment fn frag_main ( @builtin ( position) coord_in : vec4< f32> ) -> @location ( 0 ) vec4< f32> { return vec4< f32> ( coord_in . x , coord_in . y , 0.0 , 1.0 ); } @compute @workgroup_size ( 1 ) fn comp_main () { }
着色器阶段中的函数集合为以下内容的并集:
-
该阶段的入口点函数。
-
在该阶段任意函数体中调用的函数(无论实际是否执行)。
该并集会反复应用直到稳定为止。 它将在有限步内稳定。
13.2.1. 入口点的函数属性
WGSL 定义了以下可用于入口点声明的属性:
@compute @workgroup_size ( 8 , 4 , 1 ) fn sorter () { } @compute @workgroup_size ( 8u ) fn reverser () { } // 使用可管线覆盖的常量。 @id ( 42 ) override block_width = 12u ; @compute @workgroup_size ( block_width ) fn shuffler () { } // 错误:workgroup_size 必须用于 compute shader 上 @compute fn bad_shader () { }
13.3. 着色器接口
着色器接口是着色器用于读写管线外部数据的对象集合,以及用于配置着色器的可管线覆盖常量。 接口包括:
当满足以下情况时,声明 D 会被着色器静态访问:
-
函数声明的所有部分,包括属性、形式 参数、返回类型和函数主体。
-
定义上述内容所需的任何类型,包括跟随类型别名。
-
作为帮助定义类型的一个特定情况, 当 workgroup 地址空间中的变量本身被静态访问时, 在作为该变量的数组类型的元素计数的override-expression中使用的任何 override-declaration。
-
用于支持求值上述任何 override-expressions 的任何 override declarations。
-
上述任何内容上的任何属性。
现在,我们可以精确定义着色器的接口由以下内容组成:
-
入口点的返回值。 这表示着色器阶段输出。
-
由着色器静态 访问的uniform buffer、 storage buffer、纹理 资源和采样器资源变量。
13.3.1. 阶段间输入与输出接口
着色器阶段输入是由管线上传递到本阶段的数据项。 每个数据项要么为内建输入值,要么为用户自定义输入。
着色器阶段输出是本阶段要传递到管线下游的数据项。 每个数据项要么为内建输出值,要么为用户自定义输出。
IO 属性用于将对象标记为着色器阶段输入或着色器阶段输出, 或进一步描述输入/输出的属性。 IO 属性包括:
13.3.1.1. 内建输入与输出
内建输入 值提供对系统生成的控制信息的访问。 入口点不得具有两个带有相同内建 值名称的内建输入。
阶段 S 中名称为 X、类型为 TX 的内建输入, 通过 着色器阶段 S 的入口点的 形式参数访问, 方式有以下两种之一:
-
该参数具有
builtin(X)属性,且类型为 TX。 -
该参数具有结构类型,其中一个结构成员具有
builtin(X)属性,且类型为 TX。
反过来,当入口点的参数或参数成员具有 builtin 属性时, 对应的 builtin 必须是该入口点着色器阶段的输入。
内建输出 值由着色器用于向管线中的后续处理步骤传递 控制信息。 入口点不得具有两个带有相同内建 值名称的内建输出。
阶段 S 中名称为 Y、类型为 TY 的内建输出, 通过 着色器阶段 S 的入口点的 返回值设置, 方式有以下两种之一:
反过来,当入口点的返回类型或返回类型成员具有 builtin 属性时, 对应的 builtin 必须是该入口点着色器阶段的输出。
注:position 内建值既是 vertex 着色器的输出,也是 fragment 着色器的输入。
内建输入值和内建输出值合称为内建值。
下表总结了可用的内建值。 每一项都是用于内建值的内建值名称token。 每一项都在后续小节中详细描述。
| 名称 | 阶段 | 方向 | 类型 | 扩展 |
|---|---|---|---|---|
| vertex_index | vertex | 输入 | u32 | |
| instance_index | vertex | 输入 | u32 | |
| clip_distances | vertex | 输出 | array<f32, N>(N ≤ 8)
| clip_distances |
| position | vertex | 输出 | vec4<f32> | |
| fragment | 输入 | vec4<f32> | ||
| front_facing | fragment | 输入 | bool | |
| frag_depth | fragment | 输出 | f32 | |
| primitive_index | fragment | 输入 | u32 | primitive_index |
| sample_index | fragment | 输入 | u32 | |
| sample_mask | fragment | 输入 | u32 | |
| fragment | 输出 | u32 | ||
| local_invocation_id | compute | 输入 | vec3<u32> | |
| local_invocation_index | compute | 输入 | u32 | |
| global_invocation_id | compute | 输入 | vec3<u32> | |
| global_invocation_index | compute | 输入 | u32 | linear_indexing |
| workgroup_id | compute | 输入 | vec3<u32> | |
| workgroup_index | compute | 输入 | u32 | linear_indexing |
| num_workgroups | compute | 输入 | vec3<u32> | |
| subgroup_invocation_id | compute | 输入 | u32 | subgroups |
| fragment | ||||
| subgroup_size | compute | 输入 | u32 | subgroups |
| fragment | ||||
| subgroup_id | compute | 输入 | u32 | subgroups 和 subgroup_id |
| num_subgroups | compute | 输入 | u32 | subgroups 和 subgroup_id |
struct VertexOutput { @builtin ( position) my_pos : vec4< f32> , @builtin ( clip_distances ) my_clip_distances : array< f32, 8 > , } @vertex fn vs_main ( @builtin ( vertex_index) my_index : u32, @builtin ( instance_index) my_inst_index : u32, ) -> VertexOutput {} struct FragmentOutput { @builtin ( frag_depth) depth : f32, @builtin ( sample_mask) mask_out : u32} @fragment fn fs_main ( @builtin ( front_facing) is_front : bool, @builtin ( position) coord : vec4< f32> , @builtin ( sample_index) my_sample_index : u32, @builtin ( sample_mask) mask_in : u32, ) -> FragmentOutput {} @compute @workgroup_size ( 64 ) fn cs_main ( @builtin ( local_invocation_id) local_id : vec3< u32> , @builtin ( local_invocation_index) local_index : u32, @builtin ( global_invocation_id) global_id : vec3< u32> , ) {}
13.3.1.1.1. clip_distances
| 名称 | clip_distances |
| 阶段 | vertex |
| 类型 | array<f32, N> |
| 方向 | 输出 |
| 描述 |
数组中的每个值表示到用户自定义裁剪平面的距离。clip distance 为 0 表示顶点在平面上,正值表示顶点在裁剪半空间内部,负值表示顶点在裁剪半空间外部。clip_distances 的数组大小必须 ≤ 8。
参见 WebGPU § 23.2.4
原始裁剪。
|
13.3.1.1.2. frag_depth
| 名称 | frag_depth |
| 阶段 | fragment |
| 类型 | f32 |
| 方向 | 输出 |
| 描述 | 片元在视口深度范围内的更新后的深度值。 |
13.3.1.1.3. front_facing
| 名称 | front_facing |
| 阶段 | fragment |
| 类型 | bool |
| 方向 | 输入 |
| 描述 | 当前片元位于正面图元上时为 true,否则为 false。 |
13.3.1.1.4. global_invocation_id
| 名称 | global_invocation_id |
| 阶段 | compute |
| 类型 | vec3<u32> |
| 方向 | 输入 |
| 描述 | 当前调用的全局调用ID,即其在计算着色器网格中的位置。global_invocation_id 的值等于 workgroup_id * workgroup_size + local_invocation_id。 |
13.3.1.1.5. global_invocation_index
| 名称 | global_invocation_index |
| 阶段 | compute |
| 类型 | u32; |
| 方向 | 输入 |
| 描述 |
当前调用的全局调用索引,即其在
计算着色器网格中的线性位置。global_invocation_index
的值等于:
global_invocation_id.x +
注:如果已分派的
工作组数量会导致该值超过 u32
类型的范围,则该分派将失败:
|
13.3.1.1.6. instance_index
| 名称 | instance_index |
| 阶段 | vertex |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前顶点在本次 API 级 draw 命令中的实例索引。
第一个实例的索引等于 draw 的 |
13.3.1.1.7. local_invocation_id
| 名称 | local_invocation_id |
| 阶段 | compute |
| 类型 | vec3<u32> |
| 方向 | 输入 |
| 描述 | 当前调用的局部调用ID,即其在工作组网格中的位置。 |
13.3.1.1.8. local_invocation_index
| 名称 | local_invocation_index |
| 阶段 | compute |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 | 当前调用的局部调用索引,即其在工作组网格中的线性化序号。 |
13.3.1.1.9. num_workgroups
| 名称 | num_workgroups |
| 阶段 | compute |
| 类型 | vec3<u32> |
| 方向 | 输入 |
| 描述 |
由 API 分派大小,
vec3<u32>(group_count_x, group_count_y, group_count_z),分派到计算着色器上。
参见 WebGPU §3.6.3。
|
13.3.1.1.10.
position
| 名称 | position |
| 阶段 | vertex |
| 类型 | vec4<f32> |
| 方向 | 输出 |
| 描述 |
当前顶点的裁剪坐标,
以裁剪空间坐标给出。
输出值 (x,y,z,w) 将映射为 WebGPU 归一化设备坐标中的 (x/w, y/w, z/w)。 参见 WebGPU § 3.3 坐标系统 和 WebGPU § 23.2.4 原始裁剪。 如果 w 分量为零,会发生动态错误。 |
| 名称 | position |
| 阶段 | fragment |
| 类型 | vec4<f32> |
| 方向 | 输入 |
| 描述 |
当前片元的输入 position。
令 fp 为片元的输入 position。 则形式化: fp.xy = rp.destination.position 更详细地:
|
13.3.1.1.11. primitive_index
| 名称 | primitive_index |
| 阶段 | fragment |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 | 基于当前实例处理的图元数量的每图元索引,从当前绘制操作开始计数。初始为0,每处理一个点、线或三角形图元后递增1。每个实例绘制之间会重置为0。 使用 primitive restart value 重新开始条带图元时不会影响该索引。 该索引在同一个图元的所有片段间保持一致。 |
13.3.1.1.12. sample_index
| 名称 | sample_index |
| 阶段 | fragment |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前片元的采样索引。其值至少为 0,至多为 sampleCount - 1,其中 sampleCount 是为 GPU 渲染管线指定的
MSAA 采样
count。
当应用该属性时,如果片元着色器的效果会根据 sample_index 的值变化,则片元着色器会对每个采样点各调用一次。
|
13.3.1.1.13. sample_mask
| 名称 | sample_mask |
| 阶段 | fragment |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 | 当前片元的采样覆盖掩码。它包含一个位掩码,指示该片元中哪些采样点被渲染图元覆盖。 |
| 名称 | sample_mask |
| 阶段 | fragment |
| 类型 | u32 |
| 方向 | 输出 |
| 描述 | 当前片元的采样覆盖掩码控制。最后写入该变量的值将成为着色器输出掩码。写入值中的为 0 的位会导致颜色附件中对应采样点被丢弃。 |
13.3.1.1.14. vertex_index
| 名称 | vertex_index |
| 阶段 | vertex |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前顶点在本次 API 级 draw 命令中的索引,与实例化无关。
非索引绘制时,第一个顶点的索引等于 draw 的 索引绘制时,索引等于索引缓冲区当前顶点的条目加上 draw 的 |
13.3.1.1.15. workgroup_id
| 名称 | workgroup_id |
| 阶段 | compute |
| 类型 | vec3<u32> |
| 方向 | 输入 |
| 描述 |
当前调用的工作组ID,即该工作组在整个计算着色器网格中的位置。
同一工作组中的所有调用具有相同的工作组ID。 工作组ID 范围为 (0,0,0) 到 (group_count_x - 1, group_count_y - 1, group_count_z - 1)。 |
13.3.1.1.16. workgroup_index
| 名称 | workgroup_index |
| 阶段 | compute |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前调用的工作组索引,即
workgroup 在整个计算着色器网格中的线性位置。
同一 workgroup 中的所有调用都具有相同的 workgroup index。
注:如果已分派的
workgroup 数量会导致该值超过 u32
类型的范围,则该分派将失败:
|
13.3.1.1.17. subgroup_invocation_id
| 名称 | subgroup_invocation_id |
| 阶段 | compute 或 fragment |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前调用的子组调用 ID。
该 ID 位于范围 [0, subgroup_size - 1] 内。 在compute 着色器中,ID 从零开始且是稠密的。 也就是说,当 compute 着色器开始执行时,在每个 subgroup 内:
注:fragment 着色器中的 subgroup 调用索引 可以不是稠密的。实现 可以将一些较小编号的 ID 分配给辅助调用。 |
13.3.1.1.18. subgroup_size
| 名称 | subgroup_size |
| 阶段 | compute 或 fragment |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 | 当前调用所在 subgroup 的subgroup 大小。 |
13.3.1.1.19. subgroup_id
| 名称 | subgroup_id |
| 阶段 | compute |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前调用的subgroup在
workgroup内的subgroup ID。
该 ID 位于范围 [0, num_subgroups - 1] 内。 |
13.3.1.1.20. num_subgroups
| 名称 | num_subgroups |
| 阶段 | compute |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 | 当前调用的workgroup中的 subgroups数量。 |
13.3.1.2. 用户自定义输入与输出
用户定义数据可以作为输入传递到管线的开端,在管线的各阶段之间传递, 或从管线末端输出。
每个用户定义 输入数据项和 用户定义 输出数据项必须:
-
分配有 IO 位置。见 § 13.3.1.3 输入输出 位置。
13.3.1.3. 输入输出位置
每个输入输出位置可以存储大小最多为 16 字节的值。 类型的字节大小使用 § 14.4.1 对齐和大小中的 SizeOf 列定义。 例如,一个由浮点值组成的四分量向量占用一个位置。
IO 位置通过 location 属性指定。
每个用户定义输入和输出必须具有 显式指定的 IO 位置。 入口点 IO 中的每个结构成员必须是内建值 (见 § 13.3.1.1 内建输入和输出)之一,或分配有位置。
注意: 输入和输出的位置编号互不影响:入口点着色器阶段输入的位置编号不会与输出的位置编号冲突。
注意: 不需要其他规则防止入口点输出内的位置重叠。如果输出是结构体,前述规则已防止重叠。否则输出为标量或向量,只能分配单个位置。
注意: 入口点可用的位置数量由 WebGPU API 定义。
用户自定义 IO 可以与内建值在同一结构体中混用。例如:
// 混合内建值与用户自定义输入。 struct MyInputs { @location ( 0 ) x : vec4< f32> , @builtin ( front_facing) y : bool, @location ( 1 ) @interpolate ( flat) z : u32} struct MyOutputs { @builtin ( frag_depth) x : f32, @location ( 0 ) y : vec4< f32> } @fragment fn fragShader ( in1 : MyInputs ) -> MyOutputs { // ... }
struct A { @location ( 0 ) x : f32, // 无效,x 和 y 不能共享同一位置。 @location ( 0 ) y : f32} struct B { @location ( 0 ) x : f32} struct C { // 无效,含用户自定义 IO 的结构体不可嵌套。 b : B } struct D { x : vec4< f32> } @fragment // 无效,不可将 location 应用于结构体类型。 fn fragShader1 ( @location ( 0 ) in1 : D ) { // ... } @fragment // 无效,in1 和 in2 不能共享同一位置。 fn fragShader2 ( @location ( 0 ) in1 : f32, @location ( 0 ) in2 : f32) { // ... } @fragment // 无效,location 不能应用于结构体。 fn fragShader3 ( @location ( 0 ) in1 : vec4< f32> ) -> @location ( 0 ) D { // ... }
13.3.1.4. 插值
作者可以通过使用 interpolate 属性来控制用户定义 IO 数据如何插值。 WGSL 提供了插值的两个方面供控制:插值类型 和插值采样。
- perspective
-
值以透视校正方式插值。
- linear
-
值以线性、非透视校正方式插值。
- flat
-
值不进行插值。
- center
-
在像素中心执行插值。
- centroid
-
在位于当前图元内该片段所覆盖的所有 样本之内的点处执行插值。 对于该图元中的所有样本,此值相同。
- sample
-
按样本执行插值。 当应用此属性时,fragment 着色器 会对每个样本调用一次。
- first
-
该值由图元的第一个顶点提供。
- either
-
该值由图元的第一个顶点或最后一个顶点提供。 该值来自第一个顶点还是最后一个顶点取决于实现。
对于标量或向量浮点类型的用户定义 IO:
-
如果未指定插值属性,则假定为
@interpolate(perspective, center)。 -
如果插值属性指定了插值类型:
标量或向量整数类型的用户定义 vertex 输出和fragment 输入
必须始终指定插值类型
flat。
阶段间接口验证检查 在渲染 管线内, 每个用户定义 fragment 输入的插值属性是否与具有相同 location 分配的 vertex 输出的插值属性匹配。 如果不匹配,则管线创建错误将产生。
13.3.2. 资源接口
资源是一个对象, 它提供对着色器阶段外部数据的访问, 并且不是override-declaration,不是立即数据变量,也不是着色器阶段输入或输出。 资源由着色器的所有调用共享。
资源有四种:
着色器的资源 接口是着色器阶段中的函数 静态访问的模块作用域 资源变量集合。
每个资源变量必须同时使用 group 和 binding 属性声明。 它们与着色器的阶段一起,标识该资源在着色器管线上的绑定地址。 见 WebGPU § 8.3 GPUPipelineLayout。
着色器中的两个不同资源变量, 在作为一对考虑时,不得 具有相同的 group 和 binding 值。
13.3.3. 资源布局兼容性
WebGPU 要求着色器的资源接口与使用该着色器的管线布局匹配。
如果 WGSL 资源接口中的变量绑定到不兼容的 WebGPU binding member 或 binding type,则会发生管线创建错误。兼容性定义见下表。
接口验证要求见WebGPU API规范。
13.3.4. 缓冲区绑定决定运行时数组元素数量
当storage buffer变量包含运行时大小的数组时,该数组元素数量由对应resource的大小决定:
令 T 为存储类型,用于storage buffer变量, 其中 T 是运行时大小的数组类型或包含运行时数组类型。
令 bufferBinding 为 get as buffer binding(
resource)。令 EBS 为绑定到管线绑定地址的 bufferBinding 的 有效缓冲区绑定大小。
则NRuntime, 即运行时数组的元素数量,是满足SizeOf(T) ≤ EBS的最大整数。
更详细地,类型为 RAT 的运行时数组的 NRuntime 为:
truncate((EBBS − array_offset) ÷ array_stride),其中:
着色器可以通过NRuntime和arrayLength 内建函数计算运行时数组长度。
NRuntime 由对应缓冲区绑定的大小决定,每次draw 或dispatch命令都可能不同。
WebGPU 验证规则保证 1 ≤ NRuntime。
-
weights变量为storage buffer。 -
其存储类型为运行时数组类型
array<f32>。 -
数组偏移为 0。
-
数组步长为 StrideOf(array<f32>),即 4。
下表展示了 weights 变量在不同有效缓冲区绑定大小下的 NRuntime 示例:
| 有效缓冲区绑定大小 | weights 变量的 NRuntime
| 计算 |
|---|---|---|
| 1024 | 256 | truncate( 1024 ÷ 4 ) |
| 1025 | 256 | truncate( 1025 ÷ 4 ) |
| 1026 | 256 | truncate( 1026 ÷ 4 ) |
| 1027 | 256 | truncate( 1027 ÷ 4 ) |
| 1028 | 257 | truncate( 1028 ÷ 4 ) |
-
lights变量为storage buffer。 -
其存储类型为
LightStorage。 -
LightStorage的point成员为运行时数组类型array<PointLight>。
struct PointLight { // align(16) size(32) position: vec3f, // offset(0) align(16) size(12) // -- implicit member alignment padding -- // offset(12) size(4) color : vec3f, // offset(16) align(16) size(12) // -- implicit struct size padding -- // offset(28) size(4) } struct LightStorage { // align(16) pointCount : u32, // offset(0) align(4) size(4) // -- implicit member alignment padding -- // offset(4) size(12) point : array< PointLight > , // offset(16) align(16) elementsize(32) } @group ( 0 ) @binding ( 1 ) var < storage> lights : LightStorage ;
下表展示了 lights 变量的 point 成员在不同有效缓冲区绑定大小下的 NRuntime 示例:
| 有效缓冲区绑定大小 | lights 变量 point 成员的 NRuntime
| 计算 |
|---|---|---|
| 1024 | 31 | truncate( ( 1024 - 16 ) ÷ 32) ) |
| 1025 | 31 | truncate( ( 1025 - 16 ) ÷ 32) ) |
| 1039 | 31 | truncate( ( 1039 - 16 ) ÷ 32) ) |
| 1040 | 32 | truncate( ( 1040 - 16 ) ÷ 32) ) |
14. 内存
在 WGSL 中,可存储类型的值可被存入内存,供后续取用。 本节描述内存的结构,以及访问内存操作的语义。 可存入内存的值类型和用于内存访问的类型,见§ 6.4 内存视图。
14.1. 内存位置
内存由一组不同的内存位置组成。 每个内存位置大小为 8 位。 影响内存的操作会与一组一个或多个内存位置交互。 对复合类型的内存操作不会访问填充内存位置。 因此,操作所访问的内存位置集合可能不是连续的。
如果两组内存位置的交集非空,则称这两组内存位置重叠。
14.2. 内存访问模式
内存访问是对内存位置的操作。
-
读访问读取内存位置的内容。
-
写访问设置内存位置的内容。
一次操作可以进行读、写,或同时进行读写。
特定的内存位置可能只支持某些类型的访问,这通过内存的访问模式来表达。
| 访问模式 | 支持的访问类型 |
|---|---|
| read | 支持读访问,不支持写访问。 |
| write | 支持写访问,不支持读访问。 |
| read_write | 支持读和写访问。 |
WGSL 预声明了枚举值enumerant
read、write 和 read_write。
14.3. 地址空间
内存位置被划分为地址空间。 每个地址空间具有决定其可变性、可见性、可包含值类型及变量用法的独特属性。 更多细节见§ 7 变量与值声明。
给定内存视图的访问模式通常由上下文决定:
storage(存储)地址空间支持读 和读写访问模式。 其他地址空间仅支持一种访问模式。 每个地址空间的默认访问模式如下表所示。
| 地址空间 | 调用间共享 | 默认访问模式 | 注释 |
|---|---|---|---|
| function | 仅同一调用 | read_write | |
| private | 仅同一调用 | read_write | |
| workgroup | 同一compute 着色器workgroup中的调用 | read_write | 最外层数组的元素计数 可以是可由管线覆盖的常量。 |
| uniform | 同一着色器阶段中的调用 | read | 用于 uniform buffer 变量 |
| storage | 同一着色器阶段中的调用 | read | 用于 storage buffer 变量 |
| immediate | 同一着色器阶段中的调用 | read | 用于立即
数据变量。 每个入口点至多可以 静态访问一个 immediate 变量。 |
| handle | 同一着色器阶段中的调用 | read | 用于采样器
和纹理
变量。 |
WGSL 为除 handle 地址空间之外的每个地址空间预声明一个枚举项。
变量在 workgroup 地址空间中 必须只在 compute 着色器阶段中被静态 访问。
变量在 storage 地址空间中(storage buffers)只有在访问模式为
read 时,才能由vertex 着色器阶段静态
访问。
其存储类型为存储纹理,
且具有
write 或 read_write 访问模式的变量不能由vertex 着色器阶段静态
访问。
见 WebGPU createBindGroupLayout()。
注:每个地址空间可能具有不同的性能 特征。
14.4. 内存布局
WGSL 中类型的布局独立于地址空间。 然而,严格来说,该布局只能由宿主可共享 缓冲区观察到。 Uniform buffer 和 storage buffer 变量 用于共享 在内存中组织为字节序列的大块数据。 缓冲区在 CPU 和 GPU 之间共享,或在管线中的不同着色器阶段之间共享, 或在不同管线之间共享。
由于缓冲区数据在不重新格式化或转换的情况下共享,因此如果缓冲区 生产者和消费者在内存布局上不一致,则是 动态错误;内存布局描述了 缓冲区中的字节如何组织为带类型的 WGSL 值。 这些字节是一个值相对于公共基准 位置的内存 位置。
缓冲区变量的存储类型 必须是 宿主可共享的,并具有如下所述的完全 详述的内存布局。
每个缓冲区变量必须在 uniform 或 storage 地址空间中声明。
类型的内存布局仅在对具有以下内容的表达式求值时才有意义:
8 位字节是宿主可共享内存的最基本单位。 本节定义的术语表示 8 位字节的计数。
我们将使用以下记法,其中 T 是宿主可共享或固定占用空间类型, S 是宿主可共享或固定占用空间结构类型, 而 A 是宿主可共享或固定占用空间数组或运行时大小数组:
-
AlignOf(T) 是 T 的对齐。
-
AlignOfMember(S, i) 是 S 的第 i 个成员的对齐。
-
SizeOf(T) 是 T 的字节大小。
-
SizeOfMember(S, i) 是 S 的第 i 个成员的大小。
-
OffsetOfMember(S, i) 是 S 的第 i 个成员相对于 S 起始处的偏移。
-
StrideOf(A) 是 A 的元素 步幅,定义为 从一个数组元素的起始处到下一个元素的起始处的字节数。 它等于数组元素类型的大小,向上舍入到该元素类型的对齐:
StrideOf(array<E, N>) = roundUp(AlignOf(E), SizeOf(E))
StrideOf(array<E>) = roundUp(AlignOf(E), SizeOf(E)) -
AccessibleBytes(T) 是类型 T 实例中包含数据的 字节偏移集合。
-
如果 T 是标量或向量,则 AccessibleBytes(T) 是满足
0 <= k <SizeOf(T) 的整数k的集合。 -
如果 T 是具有 C 列和 R 行的矩阵,则 AccessibleBytes(T) 是按如下方式计算的集合:
-
对于
0..C-1中的每个i:-
对于 AccessibleBytes(vecR) 中的每个
k:-
该集合包含
k + i * Stride。
-
-
-
如果 T 是结构 S,则 AccessibleBytes(T) 是按如下方式计算的集合:
-
对于字节偏移
Offset= OffsetOfMember(S,i) 处的每个成员M_i:-
对于 AccessibleBytes(
M_i) 中的每个k:-
该集合包含
k + Offset。
-
-
-
-
-
AccessibleSlots(T) 是满足区间
[4*i, 4*i+4)与 AccessibleBytes(T) 的交集非空的整数i的集合。
14.4.1. 对齐与大小
各ホスト共有可能または固定フットプリントデータ型 T は、アライメントとサイズを持つ。
型のアライメントは、 その型の値をメモリ内のどこに配置できるかに関する制約であり、整数として表される: 型のアライメントは、その型の値の開始メモリ位置のバイトアドレスを 均等に割り切らなければならない。 アライメントにより、値へのアクセスにより効率的なハードウェア命令を使用できるようになり、 または特定のアドレス空間におけるより制限的なハードウェア要件を満たす。 (アドレス空間レイアウト制約を参照)。
注: 各アライメント値は、構成上、常に 2 の累乗である。
型または構造体メンバーのバイトサイズは、 その型または構造体メンバーの値を格納する目的で ホスト共有可能メモリ内に予約される連続したバイト数である。 サイズには、型の末尾にあるアドレス指定できないパディングが含まれる場合がある。 したがって、値のロードおよびストアは、値のサイズより少ないメモリ位置に アクセスする場合がある。
ホスト共有可能型および固定フットプリント型のアライメントとサイズは、次の表で 再帰的に定義される:
| ホスト共有可能型または固定フットプリント型 T | AlignOf(T) | SizeOf(T) |
|---|---|---|
| bool
注を参照。 | 4 | 4 |
| i32、u32、または f32 | 4 | 4 |
| f16 | 2 | 2 |
| atomic<T> | 4 | 4 |
| vec2<T>、 T は bool、i32、u32、または f32 | 8 | 8 |
| vec2<f16> | 4 | 4 |
| vec3<T>、T は bool、i32、u32、 または f32 | 16 | 12 |
| vec3<f16> | 8 | 6 |
| vec4<T>、T は bool、i32、u32、 または f32 | 16 | 16 |
| vec4<f16> | 8 | 8 |
|
matCxR
(列優先) (一般形) | AlignOf(vecR) | SizeOf(array<vecR, C>) |
| mat2x2<f32> | 8 | 16 |
| mat2x2<f16> | 4 | 8 |
| mat3x2<f32> | 8 | 24 |
| mat3x2<f16> | 4 | 12 |
| mat4x2<f32> | 8 | 32 |
| mat4x2<f16> | 4 | 16 |
| mat2x3<f32> | 16 | 32 |
| mat2x3<f16> | 8 | 16 |
| mat3x3<f32> | 16 | 48 |
| mat3x3<f16> | 8 | 24 |
| mat4x3<f32> | 16 | 64 |
| mat4x3<f16> | 8 | 32 |
| mat2x4<f32> | 16 | 32 |
| mat2x4<f16> | 8 | 16 |
| mat3x4<f32> | 16 | 48 |
| mat3x4<f16> | 8 | 24 |
| mat4x4<f32> | 16 | 64 |
| mat4x4<f16> | 8 | 32 |
| struct S、 メンバー M1...MN を持つ | max(AlignOfMember(S,1), ... , AlignOfMember(S,N)) | roundUp(AlignOf(S),
justPastLastMember) ここで justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) |
| array<E,
N> | AlignOf(E) | N × roundUp(AlignOf(E), SizeOf(E)) |
| array<E> | AlignOf(E) | NRuntime × roundUp(AlignOf(E),SizeOf(E)) ここで NRuntime は、T の実行時に決定される要素数である |
bool 値が 4 バイトアライメントで 4 バイトを占有すると指定することで、
実装は、データ競合を生じさせずにメモリ内の隣接するブール値をサポートできる。
14.4.2. 结构体成员布局
结构的内部布局是 根据其成员的大小和对齐来计算的。 默认情况下,成员会按顺序紧密排列,互不重叠,同时满足成员对齐 要求。
这种默认的内部布局可以通过使用布局属性来覆盖,它们是:
结构类型 S 的第 i 个成员具有大小和对齐,分别表示为 SizeOfMember(S, i) 和 AlignOfMember(S, i)。 成员的大小和对齐用于计算每个成员相对于 结构起始位置的字节偏移量, 如 § 14.4.4 值的内部布局中所述。
SizeOfMember(S, i) 为 k,如果 S 的第 i 个成员具有属性 size(k)。 否则,它为 SizeOf(T),其中 T 是该成员的类型。
AlignOfMember(S, i) 为 k,如果 S 的第 i 个成员具有属性 align(k)。 否则,它为 AlignOf(T), 其中 T 是该成员的类型。
如果结构成员应用了 size 属性,则该 值必须 至少与该成员类型的大小一样大:
SizeOfMember(S, i) ≥ SizeOf(T)
其中 T 是 S 的第 i 个成员的类型。
第一个结构成员相对于结构 起始位置的字节偏移量始终为零:
OffsetOfMember(S, 1) = 0
每个后续成员会放置在满足成员类型对齐、 且避免与前一个成员重叠的最低偏移量处。 对于每个成员索引 i > 1:
OffsetOfMember(S, i) = roundUp(AlignOfMember(S, i ), OffsetOfMember(S, i-1) + SizeOfMember(S, i-1))
struct A { // align(8) size(24) u : f32, // offset(0) align(4) size(4) v : f32, // offset(4) align(4) size(4) w : vec2< f32> , // offset(8) align(8) size(8) x : f32// offset(16) align(4) size(4) // -- implicit struct size padding -- // offset(20) size(4) } struct B { // align(16) size(160) a : vec2< f32> , // offset(0) align(8) size(8) // -- implicit member alignment padding -- // offset(8) size(8) b : vec3< f32> , // offset(16) align(16) size(12) c : f32, // offset(28) align(4) size(4) d : f32, // offset(32) align(4) size(4) // -- implicit member alignment padding -- // offset(36) size(4) e : A , // offset(40) align(8) size(24) f : vec3< f32> , // offset(64) align(16) size(12) // -- implicit member alignment padding -- // offset(76) size(4) g : array< A , 3 > , // element stride 24 offset(80) align(8) size(72) h : i32// offset(152) align(4) size(4) // -- implicit struct size padding -- // offset(156) size(4) } @group ( 0 ) @binding ( 0 ) var < storage, read_write> storage_buffer : B ;
struct A { // align(8) size(32) u : f32, // offset(0) align(4) size(4) v : f32, // offset(4) align(4) size(4) w : vec2< f32> , // offset(8) align(8) size(8) @size ( 16 ) x : f32// offset(16) align(4) size(16) } struct B { // align(16) size(208) a : vec2< f32> , // offset(0) align(8) size(8) // -- implicit member alignment padding -- // offset(8) size(8) b : vec3< f32> , // offset(16) align(16) size(12) c : f32, // offset(28) align(4) size(4) d : f32, // offset(32) align(4) size(4) // -- implicit member alignment padding -- // offset(36) size(12) @align ( 16 ) e : A , // offset(48) align(16) size(32) f : vec3< f32> , // offset(80) align(16) size(12) // -- implicit member alignment padding -- // offset(92) size(4) g : array< A , 3 > , // element stride 32 offset(96) align(8) size(96) h : i32// offset(192) align(4) size(4) // -- implicit struct size padding -- // offset(196) size(12) } @group ( 0 ) @binding ( 0 ) var < uniform> uniform_buffer : B ;
14.4.3. 数组布局示例
// 数组: // - 对齐为 4 = AlignOf(f32) // - 元素步长为 4 = roundUp(AlignOf(f32),SizeOf(f32)) = roundUp(4,4) // - 大小为 32 = stride * number_of_elements = 4 * 8 var small_stride : array< f32, 8 > ; // 数组: // - 对齐为 16 = AlignOf(vec3<f32>) = 16 // - 元素步长为 16 = roundUp(AlignOf(vec3<f32>), SizeOf(vec3<f32>)) // = roundUp(16,12) // - 大小为 128 = stride * number_of_elements = 16 * 8 var bigger_stride : array< vec3< f32> , 8 > ;
// 数组: // - 对齐为 4 = AlignOf(f32) // - 元素步长为 4 = roundUp(AlignOf(f32),SizeOf(f32)) = 4 // 若 B 为 draw/dispatch 命令对应绑定的有效缓冲区大小,则元素个数为: // N_runtime = floor(B / element stride) = floor(B / 4) @group ( 0 ) @binding ( 0 ) var < storage> weights : array< f32> ; // 数组: // - 对齐为 16 = AlignOf(vec3<f32>) = 16 // - 元素步长为 16 = roundUp(AlignOf(vec3<f32>), SizeOf(vec3<f32>)) // = roundUp(16,12) // 若 B 为 draw/dispatch 命令对应绑定的有效缓冲区大小,则元素个数为: // N_runtime = floor(B / element stride) = floor(B / 16) var < storage> directions : array< vec3< f32>> ;
14.4.4. 值的内部布局
本节描述了给定整个值的假定放置位置时,主机可共享值的内部如何放置到缓冲区的字节位置中。 这些布局取决于值的类型, 以及结构成员上的 align 和 size 属性。
放置值的缓冲区字节偏移量必须满足类型对齐要求:如果类型 T 的值被放置在缓冲区偏移量 k 处,则 k = c × AlignOf(T),其中 c 是某个非负整数。
无论地址空间如何,数据都会以相同方式出现。
注:bool 类型不是主机可共享的。 WGSL 规定 bool 值的大小和 对齐为 4 字节, 但没有规定 bool 值的内部布局。
当类型为 u32 或 i32 的值 V 被放置在 主机共享缓冲区的字节偏移量 k 处时,则:
-
字节 k 包含 V 的位 0 到 7
-
字节 k+1 包含 V 的位 8 到 15
-
字节 k+2 包含 V 的位 16 到 23
-
字节 k+3 包含 V 的位 24 到 31
注:回想一下,i32 使用二进制补码表示,因此符号位 位于第 31 位。
64 位整数布局: WebGPU API 的某些特性会将 64 位 无符号整数值写入缓冲区。当这样的值 V 出现在主机共享缓冲区的字节 偏移量 k 处时,则:
-
字节 k 包含 V 的位 0 到 7
-
字节 k+1 包含 V 的位 8 到 15
-
字节 k+2 包含 V 的位 16 到 23
-
字节 k+3 包含 V 的位 24 到 31
-
字节 k+4 包含 V 的位 32 到 39
-
字节 k+5 包含 V 的位 40 到 47
-
字节 k+6 包含 V 的位 48 到 55
-
字节 k+7 包含 V 的位 56 到 63
类型为 f32 的值 V 以 IEEE-754 binary32 格式表示。 它有一个符号位、8 个指数位和 23 个小数位。 当 V 被放置在主机共享缓冲区的字节偏移量 k 处时,则:
-
字节 k 包含小数的位 0 到 7。
-
字节 k+1 包含小数的位 8 到 15。
-
字节 k+2 的位 0 到 6 包含小数的位 16 到 22。
-
字节 k+2 的位 7 包含指数的位 0。
-
字节 k+3 的位 0 到 6 包含指数的位 1 到 7。
-
字节 k+3 的位 7 包含符号位。
类型为 f16 的值 V 以 IEEE-754 binary16 格式表示。 它有一个符号位、5 个指数位和 10 个小数位。 当 V 被放置在主机共享缓冲区的字节偏移量 k 处时,则:
-
字节 k 包含小数的位 0 到 7。
-
字节 k+1 的位 0 到 1 包含小数的位 8 到 9。
-
字节 k+1 的位 2 到 6 包含指数的位 0 到 4。
-
字节 k+1 的位 7 包含符号位。
注:上述规则意味着 主机共享缓冲区中的数值 以小端格式存储。
当原子
类型 atomic<T> 的值 V 被放置在主机共享缓冲区中时,
它具有与底层类型 T 的值相同的内部布局。
当向量类型 vecN<T> 的值 V 被放置在 主机共享缓冲区的字节偏移量 k 处时,则:
-
V.x 被放置在字节偏移量 k 处
-
V.y 被放置在字节偏移量 k + SizeOf(T) 处
-
如果 N ≥ 3,则 V.z 被放置在字节偏移量 k + 2 × SizeOf(T) 处
-
如果 N ≥ 4,则 V.w 被放置在字节偏移量 k + 3 × SizeOf(T) 处
当矩阵类型 matCxR<T> 的值 V 被放置在 主机共享缓冲区的字节偏移量 k 处时,则:
-
V 的列向量 i 被放置在字节偏移量 k + i × AlignOf(vecR<T>) 处
当数组类型 A 的值被 放置在主机共享内存缓冲区的字节偏移量 k 处时, 则:
-
数组的元素 i 被放置在字节偏移量 k + i × StrideOf(A) 处
当结构类型 S 的值被放置在主机共享内存缓冲区的字节偏移量 k 处时, 则:
-
结构值的第 i个成员被放置在字节偏移量 k + OffsetOfMember(S,i) 处。 参见 § 14.4.2 结构成员布局。
14.4.5. 地址空间布局约束
storage 和 uniform 地址空间 具有不同的缓冲区布局约束,本节对此进行了描述。
除 uniform 之外的所有地址空间 都具有与 storage 地址空间相同的 约束。
由变量直接或间接引用的所有结构和数组类型 必须遵循 该变量地址空间的约束。 违反地址空间约束会导致着色器创建错误。
在本节中,我们将 RequiredAlignOf(S, C) 定义为: 当主机可共享或固定占用空间类型 S 的值 用在地址空间 C 中时,其字节偏移量的对齐要求。
| 宿主可共享或固定占用空间类型 S, 假设 S 可以出现在 C 中 | RequiredAlignOf(S, C), uniform_buffer_standard_layout 受支持,或 C 不是 uniform | RequiredAlignOf(S, C), uniform_buffer_standard_layout 不受支持,且 C 是 uniform |
|---|---|---|
| bool、i32、u32、f32 或 f16 | AlignOf(S) | AlignOf(S) |
| atomic<T> | AlignOf(S) | AlignOf(S) |
| vecN<T> | AlignOf(S) | AlignOf(S) |
| matCxR<T> | AlignOf(S) | AlignOf(S) |
| array<T, N> | AlignOf(S) | roundUp(16, AlignOf(S)) |
| array<T> | AlignOf(S) 如果 C 是 uniform,则不允许 | 不允许 |
| struct S | AlignOf(S) | roundUp(16, AlignOf(S)) |
类型为 T 的结构成员的字节偏移量 必须是 地址空间 C 的 RequiredAlignOf(T, C) 的倍数:
OffsetOfMember(S, i) = k × RequiredAlignOf(T, C)
其中 k 是非负整数,并且结构 S 的第 i 个成员具有 类型 T
元素类型为 T 的数组 必须具有元素步幅,该步幅是 地址空间 C 的 RequiredAlignOf(T, C) 的 倍数:
StrideOf(array<T, N>) = k × RequiredAlignOf(T, C)
StrideOf(array<T>) = k × RequiredAlignOf(T, C)
其中 k 是正整数
当uniform_buffer_standard_layout 不受支持时, uniform 地址空间要求:
-
数组元素对齐到 16 字节边界。 也就是说,对于某个正整数 k',StrideOf(array<T,N>) = 16 × k’。
-
如果某个结构成员本身具有结构类型
S,则该成员起始位置与任何后续成员起始位置之间的 字节数 必须 至少为 roundUp(16, SizeOf(S))。
Note: 下列示例展示了如何在结构体成员上使用align和size属性来满足uniform缓冲区的布局要求。特别地,这些技巧可用于将GLSL带有std140布局的缓冲区机械转换为WGSL。
struct S { x : f32} struct Invalid { a : S , b : f32// invalid: offset between a and b is 4 bytes, but must be at least 16 } @group ( 0 ) @binding ( 0 ) var < uniform> invalid : Invalid ; struct Valid { a : S , @align ( 16 ) b : f32// valid: offset between a and b is 16 bytes } @group ( 0 ) @binding ( 1 ) var < uniform> valid : Valid ;
struct small_stride { a : array< f32, 8 > // stride 4 } // Invalid, stride must be a multiple of 16 @group ( 0 ) @binding ( 0 ) var < uniform> invalid : small_stride ; struct wrapped_f32 { @size ( 16 ) elem : f32} struct big_stride { a : array< wrapped_f32 , 8 > // stride 16 } @group ( 0 ) @binding ( 1 ) var < uniform> valid : big_stride ; // Valid
14.5. 内存模型
通常,WGSL 遵循 Vulkan 内存模型。 本节剩余部分描述了 WGSL 程序如何映射到 Vulkan 内存模型。
注意:Vulkan 内存模型是 形式化 Alloy 模型 的文本版本。
14.5.1. 内存操作
在 WGSL 中,读取访问 等价于 Vulkan 内存模型中的内存读取操作。 在 WGSL 中,写入访问 等价于 Vulkan 内存模型中的内存写入操作。
当调用执行以下操作之一时,会发生读取访问:
-
对 加载规则(Load Rule) 的求值
-
除以下情况外的任意纹理内置函数:
-
除 atomicStore 外的任意原子内置函数
-
workgroupUniformLoad 内置函数
当调用执行以下操作之一时,会发生写入访问:
-
textureStore 内置函数
-
除 atomicLoad 外的任意原子内置函数
-
atomicCompareExchangeWeak 仅当返回结果的
exchanged成员为true时才执行写入
-
原子读-修改-写内置函数执行一次内存操作,该操作既是读取访问也是写入访问。
在任何其他情况下都不会发生读取或写入访问。 读取和写入访问统称为 Vulkan 内存模型中的内存操作。
内存操作正好访问与该操作中使用的特定内存 视图相关联的位置集合。例如,访问包含 多个成员的结构中的 u32 的内存读取, 只会读取与该 u32 成员相关联的内存位置。
注意:对向量分量的写入访问可能访问与该向量相关的所有内存位置。
struct S { a : f32, b : u32, c : f32} @group ( 0 ) @binding ( 0 ) var < storage> v : S ; fn foo () { let x = v . b ; // 不会访问 v.a 或 v.c 的内存位置。 }
14.5.2. 内存模型引用
每个模块作用域的资源变量 为唯一的 group 和 binding 对形成一个内存模型引用。 其他每个变量(即 function、 private 和 workgroup 地址空间中的变量) 在该变量的生命周期内形成唯一的内存模型引用。
14.5.3. 作用域操作
当调用执行作用域操作时,会影响一组或两组调用。 这些集合分别是内存作用域和执行作用域。内存作用域指定了哪些调用能够看到该操作影响的内存内容的更新。 对于同步内置函数,这还意味着所有在该函数之前按程序顺序执行的受影响内存操作,对于该函数之后按程序顺序执行的受影响操作都是可见的。 执行作用域 指定了哪些调用可以参与操作(见§ 15.6 集体操作)。
同步内置函数映射为控制屏障,其执行作用域和内存作用域均为
Workgroup。
注意:如果生成的着色器未启用 Vulkan 内存模型,应使用 Device 作用域代替
QueueFamily。
14.5.4. 内存语义
所有原子内置函数都使用 Relaxed
内存语义,因此没有存储类语义。
注意:WGSL 中的地址空间等价于 SPIR-V 中的存储类。
workgroupBarrier 使用 AcquireRelease 内存语义 和 WorkgroupMemory 语义。
storageBarrier 使用 AcquireRelease 内存语义 和 UniformMemory 语义。
textureBarrier 使用 AcquireRelease 内存语义 和 ImageMemory 语义。
注意:组合使用 workgroupBarrier 和
storageBarrier 时,使用 AcquireRelease
顺序语义,以及 WorkgroupMemory 和 UniformMemory 内存语义。
注意:没有原子或同步内置函数使用 MakeAvailable 或
MakeVisible 语义。
14.5.5. 私有与非私有
所有在storage
或
workgroup 地址空间中的非原子读取访问都被视为
非私有,对应于带有 NonPrivatePointer | MakePointerVisible
内存操作数和 Workgroup 作用域的读取操作。
所有在storage
或
workgroup 地址空间中的非原子写入访问都被视为
非私有,对应于带有
NonPrivatePointer | MakePointerAvailable 内存操作数和 Workgroup 作用域的写入操作。
所有在handle
地址空间中的非原子读取访问都被视为
非私有,对应于带有 NonPrivateTexel | MakeTexelVisible
内存操作数和 Workgroup 作用域的读取操作。
所有在handle
地址空间中的非原子写入访问都被视为
非私有,对应于带有 NonPrivateTexel | MakeTexelAvailable
内存操作数和 Workgroup 作用域的写入操作。
15. 执行
§ 1.1 概述描述了着色器如何被调用以及如何被划分为调用。 本节进一步描述了调用在单独和集体执行时的限制。
15.1. 单次调用内的程序顺序
在 WGSL 模块中,每条语句在执行期间可以被执行零次或多次。 对于给定的调用,每次执行某条语句都代表一个唯一的 动态语句实例。
当语句包含表达式时,语句的语义决定:
-
在语句执行过程中是否会对表达式进行求值。
-
语句中独立表达式之间的求值相对顺序。
表达式的嵌套定义了完成求值必须满足的数据依赖。
即,嵌套表达式必须先于外层表达式被求值。
在 WGSL 中,表达式各操作数的求值顺序是从左到右。
例如,foo() + bar() 必须先求值 foo(),再求值 bar()。
参见§ 8 表达式。
WGSL 模块中的语句按控制流顺序执行。 参见§ 9 语句和§ 11.2 函数调用。
15.2. 一致性
集体操作 (例如 barrier、导数,或依赖隐式求导的纹理操作) 需要在 GPU 上并发运行的不同调用之间进行协调。 当所有调用同时执行该操作,即处于一致控制流时,该操作能够正确且可移植地执行。
相反,当仅一部分调用执行该操作(即处于非一致控制流)时,会发生不正确或不可移植的行为。 通俗而言,只有部分调用到达集体操作,其他调用因非一致控制依赖未能到达或未能同时到达。 非一致控制依赖源于控制流语句,其行为依赖于非一致值。
例如,当不同调用为 if、break-if、while 或 for 的条件表达式,switch 的选择器,或短路二元操作符(
&&或||)的左操作数计算出不同的值时,就会出现非一致控制依赖。
这些非一致值通常可以追溯到一些未被静态证明为一致的源头。 这些源头包括但不限于:
为了确保正确且可移植的行为,WGSL 实现将 执行静态一致性分析,试图证明每个集体操作 都在一致 控制流中执行。 后续小节描述该分析。
一致性失败 将被 触发, 当一致性 分析无法证明特定集体操作 在一致 控制流中执行时。
-
如果针对 计算 导数的内置函数触发了一致性失败,则 会derivative_uniformity 诊断被触发。
-
如果针对subgroup 或 quad 内置函数触发了一致性失败,则会subgroup_uniformity 诊断被触发
-
该诊断的触发位置是该 内置函数的调用点的位置, 或者在 subgroupShuffleUp、 subgroupShuffleDown 或 subgroupShuffleXor 的情况下,是要求一致的参数的位置
-
15.2.1. 术语与概念
以下定义仅为信息性定义,试图为下一小节中的分析 正在计算的内容提供一种直观理解。 真正定义这些概念以及程序何时有效或违反一致性 规则的是该分析。
对于给定的一组调用:
-
如果给定作用域中的所有调用在程序中的给定点 都像是在锁步执行一样执行,则称该点对于给定的一致控制流 一致性作用域具有一致控制流。
-
如果某个表达式在一致控制流中执行,并且所有调用计算出 相同的值,则称它是一个一致值。
-
如果各调用在局部变量处于活动状态的每个点都为其持有相同的值, 则称它是一个一致变量。
15.2.2. 一致性分析概述
下面的各小节规定了一种静态分析,来验证集合操作仅在统一控制流中执行。 如果支持 subgroup_uniformity 功能,则存在多个统一性作用域。 此分析会针对每个作用域分别执行一次。
注意:该分析被描述为对每个作用域执行一次,但实现也可以选择只做一次分析并覆盖所有作用域。 工作组和 绘制 统一性作用域在本质上是等价的,因为它们应用于不同的着色器阶段,并且在着色器阶段中代表最大的统一性作用域。
分析假定不会发生动态错误。 无论一致性分析结果如何,包含动态错误的着色器阶段本就不可移植。
每个函数都会被分析,需确保两点:
-
调用其他函数时满足一致性要求,并且
-
每当被调用时也能满足一致性要求。
作为这项工作的组成部分,分析会计算有关函数的元数据,以协助分析其调用者。 这意味着首先要构建调用图,并从叶子节点(即只调用标准库外无其他函数的函数)开始向上传递分析,直到入口点。 这样,每当分析某个函数时,其所有被调用者的元数据都已计算好。 由于语言中禁止递归,因此不会陷入循环分析的风险。
注意:换句话说,就是对函数按“可能被(直接或间接)调用”这个偏序做拓扑排序,并按此顺序分析。
此外,对于每一次函数调用,分析会计算并传播该调用若无法证明处于一致控制流时会触发的触发规则集合(如有)。 我们称这为该调用的潜在触发集(potential-trigger-set)。 该集合的元素来自如下几种可能:
-
依赖导数计算的函数对应derivative_uniformity,
-
未命名的触发规则,用于不能被过滤的一致性要求。
-
这用于依赖同步函数的计算着色器函数。
-
15.2.3. 分析函数的一致性要求
每个函数的分析分为两个阶段。
第一阶段遍历函数的语法结构,并根据后续小节中的规则沿途构建一个有向图。 第二阶段对该图进行探索,计算调用此函数的约束,并有可能触发一致性失败。
一条边可以理解为其源节点对应断言到目标节点对应断言的蕴含关系。
例如,一个一致性要求是 workgroupBarrier 内置函数只能在一致控制流中被调用。
表达该要求时,我们从 RequiredToBeUniform.error 到对应
workgroupBarrier
调用点 的节点添加一条边。
可以这样理解:RequiredToBeUniform.error 对应命题 True,
所以 RequiredToBeUniform.error -> X 就等价于 X 是真。
反之,为表达无法保证某内容一致性(例如持有线程 id 的变量),我们从对应节点到 MayBeNonUniform 添加一条边。 这样可以理解为 MayBeNonUniform 对应命题 False,X -> MayBeNonUniform 等价于 X 为假。
这种解释的一个结果是,所有能从 RequiredToBeUniform.error 可达的节点都对应于程序要合法必须一致的内容,而所有能到达 MayBeNonUniform 的节点都对应于无法保证一致性的内容。 因此,如果存在从 RequiredToBeUniform.error 到 MayBeNonUniform 的路径,就会发生一致性违规,触发一致性失败。
节点 RequiredToBeUniform.warning 和 RequiredToBeUniform.info 用法类似, 但用于帮助判断何时应触发警告或信息诊断:
-
如果存在从 RequiredToBeUniform.warning 到 MayBeNonUniform 的路径,则会触发 警告 诊断。
-
如果存在从 RequiredToBeUniform.info 到 MayBeNonUniform 的路径,则会触发信息 诊断。
如§ 2.3 诊断所述,如果已产生更高严重性的诊断,则较低严重性的诊断可能被丢弃。
对每个函数,会计算两个标签:
-
一个调用点标签(call site tag) 描述函数调用点的控制流一致性要求,
-
一个函数标签(function tag) 描述函数对一致性的影响。
对每个函数的形参,会计算一个或两个标签:
-
一个参数标签(parameter tag) 描述参数值的一致性要求。
-
一个参数返回标签(parameter return tag) 描述参数的一致性如何影响函数返回值的一致性。
-
一个指针参数标签(pointer parameter tag),当参数类型是指向function地址空间的指针时。 该标签描述该参数所指内存中的值在函数执行期间是否可能变为非一致。
| 调用点标签 | 描述 |
|---|---|
| CallSiteRequiredToBeUniform.S, 其中 S 是一种严重性:error、warning 或 info。 |
该函数只能在一致控制流中被调用。
否则将触发严重性为 S 的诊断。
关联一个潜在触发集。 |
| CallSiteNoRestriction | 该函数可以在非一致控制流中被调用。 |
| 函数标签 | 描述 |
|---|---|
| ReturnValueMayBeNonUniform | 该函数的返回值可能是非一致的。 |
| NoRestriction | 该函数不会引入非一致性。 |
| 参数标签 | 描述 |
|---|---|
| ParameterRequiredToBeUniform.S, 其中 S 是一种严重性:error、warning 或 info。 |
该参数必须是一致值。
如果参数类型为指针,则要求内存视图一致,但内容未必一致。
否则将触发严重性为 S 的诊断。
关联一个潜在触发集。 |
| ParameterContentsRequiredToBeUniform.S, 其中 S 是一种严重性:error、warning 或 info。 |
指针参数所指内存中存储的值必须是一致值。
否则将触发严重性为 S 的诊断。
关联一个潜在触发集。 |
| ParameterNoRestriction | 该参数值没有一致性要求。 |
| 参数返回标签 | 描述 |
|---|---|
| ParameterReturnContentsRequiredToBeUniform | 该参数必须是一致值,对应函数返回值才能为一致值。 如果参数为指针,则指针所指内存中的值也必须一致。 |
| ParameterReturnNoRestriction | 该参数值没有一致性要求。 |
| 指针参数标签 | 描述 |
|---|---|
| PointerParameterMayBeNonUniform | 指针参数所指内存中的值在函数调用后可能为非一致。 |
| PointerParameterNoRestriction | 指针参数所指内存中值的一致性不受该函数调用影响。 |
下面的算法描述了如何计算给定函数的这些标签:
-
创建如下节点:
-
按照§ 15.2.4 指针消糖进行指针消糖。
-
对于每个 function 地址空间的指针形参,创建如下节点:
-
param_i_contents:代表内存视图内容的一致性。
-
Value_return_i_contents:代表函数对内存视图内容一致性的影响。
-
-
-
遍历函数语法,按照后续章节(§ 15.2.5 函数作用域变量值分析、§ 15.2.6 语句一致性规则、§ 15.2.7 函数调用一致性规则、§ 15.2.8 表达式一致性规则)的规则向图中添加节点和边,使用CF_start作为函数体的起始控制流。
-
本步骤添加的节点称为内部节点(interior nodes)。
-
-
初始化如下:
-
函数标签初始化为NoRestriction。
-
每个param_i(如存在)的指针参数标签初始化为PointerParameterNoRestriction。
-
-
对每个严重性 S,按顺序 {error、warning、info},执行如下:
-
令 R.S 为从 RequiredToBeUniform.S 可达且未访问过的节点集合。
-
将 R.S 内的内部节点标记为已访问。
-
令 PTS 为与 RequiredToBeUniform.S 关联的潜在触发集。
-
如果 R.S 包含节点 MayBeNonUniform,则触发一致性失败:
-
对每个 t 属于 PTS,触发严重性为 S,触发规则为 t 的诊断。
-
-
否则:
-
如果 R.S 包含 CF_start,且调用点标签自初始化后未被更新,则 设置调用点标签为CallSiteRequiredToBeUniform.S,并设置其潜在触发集为 PTS。
-
对 R.S 中每个 param_i,如果其对应参数标签自初始化后未更新,则 设置为ParameterRequiredToBeUniform.S,并将其潜在触发集设置为 PTS。
-
对 R.S 中每个 param_i_contents,如果其对应参数标签自初始化后未更新,则 设置为ParameterContentsRequiredToBeUniform.S,并将其潜在触发集设置为 PTS。
-
-
-
将所有内部节点标记为未访问。
-
如果存在Value_return,则令 VR 为从 Value_return 可达的节点集合。
-
如果 VR 包含 MayBeNonUniform,则将函数标签设置为ReturnValueMayBeNonUniform。
-
对 VR 中每个 param_i,将对应参数返回标签设置为ParameterReturnContentsRequiredToBeUniform。
-
-
对每个Value_return_i_contents节点,令 VRi 为从 Value_return_i_contents 可达的节点集合。
-
如果 VRi 包含 MayBeNonUniform,则将对应指针参数标签设置为PointerParameterMayBeNonUniform。
-
注意:此时可销毁整个图。上述标签就是分析调用该函数所需的全部信息。 不过,图中还包含可用于输出更详细诊断的信息。 例如,某个函数中的值无法被证明是一致的, 这会导致另一个函数中触发一致性失败。 更有用的诊断会描述该非一致值以及诊断触发位置处的函数调用。
15.2.4. 指针消糖
每个function地址空间内的指针类型参数 会被消糖为一个局部变量声明,其初始值等价于对参数进行解引用。 也就是说,function 地址空间的指针被视为对局部变量声明的别名。 初始值赋值会为第i个参数(即V(e)为param_i_contents)创建一条到param_i_contents的边。
每个let 声明 L,如果其有效值类型是指针类型,则消糖如下:
-
以后序深度优先遍历的方式访问 L 的初始化表达式的每个子表达式 SE:
-
记录 L (可能已更新)的初始化表达式。
这种消糖方式通过在每次使用时直接暴露指针的根标识符,简化了后续的分析。
注意:为了一致性分析的目的,类型检查要求在消糖前后都要执行。
fn foo ( p : ptr< function, array< f32, 4 >> , i : i32) -> f32{ let p1 = p ; var x = i ; let p2 = & (( * p1 )[ x ]); x = 0 ; * p2 = 5 ; return ( * p1 )[ x ]; } // 下方是分析用的 foo 的等价实现。 fn foo_for_analysis ( p : ptr< function, array< f32, 4 >> , i : i32) -> f32{ var p_var = * p ; // 为 p 引入变量。 let p1 = & p_var ; // p1 用变量 var x = i ; let x_tmp1 = x ; // 捕获 x 的值 let p2 = & ( p_var [ x_tmp1 ]); // 替换 p1 的初始化 x = 0 ; * ( & ( p_var [ x_tmp1 ])) = 5 ; // 替换 p2 的初始化 return ( * ( & p_var ))[ x ]; // 替换 p1 的初始化 }
15.2.5. 函数作用域变量值分析
可以通过到达某语句的赋值以及可能的初始值来分析每个函数作用域的变量在某条语句的值。
如果满足以下条件,则赋值为完全赋值:
否则,赋值为部分赋值。
完整引用 是一种引用类型表达式,具体包括如下几种情况:
完整指针 是一种指针类型表达式,具体包括如下几种情况:
注意:在本分析中,我们不需要考虑指针类型的形式参数可能是完整指针的情况。
完整引用,以及类似的 完整指针,是对应源变量 x 的所有内存位置的内存视图。
不是完整引用的引用称为部分引用。 因此,部分引用要么是:
考虑一个只有一个成员的结构体类型以及一个存储该类型的变量:
struct S { member : i32; } fn foo () { var v : S ; }
此时 v 是完全引用,v.member 是部分引用。
它们的内存视图覆盖相同的内存位置,但 v 的存储类型为 S,v.s 的存储类型为 i32。
类似地,只有一个元素的数组也会出现这种情况:
fn foo () { var arr : array< i32, 1 > ; }
此时 arr 是完全引用,arr[0] 是部分引用。
它们的内存视图覆盖相同的内存位置,但 arr 的存储类型为 array<i32,1>,arr[0] 的存储类型为
i32。
为简化分析,任何类型的部分引用上的赋值都被视为不会修改源变量的所有内存位置。 这会导致分析趋于保守,可能对比实际必要的更多程序触发一致性失败。
后续章节一致性规则中提到函数作用域变量用作RHSValue时的值,是指在对 RHSValue 表达式求值前变量的值。 用作LHSValue时,是指表达式所在语句执行后的变量值。
由于控制流语句或部分赋值,变量的多次赋值可能都会到达该变量的某次使用点。 分析通过对每个控制流出口联合到达的赋值集合,将多个到达的赋值合并。
下表描述了合并赋值的规则。
在一致性图中,每次合并为从结果节点到表示值来源的节点的边。
用于任意变量 x,记号如下:
-
Vin(S) 表示
x在执行语句 S 前的值。 -
Vout(S) 表示
x在执行语句 S 后的值。 -
Vout(prev) 表示
x在当前语句执行前的值。 -
Vin(next) 表示
x在下条语句执行前的值。 -
V(e) 表示后续章节中表达式的值节点。
-
V(0) 表示
x的有效值类型的零值。
| 语句 | 结果 | 结果的边 |
|---|---|---|
| var x; | Vin(next) | V(0) |
|
var x = e; | Vin(next) |
V(e)
注意:这是对 x 的完全赋值。 |
|
x = e; | ||
|
r = e; 其中 r 是对变量 x 的完全引用 | ||
|
r = e; 其中 r 是对变量 x 的部分引用 | Vout(S) |
V(e),Vout(prev)
注意:这是对 x 的部分赋值。 |
|
s1 s2 其中 Next 属于 s1 的行为。 注意:s1 通常以分号结尾。 | Vin(s2) | Vout(s1) |
|
if e s1 else s2 其中 Next 属于 s1 和 s2 的行为 | Vin(next) | Vout(s1),Vout(s2) |
|
if e s1 else s2 其中 Next 属于 s1 的行为,不属于 s2 的行为 | Vin(next) | Vout(s1) |
|
if e s1 else s2 其中 Next 属于 s2 的行为,不属于 s1 的行为 | Vin(next) | Vout(s2) |
| loop { s1 continuing { s2 } } | Vin(s1) | Vout(prev), Vout(s2) 如果 s1 的行为与 {Next,Continue} 有交集 |
| loop { s1 continuing { s2 } } | Vin(s2) | Vout(s1) 如果 Next 属于 s1 的行为, Vout(si) 对于所有行为为 {Continue} 的 si 并转移控制到 s2 |
| loop { s1 continuing { s2 } } | Vin(next) | Vout(s2) 如果 Break 属于 s1 的行为, Vout(si) 对于所有行为为 {Break} 的 si 并转移控制到 next |
| switch e { case _: s1 case _: s2 ... case _: s3 } | Vin(si) | Vout(prev) |
| switch e { case _: s1 case _: s2 ... case _: s3 } | Vin(next) | Vout(si), 对于所有行为包含 Next 或 Break 的 si,以及 Vout(sj) 对于所有语句内部行为为 {Break} 的 sj 并转移控制到 next |
对所有其它语句(函数调用除外),Vin(next) 等价于 Vout(prev)。
注意:同语句行为分析章节,消糖规则同样适用。
15.2.6. 语句一致性规则
分析语句的规则以语句本身和对应于其起始处控制流的节点(在下文中记作 "CF")为参数,并返回以下内容:
-
对应于语句出口的控制流节点
-
需要添加到图中的新节点和边的集合
在下表中,(CF1, S) => CF2 表示“从控制流 CF1 开始对 S 运行分析,
对图应用所需的更改,并将所得控制流命名为 CF2”。
类似地,(CF1, E) => V 表示“从控制流 CF1 开始对表达式 E 运行分析,
对图应用所需的更改,并将所得值节点命名为 V”(表达式分析见下一节)。
这种表达式求值用于任何不属于左侧的赋值中的表达式,
并称为 RHSValue。
对于属于左侧的赋值中的表达式,
有一组类似规则,即
LHSValue,
记作 LHSValue: (CF, E) => L。它不是计算对应于值一致性的节点,
而是计算对应于我们所寻址变量的一致性的节点。
注:RHSValues 包括属于右侧的 赋值 语句中的表达式,或不属于赋值、 递增或递减语句的表达式。
当必须创建多条边时,我们使用 X -> {Y, Z} 作为
X -> Y, X -> Z 的简写。
在分析循环时,我们使用以下模式:
-
节点 CF' 对每次迭代开始处的控制流一致性建模。
-
节点 CF1 对循环体 s1 末尾的控制流一致性建模。
-
节点 CF2 对 continuing 块语句 s2 末尾的控制流一致性建模。
-
边 CF' -> CF1 和 CF' -> CF2 对以下事实建模: 循环末尾的控制流一致性会影响下一次迭代开始处的一致性。 当循环体语句 s1 只会 break 或 return, 即 s1 的行为是 {Break, Return} 的子集时,这些边不存在。
-
CF' -> CF 边对以下事实建模: 至少某些迭代开始处的一致性取决于控制流到达整个循环语句时的一致性。
-
当整个循环具有行为 {Next} 时,我们假设任何控制流发散在循环结束时都已解决。 因此循环的结果控制流节点被设置为 CF, 以建模离开循环时的一致性与首次到达循环时的一致性相匹配这一事实。
-
注:语句行为 分析意味着循环的行为是 {Next}、{Return} 或 {Next,Return} 之一。
| 语句 | 新节点 | 递归分析 | 结果控制流节点 | 新边 |
|---|---|---|---|---|
| empty statement | CF | |||
| {s} | (CF, s) => CF' | CF' | ||
|
s1 s2, s1 的行为中包含 Next 注:s1 通常以分号结束。 | (CF, s1) => CF1 (CF1, s2) => CF2 | CF2 | ||
|
s1 s2, s1 的行为中不包含 Next 注:s1 通常以分号结束。 |
(CF, s1) => CF1 注:s2 在静态上不可达, 不会被递归分析。 s2 不参与一致性分析。 | CF1 | ||
| if e s1 else s2 行为为 {Next} | (CF, e) => V (V, s1) => CF1 (V, s2) => CF2 | CF | ||
| if e s1 else s2 行为为其他情况 | CFend | CFend | CFend -> {CF1, CF2} | |
| loop {s1} s1 的行为中不包含 Return | CF' | (CF', s1) => CF1 | CF | CF' -> {CF1, CF}, 如果 s1 的行为与 {Next,Continue} 有交集 |
| CF' -> CF, 如果 s1 的行为与 {Next,Continue} 没有交集 | ||||
| loop {s1} s1 的行为中包含 Return | CF' | (CF', s1) => CF1 | CF1 | CF' -> {CF1, CF} 如果 s1 的行为与 {Next,Continue} 有交集 |
| CF' -> CF 如果 s1 的行为与 {Next,Continue} 没有交集 | ||||
| loop {s1 continuing {s2}} s1 具有行为 {Break} | (CF, s1) => CF1 | CF |
注:该循环只执行一次迭代, 因此不需要额外的边。 | |
| loop {s1 continuing {s2}} s1 具有行为 {Return} 或 {Break,Return} | (CF, s1) => CF1 | CF1 | ||
| loop {s1 continuing {s2}} s1 的行为与 {Next,Continue} 有交集 | CF' | (CF', s1) => CF1 (CF1, s2) => CF2 | CF 如果 s1 的行为中不包含 Return | CF' -> {CF2, CF} |
| CF' 如果 s1 的行为中包含 Return | ||||
| switch e case _: s_1 .. case _: s_n 行为为 {Next} | (CF, e) => V (V, s_1) => CF_1 ... (V, s_n) => CF_n | CF | ||
| switch e case _: s_1 .. case _: s_n 行为为其他 情况 | CFend | CFend | CFend -> {CF_1, ..., CF_n} | |
| var x: T; | CF | |||
| break; | ||||
| continue; | ||||
| break if e; | CFend | (CF, e) => V | CFend |
CFend -> V
注:从 CFend 到
V 的边捕获了这样一个事实:
当条件值非一致时,由此 |
| return; | CF | 对于每个函数地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(见 § 15.2.5 函数作用域 变量值分析) | ||
| return e; | (CF, e) => V | CF |
Value_return ->
V
对于每个函数地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(见 § 15.2.5 函数作用域 变量值分析) | |
| e1 = e2; | LHSValue:
(CF, e1) => LV (CF, e2) => RV | CF |
LV -> RV
注:LV 是来自值分析的结果值。 | |
| _ = e | (CF, e) => V | CF | ||
| let x = e; | (CF, e) => V | CF | ||
| var x = e; | (CF, e) => V | CF | ||
f()无实参的函数调用语句 | 调用函数调用分析: (CF, f()) => Result | CF | ||
f(e1,...,eN)有实参的函数调用语句 | 调用函数调用分析: (CF, f(e1,...,eN)) => Result | CF |
出于此分析的目的:
-
for循环会被脱糖(见 § 9.4.4 For Statement) -
while循环会被脱糖(见 § 9.4.5 While Statement) -
loop {s}被视为loop {s continuing {}} -
没有
else分支的if语句被视为具有空的 else 分支, 即以else {}结束 -
带有
else if分支的if语句被视为嵌套的简单if/else语句 -
以
default开始的switch_clause 的行为 与以case _:开始的switch_clause 完全相同
为了最大化性能,实现通常会尝试尽量减少 非一致控制 流的数量。 然而,可以说调用是一致的那些点会因多个因素而异。 WGSL 的静态分析保守地假定:如果语句的行为为 {Next}, 则在 if、switch 和 loop 语句结束处 会返回到一致控制流。 在前面的表中,这被建模为结果控制流节点与输入控制流节点相同。
15.2.7. 函数调用一致性规则
最复杂的规则是函数调用的规则:
-
令 CF 为函数调用表达式开始处的控制流。
-
对于每个实参,使用 CF 应用对应的表达式规则。将对应的 值节点命名为 arg_i
-
创建一个名为 Result 的新节点
-
添加一条从 Result 到 CF 的边
-
-
如果函数的调用点标签为 CallSiteRequiredToBeUniform.S,则:
-
添加一条从 RequiredToBeUniform.S 到 CF 的边
-
将调用点标签的 potential-trigger-set 的成员 添加到与 RequiredToBeUniform.S 关联的 potential-trigger-set。
-
-
如果函数标签为 ReturnValueMayBeNonUniform,则添加一条从 Result 到 MayBeNonUniform 的边
-
对于每个实参 i:
-
如果对应的形参标签为 ParameterRequiredToBeUniform.S,则:
-
添加一条从 RequiredToBeUniform.S 到 arg_i 的边。
-
将形参 标签的 potential-trigger-set 的成员 添加到与 RequiredToBeUniform.S 关联的 potential-trigger-set。
-
-
如果形参返回标签为 ParameterReturnContentsRequiredToBeUniform, 则添加一条从 Result 到 arg_i 的边
-
如果对应形参具有 指针形参标签 PointerParameterMayBeNonUniform,则 添加一条从 Vout(call) 到 MayBeNonUniform 的边
-
如果形参是函数地址空间中的指针,则添加一条从 Vout(call) 到此前记录的可达形参所对应的每个 arg_i 的边
-
如果形参标签为 ParameterContentsRequiredToBeUniform.S, 则添加一条从 RequiredToBeUniform.S 到 Vout(call) 的边
-
-
注意:Vout(call)的定义见§ 15.2.5 函数作用域变量值分析。
大多数内置函数具有如下标签:
-
每个参数:
下列为例外情况:
-
对 § 17.11 同步内置函数 中函数的调用:
-
具有 函数标签 NoRestriction。
-
具有如下 调用点标签:
-
若 subgroup_uniformity 不支持,或 统一性作用域 不是 subgroup,则为 CallSiteRequiredToBeUniform.error,其 potential-trigger-set 为一个无名的 触发规则。
-
注意:此触发规则没有名字,因此无法被过滤。
-
-
-
此外,如果 subgroup_uniformity 不支持或 统一性作用域 不是 subgroup,对于 workgroupUniformLoad 的调用,参数
p具有 参数标签 ParameterRequiredToBeUniform.error,其 potential-trigger-set 为一个无名的 触发规则。
-
-
对 § 17.6 微分内置函数、§ 17.7.8 textureSample、§ 17.7.9 textureSampleBias、§ 17.7.10 textureSampleCompare 中函数的调用:
-
调用点标签如下:
-
如果 subgroup_uniformity 不支持,或 统一性作用域 不是 subgroup:
-
令 DF 为该调用点及触发规则 derivative_uniformity 的最近包围诊断过滤器。
-
如果 DF 存在,则 S 为 DF 的新严重性参数。
-
如果 S 为 off,则为 CallSiteNoRestriction。
-
否则为 CallSiteRequiredToBeUniform.S,其 potential-trigger-set 包含 derivative_uniformity 元素。
-
-
如果没有 DF,则为 CallSiteRequiredToBeUniform.error,其 potential-trigger-set 包含 derivative_uniformity 元素。
-
-
-
对 § 17.7.4 textureLoad 的调用:
-
调用点标签为 CallSiteNoRestriction
-
函数标签如下:
-
如果参数
t是 读写存储贴图(read-write storage texture),标签为 ReturnValueMayBeNonUniform。 -
否则为 NoRestriction。
-
-
-
对 § 17.12 子组内置函数 或 § 17.13 四元操作 中函数的调用:
-
函数标签如下:
-
令 DF 为该调用点和触发规则 subgroup_uniformity 的最近包围诊断过滤器。
-
调用点标签如下:
-
如果 subgroup_uniformity 不支持,或 统一性作用域是 subgroup:
-
如果 DF 存在,S为DF的新严重性参数:
-
若 S 是 off,则为 CallSiteNoRestriction。
-
否则为 CallSiteRequiredToBeUniform.S,其 potential-trigger-set 包含 subgroup_uniformity 元素。
-
-
如果没有 DF,则为 CallSiteRequiredToBeUniform.error,其 potential-trigger-set 包含 subgroup_uniformity 元素。
-
-
-
对于 subgroupShuffleUp 或 subgroupShuffleDown 的调用,参数
delta的参数标签如下:-
如果 subgroup_uniformity 不支持,或 统一性作用域是 subgroup:
-
如果 DF 存在,S为DF的新严重性参数:
-
若 S 是 off,则参数标签为 NoRestriction。
-
否则为 ParameterRequiredToBeUniform.S,其 potential-trigger-set 包含 subgroup_uniformity 元素。
-
-
如果没有 DF,则参数标签为 ParameterRequiredToBeUniform.error,其 potential-trigger-set 包含 subgroup_uniformity 元素。
-
-
否则为 NoRestriction。
-
-
对于 subgroupShuffleXor 的调用,参数
mask的参数标签如下:-
如果 subgroup_uniformity 不支持,或 统一性作用域是 subgroup:
-
如果 DF 存在,S为DF的新严重性参数:
-
若 S 是 off,则参数标签为 NoRestriction。
-
否则为 ParameterRequiredToBeUniform.S,其 potential-trigger-set 包含 subgroup_uniformity 元素。
-
-
如果没有 DF,则参数标签为 ParameterRequiredToBeUniform.error,其 potential-trigger-set 包含 subgroup_uniformity 元素。
-
-
否则为 NoRestriction。
-
-
注意: WGSL 实现会确保若某个作用域下函数调用前的控制流是统一的,则调用后的控制流也会统一。
15.2.8. 表达式一致性规则
分析表达式的规则以表达式本身以及对应于其开始处控制流的节点(下面记为“CF”)作为参数, 并返回以下内容:
-
一个对应于其值的节点
-
一组要添加到图中的新节点和边
| 表达式 | 新节点 | 递归分析 | 结果值节点 | 新边 |
|---|---|---|---|---|
| e1 || e2 | (CF, e1) => V1 (V1, e2) => V2 | V2 | ||
| e1 && e2 | ||||
| 字面量 | CF | |||
| 解析为函数作用域变量“x”的 identifier, 其中该 identifier 作为内存视图表达式 MVE 的根 identifier出现, 并且在类型检查期间对 MVE 调用了加载规则 | Result | X 是对应于包含此表达式的语句输入处“x”的值的节点 | Result |
Result -> {CF, X}
注:X 等价于
“x”的 Vout(prev) |
| 解析为函数作用域变量“x”的 identifier, 其中“x”是脱糖后的指针形参 i,并且 该 identifier 作为内存视图表达式 MVE 的根 identifier出现, 并且在类型检查期间未对 MVE 调用加载规则 | param_i | |||
| 解析为函数作用域变量“x”的 identifier, 其中该 identifier 作为内存视图表达式 MVE 的根 identifier出现, 并且在类型检查期间未对 MVE 调用加载规则 | CF | |||
| 解析为const-declaration、override-declaration、 let-declaration,或非指针类型 的非内置形式参数“x”的 identifier | Result | X 是对应于“x”的节点 | Result | Result -> {CF, X} |
| 解析为形式 参数的 identifier, 该形式参数是在 storage、workgroup 或 private 地址空间中、具有非只读 访问模式的 指针类型参数, 其中该 identifier 作为内存视图表达式 MVE 的根 identifier出现, 并且在类型检查期间对 MVE 调用了加载规则 | MayBeNonUniform | |||
| 解析为形式 参数的 identifier, 该形式参数是在 storage、workgroup 或 private 地址空间中、具有非只读 访问模式的 指针类型参数, 其中该 identifier 作为内存视图表达式 MVE 的根 identifier出现, 并且在类型检查期间未对 MVE 调用加载规则 | CF | |||
| 解析为形式 参数的 identifier, 该形式参数是在非 function 的地址空间中、 具有只读访问模式的 指针类型参数 | CF | |||
| 解析为一致内置值“x”的 identifier | CF | |||
| 解析为非一致内置值“x”的 identifier | MayBeNonUniform | |||
| 解析为只读模块作用域变量“x”的 identifier | CF | |||
| 解析为非只读模块作用域 变量“x”的 identifier,其中该 identifier 作为内存视图表达式 MVE 的根 identifier出现, 并且在类型检查期间对 MVE 调用了加载规则 | MayBeNonUniform | |||
| 解析为非只读模块作用域 变量“x”的 identifier,其中该 identifier 作为内存视图表达式 MVE 的根 identifier出现, 并且在类型检查期间未对 MVE 调用加载规则 | CF | |||
| ( e ) | (CF, e) => V | V | ||
| op e, 其中 op 是一元运算符 | ||||
| e.field | ||||
| e1 op e2, 其中 op 是非短路二元 运算符 | Result | (CF, e1) => V1 (CF, e2) => V2 | Result | Result -> {V1, V2} |
| e1[e2] | ||||
f()无实参的函数调用表达式 | 调用函数调用分析: (CF, f()) => Result | Result | ||
f(e1,...,eN)有实参的函数调用表达式 | 调用函数调用分析: (CF, f(e1,...,eN)) => Result | Result |
以下内置输入变量被视为一致:
在子组 一致性作用域中,以下内置 输入变量也被视为一致:
所有其他内置输入变量(见内置 值)都被视为非一致。
注:作者应避免将一致的 内置值与 其他非一致输入组合在一起,因为该分析不会分别分析复合类型的组件。
| 表达式 | 新节点 | 递归分析 | 结果变量节点 | 新边 |
|---|---|---|---|---|
| 解析为函数作用域变量“x”的 identifier | Result | X 是对应于包含此表达式的语句输出处“x”的值的节点。 | Result |
Result -> {CF, X}
注:X 等价于
“x”的 Vin(next) |
| 解析为 const-declaration、override-declaration、 let-declaration,或形式参数“x”的 identifier | X 是对应于“x”的节点 | X | ||
| 解析为模块作用域变量“x”的 identifier | MayBeNonUniform | |||
| e.field | LHSValue: (CF, e) => L1 | L1 | ||
| *e | ||||
| &e | ||||
| e1[e2] | LHSValue:
(CF, e1) => L1 (CF, e2) => V2 | L1 | L1 -> V2 |
15.2.9. 为控制流中每一点注释一致性
本小节为非规范性内容。
如果实现者希望为开发者提供一个诊断模式,显示整个着色器控制流中每个点是否一致(以及是否可以在此处调用需要一致性的函数),我们建议如下:
-
运行前面小节所述的(强制性的、规范性的)分析,并为每个函数保留图。
-
反转所有这些图中的所有边
-
遍历每个函数,从入口点开始,且在访问所有调用者之前不访问某个函数:
-
为每个在至少一个调用者中为非一致的参数,从MayBeNonUniform到该参数添加一条边。
-
如果该函数在至少一个调用者中以非一致控制流被调用,则从MayBeNonUniform到CF_start添加一条边。
-
查看从MayBeNonUniform可达的节点。所有被访问到的节点都是分析无法证明一致的表达式或控制流点。
-
这些可达性分析未访问到的节点,都可以被分析证明是一致的(因此在这些点调用导数函数或类似函数是安全的)。
注意:仍然需要自底向上的分析,因为它让我们知道在遇到调用时应向图中添加哪些边。
15.2.10. 示例
后续示例中的图使用如下节点约定:
-
矩形表示值节点。
-
圆角矩形表示控制流节点。
15.2.10.1.
无效的 textureSample 函数调用
此示例展示了对textureSample
内置函数调用的无效用法。
函数调用发生在 if 语句中,其条件依赖于非一致值(即内置值 position)。
无效的依赖链以红色高亮显示。
@group ( 0 ) @binding ( 0 ) var t : texture_2d< f32> ; @group ( 0 ) @binding ( 1 ) var s : sampler; @fragment fn main ( @builtin ( position) pos : vec4< f32> ) { if ( pos . x < 0.5 ) { // 无效的 textureSample 函数调用。 _ = textureSample ( t , s , pos . xy ); } }
本例还展示了 if 语句之后的控制流一致性与 if 语句之前相同(CF_return 连接到CF_start)。
即,在 if 语句后控制流再次恢复为一致(因为在入口点开始时保证为一致控制流)。
如果textureSample函数调用移到 if 语句外,程序就会合法。
同样,如果 if 语句的条件是一个一致值(例如每个调用都从uniform buffer中读取同一个值),程序也会合法。
15.2.10.2. 函数作用域变量一致性
本例展示了依赖于函数作用域变量的
barrier函数调用的合法与非法用法。
workgroupBarrier是非法的,因为x的值来自可变的模块级变量a。
storageBarrier是合法的,因为x的值来自不可变的模块级变量b。
本例突出了值分析
能够区分函数作用域变量生命周期中不同一致性区段的能力。
此外还清楚地展示了在第一个if语句结束后控制流再次恢复为一致。
我们知道这一点是因为图的该部分与第二个 if 语句无关。
@group ( 0 ) @binding ( 0 ) var < storage, read_write> a : i32; @group ( 0 ) @binding ( 1 ) var < uniform> b : i32; @compute @workgroup_size ( 16 , 1 , 1 ) fn main () { var x : i32; x = a ; if x > 0 { // 非法的 barrier 函数调用。 workgroupBarrier (); } x = b ; if x < 0 { // 合法的 barrier 函数调用。 storageBarrier (); } }
注意:子图仅为便于理解而包含在示例中。
15.2.10.3. 复合值分析的局限性
一致性分析的一个限制是,它不会独立跟踪复合值的各分量。 也就是说,任何一个分量值只要是非常量,都会导致分析将整个复合值视为非常量。 下面的例子说明了这个问题,以及着色器作者可以采用的一种规避该限制的方法。
struct Inputs { // workgroup_id 是一致内置值。 @builtin ( workgroup_id) wgid : vec3< u32> , // local_invocation_index 是非一致内置值。 @builtin ( local_invocation_index) lid : u32} @compute @workgroup_size ( 16 , 1 , 1 ) fn main ( inputs : Inputs ) { // 此比较总是一致, // 但分析无法判定。 if inputs . wgid . x == 1 { workgroupBarrier (); } }
规避该分析局限的最简单方式是拆分复合类型,使已知一致的值与已知非一致的值分开。 下方可选 WGSL 中,将两个内置值拆分为单独参数即可通过一致性分析。 这可通过图中RequiredToBeUniform.S到 MayBeNonUniform无路径体现。
@compute @workgroup_size ( 16 , 1 , 1 ) fn main ( @builtin ( workgroup_id) wgid : vec3< u32> , @builtin ( local_invocation_index) lid : u32) { // 一致性分析现在可以正确判定此比较总是一致。 if wgid . x == 1 { // 合法的 barrier 函数调用。 workgroupBarrier (); } }
15.2.10.4. 循环中的一致性
在本例中,循环中有一个无效的 workgroupBarrier 函数调用。
非一致内置值 local_invocation_index 是根本原因,尽管它出现在循环体内 barrier 之后。
这是因为在后续迭代中,工作组中的某些调用已经提前退出循环,而其他调用试图执行 barrier。
分析将迭代间依赖建模为一条边,即循环体开始处的控制流(CF_loop_body)依赖于循环体结束处的控制流(CF_after_if)。
@compute @workgroup_size ( 16 , 1 , 1 ) fn main ( @builtin ( local_invocation_index) lid : u32) { for ( var i = 0u ; i < 10 ; i ++ ) { workgroupBarrier (); if ( lid + i ) > 7 { break ; } } }
15.2.10.5. 用户自定义函数调用
本例是第一个示例的变体,但使用了用户自定义函数调用。
分析将 scale 的两个参数的参数返回标签都设为
ParameterReturnContentsRequiredToBeUniform。
这导致 main 中 scale 函数调用的返回值与 position 内置值之间形成一条路径。
该路径是从RequiredToBeUniform.S到
MayBeNonUniform
的无效路径的子路径。
fn scale ( in1 : f32, in2 : f32) -> f32{ let v = in1 / in2 ; return v ; } @group ( 0 ) @binding ( 0 ) var t : texture_2d< f32> ; @group ( 0 ) @binding ( 1 ) var s : sampler; @fragment fn main ( @builtin ( position) pos : vec4< f32> ) { let tmp = scale ( pos . x , 0.5 ); if tmp > 1.0 { _ = textureSample ( t , s , pos . xy ); } }
注意:子图仅为便于理解而包含在示例中。
15.3. 计算着色器与工作组
工作组是一组调用,它们 并发执行计算着色器阶段入口点, 并共享对 workgroup 地址空间中着色器变量的访问。
计算着色器的工作组网格是 具有如下整数坐标 (i,j,k) 的点的集合:
-
0 ≤ i < workgroup_size_x
-
0 ≤ j < workgroup_size_y
-
0 ≤ k < workgroup_size_z
其中 (workgroup_size_x, workgroup_size_y, workgroup_size_z) 是 为入口点的 workgroup_size 属性指定的 值。
对于工作组网格中的每个点,工作组中恰好有一个调用。
调用的局部 调用 ID是该调用对应的工作组网格点的坐标 三元组 (i,j,k)。
当一个调用具有局部调用 ID 时,其 局部调用 索引为:
i + (j × workgroup_size_x) + (k × workgroup_size_x × workgroup_size_y)
注意,如果一个工作组有 W 个调用, 则该工作组中的每个调用 I 都有唯一的局部调用索引 L(I), 使得 0 ≤ L(I) < W, 并且整个范围都被覆盖。
当 WebGPU 实现从队列中 移除一个分派命令并开始在 GPU 上执行指定工作时,计算着色器开始执行。 分派命令指定一个分派大小, 它是一个整数三元组(group_count_x、group_count_y、group_count_z), 表示要执行的工作组数量,如下所述。
特定分派的计算着色器 网格是具有如下整数坐标 (CSi,CSj,CSk) 的点的集合:
-
0 ≤ CSi < workgroup_size_x × group_count_x
-
0 ≤ CSj < workgroup_size_y × group_count_y
-
0 ≤ CSk < workgroup_size_z × group_count_z
其中 workgroup_size_x、 workgroup_size_y 和 workgroup_size_z 如上所述,是计算着色器入口点的值。
计算着色器分派要执行的工作,是针对计算着色器网格中的每个点 恰好执行一次入口点调用。
调用的全局 调用 ID是该调用对应的计算着色器网格点的坐标 三元组。
调用被组织到工作组中,因此每个调用的 全局调用 ID (CSi, CSj, CSk) 会映射到 一个由工作组 ID标识的单个工作组:
( ⌊ CSi ÷ workgroup_size_x ⌋, ⌊ CSj ÷ workgroup_size_y ⌋, ⌊ CSk ÷ workgroup_size_z ⌋)
以及该工作组内由局部调用 ID标识的单个调用:
( CSi mod workgroup_size_x , CSj mod workgroup_size_y , CSk mod workgroup_size_z ).
注:工作组 ID 的范围从 (0,0,0) 到(group_count_x - 1,group_count_y - 1,group_count_z - 1)。
如果支持 linear_indexing 特性,则当一个 工作组具有工作组 ID (WGi, WGj, WGk) 时,其 工作组索引为:
WGi + ( WGj × group_count_x ) + ( WGk × group_count_x × group_count_y )
如果支持 linear_indexing 特性,则当一个 调用具有全局调用 ID (CSi, CSj, CSk) 时,其 全局调用 索引为:
CSi + ( CSj × workgroup_size_x × group_count_x ) + ( CSk × workgroup_size_x × group_count_x × workgroup_size_y × group_count_y )
WebGPU 不提供关于以下事项的保证:
-
来自不同工作组的调用是否并发执行。 也就是说,不能假设一次会执行多个工作组。
-
一旦来自某个工作组的调用开始执行,其他工作组是否 会被阻止执行。 也就是说,不能假设一次只执行一个工作组。 当一个工作组正在执行时,实现也可以选择 并发执行其他工作组,或执行其他已排队但未被阻塞的工作。
-
某个特定工作组中的调用是否会先于 另一个工作组中的调用开始执行。 也就是说,不能假设工作组按特定顺序启动。
15.4. 片元着色器与辅助调用
片元着色器阶段中的调用被划分为 X 和 Y 方向上具有相邻位置的 2x2 网格。 每个这样的网格称为一个quad(象限)。 quad 可在某些集体操作中协作(见§ 15.6.2 导数)。 某个调用的quad 调用 ID是在该 quad 内的唯一 ID,其中:
-
ID 0 是左上角调用。
-
ID 1 是右上角调用。
-
ID 2 是左下角调用。
-
ID 3 是右下角调用。
注意:没有用于 quad ID 的内置值访问器。
通常,片元处理为每个 RasterizationPoint 创建一个片元着色器调用,该 RasterizationPoint 由 光栅化生成。 有时,可能没有足够的 RasterizationPoint 填满一个 quad,例如在图元边界处。 当一个 quad 只有 1、2 或 3 个调用对应 RasterizationPoint 时,片元处理将为 quad 中每个未填充位置 创建一个辅助调用(helper invocation)。
辅助调用具有有限的可观察效果。 它们用于计算导数,也可能参与 子组操作。 因此,辅助调用受到如下限制:
-
不会对 storage 或 handle 地址空间执行任何写访问(另见 § 14.5.1 内存操作)。
-
入口点返回值将不会在 GPURenderPipeline 中进一步向下游处理。
如果 quad 中所有调用都变为辅助调用(例如因为执行了discard语句),quad 的执行可以终止;但这种终止不被认为产生非一致控制流。
15.5. 子组(Subgroups)
子组是一组 调用,它们并发 执行计算或片元着色器阶段入口点,并且可以高效地共享 数据并集体地计算结果。 计算或片元着色器中的每个调用都恰好属于一个子组。 在计算着色器中,每个子组都是特定工作组的子集。 在片元着色器中,子组可能包含来自多个绘制命令的调用。 每个四元组都将包含在单个 子组中。
子组大小(subgroup size)是子组中最大调用数。 在着色器中,可通过subgroup_size内置值访问。 子组大小在dispatch 命令以及工作组内为一致值,但在绘制命令内未必一致。 所有子组大小为 [4, 128] 范围内的 2 的幂,针对特定设备编译的着色器的值会落在该设备支持的 subgroupMinSize 与 subgroupMaxSize 之间(见 WebGPU § 4.3 GPUAdapter)。 实际大小取决于着色器、设备属性和设备编译器。 每个设备支持可能范围的一个子集(可能只有一个值)。 设备编译器会用多种启发式方法在支持范围内选择大小。 每个子组可能包含的调用数少于报告的子组大小(如实际发起的调用少于子组大小时)。
一次调用的子组调用
ID是在子组内唯一的 ID。
该 ID 可通过subgroup_invocation_id
内建值获取,范围为 [0, subgroup_size - 1]。
如果支持subgroup_id功能,则在计算着色器中,subgroup ID是工作组内的子组唯一 ID。 该 ID 可通过subgroup_id内建值获取, 范围为 [0, num_subgroups - 1]。
子组相关的值(如subgroup_invocation_id和subgroup_id)与local_invocation_index之间没有定义的关系。
为避免非可移植代码,着色器作者不应假定这两个值之间有特定的映射关系。
当同一子组内的调用执行不同的控制流路径时,称子组执行已发散(diverged)。 这是非一致控制流的一种特殊情况。 发散会影响子组操作的语义。 子组中并发执行子组操作的调用为该操作的活动调用。 其它为非活动调用。 当子组大小大于实际调用数时,多出来的假想调用被视为非活动。 辅助调用在某些操作中可以是活动或非活动的。 也就是说,在某些设备上辅助调用可能参与子组操作,另一些设备则不参与。
注意:在非一致控制流下,不同底层设备的表现差异很大,设备编译器常会对这类代码做激进优化。 结果是子组实际活动的调用集可能与着色器作者预期不同。
15.6. 集体操作
15.6.1. 屏障(Barrier)
屏障是一种同步内置函数, 它对程序中的内存操作进行排序。 控制屏障 由同一工作组中的所有调用执行, 就像它们是并发执行的一样。 因此,在计算着色器中,控制屏障必须只在一致控制流中执行。
15.6.2. 导数(Derivatives)
偏导数(partial derivative) 是某个值沿某一轴的变化率。 同一个quad内的片元着色器调用协作以计算近似的偏导数。
计算导数的内置函数包括:
片元坐标的偏导数在以下内置函数的操作过程中隐式计算:
对于这些函数,导数用于确定所采样纹素的 mip 级别;对于 textureSampleCompare,则用于采样并与参考值比较。
调用方指定值的偏导数由 § 17.6 导数内置函数中描述的内置函数计算:
-
dpdx、dpdxCoarse 及 dpdxFine 计算 x 轴方向的偏导数。
-
dpdy、dpdyCoarse 及 dpdyFine 计算 y 轴方向的偏导数。
-
fwidth、fwidthCoarse 及 fwidthFine 计算相关 x 与 y 方向偏导数的曼哈顿距离。
由于相邻调用协作计算导数,这些函数应只在片元着色器的一致控制流中调用。 若一次此类函数调用无法通过一致性分析证明其处于一致控制流,则触发derivative_uniformity诊断。
如果在非一致控制流中调用这些函数,则结果为不确定值。
注意:导数本质是一种quad 操作。 使用导数不需要subgroups扩展。
15.6.3. 子组操作(Subgroup Operations)
子组内置函数允许同一子组中的调用高效通信与计算。 子组操作为单指令多线程(SIMT)操作。
子组中的活动调用协作决定结果。 因此,当这些函数在所有调用都处于活动状态(即在子组级别一致控制流)时,可移植性最佳。
15.6.4. quad 操作(Quad Operations)
quad 内置函数在quad的调用之间操作。 它们用于 quad 内各调用间的数据通信。
quad 内的活动调用协作决定结果。 因此,当这些函数在所有调用都处于活动状态(即在 quad 级别一致控制流)时,可移植性最佳。
15.7. 浮点数运算
WGSL 的浮点特性基于IEEE-754 浮点标准, 但功能有所精简,反映了 GPU 的权衡,并增加了一些便于移植的安全措施。
15.7.1. IEEE-754 概述
WGSL 浮点类型基于IEEE-754 二进制浮点类型。
一种 IEEE-754 二进制浮点类型近似 扩展实数数轴如下:
-
该类型有有限集合的值,包括不同类别:
-
该类型支持的运算包括:
-
该类型的位表示的特性:
-
固定的位宽,每个值的位表示有三个连续的字段(从高位到低位):
-
1 位 符号字段。
-
定长 指数字段。
-
定长 尾数字段。
-
-
一个整数值的 指数偏移量,用于解析 指数字段。
-
浮点类型的有限范围是区间 [low, high], 其中 low 是该类型的最小有限值,high 是最大有限值。
IEEE-754 标准中主要关注的浮点类型有:
-
binary16:
-
binary32:
-
binary64:
下述算法将浮点值的位表示映射为其对应的扩展实数值或 NaN:
算法: 浮点位解释输入:Bits,二进制浮点类型的位表示。
输出: F,由Bits表示的浮点值。
过程:
令 bias 为该类型的 指数偏移量。
令 tsw 为该类型的 尾数字段的位宽。
令 Sign、E 和 T 分别为这些字段的无符号整数值。
如果指数字段全为 1,则:
若 Sign = 0 且 T = 0,则结果 F = +∞。
若 Sign = 1 且 T = 0,则结果 F = −∞。
若 T ≠ 0,则结果 F 为NaN。
否则,如果指数字段全为 0,则:
结果 F = (−1)Sign × 2−bias × T × 2−tsw+1。
若 T = 0,则该值为零。
每种浮点类型都有正零和负零。 负零是符号位为 1 的零值。 负零与正零比较相等。 IEEE-754 使用负零表示一些 WGSL 不关心的边界情况。
若 T ≠ 0,则 F 为非正规值。 (Denormalized 是 subnormal 的同义词。)
否则,指数字段既不是全 1 也不是全 0:
结果 F = (−1)Sign × 2(E−bias) × ( 1 + T × 2−tsw )。
此时 F 为正规值。
浮点运算的定义域为该运算在扩展实数输入中定义良好的集合。
-
例如,数学函数 √ 的定义域为 [0,+∞]:对小于零的输入 √ 没有良好定义。
-
在定义域外求值时,IEEE-754 默认的异常处理规则要求产生异常并返回NaN。 WGSL 不强制浮点异常,可能返回不确定值。见§ 15.7.2 与 IEEE-754 的差异。
舍入将扩展实数值x映射为浮点类型的值 x'。 若 x 已为浮点类型,则舍入后 x = x'。 若 x 超出该类型有限范围,舍入可能溢出。 否则 x' 是大于 x 的最小浮点值,或小于 x 的最大浮点值; 舍入模式决定如何选择。
一般来说,带有NaN输入的运算会产生NaN输出。 例外包括:
-
NaN 永远不等于、小于或大于任何其他浮点值。此类比较结果为 false。
IEEE-754 定义了五类异常(exceptions):
-
无效操作。 当运算在扩展实数输入超出定义域时发生。 结果为 NaN。 典型无效操作如 0 × +∞、
sqrt(−1) 等。 -
被零除。 当有限操作数的运算定义为精确无穷结果时发生。 例如 1 ÷ 0、log(0) 等。
-
溢出。当中间结果超出 有限范围时发生。见§ 15.7.3 浮点舍入与溢出。
-
不精确。当舍入结果与中间结果不同时,或发生溢出时发生。
15.7.2. 与 IEEE-754 的差异
WGSL 遵循IEEE-754标准,但有如下不同:
-
当将浮点值 x 转换为整数类型时, x 会先被裁剪到目标类型的取值范围。见§ 15.7.6 浮点数转换。
-
不会生成浮点异常。
-
实现可能不会生成 signaling NaN。 在中间计算中,任何 signaling NaN 可能会被转为 quiet NaN。
-
有限数学假设(Finite Math Assumption):
-
实现可以假定在 着色器执行 期间不存在溢出、无穷大和 NaN。
-
实现可忽略浮点零值的符号字段。 即正零可能表现如负零,反之亦然。
-
flush to zero 指将浮点类型的非正规值替换为同类型的零值。
-
§ 15.7.4 浮点数精度中列出的运算的输入或输出都可能被 flush to zero。
-
此外,§ 17.2 位重解释内置函数、§ 17.9 数据打包内置函数、或 § 17.10 数据解包内置函数中列举的运算的中间结果也可被 flush to zero。
-
其它运算要求保留非正规数。
-
-
各运算的精度见§ 15.7.4 浮点数精度。
-
WGSL 的部分内置函数与 IEEE-754 对应运算语义不同,具体见 WGSL 内置函数定义处。 例如 WGSL § 17.5.32 fma 可展开为普通的乘法(含一次舍入)和加法(再一次舍入), 而 IEEE-754 的
fusedMultiplyAdd要求只做一次最终舍入。
15.7.3. 浮点数舍入与溢出
溢出运算可以舍入为无穷大,也可以舍入为最近的有限值。 最终结果取决于溢出中间结果的大小,以及是在着色器模块创建、管线创建还是着色器执行期间求值。
对于浮点类型 T,定义 MAX(T) 为 T 的最大正有限值, 2EMAX(T) 为 T 可表示的最大 2 的幂。 特别地,EMAX(f32) = 127,EMAX(f16) = 15。
设 X 为浮点运算产生的无限精度中间结果。 表达式的最终值通过两个阶段、两个中间结果 X' 和 X'' 确定:
对于 X,用 T 类型的舍入得到 X':
-
若 X 在 有限范围内,则 X' 是对 X 的向上或向下舍入结果。
-
若 X 为 NaN,则 X' 也为 NaN。
-
若 MAX(T) < X < 2EMAX(T)+1,则舍入方向任选:X' 可为 MAX(T) 或 +∞。
-
若 2EMAX(T)+1 ≤ X,则 X' = +∞。
-
注意:本条款与IEEE-754一致。
-
-
若 −MAX(T) > X > −2EMAX(T)+1,则舍入方向任选:X' 可为 −MAX(T) 或 −∞。
-
若 −2EMAX(T)+1 ≥ X,则 X' = −∞。
-
注意:本条款与 IEEE-754 一致。
-
对 X',计算表达式最终值 X'',或检测程序错误:
-
若 X' 为无穷或 NaN,则根据有限数学假设:
-
否则 X'' = X'。
15.7.4. 浮点数精度
-
若 x 在 T 中,则为 x,
-
否则:
-
为 T 中大于 x 的最小值,或
-
为 T 中小于 x 的最大值。
-
注意: 浮点类型包括正无穷和负无穷,因此正确舍入的结果可能是有限值也可能是无穷。
注意: 用无限精度计算操作的结果,可能需要超过 double 精度的精度。
例如 x - y,其中 x=1.0 且 y=1.17e-38(最小正正规单精度 float)。
这两个数的指数相差 126。IEEE-754 的
binary64(double
precision)只有 52 位尾数,
做减法时 y 的所有有效位都丢失。
根据舍入模式,对于这种以及许多 y 很小但非零的情况,WGSL 表达式 x - y 可能得到和 x 相同的值。
注意 [ECMASCRIPT] 等价于 IEEE-754 的
roundTiesToEven 舍入模式。
最后一位单位(unit in the last place, ULP)的定义如下 [Muller2005]:
-
若
x在该浮点类型的有限范围内,则 ULP(x) 为 满足a≤x≤b的两个不相等有限浮点数a和b之间的最小距离(即ulp(x) = mina,b|b - a|)。 -
否则,ULP(x) 为
|b - a|,其中b和a分别是最大和次大可表示有限浮点数。
某操作的精度有以下五种可能:
-
正确结果(对于非浮点结果值)。
-
正确舍入。
-
绝对误差界限。
-
以ULP 表示的相对误差界限。
-
表达式的精度继承自其它表达式。 即,该操作的精度由给定 WGSL 表达式的精度定义。 给定表达式只是该函数的一个有效实现。
对继承表达式求值时,子表达式求值受浮点运算其它规则约束,包括 舍入、 溢出、 重结合、 融合、 以及 flush-to-zero 等规则影响。
WebGPU 实现可以用更高精度或极端输入容忍度更高的方式实现该操作。
当操作的精度只对某输入范围规定时,超出该范围的输入精度未定义。
若允许的结果超出结果类型的有限范围,则适用 § 15.7.3 浮点数舍入与溢出的规则。
15.7.4.1. 具体浮点表达式的精度
| 表达式 | f32 精度 | f16 精度 |
|---|---|---|
x + y
| 正确舍入 | |
x - y
| 正确舍入 | |
x * y
| 正确舍入 | |
x / y
| 2.5 ULP,|y| 范围 [2-126, 2126]
| 2.5 ULP,|y| 范围 [2-14, 214]
|
x % y
| 继承自 x - y * trunc(x/y)
| |
-x
| 正确舍入 | |
x == y
| 正确结果 | |
x != y
| 正确结果 | |
x < y
| 正确结果 | |
x <= y
| 正确结果 | |
x > y
| 正确结果 | |
x >= y
| 正确结果 | |
| 内置函数 | f32 精度 | f16 精度 |
|---|---|---|
abs(x)
| 正确舍入 | |
acos(x)
|
以下两者较差者:
|
以下两者较差者:
|
acosh(x)
| 继承自 log(x + sqrt(x * x - 1.0))
| |
asin(x)
|
以下两者较差者:
|
以下两者较差者:
|
asinh(x)
| 继承自 log(x + sqrt(x * x + 1.0))
| |
atan(x)
| 4096 ULP | 5 ULP |
atan2(y, x)
| 4096 ULP,|x| 范围 [2-126, 2126],且 y 有限且正规
| 5 ULP,|x| 范围 [2-14, 214],且 y 有限且正规
|
atanh(x)
| 继承自
log( (1.0 + x) / (1.0 - x) ) * 0.5
| |
ceil(x)
| 正确舍入 | |
clamp(x,low,high)
|
正确舍入。
无限精度结果可按 若 | |
cos(x)
| 当 x 在区间 [-π, π] 时,绝对误差最大为 2-11
| 当 x 在区间 [-π, π] 时,绝对误差最大为 2-7
|
cosh(x)
| 继承自 (exp(x) + exp(-x)) * 0.5
| |
cross(x, x)
| 继承自 (x[i] * y[j] - x[j] * y[i]),其中
i ≠ j
| |
degrees(x)
| 继承自 x * 57.295779513082322865
| |
determinant(m:mat2x2<T>)determinant(m:mat3x3<T>)determinant(m:mat4x4<T>)
|
无限 ULP。
注意:WebGPU 实现应提供实际可用的行列式函数。
理想数学中,行列式用加、减、乘运算计算。 但 GPU 使用浮点运算,GPU 上的行列式实现更偏向速度和简单性,而非溢出和误差鲁棒性。 例如,2x2 行列式的朴素计算
( WGSL 没有行列式的有限误差界限,反映了底层实现也没有。 | |
distance(x, y)
| 继承自 length(x - y)
| |
dot(x, y)
| 继承自 x[i] * y[i] 的和
| |
dpdx(x)dpdxCoarse(x)dpdxFine(x)dpdy(x)dpdyCoarse(x)dpdyFine(x)fwidth(x)fwidthCoarse(x)fwidthFine(x) |
无限 ULP。
注意:WebGPU 实现应提供实际可用的导数函数。
导数在 GPU 上实现为不同调用间的差值( WGSL 没有导数的有限误差界限,反映了底层实现也没有。 | |
exp(x)
| 3 + 2 * |x| ULP
| 1 + 2 * |x| ULP
|
exp2(x)
| 3 + 2 * |x| ULP
| 1 + 2 * |x| ULP
|
faceForward(x, y, z)
| 继承自
select(-x, x, dot(z, y) < 0.0)
| |
floor(x)
| 正确舍入 | |
fma(x, y, z)
| 继承自 x * y + z
| |
fract(x)
| 继承自 x - floor(x)
| |
frexp(x)
| x 为零或正规数时正确舍入
| |
inverseSqrt(x)
| 2 ULP | |
ldexp(x, y)
| 正确舍入 | |
length(x)
| 向量情况继承自 sqrt(dot(x, x)),标量情况继承自
sqrt(x*x)
| |
log(x)
| 当 x 在 [0.5, 2.0] 区间内,绝对误差最大 2-21。x 不在 [0.5, 2.0] 区间时,最大 3 ULP。 | 当 x 在 [0.5, 2.0] 区间内,绝对误差最大 2-7。x 不在 [0.5, 2.0] 区间时,最大 3 ULP。 |
log2(x)
| 当 x 在 [0.5, 2.0] 区间内,绝对误差最大 2-21。x 不在 [0.5, 2.0] 区间时,最大 3 ULP。 | 当 x 在 [0.5, 2.0] 区间内,绝对误差最大 2-7。x 不在 [0.5, 2.0] 区间时,最大 3 ULP。 |
max(x, y)
|
正确舍入
若 | |
min(x, y)
|
正确舍入。
若 | |
mix(x, y, z)
| 继承自 x * (1.0 - z) + y * z
| |
modf(x)
| 正确舍入 | |
normalize(x)
| 继承自 x / length(x)
| |
pack4x8snorm(x)
| 正确舍入的中间结果值。正确结果。 | |
pack4x8unorm(x)
| 正确舍入的中间结果值。正确结果。 | |
pack2x16snorm(x)
| 正确舍入的中间结果值。正确结果。 | |
pack2x16unorm(x)
| 正确舍入的中间结果值。正确结果。 | |
pack2x16float(x)
| 正确舍入的中间结果值。正确结果。 | |
pow(x, y)
| 继承自 exp2(y * log2(x))
| |
quantizeToF16(x)
| 正确舍入 | |
radians(x)
| 继承自 x * 0.017453292519943295474
| |
reflect(x, y)
| 继承自 x - 2.0 * dot(x, y) * y
| |
refract(x, y, z)
| 继承自
z * x - (z * dot(y, x) + sqrt(k)) * y,其中 k = 1.0 - z * z * (1.0 - dot(y, x) * dot(y, x))若 k < 0.0 结果精确为
0.0
| |
round(x)
| 正确舍入 | |
sign(x)
| 正确舍入 | |
sin(x)
| 当 x 在 [-π, π] 区间,绝对误差最大 2-11
| 当 x 在 [-π, π] 区间,绝对误差最大 2-7
|
sinh(x)
| 继承自 (exp(x) - exp(-x)) * 0.5
| |
saturate(x)
| 正确舍入 | |
smoothstep(edge0, edge1, x)
| 继承自
t * t * (3.0 - 2.0 * t),其中 t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0)
| |
sqrt(x)
| 继承自 1.0 / inverseSqrt(x)
| |
step(edge, x)
| 正确舍入 | |
tan(x)
| 继承自 sin(x) / cos(x)
| |
tanh(x)
|
取以下两者中较差者:
| |
transpose(x)
| 正确舍入 | |
trunc(x)
| 正确舍入 | |
unpack4x8snorm(x)
| 3 ULP | N/A |
unpack4x8unorm(x)
| 3 ULP | N/A |
unpack2x16snorm(x)
| 3 ULP | N/A |
unpack2x16unorm(x)
| 3 ULP | N/A |
unpack2x16float(x)
| 正确舍入 | N/A |
subgroupBroadcast(x, i)
| 正确舍入 | |
subgroupBroadcastFirst(x)
| 正确舍入 | |
subgroupAdd(x)
| 继承自子组内所有活动调用的 x 求和 | |
subgroupExclusiveAdd(x)
| 继承自子组内所有活动调用中 子组调用 ID 小于当前调用的 x 求和 | |
subgroupInclusiveAdd(x)
| 继承自子组内所有活动调用中 子组调用 ID 小于等于当前调用的 x 求和 | |
subgroupMul(x)
| 继承自子组内所有活动调用的 x 连乘 | |
subgroupExclusiveMul(x)
| 继承自子组内所有活动调用中 子组调用 ID 小于 i 的 x 连乘 | |
subgroupInclusiveMul(x)
| 继承自子组内所有活动调用中 子组调用 ID 小于等于 i 的 x 连乘 | |
subgroupMax(x)
| 继承自子组内所有活动调用的 max(x) | |
subgroupMin(x)
| 继承自子组内所有活动调用的 min(x) | |
subgroupShuffle(x, id)
| 正确舍入 | |
subgroupShuffleDown(x, delta)
| 正确舍入 | |
subgroupShuffleUp(x, delta)
| 正确舍入 | |
subgroupShuffleXor(x, mask)
| 正确舍入 | |
quadBroadcast(x, id)
| 正确舍入 | |
quadSwapDiagonal(x)
| 正确舍入 | |
quadSwapX(x)
| 正确舍入 | |
quadSwapY(x)
| 正确舍入 | |
15.7.4.2. AbstractFloat 表达式的精度
AbstractFloat 运算的精度如下:
-
当对应的 f32 运算要求正确结果时,AbstractFloat 运算也要求正确结果。
-
fract(x)的误差继承自x - floor(x),其中中间计算以 AbstractFloat 运算执行。 -
否则,对应的 f32 运算的误差为绝对误差、相对误差、继承自某种实现的误差,或它们的组合。 此时 AbstractFloat 的误差是不定的(unbounded)。
-
但 AbstractFloat 运算的误差应当绝对值上不大于对应 f32 运算的误差。
-
这样推荐是为了避免意外:表达式从 f32 类型变为 AbstractFloat 类型时精度不应降低。
-
运算可能在 WebAssembly [WASM-CORE-2] 或 ECMAScript [ECMASCRIPT] 环境中执行,这些规范没有对很多对应数值运算规定误差界限。 例如,ECMAScript 规定许多数值运算为 implementation-approximated。 鼓励实现逼近理想值,但没有严格要求。
-
ULP 对 AbstractFloat 值的定义,假设 AbstractFloat 等价于 IEEE-754 的 binary64 类型。
对于 f32 值,1 个 ULP 大小是 IEEE-754 binary64 值 1 ULP 的 229 倍, 因为 binary64 尾数比 f32 多 29 位。
例如,假设某运算真实值为 x,实际计算为 x'。 若其误差 x-x' 在 f32 中为 3 ULP,则同样绝对误差 x-x' 在 AbstractFloat 中为 3·229 ULP。
15.7.5. 重结合与融合
重结合(reassociation)指在表达式中重新排列运算顺序,使得精确计算时答案不变。例如:
-
(a + b) + c可重结合为a + (b + c) -
(a - b) + c可重结合为(a + c) - b -
(a * b) / c可重结合为(a / c) * b
但用浮点数计算时结果可能不同。 重结合后的结果可能因近似而不准确,也可能因中间结果溢出或变为 NaN。
实现可以重结合运算。
实现还可以融合运算,只要变换后的表达式精度不低于原表达式。 例如,某些融合乘加(fused multiply-add)的实现比单独乘法再加法更精确。
15.7.6. 浮点数转换
本节描述标量转换中,源或目标为浮点类型的细节。
本节中,浮点类型可以是:
-
WGSL 中的 f32、f16 和 AbstractFloat 类型。
-
与 IEEE-754 浮点标准定义的二进制格式对应的假想类型。
注意: 回顾:f32 WGSL 类型对应 IEEE-754 的 binary32,f16 对应 binary16。
浮点数到整数标量转换算法如下:
将浮点标量值 X 转换为 整数标量类型 T 的步骤:
注意: 换言之,非 NaN 情况下,浮点转整数先裁剪到目标类型范围,然后向零舍入。 这种裁剪要求是 WGSL 明确规定的,而在 C/C++ 下会导致未定义行为, IEEE-754 则会产生无效操作异常和 NaN。
数值标量转浮点算法如下:
算法: 数值标量转换为浮点输入:
X,类型为 S 的数值标量
T,目标浮点类型。
输出: XOut,即 X 转为 T 类型的结果,或产生错误。
过程:
若 X 是源类型 S 的 NaN,则 XOut 为 T 类型的 NaN。
若 X 可在目标类型 T 精确表示,则 XOut 即为等于 X 的 T 类型值。
否则,X 不能在 T 精确表示:
注意: 某些整数值可能介于两个相邻可表示浮点值之间。 特别是,f32 类型有 23 位显式小数位。 且当浮点值在正规范围时(指数不极端),尾数为所有小数位加最高位(第 23 位)上的 1。 例如,整数 228 和 1+228 都映射到同一浮点值:最末 1 位的差异无法表示。 这种碰撞发生在绝对值至少 225 的相邻整数对之间。
注意: 当原类型为 i32 或 u32,目标类型为 f32 时,原值始终在目标类型范围内。
注意: 当源类型为指数和尾数位数小于目标类型的浮点类型时,原值也始终在目标类型范围内。
15.7.7. 浮点表达式和内置函数的定义域
前述章节描述了在浮点表达式被求值于其定义域之外时的预期行为。
§ 8.7 算术表达式 和 § 17.5 数值内置函数分别定义了浮点表达式和内置函数的定义域。 若某操作未列出限制,则其定义域为全体:包括所有有限与无穷输入。 否则会明确列出定义域。
在许多情况下,WGSL 操作与 IEEE-754 定义的操作对应时,二者的定义域一致。
例如 WGSL 和 IEEE-754 的 acos 操作定义域都是 [−1,1]。
对于显式列出定义域的分量操作 WGSL 操作,仅描述了标量情形。向量情形由分量语义推导。
某些 WGSL 操作可能会用其它 WGSL 表达式实现。 § 15.7.4 浮点数精度中,对这类操作列为继承自的精度。 当列出此类操作的定义域时,有两种情况:
-
明确定义域,或
-
定义域被标注为隐含自线性项,即定义域由如下方式推导:
-
假设原操作被“继承自”表达式替换,该表达式由浮点加、减、乘组合。
-
对这些基本操作在给定参数上的定义域限制进行组合。
-
例如:dot(a,b) 函数对 2 元向量 a 和 b 的精度继承自表达式
a[0] * b[0] + a[1] * b[1]。
这用到两次浮点乘法,一次浮点加法。
-
浮点乘法在扩展实数上定义良好,除非一操作数为零,另一为无穷。
-
浮点加法除两个操作数为相反号无穷时定义良好。
-
因此,定义域为所有扩展实数的 2 元向量对 a 和 b,但排除:
-
由乘法隐含:
-
a[i] 为零且 b[i] 为无穷。
-
a[i] 为无穷且 b[i] 为零。
-
-
由加法隐含:
-
a[0] × b[0] 为 +∞ 且 a[1] × b[1] 为 +∞
-
a[0] × b[0] 为 −∞ 且 a[1] × b[1] 为 −∞
-
-
16. 关键字与记号摘要
16.1. 关键字摘要
-
alias -
break -
case -
const -
const_assert -
continue -
continuing -
default -
diagnostic -
discard -
else -
enable -
false -
fn -
for -
if -
let -
loop -
override -
requires -
return -
struct -
switch -
true -
var -
while
16.2. 保留字
保留字是一种记号,为将来使用保留。 WGSL 模块不得包含保留字。
以下为保留字:
| 'NULL'
| 'Self'
| 'abstract'
| 'active'
| 'alignas'
| 'alignof'
| 'as'
| 'asm'
| 'asm_fragment'
| 'async'
| 'attribute'
| 'auto'
| 'await'
| 'become'
| 'cast'
| 'catch'
| 'class'
| 'co_await'
| 'co_return'
| 'co_yield'
| 'coherent'
| 'column_major'
| 'common'
| 'compile'
| 'compile_fragment'
| 'concept'
| 'const_cast'
| 'consteval'
| 'constexpr'
| 'constinit'
| 'crate'
| 'debugger'
| 'decltype'
| 'delete'
| 'demote'
| 'demote_to_helper'
| 'do'
| 'dynamic_cast'
| 'enum'
| 'explicit'
| 'export'
| 'extends'
| 'extern'
| 'external'
| 'fallthrough'
| 'filter'
| 'final'
| 'finally'
| 'friend'
| 'from'
| 'fxgroup'
| 'get'
| 'goto'
| 'groupshared'
| 'highp'
| 'impl'
| 'implements'
| 'import'
| 'inline'
| 'instanceof'
| 'interface'
| 'layout'
| 'lowp'
| 'macro'
| 'macro_rules'
| 'match'
| 'mediump'
| 'meta'
| 'mod'
| 'module'
| 'move'
| 'mut'
| 'mutable'
| 'namespace'
| 'new'
| 'nil'
| 'noexcept'
| 'noinline'
| 'nointerpolation'
| 'non_coherent'
| 'noncoherent'
| 'noperspective'
| 'null'
| 'nullptr'
| 'of'
| 'operator'
| 'package'
| 'packoffset'
| 'partition'
| 'pass'
| 'patch'
| 'pixelfragment'
| 'precise'
| 'precision'
| 'premerge'
| 'priv'
| 'protected'
| 'pub'
| 'public'
| 'readonly'
| 'ref'
| 'regardless'
| 'register'
| 'reinterpret_cast'
| 'require'
| 'resource'
| 'restrict'
| 'self'
| 'set'
| 'shared'
| 'sizeof'
| 'smooth'
| 'snorm'
| 'static'
| 'static_assert'
| 'static_cast'
| 'std'
| 'subroutine'
| 'super'
| 'target'
| 'template'
| 'this'
| 'thread_local'
| 'throw'
| 'trait'
| 'try'
| 'type'
| 'typedef'
| 'typeid'
| 'typename'
| 'typeof'
| 'union'
| 'unless'
| 'unorm'
| 'unsafe'
| 'unsized'
| 'use'
| 'using'
| 'varying'
| 'virtual'
| 'volatile'
| 'wgsl'
| 'where'
| 'with'
| 'writeonly'
| 'yield'
16.3. 语法记号
语法记号是一串特殊码点,用于:
-
拼写表达式运算符,或
-
作为标点:用于分组、排列或分隔其它语法元素。
语法记号包括:
-
'&'(码点:U+0026) -
'&&'(码点:U+0026U+0026) -
'->'(码点:U+002DU+003E) -
'@'(码点:U+0040) -
'/'(码点:U+002F) -
'!'(码点:U+0021) -
'['(码点:U+005B) -
']'(码点:U+005D) -
'{'(码点:U+007B) -
'}'(码点:U+007D) -
':'(码点:U+003A) -
','(码点:U+002C) -
'='(码点:U+003D) -
'=='(码点:U+003DU+003D) -
'!='(码点:U+0021U+003D) -
'>'(码点:U+003E)(也有_greater_than用于模板消歧) -
'>='(码点:U+003EU+003D)(也有_greater_than_equal用于模板消歧) -
'>>'(码点:U+003EU+003E)(也有_shift_right用于模板消歧) -
'<'(码点:U+003C)(也有_less_than用于模板消歧) -
'<='(码点:U+003CU+003D)(也有_less_than_equal用于模板消歧) -
'<<'(码点:U+003CU+003C)(也有_shift_left用于模板消歧) -
'%'(码点:U+0025) -
'-'(码点:U+002D) -
'--'(码点:U+002DU+002D) -
'.'(码点:U+002E) -
'+'(码点:U+002B) -
'++'(码点:U+002BU+002B) -
'|'(码点:U+007C) -
'||'(码点:U+007CU+007C) -
'('(码点:U+0028) -
')'(码点:U+0029) -
';'(码点:U+003B) -
'*'(码点:U+002A) -
'~'(码点:U+007E) -
'_'(码点:U+005F) -
'^'(码点:U+005E) -
'+='(码点:U+002BU+003D) -
'-='(码点:U+002DU+003D) -
'*='(码点:U+002AU+003D) -
'/='(码点:U+002FU+003D) -
'%='(码点:U+0025U+003D) -
'&='(码点:U+0026U+003D) -
'|='(码点:U+007CU+003D) -
'^='(码点:U+005EU+003D) -
'>>='(码点:U+003EU+003EU+003D)(也有_shift_right_assign用于模板消歧) -
'<<='(码点:U+003CU+003CU+003D)(也有_shift_left_assign用于模板消歧) -
_template_args_end-
文本:
'>'(码点:U+003E) -
该记号在文本上与greater_than 语法记号相同。
-
该记号由模板列表消歧生成,用作模板列表的最后一个记号。
-
-
_template_args_start-
文本:
'<'(码点:U+003C) -
该记号在文本上与less_than 语法记号相同。
-
该记号由模板列表消歧生成,用作模板列表的第一个记号。
-
-
_disambiguate_template
17. 内置函数
某些函数是预声明的,由实现提供, 因此在 WGSL 模块中总是可用。 这些被称为内置函数。
一个内置函数是一组同名的函数, 但通过其形式参数的数量、顺序和类型加以区分。 这些不同的变体称为重载。
每个重载如下描述:
调用内置函数时,所有参数都会在函数求值开始前先被求值。 见 § 11.2 函数调用。
17.1. 构造器内置函数
值构造器内置函数用于显式创建某种类型的值。
WGSL 为所有预声明类型和所有 可构造结构类型提供值构造器。 这样的构造器内置函数与类型具有相同的拼写,或者与该类型的类型别名具有相同的拼写。 无论在何处使用这样的内置函数,该标识符 必须在该类型或类型 别名的作用域内,并且该标识符 不得解析为另一个声明。
注意:由 frexp、 modf 和 atomicCompareExchangeWeak 返回的结构体类型不能在 WGSL 模块中书写。
注意:类型的值声明需要在 WGSL 文本的该语句处有效。
WGSL 提供两类值构造器:
17.1.1. 零值内置函数
每个具体、可构造的 T 都有
一个唯一的零值,
以及一个对应的内置函数,在 WGSL 中写作类型后跟一对空括号:
T ()。
抽象数值
类型也有零值,但没有用于访问它们的内置函数。
零值如下:
-
bool()是false -
i32()是 0i -
u32()是 0u -
f32()是 0.0f -
f16()是 0.0h -
类型为 T 的 N 分量向量的零值,是由 T 的零值组成的 N 分量向量。
-
类型为 T 的 C 列 R 行矩阵的零值,是 这些维度中填充了 T 的零值的矩阵。
-
元素类型为 E 的可构造 N 元素数组的零值, 是由 N 个 E 的零值元素组成的数组。
-
可构造结构类型 S 的零值,是 具有零值成员的结构值 S。
-
AbstractInt 的零值是 0。
-
AbstractFloat 的零值是 0.0。
注:WGSL 没有用于原子类型、 运行时大小数组,或其他 非可构造类型的零值内置函数。
| 重载 |
|
| 参数化 | T 是一个具体可构造类型。 |
| 描述 | 构造类型 T 的零值。
|
注:由 AbstractInt 填充零的向量可以写作
vec2()、vec3() 和 vec4()。
vec2< f32> () // 两个 f32 分量的零值向量。 vec2< f32> ( 0.0 , 0.0 ) // 显式写出的相同值。 vec3< i32> () // 三个 i32 分量的零值向量。 vec3< i32> ( 0 , 0 , 0 ) // 显式写出的相同值。
struct Student { grade : i32, GPA : f32, attendance : array< bool, 4 > } fn func () { var s : Student ; // Student 的零值 s = Student (); // 显式写出的相同值。 s = Student ( 0 , 0.0 , array< bool, 4 > ( false , false , false , false )); // 使用零值成员写出的相同值。 s = Student ( i32(), f32(), array< bool, 4 > ()); }
17.1.2. 值构造器内置函数
下列子章节定义的内置函数通过以下方式创建可构造值:
-
复制同类型的已有值(即恒等函数),或
-
通过显式的分量列表创建复合值。
-
从其它类型值进行转换。
向量和矩阵形式可从各种分量和具备相同分量类型的子向量组合构造向量和矩阵值。 构造向量和矩阵的重载,可指定目标类型的维度而无需指定分量类型;分量类型由构造器实参推断。
17.1.2.1. array
| 重载 |
|
| 参数化 | T 是具体且可构造的
|
| 描述 | 从元素构造数组。 |
| 重载 |
|
| 参数化 | T 是可构造的
|
| 描述 |
从元素构造数组。
组件类型从元素的类型推断。 数组的大小由元素数量决定。 |
17.1.2.2. bool
| 重载 |
|
| 参数化 | T 为标量
类型。
|
| 描述 |
构造一个bool值。
若 |
17.1.2.3. f16
| 重载 |
|
| 参数化 | T 为标量类型
|
| 描述 |
构造一个f16值。
若 |
17.1.2.4. f32
| 重载 |
|
| 参数化 | T 为具体 标量类型
|
| 描述 |
构造一个f32值。
如果 |
17.1.2.5. i32
| 重载 |
|
| 参数化 | T 是标量类型
|
| 描述 |
构造一个 i32 值。
如果 |
17.1.2.6. mat2x2
| 重载 |
|
| 参数化 | T 为f16或f32S 为AbstractFloat、f16或f32
|
| 描述 |
构造 2x2 列主序矩阵。
若 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 | 从列向量构造 2x2 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 |
从元素构造 2x2 列主序矩阵。
等价于 mat2x2(vec2(e1,e2), vec2(e3,e4))。 |
17.1.2.7. mat2x3
| 重载 |
|
| 参数化 | T 为f16或f32S 为AbstractFloat、f16或f32
|
| 描述 |
构造一个 2x3 列主序矩阵。
如果 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 | 从列向量构造 2x3 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 |
从元素构造 2x3 列主序矩阵。
等价于 mat2x3(vec3(e1,e2,e3), vec3(e4,e5,e6))。 |
17.1.2.8. mat2x4
| 重载 |
|
| 参数化 | T 为f16或f32S 为AbstractFloat、f16或f32
|
| 描述 |
构造一个 2x4 列主序矩阵。
如果 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 | 从列向量构造 2x4 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 |
从元素构造 2x4 列主序矩阵。
等价于 mat2x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8))。 |
17.1.2.9. mat3x2
| 重载 |
|
| 参数化 | T 为f16或f32S 为AbstractFloat、f16或f32
|
| 描述 |
构造一个 3x2 列主序矩阵。
如果 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 | 从列向量构造 3x2 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 |
从元素构造 3x2 列主序矩阵。
等价于 mat3x2(vec2(e1,e2), vec2(e3,e4), vec2(e5,e6))。 |
17.1.2.10.
mat3x3
| 重载 |
|
| 参数化 | T 为f16或f32S 为AbstractFloat、f16或f32
|
| 描述 |
构造一个 3x3 列主序矩阵。
如果 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 | 从列向量构造 3x3 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 |
从元素构造 3x3 列主序矩阵。
等价于 mat3x3(vec3(e1,e2,e3), vec3(e4,e5,e6), vec3(e7,e8,e9))。 |
17.1.2.11.
mat3x4
| 重载 |
|
| 参数化 | T 为f16或f32S 为AbstractFloat、f16或f32
|
| 描述 |
构造一个 3x4 列主序矩阵。
如果 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 | 从列向量构造 3x4 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 为AbstractFloat、f16或f32
|
| 描述 |
从元素构造 3x4 列主序矩阵。
等价于 mat3x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8), vec4(e9,e10,e11,e12))。 |
17.1.2.12.
mat4x2
| 重载 |
|
| 参数化 | T 是 f16 或 f32S 是 AbstractFloat、f16 或 f32
|
| 描述 |
构造一个4x2列主序矩阵。
如果 |
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f16 或 f32
|
| 描述 | 从列向量构造一个4x2列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f16 或 f32
|
| 描述 |
从元素构造一个4x2列主序矩阵。
等价于 mat4x2(vec2(e1,e2), vec2(e3,e4), vec2(e5,e6), vec2(e7,e8))。 |
17.1.2.13.
mat4x3
| 重载 |
|
| 参数化 | T 是 f16 或 f32S 是 AbstractFloat、f16 或 f32
|
| 描述 |
构造一个4x3列主序矩阵。
如果 |
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f16 或 f32
|
| 描述 | 从列向量构造一个4x3列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f16 或 f32
|
| 描述 |
从元素构造一个4x3列主序矩阵。
等价于 mat4x3(vec3(e1,e2,e3), vec3(e4,e5,e6), vec3(e7,e8,e9), vec3(e10,e11,e12))。 |
17.1.2.14.
mat4x4
| 重载 |
|
| 参数化 | T 是 f16 或 f32S 是 AbstractFloat、f16 或 f32
|
| 描述 |
构造一个4x4列主序矩阵。
如果 |
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f16 或 f32
|
| 描述 | 从列向量构造一个4x4列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f16 或 f32
|
| 描述 |
从元素构造一个4x4列主序矩阵。
等价于 mat4x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8), vec4(e9,e10,e11,e12), vec4(e13,e14,e15,e16))。 |
17.1.2.15. 结构体
| 重载 |
|
| 参数化 | S 是一个可构造结构体类型,其成员类型为 T1 ... TN。
|
| 描述 | 从成员构造类型为 S 的结构体。
|
17.1.2.16. u32
| 重载 |
|
| 参数化 | T 是标量类型
|
| 描述 |
构造一个 u32 值。
如果 |
|
注:来自 AbstractInt
的重载存在,是为了让诸如 |
17.1.2.17. vec2
| 重载 |
|
| 参数化 | T 是具体标量S 是标量
|
| 描述 | 构造一个以 e 作为两个分量的二维向量。
|
| 重载 |
|
| 参数化 | T 是具体标量S 是标量
|
| 描述 |
分量方式构造二维向量,分量为 e.x 和
e.y。
如果 |
| 重载 |
|
| 参数化 | T 是标量
|
| 描述 | 分量方式构造二维向量,分量为 e1 和
e2。
|
| 重载 |
|
| 参数化 | T 是 AbstractInt
|
| 描述 | 返回值 vec2(0,0)。
|
17.1.2.18. vec3
| 重载 |
|
| 参数化 | T 是一个 具体 标量S 是 标量
|
| 描述 | 构造一个所有分量均为 e 的三分量向量。
|
| 重载 |
|
| 参数化 | T 是一个 具体 标量S 是 标量
|
| 描述 |
分量方式
构造一个三分量向量,其分量分别为
e.x、e.y 和
e.z。
如果 |
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个三分量向量,其分量为
e1、e2 和 e3。
|
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个三分量向量,其分量为
v1.x、v1.y 和
e1。
|
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个三分量向量,其分量为
e1、v1.x 和
v1.y。
|
| 重载 |
|
| 参数化 | T 是 AbstractInt
|
| 描述 | 返回值为 vec3(0,0,0)。
|
17.1.2.19. vec4
| 重载 |
|
| 参数化 | T 是一个 具体 标量S 是 标量
|
| 描述 | 构造一个所有分量均为 e 的四分量向量。
|
| 重载 |
|
| 参数化 | T 是一个 具体 标量S 是 标量
|
| 描述 |
分量方式
构造一个四分量向量,其分量分别为
e.x、e.y、e.z 和 e.w。
如果 |
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个四分量向量,其分量为
e1、e2、e3 和 e4。
|
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个四分量向量,其分量为
e1、v1.x、v1.y 和 e2。
|
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个四分量向量,其分量为
e1、e2、v1.x 和 v1.y。
|
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个四分量向量,其分量为
v1.x、v1.y、v2.x 和 v2.y。
|
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个四分量向量,其分量为
v1.x、v1.y、e1 和 e2。
|
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个四分量向量,其分量为
v1.x、v1.y、v1.z 和 e1。
|
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
构造一个四分量向量,其分量为
e1、v1.x、v1.y 和 v1.z。
|
| 重载 |
|
| 参数化 | T 是 AbstractInt
|
| 描述 | 返回值为 vec4(0,0,0,0)。
|
17.2. 位重解释内置函数
17.2.1. bitcast
bitcast 内置函数用于将一个类型的值的位表示重新解释为另一个类型的值。
内部布局规则见 § 14.4.4 值的内部布局。
| 重载 |
|
| 参数化 | T 是具体数值标量或具体数值向量
|
| 描述 | 恒等变换。 当 T 是向量时,按分量
进行。结果为 e。
|
| 重载 |
|
| 参数化 | S 是 i32、u32 或 f32T 不是 S,并且是 i32、u32 或 f32
|
| 描述 | 将位重新解释为 T。结果是将 e 中的位重新解释为 T 值。
|
| 重载 |
|
| 参数化 | S 是 i32、u32 或 f32T 不是 S,并且是 i32、u32 或 f32
|
| 描述 | 将位按分量
重新解释为 T。结果是将 e 中的位重新解释为 vecN<T> 值。
|
| 重载 |
|
| 参数化 | |
| 描述 |
如果 e 可以表示为 u32,则为恒等操作,
否则会产生着色器创建错误。
也就是说,产生与 u32(e) 相同的结果。
当 |
| 重载 |
|
| 参数化 | T 是 i32、u32 或 f32
|
| 描述 | 将位按分量
重新解释为 T。结果是按照内部布局规则,将 e 中的 32 位重新解释为 T 值。
|
| 重载 |
|
| 参数化 | T 是 i32、u32 或 f32 |
| 描述 | 将位按分量
重新解释为 T。结果是按照内部布局规则,将 e 中的 64 位重新解释为 T 值。
|
| 重载 |
|
| 参数化 | T 是 i32、u32 或 f32
|
| 描述 | 将位按分量
重新解释为 f16。 结果是按照内部布局规则,将 e 中的 32 位重新解释为 f16 值。
|
| 重载 |
|
| 参数化 | T 是 i32、u32 或 f32
|
| 描述 | 将位按分量
重新解释为 vec2<f16>。结果是按照内部布局规则,将 e 中的 64 位重新解释为 f16 值。
|
17.3. 逻辑内置函数
17.3.1. all
| 重载 |
|
| 描述 | 当 e 的所有分量都为 true 时返回 true。
|
| 重载 |
|
| 描述 | 返回 e。
|
17.3.2. any
| 重载 |
|
| 描述 | 当 e 的任意一个分量为 true 时返回 true。
|
| 重载 |
|
| 描述 | 返回 e。
|
17.3.3. select
| 重载 |
|
| 参数化 | T 是 标量 或 向量
|
| 描述 | 当 cond 为 true 时返回 t,否则返回 f。
|
| 重载 |
|
| 参数化 | T 是 标量
|
| 描述 | 分量方式
选择。结果分量 i 的计算为
select(f[i], t[i], cond[i])。
|
17.4. 数组内置函数
17.4.1.
arrayLength
| 重载 |
|
| 参数化 | E 是一个运行时大小数组的元素类型,访问模式 AM 为 read 或 read_write
|
| 描述 | 返回NRuntime,即运行时大小数组中的元素数量。 |
struct PointLight { position: vec3f, color : vec3f, } struct LightStorage { pointCount : u32, point : array< PointLight > , } @group ( 0 ) @binding ( 1 ) var < storage> lights : LightStorage ; fn num_point_lights () -> u32{ return arrayLength ( & lights . point ); }
17.5. 数值内置函数
17.5.1. abs
| 重载 |
|
| 参数化 | S 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16 T 为 S,或 vecN<S> |
| 描述 |
e 的绝对值。
当 T 是向量时,分量方式 计算。
如果 |
17.5.2. acos
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 e 的反余弦(cos-1)的主值(弧度)。即,近似 x(0 ≤ x ≤ π),使得 cos(x) =
e。
当 |
| 标量 定义域 | 区间 [−1, 1] |
17.5.3. acosh
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 x 的反双曲余弦(cosh-1),以双曲角表示。即,近似 a(0 ≤ a ≤ +∞),使得 cosh(a) = x。
当 |
| 标量 定义域 | 区间 [1, +∞] |
17.5.4. asin
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 e 的反正弦(sin-1)的主值(弧度)。即,近似 x(-π/2 ≤ x ≤ π/2),使得 sin(x) =
e。
当 |
| 标量 定义域 | 区间 [−1, 1] |
17.5.5. asinh
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 y 的反双曲正弦(sinh-1),以双曲角表示。即,近似 a,使得 sinh(y) = a。
当 |
17.5.6. atan
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 e 的反正切(tan-1)的主值(弧度)。即,近似 x(− π/2 ≤ x ≤ π/2),使得 tan(x) =
e。
当 |
17.5.7. atanh
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 t 的反双曲正切(tanh-1),以双曲角表示。即,近似 a,使得 tanh(a) = t。
当 |
| 标量 定义域 | 区间 [−1, 1] |
17.5.8. atan2
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回一个 角度,单位为弧度,范围为 [−π, π],其正切值为
y÷x。
结果所在象限取决于
注意: 结果的误差是无界的:
当 |
17.5.9. ceil
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e
的上取整值。
当 T 是向量时,分量方式 计算。
|
17.5.10. clamp
| 重载 |
|
| 参数化 | S 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16 T 为 S,或 vecN<S> |
| 描述 |
限制 e 的值在指定范围内。
如果 如果 当 如果 |
17.5.11. cos
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e 的余弦值,其中 e 的单位为弧度。
当 T 是向量时,分量方式 计算。
|
| 标量 定义域 | 区间 (−∞, +∞) |
17.5.12. cosh
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 a 的双曲余弦值,其中 a 为双曲角。
近似于纯数学函数 (ea +
e−a)÷2,
但不一定以该方式计算。
当 |
17.5.13.
countLeadingZeros
| 重载 |
|
| 参数化 | T 为 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 | 当 T 为标量类型时,返回 e 从最高有效位开始连续 0 的个数。当 T 为向量时,分量方式 计算。在某些语言中也称为 "clz"。 |
17.5.14.
countOneBits
| 重载 |
|
| 参数化 | T 为 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 | 返回 e 表示中 1 的数量。也称为 "population count"。 当 T 为向量时,分量方式 计算。
|
17.5.15.
countTrailingZeros
| 重载 |
|
| 参数化 | T 为 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 | 当 T 为标量类型时,返回 e 从最低有效位开始连续 0 的个数。当 T 为向量时,分量方式 计算。在某些语言中也称为 "ctz"。 |
17.5.16. cross
| 重载 |
|
| 参数化 | T 为 AbstractFloat、f32 或 f16
|
| 描述 | 返回 e1 和 e2 的叉积。
|
| 定义域 |
由可能的实现给出的线性项隐含:
|
17.5.17. degrees
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 将弧度转换为角度,近似为 e1 × 180 ÷ π。
当 T 是向量时,分量方式 计算。
|
17.5.18.
determinant
| 重载 |
|
| 参数化 | T 为 AbstractFloat、f32 或 f16
|
| 描述 | 返回 e 的行列式。
|
| 定义域 | 标准数学定义中线性项隐含。 |
17.5.19. distance
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 e1 和 e2 之间的距离(如
length(e1 - e2))。
定义域为所有能进行
e1−e2 的向量对(e1,e2)。
即,除非某个分量 |
17.5.20. dot
| 重载 |
|
| 参数化 | T 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16
|
| 描述 | 返回 e1 和 e2 的点积。
|
| 定义域 | 为所有项 e1[i] × e2[i] 求和的线性项隐含。 |
17.5.21.
dot4U8Packed
| 重载 |
|
| 描述 | e1 和 e2 被解释为含有四个 8 位无符号整型分量的向量。
返回这两个向量的无符号整型点积。
|
17.5.22.
dot4I8Packed
| 重载 |
|
| 描述 | e1 和 e2 被解释为包含四个 8 位有符号整数分量的向量。
返回这两个向量的有符号整数点积。每个分量在乘法前都被符号扩展为 i32,然后加法操作在 WGSL 的 i32 中完成(由于结果在 -65024 到 65536
范围内,数学上保证不会溢出,这也在 i32 可表示的范围内)。
|
17.5.23. exp
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e1 的自然指数(如
ee1)。
当 T 是向量时,分量方式 计算。
|
17.5.24. exp2
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 2 的 e 次幂(如 2e)。
当 T 是向量时,分量方式 计算。
|
17.5.25.
extractBits(有符号)
| 重载 |
|
| 参数化 | T 为 i32 或 vecN<i32>
|
| 描述 |
从整数中读取位,带符号扩展。
当
T 为向量时,分量方式 计算。
如果 |
17.5.26.
extractBits(无符号)
| 重载 |
|
| 参数化 | T 为 u32 或 vecN<u32>
|
| 描述 |
从整数中读取位,不进行符号扩展。
当
T 为向量时,分量方式 计算。
如果 |
17.5.27.
faceForward
| 重载 |
|
| 参数化 | T 为 vecN<AbstractFloat>、vecN<f32> 或 vecN<f16>
|
| 描述 | 如果 dot(e2, e3) 为负,返回 e1,否则返回 -e1。
|
| 定义域 | 定义域受 dot(e2,e3) 运算限制:由所有项 e2[i] × e3[i] 的线性项隐含。
|
17.5.28. firstLeadingBit(有符号)
| 重载 |
|
| 参数化 | T 为 i32 或 vecN<i32>
|
| 描述 |
若 T 为标量,则结果为:
分量方式 计算,当
|
|
注意: 有符号整数使用二进制补码表示, 符号位在最高有效位。 |
17.5.29. firstLeadingBit(无符号)
| 重载 |
|
| 参数化 | T 为 u32 或 vecN<u32>
|
| 描述 |
若 T 为标量,则结果为:
T 为向量时,分量方式 计算。
|
17.5.30.
firstTrailingBit
| 重载 |
|
| 参数化 | T 为 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 |
若 T 为标量,则结果为:
T 为向量时,分量方式 计算。
|
17.5.31. floor
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e 的下取整值。
当 T 是向量时,分量方式 计算。
|
17.5.32. fma
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 e1 * e2 + e3。
当 T 是向量时,分量方式 计算。
注意: 注意:
IEEE-754
的 |
| 定义域 | 为表达式 e2 × e2 + e3 的线性项隐含。 |
17.5.33. fract
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e 的小数部分,计算方式为 e - floor(e)。当 T 是向量时,分量方式 计算。
|
|
注意: 有效结果在闭区间 [0, 1.0]。
例如,若 |
17.5.34. frexp
| 重载 |
|
| 参数化 | T 为 f32
|
| 描述 |
将 e 拆分成小数部分和指数部分。
返回
注意: |
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 f16
|
| 描述 |
将 e 拆分成小数部分和指数部分。
返回
注意: |
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 AbstractFloat
|
| 描述 |
将 e 拆分成小数部分和指数部分。
注意: AbstractFloat 计算结果为无穷大或 NaN 时会导致着色器创建错误。 返回
注意: |
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 vecN<f32>
|
| 描述 |
将 e 的每个分量 ei 拆分为小数和指数部分。
返回
注意: |
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 vecN<f16>
|
| 描述 |
将 e 的每个分量 ei 拆分为小数和指数部分。
返回
注意: |
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 vecN<AbstractFloat>
|
| 描述 |
将 e 的每个分量 ei 拆分为小数和指数部分。
注意: AbstractFloat 计算结果为无穷大或 NaN 时会导致着色器创建错误。 返回
注意: |
|
注意: 不能显式声明类型为
|
17.5.35.
insertBits
| 重载 |
|
| 参数化 | T 为 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 |
设置整数中的位。
当
T 为向量时,分量方式 计算。
如果 |
17.5.36.
inverseSqrt
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 sqrt(e) 的倒数。
当 T 是向量时,分量方式 计算。
|
| 标量 定义域 | 区间 [0, +∞] |
17.5.37. ldexp
| 重载 |
|
| 参数化 |
S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> I 是 AbstractInt、i32、vecN<AbstractInt> 或 vecN<i32>当且仅当 T 是向量时,I 是向量只有当 I 也是抽象的,反之亦然时,
T 才能是抽象的
|
| 描述 |
返回 e1 * 2e2,但以下情况除外:
这里,bias 是浮点格式的指数偏置: 如果 x = ldexp(frexp(x).fract, frexp(x).exp) 当 注:名称
|
17.5.38. length
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 e 的长度。若 T 为标量,结果为
e 的绝对值。若 T 为向量类型,结果为 sqrt(e[0]2
+ e[1]2 + ...)。
注意: 标量情况可实现为
|
17.5.39. log
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e 的自然对数值。
当 T 是向量时,分量方式 计算。
|
| 标量 定义域 | 区间 [0, +∞] |
17.5.40. log2
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e 的以 2 为底的对数值。
当 T 是向量时,分量方式 计算。
|
| 标量 定义域 | 区间 [0, +∞] |
17.5.41.
max
| 重载 |
|
| 参数化 | S 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16 T 为 S,或 vecN<S> |
| 描述 |
若 e1 小于 e2,则返回 e2,否则返回 e1。
当 T 是向量时,分量方式 计算。
若
|
17.5.42.
min
| 重载 |
|
| 参数化 | S 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16 T 为 S,或 vecN<S> |
| 描述 |
若 e2 小于 e1,则返回 e2,否则返回 e1。
当 T 是向量时,分量方式 计算。
若
|
17.5.43. mix
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e1 和 e2 的线性混合(如 e1 * (T(1) - e3) + e2 * e3)。
当 T 是向量时,分量方式 计算。
|
| 定义域 | 由表达式 e1[i] × (1 − e3[i]) + e2[i] × e3[i] 的线性项隐含。 e2[i] × e2[i] + e3[i]。 |
| 重载 |
|
| 参数化 | T 为 AbstractFloat、f32 或 f16T2 为 vecN<T>
|
| 描述 | 返回 e1 和 e2 的分量线性混合,
每个分量都用标量混合因子 e3。等价于 mix(e1, e2, T2(e3))。
|
| 定义域 | 由表达式 e1[i] × (1 − e3) + e2[i] × e3 的线性项隐含。 |
17.5.44. modf
| 重载 |
|
| 参数化 | T 为 f32
|
| 描述 |
将 e 拆分为小数部分和整数部分。
整数部分为 trunc( 返回
|
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 f16
|
| 描述 |
将 e 拆分为小数部分和整数部分。
整数部分为 trunc( 返回
|
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 AbstractFloat
|
| 描述 |
将 e 拆分为小数部分和整数部分。
整数部分为 trunc( 返回
|
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 vecN<f32>
|
| 描述 |
将 e 的各分量拆分为小数部分和整数部分。
第 返回
|
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 vecN<f16>
|
| 描述 |
将 e 的各分量拆分为小数部分和整数部分。
第 返回
|
|
注意: 不能显式声明类型为
|
| 重载 |
|
| 参数化 | T 为 vecN<AbstractFloat>
|
| 描述 |
将 e 的各分量拆分为小数部分和整数部分。
第 返回
|
|
注意: 不能显式声明类型为
|
17.5.45.
normalize
| 重载 |
|
| 参数化 | T 为 AbstractFloat、f32 或 f16
|
| 描述 |
返回和 e 方向相同的单位向量。
定义域为所有非零向量。 |
17.5.46. pow
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e1 的 e2 次幂。
当 T 是向量时,分量方式 计算。
|
| 标量 定义域 |
所有扩展实数对
(x,y),但不包括:
此规则基于结果可能被计算为 |
17.5.47.
quantizeToF16
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
将 32 位浮点数 e 量化,等价于将 e 按 IEEE-754 binary16
转换为
IEEE-754 binary32 再转回。
若 中间 binary16 值可能会冲洗为零,即若中间值为非规格化数,则最终结果可能为 0。 参见 § 15.7.6 浮点数转换。 当 |
|
注意: vec2<f32> 情况等价于
|
17.5.48. radians
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 将角度转为弧度,近似为 e1 × π ÷ 180。
当 T 是向量时,分量方式 计算。
|
17.5.49. reflect
| 重载 |
|
| 参数化 | T 为 vecN<AbstractFloat>、vecN<f32> 或 vecN<f16>
|
| 描述 | 对于入射向量 e1 和表面法线 e2,返回反射方向
e1 - 2 * dot(e2, e1) * e2。
|
17.5.50. refract
| 重载 |
|
| 参数化 | T 为 vecN<I>I 为 AbstractFloat、f32 或 f16
|
| 描述 | 对于入射向量 e1、表面法线 e2 和折射率比值 e3,
令 k = 1.0 - e3 * e3 * (1.0 - dot(e2, e1) * dot(e2, e1))。
若 k < 0.0,返回折射向量 0.0,否则返回折射向量
e3 * e1 - (e3 * dot(e2, e1) + sqrt(k)) * e2。入射向量 e1 和法线
e2
应当归一化以符合斯涅尔定律,否则结果可能不符合物理预期。
|
17.5.51.
reverseBits
| 重载 |
|
| 参数化 | T 为 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 | 翻转 e 中的比特:结果的第 k 位等于 e 的第 31-k 位。当 T 为向量时,分量方式 计算。
|
17.5.52. round
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 结果为最接近 e 的整数 k(以浮点数表示)。当 e 正好在整数 k 和 k + 1 的中间时,
如果 k 为偶数,结果为 k,如果 k 为奇数,结果为 k + 1。当 T 为向量时,分量方式 计算。
|
17.5.53.
saturate
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 clamp(e, 0.0, 1.0)。
当 T 为向量时,分量方式 计算。
|
17.5.54. sign
| 重载 |
|
| 参数化 | S 为 AbstractInt、AbstractFloat、i32、f32 或 f16 T 为 S,或 vecN<S> |
| 描述 |
结果为:
分量方式 计算,当
|
17.5.55. sin
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e 的正弦值,e 单位为弧度。
当 T 为向量时,分量方式 计算。
|
| 标量 定义域 | 区间 (−∞, +∞) |
17.5.56. sinh
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 a 的双曲正弦值,其中 a 是双曲角。
近似于纯数学函数
(ea − e−a)÷2,
但不一定以此方式计算。
分量方式 计算,当
|
17.5.57.
smoothstep
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 0 到 1 之间的平滑 Hermite 插值。
当 T 为向量时,分量方式 计算。
对于标量 定性来说:
如果 |
17.5.58. sqrt
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e 的平方根。
当 T 为向量时,分量方式 计算。
|
| 标量 定义域 | 区间 [0, +∞] |
17.5.59. step
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 若 edge ≤ x,返回 1.0,否则返回 0.0。
当 T 为向量时,分量方式 计算。
|
17.5.60. tan
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 e 的正切值,e 单位为弧度。
当 T 为向量时,分量方式 计算。
|
| 标量 定义域 | 区间 (−∞, +∞) |
17.5.61. tanh
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 |
返回 a 的双曲正切值,其中 a 是双曲角。
近似为纯数学函数
(ea − e−a) ÷ (ea +
e−a)
但不一定以该方式计算。
分量方式 计算,当
|
17.5.62.
transpose
| 重载 |
|
| 参数化 | T 为 AbstractFloat、f32 或 f16
|
| 描述 | 返回 e 的转置。
|
17.5.63. trunc
| 重载 |
|
| 参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
| 描述 | 返回 truncate(e),即绝对值不大于 e 的最近整数。
当 T 为向量时,分量方式 计算。
|
17.6. 导数内置函数
参见 § 15.6.2 导数。
对此类函数的调用:
17.6.1. dpdx
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
e 关于窗口 x 坐标的偏导数。
结果与 dpdxFine(e) 或 dpdxCoarse(e) 相同。
|
17.6.2. dpdxCoarse
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
使用局部差分返回 e 关于窗口 x 坐标的偏导数。
这可能导致比 dpdxFine(e) 更少的不重复位置。
|
17.6.3. dpdxFine
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
返回 e 关于窗口 x 坐标的偏导数。
|
17.6.4. dpdy
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
e 关于窗口 y 坐标的偏导数。
结果与 dpdyFine(e) 或 dpdyCoarse(e) 相同。
|
17.6.5. dpdyCoarse
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
使用局部差分返回 e 关于窗口 y 坐标的偏导数。
这可能导致比 dpdyFine(e) 更少的不重复位置。
|
17.6.6. dpdyFine
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
返回 e 关于窗口 y 坐标的偏导数。
|
17.6.7. fwidth
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
返回 abs(dpdx(e)) + abs(dpdy(e))。
|
17.6.8.
fwidthCoarse
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
返回 abs(dpdxCoarse(e)) + abs(dpdyCoarse(e))。
|
17.6.9. fwidthFine
| 重载 |
|
| 参数化 | T 为 f32 或 vecN<f32>
|
| 描述 |
返回 abs(dpdxFine(e)) + abs(dpdyFine(e))。
|
17.7. 纹理内置函数
参数值必须对相应的纹理类型有效。
17.7.1. textureDimensions
返回纹理或纹理某个 mip 级别的尺寸(以 texel 为单位)。
| 参数化 | 重载 |
|---|---|
| ST 是 i32、u32 或 f32 F 是纹素 格式 A 是访问 模式 T 是 texture_1d<ST> 或 texture_storage_1d<F,A>
|
|
|
ST 是 i32、u32 或 f32 T 是 texture_1d<ST>
|
|
| ST 是 i32、u32 或 f32 F 是纹素 格式 A 是访问 模式 T 是 texture_2d<ST>、texture_2d_array<ST>、
texture_cube<ST>、
texture_cube_array<ST>、texture_multisampled_2d<ST>、
texture_depth_2d、texture_depth_2d_array、
texture_depth_cube、
texture_depth_cube_array、texture_depth_multisampled_2d、
texture_storage_2d<F,A>、texture_storage_2d_array<F,A>
或 texture_external
|
|
|
ST 是 i32、u32 或 f32 T 是 texture_2d<ST>、texture_2d_array<ST>、
texture_cube<ST>、
texture_cube_array<ST>、texture_depth_2d、
texture_depth_2d_array、
texture_depth_cube 或 texture_depth_cube_array
|
|
| ST 是 i32、u32 或 f32 F 是纹素 格式 A 是访问 模式 T 是 texture_3d<ST> 或 texture_storage_3d<F,A>
|
|
|
ST 是 i32、u32 或 f32 T 是 texture_3d<ST>
|
|
参数:
t
| 采样、多采样、深度、 存储或外部 纹理。 |
level
|
mip 级别,级别 0 表示完整尺寸的纹理。 如果省略,则返回级别 0 的尺寸。 |
返回:
纹理的坐标尺寸。
也就是说,结果提供了逻辑 texel 地址坐标的整型边界, 不包括mip 级别数、数组大小和采样数。
对于基于立方体的纹理,结果为立方体每个面的尺寸。 立方体的每个面都是正方形,因此结果的 x 和 y 分量相等。
如果 level 超出范围 [0, textureNumLevels(t)),则返回类型的未定值。
17.7.2. textureGather
纹理 gather 操作为 2D、2D 数组、立方体或立方体数组纹理读取, 计算一个四分量向量,具体如下:
-
找到使用线性过滤采样操作将使用的四个 texel,来自 mip 级别 0:
-
使用指定的坐标、数组索引(如有)和偏移量(如有)。
-
这些 texel 在纹理空间坐标 (u,v) 下相邻,形成一个正方形。
-
在纹理边缘、立方体面边缘或立方体角落选定的 texel,按普通纹理采样的方式处理。
-
-
对每个 texel,只读取一个通道并转换为标量值。
-
对非深度纹理,
component参数(从零开始)指定读取的通道。-
如果纹理格式支持该通道,即通道数大于
component:-
若 texel 值为
v,则返回v[component]。
-
-
否则:
-
当
component是 1 或 2 时,返回 0.0。 -
当
component是 3(即 alpha 通道)时,返回 1.0。
-
-
-
对深度纹理,直接返回 texel 值。(深度纹理只有一个通道。)
-
-
将上一步得到的四个标量,按 texel 的相对坐标排列到四分量向量中:
-
结果分量 相对 texel 坐标 x (umin,vmax) y (umax,vmax) z (umax,vmin) w (umin,vmin)
-
这四个 texel 形成的采样区域与 WebGPU 采样器描述符中描述一致。
| 参数化 | 重载 |
|---|---|
| C 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 A 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 A 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 A 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| |
| |
| |
| A 是 i32,或 u32 |
|
| A 是 i32,或 u32 |
|
| A 是 i32,或 u32 |
|
参数:
component
|
仅适用于非深度纹理。
要从所选纹素读取的通道索引。 提供时, component 表达式必须是const-expression(例如
1)。其值必须至少为 0 且至多为 3。 超出此范围的值将导致着色器创建 错误。 |
t
| 要读取的采样或深度纹理。 |
s
| 采样器类型。 |
coords
| 纹理坐标。 |
array_index
|
从 0 开始的纹理数组索引。 此值将被钳制到范围 [0, textureNumLayers(t) - 1]。
|
offset
|
在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。
此偏移会在应用任何纹理环绕模式之前应用。offset 表达式必须是const-expression(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8,且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
一个四分量向量,分量从选定 texel 的指定通道提取,如上所述。
@group ( 0 ) @binding ( 0 ) var t : texture_2d< f32> ; @group ( 0 ) @binding ( 1 ) var dt : texture_depth_2d; @group ( 0 ) @binding ( 2 ) var s : sampler; fn gather_x_components ( c : vec2< f32> ) -> vec4< f32> { return textureGather ( 0 , t , s , c ); } fn gather_y_components ( c : vec2< f32> ) -> vec4< f32> { return textureGather ( 1 , t , s , c ); } fn gather_z_components ( c : vec2< f32> ) -> vec4< f32> { return textureGather ( 2 , t , s , c ); } fn gather_depth_components ( c : vec2< f32> ) -> vec4< f32> { return textureGather ( dt , s , c ); }
17.7.3.
textureGatherCompare
纹理 gather compare 操作会对深度纹理中的四个 texel 执行深度比较,并将结果收集为一个向量,具体如下:
-
找到使用线性过滤采样操作(mip 级别 0)将会用到的四个 texel:
-
使用指定的坐标、数组索引(如有)和偏移(如有)。
-
这些 texel 在纹理空间坐标 (u,v) 下相邻,形成一个正方形。
-
在纹理边缘、立方体面边界或立方体角落选到的 texel,按普通纹理采样规则处理。
-
-
对每个 texel,与深度参考值进行比较,比较的结果根据采样器参数,返回 0.0 或 1.0。
-
将比较结果按下面的 texel 相对坐标顺序组成四分量向量:
-
结果分量 相对 texel 坐标 x (umin,vmax) y (umax,vmax) z (umax,vmin) w (umin,vmin)
-
| 参数化 | 重载 |
|---|---|
| |
| |
| A 是 i32,或 u32 |
|
| A 是 i32,或 u32 |
|
| |
| A 是 i32,或 u32 |
|
参数:
t
| 要读取的深度纹理。 |
s
| 比较采样器。 |
coords
| 纹理坐标。 |
array_index
|
从 0 开始的纹理数组索引。 此值将被钳制到范围 [0, textureNumLayers(t) - 1]。
|
depth_ref
| 用来与采样深度值进行比较的参考值。 |
offset
|
在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。
此偏移会在应用任何纹理环绕模式之前应用。offset 表达式必须是const-expression(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8,且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
四分量向量,分量为选定 texel 的比较结果,如上所述。
@group ( 0 ) @binding ( 0 ) var dt : texture_depth_2d; @group ( 0 ) @binding ( 1 ) var s : sampler; fn gather_depth_compare ( c : vec2< f32> , depth_ref : f32) -> vec4< f32> { return textureGatherCompare ( dt , s , c , depth_ref ); }
17.7.4. textureLoad
从纹理中读取单个 texel,不进行采样和过滤。
| 参数化 | 重载 |
|---|---|
| C 是 i32,或 u32 L 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 L 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 A 是 i32,或 u32 L 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 L 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 S 是 i32,或 u32 ST 是 i32、u32,或 f32 |
|
| C 是 i32,或 u32 L 是 i32,或 u32 |
|
| C 是 i32,或 u32 A 是 i32,或 u32 L 是 i32,或 u32 |
|
| C 是 i32,或 u32 S 是 i32,或 u32 |
|
| C 是 i32,或 u32 |
|
| C 是 i32,或 u32 AM 是 read 或 read_write CF 取决于存储纹素格式 F。 请参阅纹素格式 表,了解纹素 格式到通道格式的映射。 |
|
| C 是 i32,或 u32 AM 是 read 或 read_write CF 取决于存储纹素格式 F。 请参阅纹素格式 表,了解纹素 格式到通道格式的映射。 |
|
| C 是 i32,或 u32 AM 是 read 或 read_write A 是 i32 或 u32 CF 取决于存储纹素格式 F。 请参阅纹素格式 表,了解纹素 格式到通道格式的映射。 |
|
| C 是 i32,或 u32 AM 是 read 或 read_write CF 取决于存储纹素格式 F。 请参阅纹素格式 表,了解纹素 格式到通道格式的映射。 |
|
参数:
t
| 采样型、 多采样型、 深度型、 存储型,或 外部 纹理 |
coords
| 以 0 为起点的 texel 坐标。 |
array_index
| 以 0 为起点的纹理数组索引。 |
level
| mip 级别,级别 0 表示完整尺寸的纹理。 |
sample_index
| 多采样纹理的 0 基采样索引。 |
返回:
未经过滤的 texel 数据。
当以下情况之一发生时,逻辑 texel 地址无效:
-
coords的任一元素超出对应分量的[0, textureDimensions(t, level))范围,或 -
array_index超出[0, textureNumLayers(t))范围,或 -
level超出[0, textureNumLevels(t))范围,或 -
sample_index超出[0, textureNumSamples(s))范围
如果逻辑 texel 地址无效,该内置函数的返回值可能为以下之一:
-
纹理范围内某个texel的数据。
-
对于非深度纹理,可以是:
-
深度纹理为0.0。
17.7.5. textureNumLayers
返回数组纹理的层(元素)数量。
| 参数化 | 重载 |
|---|---|
| F 为texel
格式 A 为访问模式 ST 为 i32、u32 或 f32 T 为 texture_2d_array<ST>、texture_cube_array<ST>、
texture_depth_2d_array、texture_depth_cube_array,
或 texture_storage_2d_array<F,A>
|
|
参数:
t
| 采样型、 深度型, 或 存储 数组纹理。 |
返回:
如果纹理为立方体数组,则返回立方体的数量。
否则返回数组纹理的层(同质 texel 网格)数量。
17.7.6. textureNumLevels
返回纹理的 mip 级别数量。
| 参数化 | 重载 |
|---|---|
| ST 为 i32、u32 或 f32 T 为 texture_1d<ST>、texture_2d<ST>、
texture_2d_array<ST>、texture_3d<ST>、
texture_cube<ST>、texture_cube_array<ST>、
texture_depth_2d、texture_depth_2d_array、
texture_depth_cube 或 texture_depth_cube_array
|
|
参数:
t
| 采样型或 深度型纹理。 |
返回:
该纹理的mip 级别数。
17.7.7. textureNumSamples
返回多采样纹理每个 texel 的采样数。
| 参数化 | 重载 |
|---|---|
| ST 为 i32、u32 或 f32 T 为 texture_multisampled_2d<ST>
或 texture_depth_multisampled_2d
|
|
参数:
t
| 多采样纹理。 |
返回:
该多采样纹理的采样数。
17.7.8. textureSample
对纹理进行采样。
如果一致性分析无法证明对该函数的调用处于一致控制流中, 则derivative_uniformity 诊断会被触发。
| 参数化 | 重载 |
|---|---|
| |
| |
| |
| A 为 i32 或 u32 |
|
| A 为 i32 或 u32 |
|
T 为 texture_3d<f32> 或 texture_cube<f32>
|
|
| |
| A 为 i32 或 u32 |
|
| |
| |
| A 为 i32 或 u32 |
|
| A 为 i32 或 u32 |
|
| |
| A 为 i32 或 u32 |
|
参数:
t
| 进行采样的采样型或 深度 纹理。 |
s
| 采样器类型。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的 0 基数组纹理索引。 此值会被限制在 [0, textureNumLayers(t) - 1]
范围内。
|
offset
|
在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。
此偏移会在应用任何
纹理环绕模式之前应用。offset 表达式必须是const-expression(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8,且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
采样值。
17.7.9. textureSampleBias
对纹理采样并对 mip 级别应用 bias 偏移。
如果一致性分析无法证明该函数调用处于一致控制流中, 会触发derivative_uniformity 诊断。
| 参数化 | 重载 |
|---|---|
| |
| |
| A 为 i32 或 u32 |
|
| A 为 i32 或 u32 |
|
T 为 texture_3d<f32> 或 texture_cube<f32>
|
|
| |
| A 为 i32 或 u32 |
|
参数:
t
| 要采样的采样纹理。 |
s
| 采样器类型。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的 0 基数组纹理索引。 此值会被限制在 [0, textureNumLayers(t) - 1]
范围内。
|
bias
|
在采样前对 mip 级别应用的 bias 偏移。 此值会被限制在 [-16.0, 15.99] 范围内。
|
offset
|
在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。
此偏移会在应用任何
纹理环绕模式之前应用。offset 表达式必须是const-expression(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8,且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
采样值。
17.7.10.
textureSampleCompare
对深度 纹理进行采样,并将采样的深度值与参考值进行比较。
如果一致性 分析无法证明对该函数的调用处于一致控制流中, 则会触发derivative_uniformity 诊断。
| 参数化 | 重载 |
|---|---|
| |
| |
| A 是 i32,或 u32 |
|
| A 是 i32,或 u32 |
|
| |
| A 是 i32,或 u32 |
|
参数:
t
| 要采样的深度 纹理。 |
s
| sampler_comparison 类型。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的从 0 开始的纹理数组索引。 此值将被钳制到范围 [0, textureNumLayers(t) - 1]。
|
depth_ref
| 用来与采样深度值进行比较的参考值。 |
offset
|
在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。
此偏移会在应用任何
纹理环绕模式之前应用。offset 表达式必须是const-expression(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8,且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
范围 [0.0..1.0] 内的值。
每个采样的纹素都会使用 sampler_comparison 定义的比较
运算符与参考值进行比较,从而为每个纹素产生 0 或
1
值。
如果采样器使用双线性过滤,则返回值是这些值的 过滤平均值,否则返回单个 纹素的比较结果。
17.7.11.
textureSampleCompareLevel
对深度纹理进行采样,并将采样深度值与参考值进行比较。
| 参数化 | 重载 |
|---|---|
| |
| |
| A 是 i32,或 u32 |
|
| A 是 i32,或 u32 |
|
| |
| A 是 i32,或 u32 |
|
参数:
t
| 要采样的深度 纹理。 |
s
| sampler_comparison 类型。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的从 0 开始的纹理数组索引。 此值将被钳制到范围 [0, textureNumLayers(t) - 1]。
|
depth_ref
| 用来与采样深度值进行比较的参考值。 |
offset
|
在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。
此偏移会在应用任何
纹理环绕模式之前应用。offset 表达式必须是const-expression(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8,且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
区间 [0.0..1.0] 内的值。
textureSampleCompareLevel 函数与 textureSampleCompare 类似,不同之处在于:
-
textureSampleCompareLevel总是从 mip 级别 0 采样 texel。-
该函数不会计算导数。
-
textureSampleCompareLevel没有要求在一致控制流中调用。
-
-
textureSampleCompareLevel可在任何着色器阶段调用。
17.7.12.
textureSampleGrad
使用显式梯度对纹理采样。
| 参数化 | 重载 |
|---|---|
| |
| |
| A 是 i32,或 u32 |
|
| A 是 i32,或 u32 |
|
T 是 texture_3d<f32>,或 texture_cube<f32>
|
|
| |
| A 是 i32,或 u32 |
|
参数:
t
| 要采样的采样纹理。 |
s
| 采样器。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的从 0 开始的纹理数组索引。 此值将被钳制到范围 [0, textureNumLayers(t) - 1]。
|
ddx
| 用于计算采样位置的 x 方向导数向量。 |
ddy
| 用于计算采样位置的 y 方向导数向量。 |
offset
|
在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。
此偏移会在应用任何
纹理环绕模式之前应用。offset 表达式必须是const-expression(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8,且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
采样值。
17.7.13.
textureSampleLevel
使用显式 mip 级别对纹理采样。
| 参数化 | 重载 |
|---|---|
| |
| |
| |
| A 是 i32,或 u32 |
|
| A 是 i32,或 u32 |
|
T 是 texture_3d<f32>,或 texture_cube<f32>
|
|
| |
| A 是 i32,或 u32 |
|
| L 是 i32,或 u32 |
|
| L 是 i32,或 u32 |
|
| A 是 i32,或 u32 L 是 i32,或 u32 |
|
| A 是 i32,或 u32 L 是 i32,或 u32 |
|
| L 是 i32,或 u32 |
|
| A 是 i32,或 u32 L 是 i32,或 u32 |
|
参数:
t
| 要采样的采样或深度纹理。 |
s
| 采样器类型。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的从 0 开始的纹理数组索引。 此值将被钳制到范围 [0, textureNumLayers(t) - 1]。
|
level
|
mip 级别,其中级别 0 包含纹理的全尺寸版本。
对于 level 为 f32 的函数,如果格式根据
纹理格式能力
是可过滤的,则小数值可能会在两个级别之间插值。
|
offset
|
在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。
此偏移会在应用任何
纹理环绕模式之前应用。offset 表达式必须是const-expression(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8,且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
采样值。
17.7.14.
textureSampleBaseClampToEdge
在基准级别(base level)采样纹理视图,纹理坐标如下所述被夹紧到边缘。
| 参数化 | 重载 |
|---|---|
T 为 texture_2d<f32> 或 texture_external
|
|
参数:
t
| 要采样的采样型或外部纹理。 |
s
| 采样器类型。 |
coords
|
用于采样的纹理坐标。
在采样前,传入的坐标会被夹紧到矩形
其中
注意: half-texel 调整可确保无论采样器的
|
返回:
采样值。
17.7.15. textureStore
向纹理写入一个 texel。
| 参数化 | 重载 |
|---|---|
| F 是纹素
格式 C 是 i32,或 u32 AM 是 write 或 read_write CF 取决于存储纹素格式 F。 请参阅纹素格式 表,了解纹素 格式到通道格式的映射。 |
|
| F 是纹素
格式 C 是 i32,或 u32 AM 是 write 或 read_write CF 取决于存储纹素格式 F。 请参阅纹素格式 表,了解纹素 格式到通道格式的映射。 |
|
| F 是纹素
格式 C 是 i32,或 u32 AM 是 write 或 read_write A 是 i32,或 u32 CF 取决于存储纹素格式 F。 请参阅纹素格式 表,了解纹素 格式到通道格式的映射。 |
|
| F 是纹素
格式 C 是 i32,或 u32 AM 是 write 或 read_write CF 取决于存储纹素格式 F。 请参阅纹素格式 表,了解纹素 格式到通道格式的映射。 |
|
参数:
t
| 只写存储纹理或 读写存储纹理 |
coords
|
以 0 为起点的 texel 坐标。 |
array_index
| 以 0 为起点的纹理数组索引。 |
value
|
新的 texel 值。
value 会用逆通道传输函数进行转换。
|
注意:
当下列情况之一发生时,逻辑 texel 地址无效:
-
coords的任一元素超出其分量的[0, textureDimensions(t))范围,或 -
array_index超出[0, textureNumLayers(t))范围
如果逻辑 texel 地址无效,该内置函数不会被执行。
17.8. 原子内置函数
原子内置函数可用于读取/写入/读改写原子 对象。它们是 § 6.2.8 原子类型上唯一允许的操作。
所有原子内置函数都使用 relaxed 内存
顺序。这意味着同步和顺序保证只适用于作用于相同内存位置的
原子操作之间。原子和非原子内存访问之间,或
作用于不同内存位置的原子访问之间,不适用同步
或顺序保证。
所有原子内置函数中 atomic_ptr 参数的地址空间 AS
必须是storage 或
workgroup。
17.8.1. atomicLoad
fn atomicLoad ( atomic_ptr : ptr< AS , atomic< T > , read_write> ) -> T
返回atomic_ptr指向的值的原子加载结果。不会修改该对象。
17.8.2. atomicStore
fn atomicStore ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T )
以原子方式将值v存储到atomic_ptr指向的原子对象中。
17.8.3. 原子读-修改-写算术与逻辑函数
每个函数都以原子方式执行以下步骤:
-
加载
atomic_ptr指向的原值。 -
用函数名描述的操作(如max)与值v计算新值。
-
用
atomic_ptr存储新值。
每个函数都返回操作前原子对象中存储的原值。
17.8.3.1. atomicAdd
fn atomicAdd ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T
以原子方式对atomic_ptr指向的原子对象与值v进行加法操作,并返回操作前原子对象中存储的原值。
// 所有操作均以原子方式执行 fn atomicAdd ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T { let old = * atomic_ptr ; * atomic_ptr = old + v ; return old ; }
17.8.3.2. atomicSub
fn atomicSub ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T
以原子方式对atomic_ptr指向的原子对象与值v进行减法操作,并返回操作前原子对象中存储的原值。
// 所有操作均以原子方式执行 fn atomicSub ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T { let old = * atomic_ptr ; * atomic_ptr = old - v ; return old ; }
17.8.3.3. atomicMax
fn atomicMax ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T
以原子方式对atomic_ptr指向的原子对象与值v进行最大值操作,并返回操作前原子对象中存储的原值。
// 所有操作均以原子方式执行 fn atomicMax ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T { let old = * atomic_ptr ; * atomic_ptr = max ( old , v ); return old ; }
17.8.3.4. atomicMin
fn atomicMin ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T
以原子方式对atomic_ptr指向的原子对象与值v进行最小值操作,并返回操作前原子对象中存储的原值。
// 所有操作均以原子方式执行 fn atomicMin ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T { let old = * atomic_ptr ; * atomic_ptr = min ( old , v ); return old ; }
17.8.3.5. atomicAnd
fn atomicAnd ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T
以原子方式对atomic_ptr指向的原子对象与值v进行按位与操作,并返回操作前原子对象中存储的原值。
// 所有操作均以原子方式执行 fn atomicAnd ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T { let old = * atomic_ptr ; * atomic_ptr = old & v ; return old ; }
17.8.3.6. atomicOr
fn atomicOr ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T
以原子方式对atomic_ptr指向的原子对象与值v进行按位或操作,并返回操作前原子对象中存储的原值。
// 所有操作均以原子方式执行 fn atomicOr ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T { let old = * atomic_ptr ; * atomic_ptr = old | v ; return old ; }
17.8.3.7. atomicXor
fn atomicXor ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T
以原子方式对atomic_ptr指向的原子对象与值v进行按位异或操作,并返回操作前原子对象中存储的原值。
// 所有操作均以原子方式执行 fn atomicXor ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T { let old = * atomic_ptr ; * atomic_ptr = old ^ v ; return old ; }
17.8.4. atomicExchange
fn atomicExchange ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T
以原子方式将值v写入atomic_ptr指向的原子对象,并返回操作前原子对象中存储的原值。
// 所有操作均以原子方式执行 fn atomicExchange ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T { let old = * atomic_ptr ; * atomic_ptr = v ; return old ; }
17.8.5.
atomicCompareExchangeWeak
fn atomicCompareExchangeWeak ( atomic_ptr : ptr< AS , atomic< T > , read_write> , cmp : T , v : T ) -> __atomic_compare_exchange_result < T > struct __atomic_compare_exchange_result < T > { old_value : T , // 原子中的旧值 exchanged : bool// 如果发生了交换则为 true }
注意:
不能显式声明类型为__atomic_compare_exchange_result,但值可推断类型。
以原子方式执行以下步骤:
-
加载
atomic_ptr指向的原值。 -
用等号操作符将原值与
cmp进行比较。 -
仅当比较结果为
true时,将v写入。
返回一个包含两个成员的结构体。第一个成员old_value是操作前原子对象的原值,第二个成员exchanged表示比较是否成功。
// 所有操作均以原子方式执行 fn atomicCompareExchangeWeak ( atomic_ptr : ptr< AS , atomic< T > , read_write> , cmp : T , v : T ) -> _atomic_compare_exchange_result < T > { let old = * atomic_ptr ; // 此比较可能会伪失败。 let comparison = old == cmp ; if comparison { * atomic_ptr = v ; } return _atomic_compare_exchange_result < T > ( old , comparison ); }
注意: 某些实现上,等值比较可能会伪失败。
即使结果的第一个分量等于cmp,返回结构体的第二个分量也可能为false。
17.9. 数据打包内置函数
数据打包内置函数可用于将值编码为 WGSL 中没有直接类型对应的数据格式。 这样可以让程序将大量密集打包的值写入内存,从而减少着色器的内存带宽需求。
每个内置函数都会对输入值应用逆通道传输函数,然后将结果合并为单一输出值。
注意: 对 unorm 值打包时,归一化浮点值位于区间 [0.0, 1.0] 内。
注意: 对 snorm 值打包时,归一化浮点值位于区间 [-1.0, 1.0] 内。
17.9.1.
pack4x8snorm
| 重载 |
|
| 描述 |
将四个归一化浮点值转换为 8 位有符号整数,再组合成一个u32值。
输入的第 |
17.9.2.
pack4x8unorm
| 重载 |
|
| 描述 |
将四个归一化浮点值转换为 8 位无符号整数,再组合成一个u32值。
输入的第 |
17.9.3. pack4xI8
| 重载 |
|
| 描述 |
将e每个分量的低 8 位打包为一个u32值,丢弃未用的高位比特。
输入的第 |
17.9.4. pack4xU8
| 重载 |
|
| 描述 |
将e每个分量的低 8 位打包为一个u32值,丢弃未用的高位比特。
输入的第 |
17.9.5.
pack4xI8Clamp
| 重载 |
|
| 描述 |
将e每个分量限制在 [-128, 127] 区间后,取低 8 位并打包为一个u32值。
输入的第 |
17.9.6.
pack4xU8Clamp
| 重载 |
|
| 描述 |
将e的每个分量限制在 [0, 255] 区间后,取低 8 位并打包为一个u32值。
输入的第 |
17.9.7.
pack2x16snorm
| 重载 |
|
| 描述 | 将两个归一化浮点值转换为 16 位有符号整数,并合并为一个 u32 值。输入的第 e[i] 分量会被转换为 16 位补码整数
⌊ 0.5 + 32767 × min(1, max(-1, e[i])) ⌋,然后被放入结果的
16 × i 到 16 × i + 15 这 16 个比特中。
|
17.9.8.
pack2x16unorm
| 重载 |
|
| 描述 | 将两个归一化浮点值转换为 16 位无符号整数,并合并为一个 u32 值。输入的第 e[i] 分量会被转换为 16 位无符号整数
⌊ 0.5 + 65535 × min(1, max(0, e[i])) ⌋,然后被放入结果的
16 × i 到 16 × i + 15 这 16 个比特中。
|
17.9.9.
pack2x16float
| 重载 |
|
| 描述 |
将两个浮点值转换为半精度浮点数(half-precision float),并合并为一个 u32 值。输入的第 e[i] 分量会被转换为 IEEE-754 binary16
类型,然后放入结果的
16 × i 到 16 × i + 15 这 16 个比特中。
详见 § 15.7.6 浮点转换。
如果
|
17.10. 数据解包内置函数
数据解包内置函数可用于解码那些与WGSL类型不直接对应的数据格式的值。 这样可以让程序从内存中读取大量密集打包的值,从而减少着色器的内存带宽需求。
每个内置函数会将输入值拆分成多个通道,然后对每个通道应用通道传输函数。
注意: 对unorm值解包时,归一化浮点结果在区间[0.0, 1.0]内。
注意: 对snorm值解包时,归一化浮点结果在区间[-1.0, 1.0]内。
17.10.1.
unpack4x8snorm
| 重载 |
|
| 描述 | 将32位数值分解为四个8位块,然后将每个块重新解释为有符号归一化浮点值。 结果的第 i个分量为max(v ÷ 127, -1),其中v是e的
8×i到8×i + 7比特按补码有符号整数解释的值。
|
17.10.2.
unpack4x8unorm
| 重载 |
|
| 描述 | 将32位数值分解为四个8位块,然后将每个块重新解释为无符号归一化浮点值。 结果的第 i个分量为v ÷ 255,其中v是e的
8×i到8×i + 7比特按无符号整数解释的值。
|
17.10.3.
unpack4xI8
| 重载 |
|
| 描述 | e被解释为具有四个8位有符号整数分量的向量。将e解包为带符号扩展的vec4<i32>。
|
17.10.4.
unpack4xU8
| 重载 |
|
| 描述 | e被解释为具有四个8位无符号整数分量的向量。将e解包为带零扩展的vec4<u32>。
|
17.10.5.
unpack2x16snorm
| 重载 |
|
| 描述 | 将32位数值分解为两个16位块,然后将每个块重新解释为有符号归一化浮点值。 结果的第 i个分量为max(v ÷ 32767, -1),其中v是e的
16×i到16×i + 15比特按补码有符号整数解释的值。
|
17.10.6.
unpack2x16unorm
| 重载 |
|
| 描述 | 将32位数值分解为两个16位块,然后将每个块重新解释为无符号归一化浮点值。 结果的第 i个分量为v ÷ 65535,其中v是e的
16×i到16×i + 15比特按无符号整数解释的值。
|
17.10.7.
unpack2x16float
| 重载 |
|
| 描述 | 将32位数值分解为两个16位块,并将每个块重新解释为浮点值。 结果的第 i个分量是v的f32表示,其中v是e的
16×i到16×i + 15比特按IEEE-754 binary16类型解释的值。
详见§ 15.7.6 浮点转换。
|
17.11. 同步内置函数
所有同步函数都会以 Acquire/Release 内存顺序执行控制屏障。 即,所有同步函数以及受影响的内存和原子操作都按照程序顺序相对于同步函数有序。 此外,程序顺序上在同步函数之前的受影响内存和原子操作,必须在工作组内的任意成员执行同步函数之后的受影响内存或原子操作之前,对该工作组的所有其他线程可见。
所有同步函数使用Workgroup 内存作用域。
所有同步函数具有Workgroup 执行作用域。
所有同步函数必须仅用于计算着色器阶段。
所有同步函数必须仅在一致控制流中调用。
17.11.1.
storageBarrier
| 重载 |
|
| 描述 | 执行一个控制屏障同步函数,影响 storage地址空间的内存和原子操作。 |
17.11.2.
textureBarrier
| 重载 |
|
| 描述 | 执行一个控制屏障同步函数,影响 handle地址空间的内存操作。 |
17.11.3.
workgroupBarrier
| 重载 |
|
| 描述 | 执行一个控制屏障同步函数,影响 workgroup地址空间的内存和原子操作。 |
17.11.4.
workgroupUniformLoad
| 重载 |
|
| 参数化 | T 是具体可构造类型。
|
| 描述 |
将 p 指向的值返回给工作组中的所有调用。
返回值是一致的。
p 必须是一致值。
|
| 重载 |
|
| 描述 |
原子地加载 p 指向的值,并将其返回给
工作组中的所有调用。
返回值是一致的。
p 必须是一致值。
|
17.12. 子组内置函数
调用这些函数时:
注:对于计算着色器阶段,一致控制流的作用域是 工作组。 对于片元 着色器阶段,一致控制流的作用域是 绘制命令。 这两个作用域都大于子组。
17.12.1.
subgroupAdd
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
归约操作。 |
17.12.1.1. subgroupExclusiveAdd
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
独占前缀扫描(exclusive prefix scan)操作。 在活动调用中ID最小的调用返回值为 |
17.12.1.2. subgroupInclusiveAdd
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
包含性前缀扫描(inclusive prefix scan)操作。 返回活动子组中,子组调用ID小于等于当前调用ID的所有 注意:等价于 |
17.12.2.
subgroupAll
| 重载 |
|
| 描述 | 如果 活动 子组中的所有调用的 e 都为
true,则返回 true。
|
17.12.3.
subgroupAnd
| 重载 |
|
| 前置条件 | T 是 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 |
归约操作。 |
17.12.4.
subgroupAny
| 重载 |
|
| 描述 | 如果 活动 子组中任意调用的 e 为
true,则返回 true。
|
17.12.5.
subgroupBallot
| 重载 |
|
| 描述 |
返回 活动
子组中 pred 为
true 的调用的位掩码。返回值的 x 分量包含调用 0 到 31。 在每个分量内,ID 按比特位升序排列(例如,ID 32 在 y 分量的 bit 0 位置)。 |
17.12.6.
subgroupBroadcast
| 重载 |
|
| 前置条件 | T 是具体数值标量或数值向量I 是 u32 或 i32
|
| 描述 |
将子组中子组调用
ID
与 id 匹配的调用中的 e 值,返回给子组中的所有活动调用。
注:如果需要
|
17.12.6.1. subgroupBroadcastFirst
| 重载 |
|
| 前置条件 | T 是 具体 数值标量 或 数值向量
|
| 描述 | 将子组内所有活跃
调用中,subgroup invocation ID 最小的调用的 e 值,
赋值给该子组内所有活跃调用。
子组。
|
17.12.7.
subgroupElect
| 重载 |
|
| 描述 | 如果当前调用在活动子组中具有最小的子组调用ID,则返回true。
|
17.12.8.
subgroupMax
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
归约操作。 |
17.12.9.
subgroupMin
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
归约操作。 |
17.12.10.
subgroupMul
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
归约操作。 |
17.12.10.1. subgroupExclusiveMul
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
独占前缀扫描操作。 返回活动
子组中,
子组调用ID小于当前调用ID的所有 活动调用中ID最小的调用返回 |
17.12.10.2. subgroupInclusiveMul
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
包含性前缀扫描操作。 返回活动
子组中,
子组调用ID小于等于当前调用ID的所有 注意:等价于 |
17.12.11.
subgroupOr
| 重载 |
|
| 前置条件 | T 是 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 |
归约操作。 |
17.12.12.
subgroupShuffle
| 重载 |
|
| 前置条件 | T 是具体数值标量或数值向量I 是 u32 或 i32
|
| 描述 |
返回子组调用
ID与 id 匹配的调用中的 e。
如果
|
17.12.12.1. subgroupShuffleDown
| 重载 |
|
| 前置条件 | T 是具体数值标量或数值向量
|
| 描述 |
返回子组调用
ID
与当前调用的 subgroup_invocation_id + delta 匹配的调用中的 e。
如果
如果
|
17.12.12.2. subgroupShuffleUp
| 重载 |
|
| 前置条件 | T 是具体数值标量或数值向量
|
| 描述 |
返回子组调用
ID
与当前调用的 subgroup_invocation_id - delta 匹配的调用中的 e。
如果
如果
|
17.12.12.3. subgroupShuffleXor
| 重载 |
|
| 前置条件 | T 是具体数值标量或数值向量
|
| 描述 |
返回子组调用
ID
与当前调用的 subgroup_invocation_id ^ mask 匹配的调用中的 e。
如果
如果
|
17.12.13.
subgroupXor
| 重载 |
|
| 前置条件 | T 是 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 |
归约操作。 |
17.13. 四元组操作
参见 § 15.6.4 四元组操作。
调用这些函数时:
注:对于计算着色器阶段,一致控制流的作用域是 工作组。 对于片元 着色器阶段,一致控制流的作用域是 绘制命令。 这两个作用域都大于四元组。
17.13.1.
quadBroadcast
| 重载 |
|
| 前置条件 | T 是具体数值标量或数值向量I 是 u32 或 i32
|
| 描述 |
将四元组中四元组调用
ID
与 id 匹配的调用中的 e 值,返回给四元组中的所有活动调用。
注:与 subgroupBroadcast 不同,目前 没有非常量替代方案。 |
17.13.2.
quadSwapDiagonal
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
返回四元组中对角线上的调用的e值。
即:
|
17.13.3.
quadSwapX
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
返回四元组中具有相同X维度的调用的e值。
即:
|
17.13.4.
quadSwapY
| 重载 |
|
| 前置条件 | T 是具体 数值标量或数值向量
|
| 描述 |
返回四元组中具有相同Y维度的调用的e值。
即:
|
18. 递归下降解析器的语法
本节为非规范性内容。
WGSL 语法以适合 LALR(1) 解析器的形式给出。 实现时也可以考虑使用递归下降解析器。
规范语法不能直接用于递归下降解析器,因为 其中有若干规则是左递归的。 如果被定义的非终结符在某个产生式的首位出现,则称该语法规则为直接左递归。
以下是 WGSL 语法,但经过了机械转换:
-
消除了直接和间接左递归。
-
避免了空产生式(即避免 epsilon 规则)。
-
合并了兄弟产生式中的共同前缀。
然而,它并不是 LL(1)。
对于某些非终结符,多个产生式具有相同的前瞻集。
例如,attribute 非终结符的所有产生式都以 attr 记号开头。
更微妙的例子是 global_decl,其中三个产生式都以 attribute * 短语开头,但随后分别以
fn、override 和 var 记号区分。
为简洁起见,许多记号定义未在此重复。 记号定义请参见规范的主体部分。
| '+'
| '-'
| '(' ( expression ( ',' expression )* ',' ? )?
')'
| compound_assignment_operator
| '='
| '@' ident_pattern_token ( '(' ( expression ( ',' expression )* ',' ? )?
')' )?
| '@' 'align' '(' expression ',' ? ')'
| '@' 'binding' '(' expression ',' ? ')'
| '@' 'blend_src' '(' expression ',' ? ')'
| '@' 'builtin' '(' builtin_value_name ',' ?
')'
| '@' 'diagnostic' diagnostic_control
| '@' 'group' '(' expression ',' ? ')'
| '@' 'id' '(' expression ',' ? ')'
| '@' 'location' '(' expression ',' ? ')'
| '@' 'size' '(' expression ',' ?
')'
| '&' unary_expression ( '&'
unary_expression )*
| '^' unary_expression ( '^' unary_expression )*
| '|' unary_expression ( '|' unary_expression )*
| 'false'
| 'true'
| template_elaborated_ident.post.ident
'(' ( expression ( ',' expression )* ',' ? )?
')'
| 'default'
| '.' member_ident component_or_swizzle_specifier
?
| '.' swizzle_name component_or_swizzle_specifier
?
| '[' expression ']' component_or_swizzle_specifier
?
| '%='
| '&='
| '*='
| '+='
| '-='
| '/='
| '^='
| '|='
| '@' 'compute'
| '@' 'const'
| /0[fh]/
| /[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?[fh]?/
| /[0-9]+[eE][+-]?[0-9]+[fh]?/
| /[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?[fh]?/
| /[1-9][0-9]*[fh]/
| /0[iu]?/
| /[1-9][0-9]*[iu]?/
| '(' ident_pattern_token ',' diagnostic_rule_name ','
? ')'
| unary_expression bitwise_expression.post.unary_expression
| unary_expression relational_expression.post.unary_expression
| unary_expression relational_expression.post.unary_expression
'&&' unary_expression relational_expression.post.unary_expression
( '&&' unary_expression relational_expression.post.unary_expression
)*
| unary_expression relational_expression.post.unary_expression
'||' unary_expression relational_expression.post.unary_expression
( '||' unary_expression relational_expression.post.unary_expression
)*
| '@' 'fragment'
| 'const_assert' ';'
| attribute * 'fn' ident '(' ( attribute * ident
':' type_specifier ( ',' param )* ',' ? )? ')' (
'->' attribute * ident template_elaborated_ident.post.ident
)? attribute * '{' statement * '}'
| attribute * 'var' ( _template_args_start expression ( ',' expression )* ',' ? _template_args_end )? optionally_typed_ident (
'=' expression )? ';'
| global_value_decl ';'
| 'alias' ident '=' ident template_elaborated_ident.post.ident
';'
| 'struct' ident '{' attribute * member_ident ':' type_specifier ( ',' attribute * member_ident ':' type_specifier )* ',' ?
'}'
| 'diagnostic' '(' ident_pattern_token ',' diagnostic_rule_name
',' ? ')' ';'
| 'enable' ident_pattern_token ( ',' ident_pattern_token )* ',' ?
';'
| 'requires' ident_pattern_token ( ',' ident_pattern_token )* ',' ?
';'
| attribute * 'override' optionally_typed_ident (
'=' expression )?
| 'const' optionally_typed_ident
'=' expression
| /0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+([pP][+-]?[0-9]+[fh]?)?/
| /0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fh]?/
| /0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*([pP][+-]?[0-9]+[fh]?)?/
| '@' 'interpolate' '(' ident_pattern_token
',' ? ')'
| '@' 'interpolate' '(' ident_pattern_token
',' ident_pattern_token ',' ?
')'
| '@' 'invariant'
| core_lhs_expression component_or_swizzle_specifier ?
| '&' lhs_expression
| '*' lhs_expression
| '%'
| '*'
| '/'
| '@' 'must_use'
| ident ( ':' type_specifier )?
| attribute * ident
':' type_specifier
| ident template_elaborated_ident.post.ident
| ident template_elaborated_ident.post.ident argument_expression_list
| literal
| '(' expression ')'
| shift_expression.post.unary_expression
| shift_expression.post.unary_expression greater_than unary_expression shift_expression.post.unary_expression
| shift_expression.post.unary_expression greater_than_equal unary_expression shift_expression.post.unary_expression
| shift_expression.post.unary_expression less_than unary_expression shift_expression.post.unary_expression
| shift_expression.post.unary_expression less_than_equal unary_expression shift_expression.post.unary_expression
| shift_expression.post.unary_expression
'!=' unary_expression shift_expression.post.unary_expression
| shift_expression.post.unary_expression
'==' unary_expression shift_expression.post.unary_expression
| ( multiplicative_operator unary_expression )* ( additive_operator unary_expression ( multiplicative_operator unary_expression )* )*
| attribute * 'for'
'(' for_init ? ';' expression ? ';' for_update ? ')' compound_statement
| attribute * 'if' expression compound_statement (
'else' 'if' expression compound_statement )* (
'else' compound_statement )?
| attribute * 'loop' attribute * '{' statement * ( 'continuing' attribute * '{' statement * ( 'break'
'if' expression ';' )?
'}' )? '}'
| attribute * 'switch' expression attribute * '{' switch_clause * '}'
| attribute * 'while' expression compound_statement
| ident template_elaborated_ident.post.ident
argument_expression_list
';'
| variable_or_value_statement
';'
| variable_updating_statement
';'
| assert_statement ';'
| break_statement ';'
| continue_statement ';'
| ';'
| 'discard' ';'
| 'return' expression ? ';'
| 'case' case_selector ( ',' case_selector )* ',' ?
':' ? compound_statement
| 'default' ':' ? compound_statement
| /[rgba]/
| /[rgba][rgba]/
| /[rgba][rgba][rgba]/
| /[rgba][rgba][rgba][rgba]/
| /[xyzw]/
| /[xyzw][xyzw]/
| /[xyzw][xyzw][xyzw]/
| /[xyzw][xyzw][xyzw][xyzw]/
| ( _template_args_start template_arg_expression (
',' expression )* ',' ? _template_args_end )?
| global_directive * ( global_decl | global_assert | ';' ) *
| ident ( _template_args_start template_arg_expression (
',' expression )* ',' ? _template_args_end )?
| primary_expression component_or_swizzle_specifier ?
| '!' unary_expression
| '&' unary_expression
| '*' unary_expression
| '-' unary_expression
| '~' unary_expression
| 'var' ( _template_args_start expression ( ',' expression )* ',' ? _template_args_end )? optionally_typed_ident
| variable_decl '=' expression
| 'const' optionally_typed_ident
'=' expression
| 'let' optionally_typed_ident
'=' expression
| lhs_expression ( '=' | compound_assignment_operator
) expression
| lhs_expression '++'
| lhs_expression '--'
| '_' '=' expression
| '@' 'vertex'
| '@' 'workgroup_size' '(' expression ',' ?
')'
| '@' 'workgroup_size' '(' expression ',' expression ',' ?
')'
| '@' 'workgroup_size' '(' expression ',' expression ',' expression ',' ?
')'
附录A:text/wgsl
媒体类型
互联网号码分配局(IANA)维护着媒体类型注册表,见 [IANA-MEDIA-TYPES]。
以下是 WGSL 模块的 text/wgsl 媒体类型定义。
该类型已经在 IANA 注册,
并可在 https://www.iana.org/assignments/media-types/text/wgsl
上查阅。
- 类型名称
-
text
- 子类型名称
-
wgsl
- 必需参数
-
N/A
- 可选参数
-
无
- 编码注意事项
-
二进制
WGSL 是使用 UTF-8 编码的 Unicode 文本,不包含字节序标记(BOM)。 参见 § 3 文本结构。
- 安全注意事项
-
WebGPU 着色语言(WGSL)是一种用于在 WebGPU API 上下文中执行的 GPU 代码的编程语言。安全注意事项见 [WebGPU] 第2.1节 安全注意事项。 隐私注意事项见 [WebGPU] 第2.2节 隐私注意事项。
- 互操作性注意事项
-
WebGPU 的实现可能具有不同的能力,这些差异可能会影响 WGSL 程序能够使用的功能。参见 [WebGPU] 第3.6节 可选能力, 以及 § 4.1.2 语言扩展。
预期实现会将本注册适用于 WGSL 的后续版本,其发布规范参考也会随时更新。虽然这种预期在媒体类型注册中较为少见,但符合业界广泛惯例。
- 发布规范
-
WebGPU 着色语言
- 使用此媒体类型的应用
-
WebGPU 的实现。预计包括网页浏览器。
- 片段标识符注意事项
-
无
- 附加信息
-
魔数(Magic number):无
文件扩展名:
.wgslMacintosh 文件类型代码:
TEXT - 联系人及邮箱
-
David Neto, dneto@google.com,或 WGSL 中列出的编辑。
- 预期用途
-
COMMON
- 作者
-
W3C。见 WGSL 中列出的编辑。
- 变更控制者
-
W3C
- 规范性引用
-
[WebGPU] W3C,“WebGPU” W3C 工作草案,2023年1月。https://w3.org/TR/webgpu
Webgpu 着色语言 W3C,“WebGPU Shading Language” W3C 工作草案,2023年1月。 https://w3.org/TR/WGSL