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 模块源文本及
createShaderModule
API 方法可用的其它信息。 本规范中要求程序 必须 做某事的描述,若被违反,通常会导致着色器创建错误。 -
管线创建错误 指在 管线创建 时可检测到的错误。 检测依赖 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 |
值构造器表达式中数组类型的最大元素数 | 2047 |
3. 文本结构
text/wgsl
媒体类型用于标识内容为 WGSL 模块。
参见附录 A:text/wgsl 媒体类型。
WGSL 模块是使用 UTF-8 编码的 Unicode 文本,不带字节顺序标记(BOM)。
WGSL 模块文本由一系列 Unicode 码点组成,这些码点被分组成连续且非空的集合,形成:
程序文本不得包含空码点(U+0000
)。
3.1. 解析
解析 WGSL 模块的步骤如下:
去除注释:
用空格码点(
U+0020
)替换第一个注释。重复此步骤,直到没有注释为止。
查找模板列表,使用算法,详见§ 3.9 模板列表。
解析整个文本,尝试匹配translation_unit 语法规则。 解析器使用 LALR(1) 解析器(单标记前瞻)[DeRemer1969],并有如下定制:
词法分析与语法分析交错进行,并且与上下文相关。 当解析器请求下一个标记时:
消费并忽略初始序列的空白符码点。
如果下一个码点是模板列表的开始,则消费它并返回_template_args_start。
如果下一个码点是模板列表的结束,则消费它并返回_template_args_end。
否则:
标记候选是指由剩余未消费码点的非空前缀形成的任何 WGSL 标记。
返回的标记是当前解析器状态下也是有效前瞻标记的最长标记候选。[VanWyk2007]
若出现以下情况,则会导致着色器创建错误:
-
整个源文本无法转换为有限序列的有效标记,或
-
translation_unit 语法规则不能匹配整个标记序列。
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 内建函数。 否则,任何使用都会导致着色器创建错误。 |
// 启用假设的任意精度浮点类型扩展。 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 |
支持复合值分解表达式,其根表达式为指针时,返回引用。
例如,若 同理,若 |
注意: 设计目标是 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 源中书写。
枚举 (不能在 WGSL 中书写) | 预声明枚举项 |
---|---|
访问模式 | read |
write | |
read_write | |
地址空间
注意: | function |
private | |
workgroup | |
uniform | |
storage | |
纹素格式 | rgba8unorm |
rgba8snorm | |
rgba8uint | |
rgba8sint | |
rgba16unorm | |
rgba16snorm | |
rgba16uint | |
rgba16sint | |
rgba16float | |
rg8unorm | |
rg8snorm | |
rg8uint | |
rg8sint | |
rg16unorm | |
rg16snorm | |
rg16uint | |
rg16sint | |
rg16float | |
r32uint | |
r32sint | |
r32float | |
rg32uint | |
rg32sint | |
rg32float | |
rgba32uint | |
rgba32sint | |
rgba32float | |
bgra8unorm | |
r8unorm | |
r8snorm | |
r8uint | |
r8sint | |
r16unorm | |
r16snorm | |
r16uint | |
r16sint | |
r16float | |
rgb10a2unorm | |
rgb10a2uint | |
rg11b10ufloat |
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
。
WGSL 源码中只使用特定的纹素格式。 用于定义这些纹素格式的通道格式列于 通道格式表中。 倒数第二列指定了从存储通道位到着色器内值的转换方式, 也称为通道传递函数(CTF)。 最后一列指定了从着色器值到存储通道位的转换方式, 也称为逆通道传递函数(ICTF)。
注意: 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。
-
下表最后一列采用了通道传递函数,其定义见通道格式表。
纹素格式 | 通道格式 | 内存顺序通道 | 对应着色器值 |
---|---|---|---|
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)) |
rgba16snorm | 16snorm | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) |
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) |
rg8snorm | 8snorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) |
rg8uint | 8uint | r, g | vec4<u32>(CTF(r), CTF(g), 0u, 1u) |
rg8sint | 8sint | r, g | vec4<i32>(CTF(r), CTF(g), 0, 1) |
rg16unorm | 16unorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) |
rg16snorm | 16snorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) |
rg16uint | 16uint | r, g | vec4<u32>(CTF(r), CTF(g), 0u, 1u) |
rg16sint | 16sint | r, g | vec4<i32>(CTF(r), CTF(g), 0, 1) |
rg16float | 16float | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) |
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) |
r8snorm | 8snorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) |
r8uint | 8uint | r | vec4<u32>(CTF(r), 0u, 0u, 1u) |
r8sint | 8sint | r | vec4<i32>(CTF(r), 0, 0, 1) |
r16unorm | 16unorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) |
r16snorm | 16snorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) |
r16uint | 16uint | r | vec4<u32>(CTF(r), 0u, 0u, 1u) |
r16sint | 16sint | r | vec4<i32>(CTF(r), 0, 0, 1) |
r16float | 16float | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) |
rgb10a2unorm | r, g, b: 10unorm a: 2unorm | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) |
rgb10a2uint | r, g, b: 10uint a: 2uint | r, g, b, a | vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a)) |
rg11b10ufloat | r, g: 11float b: 10float | r, g, b | vec4<f32>(CTF(r), CTF(g), CTF(b), 1.0) |
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 | 不可变 | 函数 | 具体可构造或 指针类型 | 必须 | const-expression、override-expression 或 runtime expression | 否 |
var<storage, read> var<storage> | 不可变 | 模块 | 具体 主机可共享 | 不允许 | 是。 storage buffer | |
var<storage, read_write>5,6 | 可变 | 模块 | 具体 主机可共享 | 不允许 | 是。 storage buffer | |
var<uniform> | 不可变 | 模块 | 具体 可构造 主机可共享 | 不允许 | 是。 uniform buffer | |
var6 | 不可变7 | 模块 | 纹理 | 不允许 | 是。 texture resource | |
var | 不可变 | 模块 | 采样器 | 不允许 | 是。 sampler resource | |
var<workgroup>6,8 | 可变 | 模块 | 具体 普通类型,且具备固定占用9 | 不允许10 | 否 | |
var<private> | 可变 | 模块 | 具体 可构造 | 可选10 | const-expression 或 override-expression | 否 |
var<function> var | 可变 | 函数 | 具体 可构造 | 可选10 | const-expression、override-expression 或 runtime expression | 否 |
-
若未指定初始值,则必须在管线创建时提供值。
-
override 声明属于着色器接口,但不是绑定资源。
-
存储缓冲区和 存储纹理(访问模式非read)不能在 顶点着色器阶段中静态访问。 详见 WebGPU
createBindGroupLayout()
。 -
原子类型只能出现在可变存储缓冲区或 workgroup 变量中。
-
存储纹理中,访问模式为write或read_write 的访问模式数据是可变的,但只能通过textureStore内建函数修改。 变量本身不能被修改。
-
最外层数组的元素数量可以是override-expression。
-
若无初始值,变量会默认初始化。
7.1. 变量与值
变量声明是 WGSL 模块中唯一可变的数据。 值声明始终是不可变的。 变量可以作为引用类型和指针类型值的基础,因为变量有相关联的内存位置, 而值声明不能作为指针或引用值的基础。
通常,使用变量比使用值声明代价更高, 因为对变量的使用需要额外的操作来读取或 写入变量关联的内存位置。
一般来说,建议作者按如下顺序优先使用声明,最优先的在前:
这样通常能获得着色器的最佳整体性能。
7.2. 值声明
WGSL 提供多种值声明。 每种声明的值在着色器生命周期的不同阶段被固定。 不同种类的值声明及其值被固定的时机如下:
7.2.1. const
声明
const 声明 为一个在着色器创建时固定的数据值指定名称。 每个 const 声明都需要一个初始值。 const 声明可以在模块或函数作用域中声明。 初始值表达式必须为const-expression。 const 声明的类型必须是具体或抽象的可构造类型。 const 声明是唯一允许实际值类型为抽象的声明。
注意: 由于抽象数值类型不能在 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 声明为可管线覆盖常量值指定名称。 override 声明只能在模块作用域声明。 可管线覆盖常量的值在管线创建时确定。 该值由 WebGPU 管线创建 API 提供(如有),否则为其具体化初始值表达式的值。 override 声明的实际值类型必须是具体 标量类型。
初始值表达式为可选项。 如指定,必须是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 声明 为一个在运行时每次执行该语句时固定的值指定名称。 let 声明只能在函数作用域声明,因此属于动态上下文。 let 声明必须有初始值表达式。 该值为初始值的具体化值。 let 声明的实际值类型必须是具体 可构造类型或指针类型。
// '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 变量的初始值必须为 const-expression 或 override-expression。 除 function 和 private 外,其他地址空间的变量不得有初始值。
当标识符解析为变量声明时, 该标识符是表示该变量内存的内存视图的表达式,其类型为变量的引用类型。 参见 § 8.11 变量标识符表达式。
如变量声明的地址空间或访问模式在源码中指定,则写作 var
关键字后的模板参数列表:
private、storage、 uniform、workgroup 和 handle 地址空间的变量只能在模块作用域声明, function 地址空间变量只能在函数作用域声明。 除 handle 和 function 外,所有地址空间都必须显式指定。 handle 地址空间不得指定。 function 地址空间可省略不写。
访问模式总有默认值,除 storage 地址空间外,不得在 WGSL 源码中指定。参见 § 14.3 地址空间。
uniform 地址空间变量为uniform 缓冲区变量。 其存储类型必须为主机可共享的可构造类型,并满足地址空间布局约束。
storage 地址空间变量为storage 缓冲区变量。 其存储类型必须为主机可共享类型,且满足 地址空间布局约束。 变量可声明为read或read_write访问模式,默认 read。
纹理资源是实际值类型为纹理类型的变量。 只能在模块作用域声明。 它保存一个不透明句柄,用于访问纹理中底层纹素的网格。 该句柄本身在handle地址空间,总是只读。 多数情况下,底层纹素只读,此时纹理变量为不可变;对于只写存储纹理和读写存储纹理,底层纹素可变,约定为可变变量。
采样器资源是实际值类型为采样器类型的变量。 只能在模块作用域声明, 存于handle地址空间,且不可变。
如§ 13.3.2 资源接口所述,uniform 缓冲区、storage 缓冲区、纹理和采样器共同构成着色器的资源接口。
变量生命周期是着色器执行期间,与变量关联的内存位置的存活期。 模块作用域变量的生命周期为整个着色器阶段的执行期。 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
是变量,对其的所有访问都转化为 load/store 操作。
但通常浏览器或驱动会优化中间表示,消除冗余的 load 操作。
var < private> decibels : f32; var < workgroup> worklist : array< i32, 10 > ; struct Params { specular : f32, count : i32} // Uniform 缓冲区。始终只读,且有更严格的布局规则。 @group ( 0 ) @binding ( 2 ) var < uniform> param : Params ; // 一个 uniform 缓冲区 // 一个可读写的 storage 缓冲区 @group ( 0 ) @binding ( 0 ) var < storage, read_write> pbuf : array< vec2< f32>> ; // 纹理和采样器总是在 "handle" 空间。 @group ( 0 ) @binding ( 1 ) var filter_params : sampler;
// Storage 缓冲区 @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 缓冲区。始终只读,且有更严格的布局规则。 @group ( 0 ) @binding ( 2 ) var < uniform> params : ParamsTable ; // 只读,不能写。
fn f () { var < function> count : u32; // 函数地址空间变量。 var delta : i32; // 另一个函数地址空间变量。 var sum : f32= 0.0 ; // 带初值的函数地址空间变量。 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 声明无显式类型,因此会重载解析。 可用候选项通过可行自动转换将 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 is 3 or 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 |
选择向量的第 i 个分量 第一个分量下标为 i=0。 若 i 不在 [0,N-1] 范围内:
|
e: vecN<T> i: i32 或 u32 T 为抽象类型 i 为const-expression | e[i]: T |
选择向量的第 i 个分量 第一个分量下标为 i=0。 若 i 不在 [0,N-1] 范围内,则为着色器创建错误。 注意: 当抽象向量值 e 由非常量表达式索引时,索引前会对向量进行具体化。 |
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 或
w AM 为 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 或
a AM 为 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 或 w AM 为 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 或 a AM 为 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中的第一个词法单元时,其含义有:
声明与作用域规则保证这些名称总是唯一的。
注意: call_expression 规则确保对调用表达式进行类型检查。
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'
关键字不得在同一个case_selector列表中出现多次。
类型规则前置条件: 对于同一 switch 语句,选择器表达式和所有 case 选择器表达式必须是相同的具体整型标量类型。
case_selectors中的表达式必须是常量表达式。
同一 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 (initializer; condition; update_part) { body }
,其本质是对具有相同body
的loop语句的语法糖。
此外:
-
-
如有条件,则每次执行循环体前立即判断。 若条件为假,则执行§ 9.4.6 Break Statement,循环结束。 该判断每次循环迭代开始时执行。
-
-
如果
update_part
非空,则会作为continuing语句出现在循环体结尾。
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 ++ ; } } }
如果for循环会执行无限多次迭代,则会发生动态错误。 这可能导致循环提前终止、其它非局部影响,甚至设备丢失。
9.4.5. While 语句
attribute * 'while'
expression compound_statement
while 语句是一种带条件的循环。 每次循环迭代开始时,会计算一个布尔条件。 如果条件为假,则 while 循环结束执行。 否则,执行本次迭代剩余部分。
while 循环可视为loop或for语句的语法糖。 下列三种写法等价:
-
while
condition{
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 语句中声明的变量的地方。
注意: continue
只能在 continuing
语句内用于跳转到嵌套在 continuing
内的其他循环的控制流。也就是说,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 一致性)需要知道控制流何时会以多种方式退出某语句。
这两个目标都通过一种语句执行行为汇总系统实现。行为分析将每个语句映射为该语句求值完毕后执行可继续的方式集合。 类似于值和表达式的类型分析,行为分析自底向上进行:先确定某些基本语句的行为,再通过组合规则确定更高级结构的行为。
行为是一个集合,元素可以为:
-
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 {Continue, Return} 均不在 B2 中 Break 不在 (B1 ∪ B2) 中 | (B1 ∪ B2)∖{Continue, Next} |
s1: B1 s2: B2 {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} 或已由先前分析的函数导致错误。
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. 常量断言语句
常量断言语句是一种断言,
如果表达式的值为false
,则会产生着色器创建错误。
表达式必须是常量表达式。
该语句可以满足着色器中的静态访问条件,否则对编译后的着色器没有影响。
常量断言可以出现在模块作用域或作为函数作用域的语句。
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 资源接口。 |
参数 | 必须是常量表达式,解析为i32或u32类型。 必须为非负数。 |
12.3. blend_src
'@'
'blend_src'
'('
expression ','
?
')'
描述 |
当启用特性dual_source_blending时,
指定片元输出的一部分。
参见§ 13.3.1.3 输入输出 location。
只能用于带有location属性的结构体类型成员。 只能用于声明数值标量或数值向量类型对象。 不能用于着色器阶段输入。 不能用于着色器阶段输出声明, 除非是片元着色器阶段。 |
参数 | 必须是常量表达式,
解析为0 或1 的i32或u32值。
|
12.4. builtin
'@'
'builtin'
'('
builtin_value_name ','
? ')'
描述 | 指定关联对象是由指定token标注的内建值。 参见§ 13.3.1.1 内建输入与输出。 |
参数 | 必须为某内建值的内建值名 token。 |
12.5. const
'@'
'const'
描述 |
指定函数可以作为常量函数使用。
该属性不能应用于用户自定义函数。
只能用于函数声明。 注意: 此属性作为记号惯例用于描述哪些内建函数可以用于常量表达式。 |
参数 | 无 |
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 资源接口。 |
参数 | 必须是常量表达式,
解析为i32或u32类型。 必须为非负数。 |
12.8. id
'@'
'id'
'('
expression ','
?
')'
描述 |
为可管线覆盖的常量指定一个数值标识符作为别名。
只能用于override 声明,且类型为标量类型。 |
参数 | 必须是常量表达式,解析为i32或u32。 必须为非负数。 |
12.9. interpolate
'@'
'interpolate'
'('
interpolate_type_name ','
? ')'
| '@'
'interpolate'
'('
interpolate_type_name ','
interpolate_sampling_name ','
? ')'
描述 | 指定用户自定义 IO 必须如何插值。 该属性仅对用户自定义顶点输出和片元输入有效。 参见§ 13.3.1.4 插值。 |
参数 |
第一个参数必须为某插值类型的插值类型名
token。
如果有第二个参数,则必须为该插值采样的插值采样名 token。 |
12.10. invariant
'@'
'invariant'
描述 |
当应用于顶点着色器的position 内建输出值
时,其结果的计算在不同程序和相同入口点的不同调用间保持不变。
即,如果两个入口点的 position 输出数据和控制流一致,则结果值必然相同。
对 position 内建输入值无影响。
注意: 此属性对应 HLSL 的 |
参数 | 无 |
12.11. location
'@'
'location'
'('
expression ','
?
')'
描述 |
指定入口点的用户自定义 IO 的一部分。
参见§ 13.3.1.3 输入输出 location。
只能用于入口点函数参数、入口点 返回类型,或结构体类型成员。 只能用于声明数值标量或数值向量类型对象。 不能用于计算着色器输入。 |
参数 | 必须是常量表达式,解析为i32或u32。 必须为非负数。 |
12.12. must_use
'@'
'must_use'
描述 |
指定对此函数的调用必须作为表达式使用。
即,对该函数的调用不能单独作为函数调用语句。
注意:
许多函数返回值且无副作用。将这类函数调用作为函数调用语句通常是编程缺陷。带有此属性的内建函数声明为 注意: 如需特意绕过 |
参数 | 无 |
12.13. size
'@'
'size'
'('
expression ','
?
')'
描述 |
指定结构体成员预留的字节数。
若 参见§ 14.4 内存布局。 |
参数 | 必须是常量表达式,解析为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。 |
参数 |
可带一、二或三个参数。
每个参数必须是常量表达式或override 表达式。 所有参数必须为同一类型,即i32或u32。 如果任一参数为常量表达式且其值不为正数,则会导致着色器创建错误。 如果参数值不为正数、超过 WebGPU API 指定的上限,或参数值乘积超过 WebGPU API 指定的上限(参见 WebGPU § 3.6.2 Limits),则会导致管线创建错误。 |
12.15. 着色器阶段属性
下列着色器阶段属性 用于将函数指定为某一特定着色器阶段的入口点。 这些属性只能用于函数声明, 且每个函数至多只能有一个。 它们不带参数。
12.15.1. vertex
'@'
'vertex'
vertex
属性声明该函数为渲染管线的顶点着色器阶段的入口点。
12.15.2. fragment
'@'
'fragment'
fragment
属性声明该函数为渲染管线的片元着色器阶段的入口点。
12.15.3. 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
属性。
入口点的形式参数表示该阶段的着色器阶段输入。 入口点的返回值(如有)表示该阶段的着色器阶段输出。
每个形式参数类型和入口点返回类型必须为以下之一:
结构体类型可以用来将用户自定义输入与其他输入及可选的内建输入组合。 结构体类型也可以作为返回类型,将用户自定义输出与其他输出及可选的内建输出组合。
注意: bool 类型不允许用于用户自定义输入/输出, 仅允许用于front_facing 内建值。
注意: 计算入口点不能有返回类型。
@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 被着色器静态访问:
-
在着色器阶段的函数声明中出现了解析为 D 的标识符。
-
用于为静态访问声明定义类型时,出现了解析为 D 的标识符。
-
用于静态访问声明的初始化器中,出现了解析为 D 的标识符。
-
用于静态访问声明使用的属性中,出现了解析为 D 的标识符。
-
函数声明的所有部分,包括属性、形式参数、返回类型和函数体。
-
定义上述内容所需的任意类型,包括跟随类型别名。
-
作为类型定义的特殊情况, 用于 override 表达式 的 override 声明,若该表达式作为 数组类型变量的 元素数量,且该变量处于 workgroup 地址空间,并被静态访问。
-
为支持上述任意 override 表达式求值所用的 override 声明。
-
上述任意内容上的属性。
现在可以精确定义着色器的接口包含:
-
入口点的返回值。 表示着色器阶段输出。
-
被着色器静态访问的uniform buffer、storage buffer、texture resource、sampler resource变量。
-
被着色器静态访问的override 声明。
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 builtin 既是顶点着色器的输出,也是片元着色器的输入。
内建输入值和内建输出值统称为内建值。
下表总结了可用的内建值。 每项为某内建值的内建值名 token。具体细节见后续章节。
名称 | 阶段 | 方向 | 类型 | 扩展 |
---|---|---|---|---|
vertex_index | vertex | input | u32 | |
instance_index | vertex | input | u32 | |
clip_distances | vertex | output | array<f32, N> (N ≤ 8 )
| clip_distances |
position | vertex | output | vec4<f32> | |
fragment | input | vec4<f32> | ||
front_facing | fragment | input | bool | |
frag_depth | fragment | output | f32 | |
sample_index | fragment | input | u32 | |
sample_mask | fragment | input | u32 | |
fragment | output | u32 | ||
local_invocation_id | compute | input | vec3<u32> | |
local_invocation_index | compute | input | u32 | |
global_invocation_id | compute | input | vec3<u32> | |
workgroup_id | compute | input | vec3<u32> | |
num_workgroups | compute | input | vec3<u32> | |
subgroup_invocation_id | compute | input | u32 | subgroups |
fragment | ||||
subgroup_size | compute | input | u32 | subgroups |
fragment |
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. instance_index
名称 | instance_index |
阶段 | vertex |
类型 | u32 |
方向 | 输入 |
描述 |
当前顶点在本次 API 级 draw 命令中的实例索引。
第一个实例的索引等于 draw 的 |
13.3.1.1.6. local_invocation_id
名称 | local_invocation_id |
阶段 | compute |
类型 | vec3<u32> |
方向 | 输入 |
描述 | 当前调用的局部调用ID,即其在工作组网格中的位置。 |
13.3.1.1.7. local_invocation_index
名称 | local_invocation_index |
阶段 | compute |
类型 | u32 |
方向 | 输入 |
描述 | 当前调用的局部调用索引,即其在工作组网格中的线性化序号。 |
13.3.1.1.8. 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.9.
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.10. sample_index
名称 | sample_index |
阶段 | fragment |
类型 | u32 |
方向 | 输入 |
描述 |
当前片元的采样索引。其值至少为 0,至多为 sampleCount - 1,其中 sampleCount 是为 GPU 渲染管线指定的
MSAA 采样
count 。
当应用该属性时,如果片元着色器的效果会根据 sample_index 的值变化,则片元着色器会对每个采样点各调用一次。
|
13.3.1.1.11. sample_mask
名称 | sample_mask |
阶段 | fragment |
类型 | u32 |
方向 | 输入 |
描述 | 当前片元的采样覆盖掩码。它包含一个位掩码,指示该片元中哪些采样点被渲染图元覆盖。 |
名称 | sample_mask |
阶段 | fragment |
类型 | u32 |
方向 | 输出 |
描述 | 当前片元的采样覆盖掩码控制。最后写入该变量的值将成为着色器输出掩码。写入值中的为 0 的位会导致颜色附件中对应采样点被丢弃。 |
13.3.1.1.12. vertex_index
名称 | vertex_index |
阶段 | vertex |
类型 | u32 |
方向 | 输入 |
描述 |
当前顶点在本次 API 级 draw 命令中的索引,与实例化无关。
非索引绘制时,第一个顶点的索引等于 draw 的 索引绘制时,索引等于索引缓冲区当前顶点的条目加上 draw 的 |
13.3.1.1.13. 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.14. subgroup_invocation_id
名称 | subgroup_invocation_id |
阶段 | compute 或 fragment |
类型 | u32 |
方向 | 输入 |
描述 |
当前调用的子组调用ID。
该ID的范围为 [0, subgroup_size - 1]。 |
13.3.1.1.15. subgroup_size
名称 | subgroup_size |
阶段 | compute 或 fragment |
类型 | u32 |
方向 | 输入 |
描述 | 当前调用所在子组的子组大小。 |
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
-
在当前 primitive 内所有被片元覆盖的采样点的内部某一点进行插值。这个值在整个 primitive 的所有采样点中是相同的。
- sample
-
按采样点插值。 当应用此属性时,片元着色器会对每个采样点各调用一次。
- first
-
值由 primitive 的第一个顶点提供。
- either
-
值由 primitive 的第一个或最后一个顶点提供,由实现决定具体是哪一个。
对于标量或向量浮点类型的用户自定义 IO:
-
如果未指定插值属性,则默认为
@interpolate(perspective, center)
。 -
如果指定了插值类型:
标量或向量整型的用户自定义顶点输出和片元输入必须始终指定插值类型为 flat
。
阶段间接口校验会检查在渲染管线内, 每个用户自定义片元输入的插值属性与同一location分配的顶点输出插值属性是否一致。 否则会产生管线创建错误。
13.3.2. 资源接口
资源是一个对象,用于访问某着色器阶段外部的数据, 且不是override 声明,也不是着色器阶段输入或输出。 资源由着色器的所有调用共享。
资源分为四类:
着色器的资源接口是该阶段中 着色器阶段函数 静态访问到的所有模块级资源变量的集合。
每个资源变量必须同时声明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 | 同一计算着色器工作组内的调用 | read_write | 最外层数组的元素数量可为可管线覆盖常量。 |
uniform | 同一着色器阶段内的调用 | read | 针对均匀缓冲区变量 |
storage | 同一着色器阶段内的调用 | read | 针对存储缓冲区变量 |
handle | 同一着色器阶段的调用 | read | 针对采样器与纹理变量。 |
WGSL 预声明了每个地址空间的枚举值,但 handle
地址空间除外。
workgroup 地址空间中的变量 只能 在计算着色器阶段中静态访问。
storage 地址空间(存储缓冲区)中的变量,只能被顶点着色器阶段静态访问,且访问模式为read。
存储类型为storage
texture、且访问模式为write或read_write的变量,不可被顶点着色器阶段静态访问。
参见 WebGPU createBindGroupLayout()
。
注意: 各地址空间的性能特性可能不同。
14.4. 内存布局
WGSL 中类型的布局与地址空间无关。 严格来说,该布局只有主机可共享 缓冲区才能被观测到。 均匀缓冲区和存储缓冲区变量 用于以内存中的字节序列共享大批量数据。 缓冲区可以在 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个成员距离结构体开头的偏移。
-
StrideOf(A) 表示A的元素步长, 即从一个数组元素起始到下一个元素起始的字节数。 等于元素类型大小向上取整至元素类型对齐:
StrideOf(array<E, N>) = roundUp(AlignOf(E), SizeOf(E))
StrideOf(array<E>) = roundUp(AlignOf(E), SizeOf(E))
14.4.1. 对齐与大小
对齐是类型在内存中可放置位置的约束,表现为一个整数: 类型的对齐必须整除该类型值起始内存位置的字节地址。 对齐有助于使用更高效的硬件指令访问值,或满足特定地址空间更严格的硬件要求。(参见地址空间布局约束)。
注意: 每个对齐值始终是 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 的运行时元素数量 |
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 字节,但未规定其内部布局。
当类型为 u32 或 i32 的值 V 放在主机共享缓冲区字节偏移 k 时:
-
字节 k 存放 V 的第 0~7 位
-
字节 k+1 存放第 8~15 位
-
字节 k+2 存放第 16~23 位
-
字节 k+3 存放第 24~31 位
注意: i32 使用补码表示,符号位在第 31 位。
64 位整数布局: WebGPU API 某些特性会将 64 位无符号整数写入缓冲区。若此值 V 放在主机共享缓冲区字节偏移 k,则:
-
字节 k 存放 V 的第 0~7 位
-
字节 k+1 存放第 8~15 位
-
字节 k+2 存放第 16~23 位
-
字节 k+3 存放第 24~31 位
-
字节 k+4 存放第 32~39 位
-
字节 k+5 存放第 40~47 位
-
字节 k+6 存放第 48~55 位
-
字节 k+7 存放第 56~63 位
类型为f32的值V以 IEEE-754 binary32格式表示。 1 位符号,8 位指数,23 位尾数。 放在主机共享缓冲区字节偏移 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格式表示。 1 位符号,5 位指数,10 位尾数。 放在主机共享缓冲区字节偏移 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), C 非 uniform | RequiredAlignOf(S, C), 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) | 不适用 |
struct S | AlignOf(S) | roundUp(16, AlignOf(S)) |
类型为 T 的结构体成员 必须具有从结构体起始处为 RequiredAlignOf(T, C) 的整数倍的字节偏移(C为地址空间):
OffsetOfMember(S, i) = k × RequiredAlignOf(T, C)
其中 k 为非负整数,结构体 S 的第 i 个成员类型为 T
元素类型为 T 的数组 必须具有为 RequiredAlignOf(T, C) 整数倍的元素步长(C为地址空间):
StrideOf(array<T, N>) = k × RequiredAlignOf(T, C)
StrideOf(array<T>) = k × RequiredAlignOf(T, C)
其中 k 为正整数
uniform 地址空间还要求:
-
数组元素必须 16 字节对齐。 即 StrideOf(array<T,N>) = 16 × k',其中 k' 为正整数。
-
若结构体成员本身为结构体类型
S
,则该成员起始字节至下一个成员起始字节之间的字节数 必须 不小于 roundUp(16, SizeOf(S))。
注意: 下例展示如何在结构体成员上使用 align 和 size 属性 来满足 uniform 缓冲区的布局要求。 这些方法尤其可用于将带 std140 布局的 GLSL 缓冲区机械转换为 WGSL。
struct S { x : f32} struct Invalid { a : S , b : f32// 无效:a 和 b 之间偏移为 4 字节,但必须至少为 16 } @group ( 0 ) @binding ( 0 ) var < uniform> invalid : Invalid ; struct Valid { a : S , @align ( 16 ) b : f32// 有效:a 和 b 之间偏移为 16 字节 } @group ( 0 ) @binding ( 1 ) var < uniform> valid : Valid ;
struct small_stride { a : array< f32, 8 > // stride 4 } // 无效,stride 必须为 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 ; // 有效
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 的选择器,或短路二元操作符(
&&
或||
)的左操作数计算出不同的值时,就会出现非一致控制依赖。
这些非一致值通常可以追溯到一些未被静态证明为一致的源头。 这些源头包括但不限于:
-
大多数内置值,除了 num_workgroups 和 workgroup_id
为确保正确且可移植的行为,WGSL 实现需 执行静态一致性分析,尝试证明每个集体操作都在一致控制流中执行。 后续小节描述了该分析方法。
当一致性分析无法证明某个集体操作 在一致控制流中执行时,将触发一致性失败 。
-
如果为计算导数的内置函数触发了一致性失败, 则会 触发derivative_uniformity诊断。
-
如果为subgroup或 quad内置函数触发了一致性失败, 则会触发subgroup_uniformity 诊断
-
诊断的触发位置为该内置函数的调用点,或在 subgroupShuffleUp、 subgroupShuffleDown、 subgroupShuffleXor 的情况下为需要一致性的参数位置
-
15.2.1. 术语与概念
以下定义仅为说明性,旨在帮助直观理解下一小节分析所计算的内容。 真正定义这些概念,以及何时程序有效或违反一致性规则的,是后面的分析。
对于给定的一组调用:
-
如果某一作用域内的所有调用在程序的某一点看起来如同以锁步方式同步执行,则该点具有一致控制流。
-
对于计算着色器阶段,一致控制流的作用域为同一工作组(workgroup)内的所有调用。
-
-
如果一个表达式在一致控制流中执行,并且所有调用计算出相同的值,则称其为一致值。
-
如果在局部变量生命周期内,所有调用在每个时刻都持有该变量的相同值,则称其为一致变量。
15.2.2. 一致性分析概述
接下来的小节规定了一种静态分析方法,用于验证集体操作仅在一致控制流中执行。
分析假定不会发生动态错误。 无论一致性分析结果如何,包含动态错误的着色器阶段本就不可移植。
每个函数都会被分析,需确保两点:
-
调用其他函数时满足一致性要求,并且
-
每当被调用时也能满足一致性要求。
作为这项工作的组成部分,分析会计算有关函数的元数据,以协助分析其调用者。 这意味着首先要构建调用图,并从叶子节点(即只调用标准库外无其他函数的函数)开始向上传递分析,直到入口点。 这样,每当分析某个函数时,其所有被调用者的元数据都已计算好。 由于语言中禁止递归,因此不会陷入循环分析的风险。
注意:换句话说,就是对函数按“可能被(直接或间接)调用”这个偏序做拓扑排序,并按此顺序分析。
此外,对于每一次函数调用,分析会计算并传播该调用若无法证明处于一致控制流时会触发的触发规则集合(如有)。 我们称这为该调用的潜在触发集(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), V(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) |
loop { s1 continuing { s2 } } | Vin(s2) | Vout(s1), Vout(si) 对 s1 中所有行为为 {Continue} 并转移控制到 s2 的 si |
loop { s1 continuing { s2 } } | Vin(next) | Vout(s2), Vout(si) 对 s1 中所有行为为 {Break} 并转移控制到 next 的 si |
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) 对 sj 中所有行为为 {Break} 并转移控制到 next 的语句 |
对所有其它语句(函数调用除外),Vin(next) 等价于 Vout(prev)。
注意:同语句行为分析章节,消糖规则同样适用。
15.2.6. 语句一致性规则
分析语句的规则以语句本身和对应于其开始时控制流的节点(下文记作 "CF")为参数,并返回以下两项:
-
对应于语句出口时控制流的节点
-
要添加到图中的新节点和边的集合
在下表中,(CF1, S) => CF2
表示“以控制流 CF1 为起点分析 S,对图应用所需更改,并将结果控制流命名为 CF2”。
类似地,(CF1, E) => (CF2, V)
表示“以控制流 CF1 为起点分析表达式 E,对图应用所需更改,并将结果控制流节点命名为 CF2,结果值节点命名为
V”(分析表达式详见下一节)。
这种表达式求值用于所有不属于左值的表达式,并称为RHSValue。
对于属于左值的表达式,也有类似的规则,LHSValue,记作
LHSValue: (CF, E) => (CF, L)
。它不是计算值的一致性节点,而是计算我们要访问的变量的一致性节点。
注意:LHSValue 包括 自增语句和 自减语句中的表达式。
注意:RHSValue 包括赋值语句右值部分的表达式,以及不属于赋值、自增或自减语句的表达式。
当需要创建多条边时,我们用 X -> {Y, Z}
作为 X -> Y, X -> Z
的简写。
语句 | 新节点 | 递归分析 | 结果控制流节点 | 新边 |
---|---|---|---|---|
{s} | (CF, s) => CF' | CF' | ||
s1 s2, Next 属于 s1 行为 注意: s1 通常以分号结尾。 | (CF, s1) => CF1 (CF1, s2) => CF2 | CF2 | ||
s1 s2, Next 不属于 s1 行为 注意: s1 通常以分号结尾。 |
(CF, s1) => CF1 注意: s2 静态不可达,不递归分析,不参与一致性分析。 | CF1 | ||
if e s1 else s2 行为为 {Next} | (CF, e) => (CF', V) (V, s1) => CF1 (V, s2) => CF2 | CF | ||
if e s1 else s2 其它行为 | CFend | CFend | CFend -> {CF1, CF2} | |
loop {s1 continuing {s2}} 行为为 {Next} | CF' | (CF', s1) => CF1 (CF1, s2) => CF2 | CF | CF' -> {CF2, CF} |
loop {s1 continuing {s2}} 其它行为 | CF' | |||
loop {s1} 行为为 {Next} | CF' | (CF', s1) => CF1 | CF | CF' -> {CF1, CF} |
loop {s1} 其它行为 | CF' | |||
switch e case _: s_1 .. case _: s_n 行为为 {Next} | (CF, e) => (CF', 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 | 注意:若 x 是function 地址空间变量,CF 作为值分析中的零值初值。 | ||
break; | ||||
continue; | ||||
break if e; | (CF, e) => (CF', V) | CF' | ||
return; | CF | 对每个function 地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(见§ 15.2.5 函数作用域变量值分析) | ||
return e; | (CF, e) => (CF', V) | CF' |
Value_return ->
V
对每个function 地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(见§ 15.2.5 函数作用域变量值分析) | |
e1 = e2; | LHSValue:
(CF, e1) => (CF1, LV) (CF1, e2) => (CF2, RV) | CF2 |
LV -> RV
注意: LV 是值分析的结果值。 | |
_ = e | (CF, e) => (CF', V) | CF' | ||
let x = e; | (CF, e) => (CF', V) | CF' | ||
var x = e; | (CF, e) => (CF', V) | CF' | 注意:若 x 是function 地址空间变量, V 作为值分析中的结果值。 |
for 和 while 循环的分析来自它们对loop语句的消糖转换。
在switch中,default-alone 子句块在一致性上与case 子句完全等同处理。
为了最大化性能,实现通常会尽量减少非一致控制流的数量。 然而,在哪些点上可认为调用是一致的会因多种因素而异。WGSL 的静态分析保守地假设在 if、switch 和 loop 语句末尾会恢复到一致控制流(如果 行为为 {Next})。 这在上表中体现为结果控制流节点与输入控制流节点相同。
15.2.7. 函数调用一致性规则
函数调用的规则最为复杂:
-
对每个参数,应用相应的表达式规则,控制流为前一个参数出口处的控制流(第一个参数用函数调用开始处的控制流)。将相应的值节点命名为 arg_i,控制流节点命名为 CF_i
-
新建两个节点,分别命名为 Result 和 CF_after
-
如果该函数的调用点标签为 CallSiteRequiredToBeUniform.S,则:
-
从RequiredToBeUniform.S到最后一个CF_i添加一条边。
-
将调用点标签的潜在触发集成员加入到 RequiredToBeUniform.S 关联的潜在触发集中。
-
-
如果函数标签为ReturnValueMayBeNonUniform,则从Result到MayBeNonUniform添加一条边
-
对于每个参数 i:
-
如果对应参数标签为ParameterRequiredToBeUniform.S,则:
-
从RequiredToBeUniform.S到arg_i添加一条边。
-
将参数标签的潜在触发集成员加入到RequiredToBeUniform.S关联的潜在触发集中。
-
-
如果参数返回标签为ParameterReturnContentsRequiredToBeUniform,则从Result到arg_i添加一条边
-
如果对应参数的指针参数标签为PointerParameterMayBeNonUniform,则从Vout(call)到MayBeNonUniform添加一条边
-
如果参数为function地址空间的指针,则从Vout(call)到之前记录的可达参数的每个arg_i添加一条边
-
如果参数标签为ParameterContentsRequiredToBeUniform.S,则从RequiredToBeUniform.S到Vout(call)添加一条边
-
-
注意:Vout(call)的定义见§ 15.2.5 函数作用域变量值分析。
大多数内置函数具有如下标签:
-
每个参数:
下列为例外情况:
-
调用§ 17.11 同步内置函数中的函数:
-
调用点标签为CallSiteRequiredToBeUniform.error,其潜在触发集包含一个无名触发规则。
-
注意:该触发规则没有名字,无法被过滤。
-
-
对于workgroupUniformLoad的调用,参数
p
有参数标签为ParameterRequiredToBeUniform.error,其潜在触发集包含一个无名触发规则。
-
调用§ 17.6 导数内置函数、§ 17.7.8 textureSample、§ 17.7.9 textureSampleBias 和 § 17.7.10 textureSampleCompare 中的函数:
-
调用点标签如下:
-
设 DF 为调用点位置及触发规则derivative_uniformity的最近外包诊断过滤器
-
若 DF 存在,则令 S 为 DF 的新严重性参数。
-
若 S 为严重性 off,则调用点标签为CallSiteNoRestriction。
-
否则调用点标签为CallSiteRequiredToBeUniform.S,其潜在触发集包含一个derivative_uniformity元素。
-
-
若无 DF,调用点标签为CallSiteRequiredToBeUniform.error,其潜在触发集包含一个derivative_uniformity元素。
-
-
-
函数标签如下:
-
若
t
参数对应的参数是读写存储纹理,则为ReturnValueMayBeNonUniform
-
-
调用§ 17.12 子组内置函数或§ 17.13 Quad 操作:
-
设 DF 为调用点位置及触发规则subgroup_uniformity的最近外包诊断过滤器
-
调用点标签如下:
-
若 DF 存在,则令 S 为 DF 的新严重性参数。
-
若 S 为严重性 off,则调用点标签为CallSiteNoRestriction。
-
否则,调用点标签为CallSiteRequiredToBeUniform.S,其潜在触发集包含一个subgroup_uniformity元素。
-
-
若无 DF,调用点标签为CallSiteRequiredToBeUniform.error,其潜在触发集包含一个subgroup_uniformity元素。
-
-
对于subgroupShuffleUp或subgroupShuffleDown,参数
delta
有如下参数标签:-
若 DF 存在,则令 S 为 DF 的新严重性参数。
-
若 S 为严重性 off,则参数标签为NoRestriction。
-
否则,参数标签为ParameterRequiredToBeUniform.S,其潜在触发集包含一个subgroup_uniformity元素。
-
-
若无 DF,参数标签为ParameterRequiredToBeUniform.error,其潜在触发集包含一个subgroup_uniformity元素。
-
-
对于subgroupShuffleXor,参数
mask
有如下参数标签:-
若 DF 存在,则令 S 为 DF 的新严重性参数。
-
若 S 为严重性 off,则参数标签为NoRestriction。
-
否则,参数标签为ParameterRequiredToBeUniform.S,其潜在触发集包含一个subgroup_uniformity元素。
-
-
若无 DF,参数标签为ParameterRequiredToBeUniform.error,其潜在触发集包含一个subgroup_uniformity元素。
-
注意:WGSL 实现会确保如果函数调用前的控制流是一致的,则调用后也会是一致的。
15.2.8. 表达式一致性规则
分析表达式的规则以表达式本身和对应于其开始时控制流的节点(下文记作 "CF")为参数,并返回以下内容:
-
对应于表达式出口时控制流的节点
-
对应于表达式值的节点
-
要添加到图中的新节点和边的集合
表达式 | 新节点 | 递归分析 | 结果控制流节点, 值节点 | 新边 |
---|---|---|---|---|
e1 || e2 | (CF, e1) => (CF1, V1) (V1, e2) => (CF2, V2) | CF, V2 | ||
e1 && e2 | ||||
字面量 | CF, CF | |||
identifier 解析到函数作用域变量 "x",该标识符作为根标识符出现在内存视图表达式 MVE 中,并且加载规则在 MVE 上于类型检查期间被调用 | Result | X 是在包含此表达式的语句入口时 "x" 的值节点 | CF, Result |
Result -> {CF, X}
注意: X 等价于 "x" 的
Vout(prev) |
identifier 解析到函数作用域变量 "x",其中 "x" 是消糖后的指针参数 i,并且该标识符作为根标识符出现在内存视图表达式 MVE 中,且加载规则在 MVE 上于类型检查期间未被调用 | CF, param_i | |||
identifier 解析到函数作用域变量 "x",该标识符作为根标识符出现在内存视图表达式 MVE 中,且加载规则在 MVE 上于类型检查期间未被调用 | CF, CF | |||
identifier 解析到 const 声明、override 声明、 let 声明,或非指针类型形参 "x" | Result | X 为 "x" 的节点 | CF, Result | Result -> {CF, X} |
identifier 解析到 指针类型形参,位于 storage、workgroup、 或 private 地址空间,且访问模式非只读,标识符作为根标识符 出现在内存视图表达式 MVE 中,且加载规则在 MVE 上于类型检查期间被调用 | CF, MayBeNonUniform | |||
identifier 解析到 指针类型形参,位于 storage、workgroup、 或 private 地址空间,且访问模式非只读,标识符作为根标识符 出现在内存视图表达式 MVE 中,且加载规则在 MVE 上于类型检查期间未被调用 | CF, CF | |||
identifier 解析到 指针类型形参,位于除function外的地址空间,且访问模式为只读 | CF, CF | |||
identifier 解析到一致内置值 "x" | CF, CF | |||
identifier 解析到非一致内置值 "x" | CF, MayBeNonUniform | |||
identifier 解析到只读模块级作用域变量 "x" | CF, CF | |||
identifier 解析到 非只读模块级变量 "x",标识符作为根标识符出现在内存视图表达式 MVE 中,且加载规则在 MVE 上于类型检查期间被调用 | CF, MayBeNonUniform | |||
identifier 解析到 非只读模块级变量 "x",标识符作为根标识符出现在内存视图表达式 MVE 中,且加载规则在 MVE 上于类型检查期间未被调用 | CF,CF | |||
op e, op 为一元操作符 | (CF, e) => (CF', V) | CF', V | ||
e.field | ||||
e1 op e2, op 为非短路二元操作符 | Result | (CF, e1) => (CF1, V1) (CF1, e2) => (CF2, V2) | CF2, Result | Result -> {V1, V2} |
e1[e2] |
以下内置输入变量被视为一致:
所有其他(见内置值)都视为非一致。
注意:作者应避免将一致性内置值与其他非常量输入分组,因为分析不会分别分析复合类型的各分量。
表达式 | 新节点 | 递归分析 | 结果控制流节点, 变量节点 | 新边 |
---|---|---|---|---|
identifier 解析到函数作用域变量 "x" | Result | X 是在包含此表达式的语句输出时 "x" 的值节点。 | CF, Result |
Result -> {CF, X}
注意: X 等价于 "x" 的
Vin(next) |
identifier 解析到 const 声明、override 声明、 let 声明或形参 "x" | X 是 "x" 的节点 | CF, X | ||
identifier 解析到模块级变量 "x" | CF, MayBeNonUniform | |||
e.field | LHSValue: (CF, e) => (CF1, L1) | CF1, L1 | ||
*e | ||||
&e | ||||
e1[e2] | LHSValue:
(CF, e1) => (CF1, L1) (CF1, e2) => (CF2, V2) | CF2, 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)是一组并发执行同一计算着色器阶段入口点的调用, 并共享对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 实现从队列中移除一个分派(dispatch)命令并开始在 GPU 上执行指定工作时,计算着色器开始执行。 分派命令指定一个分派尺寸(dispatch size), 即整数三元组(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 为workgroup ID:
( ⌊ CSi ÷ workgroup_size_x ⌋, ⌊ CSj ÷ workgroup_size_y ⌋, ⌊ CSk ÷ workgroup_size_z ⌋)
以及该工作组内的唯一调用,其本地 ID 为local invocation 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)。
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)是一组并发执行计算或片元着色器阶段入口点的调用,这些调用可以高效地共享数据并协同计算结果。 在计算或片元着色器中,每个调用恰好属于一个子组。 在计算着色器中,每个子组是某个特定工作组的子集。 在片元着色器中,一个子组可能包含来自多个绘制命令的调用。 每个quad都包含在一个子组中。
子组大小(subgroup size)是子组中最大调用数。 在着色器中,可通过subgroup_size内置值访问。 子组大小在dispatch 命令以及工作组内为一致值,但在绘制命令内未必一致。 所有子组大小为 [4, 128] 范围内的 2 的幂,针对特定设备编译的着色器的值会落在该设备支持的 subgroupMinSize 与 subgroupMaxSize 之间(见 WebGPU § 4.3 GPUAdapter)。 实际大小取决于着色器、设备属性和设备编译器。 每个设备支持可能范围的一个子集(可能只有一个值)。 设备编译器会用多种启发式方法在支持范围内选择大小。 每个子组可能包含的调用数少于报告的子组大小(如实际发起的调用少于子组大小时)。
调用的子组调用 ID(subgroup
invocation ID)是该子组内的唯一 ID。
该 ID 可通过subgroup_invocation_id内置值访问,范围为 [0,
subgroup_size
- 1]。
subgroup_invocation_id
与
local_invocation_index 没有定义的关系。
为避免不可移植的代码,着色器作者不应假设这两个值有特定映射关系。
当同一子组内的调用执行不同的控制流路径时,称子组执行已发散(diverged)。 这是非一致控制流的一种特殊情况。 发散会影响子组操作的语义。 子组中并发执行子组操作的调用为该操作的活动调用。 其它为非活动调用。 当子组大小大于实际调用数时,多出来的假想调用被视为非活动。 辅助调用在某些操作中可以是活动或非活动的。 也就是说,在某些设备上辅助调用可能参与子组操作,另一些设备则不参与。
注意:在非一致控制流下,不同底层设备的表现差异很大,设备编译器常会对这类代码做激进优化。 结果是子组实际活动的调用集可能与着色器作者预期不同。
15.6. 集体操作
15.6.1. 屏障(Barrier)
屏障是一种同步内置函数,用于对程序中的内存操作进行排序。 控制屏障(control 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) = min
a,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+0026
U+0026
) -
'->'
(码点:U+002D
U+003E
) -
'@'
(码点:U+0040
) -
'/'
(码点:U+002F
) -
'!'
(码点:U+0021
) -
'['
(码点:U+005B
) -
']'
(码点:U+005D
) -
'{'
(码点:U+007B
) -
'}'
(码点:U+007D
) -
':'
(码点:U+003A
) -
','
(码点:U+002C
) -
'='
(码点:U+003D
) -
'=='
(码点:U+003D
U+003D
) -
'!='
(码点:U+0021
U+003D
) -
'>'
(码点:U+003E
)(也有_greater_than
用于模板消歧) -
'>='
(码点:U+003E
U+003D
)(也有_greater_than_equal
用于模板消歧) -
'>>'
(码点:U+003E
U+003E
)(也有_shift_right
用于模板消歧) -
'<'
(码点:U+003C
)(也有_less_than
用于模板消歧) -
'<='
(码点:U+003C
U+003D
)(也有_less_than_equal
用于模板消歧) -
'<<'
(码点:U+003C
U+003C
)(也有_shift_left
用于模板消歧) -
'%'
(码点:U+0025
) -
'-'
(码点:U+002D
) -
'--'
(码点:U+002D
U+002D
) -
'.'
(码点:U+002E
) -
'+'
(码点:U+002B
) -
'++'
(码点:U+002B
U+002B
) -
'|'
(码点:U+007C
) -
'||'
(码点:U+007C
U+007C
) -
'('
(码点:U+0028
) -
')'
(码点:U+0029
) -
';'
(码点:U+003B
) -
'*'
(码点:U+002A
) -
'~'
(码点:U+007E
) -
'_'
(码点:U+005F
) -
'^'
(码点:U+005E
) -
'+='
(码点:U+002B
U+003D
) -
'-='
(码点:U+002D
U+003D
) -
'*='
(码点:U+002A
U+003D
) -
'/='
(码点:U+002F
U+003D
) -
'%='
(码点:U+0025
U+003D
) -
'&='
(码点:U+0026
U+003D
) -
'|='
(码点:U+007C
U+003D
) -
'^='
(码点:U+005E
U+003D
) -
'>>='
(码点:U+003E
U+003E
U+003D
)(也有_shift_right_assign
用于模板消歧) -
'<<='
(码点:U+003C
U+003C
U+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 的自然指数(如
e e1 )。
当 T 是向量时,分量方式 计算。
|
17.5.24. exp2
重载 |
|
参数化 | S 为 AbstractFloat、f32 或 f16 T 为 S 或 vecN<S> |
描述 | 返回 2 的 e 次幂(如 2 e )。
当 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>I 为向量当且仅当 T 为向量T 仅当 I 也为 抽象类型时可为抽象类型,反之亦然
注意: 若任一参数为 具体类型 则另一参数会自动转换为具体类型(如适用),结果也为具体类型。 |
描述 |
返回 e1 * 2 e2 ,但有例外:
其中,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 为texel 格式 A 为访问模式 T 为 texture_1d<ST> 或 texture_storage_1d<F,A>
|
|
ST 为 i32、u32 或 f32 T 为 texture_1d<ST>
|
|
ST 为 i32、u32 或 f32 F 为texel 格式 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 为texel 格式 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 表达式必须是常量表达式(如 1 )。取值必须在 0 到 3 之间。 超出此范围会导致着色器创建错误。 |
t
| 采样或深度纹理。 |
s
| 采样器类型。 |
coords
| 纹理坐标。 |
array_index
|
数组纹理的 0 基索引。 此值会被限制在 [0, textureNumLayers(t) - 1]
范围内。
|
offset
|
可选的 texel 偏移,在采样前加到未归一化纹理坐标上。此偏移在应用任何
纹理环绕模式之前加上。offset 表达式必须是常量表达式(如
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
|
可选的 texel 偏移,在采样纹理前加到未归一化纹理坐标上。此偏移在应用任何纹理环绕模式前加上。offset 表达式必须为常量表达式(如
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 取决于存储 texel 格式 F。 见 texel 格式表获取 texel 格式到通道格式的映射。 |
|
C 为 i32 或 u32 AM 为 read 或 read_write CF 取决于存储 texel 格式 F。 见 texel 格式表获取 texel 格式到通道格式的映射。 |
|
C 为 i32 或 u32 AM 为 read 或 read_write A 为 i32 或 u32 CF 取决于存储 texel 格式 F。 见 texel 格式表获取 texel 格式到通道格式的映射。 |
|
C 为 i32 或 u32 AM 为 read 或 read_write CF 取决于存储 texel 格式 F。 参见 texel 格式表,了解 texel 格式到通道格式的映射。 |
|
参数:
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,0,0) 或 (0,0,0,1) 类型的向量
-
深度纹理时为 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
|
可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。offset 表达式必须是常量表达式(如
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
|
可选的 texel 偏移,在采样前加到未归一化纹理坐标上。该偏移在应用任何纹理环绕模式前加上。offset 表达式必须是常量表达式(如
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
|
可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。offset 表达式必须是常量表达式(如
vec2<i32>(1, 2) )。每个 offset 分量必须在 -8 到 7 之间。超出
此范围会导致着色器创建错误。
|
返回:
区间 [0.0..1.0]
内的值。
每个采样 texel 会与参考值用 sampler_comparison
定义的比较操作符比较,结果为 0
或 1
。
若采样器使用双线性过滤,则返回值为这些结果的滤波平均,否则为单个 texel 的比较结果。
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
|
可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。offset 表达式必须是常量表达式(如
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
|
可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。offset 表达式必须是常量表达式(如
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 类型时,若格式可过滤,可在两个级别间插值,详见
Texture Format Capabilities。
|
offset
|
可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。offset 表达式必须是常量表达式(如
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 为texel
格式 C 为 i32 或 u32 AM 为write 或 read_write CF 取决于存储 texel 格式 F。 参见 texel 格式表,了解 texel 格式到通道格式的映射。 |
|
F 为texel
格式 C 为 i32 或 u32 AM 为write 或 read_write CF 取决于存储 texel 格式 F。 参见 texel 格式表,了解 texel 格式到通道格式的映射。 |
|
F 为texel
格式 C 为 i32 或 u32 AM 为write 或 read_write A 为 i32 或 u32 CF 取决于存储 texel 格式 F。 参见 texel 格式表,了解 texel 格式到通道格式的映射。 |
|
F 为texel
格式 C 为 i32 或 u32 AM 为write 或 read_write CF 取决于存储 texel 格式 F。 参见 texel 格式表,了解 texel 格式到通道格式的映射。 |
|
参数:
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 是具体 数值标量或数值向量
|
描述 | 返回 子组调用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
|
描述 |
返回四元组中quad
调用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'
| 'default'
| '.'
member_ident component_or_swizzle_specifier
?
| '.'
swizzle_name component_or_swizzle_specifier
?
| '['
expression ']'
component_or_swizzle_specifier
?
| '%='
| '&='
| '*='
| '+='
| '-='
| '/='
| '^='
| '|='
| '@'
'compute'
| '@'
'const'
| ident
| '('
lhs_expression ')'
| /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'
| template_elaborated_ident.post.ident
'('
( expression ( ','
expression )* ','
? )?
')'
| '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):无
文件扩展名:
.wgsl
Macintosh 文件类型代码:
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