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 确实提供了一些有限机制,可将标量 类型提升为向量类型。 这同样适用于复合类型。
着色器阶段的工作被划分为一个或多个调用, 每个调用都会执行入口点,但所处条件略有不同。 着色器阶段中的调用共享对某些变量的访问:
但是,各调用作用于不同的着色器阶段输入集合,其中包括 提供标识值以区分一个调用及其同级调用的内置输入。 每个调用都有自己的独立内存空间,其形式为 私有和函数地址空间中的变量。
着色器阶段中的调用并发执行,并且通常可以并行执行。 着色器作者负责确保着色器阶段中调用的动态行为:
-
满足某些原始操作的一致性要求,包括 纹理采样和控制屏障。
-
协调对共享变量可能发生冲突的访问,以避免数据竞争。
对于给定特性,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 的唯一整数
truncate 函数 针对扩展实数 x 定义如下:
-
truncate(+∞) = +∞
-
truncate(−∞) = −∞
-
对于实数 x,计算绝对值小于或等于 x 绝对值的最接近整数:
-
如果 x ≥ 0,则 truncate(x) = ⌊x⌋;如果 x < 0,则为 ⌈x⌉。
-
roundUp 函数针对 正整数 k 和 n 定义如下:
-
roundUp(k, n) = ⌈n ÷ k⌉ × k
roundDown 函数 针对正整数 k 和 n 定义如下:
-
roundDown(k, n) = ⌊n ÷ k⌋ × k
具有 c 列、r 行的矩阵 A 的 转置是具有 r 列、c 行的矩阵 AT,它通过将 A 的各行复制为 AT 的各列形成:
-
transpose(A) = AT
-
transpose(A)i,j = Aj,i
列向量的转置通过将列向量解释为一行矩阵来定义。 类似地,行向量的转置通过将行向量解释为一列矩阵来定义。
2. WGSL 模块
WGSL 程序由单个 WGSL 模块组成。
模块是由可选的指令序列,后跟模块作用域声明和断言组成的序列。 模块由以下内容组成:
-
指令,用于指定模块级行为控制。
-
函数,用于指定执行行为。
-
语句,即声明或可执行行为单元。
-
字面量,即纯数学值的文本表示形式。
-
变量,每个变量都为保存值的内存提供名称。
-
常量,每个常量都为在特定时间计算的值提供名称。
-
表达式,每个表达式都组合一组值以产生结果值。
-
类型,每种类型描述:
-
一组值。
-
对受支持表达式的约束。
-
这些表达式的语义。
-
-
属性,用于修改对象以指定额外信息,例如:
global_directive * ( global_decl | global_assert | ';' ) *
2.1. 着色器生命周期
WGSL 程序及其可能包含的着色器的生命周期中有四个关键事件。 前两个对应于用于为执行准备 WGSL 程序的 WebGPU API 方法。 后两个是着色器执行的开始和结束。
这些事件是:
-
着色器模块 创建
-
当调用 WebGPU
createShaderModule()方法时会发生此事件。 此时会提供 WGSL 程序的源文本。
-
-
管线 创建
-
当调用 WebGPU
createComputePipeline()方法 或 WebGPUcreateRenderPipeline()方法时会发生此事件。 这些方法使用一个或多个先前创建的着色器模块,以及其他 配置信息。 -
在管线创建期间,仅考虑指定入口点的
GPUProgrammableStage中构成着色器的代码。 也就是说,与入口点无关的代码实际上会在编译前被移除。 -
注: 每个着色器阶段都被视为 单独编译,因此可能 包含模块的不同部分。
-
-
着色器 执行开始
-
着色器执行 结束
这些事件之所以具有顺序,是由于:
-
数据依赖:着色器执行需要管线,而管线需要着色器模块。
-
因果关系:着色器必须先开始执行,之后才能完成执行。
2.2. 错误
WebGPU 实现可能由于两个原因而无法处理着色器:
-
如果着色器不满足 WGSL 或 WebGPU 规范的要求,则会发生程序错误。
-
即使所有 WGSL 和 WebGPU 要求都已满足,也可能发生未分类 错误。 可能的原因包括:
-
着色器过于复杂,超出实现的能力, 但这种情况难以通过规定的限制来准确表达。 简化着色器可能可以规避此问题。
-
WebGPU 实现中的缺陷。
-
处理错误可能在着色器生命周期的三个阶段发生:
-
着色器创建 错误 是可以在着色器模块创建时检测到的错误。 检测仅依赖 WGSL 模块源文本 以及
createShaderModuleAPI 方法可获得的其他信息。 本规范中描述程序必须 执行某事的陈述,如果违反这些断言,通常会产生着色器创建错误。 -
管线创建错误 是可在管线创建时检测到的错误。 检测依赖 WGSL 模块源文本 以及特定管线创建 API 方法可获得的其他信息。 这些错误只会由为
GPUProgrammableStage编译的入口点的 着色器中存在的代码触发。 -
动态错误 是在着色器执行期间发生的错误。 这些错误可能能够检测,也可能无法检测。
注: 例如,数据竞争可能无法检测。
每项要求都会在最早可能的时机进行检查。 也就是说:
-
如果未满足可在着色器创建时检测的要求,则会产生着色器创建错误。
-
如果未满足可在管线创建时检测、但无法更早检测的要求,则会产生管线创建错误。
当上下文不能明确说明时,本规范会指出 未满足特定要求 会导致着色器创建错误、管线创建错误还是动态错误。
错误的后果如下:
-
如果发生动态错误:
2.3. 诊断
实现可以在着色器模块创建或管线创建期间生成诊断。 诊断是 实现为了帮助应用程序作者而产生的消息。
当满足某个特定条件时,会创建(即触发)诊断, 该条件称为触发规则。 源文本中满足该条件的位置,以源文本中的点或范围表示, 称为 触发位置。
诊断具有以下 属性:
诊断的严重级别是以下值之一,按严重程度从高到低排列:
- 错误
- 警告
-
该诊断描述了值得应用程序开发者注意、但并非 错误的异常情况。
- 信息
-
该诊断描述了值得应用程序开发者注意的显著情况,但 既不是错误也不是警告。
- 关闭
-
该诊断已禁用。它不会传达给应用程序。
触发规则的名称可以是:
-
两个 diagnostic_name_token 词法单元,由 句点
'.'(U+002E) 分隔。
2.3.1. 诊断处理
已触发的诊断将按如下方式处理:
-
对于每个诊断 D,查找包含 D 的触发 位置、具有最小受影响范围且具有相同触发规则的诊断过滤器。
-
如果存在这样的过滤器,则将其应用于 D,更新 D 的严重级别。
-
否则 D 保持不变。
-
-
丢弃严重级别为关闭的诊断。
-
如果至少有一个剩余诊断 DI 的严重级别为信息,则:
-
可以丢弃具有相同触发规则的其他信息 诊断,只保留原始 诊断 DI。
-
-
如果至少有一个剩余诊断 DW 的严重级别为警告,则:
-
如果至少有一个剩余诊断具有错误严重级别,则:
-
如果是在着色器模块创建期间处理,则剩余诊断 会填充 WebGPU
GPUCompilationInfo对象的messages成员。 -
如果是在管线创建期间处理,则错误诊断 会在验证
GPUProgrammableStage时导致 WebGPU 验证失败。
注: 这些规则允许实现在 检测到错误后立即停止处理 WGSL 模块。 此外,对特定类型警告的分析可以在遇到第一个警告时停止, 对特定类型信息诊断的分析也可以在第一次出现时停止。 WGSL 未规定执行不同类型分析的顺序,也未规定单次 分析内部的顺序。 因此,对于同一 WGSL 模块,不同实现可能报告具有相同严重级别的 不同诊断实例。
2.3.2. 可过滤的触发规则
大多数诊断都会无条件 报告给 WebGPU 应用程序。 某些类型的诊断可以进行过滤,其中一种方式是指定其触发规则的名称。 下表列出了可以过滤的标准触发规则集合。
| 可过滤的触发规则 | 默认严重级别 | 触发位置 | 描述 |
|---|---|---|---|
| derivative_uniformity | 错误 | 任意计算导数的内置函数的调用 点位置。 也就是说,以下任意函数的调用位置: |
对内置函数的调用计算导数,但一致性分析无法
证明该调用发生在一致控制流中。
请参阅§ 15.2 一致性。 |
| subgroup_uniformity | 错误 | 任意子组或四元组 内置函数的调用 点位置。 |
调用了子组或四元组内置函数,但一致性分析无法
证明该调用发生在一致控制流中。
此外,当一致性分析无法证明以下参数值是一致的时:
请参阅§ 15.2 一致性。 |
使用由单个诊断名称词法单元组成的无法识别的触发规则时,用户代理应 触发警告。
实现可以支持此处未指定的触发规则, 前提是这些规则使用 diagnostic_rule_name 的多词法单元形式拼写。 使用无法识别且以多词法单元形式拼写的触发规则,可以自身触发 诊断。
本规范的未来版本可以删除某个特定规则或降低其默认严重级别
(即用较低严重级别的默认值替换当前默认值),并且仍被视为满足向后
兼容性。
例如,WGSL 的未来版本可以将derivative_uniformity 的默认严重级别从 error 更改为
warning 或 info。
在规范作出此类更改后,先前有效的程序仍将保持有效。
2.3.3. 诊断过滤
一旦具有可过滤触发规则的诊断被触发,WGSL 就会提供用于丢弃该 诊断或修改其严重级别的机制。
诊断过滤器 DF 有三个参数:
将诊断过滤器 DF(AR,NS,TR) 应用于诊断 D 会产生 以下效果:
范围诊断
过滤器是一种诊断过滤器,其受影响范围是指定的
源文本范围。
范围诊断过滤器以 @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 子句、 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 |
| 由单个着色器静态访问的、在 私有地址空间中实例化的所有变量的 最大合计字节大小 | 8192 |
| 在单个函数中声明的、 在函数地址空间中实例化的所有变量的 最大合计字节大小 | 8192 |
|
由单个着色器静态访问的、在
工作组地址空间中实例化的所有变量的
最大合计字节大小
为计算此限制,在替换 override 值时, 固定占用空间数组被视为创建时固定占用空间数组。 这会将 WebGPU maxComputeWorkgroupStorageSize 限制 映射为独立的 WGSL 限制。 | 16384 |
|
立即数据
变量的最大字节大小。
这会将 WebGPU maxImmediateSize 限制 映射为独立的 WGSL 限制。 | 64 |
| 数组类型的值 构造器表达式中的最大元素数 | 2047 |
3. 文本结构
text/wgsl 媒体类型用于将内容标识为 WGSL 模块。
请参阅附录 A:text/wgsl 媒体类型。
WGSL 模块是使用 UTF-8 编码且不带字节顺序标记 (BOM) 的 Unicode 文本。
WGSL 模块文本由 Unicode 码位序列组成,这些码位被分组为连续的非空集合,形成:
程序文本不得包含空码位(U+0000)。
3.1. 解析
要解析 WGSL 模块:
移除注释:
将第一个注释替换为空格码位(
U+0020)。重复此操作,直到没有注释剩余。
使用§ 3.9 模板列表中的算法查找模板列表。 此步骤消除
'<'(U+003C) 和'>'(U+003E) 码位 作为模板列表定界符与其他用途(例如比较运算符)之间的歧义。解析整个文本,尝试匹配 translation_unit 语法规则。 解析使用 LALR(1) 解析器(一个词法单元的前瞻)[DeRemer1969],并进行以下 定制:
词法单元化与解析交错进行,并且具有上下文感知能力。 当解析器请求下一个词法单元时:
消费并忽略起始的一系列空白码位。
如果下一个码位是模板列表的开头,则消费 该码位并返回 _template_args_start。
如果下一个码位是模板列表的结尾,则消费 该码位并返回 _template_args_end。
否则:
候选词法单元是由剩余未消费码位的非空前缀形成的任意 WGSL 词法单元。
返回的词法单元是最长的候选 词法单元,且它同时也是当前 解析器状态的有效前瞻词法单元。[VanWyk2007]
如果出现以下情况,则会产生着色器创建 错误:
-
无法将整个源文本转换为有限的有效词法单元序列,或
-
translation_unit 语法规则无法匹配 整个词法单元序列。
另一种方法是将模板列表发现与词法单元化交错进行。 在这种方法中,会在语法规则中任何可能出现模板列表的位置放置一个合成词法单元(_disambiguate_template)。 当扫描器尝试匹配 _disambiguate_template 词法单元时:
-
扫描器在剩余文本上运行模板列表发现算法, 记录模板列表定界符的位置。
-
扫描器表示
_disambiguate_template词法单元匹配成功,其 关联文本为空字符串。
未来的词法单元扫描步骤将使用记录的模板列表定界符位置,按所指示的方式产生 _template_args_start 和 _template_args_end 词法单元。
这种替代方法是非规范性的。 规范性语法包含 _disambiguate_template 词法单元,以帮助 使用这种替代方法的实现。 使用标准方法的解析器可以忽略该合成词法单元,或者等价地,始终 成功将其匹配为空字符串。
3.2. 空白和换行
空白是 Unicode Pattern_White_Space 属性中 一个或多个码位的任意组合。 以下是 Pattern_White_Space 中的码位集合:
-
空格(
U+0020) -
水平制表符(
U+0009) -
换行符(
U+000A) -
垂直制表符(
U+000B) -
换页符(
U+000C) -
回车符(
U+000D) -
下一行(
U+0085) -
从左到右标记(
U+200E) -
从右到左标记(
U+200F) -
行分隔符(
U+2028) -
段落分隔符(
U+2029)
换行是指示一行结束的连续 空白码位序列。 它被定义为 UAX14 第 6.1 节 不可定制的换行规则 LB4 和 LB5 中定义的表示“强制换行”的空白。 也就是说,换行是以下任一种:
-
换行符(
U+000A) -
垂直制表符(
U+000B) -
换页符(
U+000C) -
未紧跟换行符(
U+000A)的回车符(U+000D) -
后跟换行符(
U+000A)的回车符(U+000D) -
下一行(
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]?/
浮点 字面量有两个逻辑部分:用于表示小数的有效数,以及可选的指数。 粗略地说,字面量的值是有效数乘以底数的给定 指数次幂。 如果一个有效数数字非零, 或者其左侧和右侧都存在非零的有效数数字,则该数字是有效的。 有效数字从左到右计数:第 N 个有效数字的左侧有 N-1 个 有效数字。
十进制 浮点字面量为:
-
一个有效数,指定为数字序列,其中可在任意位置包含可选的小数点(
.)。 有效数表示以 10 为底的分数。 -
然后是可选的指数后缀,由以下内容组成:
-
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前缀 -
然后是一个有效数,指定为十六进制数字序列,其中可在任意位置包含可选的十六进制小数点 (
.)。 有效数表示以 16 为底的分数。 -
然后是可选的指数后缀,由以下内容组成:
-
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 版的 Unicode 字符数据库包含一个非规范性列表, 其中列出了 XID_Start 和 XID_Continue 的所有有效码位。
注: 某些内置函数的返回类型是其名称不能在
WGSL 源代码中使用的结构体类型。
这些结构体类型被描述为仿佛以两个下划线开头的名称预声明。
结果值可以使用类型
推断保存到新声明的 let 或 var 中,也可以立即按名称提取其某个成员。
示例用法请参阅 frexp 和
modf 的描述。
3.7.1. 标识符比较
当且仅当两个 WGSL 标识符由相同的码位序列组成时,它们才相同。
注: 本规范不允许为了比较而对值进行 Unicode 规范化。 在视觉和语义上相同但使用不同 Unicode 字符序列的值不会 匹配。 建议内容作者始终使用相同的编码序列,或者在选择值时避免可能 引发问题的 字符。有关更多信息,请参阅 [CHARMOD-NORM]。
注: 如果将某个标识符的所有实例替换为该标识符的某个同形异义字后, WGSL 模块的含义会发生变化,则用户代理应发出开发者可见的 警告。 (同形字形是一个码位序列,在读者看来可能与另一个码位 序列相同。 用于检测同形字形的映射示例包括前一段中提到的转换、映射和匹配算法。 如果一个标识符能够通过 反复将某个子序列替换为其同形字形,将一个码位序列转换为另一个码位序列,则这两个码位序列互为同形异义字。)
3.8. 上下文相关名称
上下文相关 名称是一个仅在特定语法上下文中用于命名 概念的词法单元。 该词法单元的拼写可能与标识符相同,但该词法单元不会解析到已声明的对象。 本节列出用作上下文相关名称的词法单元。 该词法单元不得是关键字或保留字。
3.8.1. 属性名称
请参阅§ 12 属性。
attribute 名称为:
3.8.2. 内置值名称
内置值名称 为:
3.8.3. 诊断规则名称
请参阅§ 2.3 诊断。
预定义的诊断规则名称为:
3.8.4. 诊断严重级别控制名称
有效的诊断 过滤器严重级别控制名称列在§ 2.3 诊断中,但其 形式与标识符相同:
诊断过滤器 严重级别控制名称为:
3.8.5. 扩展名称
有效的启用扩展 名称列在§ 4.1.1 启用扩展中,但通常具有与标识符相同的形式:
启用扩展 名称为:
有效的语言 扩展名称列在§ 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.8.9. 深度模式名称
深度模式 名称标记是一个标记,当支持fragment_depth特性时,用于 frag_depth内置值中。
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) 码位
也用于拼写:
-
relational_expression 中的比较运算符。
-
shift_expression 中的移位运算符。
-
用于执行 移位操作后再赋值的compound_assignment_operator。
语法歧义优先解析为模板列表:
-
在之后阶段的词法单元化过程中, 模板列表起始的
'<'(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。
如果存在,则将 CurrentPosition 向前推进,越过空白和注释。
如果 CurrentPosition 处出现
'<'(U+003C),则:
注: 此码 位可能是模板列表的起始位置。 保存足够的状态,以便将它与之后输入中出现的终止
'>'(U+003E) 匹配。将 UnclosedCandidate(position=CurrentPosition,depth=NestingDepth) 压入 Pending 栈。
将 CurrentPosition 向前推进到下一个码位。
如果 CurrentPosition 处出现
'<'(U+003C), 则:
注: 根据 假设 1,模板参数不会以
'<'(U+003C) 开头,因此前一个码位不可能 是模板列表的起始位置。 因此当前码位和前一个码位必须构成'<<'运算符。从 Pending 栈中弹出栈顶条目。
将 CurrentPosition 向前推进越过此码位,并 开始循环的下一次迭代。
如果 CurrentPosition 处出现
'='(U+003D),则:
注: 根据 假设 1,模板参数不会以
'='(U+003C) 开头,因此前一个码位不可能是模板 列表的起始位置。 假定当前码位和前一个码位构成'<='比较运算符。 跳过'='(U+003D) 码位,以免后续 步骤将其误认为赋值。从 Pending 栈中弹出栈顶条目。
将 CurrentPosition 向前推进越过此码位,并 开始循环的下一次迭代。
开始循环的下一次迭代。
如果 CurrentPosition 处出现
'>'(U+003E),则:
注: 此码位可能 是模板列表的结束位置。
如果 Pending 非空,则令 T 为其栈顶条目,并且 如果 T.depth 等于 NestingDepth,则:
注: 此码 位结束当前模板列表,其起始位置记录在 T 中。
将 TemplateList(start_position=T.position, end_position=CurrentPosition) 添加到 DiscoveredTemplateLists。
从 Pending 栈中弹出 T。
将 CurrentPosition 向前推进越过此码位,并开始 循环的下一次迭代。
否则,此码位不会结束模板列表:
将 CurrentPosition 向前推进越过此码位。
如果 CurrentPosition 处出现
'='(U+003D),则:
注: 假定 当前码位和前一个码位构成
'>='比较运算符。 跳过'='(U+003D) 码位,以免后续 步骤将其误认为赋值。将 CurrentPosition 向前推进越过此码位。
开始循环的下一次迭代。
如果 CurrentPosition 处出现
'('(U+0028) 或'['(U+005B), 则:
注: 进入嵌套 表达式。
将 NestingDepth 加 1。
将 CurrentPosition 向前推进越过此码位,并开始下一次 循环迭代。
如果 CurrentPosition 处出现
')'(U+0029) 或']'(U+005D), 则:
注: 退出嵌套 表达式。
从 Pending 栈中弹出条目,直到其为空,或直到其 栈顶条目的 depth < NestingDepth。
将 NestingDepth 设置为 0 或 NestingDepth − 1 中较大的 一个。
将 CurrentPosition 向前推进越过此码位,并开始下一次 循环迭代。
如果 CurrentPosition 处出现
'!'(U+0021),则:
将 CurrentPosition 向前推进越过此码位。
如果 CurrentPosition 处出现
'='(U+003D),则:
注: 假定 当前码位和前一个码位构成
'!='比较 运算符。 跳过'='(U+003D) 码位,以免后续步骤 将其误认为赋值。将 CurrentPosition 向前推进越过此码位。
开始循环的下一次迭代。
如果 CurrentPosition 处出现
'='(U+003D),则:
将 CurrentPosition 向前推进越过此码位。
如果 CurrentPosition 处出现
'='(U+003D),则:
注: 假定 当前码位和前一个码位构成
'=='比较运算符。 跳过'='(U+003D) 码位,以免后续步骤 将其误认为赋值。将 CurrentPosition 向前推进越过此码位,并开始 循环的下一次迭代。
注: 假定此码位 是赋值的一部分,而赋值不能作为表达式的一部分出现,因此 也不能出现在模板列表中。 清除待处理的未闭合候选项。
将 NestingDepth 设置为 0。
从 Pending 栈中移除所有条目。
将 CurrentPosition 向前推进越过此码位,并开始下一次 循环迭代。
如果 CurrentPosition 处出现
';'(U+003B) 或'{'(U+007B) 或':'(U+003A), 则:
注: 这些内容不能出现在 表达式中间,因此也不能出现在模板列表中。 清除待处理的未闭合候选项。
将 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。
-
-
在循环末尾、向前越过当前码位之前插入一项检查:
-
如果 CurrentPosition 处出现 '
,' (U+002C),且 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,然后检查 '<' (U+003C),但随后
发现下一个 '<' (U+003C) 码位不能开始模板参数,因此
紧跟 B 的 '<' 不是模板列表的起始位置。
起始的 '<' 和末尾的 '>' 是唯一的模板列表定界符,该模板列表的
模板参数为 B<<C。
注: 短语 A<B<=C> 的分析方式
与上一条注释类似,因此短语 B<=C 被解析为 B,后跟
小于等于运算符'<=',再后跟 C。
模板发现算法开始检查 B,然后检查 '<' (U+003C),但随后
发现下一个 '=' (U+003D) 码位不能开始模板参数,因此
紧跟 B 的 '<' 不是模板列表的起始位置。
起始的 '<' 和末尾的 '>' 是唯一的模板列表定界符,该模板列表的
模板参数为 B<=C。
注: 检查短语
A<(B>=C)> 时,存在一个模板列表,从第一个 '<' (U+003C)
码位开始,到最后一个 '>' (U+003E) 码位结束,并具有模板参数
B>=C。
检查第一个 '>' (U+003C) 码位(位于 B 之后)后,需要特别识别
'=' (U+003D) 码位,以免将其假定为赋值的一部分。
注: 检查短语
A<(B!=C)> 时,存在一个模板列表,从第一个 '<'' (U+003C)
码位开始,到最后一个 '>' (U+003E) 码位结束,并具有模板参数
B!=C。
检查 '!' (U+0021) 码位(位于 'B' 之后)后,需要特别识别
'=' (U+003D) 码位,以免将其假定为赋值的一部分。
注: 检查短语
A<(B==C)> 时,存在一个模板列表,从第一个 '<' (U+003C)
码位开始,到最后一个 '>' (U+003E) 码位结束,并具有模板参数
B==C。
检查第一个 '=' (U+003D) 码位(位于 '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 规范的一组连贯修改的命名分组,由以下内容的任意组合构成:
-
通过新语法添加新概念和行为,包括:
-
声明、语句、属性和内置函数。
-
-
移除当前规范或先前发布扩展中的限制。
-
用于减少允许行为集合的语法。
-
用于限制程序某部分可用特性的语法。
-
描述该扩展如何与现有规范交互,并可选地描述其如何与 其他扩展交互。
假设扩展可以:
-
添加数值标量类型,例如不同位宽的整数。
-
添加用于约束浮点舍入模式的语法。
-
添加用于表明着色器不使用原子类型的语法。
-
添加新类型的语句。
-
添加新的内置函数。
-
添加用于约束着色器调用执行方式的语法。
-
添加新的着色器阶段。
4.1.1. 启用扩展
启用扩展是一种 扩展,其功能仅在以下条件下 可用:
-
实现支持它,并且
-
着色器通过enable 指令显式请求它,并且
-
创建
GPUDevice时请求的必要特性之一是对应的 WebGPUGPUFeatureName。
启用扩展 旨在公开并非所有硬件都支持的硬件功能。
enable 指令是一种 指令,用于开启对 一个或多个启用扩展的支持。 如果实现不支持列出的所有启用扩展,则会产生着色器创建 错误。
'enable' enable_extension_list ';'
与其他指令一样,如果存在enable 指令,则它必须出现在所有声明和const 断言之前。 扩展名称不是标识符: 它们不会解析到声明。
有效的启用扩展列在下表中。
| WGSL 启用扩展 | WebGPU GPUFeatureName
| 描述 |
|---|---|---|
f16
| "shader-f16" | f16 类型可在 WGSL 模块中有效使用。否则,使用f16 (直接或间接)将导致着色器创建错误。 |
clip_distances
| "clip-distances" | 内置变量 clip_distances 可在 WGSL 模块中有效使用。否则,使用 clip_distances 将导致 着色器创建错误。 |
dual_source_blending
| "dual-source-blending" | 属性 blend_src 可在 WGSL 模块中有效使用。 否则,使用 blend_src 将导致着色器创建 错误。 |
subgroups
| "subgroups" | 可以在 WGSL 模块中有效使用子组内置值、 子组内置函数和 四元组内置函数。 否则,任何使用都将导致着色器创建错误。 |
primitive_index
| "primitive-index"
| 内置变量 primitive_index 可在 WGSL 模块中有效使用。否则,使用 primitive_index 将导致 着色器创建错误。 |
subgroup_size_control
| "subgroup-size-control"
| 属性 subgroup_size 可在 WGSL 模块中有效使用。
否则,使用 subgroup_size 将导致着色器创建错误。
启用
subgroup_size_control 时,subgroups将自动启用。
|
// 启用一个用于任意精度浮点类型的假设扩展。 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 ; // 使用舍入到偶数的舍入模式。 }
4.1.2. 语言扩展
语言扩展 是一种扩展,如果实现支持它,则会自动 可用。 程序不必显式请求它。
语言扩展 体现了任何 WebGPU 实现都可以合理支持的功能。 如果某项特性没有得到普遍支持,那是因为某些 WebGPU 实现尚未 实现它。
注: 例如,do-while 循环可以是一种语言 扩展。
WebGPU GPU
对象的 wgslLanguageFeatures
成员列出了
实现支持的语言扩展集合。
requires 指令 是一种指令,用于记录 程序对一个或多个语言扩展的使用。 它不会改变实现公开的功能。 如果实现不支持某个必需扩展,则会产生着色器创建 错误。
WGSL 模块可以使用requires 指令来表明潜在的不可移植性, 并表明预期的最低可移植性基准。
注: WebGPU 实现之外的工具可以 检查程序使用的所有语言扩展是否都由程序中的requires 指令覆盖。
'requires' language_extension_list ';'
language_extension_name ( ',' language_extension_name ) * ',' ?
与其他指令一样,如果存在requires 指令,则它必须出现在所有声明和const 断言之前。 扩展名称不是标识符: 它们不会解析到声明。
| WGSL 语言扩展 | 描述 |
|---|---|
| readonly_and_readwrite_storage_textures | 允许将read 和read_write访问模式 与存储纹理一起使用。 此外,还添加 textureBarrier 内置函数。 |
| packed_4x8_integer_dot_product | 支持将打包 4 分量 8 位整数向量的 32 位整数标量用作点积指令的输入, 并配合 dot4U8Packed 和 dot4I8Packed 内置函数使用。 此外,还通过 pack4xI8、pack4xU8、 pack4xI8Clamp、pack4xU8Clamp、 unpack4xI8 和 unpack4xU8 内置函数,为打包的 4 分量 8 位整数向量添加打包和解包指令。 |
| unrestricted_pointer_parameters | 从用户定义函数中移除以下限制: |
| pointer_composite_access |
支持根表达式为指针的复合值
分解表达式,其结果为引用。
例如,如果 类似地,如果 |
| uniform_buffer_standard_layout | 允许uniform 地址空间中的缓冲区使用与 其他地址空间相同的内存布局约束。 |
| subgroup_id | 当启用subgroups 扩展时,允许使用subgroup_id 和num_subgroups 内置值。 |
| subgroup_uniformity |
为一致控制流添加额外的作用域 subgroup,
使子组和
四元组内置函数的范围成为同一子组中的所有调用。
注: 使用子组和四元组 内置函数要求启用 subgroups 扩展。 |
| texture_and_sampler_let | 允许let 声明的有效值类型为 纹理或采样器类型。 |
| texture_formats_tier1 | 支持额外的纹素 格式: rgba16unorm、rgba16snorm、rg8unorm、 rg8snorm、rg8uint、rg8sint、 rg16unorm、rg16snorm、rg16uint、 rg16sint、rg16float、r8unorm、 r8snorm、r8uint、r8sint、 r16unorm、r16snorm、r16uint、 r16sint、r16float、rgb10a2unorm、 rgb10a2uint、rg11b10ufloat |
| linear_indexing | 支持global_invocation_index 和 workgroup_index内置值。 |
| immediate_address_space |
启用immediate 地址空间,允许使用
var<immediate> 声明变量,并将其绑定到通过 WebGPU API
直接从命令编码器传递的少量频繁更新数据。
|
| fragment_depth |
为
frag-depth 内置值引入一个新的depth_mode内置参数,其模式为
less和greater,
当片段着色器声明其只会写入保证小于(或大于)现有深度的深度值时
使用这些模式。
|
| buffer_view |
启用buffer 类型
和
buffer_view 内置函数。
允许声明具有不透明存储类型的变量,该存储类型可以 重新解释为其他主机可共享类型。 |
注: 其意图是,随着时间推移,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 将这些视为不同的值,因为它们的机器表示和操作不同。
某些类型是不透明的,这意味着 它们的值具有由实现定义的表示, 无法在 WGSL 程序中直接检查、分解或操作。
类型要么是预声明的,要么 通过声明在 WGSL 源代码中创建。
某些类型表示为模板参数化。
类型生成器是一个
预声明的对象,当使用模板
列表对其参数化时,它表示一种类型。
例如,类型 atomic<u32> 将类型生成器 atomic 与
模板列表 <u32> 组合在一起。
我们区分类型的概念和 WGSL 中表示该类型的语法。 在许多情况下,本规范中类型的拼写形式与其 WGSL 语法相同。 例如:
-
32 位无符号整数值的集合在本规范中写作
u32, 在 WGSL 模块中也如此。 -
结构体类型或包含结构体的类型,其拼写形式有所不同。
某些 WGSL 类型仅用于分析源程序以及 确定程序的运行时行为。 本规范会描述此类类型,但它们不会出现在 WGSL 源文本中。
注: 引用类型不会写入 WGSL 模块。请参阅§ 6.5.3 引用和指针类型。
6.1. 类型检查
WGSL 值通过对表达式求值来计算。
表达式是一段
源文本,
它被解析为名称以“expression”结尾的 WGSL 语法规则之一。
表达式 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,并且
-
我们可以在类型规则中用 u32 替换类型参数 T, 从而得到一个适用于整个表达式的完全 展开的规则。
分析句法 短语时,可能出现三种情况:
-
没有类型规则适用于该表达式。这会产生类型错误。
-
有多个类型规则适用。也就是说,多个重载的前置条件得到满足。 在这种情况下,使用§ 6.1.3 重载解析中描述的决胜过程。
继续上面的示例,只有一个类型规则适用于表达式 1u+2u,因此类型
检查
接受该类型规则的结论,即 1u+2u 的类型为 u32。
当满足以下条件时,WGSL 源程序是良类型的:
-
通过应用类型规则,可以确定程序中每个表达式的静态类型,并且
-
每个语句的类型要求均得到满足。
否则会出现类型错误, 源程序不是有效的 WGSL 模块。
WGSL 是一种静态类型语言, 因为只需检查程序源文本,对 WGSL 模块执行类型检查将要么成功,要么 发现类型错误。
6.1.1. 类型规则表
WGSL 针对表达式的类型规则 被组织为类型规则 表, 每个类型规则占一行。
表达式的 语义是对该表达式求值所产生的效果, 主要是产生结果值。 适用于表达式的类型规则的描述列将指定该表达式的 语义。 语义通常取决于类型规则参数的值,包括 任何子表达式的假定值。 有时表达式的语义还包括产生 结果值之外的效果,例如其子表达式的非结果值效果。
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 浮点求值中所述限制的约束。
注: 自动转换只会出现在两类 情况中。 第一种是将const 表达式转换为可在 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 | |
| ptr<AS, buffer<N1>, AM> 其中 Src 是函数调用的某个实参的类型 | ptr<AS, buffer<N2>, AM> 其中 N1 和 N2 都是const 表达式,并且 Dest 是 Src 中实参所对应形式参数的类型 | 如果 N2 < N1,则为 1, 否则为无穷大 | 将固定大小缓冲区指针实参转换为尺寸更小的 指针形参。1 |
| ptr<AS, buffer<N>, AM> 其中 Src 是函数调用的某个实参的类型 | ptr<AS, buffer, AM> 其中 Dest 是 Src 中实参所对应形式参数的类型 | 1 | 将固定大小缓冲区指针实参转换为运行时大小缓冲区指针形参。1 |
| S | T 以上情况均不适用 | 无穷大 | 其他类型之间不存在自动转换。 |
-
这些转换要求unrestricted_pointer_parameters 和buffer_view 语言特性。
如果满足以下条件,则类型 T 是类型 S 的具体化:
-
T是具体的,并且 -
T不是引用类型,并且 -
ConversionRank(
S,T) 是有限值,并且 -
对于任何其他非引用类型
T2,ConversionRank(S,T2) > ConversionRank(S,T)。
类型为 T 的值 e 的值的具体化是
对 e 应用将 T 映射到
T 的具体化的可行转换后所得的值。
注: 转换为f32 始终优先于转换为f16,因此 只有在模块中启用了 f16 扩展时,自动转换才可能产生f16 值。
6.1.3. 重载解析
当多个类型规则适用于某个句法短语时,会使用 决胜过程 来确定应采用哪一个规则。 此过程称为重载解析, 并假定类型检查已经成功找到子表达式的静态类型。
考虑一个句法 短语 P,以及所有适用于 P 的类型规则。 重载解析算法将这些类型规则称为重载候选。 对于每个候选:
针对 P 的重载解析按如下方式进行,目标是找到唯一一个最优选的重载 候选:
-
对于每个候选 C,枚举句法 短语中子表达式的转换等级。 候选的前置条件已经满足,因此对于 P 中第 i 个子表达式:
-
其静态类型已经计算出来。
-
存在从该表达式的静态类型到前置条件中相应类型断言所要求类型的可行自动转换。 令 C.R(i) 为该转换的ConversionRank。
-
-
如果某候选的某个子表达式在可行自动转换后解析为抽象类型, 但该候选的另一个子表达式不是const 表达式,则淘汰该候选。
-
对候选进行排序:给定两个重载候选 C1 和 C2,如果满足以下条件,则 C1 优先于 C2:
-
对于 P 中的每个表达式位置 i,C1.R(i) ≤ C2.R(i)。
-
也就是说,将 C1 应用于 P 所需的每个表达式转换 至少与将 C2 应用于 P 所需的对应表达式转换同样优先。
-
-
至少存在一个表达式位置 i,使得 C1.R(i) < C2.R(i)。
-
也就是说,应用 C1 所需的至少一个表达式转换 严格优先于应用 C2 所需的对应转换。
-
-
-
如果存在唯一一个候选 C,它优先于所有 其他候选,则重载解析成功,并产生候选类型规则 C。 否则,重载解析失败。
6.2. 普通类型
普通类型是用于布尔值、数值、向量、 矩阵或这些值的聚合的机器表示的类型。
注: WGSL 中的普通类型类似于 C++ 中的简单旧式数据 类型,但还 包括原子类型和抽象数值类型。
6.2.1. 抽象数值类型
这些类型无法在 WGSL 源代码中写出。它们仅用于类型检查。
某些表达式在着色器创建时求值, 并且其数值范围和精度可能大于 GPU 直接实现的范围和精度。
WGSL 为这些求值定义了两种抽象数值类型:
-
AbstractInt 类型是能够以 64 位二进制补码格式表示的整数集合, 其中符号位位于最高有效位。
对这些类型之一的表达式求值不得溢出,也不得产生无穷大或 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 到可构造类型最优先的转换(具有最低 // 转换等级)是 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 到可构造类型的最优先自动转换 // 是 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 位 有符号整数的集合。 它使用二进制补码表示,符号位位于最高有效位。
对具体整数类型执行的表达式发生溢出时,会产生模 2bitwidth 的结果
| 类型 | 最小值 | 最大值 |
|---|---|---|
| i32 | i32(-2147483648) | 2147483647i |
| i32(-0x80000000) | 0x7fffffffi | |
| u32 | 0u | 4294967295u |
| 0x0u | 0xffffffffu |
注: AbstractInt 也是整数类型。
6.2.4. 浮点类型
f32 类型是 IEEE-754 binary32(单精度)格式的 32 位 浮点值集合。 有关详细信息,请参阅§ 15.7 浮点求值。
f16 类型是
IEEE-754 binary16(半精度)格式的 16 位
浮点值集合。如果程序未包含 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 空间中的变量在一个 工作组内共享,但不同 工作组之间不共享。
6.2.9. 数组类型
数组是可通过索引访问的 元素值序列。
| 类型 | 描述 |
|---|---|
| array<E,N> | 包含 N 个类型为 E 的元素的固定大小
数组。 N 称为数组的元素数量。 |
| array<E> | 由类型为 E 的元素组成的运行时大小数组。
它们只能出现在特定上下文中。 |
数组中的第一个元素位于索引 0,之后的每个元素都位于下一个整数索引。 请参阅§ 8.5.3 数组访问表达式。
表达式不得 求值为运行时大小数组类型。
固定大小数组的元素数量表达式 N 受以下约束:
注: 如果 N 依赖任何override 声明,则元素数量值在管线创建 时完全确定,否则在着色器模块创建时完全确定。
注: 为了满足类型等价性要求,任何不是 const 表达式的 override 表达式都必须是标识符。 请参阅由可覆盖常量确定大小的工作组变量
运行时大小数组中的元素数量 由与相应存储缓冲区变量关联的缓冲区绑定大小决定。 请参阅§ 13.3.4 缓冲区绑定 确定运行时大小数组的元素数量。
数组元素类型必须是以下之一:
注: 数组元素类型不能是buffer 类型。
注: 元素类型必须是普通类型。
当且仅当以下所有条件都为真时,两个数组类型才相同:
-
它们具有相同的元素类型。
-
它们的元素数量规范匹配,即以下情况之一为真:
// 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 > ;
注: 由可覆盖常量确定大小的数组类型唯一有效的用途是 作为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>; // 无效示例,因为可覆盖的元素数量仅 // 对工作组变量有效。 // 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 声明和作用域。
当且仅当两个结构体类型具有相同名称时,它们才相同。
结构体成员类型必须是以下之一:
注: 结构体成员类型不能是buffer 类型。
注: 所有用户声明的结构体类型都是具体的。
注: 每个成员类型都必须是普通类型。
结构体成员类型和数组元素类型限制的一些结果包括:
// 具有三个成员的结构体。 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 缓冲区或 存储缓冲区,则可能需要这些属性。 请参阅§ 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,T 的嵌套深度,记作 NestDepth(T),为:
-
向量类型为 1
-
矩阵类型为 2
-
元素类型为 E 的数组类型为 1 + NestDepth(E)
-
如果 T 是成员类型为 M1,...,MN 的结构体类型,则为 1 + max(NestDepth(M1),..., NestDepth(MN))
6.2.12. 可构造类型
许多种类的值可以被创建、加载、存储、传入函数 以及从函数返回。 我们称这些值为可构造的。
如果类型是以下之一,则它是可构造的:
注: 所有可构造类型都具有创建时固定占用空间。
注: 原子类型和运行时大小数组类型不是 可构造的。 包含原子类型和运行时大小数组的复合类型也不可构造。
6.2.13. 固定占用空间类型
变量的内存占用空间 是用于存储变量内容的内存位置 数量。 变量的内存占用空间取决于其存储类型,并在着色器生命周期中的某个时刻 最终确定。 大多数变量很早就确定大小,即在着色器创建时。 某些变量可能稍后在管线创建时确定大小, 还有一些最晚在着色器执行开始时才确定。
如果一种类型的具体化具有 在着色器创建时完全确定的大小,则该类型具有创建时固定占用空间。
如果一种类型的大小在管线创建 时完全确定,则该类型具有固定 占用空间。
注: 所有具体的创建时固定占用空间类型和固定占用空间类型都是可存储的。
注: 管线创建依赖于着色器创建,因此 具有创建时固定占用空间的类型也具有固定占用空间。
具有创建时固定占用空间的类型包括:
具有固定 占用空间的类型可以是以下任一种:
注: 对于元素数量是 override 表达式但不是const 表达式的固定大小数组,唯一有效的用途是作为 workgroup 地址空间中的内存视图。 这包括工作组变量的存储类型。
注: 固定占用空间类型可以直接或 间接包含原子类型,而可构造类型则不能。
注: 固定占用空间类型不包括运行时大小数组、 运行时大小 缓冲区以及任何包含运行时大小数组的结构体。
6.3. 缓冲区类型
这些类型需要 buffer_view 语言特性。
缓冲区值表示一段内存, 其内容可以被解释为 另一种主机可共享类型。 除此之外,缓冲区值是不透明的。 当需要以多种不同类型访问变量中的数据,或者着色器作者希望将一个变量细分为 多个逻辑变量时,它们非常有用。
缓冲区类型不是可构造的,并且只能在通过 buffer_view 内置函数将其数据解释为非不透明的主机可共享类型后访问。
| 类型 | 描述 |
|---|---|
| buffer<N> | 具有 N 字节存储空间的固定大小
缓冲区。 N 必须是一个override 表达式。 |
| buffer | 一个运行时大小缓冲区。 |
固定大小缓冲区只能由storage、uniform 和workgroup 地址空间中的变量实例化。 在 storage 和 uniform 地址空间中,其大小必须是const 表达式。 运行时大小缓冲区只能由 storage 地址空间中的变量实例化。
如果 N 不大于 0:
如果启用了f16 扩展 且 N 不能被 2 整除:
如果未启用f16 扩展 且 N 不能被 4 整除:
当且仅当满足以下任一条件时,两个缓冲区类型才相同:
-
它们都是运行时大小缓冲区。
-
它们都是具有创建时固定占用空间的固定大小缓冲区,并且 大小值相等,即使一个有符号而另一个无符号也是如此。(在这种情况下有符号和 无符号值可以比较,因为大小始终为 正值。)
-
它们都是固定大小缓冲区,并且其大小被指定为解析 到同一个管线可覆盖常量声明的标识符。
6.4. 枚举类型
枚举类型是一组有限的 命名值。 枚举用于区分某个特定概念的一组可能情况,例如有效的纹素格式集合。
枚举值是 枚举中的一个命名值。 每个枚举值都不同于所有 其他枚举值,也不同于所有其他种类的值。
WGSL 源代码中没有声明新枚举值或新枚举类型的机制。
注: 枚举值被用作模板参数。
6.4.1. 预声明枚举值
下表列出了 WGSL 中的枚举类型、它们预声明的枚举值,以及这些枚举类型所需的语言扩展。 这些枚举类型存在,但无法在 WGSL 源代码中写出。
6.5. 内存视图
除了使用普通 值进行计算之外,WGSL 程序还经常通过内存访问操作 从内存读取值或向内存写入值。 每次内存访问都通过内存视图执行。
内存视图包括:
内存视图的访问模式必须受该地址空间支持。请参阅§ 7 变量和值声明。
6.5.1. 可存储类型
变量中包含的值必须具有 可存储类型。 可存储类型可以具有由 WGSL 定义的显式表示, 如§ 14.4.4 值的内部布局中所述, 也可以是不透明的,例如纹理和采样器。
如果一种类型既是具体的,又是以下之一,则它是可存储的:
注: 也就是说,可存储类型包括具体的普通类型、 纹理类型、采样器类型和缓冲区类型。
6.5.2. 主机可共享类型
主机可共享类型用于描述主机和 GPU 之间共享的缓冲区内容, 或者无需格式转换即可在主机和 GPU 之间复制的内容。 当用于此目的时,还可以按照§ 14.4 内存布局中的描述,为该类型应用布局属性。 如§ 7.3 var 声明中所述,uniform 缓冲区和存储缓冲区 变量的存储类型必须是主机可共享的。
如果一种类型既是具体的,又是以下之一,则它是主机可共享的:
注: 阶段间输入和输出类型的限制 在§ 13.3.1 阶段间输入和输出接口及后续各节中 描述。 这些类型同样有大小,但计数方式不同。
注: 纹理和 采样器也可以在主机和 GPU 之间共享, 但其内容是不透明的。 本节中的主机可共享类型专门用于storage 和uniform 缓冲区。
6.5.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>' 是引用 用于保存 'i32' 值的内存的指针值类型,该内存使用 'function' 地址空间中的内存位置。此处 'i32' 是存储类型。 隐含的访问模式是 'read_write'。 有关默认值,请参阅“地址空间”一节。 */ ptr_int : ptr< function, i32> , // 'ptr<private,array<f32,50>,read_write>' 是这样一种指针值类型: // 它引用用于保存 50 个 'f32' 类型元素数组的内存,并使用 // 'private' 地址空间中的内存位置。 // 此处存储类型是 '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.5.4. 有效和无效的内存引用
引用的形成方式在§ 6.5.8 形成 引用和指针值中有详细描述。 通常,有效 引用通过以下方式形成:
通常,无效内存引用通过以下方式形成:
无效指针通过 以下方式形成:
-
调用 bufferView 或bufferArrayView 内置函数,而所得指针的内存视图原本会包含超出 缓冲区指针实参内存视图边界之外的内存位置。
有效指针是 与有效引用相对应的指针。
6.5.5. 起源变量
指针值的起源 变量被定义为相应引用值的起源变量。
注: 起源变量是一个动态概念。 函数形式参数的起源变量取决于该函数的 调用点。 不同调用点可能提供指向不同起源变量的指针。
有效引用始终 对应某个变量的部分或全部内存位置的非空内存视图。
在下面的示例中,当且仅当
i 为 0 或 1 时,引用 the_particle.position[i] 才有效。
当 i 为 2 时,该引用将是无效内存
引用,但若忽略这一点,它会对应
the_particle.color_index 的内存位置。
6.5.6. 越界访问
越界访问是一种程序缺陷,因为如果按照所写的方式执行,它通常会:
因此,实现不会按照所写的方式执行该访问。 执行越界访问会产生动态错误。
注: 错误解释存储类型的一个例子出现在
上一节的示例中。
当 i 为 2 时,表达式 the_particle.velocity[i] 的
类型为 ref<storage,f32,read_write>,这意味着它是一个以f32 为
存储类型的内存视图。
但是,这些内存位置分配给了
color_index 成员,因此实际存储的值类型为i32。
这些结果包括但不限于以下情况:
- 陷阱
-
着色器调用立即终止,并且着色器阶段输出被 设置为零值。
- 无效 加载
-
从无效引用执行的加载 可以返回以下之一:
-
当起源变量是uniform 缓冲区或存储缓冲区时, 返回绑定到起源变量的 WebGPU
GPUBuffer的任意内存位置 中的值 -
当起源变量不是uniform 缓冲区或存储缓冲区时, 返回起源变量中任意内存位置中的值
-
该引用存储类型的零值
-
如果加载的值是向量,则返回值 (0, 0, 0, x),其中 x 为:
-
对于整数分量,为 0、1 或最大正值
-
对于浮点分量,为 0.0 或 1.0
-
-
- 无效 存储
-
对无效引用执行的存储可以执行以下操作之一:
如果无效加载或存储被重定向,以访问共享地址空间中某个变量内的不同 位置,则可能发生数据竞争。 例如,多个并发执行的调用的访问可能被重定向 到数组中的第一个元素。 如果至少有一次访问是写入,并且它们没有以其他方式同步, 则结果是数据竞争,从而产生动态错误。
越界访问会使一致性分析的假设失效。 例如,如果某个调用由于越界访问而提前终止,则它 将无法再参与集合操作。 特别是,对 workgroupBarrier 的调用可能使着色器挂起, 而导数可能产生无效结果。
6.5.7. 引用和指针的用例
引用和指针根据其使用方式进行区分:
-
变量的类型是 引用类型。
-
取地址操作 (一元
&)将引用值转换为其对应的指针值。 -
间接操作 (一元
*)将指针值转换为其对应的引用值。 -
let 声明 可以具有指针类型,但不能具有引用类型。
-
形式 参数可以具有指针类型,但不能具有引用类型。
-
加载规则:在 函数内部,引用会自动解引用(读取)以满足类型规则:
-
在函数中,当存储类型为 T 的引用表达式 r 被用于 语句或表达式中,并且
-
r 的访问模式为read 或read_write,并且
-
唯一可能匹配的类型规则要求 r 具有类型 T 的值时,
-
该类型规则要求被视为已满足,并且
-
在该上下文中对 r 求值的结果,是求值时 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>。 // 应用加载规则,因为引用的存储类型与 // 表达式所需类型匹配,并且没有其他类型规则适用。 // 在此上下文中对 'age' 求值得到的结果,是执行 return 语句时 // 从 'age' 所引用的内存位置中加载的 i32 值。 // return age ; } fn caller () { age = 21 ; // copy_age 常量将得到 i32 值 21。 let copy_age : i32= get_age (); }
以这种方式定义指针可以支持两个主要用例:
-
使用指针类型的 let 声明,为变量内容的一部分创建简短名称。
-
使用函数的形式参数引用调用函数可访问的变量内存。 调用函数。
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 加到 0,得到右侧的最终值 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.5.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 所引用的 storage 内存位置。 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] 所引用的 storage 内存位置。 uv [ 1 ] = 2.0 ; var m : mat3x2< f32> ; // 对 'm[2]' 求值时: // 1. 首先对 'm' 求值,得到对 // 'm' 变量内存的引用。结果类型为 ref<function,mat3x2<f32>,read_write>。 // 2. 然后应用 '[2]' 数组索引短语,得到对 // 上一步引用值所指向的第三个列向量内存的引用。 // // 因此 'm[2]' 表达式的类型为 ref<function,vec2<f32>,read_write>。 // 'let' 声明的类型为 vec2<f32>,因此声明 // 语句要求初始化器的类型为 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]' 数组索引短语,得到对 // 上一步引用值所引用数组的第五个元素内存的引用。 // // 结果值的类型为 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)。 // 将值 (1.0, 2.0) 存储到 uv 所引用的 storage 内存位置。 * 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。 // 将 f32 值 1.0 存储到 uv.x 所引用的 storage 内存位置。 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。 // 将 f32 值 2.0 存储到 uv[1] 所引用的 storage 内存位置。 uv_ptr [ 1 ] = 2.0 ; let m_ptr = & m ; // 对 'm_ptr[2]' 求值时: // 1. 首先对 'm_ptr' 求值,得到指向 // 'm' 变量内存的指针。结果类型为 ptr<function,mat3x2<f32>,read_write>。 // 2. 然后应用 '[2]' 数组索引短语,得到对 // 上一步引用值所指向的第三个列向量内存的引用。 // // 因此 'm[2]' 表达式的类型为 ref<function,vec2<f32>,read_write>。 // 'let' 声明的类型为 vec2<f32>,因此声明 // 语句要求初始化器的类型为 vec2<f32>。 // 加载规则适用(因为没有其他类型规则可以适用),并且 // 对初始化器求值得到执行声明时从 // 'm[2]' 所引用内存位置加载的 vec2<f32> 值。 // let p_m_col2 : vec2< f32> = m_ptr [ 2 ]; let A_ptr = & A ; // 对 'A[4]' 求值时 // 1. 首先对 'A' 求值,得到指向 // 'A' 变量内存的指针。结果类型为 ptr<function,array<i32,5>,read_write>。 // 2. 然后应用 '[4]' 数组索引短语,得到对 // 上一步引用值所引用数组的第五个元素内存的引用。 // // 结果值的类型为 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 ; }
指针值通过以下方式之一形成:
-
对引用使用取地址(一元
&)运算符。 -
如果函数形式 参数具有指针类型,那么当函数在 运行时被调用时,对该形式参数的使用表示 调用函数中调用点为对应操作数提供的指针值。
-
bufferView 或 bufferArrayView 内置函数调用的结果。
在所有情况下,结果的访问模式 与原始指针的访问模式相同。
// 在 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 地址空间的默认访问模式,因此在这种情况下 // 不必写出 read_write 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' 解析到上一条语句中声明的函数作用域变量 'x', // 并具有类型 ref<function,u32,read_write>。 // 应用一元 '&' 运算符会将引用转换为指针。 // 访问模式默认为 'read_write'。 let inner_x_ptr : ptr< function, u32> = & x ; }
6.5.9. 与其他语言中的引用和指针比较
本节是非规范性的,而非规范性的。
WGSL 中的引用和指针比其他语言中的限制更多。 特别是:
-
在 WGSL 中,引用不能直接声明为另一个引用或变量的别名, 无论是作为变量还是形式参数。
-
在 WGSL 中,函数不得返回指针或引用。
-
在 WGSL 中,无法在整数值与指针值之间进行转换。
-
在 WGSL 中,无法强制将指针值的类型更改为另一种指针类型。
-
复合分量引用表达式有所不同: 它接受对复合值的引用,并产生对 复合值内部某个分量或元素的引用。 在 WGSL 中,这些被视为不同的引用,即使在较低层级的实现抽象中 它们可能具有相同的机器地址。
-
-
在 WGSL 中,无法强制将引用值的类型更改为另一种引用类型。
-
在 WGSL 中,无法更改指针或引用的访问模式。
-
相比之下,C++ 会自动将非 const 指针转换为 const 指针, 并提供
const_cast将 const 值转换为非 const 值。
-
-
在 WGSL 中,无法从“堆”分配新内存。
-
在 WGSL 中,无法显式销毁变量。 WGSL 变量的内存只有在变量离开作用域时才会变得不可访问。
注: 根据上述规则,不可能形成 “悬空”指针, 即不引用某个“存活” 起源变量内存的指针。 内存视图可能是无效 内存引用,但它 绝不会访问与 起源 变量或缓冲区无关的内存 位置。
6.6. 纹理和采样器类型
纹素是用作纹理中可独立访问的最小元素的标量或向量。 单词 texel 是 texture element(纹理元素)的缩写。
纹理是一组 支持对渲染有用的特殊操作的纹素。 在 WGSL 中,这些操作通过纹理内置函数调用。 完整列表请参阅§ 17.7 纹理内置函数。
WGSL 纹理对应于 WebGPU GPUTexture。
纹理具有以下特征:
- 纹素格式
-
每个纹素的数据表示。请参阅§ 6.6.1 纹素格式。
- 维度
-
网格坐标中的维数,以及坐标的解释方式。 维数为 1、2 或 3。 大多数纹理使用笛卡尔坐标。 立方体纹理具有六个正方形面,并使用 三维坐标进行采样,该坐标被解释为从原点朝向 以原点为中心的立方体的方向向量。
- 大小
-
沿各维度的网格坐标范围。它是mip 级别的函数。
- mip 级别数量
-
对于采样纹理和深度纹理,mip 级别数量至少为 1;对于存储 纹理,mip 级别数量等于 1。
Mip 级别 0 包含 纹理的完整大小版本。 每个后续 mip 级别都包含前一个 mip 级别的过滤版本, 其大小为前一个 mip 级别的一半(考虑舍入)。
对纹理进行采样时,会使用显式或隐式计算的细节级别 选择要从中读取纹素数据的 mip 级别。随后通过 过滤将这些数据组合起来以产生采样值。 - 数组化
-
纹理是否数组化。
-
非数组化纹理是纹素网格。
-
数组化纹理是纹素网格的同质数组。
-
- 数组大小
-
如果纹理是数组化的,则为同质网格的数量。
- 采样数量
-
如果纹理是多重采样的,则为采样数量。
纹理中的每个纹素都与唯一的逻辑纹素地址相关联, 它是具有以下内容的整数元组:
纹理的物理组织通常针对渲染操作进行优化。 为实现这一点,许多细节对程序员隐藏,包括数据布局、数据类型以及 无法直接用着色器语言表达的内部操作。
因此,着色器无法直接访问纹理变量中的纹素内存。 相反,访问通过一个不透明句柄进行:
-
在着色器中:
-
构造 WebGPU 管线时,纹理变量的存储类型和绑定 必须 与相应的绑定组布局条目兼容。
通过这种方式,纹理类型所支持的操作集合 由具有该纹理类型形式 参数的纹理内置函数的可用性决定。
注: 纹理变量存储的句柄不能被 着色器更改。 也就是说,即使它提供访问的底层纹理可能是可变的(例如只写存储纹理),该变量本身也是只读的。
纹理类型是 以下各节中定义的类型集合:
采样器是一个不透明句柄,用于控制如何从采样 纹理或深度纹理中访问纹素。
WGSL 采样器映射到 WebGPU GPUSampler。
纹素访问由采样器的若干属性控制:
- 寻址模式
-
控制如何处理纹理边界和越界 坐标。 每个纹理维度的寻址模式可以独立设置。 请参阅 WebGPU
GPUAddressMode。 - 过滤模式
-
控制访问哪些纹素以产生最终结果。 过滤可以使用最近的纹素,也可以在多个 纹素之间进行插值。 多种过滤模式可以独立设置。 请参阅 WebGPU
GPUFilterMode。 - LOD 限制
-
控制所访问的最小和最大细节级别。
- 比较
-
控制比较采样器所执行的比较类型。 请参阅 WebGPU
GPUCompareFunction。 - 最大各向异性
-
控制采样器使用的最大各向异性值。
采样器不能在 WGSL 模块中创建,其状态(例如 上面列出的属性)在着色器中不可变,只能由 WebGPU API 设置。
如果过滤采样器(即任何 使用插值过滤的采样器)与具有不可过滤格式的纹理一起使用,则会产生管线创建错误。
注: 采样器变量存储的句柄不能被 着色器更改。
6.6.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.6.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)) | texture_formats_tier1 |
| rgba16snorm | 16snorm | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | texture_formats_tier1 |
| rgba16uint | 16uint | r, g, b, a | vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba16sint | 16sint | r, g, b, a | vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba16float | 16float | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rg8unorm | 8unorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| rg8snorm | 8snorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| rg8uint | 8uint | r, g | vec4<u32>(CTF(r), CTF(g), 0u, 1u) | texture_formats_tier1 |
| rg8sint | 8sint | r, g | vec4<i32>(CTF(r), CTF(g), 0, 1) | texture_formats_tier1 |
| rg16unorm | 16unorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| rg16snorm | 16snorm | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| rg16uint | 16uint | r, g | vec4<u32>(CTF(r), CTF(g), 0u, 1u) | texture_formats_tier1 |
| rg16sint | 16sint | r, g | vec4<i32>(CTF(r), CTF(g), 0, 1) | texture_formats_tier1 |
| rg16float | 16float | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | texture_formats_tier1 |
| r32uint | 32uint | r | vec4<u32>(CTF(r), 0u, 0u, 1u) | |
| r32sint | 32sint | r | vec4<i32>(CTF(r), 0, 0, 1) | |
| r32float | 32float | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | |
| rg32uint | 32uint | r, g | vec4<u32>(CTF(r), CTF(g), 0u, 1u) | |
| rg32sint | 32sint | r, g | vec4<i32>(CTF(r), CTF(g), 0, 1) | |
| rg32float | 32float | r, g | vec4<f32>(CTF(r), CTF(g), 0.0, 1.0) | |
| rgba32uint | 32uint | r, g, b, a | vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba32sint | 32sint | r, g, b, a | vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| rgba32float | 32float | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| bgra8unorm | 8unorm | b, g, r, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | |
| r8unorm | 8unorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| r8snorm | 8snorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| r8uint | 8uint | r | vec4<u32>(CTF(r), 0u, 0u, 1u) | texture_formats_tier1 |
| r8sint | 8sint | r | vec4<i32>(CTF(r), 0, 0, 1) | texture_formats_tier1 |
| r16unorm | 16unorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| r16snorm | 16snorm | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| r16uint | 16uint | r | vec4<u32>(CTF(r), 0u, 0u, 1u) | texture_formats_tier1 |
| r16sint | 16sint | r | vec4<i32>(CTF(r), 0, 0, 1) | texture_formats_tier1 |
| r16float | 16float | r | vec4<f32>(CTF(r), 0.0, 0.0, 1.0) | texture_formats_tier1 |
| rgb10a2unorm | r, g, b: 10unorm a: 2unorm | r, g, b, a | vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a)) | texture_formats_tier1 |
| rgb10a2uint | r, g, b: 10uint a: 2uint | r, g, b, a | vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a)) | texture_formats_tier1 |
| rg11b10ufloat | r, g: 11float b: 10float | r, g, b | vec4<f32>(CTF(r), CTF(g), CTF(b), 1.0) | texture_formats_tier1 |
6.6.2. 采样纹理类型
采样纹理能够 与采样器结合进行访问。 它也可以在不使用采样器的情况下访问。 采样纹理只允许读 访问。
纹素格式是
绑定到纹理变量的
GPUTexture
的 format
属性。
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 位浮点结果(或 f32 向量)。
6.6.3. 多重采样纹理类型
多重采样纹理的 采样数量为 1 或更多。 尽管名称如此,它不能与采样器一起使用。 如果忽略采样索引,则每个 逻辑纹素 地址实际上存储相当于多个纹素的数据。
纹素格式是
绑定到纹理变量的
GPUTexture
的 format
属性。
WebGPU 会验证纹理、绑定组布局的 sampleType
与纹理变量的采样类型之间的兼容性。
texture_multisampled_2d 通过采样类型进行参数化,并且该类型
必须是
f32、i32 或 u32。
| 类型 | 维度 | 数组化 |
|---|---|---|
| texture_multisampled_2d<T> | 2D
| 否 |
| texture_depth_multisampled_2d | 2D
| 否 |
-
T 是采样 类型。
6.6.4. 外部采样纹理类型
外部纹理是一种不透明的
二维浮点采样纹理类型,类似于
texture_2d<f32>,
但可能具有不同的表示。
可以使用 textureLoad 或
textureSampleBaseClampToEdge 内置
函数读取它们,这些函数会处理这些不同的表示。
请参阅 WebGPU § 6.4 GPUExternalTexture。
| 类型 | 维度 | 数组化 |
|---|---|---|
| texture_external | 2D
| 否 |
6.6.5. 存储纹理类型
存储纹理支持在不使用采样器的情况下 访问单个纹素值。
存储纹理类型必须通过 存储纹理的 纹素格式之一进行参数化。 纹素格式决定§ 6.6.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
| 否 |
-
Format 必须是枚举值,对应存储纹理的纹素 格式之一
-
Format 和 Access 的无效组合不会导致着色器创建错误。 Format 与 Access 的组合会在管线创建期间的着色器绑定验证 步骤中检查。 无效组合将导致管线创建 错误。
6.6.6. 深度纹理类型
深度纹理能够 与sampler_comparison结合进行访问。 它也可以在不使用采样器的情况下访问。 深度纹理只允许读 访问。
纹理的纹素格式
在 GPUTextureBindingLayout
中定义。
| 类型 | 维度 | 数组化 |
|---|---|---|
| texture_depth_2d | 2D
| 否 |
| texture_depth_2d_array | 2D
| 是 |
| texture_depth_cube | Cube
| 否 |
| texture_depth_cube_array | Cube
| 是 |
6.6.7. 采样器类型
采样器通过 以下操作的组合来调节对采样纹理 或 深度纹理的访问:
采样器类型包括:
| 类型 | 描述 |
|---|---|
| sampler | 采样器。调节对采样纹理的访问。 |
| sampler_comparison | 比较采样器。 调节对深度纹理的访问。 |
采样器在 WebGPU API 中创建时进行参数化。 WGSL 模块无法修改它们。
采样器只能由纹理内置函数使用。
sampler sampler_comparison
6.7. AllTypes 类型
AllTypes 类型是所有 WGSL 类型的集合。
无法在 WGSL 源代码中写出 AllTypes 类型。
所有 预声明类型和类型生成器的列表请参阅§ 6.10 预声明类型和类型生成器摘要。
相反,AllTypes 类型的存在使类型检查规则能够应用于任何可能 包含普通值的短语。 WGSL 通过将类型定义为一种值,并允许表达式 表示类型,使这些规则保持一致。
促成这一设计的情形是模板参数,它在不同上下文中可以表示 多种事物, 包括类型、枚举值或普通值。 特别是,template_arg_expression 语法规则展开为 expression 语法 非终结符。
6.8. 类型别名
类型别名为现有类型声明一个新 名称。 该声明必须出现在模块作用域中,并且其作用域是整个程序。
当类型 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.9. 类型说明符语法
请参阅§ 8.17 类型表达式。
注: 表达式也可以表示类型,方法是通过primary_expression 语法规则展开为template_elaborated_ident, 并通过圆括号化实现。
6.10. 预声明类型和类型生成器摘要
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.5.3 引用和指针类型 |
| texture_1d | 请参阅§ 6.6.2 采样纹理类型 |
| texture_2d | |
| texture_2d_array | |
| texture_3d | |
| texture_cube | |
| texture_cube_array | |
| texture_multisampled_2d | 请参阅§ 6.6.3 多重采样纹理类型 |
| texture_storage_1d | 请参阅§ 6.6.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 ;
每个此类声明必须具有显式 指定的类型或初始化器。 可以同时指定类型和初始化器。 每个此类声明都会确定关联数据值的类型,称为 该声明的有效值类型。 声明的有效值类型为:
-
如果显式指定,则为所声明的类型。
-
否则,如果初始化器表达式的类型为
T:-
对于
const声明,有效值类型就是T本身。 -
对于
override、let或var声明, 有效值类型 是T的具体化。
-
每一种值或变量声明都可以对初始化器表达式的形式(如果存在) 以及有效值类型施加额外约束。
| 声明 | 可变性 | 作用域 | 有效值类型1 | 初始化器支持 | 初始化器表达式2 | 资源接口的一部分 |
|---|---|---|---|---|---|---|
| const | 不可变 | 模块或函数 | 可构造(具体或抽象) | 必需 | const 表达式 | 否 |
| override | 不可变 | 模块 | 具体标量 | 可选3 | const 表达式或override 表达式 | 否4 |
| let | 不可变 | 函数 | 具体可构造类型或 指针类型。 此外,如果支持texture_and_sampler_let 特性, 还可以是纹理或采样器类型。 | 必需 | const 表达式、override 表达式或 运行时 表达式 | 否 |
|
var<storage, read> var<storage> | 不可变 | 模块 | 具体主机可共享 | 不允许 | 是。 存储 缓冲区 | |
| var<storage, read_write>5,6 | 可变 | 模块 | 具体主机可共享 | 不允许 | 是。 存储 缓冲区 | |
| var<uniform> | 不可变 | 模块 | 具体可构造主机可共享 类型或固定大小 缓冲区 | 不允许 | 是。 uniform 缓冲区 | |
| var<immediate> | 不可变 | 模块 | 具体可构造主机可共享类型, 不包括数组以及包含数组成员的结构体 | 不允许 | 是。 立即 数据 | |
| var5 | 不可变7 | 模块 | 纹理 | 不允许 | 是。 纹理 资源 | |
| var | 不可变 | 模块 | 采样器 | 不允许 | 是。 采样器 资源 | |
| var<workgroup>6,8 | 可变 | 模块 | 具有固定 占用空间的具体普通类型,或固定大小缓冲区9 | 不允许10 | 否 | |
| var<private> | 可变 | 模块 | 具体可构造 | 可选10 | const 表达式或override 表达式 | 否 |
| var<function> var | 可变 | 函数 | 具体可构造 | 可选10 | const 表达式、override 表达式或 运行时 表达式 | 否 |
-
如果未指定初始化器,则必须在管线创建时提供值。
-
Override 声明是着色器接口的一部分, 但不是 绑定资源。
-
访问模式不是read 的存储缓冲区和 存储 纹理不能在顶点着色器阶段中被静态 访问。 请参阅 WebGPU
createBindGroupLayout()。 -
原子类型只能 出现在可变存储缓冲区或 workgroup 变量中。
-
访问模式为write 或read_write 的存储纹理中的数据是可变的, 但只能通过 textureStore 内置函数修改。 变量本身不能修改。
-
最外层数组的元素数量或 缓冲区的大小可以是override 表达式。
-
如果没有初始化器,则变量会被默认 初始化。
7.1. 变量与值
变量 声明是 WGSL 模块中唯一的可变数据。 值声明始终 不可变。 变量可以作为引用和指针值的基础,因为变量有关联的内存位置, 而值声明不能作为指针或引用 值的基础。
使用变量通常比使用值声明开销更大, 因为使用变量需要额外操作来读取或 写入与变量关联的内存位置。
一般而言,作者应按以下顺序优先使用声明, 最优先的选项列在最前:
这通常会使着色器获得最佳的整体性能。
7.2. 值声明
WGSL 提供多种值声明。 每种声明的值都会在 着色器生命周期中的不同时间点固定下来。 不同种类的值声明及其值固定的时机如下:
7.2.1. const 声明
const 声明 为一个在着色器创建时固定的数据值指定名称。 每个 const 声明都需要一个初始化器。 const 声明可以在模块或函数作用域中声明。 初始化器表达式必须是 const 表达式。 const 声明的类型必须是具体或 抽象的可构造类型。 const 声明是唯一一种有效值类型 可以是抽象类型的声明。
注: 由于抽象数值类型无法在 WGSL 中写出,因此只能 通过类型推断使用它们。
const a = 4 ; // 值为 4 的 AbstractInt。 const b : i32= 4 ; // 值为 4 的 i32。 const c : u32= 4 ; // 值为 4 的 u32。 const d : f32= 4 ; // 值为 4 的 f32。 const e = vec3( a , a , a ); // 值为 (4, 4, 4) 的 AbstractInt vec3。 const f = 2.0 ; // 值为 2 的 AbstractFloat。 const g = mat2x2( a , f , a , f ); // 值如下的 AbstractFloat mat2x2: // ((4.0, 2.0), (4.0, 2.0))。 // AbstractInt a 转换为 AbstractFloat。 // AbstractFloat 无法转换为 AbstractInt。 const h = array( a , f , a , f ); // 具有 4 个分量的 AbstractFloat 数组: // (4.0, 2.0, 4.0, 2.0)。
7.2.2. override 声明
override 声明为一个 管线可覆盖常量值指定名称。 override 声明必须只能在 模块作用域中声明。 管线可覆盖常量的值在 管线创建 时固定。 如果指定了值,则该值是 WebGPU 管线创建方法提供的值,否则 是其具体化后的初始化器表达式的值。 override 声明的有效值类型必须是具体的 标量类型。
初始化器表达式是可选的。 如果存在,它必须是override 表达式,并表示管线可覆盖 常量默认值。 如果未指定初始化器,并且在管线创建时没有提供值,则会产生管线创建错误。
如果声明应用了 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 声明的有效值类型必须是具体的可构造类型或 指针类型。 如果支持texture_and_sampler_let 特性,则 有效值类型还可以是纹理或采样器类型。
注: 由于buffer 类型不是可构造的,因此只有指向 缓冲区的指针才能用于 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 表达式或override 表达式。 位于function 或 private 之外地址空间的变量不得具有 初始化器。
当一个标识符解析到变量声明时,该 标识符 是一个表示变量内存的引用内存视图的表达式, 其类型是变量的引用类型。 请参阅§ 8.11 变量标识符表达式。
如果变量声明的地址空间或访问模式在
程序源代码中指定,则它们作为 var 关键字之后的模板列表写出:
位于private、storage、 uniform、workgroup 和handle 地址空间中的变量只能 在模块作用域中声明,而 function 地址空间中的变量只能 在函数作用域中声明。 除 handle 和 function 之外的所有地址 空间都必须指定地址空间。 handle 地址空间不得指定。 指定 function 地址空间是可选的。
访问模式始终具有 默认值,并且除storage 地址空间中的变量之外, 不得在 WGSL 源代码中 指定访问模式。 请参阅§ 14.3 地址空间。
uniform 地址空间中的变量是uniform 缓冲区变量。 其存储类型必须是主机可共享的可构造类型或 固定大小缓冲区 类型,并且必须满足 地址空间布局约束。
storage 地址空间中的变量是存储缓冲区变量。 其存储类型必须是主机可共享类型,并且 必须满足 地址空间布局约束。 变量可以使用read 或read_write 访问模式声明;默认为 read。
immediate 地址空间中的变量是立即数据变量。 其存储类型必须是主机可共享的可构造类型, 不包括数组以及包含数组成员的结构体。 每个入口点必须静态访问 至多一个立即数据变量。 立即变量的值通过 WebGPU API 命令编码器记录的setImmediates 命令设置, 并在着色器执行期间保持不变。 变量大小受管线布局的immediateSize 配置限制。
纹理资源是 一个其有效值类型为纹理类型的变量。 它在模块作用域中声明。 它保存一个不透明句柄,用于 访问纹理中底层的纹素网格。 句柄本身位于handle 地址空间中,并且始终只读。 在许多情况下,底层纹素是只读的,我们称纹理变量不可变。 对于只写存储纹理和读写存储 纹理,底层纹素是可变的,按照约定, 我们称纹理变量可变。
采样器资源是 一个其有效值类型为采样器类型的变量。 它在模块作用域中声明, 位于handle 地址空间中, 并且不可变。
如§ 13.3.2 资源接口中所述,uniform 缓冲区、存储 缓冲区、 纹理和采样器构成着色器的资源接口。
变量的生命周期是 着色器执行期间 内存位置与变量相关联的时间段。 模块作用域 变量的生命周期是整个 着色器阶段的执行期间。 对于每次调用,private 和function 地址空间中的变量都有一个独立实例。 函数作用域变量是 一个动态上下文。 函数作用域变量的生命周期由其作用域决定:
两个资源变量的内存位置可能重叠, 但如果其中任一变量可变,则会产生动态错误。 其他生命周期重叠的变量不会 具有重叠的内存位置。 当变量的生命周期结束时,其内存可以用于另一个变量。
注: WGSL 确保变量内容只能在 变量的生命周期内被观察到。
当private、function 或workgroup 地址空间中的变量被创建时,它将 具有初始值。 如果未指定初始化器,则初始值是默认 初始值。 初始值按如下方式计算:
-
对于 function 地址空间中的变量:
-
对于 private 地址空间中的变量:
-
否则,它是求值具体化后的初始化器表达式所得的结果。 初始化器必须是override 表达式,因此其值 最迟在管线创建时固定。
-
对于 workgroup 地址空间中的变量:
其他地址空间中的变量是由 绘制命令或分派命令中的绑定设置的资源。
考虑以下 WGSL 片段:
var i : i32; // 初始值为 0。不推荐这种风格。 loop { var twice : i32= 2 * i ; // 每次迭代都会重新求值。 i ++ ; if i == 5 { break ; } }
i 将依次取值 0、1、2、3、4、5,而变量 twice 将依次取
值 0、2、4、6 和 8。
考虑以下 WGSL 片段:
因为x 是变量,所以对它的所有访问都会转换为加载和存储操作。
不过,预计浏览器或驱动程序会优化此中间表示,
以消除冗余加载。
var < private> decibels : f32; var < workgroup> worklist : array< i32, 10 > ; struct Params { specular : f32, count : i32} // Uniform 缓冲区。始终只读,并具有更严格的布局规则。 @group ( 0 ) @binding ( 2 ) var < uniform> param : Params ; // 一个 uniform 缓冲区 // 一个用于读取和写入的存储缓冲区 @group ( 0 ) @binding ( 0 ) var < storage, read_write> pbuf : array< vec2< f32>> ; // 纹理和采样器始终位于 "handle" 空间。 @group ( 0 ) @binding ( 1 ) var filter_params : sampler;
// 存储缓冲区 @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; // function 地址空间中的变量。 var delta : i32; // function 地址空间中的另一个变量。 var sum : f32= 0.0 ; // 具有初始化器的 function 地址空间变量。 var pi = 3.14159 ; // 从初始化器推断 f32 存储类型。 }
7.4. 变量和值声明语法摘要
| variable_decl '=' expression
| 'let' optionally_typed_ident '=' expression
| 'const' optionally_typed_ident '=' expression
'var' _disambiguate_template template_list ? optionally_typed_ident
ident ( ':' type_specifier ) ?
attribute * variable_decl ( '=' expression ) ?
'const' optionally_typed_ident '=' expression
| attribute * 'override' optionally_typed_ident ( '=' expression ) ?
8. 表达式
表达式指定如何计算值。
不同种类的值表达式在何时 求值以及表达能力之间进行权衡。 求值越早,操作受到的约束越多,但 值可以使用的位置也越多。这种权衡使每种值声明具有不同的 灵活性。 const 表达式和 override 表达式在 GPU 执行之前求值,因此最终 GPU 代码中只需要 表达式计算的结果。 此外,由于const 表达式在着色器创建时求值,因此 它们可用于比override 表达式更多的情形,例如用于确定函数作用域 变量中数组的大小。 运行时 表达式是既不是 const 表达式也不是 override 表达式的表达式。 运行时表达式在着色器执行期间由 GPU 计算。 虽然运行时表达式能用于的语法元素更少,但它们可以由 更广泛的一类表达式计算得到,例如其他运行时值。
8.1. 提前求值表达式
WGSL 定义了两种可以在运行时之前求值的表达式:
8.1.1. const 表达式
可以在着色器创建 时求值的表达式称为const 表达式。 如果一个表达式的所有标识符都解析到以下对象,则该表达式是 const 表达式:
const 表达式的类型必须解析为具有
创建时固定占用空间的类型。
注: 抽象类型可以是 const 表达式推断得到的类型。
当且仅当满足以下任一条件时,const 表达式 E 将被求值:
-
E 是顶层表达式,
-
E 是表达式 OuterE 的一个子表达式,并且 OuterE 将被求值,而且对 OuterE 的求值 要求对 E 求值,
-
E 是表达式 OuterE 的一个子表达式,且 OuterE 要求 对 E 求值以产生着色器创建错误 (例如整数除法)。
注: 该求值规则意味着短路
运算符 && 和 || 会阻止对其右侧
子表达式求值,除非其中存在一个为了确定
静态类型而必须求值的子表达式。
const 表达式可以由实现 WebGPU API 方法的 CPU 求值。 因此,对AbstractFloat 值执行操作的精度要求不会比 WebAssembly [WASM-CORE-2] 和 ECMAScript [ECMASCRIPT] 等常见 WebGPU 运行时环境所要求的更严格。 具体浮点类型(例如 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 表达式。
-
但是,
&&运算符的短路规则适用: 左侧求值得到false,因此右侧不会 求值。 -
对 i32(5 * 1000 * 1000 * 1000) 求值原本会导致着色器创建错误, 因为AbstractInt 值 5000000000 溢出了i32 类型。
示例:false && array<u32, 1 + 2>(0, 1, 2)[0] == 0
-
整个表达式是 const 表达式。
-
类型检查要求
e1 : bool && e2 : bool:-
false是 bool 值。 -
类型检查继续处理右侧,并最终对数组元素数量表达式中的
1 + 2求值。
-
-
1 + 2求值得到 i32 值3。-
数组的类型为
array<u32, 3i>。
-
-
数组访问表达式和相等运算符均不会被求值。
8.1.2. override 表达式
可以在管线创建时求值的表达式称为override 表达式。 如果一个表达式的所有标识符都解析到以下对象,则该表达式是 override 表达式:
注: 所有const 表达式也都是 override 表达式。
除 const 表达式之外的 override 表达式仅在 管线 创建期间进行验证或求值,并且仅在 API 提供的 值已替换override 声明之后进行。 如果override 声明的值通过 API 被替换,则其 初始化器表达式(如果存在)不会被求值。 否则,当且仅当满足以下条件时,override 表达式 E 将被 求值:
-
E 构成
GPUProgrammableStage所指定入口点的着色器 的一部分,并且: -
满足以下任一条件:
注: 并非所有 override 表达式都可用作 override 声明的初始化器,因为此类初始化器必须解析 为具体的标量类型。
示例:override x = 42; 按如下方式分析:
-
项
42是AbstractInt 值 42。 -
override 声明要求具体的标量类型。
示例:let y = x + 1; 按如下方式分析:
-
由上可知,
x的类型为i32。 -
表达式
x + 1是 override 表达式,因为它由 一个override 声明和一个整数字面量组成。
示例:vec3(x,x,x) 按如下方式分析:
-
由上可知,
x是类型为i32 的override 声明。 -
vec3(x,x,x)是 override 表达式,因为其中唯一的标识符 解析到 override 声明。
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. 复合值分解表达式
本节描述用于获取复合值的一个分量的表达式, 以及从包含复合值的内存视图获取 指向某个分量的引用的表达式。 在本讨论中,复合值或指向复合值的内存视图 称为基。
有两种方式可以实现:
- 命名 分量表达式
-
基 B 的表达式后跟句点
'.'(U+002D),然后是分量名称。 - 索引 表达式
-
基表达式后跟
'['(U+005B),然后是索引 表达式,再后跟']'(U+005D)。
在语法上,这两种形式通过使用 component_or_swizzle_specifier 语法规则来体现。
此外,向量类型支持重排语法,用于从另一个向量的分量创建新的向量值。
8.5.1. 向量访问表达式
可以通过以下任一种方式访问向量的分量:
-
使用数组下标(例如
v[2]),或 -
使用重排名称,即一个 上下文相关名称,它由一系列便捷名称组成, 每个名称映射到源向量的一个分量。
-
颜色便捷名称集合:
r、g、b、a,分别对应向量分量 0、1、2 和 3。 -
维度便捷名称集合:
x、y、z、w,分别对应向量分量 0、1、2 和 3。
-
使用 . 表示法访问便捷名称。(例如 color.bgra)。
便捷字母不得混用。例如,不能使用
.rybw。
便捷字母不得访问超出向量末尾的分量。
便捷字母可以按任意顺序应用,并可根据需要重复字母。 提供的字母数量必须介于 1 和 4 之间。 也就是说,使用便捷字母只能产生标量类型或有效的向量类型。
结果类型取决于所提供的字母数量。假设为 vec4<f32>
| 访问器 | 结果类型 |
|---|---|
| r | f32
|
| rg | vec2<f32>
|
| rgb | vec3<f32>
|
| rgba | vec4<f32>
|
var a : vec3< f32> = vec3< f32> ( 1. , 2. , 3. ); var b : f32= a . y ; // b = 2.0 var c : vec2< f32> = a . bb ; // c = (3.0, 3.0) var d : vec3< f32> = a . zyx ; // d = (3.0, 2.0, 1.0) var e : f32= a [ 1 ]; // e = 2.0
8.5.1.1. 向量单分量选择
| 前置条件 | 结论 | 描述 |
|---|---|---|
| e: vecN<T> |
e.x: Te .r: T
|
选择 e 的第一个分量
这是一个单字母重排。 |
| e: vecN<T> |
e.y: Te .g: T
|
选择 e 的第二个分量
这是一个单字母重排。 |
| e: vecN<T> N 为 3 或 4 |
e.z: Te .b: T
|
选择 e 的第三个分量
这是一个单字母重排。 |
| e: vec4<T> |
e.w: Te .a: T
|
选择 e 的第四个分量
这是一个单字母重排。 |
| e: vecN<T> i: i32 或 u32 T 是具体类型 | e[i]: T |
选择向量的第 i 个分量 第一个分量的索引为 i=0。 如果 i 超出范围 [0,N-1]:
|
| e: vecN<T> i: i32 或 u32 T 是抽象类型 i 是const 表达式 | e[i]: T |
选择向量的第 i 个分量 第一个分量的索引为 i=0。 如果 i 超出范围 [0,N-1],则会产生着色器创建错误。 |
8.5.1.2. 向量多分量选择
本节中的表达式都是多字母重排。 每个表达式都从另一个向量的分量形成一个向量。
多字母重排不能出现在赋值的左侧: 赋值的左侧必须是引用类型, 但多字母重排表达式始终产生向量类型的值。
| 前置条件 | 结论 | 描述 |
|---|---|---|
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I 是字母 x、y、z 或 wJ 是字母 x、y、z 或 wAM 是 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 或 aJ 是字母 r、g、b 或 aAM 是 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 是字母 x、y、z 或 wJ 是字母 x、y、z 或 wK 是字母 x、y、z 或 wAM 是 read 或 read_write |
e.IJK: vec3<T> | 计算一个三分量向量,其第一个分量为 e.I,第二个
分量为 e.J,第三个分量为 e.K。 字母 z 仅当 N 为 3 或 4 时有效。字母 w 仅当 N 为 4 时有效。如果 e 是指针,则首先应用间接操作,然后调用加载规则。 |
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I 是字母 r、g、b 或 aJ 是字母 r、g、b 或 aK 是字母 r、g、b 或 aAM 是 read 或 read_write |
e.IJK: vec3<T> | 计算一个三分量向量,其第一个分量为 e.I,第二个
分量为 e.J,第三个分量为 e.K。 字母 b 仅当 N 为 3 或 4 时有效。字母 a 仅当 N 为 4 时有效。如果 e 是指针,则首先应用间接操作,然后调用加载规则。 |
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I 是字母 x、y、z 或 wJ 是字母 x、y、z 或 wK 是字母 x、y、z 或 wL 是字母 x、y、z 或 wAM 是 read 或 read_write |
e.IJKL:
vec4<T> | 计算一个四分量向量,其第一个分量为 e.I,第二个
分量为 e.J,第三个分量为 e.K,第四个
分量为 e.L。 字母 z 仅当 N 为 3 或 4 时有效。字母 w 仅当 N 为 4 时有效。如果 e 是指针,则首先应用间接操作,然后调用加载规则。 |
|
e: vecN<T> 或
ptr<AS,vecN<T,AM>> I 是字母 r、g、b 或 aJ 是字母 r、g、b 或 aK 是字母 r、g、b 或 aL 是字母 r、g、b 或 aAM 是 read 或 read_write |
e.IJKL:
vec4<T> | 计算一个四分量向量,其第一个分量为 e.I,第二个
分量为 e.J,第三个分量为 e.K,第四个
分量为 e.L。 字母 b 仅当 N 为 3 或 4 时有效。字母 a 仅当 N 为 4 时有效。如果 e 是指针,则首先应用间接操作,然后调用加载规则。 |
8.5.1.3. 来自向量内存视图的分量引用
本节中的表达式从整个向量的内存视图形成 该向量单个分量的内存视图。
WGSL 类型规则意味着此类 表达式可以出现在:
对向量某个分量的写访问 可以访问与该向量关联的所有内存位置。
注: 这意味着不同 调用对内存中向量的不同分量进行访问时,如果至少有一次访问是写访问,则必须同步这些访问。 请参阅§ 17.11 同步内置函数。
| 前置条件 | 结论 | 描述 |
|---|---|---|
| r: ref<AS,vecN<T>,AM> 或 ptr<AS,vecN<T>,AM> |
r.x: ref<AS,T,AM>r .r: ref<AS,T,AM> | 计算对内存视图
r 所引用向量第一个分量的引用。 结果引用的起源变量与 r 的起源变量相同。 |
| r: ref<AS,vecN<T>,AM> 或 ptr<AS,vecN<T>,AM> |
r.y: ref<AS,T,AM>r .g: ref<AS,T,AM> | 计算对内存视图
r 所引用向量第二个分量的引用。 结果引用的起源变量与 r 的起源变量相同。 |
| r: ref<AS,vecN<T>,AM> 或 ptr<AS,vecN<T>,AM> N 为 3 或 4 |
r.z: ref<AS,T,AM>r .b: ref<AS,T,AM> | 计算对内存视图
r 所引用向量第三个分量的引用。 结果引用的起源变量与 r 的起源变量相同。 |
| r: ref<AS,vec4<T>,AM> 或 ptr<AS,vec4<T>,AM> |
r.w: ref<AS,T,AM>r .a: ref<AS,T,AM> | 计算对内存视图
r 所引用向量第四个分量的引用。 结果引用的起源变量与 r 的起源变量相同。 |
| r: ref<AS,vecN<T>,AM> 或 ptr<AS,vecN<T>,AM> i: i32 或 u32 |
r[i] : ref<AS,T,AM> |
计算对内存
视图 r 所引用向量第 i 个分量的引用。
如果 i 超出范围 [0,N-1]:
结果引用的起源变量 与 r 的起源变量相同。 |
8.5.2. 矩阵访问表达式
| 前置条件 | 结论 | 描述 |
|---|---|---|
|
e: matCxR<T> i: i32 或 u32 T 是具体类型 | e[i]: vecR<T> |
结果是 e 的第 i 个列向量。
如果 i 超出范围 [0,C-1]:
|
|
e: matCxR<T> i: i32 或 u32 T 是抽象类型 i 是const 表达式 | e[i]: vecR<T> |
结果是 e 的第 i 个列向量。
如果 i 超出范围 [0,C-1],则会产生着色器创建错误。 |
| 前置条件 | 结论 | 描述 |
|---|---|---|
|
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 表达式 | e[i] : T |
结果是数组值
e 的第 i 个元素的值。
如果 i 超出范围 [0,N-1],则会产生着色器创建错误。 |
| 前置条件 | 结论 | 描述 |
|---|---|---|
|
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> 如果 T 是向量,则 TB 是 vecN<bool>, 否则 TB 是 bool | e1 == e2: TB
| 相等。当 T 是向量时逐分量执行。 |
| e1: T e2: T S 是 AbstractInt、AbstractFloat、bool、i32、u32、f32 或 f16 T 是 S 或 vecN<S> 如果 T 是向量,则 TB 是 vecN<bool>, 否则 TB 是 bool | e1 != e2: TB
| 不相等。当 T 是向量时逐分量执行。 |
| e1: T e2: T S 是 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 是 S 或 vecN<S> 如果 T 是向量,则 TB 是 vecN<bool>, 否则 TB 是 bool | e1 < e2: TB
| 小于。当 T 是向量时逐分量执行。 |
| e1: T e2: T S 是 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 是 S 或 vecN<S> 如果 T 是向量,则 TB 是 vecN<bool>, 否则 TB 是 bool | e1 <= e2: TB
| 小于或等于。当 T 是向量时逐分量执行。 |
| e1: T e2: T S 是 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 是 S 或 vecN<S> 如果 T 是向量,则 TB 是 vecN<bool>, 否则 TB 是 bool | e1 > e2: TB
| 大于。当 T 是向量时逐分量执行。 |
| e1: T e2: T S 是 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16 T 是 S 或 vecN<S> 如果 T 是向量,则 TB 是 vecN<bool>, 否则 TB 是 bool | e1 >= e2: TB
| 大于或等于。当 T 是向量时逐分量执行。 |
8.9. 位表达式
| 前置条件 | 结论 | 描述 |
|---|---|---|
| e: T S 是 AbstractInt、i32 或 u32 T 是 S 或 vecN<S> | ~e : T
| 对 e 进行按位取反。 结果中的每个位都与 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> 当 T 是 S 时,TS 是 u32,否则 TS 是 vecN<u32> | e1 << e2: T
|
左移(被移位值是具体值):
将 e1 左移,在最低有效位位置插入零位, 并丢弃最高有效位。 移位的位数是 e2 的值对
e1 位宽取模后的结果。
当 e1 和 e2 在着色器执行 开始之前都已知时, 结果不得溢出:
当 T 是向量时逐分量执行。 |
| e1: T e2: TS T 是 AbstractInt 或 vecN<AbstractInt> 当 T 是 AbstractInt 时,TS 是 u32,否则 TS 是 vecN<u32> | e1 << e2: T
|
左移(被移位值是抽象值):
将 e1 左移,在最低有效位位置插入零位, 并丢弃最高有效位。 移位的位数是 e2 的值。 e1 的最高 e2+1 个有效位必须具有 相同的位值。 否则会发生溢出。 注: 此条件意味着所有 被丢弃的位都必须与原始值的符号位相同, 并且也与最终值的符号位相同。 当 T 是向量时逐分量执行。 |
| e1: T e2: TS S 是 i32 或 u32 T 是 S 或 vecN<S> 当 T 是 S 时,TS 是 u32,否则 TS 是 vecN<u32> | e1 >> e2: T |
右移(被移位值是具体值)。
将 e1 右移,丢弃最低有效位。 如果 S 是无符号类型,则在最高有效位位置插入零位。 如果 S 是有符号类型:
移位的位数是 e2 的值对 e1 位宽取模后的结果。 如果 e2 大于或等于 e1 的位宽,则:
当 T 是向量时逐分量执行。 |
| e1: T e2: TS T 是 AbstractInt 或 vecN<AbstractInt> 当 T 是 AbstractInt 时,TS 是 u32,否则 TS 是 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 是一个标识符,解析到 一个类型为 T 且处于作用域内的const 声明 | c: T | 结果是为初始化器表达式计算得到的值。 该表达式是const 表达式,并在着色器创建时求值。 |
| c 是一个标识符,解析到 一个类型为 T 且处于作用域内的override 声明 | c: T |
如果管线创建为常量 ID指定了值,
则结果为该值。
对于不同的管线实例,该值可能不同。
否则,结果是为初始化器表达式计算得到的值。 管线可覆盖常量出现在模块作用域,因此求值发生在 着色器开始执行之前。 注: 如果 API 调用中未
指定初始值,
且 |
| c 是一个标识符,解析到 一个类型为 T 且处于作用域内的let 声明 | c: T | 结果是为初始化器表达式计算得到的值。
let
声明出现在函数体内部,其
初始化器
每当控制流到达该声明时都会求值。 |
8.16. 枚举表达式
| 前置条件 | 结论 | 描述 |
|---|---|---|
| e 是一个标识符,解析到一个预声明的枚举值, 该枚举值属于枚举类型 E | e : E | 请参阅§ 6.4.1 预声明枚举值 |
8.17. 类型表达式
| 前置条件 | 结论 | 描述 |
|---|---|---|
| t 是一个标识符,解析到一个预声明的类型 | t : AllTypes | 请参阅§ 6.10 预声明类型和类型生成器摘要 |
| a 是一个标识符,解析到一个类型别名。 | a : AllTypes | 此外,a 表示其所设置别名的类型。 |
| s 是一个标识符,解析到一个结构体类型的声明。 | s : AllTypes | 此外,s 表示该结构体类型。 |
|
tg 是一个标识符,解析到一个类型生成器
e1: T1 | tg _template_args_start e1, ..., eN _template_args_end : AllTypes |
每个类型生成器对其所需和接受的模板
参数都有自己的要求,
并定义模板参数如何帮助确定结果类型。
表达式 e1 到 eN 是该类型生成器的模板参数。 例如,类型表达式 预声明类型生成器列表请参阅§ 6.10 预声明类型和类型生成器摘要。 注: 此处的两个变体仅 区别在于 eN 之后是否有尾随逗号。 |
| tg _template_args_start e1, ..., eN, _template_args_end : AllTypes |
8.18. 表达式语法摘要
当一个标识符是 call_phrase 中的第一个词法单元时,它是以下之一:
声明和作用域规则确保这些名称始终彼此不同。
expression ( ',' expression ) * ',' ?
'[' expression ']' component_or_swizzle_specifier ?
| multiplicative_expression multiplicative_operator unary_expression
| additive_expression additive_operator multiplicative_expression
| shift_expression _less_than shift_expression
| shift_expression _greater_than shift_expression
| shift_expression _less_than_equal shift_expression
| shift_expression _greater_than_equal shift_expression
binary_and_expression '&' unary_expression
| short_circuit_or_expression '||'
relational_expression
8.19. 运算符优先级和结合性
整个小节都是非规范性的。
右侧 WGSL 表达式中的运算符优先级和结合性 由其摘要语法自然产生。右侧表达式对运算符进行分组以组织它们, 如下图所示:
为了通过显式书写提高可读性,以下组不与其他组结合:
以下组则不与自身结合:
要使上述两类组发生结合,需要使用括号显式指定关系。 以下示例在注释中展示了这些规则会使哪些表达式无效:
let a = x & ( y ^ ( z | w )); // 无效:x & y ^ z | w let b = ( x + y ) << ( z >= w ); // 无效:x + y << z >= w let c = x < ( y > z ); // 无效:x < y > z let d = x && ( y || z ); // 无效:x && y || z
自然产生的优先级控制表达式的隐式括号,其中结合更强的
运算符与优先级较弱的运算符一起出现时,会表现得仿佛它被括号包围。
例如,乘法运算符的结合强度高于加法运算符,因此
会从表达式 a + b * c 推导出 (a + (b * c))。类似地,自然产生的
结合性控制
这些隐式括号的方向。例如,从左到右的结合会
从表达式 a + b + c 推导出 ((a + b) + c),而从右到左的结合
会从表达式 * * a 推导出 (* (* a))。
下表总结了运算符优先级、结合性和绑定关系,并按照 从最强到最弱的顺序排列。绑定列包含给定运算符可绑定的更强表达式, 例如,如果值为“All above”,则该运算符 可以包含任意更强的表达式。但例如,如果值为“Unary”, 则任何比一元表达式弱、但比该行运算符更强的内容都需要括号 才能与该运算符绑定。为了以线性方式列出运算符,需要此列。
| 名称 | 运算符 | 结合性 | 绑定 |
|---|---|---|---|
| 带括号 | (...)
| ||
| 主表达式 | a(), a[], a.b
| 从左到右 | |
| 一元 | -a, !a, ~a, *a, &a
| 从右到左 | 以上全部 |
| 乘法 | a*b, a/b, a%b
| 从左到右 | 以上全部 |
| 加法 | a+b, a-b
| 从左到右 | 以上全部 |
| 移位 | a<<b, a>>b
| 需要括号 | 一元 |
| 关系 | a<b, a>b, a<=b, a>=b,
a==b, a!=b
| 需要括号 | 以上全部 |
| 二元 AND | a&b
| 从左到右 | 一元 |
| 二元 XOR | a^b
| 从左到右 | 一元 |
| 二元 OR | a|b
| 从左到右 | 一元 |
| 短路 AND | a&&b
| 从左到右 | 关系 |
| 短路 OR | a||b
| 从左到右 | 关系 |
9. 语句
语句是控制执行的程序 片段。 语句通常按顺序执行;但是, 控制流语句可能使程序以 非顺序方式执行。
9.1. 复合语句
复合语句 是由花括号括起的零条或多条语句序列。 当声明是 其中一条语句时,其标识符从 下一条语句开始一直到复合语句结束都处于作用域内。
continuing_compound_statement 是复合 语句的一种特殊形式,它构成continuing 语句的主体,并允许在 末尾有一个可选的break-if 语句。
9.2. 赋值语句
赋值会对一个表达式求值, 并可选地将其存储到内存中(从而更新变量的内容)。
lhs_expression ( '=' | compound_assignment_operator ) expression
| '_' '=' expression
运算符词法单元左侧的文本是左侧, 而 运算符词法单元右侧的表达式是右侧。
9.2.1. 简单赋值
当赋值的
左侧是一个
表达式,并且运算符是等号('=')词法单元时,该赋值是简单赋值。
在这种情况下,右侧的值会写入左侧所引用的内存。
| 前置条件 | 语句 | 描述 |
|---|---|---|
| e: T, T 是具体的可构造类型, r: ref<AS,T,AM>, AS 是可写的地址空间, 访问模式 AM 是write 或read_write | r = e |
对 r 求值,然后对 e 求值,然后将为
e 计算得到的值写入
r 所引用的内存
位置。
注: 如果该引用是无效内存引用,则写入 可能不会执行,或者可能写入与 预期不同的内存位置。 |
最简单的情况下,左侧是变量名称。 其他情况请参阅§ 6.5.8 形成引用和指针值。
struct S { age : i32, weight : f32} var < private> person : S ; fn f () { var a : i32= 20 ; a = 30 ; // 将 'a' 的内容替换为 30。 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. 虚赋值
当赋值的
左侧是
下划线('_')词法单元时,该赋值是虚赋值。
在这种情况下,会对右
侧求值,然后忽略结果。
| 前置条件 | 语句 | 描述 |
|---|---|---|
| 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. 复合赋值
当赋值的 左侧是一个 表达式,并且运算符是compound_assignment_operators 之一时,该赋值是复合 赋值。
每条语句的类型要求、语义和行为都被定义为仿佛 复合赋值按下表展开,但以下情况除外:
-
引用表达式 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' 词法单元,
后跟以逗号分隔的case
选择器列表,以及一个
采用复合语句形式的主体。
独立 default
子句是'default' 词法单元,后跟一个采用复合语句形式的主体。
default 子句是 以下任一种:
每个 switch 语句必须恰好具有一个default 子句。
'default'
词法单元不得在单个
case_selector
列表中出现多次。
类型规则 前置条件: 对于单个 switch 语句,选择器表达式和所有 case 选择器表达式必须具有 相同的具体整数标量类型。
case_selectors 中的表达式必须 是const 表达式。
同一个 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 { // const 表达式可以用于 case 选择器 a = 3 ; } case 3 , default { // default 关键字可以与其他子句一起使用 a = 4 ; } }
9.4.3. Loop 语句
loop 语句会反复执行一个循环体; 循环体被指定为一个复合语句。 循环体的每次执行称为一次迭代。
可选地,循环体中的最后一条语句可以是一个 continuing 语句。
如果loop 会执行无界数量的迭代,则会发生动态错误。 这可能导致循环提前终止、其他非局部效果,甚至导致设备丢失。
当循环体中的某条语句是声明时, 它遵循复合语句中声明的正常作用域和生命周期规则。 也就是说,循环体是一个语句序列,如果其中一条是声明, 则该声明的作用域从序列中下一条语句的开头 一直延伸到循环体末尾。 每当执行到该声明时,它都会执行,因此每次新的迭代 都会创建变量或值的新实例,并重新初始化它。
注: loop 语句是一种专门的构造,你
可能更需要 for
或 while 语句。loop 语句是 WGSL 与
其他着色器语言最大的区别之一。
这种设计直接表达了编译后代码中常见的循环惯用形式。 特别是,将循环更新语句放在循环体末尾 使它们能够自然地使用循环体中定义的值。
-
<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 语句是对包含一个loop 语句的复合语句
的语法糖。
一般而言,for 语句采用以下形式
for (initializer;condition;update_part) {body}
当条件表达式存在时,for 语句会脱糖为以下形式的循环:
{当条件表达式不存在时,
initializer ;
loop {
if !(condition) { break; }
body
continuing { update_part }
}
}
for 语句会脱糖为以下形式的循环:
{
initializer ;
loop {
body
continuing { update_part }
}
}
此外:
-
如果
initializer非空,则会在第一次迭代之前,在一个额外的作用域内执行它。 初始化器中声明的作用域一直延伸到循环体末尾。 -
类型 规则前置条件:如果条件非空,则它必须是 bool 类型的表达式。
-
如果存在条件,则会在执行 for 循环体之前立即求值。 如果条件为 false,则会执行§ 9.4.6 Break 语句, 从而结束循环执行。 此检查会在每次循环迭代开始时执行。
-
-
如果
update_part非空,则它会成为循环构造末尾的一个continuing 语句。 -
脱糖将根据需要重命名
body中声明的标识符,以确保update_part中的所有标识符仍解析到脱糖之前 所解析到的相同声明。
for 循环的 initializer 在执行循环之前执行一次。
当声明出现在
初始化器中时,其标识符一直处于作用域内直到
body 末尾。
与 body 中的声明不同,该声明不会在每次迭代时重新初始化。
condition、body 和 update_part 按此顺序执行,构成一次
循环迭代。
body 是一种特殊形式的复合语句。
body 中声明的标识符从
下一条语句开始一直到 body 末尾都处于作用域内。
每当执行到该声明时,它都会执行,因此每次新的迭代
都会创建变量或常量的新实例,并重新初始化它。
var a : i32= 2 ; for ( var i : i32= 0 ; i < 4 ; i ++ ) { if a == 0 { continue ; } a = a + 2 ; }
转换为:
var a : i32= 2 ; { // 为循环变量 i 引入新作用域 var i : i32= 0 ; loop { if ! ( i < 4 ) { break ; } if a == 0 { continue ; } a = a + 2 ; continuing { i ++ ; } } }
var a : i32= 2 ; for ( var i : i32= 0 ; ; i ++ ) { if a == 0 { continue ; } if i == 4 { break ; } a = a + 2 ; }
转换为:
var a : i32= 2 ; { // 为循环变量 i 引入新作用域 var i : i32= 0 ; loop { // 注:脱糖不会在此处引入 if 子句。 if a == 0 { continue ; } if i == 4 { break ; } a = a + 2 ; continuing { i ++ ; } } }
如果for 循环会执行无界数量的迭代,则会发生动态错误。 这可能导致循环提前终止、其他非局部效果,甚至导致设备丢失。
9.4.5. While 语句
attribute * 'while' expression compound_statement
while 语句是一种由条件参数化的循环。 在每次循环迭代开始时,会对一个 布尔条件求值。 如果条件为 false,则 while 循环结束执行。 否则,执行该次迭代的其余部分。
while 循环可以视为对loop 或for 语句的语法糖。 以下语句形式等价:
-
whilecondition{body_statements} -
loop { if !condition{break;}body_statements} -
for (;condition;) {body_statements}
如果while 循环会执行无界数量的迭代,则会发生动态错误。 这可能导致循环提前终止、其他非局部效果,甚至导致设备丢失。
9.4.6. Break 语句
'break'
break 语句将控制转移到最近外围循环 或switch 语句主体 之后的位置, 从而结束循环或 switch 语句的执行。
break 语句必须仅在loop、for、while 和switch 语句中使用。
break 语句不得放置在会导致其退出循环continuing
语句的位置。
请改用break-if 语句。
var a : i32= 2 ; var i : i32= 0 ; loop { let step : i32= 1 ; if i % 2 == 0 { continue ; } a = a * 2 ; continuing { i = i + step ; if i >= 4 { break ; } // 无效。请改用 break-if。 } }
9.4.7. Break-If 语句
'break' 'if' expression ';'
break-if 语句会对一个布尔条件求值; 如果条件为 true,则控制转移到最近外围loop 语句主体之后的位置,从而结束该循环的执行。
注: break-if 语句只能作为continuing 语句主体中的最后一条 语句出现。
var a : i32= 2 ; var i : i32= 0 ; loop { let step : i32= 1 ; if i % 2 == 0 { continue ; } a = a * 2 ; continuing { i = i + step ; break if i >= 4 ; } }
9.4.8. Continue 语句
'continue'
continue 语句会在最近外围loop 中转移控制:
-
如果存在,则向前转移到该循环主体末尾的continuing 语句。
-
否则向后转移到循环体中的第一条语句,从而开始下一次迭代。
continue 语句必须仅在loop、for 或while 语句中使用。
continue 语句不得放置在会将
控制转移到外围continuing 语句的位置。
(当分支到 continuing 语句时,它是一个向前分支。)
continue 语句不得放置在会将
控制转移越过目标continuing 语句所使用声明的位置。
注: 只有当 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 语句必须仅在片段着色器阶段中使用。
更准确地说,执行 discard 语句将:
-
把当前调用转换为一个辅助调用,并且
-
阻止当前片段输出在GPURenderPipeline 中被后续处理。
只有在 discard 语句之前执行的语句将具有可观察效果。
注: discard 语句可以由
片段阶段中的任何函数执行,并且效果相同:
片段输出将被丢弃。
@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 ); // 将值设置为 1 并输出红色,但仅当辅助函数 // 没有执行 discard 语句时。 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 B1 = {Return} {Continue, Return} 均不在 B2 中 | {Return} |
|
s1: B1 s2: B2 B1 ≠ {Return} {Continue, Return} 均不在 B2 中 Break 不在 (B1 ∪ B2) 中 | (B1 ∪ B2)∖{Continue, Next} | |
|
s1: B1 s2: B2 B1 ≠ {Return} {Continue, Return} 均不在 B2 中 Break 在 (B1 ∪ B2) 中 | (B1 ∪ B2 ∪ {Next})∖{Break, Continue} | |
| switch e {case c1: s1 ... case cn: sn} |
s1: B1 ... sn: Bn Break 不在 (B1 ∪ ... ∪ Bn) 中 | B1 ∪ ... ∪ Bn |
|
s1: B1 ... sn: Bn Break 在 (B1 ∪ ... ∪ Bn) 中 | (B1 ∪ ... ∪ Bn ∪ {Next})∖Break |
注: ∪ 是集合并运算,∖ 是集合 差运算。
注: 当
loop 的主体为空,或 for 循环缺少初始化或更新
语句时,会出现空语句情况。
为进行此分析:
-
for循环会被脱糖(请参阅§ 9.4.4 For 语句) -
while循环会被脱糖(请参阅§ 9.4.5 While 语句) -
loop {s}被视为loop {s continuing {}} -
没有
else分支的if语句被视为具有一个空的 else 分支,即以else {}结尾;这会将 Next 添加到其行为中 -
带有
else if分支的if语句被视为嵌套的 简单if/else语句 -
以
default开始的switch_clause 与以case _:开始的switch_clause 行为完全相同
每个内置 函数都具有 {Next} 的行为。 并且上表中未列出的每个运算符应用都具有与以下函数调用相同的行为:该函数调用具有相同的 操作数,并且函数的行为 为 {Next}。
函数的行为必须满足上述规则。
注: 上述规则意味着循环的行为 是 {Next}、{Return} 或 {Next,Return}。
注: 无需分析表达式的行为,因为它们 始终会是 {Next},或者先前分析的函数已经产生 错误。
9.7.2. 说明
本节为资料性、非规范性内容。
行为分析可能以下列方式导致程序被拒绝 (重述上述要求):
-
函数主体(视为普通语句)的行为不包含在 {Next, Return} 中。
-
具有返回类型的函数主体,其行为不是 {Return}。
-
continuing 块的行为包含 Continue 或 Return 中的任何一个。
-
某些明显的无限循环具有空行为集合,因此无效。
通过自底向上分析调用图,此分析可以在线性时间内完成(因为 函数调用的行为可能取决于函数代码)。
9.7.3. 示例
以下是一些展示该分析实际运作方式的示例:
fn simple () -> i32{ var a : i32; return 0 ; // 行为:{Return} a = 1 ; // 有效,静态不可达代码。 // 语句行为:{Next} // 总体行为(由于顺序语句):{Return} return 2 ; // 有效,静态不可达代码。行为:{Return} } // 函数行为:{Return}
fn nested () -> i32{ var a : i32; { // 复合语句的开始。 a = 2 ; // 行为:{Next} return 1 ; // 行为:{Return} } // 整个复合语句的行为为 {Return} a = 1 ; // 有效,静态不可达代码。 // 语句行为:{Next} // 总体行为(由于顺序语句):{Return} return 2 ; // 有效,静态不可达代码。行为:{Return} }
fn if_example () { var a : i32= 0 ; loop { if a == 5 { break ; // 行为:{Break} } // 整个 if 复合语句的行为:{Break, Next}, // 因为 if 具有隐式空 else a = a + 1 ; // 有效,因为上一条语句的行为中包含 "Next" } }
fn if_example () { var a : i32= 0 ; loop { if a == 5 { break ; // 行为:{Break} } else { continue ; // 行为:{Continue} } // 整个 if 复合语句的行为:{Break, Continue} a = a + 1 ; // 有效,静态不可达代码。 // 语句行为:{Next} // 总体行为:{Break, Continue} } }
fn if_example () { var a : i32= 0 ; loop { // if e1 s1 else if e2 s2 else s3 // 等同于 // if e1 else { if e2 s2 else s3 } if a == 5 { break ; // 行为:{Break} } else if a == 42 { continue ; // 行为:{Continue} } else { return ; // 行为 {Return} } // 整个 if 复合语句的行为: // {Break, Continue, Return} } // 整个 loop 复合语句的行为 {Next, Return} } // 整个函数的行为 {Next}
fn switch_example () { var a : i32= 0 ; switch a { default : { break ; // 行为:{Break} } } // 行为:{Next},因为 switch 将 Break 替换为 Next a = 5 ; // 有效,因为上一条语句的行为中包含 Next }
fn invalid_infinite_loop () { loop { discard ; // 行为 { Next }。 } // 无效,整个循环的行为为 { }。 }
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 ; } } // 整个循环的行为为 {Next}, // 因为它吸收 "Continue" 和 "Next", // 然后将 "Break" 替换为 "Next" }
fn redundant_continue_with_continuing () { var a : i32; loop { if a == 5 { break ; } continue ; // 有效。这是冗余的,会分支到下一条语句。 continuing { a = a + 1 ; } } }
fn continue_end_of_loop_body () { for ( var i : i32= 0 ; i < 5 ; i ++ ) { continue ; // 有效。这是冗余的, // 会分支到循环体末尾。 } // 行为:{Next}, // 因为循环会吸收 "Continue", // 并且 "for" 循环总会添加 "Next" }
for 循环会脱糖为带条件 break 的 loop。如前面的示例所示,
条件 break 具有行为 {Break,
Next},这会向循环的行为中添加 "Next"。
fn missing_return () -> i32{ var a : i32= 0 ; if a == 42 { return a ; // 行为:{Return} } // 行为:{Next, Return} } // 错误:Next 在具有返回类型的 // 函数主体中无效
fn continue_out_of_loop () { var a : i32= 0 ; if a > 0 { continue ; // 行为:{Continue} } // 行为:{Next, Continue} } // 错误:Continue 在函数主体中无效
continue 替换为
break,同一个示例也会因相同原因而无效。
10. 断言
断言是一项检查, 用于确保某个布尔条件得到满足。
'const_assert' expression
10.1. Const 断言语句
const 断言语句是一种断言,如果表达式求值得到
false,则会产生着色器创建错误。
该表达式必须是const 表达式。
该语句可以满足着色器中的静态访问条件,
但除此之外对编译后的着色器没有影响。
const 断言可以出现在
模块作用域中,也可以作为函数作用域中的语句出现。
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 ; // 无效,表达式必须是 const 表达式。 }
11. 函数
函数在被调用时执行计算工作。
函数以下列方式之一被调用:
-
通过对函数调用表达式求值。请参阅§ 8.10 函数调用 表达式。
-
通过执行函数调用语句。请参阅§ 9.5 函数调用 语句。
WGSL 中的函数可以按任意顺序定义,包括在源代码中晚于其使用位置定义。 因此不需要函数原型或前向声明,也无法这样做。
函数分为两种:
11.1. 声明用户定义函数
函数 声明通过指定以下内容创建一个用户定义函数:
函数声明必须仅出现在模块作用域中。 函数名称在整个程序中都处于作用域内。
形式参数声明为一个在 调用函数时必须提供的值指定一个标识符名称和类型。 入口 点函数的形式参数可以具有属性。 请参阅§ 11.2 函数调用。 该标识符的作用域是函数体。 给定函数的两个形式参数不得具有相同名称。
注: 某些内置函数可能允许参数为 抽象数值 类型; 但是,用户声明的函数目前不支持此功能。
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 语法规则实例中的第一个词法单元的位置。 调用点是一个动态 上下文。 因此,同一个文本位置可能表示多个调用点。
注: 如果一个片段着色器中的函数调用所在 四元组中的所有调用都被丢弃, 则该函数调用可能永远不会返回。 在这种情况下,控制不会被转移回调用函数。
11.3. const 函数
使用const 属性声明的函数可以在着色器创建时求值。 这些函数称为const 函数。 对这些函数的调用可以作为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 ); // const 表达式可以 // 用于 override 表达式。 // firstLeadingBit(x) 在此上下文中不是 // const 表达式。 fn foo () { var a : array< i32, firstLeadingBit ( 257 ) > ; // 如果所有参数都是 const 表达式, // 则 const 函数可以用于 // const 表达式。 }
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 的根标识符。
-
如果 E 是 bufferView 或 bufferArrayView 内置函数调用,则根标识符是 指针实参的根标识符。
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) 应用于 S
中类型为 T 的成员,
并且 S 可以作为地址空间 AS 中变量的存储类型,
其中 AS 不是uniform,
则 n 必须满足:
n = k × RequiredAlignOf(T,AS) 其中 k 为某个正整数。 对齐和大小规则相互递归。 但是,上述约束是良定义的,因为它依赖一个 嵌套类型的所需对齐,并且类型具有有界的嵌套深度。 如果绑定到统一或 存储 缓冲区的内存起始地址的对齐要求 低于属性中指定的值,则最终的对齐要求将为 两者中的较小值。 实现只会相对于变量的基 地址满足该属性。 请参阅§ 14.4 内存布局。 |
| 参数 | 必须是一个const 表达式,并解析为i32 或u32。 必须为正数。 必须是 2 的幂。 |
12.2. binding
'@' 'binding' '(' expression ',' ?
')'
| 描述 | 指定绑定组中资源的绑定编号。 请参阅§ 13.3.2 资源接口。 |
| 参数 | 必须是一个const 表达式,并解析为i32 或u32。 必须为非负数。 |
12.3. blend_src
'@' 'blend_src' '(' expression ',' ?
')'
| 描述 |
指定启用特性 dual_source_blending
时片段输出的一部分。
请参阅 § 13.3.1.3 输入输出位置。
必须仅应用于具有 location 属性的结构类型的成员。 必须仅应用于具有数值标量 或数值 向量类型的对象声明。 不得包含在着色器 阶段输入中。 不得包含在着色器阶段输出中, 片段 着色器阶段除外。 |
| 参数 | 必须是一个常量表达式,其求值得到一个值为 0 或
1 的i32 或u32。
|
12.4. builtin
'@' 'builtin' '(' builtin_value_name ',' ? ')'
| 描述 | 指定关联对象是由指定词法单元表示的内置值。 请参阅§ 13.3.1.1 内置输入和输出。 |
| 参数 | 必须是内置值 名称词法单元,用于表示一个内置值。 |
12.5. const
'@' 'const'
| 描述 |
指定该函数可以用作const
函数。
此属性不得应用于
用户定义函数。
必须仅应用于函数声明。 注: 此属性用作一种 表示约定,用于描述哪些 内置函数可以用于const 表达式。 |
| 参数 | 无 |
12.6. diagnostic
'@' 'diagnostic' diagnostic_control
'(' severity_control_name ',' diagnostic_rule_name ',' ? ')'
| 描述 |
指定一个范围诊断过滤器。请参阅§ 2.3 诊断。
一个句法形式上可以指定多个diagnostic 属性, 但它们必须指定不同的触发 规则。 |
| 参数 |
第一个参数是severity_control_name。
第二个参数是diagnostic_rule_name 词法单元, 用于指定一个触发规则。 |
12.7. group
'@' 'group' '(' expression ',' ?
')'
| 描述 | 指定资源的绑定组。 请参阅§ 13.3.2 资源接口。 |
| 参数 | 必须是一个const 表达式,并解析为i32 或u32。 必须为非负数。 |
12.8. id
'@' 'id' '(' expression ',' ?
')'
| 描述 |
指定一个数值标识符,作为
管线可覆盖常量的备用名称。
必须仅应用于标量类型的override 声明。 |
| 参数 | 必须是一个const 表达式,并解析为i32 或u32。 必须为非负数。 |
12.9. interpolate
'@' 'interpolate' '(' interpolate_type_name ',' ? ')'
| '@' 'interpolate' '(' interpolate_type_name ',' interpolate_sampling_name ',' ? ')'
| 描述 | 指定用户定义的 IO 必须如何进行插值。 请参阅§ 13.3.1.4 插值。 |
| 参数 |
第一个参数必须是
插值类型名称词法单元,用于表示一个插值类型。
第二个参数(如果存在)必须是 插值采样名称词法单元,用于表示 插值采样。 |
12.10. invariant
'@' 'invariant'
| 描述 |
当应用于顶点
着色器的position 内置输出值时,
结果的计算在不同
程序之间以及同一入口点的不同调用之间保持不变。
也就是说,如果不同入口点中的两个 position 输出具有
相同的数据流和控制流,则保证结果值
相同。
对 position 内置输入值没有影响。
注: 此属性映射到 HLSL 中的
|
| 参数 | 无 |
12.11. location
'@' 'location' '(' expression ',' ?
')'
| 描述 |
指定入口点用户定义 IO 的一部分。
请参阅§ 13.3.1.3 输入输出位置。
必须仅应用于入口点 函数参数、入口点 返回类型或结构体类型的成员。 必须仅应用于 对象类型为数值标量 或数值 向量的声明。 不得包含在计算着色器 阶段输入中。 |
| 参数 | 必须是一个const 表达式,并解析为i32 或u32。 必须为非负数。 |
12.12. must_use
'@' 'must_use'
| 描述 |
指定对此函数的调用必须用作
表达式。
也就是说,对此函数的调用不得构成整个函数调用语句。
注: 许多函数会返回值,并且
没有副作用。
将这样的函数作为函数调用语句中的唯一内容来调用通常是编程缺陷。
具有这些特性的内置函数会声明为 |
| 参数 | 无 |
12.13. size
'@' 'size' '(' expression ',' ?
')'
| 描述 |
指定为结构体成员保留的字节数。
如果 请参阅§ 14.4 内存布局。 必须仅应用于结构体类型的成员。 成员类型必须具有创建时固定 占用空间。 |
| 参数 | 必须是一个const 表达式,并解析为i32 或u32。 必须为正数。 |
12.14. subgroup_size
'@' 'subgroup_size' '(' expression ',' ? ')'
| 描述 | 指定计算着色器调用的子组大小。 |
| 要求 | 必须仅在启用subgroup_size_control 扩展时使用。 |
| 参数 |
必须是const 表达式或override 表达式,并解析为i32 或u32。 如果该值不是 2 的幂,则:
如果入口点
如果该值大于 即使该值介于
|
12.15. workgroup_size
'@' 'workgroup_size' '(' expression ',' ? ')'
| '@' 'workgroup_size' '(' expression ',' expression ',' ?
')'
| '@' 'workgroup_size' '(' expression ',' expression ','
expression ',' ?
')'
| 描述 |
指定计算着色器工作组网格的 x、y
和 z 维度。
第一个参数指定 x 维度。 第二个参数(如果提供)指定 y 维度,否则假定为 1。 第三个参数(如果提供)指定 z 维度,否则假定为 1。 |
| 参数 |
接受一个、两个或三个参数。
每个参数必须是const 表达式或override 表达式。 所有参数必须具有相同类型,即i32 或u32。 如果任一指定参数是一个求值得到非正值的 const 表达式,则会产生着色器创建错误。 如果任一指定参数求值得到 非正值或超过 WebGPU API 指定的上限,或者参数值的乘积超过 WebGPU API 指定的上限,则会产生管线创建错误(请参阅WebGPU § 3.6.2 限制)。 |
12.16. 着色器阶段属性
下面的着色器阶段 属性 将函数指定为特定着色器阶段的入口 点。 这些属性必须仅应用于函数声明, 并且给定函数上至多只能出现一个。 它们不接受参数。
12.16.1. vertex
'@' 'vertex'
vertex 属性将该函数声明为
渲染
管线的顶点着色器
阶段的入口点。
12.16.2. fragment
'@' 'fragment'
fragment 属性将该函数声明为
渲染
管线的片段着色器
阶段的入口点。
12.16.3. compute
'@' 'compute'
compute 属性将该函数声明为
计算管线的计算着色器
阶段的入口点。
13. 入口点
入口点是一个用户定义 函数,用于执行 特定着色器 阶段的工作。
13.1. 着色器阶段
WebGPU 以绘制或分派命令的形式向 GPU 发出工作。 这些命令在一组着色器阶段 输入、输出和附加 资源的上下文中执行管线。
管线描述要在 GPU 上执行的 工作,它由一系列阶段组成,其中一些是可编程的。 在 WebGPU 中,会在调度绘制或分派命令执行之前创建管线。 管线有两种:GPUComputePipeline 和 GPURenderPipeline。
分派命令使用 GPUComputePipeline 在具有可控并行度的逻辑 点网格上运行一个 计算着色器 阶段, 同时读取并可能更新缓冲区和图像资源。
绘制命令使用一个GPURenderPipeline 来 运行一个多阶段过程,其中在其他固定功能阶段之间包含 两个可编程阶段:
-
顶点着色器 阶段将单个顶点的输入属性映射为 该顶点的输出属性。
-
固定功能阶段将顶点映射为图形图元(例如三角形), 随后对其进行光栅化以产生光栅化片段。 每个光栅化片段表示正在绘制的图元与 帧缓冲区中特定像素之间的重叠。
-
片段 着色器阶段处理每个片段, 并可能产生片段输出。
-
通常,每个光栅化 片段会创建一个片段着色器调用。 如果光栅化片段覆盖多个 采样,则可能创建多个片段调用。请参阅WebGPU § 23.2.10 每采样着色。
-
如果光栅化片段位于图元边界附近,则还可能创建额外的辅助 调用。请参阅§ 15.4 片段 着色器和辅助调用。
-
-
固定功能阶段会使用片段输出,并可能更新外部状态, 例如颜色附件以及深度和模板缓冲区。
WebGPU 规范对管线进行了更详细的描述。
WGSL 定义了三个着色器 阶段,对应于管线的 可编程部分:
-
计算
-
顶点
-
片段
每个着色器阶段都有自己的一组特性和约束,这些内容在其他位置描述。
13.2. 入口点声明
要创建一个入口点,请声明 一个具有着色器阶段属性的用户定义 函数。
在 WebGPU
API 中配置管线时,
入口点的函数名称映射到
WebGPU GPUProgrammableStage
对象的 entryPoint 属性。
入口点的形式 参数表示该阶段的着色器阶段输入。 结构体类型可用于将用户定义输入彼此分组,并可选地与内置 输入一起分组。 每个参数必须是着色器阶段输入, 或者它必须 使用一个结构体类型声明,其中 每个结构体成员都是着色器阶段输入。
如果指定,入口点的返回 类型及其属性表示该阶段的着色器阶段输出。 结构体类型可用于将用户定义输出彼此分组,并可选地与内置 输出一起分组。 如果存在,则返回类型及其属性 必须是 着色器阶段输出, 或者它必须 是一个结构体类型,其中每个结构体成员都是着色器阶段输出。
注: 计算入口点绝不会有返回类型。
@vertex fn vert_main () -> @builtin ( position) vec4< f32> { return vec4< f32> ( 0.0 , 0.0 , 0.0 , 1.0 ); } @fragment fn frag_main ( @builtin ( position) coord_in : vec4< f32> ) -> @location ( 0 ) vec4< f32> { return vec4< f32> ( coord_in . x , coord_in . y , 0.0 , 1.0 ); } @compute @workgroup_size ( 1 ) fn comp_main () { }
着色器阶段中的函数集合是以下内容的并集:
-
该阶段的入口点函数。
-
着色器阶段中某个函数主体内函数调用的目标, 无论该调用是否会执行。
反复应用此并集,直到它稳定。 它会在有限步内稳定。
13.2.1. 入口点的函数属性
WGSL 定义了以下可应用于入口点声明的属性:
@compute @workgroup_size ( 8 , 4 , 1 ) fn sorter () { } @compute @workgroup_size ( 8u ) fn reverser () { } // 使用管线可覆盖常量。 @id ( 42 ) override block_width = 12u ; @compute @workgroup_size ( block_width ) fn shuffler () { } // 错误:必须在计算着色器上指定 workgroup_size @compute fn bad_shader () { }
13.3. 着色器接口
着色器接口是这样一组对象: 着色器通过这些对象访问着色器阶段外部的数据, 以进行读取或写入,以及用于配置着色器的管线可重写常量。 该接口包括:
当满足以下条件时,声明 D 被着色器静态访问:
现在,我们可以将着色器的接口精确定义为由以下内容组成:
-
入口点的返回值。 它表示着色器阶段输出。
13.3.1. 阶段间输入和输出接口
着色器阶段输入 是由管线上游提供给着色器阶段的数据。 每项数据要么是内置输入值,要么是用户定义输入。
着色器阶段 输出是着色器提供给管线下游进一步处理的数据。 每项数据要么是内置输出值,要么是用户定义输出。
IO 属性用于 将对象确立为着色器阶段输入或着色器阶段输出, 或进一步描述输入或输出的属性。 IO 属性包括:
13.3.1.1. 内置输入和输出
内置输入 值提供对系统生成的控制信息的访问。 一个入口点不得具有两个内置 值名称相同的内置输入。
阶段 S 中名称为 X、类型为 TX 的内置输入 通过着色器阶段 S 的入口点的形式参数访问,有 以下两种方式:
-
参数具有属性
builtin(X),且类型为 TX。 -
参数具有结构体类型,其中一个结构体成员具有属性
builtin(X),且类型为 TX。
反过来,当入口点的参数或参数成员具有builtin 属性时, 对应的 builtin 必须是该入口点着色器阶段的输入。
内置输出 值由着色器用于向管线中的后续处理步骤传递 控制信息。 一个入口点不得具有两个内置 值名称相同的内置输出。
阶段 S 中名称为 Y、类型为 TY 的内置输出 通过着色器阶段 S 的入口点的返回值设置,有 以下两种方式:
反过来,当入口点的返回类型或返回类型成员具有builtin 属性时, 对应的 builtin 必须是该入口点着色器阶段的输出。
注: position 内置值既是 顶点着色器的输出,也是片段着色器的输入。
内置输入值和内置输出值统称为内置值。
下表总结了可用的内置值。 每一项都是用于某个内置值的内置值名称词法单元。 后续各节将详细描述每一项。
| 名称 | 阶段 | 方向 | 类型 | 扩展 |
|---|---|---|---|---|
| vertex_index | 顶点 | 输入 | u32 | |
| instance_index | 顶点 | 输入 | u32 | |
| clip_distances | 顶点 | 输出 | array<f32, N> (N ≤ 8)
| clip_distances |
| position | 顶点 | 输出 | vec4<f32> | |
| 片段 | 输入 | vec4<f32> | ||
| front_facing | 片段 | 输入 | bool | |
| frag_depth | 片段 | 输出 | f32 | |
| primitive_index | 片段 | 输入 | u32 | primitive_index |
| sample_index | 片段 | 输入 | u32 | |
| sample_mask | 片段 | 输入 | u32 | |
| 片段 | 输出 | u32 | ||
| local_invocation_id | 计算 | 输入 | vec3<u32> | |
| local_invocation_index | 计算 | 输入 | u32 | |
| global_invocation_id | 计算 | 输入 | vec3<u32> | |
| global_invocation_index | 计算 | 输入 | u32 | linear_indexing |
| workgroup_id | 计算 | 输入 | vec3<u32> | |
| workgroup_index | 计算 | 输入 | u32 | linear_indexing |
| num_workgroups | 计算 | 输入 | vec3<u32> | |
| subgroup_invocation_id | 计算 | 输入 | u32 | subgroups |
| 片段 | ||||
| subgroup_size | 计算 | 输入 | u32 | subgroups |
| 片段 | ||||
| subgroup_id | 计算 | 输入 | u32 | subgroups 和subgroup_id |
| num_subgroups | 计算 | 输入 | u32 | subgroups 和subgroup_id |
struct VertexOutput { @builtin ( position) my_pos : vec4< f32> , @builtin ( clip_distances ) my_clip_distances : array< f32, 8 > , } @vertex fn vs_main ( @builtin ( vertex_index) my_index : u32, @builtin ( instance_index) my_inst_index : u32, ) -> VertexOutput {} struct FragmentOutput { @builtin ( frag_depth) depth : f32, @builtin ( sample_mask) mask_out : u32} @fragment fn fs_main ( @builtin ( front_facing) is_front : bool, @builtin ( position) coord : vec4< f32> , @builtin ( sample_index) my_sample_index : u32, @builtin ( sample_mask) mask_in : u32, ) -> FragmentOutput {} @compute @workgroup_size ( 64 ) fn cs_main ( @builtin ( local_invocation_id) local_id : vec3< u32> , @builtin ( local_invocation_index) local_index : u32, @builtin ( global_invocation_id) global_id : vec3< u32> , ) {}
13.3.1.1.1. clip_distances
| 名称 | clip_distances |
| 阶段 | 顶点 |
| 类型 | array<f32, N> |
| 方向 | 输出 |
| 描述 |
数组中的每个值表示到用户定义裁剪平面的距离。裁剪距离为
0 表示顶点位于平面上,正距离表示顶点位于裁剪
半空间内部,负距离表示顶点位于裁剪半空间外部。clip_distances 的数组
大小必须 ≤
8。
请参阅WebGPU § 23.2.4
图元裁剪。
|
13.3.1.1.2. frag_depth
| 名称 | frag_depth |
| 阶段 | 片段 |
| 类型 | f32 |
| 方向 | 输出 |
| 描述 |
片段的更新深度,位于视口深度范围内。
请参阅 WebGPU § 3.3 坐标系统。 如果支持 fragment_depth 特性, 那么 builtin 属性可以有一个可选的第二个 参数,用于指定 深度模式:
片段的原始深度是该片段的 RasterizationPoint 的深度属性。 如果着色器返回的深度值违反了深度模式保证,则可以改用一个 不确定的深度值。
|
13.3.1.1.3. front_facing
| 名称 | front_facing |
| 阶段 | 片段 |
| 类型 | bool |
| 方向 | 输入 |
| 描述 | 当当前片段位于正面朝向的图元上时为 True。 否则为 False。 |
13.3.1.1.4. global_invocation_id
| 名称 | global_invocation_id |
| 阶段 | 计算 |
| 类型 | vec3<u32> |
| 方向 | 输入 |
| 描述 | 当前调用的全局调用 ID,即它在 计算 着色器网格中的位置。global_invocation_id 的值 等于 workgroup_id * workgroup_size + local_invocation_id。 |
13.3.1.1.5. global_invocation_index
| 名称 | global_invocation_index |
| 阶段 | 计算 |
| 类型 | u32; |
| 方向 | 输入 |
| 描述 |
当前调用的全局调用索引,即它在
计算着色器网格中的线性位置。global_invocation_index 的值
等于:
global_invocation_id.x +
注: 如果分派的
工作组数量会导致此值超出u32
类型的范围,则分派将失败:
|
13.3.1.1.6. instance_index
| 名称 | instance_index |
| 阶段 | 顶点 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前 API 级绘制命令中当前顶点的实例索引。
第一个实例的索引等于绘制的 |
13.3.1.1.7. local_invocation_id
| 名称 | local_invocation_id |
| 阶段 | 计算 |
| 类型 | vec3<u32> |
| 方向 | 输入 |
| 描述 | 当前调用的局部调用 ID,即它在 工作组 网格中的位置。 |
13.3.1.1.8. local_invocation_index
| 名称 | local_invocation_index |
| 阶段 | 计算 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 | 当前调用的局部调用索引,即调用在工作组网格中位置的线性化索引。 |
13.3.1.1.9. num_workgroups
| 名称 | num_workgroups |
| 阶段 | 计算 |
| 类型 | vec3<u32> |
| 方向 | 输入 |
| 描述 |
API 分派大小,
vec3<u32>(group_count_x, group_count_y, group_count_z),即由 API
分派的计算
着色器。
|
13.3.1.1.10. position
| 名称 | position |
| 阶段 | 顶点 |
| 类型 | vec4<f32> |
| 方向 | 输出 |
| 描述 |
当前顶点的裁剪位置,
使用裁剪空间坐标表示。
输出值 (x,y,z,w) 将映射到 WebGPU 归一化设备坐标中的 (x/w, y/w, z/w)。 请参阅WebGPU § 3.3 坐标系和WebGPU § 23.2.4 图元裁剪。 如果 w 坐标为零,则会发生动态错误。 |
| 名称 | position |
| 阶段 | 片段 |
| 类型 | vec4<f32> |
| 方向 | 输入 |
| 描述 |
当前片段光栅化点的输入位置。
令 rp 为片段的RasterizationPoint。 用示意形式表示: fp.xy = rp.destination.position 更详细地说:
|
13.3.1.1.11. primitive_index
| 名称 | primitive_index |
| 阶段 | 片段 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 | 基于当前绘制操作开始以来当前实例已处理的图元数量得到的逐图元索引。 从 0 开始,每处理一个点、线或三角形图元后递增 1。每个绘制实例之间重置为 0。 使用图元重启值重新开始条带图元不会影响 图元索引。该索引在图元的所有片段中保持一致。 |
13.3.1.1.12. sample_index
| 名称 | sample_index |
| 阶段 | 片段 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前片段光栅化点的采样索引。
该值最小为 0,最大为
sampleCount-1,其中 sampleCount 是为 GPU 渲染管线
指定的采样
count。
应用此属性时,如果片段着色器的效果会根据sample_index 的值而变化,则片段着色器将
每个采样调用一次。
|
13.3.1.1.13. sample_mask
| 名称 | sample_mask |
| 阶段 | 片段 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前片段的采样覆盖位掩码。
各个位由半开区间 [0, 仅当采样被正在渲染的图元覆盖时,对应位才会设置为 1。
索引为 该位掩码有两种可能的值:
注: 当
注: 当 |
| 名称 | sample_mask |
| 阶段 | 片段 |
| 类型 | u32 |
| 方向 | 输出 |
| 描述 | 当前片段的采样覆盖掩码控制。最后一次 写入此变量的值将成为着色器输出掩码。写入值中的零位 会导致颜色附件中对应的采样被丢弃。 |
13.3.1.1.14. vertex_index
| 名称 | vertex_index |
| 阶段 | 顶点 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前 API 级绘制命令中当前顶点的索引,
与绘制实例化无关。
对于非索引绘制,第一个顶点的索引等于绘制的 对于索引绘制,该索引等于顶点的索引缓冲区条目
加上绘制的 |
13.3.1.1.15. workgroup_id
| 名称 | workgroup_id |
| 阶段 | 计算 |
| 类型 | vec3<u32> |
| 方向 | 输入 |
| 描述 |
当前调用的工作组 ID,即
工作组在整个计算着色器网格中的位置。
同一工作组中的所有调用具有相同的工作组 ID。 工作组 ID 的范围从 (0,0,0) 到 (group_count_x - 1, group_count_y - 1, group_count_z - 1)。 |
13.3.1.1.16. workgroup_index
| 名称 | workgroup_index |
| 阶段 | 计算 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前调用的工作组索引,即
工作组在整个计算着色器网格中的线性位置。
同一工作组中的所有调用具有相同的工作组索引。
注: 如果分派的
工作组数量会导致此值超出u32
类型的范围,则分派将失败:
|
13.3.1.1.17. subgroup_invocation_id
| 名称 | subgroup_invocation_id |
| 阶段 | 计算或片段 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前调用的子组调用 ID。
该 ID 位于范围 [0, subgroup_size - 1] 内。 在计算着色器中,ID 从零开始且是稠密的。 也就是说,当计算着色器开始执行时,在每个子组内:
注: 片段着色器中的子组调用索引 可能不是稠密的。实现 可能会将一些较小编号的 ID 分配给辅助调用。 |
13.3.1.1.18. subgroup_size
| 名称 | subgroup_size |
| 阶段 | 计算或片段 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前调用所在子组的子组大小。
对于具有subgroup_size 属性的计算着色器,该值 等于指定的属性值。 |
13.3.1.1.19. subgroup_id
| 名称 | subgroup_id |
| 阶段 | 计算 |
| 类型 | u32 |
| 方向 | 输入 |
| 描述 |
当前调用的子组在
工作组内的子组 ID。
该 ID 位于范围 [0, num_subgroups - 1] 内。 |
13.3.1.1.20. num_subgroups
| 名称 | num_subgroups |
| 阶段 | 计算 |
| 类型 | 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。 @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 不能共享一个 location。 fn fragShader2 ( @location ( 0 ) in1 : f32, @location ( 0 ) in2 : f32) { // ... } @fragment // 无效,location 不能应用于结构体。 fn fragShader3 ( @location ( 0 ) in1 : vec4< f32> ) -> @location ( 0 ) D { // ... }
13.3.1.4. 插值
作者可以通过使用 interpolate 属性来控制用户定义的 IO 数据如何进行插值。 WGSL 提供两个可控制的插值方面:插值 类型和插值采样。
- perspective
-
值以透视校正方式进行插值。
- linear
-
值以线性的、非透视校正方式进行插值。
- flat
-
值不进行插值。
- center
-
在像素中心执行插值。
- centroid
-
在一个位于当前图元中片段所覆盖的所有 样本范围内的点处执行插值。 对于图元中的所有样本,此值都相同。
- sample
-
按样本执行插值。 应用此属性时,片段着色器会 每个样本调用一次。
- first
-
该值由图元的第一个顶点提供。
- either
-
该值由图元的第一个顶点或最后一个顶点提供。 该值来自第一个还是最后一个顶点取决于实现。
对于标量或向量浮点类型的用户定义 IO:
-
如果未指定插值属性,则假定为
@interpolate(perspective, center)。 -
如果指定了带有插值类型的插值属性:
标量或向量整数类型的用户定义顶点输出和片段输入必须始终指定插值类型
flat。
阶段间接口验证会检查 在渲染 管线中, 每个用户定义片段输入的插值属性是否与具有相同location 指派的顶点输出的插值属性匹配。 如果不匹配,则管线创建错误将会发生。
13.3.2. 资源接口
资源是一个对象, 它提供对着色器阶段外部数据的访问, 并且既不是重写声明,也不是即时数据变量,也不是着色器阶段输入或输出。 资源由着色器的所有调用共享。
资源有四种:
着色器的资源 接口是由着色器阶段中的函数 静态访问的模块作用域 资源变量集合。
每个资源变量必须同时使用group和binding 属性进行声明。 它们与着色器的阶段一起标识 该资源在着色器管线上的绑定地址。 请参阅 WebGPU § 8.3 GPUPipelineLayout。
着色器中的两个不同资源变量, 当将group和binding值视为一对时,不得 具有相同的值。
13.3.3. 资源布局兼容性
WebGPU 要求着色器的资源接口与使用该着色器的管线布局匹配。
如果资源接口中的 WGSL 变量绑定到不兼容的 WebGPU 绑定成员或绑定类型, 则会产生管线创建错误, 兼容性由下表定义。
| WGSL 资源 | WebGPU 绑定成员 | WebGPU 绑定类型 | |
|---|---|---|---|
| 统一缓冲区 | buffer
| GPUBufferBindingType
| "uniform"
|
| 存储缓冲区 具有 read_write 访问权限 | "storage"
| ||
| 存储缓冲区 具有 read 访问权限 | "read-only-storage"
| ||
| sampler | sampler
| GPUSamplerBindingType
| "filtering"
|
"non-filtering"
| |||
| sampler_comparison | "comparison"
| ||
| 采样纹理、 深度 纹理或 多重采样纹理 | texture
| GPUTextureSampleType
| "float"
|
"unfilterable-float"
| |||
"sint"
| |||
"uint"
| |||
"depth"
| |||
| 只写存储纹理 | storageTexture
| GPUStorageTextureAccess
| "write-only"
|
| 读写存储纹理 | "read-write"
| ||
| 只读存储纹理 | "read-only"
| ||
| 外部纹理 | externalTexture
| (不适用) | |
有关接口验证要求,请参阅 WebGPU API 规范。
13.3.4. 缓冲区绑定确定运行时大小数组的元素数量
当存储缓冲区
变量包含运行时大小数组时,该数组中的元素数量
由对应resource的大小确定:
更详细地说,类型为 RAT 的运行时大小数组的 NRuntime 为:
截断((EBBS − array_offset) ÷ array_stride),其中:
着色器可以通过 arrayLength 内置函数计算 NRuntime。
NRuntime 由 对应缓冲区绑定的大小确定,并且对于每个绘制 或分派命令,该值可能不同。
WebGPU 验证规则确保 1 ≤ NRuntime。
下表展示了基于
对应有效缓冲区绑定大小的 weights 变量的 NRuntime 示例。
| 有效缓冲区绑定大小 | weights 变量的 NRuntime
| 计算 |
|---|---|---|
| 1024 | 256 | 截断( 1024 ÷ 4 ) |
| 1025 | 256 | 截断( 1025 ÷ 4 ) |
| 1026 | 256 | 截断( 1026 ÷ 4 ) |
| 1027 | 256 | 截断( 1027 ÷ 4 ) |
| 1028 | 257 | 截断( 1028 ÷ 4 ) |
-
lights变量是一个存储缓冲区。 -
其存储类型为
LightStorage。 -
LightStorage的point成员是一个类型为array<PointLight>的运行时大小数组。
struct PointLight { // 对齐(16) 大小(32) position: vec3f, // 偏移量(0) 对齐(16) 大小(12) // -- 隐式成员对齐填充 -- // 偏移量(12) 大小(4) color : vec3f, // 偏移量(16) 对齐(16) 大小(12) // -- 隐式结构大小填充 -- // 偏移量(28) 大小(4) } struct LightStorage { // 对齐(16) pointCount : u32, // 偏移量(0) 对齐(4) 大小(4) // -- 隐式成员对齐填充 -- // 偏移量(4) 大小(12) point : array< PointLight > , // 偏移量(16) 对齐(16) 元素大小(32) } @group ( 0 ) @binding ( 1 ) var < storage> lights : LightStorage ;
下表展示了 lights
变量的 point 成员的 NRuntime
示例。
| 有效缓冲区绑定大小 | lights 变量的 point 成员的 NRuntime
| 计算 |
|---|---|---|
| 1024 | 31 | 截断( ( 1024 - 16 ) ÷ 32) ) |
| 1025 | 31 | 截断( ( 1025 - 16 ) ÷ 32) ) |
| 1039 | 31 | 截断( ( 1039 - 16 ) ÷ 32) ) |
| 1040 | 32 | 截断( ( 1040 - 16 ) ÷ 32) ) |
14. 内存
在 WGSL 中,可存储类型的值可以 存储在内存中,以便稍后取回。 本节描述内存的结构,以及访问内存的操作的语义。 有关可以放置在内存中的值的类型以及用于执行内存访问的类型,请参阅§ 6.5 内存视图。
14.1. 内存位置
内存由一组不同的内存位置组成。 每个内存位置的大小为 8 位。 影响内存的操作会与一个或多个内存位置的集合交互。 对复合值的内存操作不会 访问填充内存位置。 因此,某个操作访问的内存位置集合可能不是连续的。
如果两个内存位置集合的交集 非空,则这两个内存位置集合重叠。
14.2. 内存访问模式
内存访问是作用于内存位置的 操作。
-
读访问 观察内存位置的内容。
-
写访问 设置内存位置的内容。
单个操作可以读取、写入,或同时读取和写入。
特定内存位置可能只支持某些类型的访问,这表示为 内存的访问 模式。
| 访问模式 | 支持的访问 |
|---|---|
| read | 支持读访问,但不支持写访问。 |
| write | 支持写访问,但不支持读访问。 |
| read_write | 同时支持读访问和写访问。 |
WGSL 预声明了枚举值 read、
write 和 read_write。
14.3. 地址空间
内存位置被划分为地址空间。 每个地址空间都具有决定 可变性、可见性、可包含的值 以及如何使用其中变量的独特属性。 有关更多详细信息,请参阅§ 7 变量和值声明。
给定内存 视图的访问模式通常由上下文决定:
storage 地址空间同时支持read 和 read_write 访问 模式。 其他每个地址空间只支持一种访问模式。 每个地址空间的默认访问模式在下 表中描述。
| 地址空间 | 调用之间的共享 | 默认访问模式 | 说明 |
|---|---|---|---|
| function | 仅同一调用 | read_write | |
| private | 仅同一调用 | read_write | |
| workgroup | 同一计算着色器工作组中的调用 | read_write | 最外层数组的元素数量 可以是管线可覆盖常量。 |
| uniform | 同一着色器阶段中的调用 | read | 用于uniform 缓冲区变量 |
| storage | 同一着色器阶段中的调用 | read | 用于存储 缓冲区变量 |
| immediate | 同一着色器阶段中的调用 | read | 用于立即
数据变量。 每个入口点最多可以 静态访问一个立即变量。 |
| handle | 同一着色器阶段中的调用 | read | 用于采样器
和纹理
变量。 |
WGSL 为每个地址空间预声明一个枚举值,
handle 地址空间除外。
workgroup 地址空间中的变量 必须仅在 计算着色器阶段中被静态 访问。
storage
地址空间中的变量(存储缓冲区)只有
在访问模式为read 时,才能由顶点着色器阶段静态
访问。
存储类型为具有
write 或read_write访问模式的存储纹理的变量不能
由顶点着色器阶段静态
访问。
请参阅 WebGPU createBindGroupLayout()。
注: 每个地址空间可能具有不同的性能 特征。
14.4. 内存布局
WGSL 中类型的布局与地址空间无关。 但严格来说,这种布局只能通过主机可共享 缓冲区观察到。 Uniform 缓冲区和存储缓冲区变量 用于共享 在内存中组织为字节序列的大批量数据。 缓冲区可以在 CPU 与 GPU 之间、管线中的不同着色器阶段 之间,或不同管线之间共享。
因为缓冲区数据在共享时不会重新格式化或转换,所以如果缓冲区 生产者和消费者对内存布局不一致,就会发生动态错误;内存布局描述 缓冲区中的字节如何组织为有类型的 WGSL 值。 这些字节是相对于公共基 位置的值的内存 位置。
缓冲区变量的存储类型 必须是 主机可共享的,并具有如下所述的完全 展开的内存布局。
每个缓冲区变量必须声明在 uniform 或 storage 地址空间中。
仅当对具有以下内容的表达式求值时,类型的内存布局才有意义:
8 位字节是主机可共享内存的最基本单位。 本节中定义的术语均表示 8 位字节的数量。
我们将使用以下表示法,其中 T 是主机可共享或固定占用空间类型, S 是主机可共享或固定占用空间的结构体类型, A 是主机可共享或固定占用空间的数组或运行时大小数组:
-
AlignOfMember(S, i) 是 S 的第 i 个成员的对齐。
-
SizeOf(T) 是 T 的字节大小。
-
SizeOfMember(S, i) 是 S 的第 i 个成员的大小。
-
OffsetOfMember(S, i) 是 第 i 个成员相对于 S 起始位置的偏移。
-
StrideOf(A) 是 A 的元素 步幅,定义为 从一个数组元素起始位置到下一个元素起始位置的字节数。 它等于数组元素类型的大小向上舍入到元素类型对齐后的值:
StrideOf(array<E, N>) = roundUp(AlignOf(E), SizeOf(E))
StrideOf(array<E>) = roundUp(AlignOf(E), SizeOf(E)) -
AccessibleBytes(T) 是类型 T 的实例中 包含数据的字节偏移集合。
-
如果 T 是标量或向量,则AccessibleBytes(T) 是满足
0 <= k <SizeOf(T) 的整数k集合。 -
如果 T 是具有 C 列和 R 行的矩阵,则AccessibleBytes(T) 是按以下方式计算的 集合:
-
对于
0..C-1中的每个i:-
对于AccessibleBytes(vecR) 中的每个
k:-
该集合包含
k + i * Stride。
-
-
-
如果 T 是结构体 S,则AccessibleBytes(T) 是按以下方式计算的 集合:
-
对于字节偏移
Offset= OffsetOfMember(S,i) 处的每个成员M_i:-
对于AccessibleBytes(
M_i) 中的每个k:-
该集合包含
k + Offset。
-
-
-
-
-
AccessibleSlots(T) 是整数
i的集合, 使区间[4*i, 4*i+4)与AccessibleBytes(T) 的交集 非空。
14.4.1. 对齐和大小
每个主机可共享或固定占用空间数据类型 T 都有大小。 除buffer 类型外,每个 主机可共享或固定占用空间类型都有对齐。
类型的对齐是 对该类型的值可以放置在内存中何处的约束,用整数表示: 类型的对齐必须整除 该类型值起始内存位置的字节地址。 对齐可以使用更高效的硬件指令来访问值, 或满足某些 地址空间上更严格的硬件要求。(请参阅地址空间布局约束)。
注: 根据构造,每个对齐值始终是 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 的元素数量 |
| buffer<N> | 不适用 | N |
| buffer | 不适用 | 所附加 WebGPU GPUBuffer
的大小
|
注: buffer 类型的对齐无关紧要,因为它们不能 包含在任何其他类型中。
14.4.2. 结构体成员布局
结构体的内部布局 根据其成员的大小和对齐计算。 默认情况下,成员会按顺序紧密排列、互不重叠,同时满足成员对齐 要求。
这种默认内部布局可以通过使用布局属性覆盖,这些属性包括:
结构体类型 S 的第 i 个成员具有大小和对齐,分别记作 SizeOfMember(S, i) 和AlignOfMember(S, i)。 成员大小和对齐用于计算每个成员相对于结构体起始位置的字节偏移, 如§ 14.4.4 值的内部布局中所述。
如果 S 的第 i 个成员具有属性size(k), 则SizeOfMember(S, i) 为 k。 否则,它为 SizeOf(T),其中 T 是该成员的类型。
如果 S 的第 i 个成员具有属性align(k), 则AlignOfMember(S, i) 为 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 { // 对齐(8) 大小(24) u : f32, // 偏移(0) 对齐(4) 大小(4) v : f32, // 偏移(4) 对齐(4) 大小(4) w : vec2< f32> , // 偏移(8) 对齐(8) 大小(8) x : f32// 偏移(16) 对齐(4) 大小(4) // -- 隐式结构体大小填充 -- // 偏移(20) 大小(4) } struct B { // 对齐(16) 大小(160) a : vec2< f32> , // 偏移(0) 对齐(8) 大小(8) // -- 隐式成员对齐填充 -- // 偏移(8) 大小(8) b : vec3< f32> , // 偏移(16) 对齐(16) 大小(12) c : f32, // 偏移(28) 对齐(4) 大小(4) d : f32, // 偏移(32) 对齐(4) 大小(4) // -- 隐式成员对齐填充 -- // 偏移(36) 大小(4) e : A , // 偏移(40) 对齐(8) 大小(24) f : vec3< f32> , // 偏移(64) 对齐(16) 大小(12) // -- 隐式成员对齐填充 -- // 偏移(76) 大小(4) g : array< A , 3 > , // 元素步幅 24 偏移(80) 对齐(8) 大小(72) h : i32// 偏移(152) 对齐(4) 大小(4) // -- 隐式结构体大小填充 -- // 偏移(156) 大小(4) } @group ( 0 ) @binding ( 0 ) var < storage, read_write> storage_buffer : B ;
struct A { // 对齐(8) 大小(32) u : f32, // 偏移(0) 对齐(4) 大小(4) v : f32, // 偏移(4) 对齐(4) 大小(4) w : vec2< f32> , // 偏移(8) 对齐(8) 大小(8) @size ( 16 ) x : f32// 偏移(16) 对齐(4) 大小(16) } struct B { // 对齐(16) 大小(208) a : vec2< f32> , // 偏移(0) 对齐(8) 大小(8) // -- 隐式成员对齐填充 -- // 偏移(8) 大小(8) b : vec3< f32> , // 偏移(16) 对齐(16) 大小(12) c : f32, // 偏移(28) 对齐(4) 大小(4) d : f32, // 偏移(32) 对齐(4) 大小(4) // -- 隐式成员对齐填充 -- // 偏移(36) 大小(12) @align ( 16 ) e : A , // 偏移(48) 对齐(16) 大小(32) f : vec3< f32> , // 偏移(80) 对齐(16) 大小(12) // -- 隐式成员对齐填充 -- // 偏移(92) 大小(4) g : array< A , 3 > , // 元素步幅 32 偏移(96) 对齐(8) 大小(96) h : i32// 偏移(192) 对齐(4) 大小(4) // -- 隐式结构体大小填充 -- // 偏移(196) 大小(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 = 步幅 * 元素数量 = 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 = 步幅 * 元素数量 = 16 * 8 var bigger_stride : array< vec3< f32> , 8 > ;
// 数组,其中: // - 对齐为 4 = AlignOf(f32) // - 元素步幅为 4 = roundUp(AlignOf(f32),SizeOf(f32)) = 4 // 如果 B 是绘制或分派命令上的绑定的有效缓冲区绑定大小, // 则元素数量为: // N_runtime = floor(B / 元素步幅) = 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 是绘制或分派命令上的绑定的有效缓冲区绑定大小, // 则元素数量为: // N_runtime = floor(B / 元素步幅) = floor(B / 16) var < storage> directions : array< vec3< f32>> ;
14.4.4. 值的内部布局
本节描述在假定整体值放置位置的情况下,主机可共享值的内部内容如何放置在 缓冲区的字节位置中。 这些布局取决于值的类型, 以及结构体成员上的align 和size 属性。 这些规则适用于如下所述的非不透明类型; buffer 类型的值没有内部 结构,也不受 这些规则约束。
放置值的缓冲区字节偏移必须满足类型对齐要求:如果类型为 T 的值 放置在缓冲区偏移 k 处,则对于某个 非负整数 c,k = c × AlignOf(T)。
无论地址空间如何,数据都将以相同方式出现。
注: bool 类型不是主机可共享的。 WGSL 规定 bool 值的大小和 对齐均为 4 字节, 但没有规定 bool 值的内部布局。
当类型为u32 或i32 的值 V 放置在 主机共享缓冲区的字节偏移 k 处时:
-
字节 k 包含 V 的第 0 到 7 位
-
字节 k+1 包含 V 的第 8 到 15 位
-
字节 k+2 包含 V 的第 16 到 23 位
-
字节 k+3 包含 V 的第 24 到 31 位
注: 请记住,i32 使用二进制补码表示,因此符号位 位于第 31 位。
64 位整数布局: WebGPU API 的某些特性会将 64 位 无符号整数值写入缓冲区。当这样的值 V 出现在主机共享缓冲区的字节 偏移 k 处时:
-
字节 k 包含 V 的第 0 到 7 位
-
字节 k+1 包含 V 的第 8 到 15 位
-
字节 k+2 包含 V 的第 16 到 23 位
-
字节 k+3 包含 V 的第 24 到 31 位
-
字节 k+4 包含 V 的第 32 到 39 位
-
字节 k+5 包含 V 的第 40 到 47 位
-
字节 k+6 包含 V 的第 48 到 55 位
-
字节 k+7 包含 V 的第 56 到 63 位
类型为f32 的值 V 使用 IEEE-754 binary32 格式表示。 它具有一个符号位、8 个指数位和 23 个尾数位。 当 V 放置在主机共享缓冲区的字节偏移 k 处时:
-
字节 k 包含尾数的第 0 到 7 位。
-
字节 k+1 包含尾数的第 8 到 15 位。
-
字节 k+2 的第 0 到 6 位包含尾数的第 16 到 22 位。
-
字节 k+2 的第 7 位包含指数的第 0 位。
-
字节 k+3 的第 0 到 6 位包含指数的第 1 到 7 位。
-
字节 k+3 的第 7 位包含符号位。
类型为f16 的值 V 使用 IEEE-754 binary16 格式表示。 它具有一个符号位、5 个指数位和 10 个尾数位。 当 V 放置在主机共享缓冲区的字节偏移 k 处时:
-
字节 k 包含尾数的第 0 到 7 位。
-
字节 k+1 的第 0 到 1 位包含尾数的第 8 到 9 位。
-
字节 k+1 的第 2 到 6 位包含指数的第 0 到 4 位。
-
字节 k+1 的第 7 位包含符号位。
注: 上述规则意味着主机共享缓冲区中的数值 以小端格式存储。
当原子
类型 atomic<T> 的值 V 放置在主机共享缓冲区中时,
它具有与底层类型 T 的值相同的内部布局。
当向量类型 vecN<T> 的值 V 放置在 主机共享缓冲区的字节偏移 k 处时:
-
V.x 放置在字节偏移 k 处
-
V.y 放置在字节偏移 k + SizeOf(T) 处
-
如果 N ≥ 3,则 V.z 放置在字节偏移 k + 2 × SizeOf(T) 处
-
如果 N ≥ 4,则 V.w 放置在字节偏移 k + 3 × SizeOf(T) 处
当矩阵类型 matCxR<T> 的值 V 放置在 主机共享缓冲区的字节偏移 k 处时:
-
V 的列向量 i 放置在字节偏移 k + i × AlignOf(vecR<T>) 处
当数组类型 A 的值 放置在主机共享内存缓冲区的字节偏移 k 处时, 则:
-
数组的元素 i 放置在字节偏移 k + i × StrideOf(A) 处
当结构体类型 S 的值放置在主机共享内存缓冲区的字节偏移 k 处时, 则:
-
结构体值的第 i个成员放置在字节偏移 k + OffsetOfMember(S,i) 处。 请参阅§ 14.4.2 结构体成员布局。
14.4.5. 地址空间布局约束
storage 和uniform 地址空间 具有不同的布局约束,本节将对此进行描述。
除uniform 外的所有地址空间 都具有与storage 地址空间相同的 约束。
由变量直接或间接引用的所有结构体和数组类型 必须遵守 变量地址空间的约束。 违反地址空间约束会导致着色器创建错误。
在本节中,我们将 RequiredAlignOf(S, C) 定义为 主机可共享或固定占用空间类型 S 的值在 地址空间 C 中使用时的字节偏移对齐要求。
| 主机可共享或固定占用空间类型 S, 假定 S 可以出现在 C 中 | RequiredAlignOf(S, C), 支持uniform_buffer_standard_layout 或 C 不是uniform | RequiredAlignOf(S, C), 不支持uniform_buffer_standard_layout 且 C 是uniform |
|---|---|---|
| bool、i32、u32、f32 或f16 | AlignOf(S) | AlignOf(S) |
| atomic<T> | AlignOf(S) | AlignOf(S) |
| vecN<T> | AlignOf(S) | AlignOf(S) |
| matCxR<T> | AlignOf(S) | AlignOf(S) |
| array<T, N> | AlignOf(S) | roundUp(16, AlignOf(S)) |
| array<T> | AlignOf(S) 如果 C 是uniform,则不允许,除非作为 buffer_view 内置函数的结果 | roundUp(16, AlignOf(S)) 不允许,除非作为 buffer_view 内置函数的结果 |
| struct S | AlignOf(S) | roundUp(16, AlignOf(S)) |
类型为 T 的结构体成员必须具有相对于结构体起始位置的字节偏移, 该偏移是地址空间 C 的RequiredAlignOf(T, C) 的倍数:
OffsetOfMember(S, i) = k × RequiredAlignOf(T, C)
其中 k 是非负整数,且结构体 S 的第 i 个成员的 类型为 T
元素类型为 T 的数组必须具有一个元素步幅,该步幅是 地址空间 C 的RequiredAlignOf(T, C) 的倍数:
StrideOf(array<T, N>) = k × RequiredAlignOf(T, C)
StrideOf(array<T>) = k × RequiredAlignOf(T, C)
其中 k 是正整数
当uniform_buffer_standard_layout 不受支持时, uniform 地址空间要求:
-
数组元素按 16 字节边界对齐。 即 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 > // 步幅 4 } // 无效,步幅必须是 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 > // 步幅 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 内存模型中的内存写操作。
当调用执行以下任一操作时,会发生读访问:
-
对加载规则求值
-
除以下函数外的任何纹理内置函数:
-
除 atomicStore 外的任何原子内置函数
-
workgroupUniformLoad 内置函数
当调用执行以下任一操作时,会发生写访问:
-
textureStore 内置函数
-
除 atomicLoad 外的任何原子内置函数
-
仅当返回结果的
exchanged成员为true时, atomicCompareExchangeWeak 才执行写入
-
原子读取-修改-写入内置函数执行单个 内存操作,该操作既是读 访问也是写 访问。
在其他任何情况下都不会发生读访问和写访问。 在 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. Private 与 Non-private
storage
或
workgroup 地址空间中的所有非原子读访问
都被视为
非私有,并对应于具有
Workgroup 作用域以及 NonPrivatePointer | MakePointerVisible 内存操作数的读操作。
storage
或
workgroup 地址空间中的所有非原子写访问
都被视为
非私有,并对应于具有
Workgroup 作用域以及 NonPrivatePointer | MakePointerAvailable 内存操作数的
写操作。
handle
地址
空间中的所有非原子读访问
都被视为非私有,并对应于
具有 Workgroup 作用域以及 NonPrivateTexel | MakeTexelVisible 内存操作数的
读操作。
handle
地址
空间中的所有非原子写访问
都被视为非私有,并对应于
具有 Workgroup 作用域以及 NonPrivateTexel | MakeTexelAvailable 内存操作数的
写操作。
15. 执行
§ 1.1 概述描述了着色器如何被调用以及如何划分为调用。 本节进一步描述调用如何单独和集体执行的约束。
15.1. 调用内的程序顺序
WGSL 模块中的每条语句在 执行期间可能执行零次或多次。 对于给定调用,给定语句的每次执行都表示一个唯一的 动态语句 实例。
当语句包含表达式时,语句的语义决定:
-
表达式是否作为语句执行的一部分进行求值。
-
语句中独立表达式之间的相对求值顺序。
表达式嵌套定义了必须满足才能
完成求值的数据依赖关系。
也就是说,必须先对嵌套表达式求值,然后才能对外围表达式
求值。
WGSL 中表达式操作数的求值顺序为从左到右。
例如,foo() + bar() 必须先对 foo() 求值,再对 bar() 求值。
请参阅§ 8 表达式。
WGSL 模块中的语句按控制流顺序执行。 请参阅§ 9 语句和§ 11.2 函数调用。
15.2. 一致性
集体操作 (例如屏障、导数或依赖隐式计算导数的纹理操作) 需要在 GPU 上并发运行的不同调用之间进行协调。 当所有调用并发执行该操作,即处于一致控制流中时,该操作才能正确且可移植地执行。
相反,当只有调用的严格子集 执行该操作,即处于非一致控制流中时,会出现不正确或不可移植的行为。 非正式地说,由于非一致控制依赖关系,一些调用到达集体操作,而其他调用没有到达, 或没有在同一时间到达。 非一致控制依赖关系来自控制流 语句,其行为取决于非一致值。
例如,当不同调用为 if、break-if、while 或for 的条件计算出不同的值, 为switch 的选择器计算出不同的值, 或为短路二元运算符(
&&或||)的左操作数计算出不同的值时,就会产生非一致控制依赖关系。
这些非一致值通常可以追溯到某些 未被静态证明为一致的来源。 这些来源包括但不限于:
为确保正确且可移植的行为,WGSL 实现将 执行静态一致性分析,尝试证明每个集体操作 都在一致 控制流中执行。 后续小节将描述该分析。
当一致性 分析无法证明某个特定集体操作 在一致 控制流中执行时,将 触发一致性失败。
-
如果为一个 计算导数的内置函数触发一致性失败,则 derivative_uniformity诊断会被触发。
-
如果为子组或 四元组内置函数触发一致性失败,则会触发一个subgroup_uniformity 诊断
-
该诊断的触发位置是该 内置函数的调用点位置, 或者在 subgroupShuffleUp、 subgroupShuffleDown 或 subgroupShuffleXor 的情况下,是要求保持一致的参数位置
-
15.2.1. 术语和概念
以下定义仅为资料性内容,旨在帮助直观理解下一小节中的分析 正在计算什么。 实际定义这些概念以及程序何时有效或违反一致性 规则的是该分析本身。
对于给定的一组调用:
-
如果给定作用域中的所有调用在程序中的某个给定 点表现得如同以锁步方式执行,则称该点对于给定的一致性作用域具有一致控制流。
-
如果表达式在一致控制流中执行,并且所有调用都计算出 相同的值,则称它是一致值。
-
如果调用在局部变量的整个存活期间的每个点都持有相同的值, 则称它是一致变量。
15.2.2. 一致性分析概述
其余小节规定一种静态分析,用于验证 集体操作是否只在一致控制流中执行。 如果支持subgroup_uniformity 特性,则 存在多个一致性 作用域。 对每个作用域分别执行一次此分析。
注: 该分析被描述为每个作用域执行一次,但 实现可以执行一次包含所有作用域的分析。 工作组和绘制 一致性作用域实际上等价,因为它们作用于不同的 着色器阶段,并表示着色器阶段中最大的一致性作用域。
该分析假定不会发生动态 错误。 无论一致性分析结果如何,具有动态 错误的着色器阶段本身就已经不可移植。
分析每个函数时,会尝试确保两件事:
-
调用其他函数时满足一致性要求,并且
-
每当它被调用时都满足一致性要求。
作为此工作的一部分,分析会计算有关函数的元数据,以帮助依次分析其调用方。 这意味着必须首先构建调用图,并且必须从叶节点向上分析函数, 即从不调用标准库之外任何函数的函数开始,朝入口点方向分析。 这样,每当分析一个函数时,其所有被调用方的元数据都已经计算完成。 由于语言禁止递归,因此不存在陷入环路的风险。
注: 另一种表达同一件事的方式是,我们 按“是其(可能间接的)被调用方”这一偏序关系对函数进行拓扑排序,并 按该顺序分析它们。
此外,对于每个函数调用,分析都会计算并传播 一组触发规则(如果有),如果无法证明该 调用处于一致控制流中,则这些规则将被触发。 我们称其为该调用的潜在触发集合。 该集合的元素来自以下可能项:
-
derivative_uniformity,用于依赖 计算导数的函数,
-
一个未命名的触发规则,用于无法过滤的一致性要求。
-
这用于依赖同步函数的计算着色器函数。
-
15.2.3. 分析函数的一致性要求
每个函数分两个阶段进行分析。
第一阶段遍历函数的语法,并按照 后续小节中的规则沿途构建一个有向图。 第二阶段探索该图,计算调用此函数的约束, 并可能触发一致性失败。
一条边可以理解为从与其源节点对应的陈述到与其目标节点对应的 陈述的蕴含关系。
例如,一项一致性要求是 workgroupBarrier 内置函数必须
仅在一致控制流中调用。
为表示这一点,我们从RequiredToBeUniform.error 添加一条边到对应于
workgroupBarrier调用点的节点。
一种理解方式是,RequiredToBeUniform.error 对应命题
True,
因此 RequiredToBeUniform.error -> X 就等同于说
X 为 true。
反过来,为表示我们无法确保某个对象的一致性(例如保存 线程 id 的变量),我们从对应节点添加一条边到MayBeNonUniform。 一种理解方式是,MayBeNonUniform 对应命题 False,因此 X -> MayBeNonUniform 就等同于说 X 为 false。
这种解释的一个结果是,从RequiredToBeUniform.error 可达的每个节点都对应于 为使程序有效而要求保持一致的对象,而从其可以到达 MayBeNonUniform 的每个节点 都对应于我们无法保证其一致性的对象。 因此,如果存在任何从RequiredToBeUniform.error 到MayBeNonUniform 的路径, 就会出现一致性违规,并触发一致性失败。
节点RequiredToBeUniform.warning 和RequiredToBeUniform.info 的使用方式类似, 但它们用于帮助确定何时应触发警告或信息诊断:
-
如果存在从RequiredToBeUniform.warning 到MayBeNonUniform 的路径,则会触发警告 诊断。
-
如果存在从RequiredToBeUniform.info 到MayBeNonUniform 的路径,则会触发信息 诊断。
如§ 2.3 诊断中所述,如果还生成了更高严重级别的诊断, 则较低严重级别的诊断可能会被丢弃。
对于每个函数,会计算两个标签:
-
调用点标签 描述函数调用点上的控制流一致性要求,以及
-
函数标签 描述函数对一致性的影响。
对于函数的每个形式 参数,会计算一个或两个标签:
-
参数标签 描述参数值的一致性要求。
-
参数 返回标签描述参数的一致性如何影响函数的 返回值。
-
当参数类型是指向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 作为函数体的起始 控制流。
-
此步骤中添加的节点称为内部节点。
-
-
按如下方式初始化:
-
函数标签 初始化为NoRestriction。
-
每个param_i 的参数 标签初始化为ParameterNoRestriction。
-
每个param_i 的指针参数标签(如果存在)初始化为PointerParameterNoRestriction。
-
-
对于按 {error、warning、info} 顺序排列的每个严重级别 S,执行以下操作:
-
令 R.S 为从RequiredToBeUniform.S 可达的未访问节点集合。
-
将 R.S 中的内部 节点标记为已访问。
-
令 PTS 为与RequiredToBeUniform.S 关联的潜在触发集合。
-
如果 R.S 包含节点MayBeNonUniform,则触发一致性失败:
-
对于 PTS 中的每个 t,触发一个 严重级别为 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个参数的param_i_contents 的边(即 V(e) 是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 使用 p 的变量 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) 是执行语句 S 之前
x的值。 -
Vout(S) 是执行语句 S 之后
x的值。 -
Vout(prev) 是执行当前 语句之前
x的值。 -
Vin(next) 是执行下一条 语句之前
x的值。 -
V(e) 是后续各节中表达式的值节点。
-
V(0) 是
x的有效值类型的零值。
| 语句 | 结果 | 从结果发出的边 |
|---|---|---|
| var x; | Vin(next) | V(0) |
|
var x = e; | Vin(next) |
V(e)
注: 这是对 x 的完整赋值。 |
|
x = e; | ||
|
r = e; 其中 r 是变量 x 的完整引用 | ||
|
r = e; 其中 r 是变量 x 的部分引用 | Vout(S) |
V(e), Vout(prev)
注: 这是对 x 的部分赋值。 |
|
s1 s2 其中 Next 位于 s1 的行为中。 注: s1 通常以 分号结尾。 | Vin(s2) | Vout(s1) |
|
if e s1 else s2 其中 Next 同时位于 s1 和 s2 的行为中 | Vin(next) | Vout(s1), Vout(s2) |
|
if e s1 else s2 其中 Next 位于 s1 的行为中,但不位于 s2 的行为中 | Vin(next) | Vout(s1) |
|
if e s1 else s2 其中 Next 位于 s2 的行为中,但不位于 s1 的行为中 | Vin(next) | Vout(s2) |
| loop { s1 continuing { s2 } } | Vin(s1) | Vout(prev), 如果 s1 的行为与 {Next,Continue} 相交,则为 Vout(s2) |
| loop { s1 continuing { s2 } } | Vin(s2) | 如果 Next 位于 s1 的行为中,则为 Vout(s1), 对 s1 中行为为 {Continue} 且将控制 转移到 s2 的所有 si,为 Vout(si) |
| loop { s1 continuing { s2 } } | Vin(next) | 如果 Break 位于 s1 的行为中,则为 Vout(s2), 对 s1 中行为为 {Break} 且将控制 转移到 next 的所有 si,为 Vout(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) => V 表示“从控制流 CF1 开始
对表达式 E 运行分析,对图应用所需更改,并将
得到的值节点命名为 V”(有关表达式分析,请参阅下一节)。
这种表达式求值用于任何不属于赋值的左侧的表达式,并
称为RHSValue。
对于属于
赋值的左侧的表达式,有一组类似的规则,即
LHSValue,
我们将其记作 LHSValue: (CF, E) => L。它不会计算
与值的一致性对应的节点,而是计算
与我们正在寻址的变量的一致性对应的节点。
注: RHSValue 包括属于 赋值 语句右侧的表达式,或不属于 赋值、递增或递减语句的表达式。
当需要创建多条边时,我们使用 X -> {Y, Z} 作为
X -> Y, X -> Z 的简写。
在分析循环时,我们使用以下模式:
-
节点 CF' 对每次迭代开始时的控制流一致性建模。
-
节点 CF1 对循环体 s1 结束时的控制流一致性建模。
-
节点 CF2 对 continuing 块语句 s2 结束时的控制流一致性建模。
-
边 CF' -> CF1 和 CF' -> CF2 对以下事实建模: 循环结束时的控制流 一致性会影响下一次迭代开始时的一致性。 当循环体语句 s1 只会 break 或 return, 即 s1 的行为是 {Break, Return} 的子集时,这些边不存在。
-
CF' -> CF 边对以下事实建模:至少某些迭代开始时的 一致性取决于控制流到达整个循环语句时的一致性。
-
当整个循环的行为为 {Next} 时,我们假设任何控制流 分歧在循环结束时都已解决。 因此,循环的结果控制流节点被设置为 CF, 以对以下事实建模:离开循环时的一致性与 首次到达循环时的一致性相匹配。
-
注: 语句行为 分析意味着 循环的行为是 {Next}、{Return} 或 {Next,Return} 之一。
| 语句 | 新节点 | 递归分析 | 结果控制流节点 | 新边 |
|---|---|---|---|---|
| 空语句 | CF | |||
| {s} | (CF, s) => CF' | CF' | ||
|
s1 s2, s1 的行为中包含 Next 注: s1 通常以 分号结尾。 | (CF, s1) => CF1 (CF1, s2) => CF2 | CF2 | ||
|
s1 s2, s1 的行为中不包含 Next 注: s1 通常以 分号结尾。 |
(CF, s1) => CF1 注: s2 在静态上 不可达,因此不会递归分析。 s2 不参与一致性分析。 | CF1 | ||
| if e s1 else s2 行为为 {Next} | (CF, e) => V (V, s1) => CF1 (V, s2) => CF2 | CF | ||
| if e s1 else s2 具有另一种行为 | CFend | CFend | CFend -> {CF1, CF2} | |
| loop {s1} s1 的行为中不包含 Return | CF' | (CF', s1) => CF1 | CF | CF' -> {CF1, CF}, 如果 s1 的行为与 {Next,Continue} 相交 |
| CF' -> CF, 如果 s1 的行为不与 {Next,Continue} 相交 | ||||
| loop {s1} s1 的行为中包含 Return | CF' | (CF', s1) => CF1 | CF1 | CF' -> {CF1, CF} 如果 s1 的行为与 {Next,Continue} 相交 |
| CF' -> CF 如果 s1 的行为不与 {Next,Continue} 相交 | ||||
| loop {s1 continuing {s2}} s1 的行为为 {Break} | (CF, s1) => CF1 | CF |
注: 循环只执行一次 迭代,因此不需要额外的边。 | |
| loop {s1 continuing {s2}} s1 的行为为 {Return} 或 {Break,Return} | (CF, s1) => CF1 | CF1 | ||
| loop {s1 continuing {s2}} s1 的行为与 {Next,Continue} 相交 | CF' | (CF', s1) => CF1 (CF1, s2) => CF2 | CF 如果 s1 的行为中不包含 Return | CF' -> {CF2, CF} |
| CF' 如果 s1 的行为中包含 Return | ||||
| switch e case _: s_1 .. case _: s_n 行为为 {Next} | (CF, e) => V (V, s_1) => CF_1 ... (V, s_n) => CF_n | CF | ||
| switch e case _: s_1 .. case _: s_n 具有另一种 行为 | CFend | CFend | CFend -> {CF_1, ..., CF_n} | |
| var x: T; | CF | |||
| break; | ||||
| continue; | ||||
| break if e; | CFend | (CF, e) => V | CFend |
CFend -> V
注: 从 CFend 到
V 的边捕获了这样一个事实:当
条件值非一致时,该 |
| return; | CF | 对于每个function 地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(请参阅§ 15.2.5 函数作用域 变量值分析) | ||
| return e; | (CF, e) => V | CF |
Value_return ->
V
对于每个function 地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(请参阅§ 15.2.5 函数作用域 变量值分析) | |
| e1 = e2; | LHSValue:
(CF, e1) => LV (CF, e2) => RV | CF |
LV -> RV
注: LV 是 值分析的结果值。 | |
| _ = e | (CF, e) => V | CF | ||
| let x = e; | (CF, e) => V | CF | ||
| var x = e; | (CF, e) => V | CF | ||
f()无实参的函数调用语句 | 调用函数调用分析: (CF, f()) => Result | CF | ||
f(e1,...,eN)带实参的函数调用语句 | 调用函数调用分析: (CF, f(e1,...,eN)) => Result | CF |
为了此分析:
-
for循环会被脱糖(请参阅§ 9.4.4 For 语句) -
while循环会被脱糖(请参阅§ 9.4.5 While 语句) -
loop {s}被视为loop {s continuing {}} -
没有
else分支的if语句被视为具有一个空的 else 分支,即以else {}结尾 -
带有
else if分支的if语句被视为嵌套的 简单if/else语句 -
以
default开始的switch_clause 的行为 与以case _:开始的switch_clause 完全相同
为了最大化性能,实现通常会尝试最小化 非一致控制 流的数量。 但是,可以认为调用重新变得一致的位置会因 多种因素而异。WGSL 的静态分析保守地 假定当if、switch 和loop 语句的 行为为 {Next} 时,会在这些语句末尾恢复一致控制流。 前表通过将结果控制流节点 设置为与输入控制流节点相同来对此建模。
15.2.7. 函数调用的一致性规则
最复杂的规则适用于函数调用:
-
令 CF 为函数调用表达式开始处的控制流。
-
对于每个实参,使用 CF 应用对应的表达式规则。将对应的 值节点命名为arg_i
-
创建一个名为Result 的新节点
-
添加一条从Result 到 CF 的边
-
-
如果函数的调用点标签为CallSiteRequiredToBeUniform.S,则:
-
添加一条从RequiredToBeUniform.S 到 CF 的边
-
将调用点标签 的潜在触发集合成员添加到与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 同步内置 函数中的函数:
-
具有值为NoRestriction 的函数 标签。
-
具有如下调用点 标签:
-
如果不支持subgroup_uniformity,或 一致性作用域不是子组,则为CallSiteRequiredToBeUniform.error, 其潜在触发集合 由一个未命名的触发规则组成。
-
注: 该触发 规则没有名称,因此无法过滤。
-
-
-
此外,如果不支持subgroup_uniformity,或一致性作用域不是子组,则对于 对 workgroupUniformLoad 的 调用,参数
p具有值为ParameterRequiredToBeUniform.error 的参数标签, 其潜在触发集合由一个未命名的 触发规则组成。
-
-
调用 § 17.6 导数内置函数、§ 17.7.8 textureSample、§ 17.7.9 textureSampleBias 和§ 17.7.10 textureSampleCompare 中的函数:
-
具有如下调用点 标签:
-
如果不支持subgroup_uniformity,或一致性作用域不是子组
-
令 DF 为调用点位置和触发规则derivative_uniformity 的最近外围 诊断过滤器
-
如果 DF 存在,则令 S 为 DF 的新严重级别 参数。
-
如果 S 是严重级别off,则 调用点标签为CallSiteNoRestriction。
-
否则,调用点标签为CallSiteRequiredToBeUniform.S, 其潜在触发集合 由一个derivative_uniformity 元素组成。
-
-
如果不存在这样的 DF, 则调用点标签为CallSiteRequiredToBeUniform.error, 其潜在触发集合 由一个derivative_uniformity 元素组成。
-
-
-
-
具有如下函数 标签:
-
如果与
t参数对应的实参 是读写存储纹理,则为ReturnValueMayBeNonUniform
-
-
调用 § 17.12 子组内置函数或§ 17.13 四元组操作中的函数:
-
具有如下函数 标签:
-
令 DF 为调用点位置和触发规则subgroup_uniformity 的最近外围诊断 过滤器
-
具有如下调用点 标签:
-
如果不支持subgroup_uniformity,或一致性作用域是子组,则:
-
如果 DF 存在,则令 S 为 DF 的新严重级别 参数。
-
如果 S 是严重级别off,则 调用点标签为CallSiteNoRestriction。
-
否则,调用点标签为CallSiteRequiredToBeUniform.S, 其潜在触发集合 由一个subgroup_uniformity 元素组成。
-
-
如果不存在这样的 DF, 则调用点标签为CallSiteRequiredToBeUniform.error, 其潜在触发集合 由一个subgroup_uniformity 元素组成。
-
-
-
此外,对于调用subgroupShuffleUp 或subgroupShuffleDown 的情况, 参数
delta具有如下参数标签:-
如果不支持subgroup_uniformity,或一致性作用域是子组,则:
-
如果 DF 存在,则令 S 为 DF 的新严重级别 参数。
-
如果 S 是严重级别off,则 参数标签为NoRestriction。
-
否则,参数标签为ParameterRequiredToBeUniform.S, 其潜在触发集合 由一个subgroup_uniformity 元素组成。
-
-
如果不存在这样的 DF, 则参数标签为ParameterRequiredToBeUniform.error, 其潜在触发集合 由一个subgroup_uniformity 元素组成。
-
-
否则为NoRestriction。
-
-
此外,对于调用subgroupShuffleXor 的情况, 参数
mask具有如下参数标签:-
如果不支持subgroup_uniformity,或一致性作用域是子组,则:
-
如果 DF 存在,则令 S 为 DF 的新严重级别 参数。
-
如果 S 是严重级别off,则 参数标签为NoRestriction。
-
否则,参数标签为ParameterRequiredToBeUniform.S, 其潜在触发集合 由一个subgroup_uniformity 元素组成。
-
-
如果不存在这样的 DF, 则参数标签为ParameterRequiredToBeUniform.error, 其潜在触发集合 由一个subgroup_uniformity 元素组成。
-
-
否则为NoRestriction。
-
-
注: WGSL 实现将确保,如果函数调用之前的控制 流对于某个特定作用域是一致的, 那么函数调用之后它也将保持一致。
15.2.8. 表达式的一致性规则
分析表达式的规则将表达式本身以及与其开始处控制流对应的节点(下文记作“CF”)作为参数,并返回以下内容:
-
与其值对应的节点
-
要添加到图中的一组新节点和边
| 表达式 | 新节点 | 递归分析 | 结果值节点 | 新边 |
|---|---|---|---|---|
| e1 || e2 | (CF, e1) => V1 (V1, e2) => V2 | V2 | ||
| e1 && e2 | ||||
| 字面量 | CF | |||
| 标识符解析到 函数作用域变量 "x", 其中该标识符作为内存视图表达式 MVE 的根标识符出现,并且在 类型检查期间对 MVE 调用了加载规则 | Result | X 是与包含此表达式的语句输入处 "x" 的值对应的节点 | Result |
Result -> {CF, X}
注: X 等价于
"x" 的 Vout(prev) |
| 标识符解析到 函数作用域变量 "x", 其中 "x" 是脱糖后的指针参数 i,并且 该标识符作为内存视图表达式 MVE 的根标识符出现,并且在 类型检查期间未对 MVE 调用加载规则 | param_i | |||
| 标识符解析到 函数作用域变量 "x", 其中该标识符作为内存视图表达式 MVE 的根标识符出现,并且在 类型检查期间未对 MVE 调用加载规则 | CF | |||
| 标识符解析到const 声明、override 声明、 let 声明,或非指针类型的非内置形式参数 "x" | Result | X 是与 "x" 对应的节点 | Result | Result -> {CF, X} |
| 标识符解析到 storage、workgroup 或private地址空间中具有非只读 访问模式的指针类型形式 参数,其中该标识符作为 内存视图 表达式 MVE 的根标识符出现,并且 在类型检查期间对 MVE 调用了加载规则 | MayBeNonUniform | |||
| 标识符解析到 storage、workgroup 或private地址空间中具有非只读 访问模式的指针类型形式 参数,其中该标识符作为 内存视图 表达式 MVE 的根标识符出现,并且在类型检查期间未对 MVE 调用加载规则 | CF | |||
| 标识符解析到 function 之外的地址空间中具有只读访问模式的指针类型形式 参数 | CF | |||
| 标识符解析到 一致的内置值 "x" | CF | |||
| 标识符解析到 非一致的内置值 "x" | MayBeNonUniform | |||
| 标识符解析到 只读模块作用域变量 "x" | CF | |||
| 标识符解析到 非只读模块作用域 变量 "x",其中该标识符作为内存视图 表达式 MVE 的根标识符出现,并且在 类型检查期间对 MVE 调用了加载规则 | MayBeNonUniform | |||
| 标识符解析到 非只读模块作用域 变量 "x",其中该标识符作为内存视图 表达式 MVE 的根标识符出现,并且在 类型检查期间未对 MVE 调用加载规则 | CF | |||
| ( e ) | (CF, e) => V | V | ||
| op e, 其中 op 是一元运算符 | ||||
| e.field | ||||
| e1 op e2, 其中 op 是非短路二元 运算符 | Result | (CF, e1) => V1 (CF, e2) => V2 | Result | Result -> {V1, V2} |
| e2[e1] | ||||
f()无实参的函数调用表达式 | 调用函数调用分析: (CF, f()) => Result | Result | ||
f(e1,...,eN)带实参的函数调用表达式 | 调用函数调用分析: (CF, f(e1,...,eN)) => Result | Result |
以下内置输入变量被视为一致:
在子组 一致性作用域中,以下内置 输入变量也被视为一致:
所有其他内置输入(请参阅内置 值)都被视为非一致。
注: 作者应避免将一致的 内置值与其他非一致输入分组在一起,因为该分析不会分别分析复合类型的分量。
| 表达式 | 新节点 | 递归分析 | 结果变量节点 | 新边 |
|---|---|---|---|---|
| 标识符解析到 函数作用域变量 "x" | Result | X 是与包含此表达式的语句输出处 "x" 的值对应的节点。 | Result |
Result -> {CF, X}
注: X 等价于
"x" 的 Vin(next) |
| 标识符解析到 const 声明、override 声明、 let 声明或形式参数 "x" | X 是与 "x" 对应的节点 | X | ||
| 标识符解析到 模块作用域变量 "x" | MayBeNonUniform | |||
| e.field | LHSValue: (CF, e) => L1 | L1 | ||
| *e | ||||
| &e | ||||
| e2[e1] | (CF, e1) => V1 LHSValue: (CF, e2) => L2 | L2 | L2 -> V1 | |
f()无实参的函数调用表达式 | 调用函数调用分析: LHSValue: (CF, f()) => Result | Result | ||
f(e1,...,eN)带实参的函数调用表达式 | 调用函数调用分析: LHSValue: (CF, f(e1,...,eN)) => Result | Result |
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 缓冲区中读取相同的值),程序也
是有效的。
15.2.10.2. 函数作用域变量一致性
此示例同时展示了一个有效和一个无效的
屏障函数调用,它们依赖于
函数作用域变量的值。
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 { // 无效的屏障函数调用。 workgroupBarrier (); } x = b ; if x < 0 { // 有效的屏障函数调用。 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 { // 有效的屏障函数调用。 workgroupBarrier (); } }
15.2.10.4. 循环中的一致性
在此示例中,循环内存在一个无效的 workgroupBarrier 函数调用。
非一致内置值 local_invocation_index 是最终原因,
尽管它出现在循环中的屏障之后。
之所以如此,是因为在后续迭代中,工作组中的一些调用
会提前退出循环,而其他调用则尝试执行
屏障。
分析将迭代间依赖关系建模为一条边,其中循环体开始处的控制
(CF_loop_body)依赖于循环体
结束处的控制流(CF_after_if)。
@compute @workgroup_size ( 16 , 1 , 1 ) fn main ( @builtin ( local_invocation_index) lid : u32) { for ( var i = 0u ; i < 10 ; i ++ ) { workgroupBarrier (); if ( lid + i ) > 7 { break ; } } }
15.2.10.5. 用户定义函数调用
此示例是对第一个示例的修改,但
使用了用户定义函数调用。
分析将 scale 的两个参数的参数返回标签都设置为
ParameterReturnContentsRequiredToBeUniform。
这会在 main 中形成一条路径,连接 scale
函数调用的返回值与 position 内置值。
该路径是从RequiredToBeUniform.S 到
MayBeNonUniform
的整体无效路径的子路径。
fn scale ( in1 : f32, in2 : f32) -> f32{ let v = in1 / in2 ; return v ; } @group ( 0 ) @binding ( 0 ) var t : texture_2d< f32> ; @group ( 0 ) @binding ( 1 ) var s : sampler; @fragment fn main ( @builtin ( position) pos : vec4< f32> ) { let tmp = scale ( pos . x , 0.5 ); if tmp > 1.0 { _ = textureSample ( t , s , pos . xy ); } }
注: 子图仅为便于理解而包含在示例中。
15.3. 计算着色器和工作组
工作组是一组并发 执行计算着色器阶段入口点的调用, 并共享对workgroup 地址空间中着色器变量的访问。
计算着色器的工作组网格是 具有整数坐标 (i,j,k) 且满足以下条件的点集合:
-
0 ≤ i < workgroup_size_x
-
0 ≤ j < workgroup_size_y
-
0 ≤ k < workgroup_size_z
其中 (workgroup_size_x, workgroup_size_y, workgroup_size_z) 是 为入口点的workgroup_size 属性指定的值。
工作组网格中的每个点在工作组中恰好有一个调用。
调用的局部 调用 ID是与该调用对应的工作组网格点的坐标 三元组 (i,j,k)。
当调用具有局部调用 ID 时,其 局部调用 索引为:
i + (j × workgroup_size_x) + (k × workgroup_size_x × workgroup_size_y)
请注意,如果一个工作组有 W 个调用, 那么工作组中的每个调用 I 都有唯一的局部调用索引 L(I), 使得 0 ≤ L(I) < W, 并且整个范围都被覆盖。
当 WebGPU 实现从队列中移除分派命令并开始在 GPU 上执行指定工作时范围都被覆盖。
当 WebGPU 实现从队列中移除分派命令并开始在 GPU 上执行指定工作时, 计算着色器开始执行。 分派命令指定一个分派大小, 它是一个整数三元组(group_count_x、group_count_y、group_count_z), 表示要执行的工作组数量,如下所述。
特定分派的计算着色器 网格是具有整数坐标 (CSi,CSj,CSk) 且满足以下条件的点集合:
-
0 ≤ CSi < workgroup_size_x × group_count_x
-
0 ≤ CSj < workgroup_size_y × group_count_y
-
0 ≤ CSk < workgroup_size_z × group_count_z
其中 workgroup_size_x、 workgroup_size_y 和 workgroup_size_z 如上所述,由计算着色器入口点指定。
计算着色器分派要执行的工作,是对计算着色器网格中的每个点恰好 执行一次入口点调用。
调用的全局 调用 ID是与该调用对应的计算着色器网格点的坐标 三元组。
调用被组织到工作组中,因此每个调用的 全局调用 ID (CSi, CSj, CSk) 映射到 一个工作组,该工作组由工作组 ID标识:
( ⌊ CSi ÷ workgroup_size_x ⌋, ⌊ CSj ÷ workgroup_size_y ⌋, ⌊ CSk ÷ workgroup_size_z ⌋)
以及该工作组内的一个调用,由局部调用 ID标识:
( CSi mod workgroup_size_x , CSj mod workgroup_size_y , CSk mod workgroup_size_z ).
注: 工作组 ID 的范围从 (0,0,0) 到 (group_count_x - 1, group_count_y - 1, group_count_z - 1)。
如果支持linear_indexing 特性,那么当一个 工作组具有工作组 ID (WGi, WGj, WGk) 时,其 工作组索引为:
WGi + ( WGj × group_count_x ) + ( WGk × group_count_x × group_count_y )
如果支持linear_indexing 特性,那么当一个 调用具有全局调用 ID (CSi, CSj, CSk) 时,其 全局调用 索引为:
CSi + ( CSj × workgroup_size_x × group_count_x ) + ( CSk × workgroup_size_x × group_count_x × workgroup_size_y × group_count_y )
WebGPU 不对以下事项提供任何保证:
-
不同工作组中的调用是否并发执行。 也就是说,不能假设一次会执行多个工作组。
-
一旦某个工作组中的调用开始执行,其他工作组 是否会被阻止执行。 也就是说,不能假设一次只执行一个工作组。 当一个工作组正在执行时,实现也可以选择 并发执行其他工作组,或其他已排队但未被阻塞的工作。
-
某个特定工作组中的调用是否会先于 另一个工作组中的调用开始执行。 也就是说,不能假设工作组按特定顺序启动。
15.4. 片段着色器和辅助调用
片段着色器阶段中的调用按 X 和 Y 维度中相邻位置划分为 2x2 调用网格。 每个这样的网格称为一个四元组。 四元组可以在某些集体操作中协作(请参阅§ 15.6.2 导数)。 调用的四元组 调用 ID是在 四元组内的唯一 ID,其中:
-
ID 0 是左上调用。
-
ID 1 是右上调用。
-
ID 2 是左下调用。
-
ID 3 是右下调用。
注: 没有用于四元组 ID 的内置值访问器。
通常,片段处理会为 光栅化产生的每个 RasterizationPoint 创建一个片段着色器调用。 有时可能没有足够的 RasterizationPoint 来完全填满一个 四元组,例如在图形图元的边缘。 当四元组中只有 1、2 或 3 个与 RasterizationPoint 对应的调用时,片段处理将为 四元组中每个未填充的位置创建一个辅助调用。
辅助调用具有有限的可观察效果。 它们帮助计算导数,并且可以参与 子组操作。 因此,辅助调用受以下限制:
-
不会在storage 或handle 地址空间上执行任何写访问(另请参阅 § 14.5.1 内存操作) 。
-
入口点的返回值将不会在GPURenderPipeline 中由下游进一步处理。
如果四元组中的所有调用都变为辅助调用(例如由于 执行了discard 语句),则四元组的执行可能被 终止;但是,这种终止不被视为产生非一致控制流。
15.5. 子组
子组是一组并发 执行计算或片段着色器阶段入口点的调用,并且可以高效地共享 数据并共同计算结果。 计算着色器或片段着色器中的每个调用恰好属于一个子组。 在计算着色器中,每个子组都是特定工作组的子集。 在片段着色器中,一个子组可能包含来自多个绘制命令的调用。 每个四元组都将包含在单个 子组中。
子组大小是 一个子组中调用的最大数量。 在着色器内,可通过subgroup_size 内置值访问该值。 子组大小在一个分派命令内是一致 值,因此在一个工作组内也是一致值, 但在一个绘制命令内可能不是一致值。 所有子组大小都是范围 [4, 128] 内的 2 的幂,并且为特定设备编译的着色器 所使用的值将位于WebGPU § 4.3 GPUAdapter 的 [subgroupMinSize, subgroupMaxSize] 范围内。 实际大小取决于着色器、设备属性和设备编译器。 每个设备支持可能子组大小范围的一个子集(可能只有一个值)。 设备编译器使用多种启发式方法从支持的大小中选择一个大小。 每个子组包含的调用数量可能少于报告的子组大小 (例如,启动的调用少于子组大小时)。
调用的子组调用
ID是子组内的唯一 ID。
可通过subgroup_invocation_id
内置值访问该 ID,其范围为 [0, subgroup_size - 1]。
如果支持subgroup_id 特性,则在 计算着色器内,子组 ID是在工作组内标识 子组的唯一 ID。 可通过subgroup_id 内置值 访问该 ID,其范围为 [0, num_subgroups - 1]。
子组值(即
subgroup_invocation_id 和 subgroup_id)与local_invocation_index 之间没有定义的关系。
为避免不可移植的代码,着色器作者不应假设这两个值之间存在特定
映射。
当同一子组中的调用执行不同的控制流路径时,我们称 子组执行发生了分歧。 这是非一致控制流的一种特殊情况。 分歧会影响子组操作的语义。 子组中并发执行某个子组操作的调用对于该操作而言是活跃的。 子组中的其他调用对于该操作而言是非活跃的。 当子组大小超过子组中的调用数量时,多出的 假想调用被视为非活跃。 辅助调用在某个操作中可能 是活跃的,也可能是非活跃的。 也就是说,在某些设备上辅助调用可能参与子组操作, 而在另一些设备上则可能不参与。
注: 在非一致控制流中运行时, 底层设备之间存在相当大的不可移植性,并且设备编译器通常会积极 优化此类代码。 结果是,子组可能包含与着色器作者预期不同的一组活跃 调用。
15.6. 集体操作
15.6.1. 屏障
屏障是一个同步内置函数, 用于对程序中的内存操作进行排序。 控制屏障由同一工作组中的所有调用执行, 就像它们并发执行一样。 因此,控制屏障必须仅在计算着色器中的一致控制流中执行。
15.6.2. 导数
偏导数 是一个值沿某个轴的变化率。 同一四元组内的片段着色器调用 协作计算近似偏导数。
计算导数的内置函数包括:
以下内置函数在操作过程中会隐式计算片段坐标的偏导数:
对于这些函数,导数有助于确定要采样的纹素的 mip 级别,或者对于
textureSampleCompare,确定要采样并与参考值比较的纹素。
调用指定值的偏导数由 § 17.6 导数内置 函数中描述的内置函数计算:
-
dpdx、dpdxCoarse 和dpdxFine 计算沿 x 轴的偏导数。
-
dpdy、dpdyCoarse 和dpdyFine 计算沿 y 轴的偏导数。
-
fwidth、fwidthCoarse 和fwidthFine 计算相关 x 和 y 偏导数上的曼哈顿度量。
由于相邻调用协作计算导数,这些 函数应仅在片段着色器的一致控制流中调用。 对这些函数的每次调用,如果 一致性分析无法证明该调用发生在一致控制流中,则会触发一个derivative_uniformity诊断。
如果其中一个函数在非一致控制流中被调用,则结果是不确定 值。
注: 导数是一种隐式的四元组操作。 使用它们不要求subgroups 扩展。
15.6.3. 子组操作
子组内置函数允许 子组中的调用之间进行高效通信和计算。 子组操作是单指令多线程(SIMT)操作。
子组中的活跃调用 相互通信以确定结果。 因此,当所有调用都处于活跃状态时调用这些函数,可以最大化可移植性 (即在子组级别的一致控制流中)。
15.6.4. 四元组操作
四元组内置函数作用于一个调用四元组。 它们可用于四元组中的数据通信。
四元组中的活跃调用 相互通信以确定结果。 因此,当所有调用都处于活跃状态时调用这些函数,可以最大化可移植性 (即在四元组级别的一致控制流中)。
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 是次正规值。 (非正规化是次正规的同义词。)
否则,指数域既不全为 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 的最低浮点值,要么是 小于 x 的最高浮点值; 舍入模式决定选择其中哪一个。
通常,输入为NaN 的操作会产生 NaN 输出。 例外包括:
-
NaN 永远不等于、小于或大于任何其他浮点值。此类比较 会产生 false。
IEEE-754 定义了五种异常:
-
无效操作。 当操作在其定义域之外的扩展实数输入上求值时,会发生这种情况。 此类操作产生 NaN。 无效操作的示例有 0 × +∞ 和
sqrt(−1)。 -
除以零。 当对有限操作数执行的操作被定义为具有精确的无穷大结果时,会发生这种情况。 示例包括 1 ÷ 0 和 log(0)。
-
溢出。当中间结果超出该类型的 有限范围时会发生这种情况。 请参阅§ 15.7.3 浮点舍入和 溢出。
-
不精确。当舍入后的结果不同于中间 结果, 或发生溢出时,会发生这种情况。
15.7.2. 与 IEEE-754 的差异
WGSL 遵循IEEE-754 标准,但存在以下差异:
-
将浮点值 x转换 为整数类型时, 首先将 x 限制到目标类型的值范围内。请参阅§ 15.7.6 浮点转换。
-
不会生成浮点异常。
-
不得生成信令 NaN。 在中间计算中,任何信令 NaN 都可以转换为静默 NaN。
-
有限数学 假设:
-
在着色器执行之前生成的溢出、 无穷大和 NaN 将产生 错误。
-
针对有限值的const 表达式和override 表达式 将按照 IEEE-754 规则生成溢出、无穷大 和 NaN 作为中间结果值。
-
注: 此规则要求 实现可靠地检测这些类型表达式中的溢出、无穷大和 NaN, 并达到精度限制,从而可以一致地生成错误。
-
-
如果任何浮点类型的override 表达式发生溢出或求值得到 NaN 或无穷大,则会产生管线创建错误。
-
-
实现可以假定在着色器执行期间不会出现溢出、无穷大和 NaN。
-
-
实现可以忽略浮点零值的符号域。 也就是说,带正号的零可以表现得像带负号的零,反之亦然。
-
刷新为零是将浮点类型的次正规值 替换为该类型的零值。
-
§ 15.7.4 浮点 精度中列出的操作的任何输入或输出都可以刷新为零。
-
此外,§ 17.2 位重解释内置 函数、§ 17.9 数据打包内置 函数或 § 17.10 数据解包内置函数中列出的操作的中间结果值可以 刷新为零。
-
其他操作必须保留次正规数。
-
-
操作的精度在§ 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 位于 T 的有限范围内,则 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 的最大值。
-
也就是说,结果可以向上或向下舍入: WGSL 不指定舍入模式。
注: 浮点类型包括正无穷大和负 无穷大,因此 正确舍入的结果可以是有限值或无穷大。
注: 使用无限精度计算得到的操作结果
可能需要超出 double 精度的精度。
这种情况的一个示例是 x - y,其中 x=1.0 且 y=1.17e-38(最小的
正正规单精度浮点数)。
这些数的指数相差 126。IEEE-754 binary64(双
精度)格式的有效数字中只有 52 位,
因此在执行减法时,y 的所有有效位都会丢失。
根据舍入模式,对于这种情况以及许多其他 y 很小但非零的情况,
WGSL 表达式 x - y 可能产生与 x 相同的值。
请注意,[ECMASCRIPT] 使用等价于IEEE-754 roundTiesToEven
舍入模式的方式。
浮点数 x 的末位单位,ULP,
定义如下[Muller2005]:
-
如果
x位于浮点类型的有限范围内,则 ULP(x) 是 两个不相等的有限浮点数a和b之间的最小距离,其中a≤x≤b(即ulp(x) = mina,b|b - a|)。 -
否则,ULP(x) 为
|b - a|,其中b和a分别是最大和 第二大的 可表示有限浮点值。
操作的精度由以下五种 可能形式之一给出:
-
正确结果(对于非浮点结果值)。
-
绝对误差界。
-
以ULP 表示的相对误差界。
-
一个表达式,表示精度继承自该表达式。 也就是说,操作的精度被定义为对给定 WGSL 表达式求值的精度。 给定表达式只是该函数的一种有效实现。
对继承自表达式进行求值时,子表达式求值 受其他浮点求值规则约束,包括有关 舍入、 溢出、 重结合、 融合 和刷新为零的规则。
WebGPU 实现可以采用不同方式实现该操作,并具有更高精度 或对极端输入有更大的容忍度。
当操作的精度是在某个输入范围上指定时, 对于该范围之外的输入值,精度未定义。
如果允许的结果位于结果类型的有限范围之外,则适用§ 15.7.3 浮点舍入和 溢出中的规则。
15.7.4.1. 具体浮点表达式的精度
| 表达式 | f32 的精度 | f16 的精度 |
|---|---|---|
x + y
| 正确舍入 | |
x - y
| 正确舍入 | |
x * y
| 正确舍入 | |
x / y
| 当 |y| 位于范围 [2-126, 2126] 内时为 2.5 ULP
| 当 |y| 位于范围 [2-14, 214] 内时为 2.5 ULP
|
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)
| 当 |x| 位于范围 [2-126, 2126] 内,且
y 为有限正规值时,为 4096 ULP
| 当 |x| 位于范围 [2-14, 214] 内,且 y 为
有限正规值时,为 5 ULP
|
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 实现应提供一个在实际使用中有用的
determinant 函数。
在理想数学中,行列式使用加法、减法和乘法操作计算。 但是,GPU 使用浮点数学,而 GPU 的 determinant 实现 更偏重速度和简单性,而不是抵御溢出和误差的鲁棒性。 例如,即使是 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 | 不适用 |
unpack4x8unorm(x)
| 3 ULP | 不适用 |
unpack2x16snorm(x)
| 3 ULP | 不适用 |
unpack2x16unorm(x)
| 3 ULP | 不适用 |
unpack2x16float(x)
| 正确舍入 | 不适用 |
subgroupBroadcast(x, i)
| 正确舍入 | |
subgroupBroadcastFirst(x)
| 正确舍入 | |
subgroupAdd(x)
| 继承自子组中所有活跃调用的 x 之和 | |
subgroupExclusiveAdd(x)
| 继承自子组中所有活跃调用的 x 之和,其中这些调用的子组调用 ID小于当前 调用的 ID。 | |
subgroupInclusiveAdd(x)
| 继承自子组中所有活跃调用的 x 之和,其中这些调用的子组调用 ID小于或等于 当前调用的 ID。 | |
subgroupMul(x)
| 继承自子组中所有活跃调用的 x 之积 | |
subgroupExclusiveMul(x)
| 继承自子组中所有活跃调用的 xi 之积, 其中这些调用的子组调用 ID小于第 i 个 调用的 ID | |
subgroupInclusiveMul(x)
| 继承自子组中所有活跃调用的 xi 之积, 其中这些调用的子组调用 ID小于或等于第 i 个 调用的 ID | |
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 操作要求正确结果时,也要求正确结果。
-
fract(x)的误差继承自x - floor(x),其中中间 计算作为 AbstractFloat 操作执行。 -
否则,对应的 f32 操作的误差是绝对误差、相对误差、继承 自一种可能实现的误差,或这些误差的组合。 在这种情况下,AbstractFloat 的误差没有上界。
-
但是,AbstractFloat 操作的误差应当在绝对值意义上至多为 对应 f32 操作的误差。
-
此建议旨在避免意外情况:当表达式的类型从 f32 更改为 AbstractFloat 时,其精度不应被降低。
-
该操作可以在 WebAssembly [WASM-CORE-2] 或 ECMAScript [ECMASCRIPT] 环境中求值,而这些规范并未为许多 对应数值计算规定误差界。 例如,ECMAScript 将许多浮点操作规定为 由实现近似。 鼓励实现尽力逼近理想结果,但 并未规定严格要求。
-
AbstractFloat 值的 ULP 假定 AbstractFloat 与 IEEE-754 binary64 类型相同。
f32 值的一个 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. 重结合与融合
重结合是对 表达式中的操作进行重新排序,使得在精确计算时答案保持不变。例如:
-
(a + b) + c重结合为a + (b + c) -
(a - b) + c重结合为(a + c) - b -
(a * b) / c重结合为(a / c) * b
但是,在使用浮点数计算时,结果可能并不相同。 重结合后的结果可能由于近似而不精确,或者在计算中间结果时触发 溢出或 NaN。
实现可以对操作进行重结合。
如果转换后的表达式至少与原始形式一样精确, 实现可以融合操作。 例如,某些融合乘加实现可能比 先执行乘法再执行加法更精确。
15.7.6. 浮点转换
本节描述源类型或目标类型任一为 浮点类型时标量转换的详细信息。
在本节中,浮点类型可以是以下任一种:
-
WGSL 中的 f32、f16 和 AbstractFloat 类型。
-
一种与 IEEE-754 浮点标准定义的二进制格式相对应的假想类型。
注: 回想一下,WGSL 的 f32 类型对应于 IEEE-754 binary32 格式,而 WGSL 的 f16 类型对应于 IEEE-754 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 是 T 中等于 X 的值。
否则,X 不能在 T 中精确表示:
如果 X 位于 T 中两个相邻有限值之间, 则 XOut 是这两个值之一。 WGSL 不规定选择较高还是较低的可表示 值,并且此类转换的不同实例可以作出不同选择。
否则,X 位于目标类型的有限范围之外:
如果 X 的表达式是override 表达式,则产生管线创建错误。
否则转换按如下方式继续:
将 X' 设置为原始值 X。
如果源类型 S 是比目标类型 T 具有更多 有效数字位的浮点类型, 则源值 X 的额外有效数字位可以 被丢弃(即视为 0)。 相应地更新 X'。
如果 X' 是目标类型 T 的最大正或最大负有限值, 则设置 XOut = X'。
否则,将 XOut 设置为目标类型 T 的无穷大值,其符号与 X' 相同。
注: 一个整数值可能位于两个相邻的 可表示浮点值之间。 特别是,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 浮点精度将这些操作列为精度继承自其他表达式。 在列出其中一个操作的定义域时,要么:
-
明确定义该定义域,或
-
将定义域表述为由线性项推导,表示该定义域 通过以下方式导出:
-
假定原始操作被“继承自”表达式替换,该表达式是 浮点加法、减法和乘法操作的组合。
-
对给定参数应用并组合这些剩余操作的定义域限制。
-
例如:对两个 2 元素向量 a 和 b 的 dot(a,b) 函数,其
精度继承自表达式
a[0] * b[0] + a[1] * b[1]。
这使用了两个浮点乘法和一个浮点加法。
-
除非一个操作数为零且另一个为无穷大,否则浮点乘法在扩展实数上定义良好。
-
除非两个操作数是符号相反的无穷大,否则浮点加法定义良好。
-
因此,定义域是所有扩展实数二元素向量 a 和 b 的配对, 以下情况除外:
-
由乘法推导:
-
a[i] 为零且 b[i] 为无穷大。
-
a[i] 为无穷大且 b[i] 为零。
-
-
由加法推导:
-
a[0] × b[0] 为 +∞ 且 a[1] × b[1] 为 +∞
-
a[0] × b[0] 为 −∞ 且 a[1] × b[1] 为 −∞
-
-
16. 关键字和词法单元摘要
16.1. 关键字摘要
-
alias -
break -
case -
const -
const_assert -
continue -
continuing -
default -
diagnostic -
discard -
else -
enable -
false -
fn -
for -
if -
let -
loop -
override -
requires -
return -
struct -
switch -
true -
var -
while
16.2. 保留字
保留字是为将来使用而保留的词法单元。 WGSL 模块不得包含保留字。
以下是保留字:
| 'NULL'
| 'Self'
| 'abstract'
| 'active'
| 'alignas'
| 'alignof'
| 'as'
| 'asm'
| 'asm_fragment'
| 'async'
| 'attribute'
| 'auto'
| 'await'
| 'become'
| 'cast'
| 'catch'
| 'class'
| 'co_await'
| 'co_return'
| 'co_yield'
| 'coherent'
| 'column_major'
| 'common'
| 'compile'
| 'compile_fragment'
| 'concept'
| 'const_cast'
| 'consteval'
| 'constexpr'
| 'constinit'
| 'crate'
| 'debugger'
| 'decltype'
| 'delete'
| 'demote'
| 'demote_to_helper'
| 'do'
| 'dynamic_cast'
| 'enum'
| 'explicit'
| 'export'
| 'extends'
| 'extern'
| 'external'
| 'fallthrough'
| 'filter'
| 'final'
| 'finally'
| 'friend'
| 'from'
| 'fxgroup'
| 'get'
| 'goto'
| 'groupshared'
| 'highp'
| 'impl'
| 'implements'
| 'import'
| 'inline'
| 'instanceof'
| 'interface'
| 'layout'
| 'lowp'
| 'macro'
| 'macro_rules'
| 'match'
| 'mediump'
| 'meta'
| 'mod'
| 'module'
| 'move'
| 'mut'
| 'mutable'
| 'namespace'
| 'new'
| 'nil'
| 'noexcept'
| 'noinline'
| 'nointerpolation'
| 'non_coherent'
| 'noncoherent'
| 'noperspective'
| 'null'
| 'nullptr'
| 'of'
| 'operator'
| 'package'
| 'packoffset'
| 'partition'
| 'pass'
| 'patch'
| 'pixelfragment'
| 'precise'
| 'precision'
| 'premerge'
| 'priv'
| 'protected'
| 'pub'
| 'public'
| 'readonly'
| 'ref'
| 'regardless'
| 'register'
| 'reinterpret_cast'
| 'require'
| 'resource'
| 'restrict'
| 'self'
| 'set'
| 'shared'
| 'sizeof'
| 'smooth'
| 'snorm'
| 'static'
| 'static_assert'
| 'static_cast'
| 'std'
| 'subroutine'
| 'super'
| 'target'
| 'template'
| 'this'
| 'thread_local'
| 'throw'
| 'trait'
| 'try'
| 'type'
| 'typedef'
| 'typeid'
| 'typename'
| 'typeof'
| 'union'
| 'unless'
| 'unorm'
| 'unsafe'
| 'unsized'
| 'use'
| 'using'
| 'varying'
| 'virtual'
| 'volatile'
| 'wgsl'
| 'where'
| 'with'
| 'writeonly'
| 'yield'
16.3. 句法词法单元
句法词法单元是一串 特殊码位,用于:
-
拼写表达式运算符,或
-
作为标点符号:对其他语法元素进行分组、排序或分隔。
句法词法单元包括:
-
'&'(码位:U+0026) -
'&&'(码位:U+0026U+0026) -
'->'(码位:U+002DU+003E) -
'@'(码位:U+0040) -
'/'(码位:U+002F) -
'!'(码位:U+0021) -
'['(码位:U+005B) -
']'(码位:U+005D) -
'{'(码位:U+007B) -
'}'(码位:U+007D) -
':'(码位:U+003A) -
','(码位:U+002C) -
'='(码位:U+003D) -
'=='(码位:U+003DU+003D) -
'!='(码位:U+0021U+003D) -
'>'(码位:U+003E)(另有_greater_than用于模板消歧) -
'>='(码位:U+003EU+003D)(另有_greater_than_equal用于模板 消歧) -
'>>'(码位:U+003EU+003E)(另有_shift_right用于模板消歧) -
'<'(码位:U+003C)(另有_less_than用于 模板消歧) -
'<='(码位:U+003CU+003D)(另有_less_than_equal用于模板 消歧) -
'<<'(码位:U+003CU+003C)(另有_shift_left用于模板消歧) -
'%'(码位:U+0025) -
'-'(码位:U+002D) -
'--'(码位:U+002DU+002D) -
'.'(码位:U+002E) -
'+'(码位:U+002B) -
'++'(码位:U+002BU+002B) -
'|'(码位:U+007C) -
'||'(码位:U+007CU+007C) -
'('(码位:U+0028) -
')'(码位:U+0029) -
';'(码位:U+003B) -
'*'(码位:U+002A) -
'~'(码位:U+007E) -
'_'(码位:U+005F) -
'^'(码位:U+005E) -
'+='(码位:U+002BU+003D) -
'-='(码位:U+002DU+003D) -
'*='(码位:U+002AU+003D) -
'/='(码位:U+002FU+003D) -
'%='(码位:U+0025U+003D) -
'&='(码位:U+0026U+003D) -
'|='(码位:U+007CU+003D) -
'^='(码位:U+005EU+003D) -
'>>='(码位:U+003EU+003EU+003D)(另有_shift_right_assign用于模板 消歧) -
'<<='(码位:U+003CU+003CU+003D)(另有_shift_left_assign用于模板 消歧) -
_template_args_end-
文本:
'>'(码位:U+003E) -
该词法单元在文本上与greater_than 句法词法单元相同。
-
它由模板列表消歧生成,并用作模板 列表中的最后一个词法单元。
-
-
_template_args_start-
文本:
'<'(码位:U+003C) -
该词法单元在文本上与less_than 句法词法单元相同。
-
它由模板列表消歧生成,并用作模板 列表中的第一个词法单元。
-
-
_disambiguate_template
17. 内置函数
某些函数是预声明的, 由实现提供,因此 始终可在 WGSL 模块中使用。 这些称为内置 函数。
一个内置函数是一组名称相同的函数, 但通过其形式参数的数量、顺序和类型加以区分。 这些不同函数变体中的每一个都是一个重载。
下面通过以下内容描述每个重载:
调用内置函数时,在函数求值开始之前会先对 函数的所有实参求值。 请参阅§ 11.2 函数调用。
17.1. 构造器内置函数
值构造器 内置函数显式创建给定类型的 值。
WGSL 为所有预声明类型和所有 可构造结构体类型提供值构造器。 这种构造器内置函数的拼写与该类型相同,或者与该类型的类型别名相同。 无论在何处使用此类内置函数,该标识符 必须处于该类型或类型 别名的作用域内,并且该标识符 不得解析到其他声明。
注: frexp、 modf 和 atomicCompareExchangeWeak 返回的结构体类型无法 在 WGSL 模块中写出。
注: 该类型的值声明需要在 WGSL 文本的该语句处 有效。
WGSL 提供两种值构造器:
17.1.1. 零值内置函数
每个具体的、可构造的 T 都有
唯一的零值,
以及相应的内置函数,在 WGSL 中写作类型后跟一对空圆括号:
T ()。
抽象数值
类型也有零值,但没有用于访问它们的内置函数。
零值如下:
-
bool()是false -
i32()是 0i -
u32()是 0u -
f32()是 0.0f -
f16()是 0.0h -
类型为 T 的 N 分量向量的零值,是由 T 的零值组成的 N 分量 向量。
-
类型为 T 的 C 列 R 行矩阵的零值,是 具有这些维度并以 T 的零值填充的矩阵。
-
元素类型为 E 的可构造 N 元素数组的零值,是由 N 个 E 的零值元素组成的数组。
-
可构造结构体类型 S 的零值,是 成员均为零值的结构体值 S。
-
AbstractInt 的零值是 0。
-
AbstractFloat 的零值是 0.0。
注: WGSL 没有用于原子类型、 运行时大小数组、缓冲区 或其他不可构造类型的零值内置函数。
| 重载 |
|
| 参数化 | T 是具体的可构造类型。 |
| 描述 | 构造类型 T 的零值。
|
注: 由 AbstractInt 组成的零填充向量可以写作
vec2()、vec3() 和 vec4()。
vec2< f32> () // 两个 f32 分量组成的零值向量。 vec2< f32> ( 0.0 , 0.0 ) // 相同的值,显式写出。 vec3< i32> () // 三个 i32 分量组成的零值向量。 vec3< i32> ( 0 , 0 , 0 ) // 相同的值,显式写出。
struct Student { grade : i32, GPA : f32, attendance : array< bool, 4 > } fn func () { var s : Student ; // Student 的零值 s = Student (); // 相同的值,显式写出。 s = Student ( 0 , 0.0 , array< bool, 4 > ( false , false , false , false )); // 相同的值,使用零值成员写出。 s = Student ( i32(), f32(), array< bool, 4 > ()); }
17.1.2. 值构造器内置函数
以下小节中定义的内置函数通过以下方式创建可构造值:
-
复制相同类型的现有值(即恒等函数),或
-
从显式分量列表创建复合值。
-
从另一种值类型进行转换。
向量和矩阵形式使用各种分量和 子向量组合构造向量和矩阵值, 这些分量和子向量具有匹配的分量类型。 存在用于构造向量和矩阵的重载, 它们只指定目标类型的维度而无需 指定分量类型;分量类型从构造器实参中推断。
17.1.2.1. array
| 重载 |
|
| 参数化 | T 是具体的且可构造
|
| 描述 | 从元素构造数组。 |
| 重载 |
|
| 参数化 | T 是可构造的
|
| 描述 |
从元素构造数组。
分量类型从元素的类型推断。 数组大小由元素数量确定。 |
17.1.2.2. bool
| 重载 |
|
| 参数化 | T 是标量
类型。
|
| 描述 |
构造一个bool 值。
如果 |
17.1.2.3. f16
| 重载 |
|
| 参数化 | T 是标量类型
|
| 描述 |
构造一个f16 值。
如果 |
17.1.2.4. f32
| 重载 |
|
| 参数化 | T 是具体的标量类型
|
| 描述 |
构造一个f32 值。
如果 |
17.1.2.5. i32
| 重载 |
|
| 参数化 | T 是标量类型
|
| 描述 |
构造一个i32 值。
如果 |
17.1.2.6. mat2x2
| 重载 |
|
| 参数化 | T 是f16 或f32S 是AbstractFloat、f16 或f32
|
| 描述 |
2x2 列主序矩阵的构造器。
如果 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 | 从列向量构造 2x2 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 |
从元素构造 2x2 列主序矩阵。
与 mat2x2(vec2(e1,e2), vec2(e3,e4)) 相同。 |
17.1.2.7. mat2x3
| 重载 |
|
| 参数化 | T 是f16 或f32S 是AbstractFloat、f16 或f32
|
| 描述 |
2x3 列主序矩阵的构造器。
如果 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 | 从列向量构造 2x3 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 |
从元素构造 2x3 列主序矩阵。
与 mat2x3(vec3(e1,e2,e3), vec3(e4,e5,e6)) 相同。 |
17.1.2.8. mat2x4
| 重载 |
|
| 参数化 | T 是f16 或f32S 是AbstractFloat、f16 或f32
|
| 描述 |
2x4 列主序矩阵的构造器。
如果 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 | 从列向量构造 2x4 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 |
从元素构造 2x4 列主序矩阵。
与 mat2x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8)) 相同。 |
17.1.2.9. mat3x2
| 重载 |
|
| 参数化 | T 是f16 或f32S 是AbstractFloat、f16 或f32
|
| 描述 |
3x2 列主序矩阵的构造器。
如果 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 | 从列向量构造 3x2 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 |
从元素构造 3x2 列主序矩阵。
与 mat3x2(vec2(e1,e2), vec2(e3,e4), vec2(e5,e6)) 相同。 |
17.1.2.10.
mat3x3
| 重载 |
|
| 参数化 | T 是f16 或f32S 是AbstractFloat、f16 或f32
|
| 描述 |
3x3 列主序矩阵的构造器。
如果 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 | 从列向量构造 3x3 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 |
从元素构造 3x3 列主序矩阵。
与 mat3x3(vec3(e1,e2,e3), vec3(e4,e5,e6), vec3(e7,e8,e9)) 相同。 |
17.1.2.11.
mat3x4
| 重载 |
|
| 参数化 | T 是f16 或f32S 是AbstractFloat、f16 或f32
|
| 描述 |
3x4 列主序矩阵的构造器。
如果 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 | 从列向量构造 3x4 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 |
从元素构造 3x4 列主序矩阵。
与 mat3x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8), vec4(e9,e10,e11,e12)) 相同。 |
17.1.2.12.
mat4x2
| 重载 |
|
| 参数化 | T 是f16 或f32S 是AbstractFloat、f16 或f32
|
| 描述 |
4x2 列主序矩阵的构造器。
如果 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 | 从列向量构造 4x2 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 |
从元素构造 4x2 列主序矩阵。
与 mat4x2(vec2(e1,e2), vec2(e3,e4), vec2(e5,e6), vec2(e7,e8)) 相同。 |
17.1.2.13.
mat4x3
| 重载 |
|
| 参数化 | T 是f16 或f32S 是AbstractFloat、f16 或f32
|
| 描述 |
4x3 列主序矩阵的构造器。
如果 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 | 从列向量构造 4x3 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 |
从元素构造 4x3 列主序矩阵。
与 mat4x3(vec3(e1,e2,e3), vec3(e4,e5,e6), vec3(e7,e8,e9), vec3(e10,e11,e12)) 相同。 |
17.1.2.14.
mat4x4
| 重载 |
|
| 参数化 | T 是f16 或f32S 是AbstractFloat、f16 或f32
|
| 描述 |
4x4 列主序矩阵的构造器。
如果 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 | 从列向量构造 4x4 列主序矩阵。 |
| 重载 |
|
| 参数化 | T 是AbstractFloat、f16 或f32
|
| 描述 |
从元素构造 4x4 列主序矩阵。
与 mat4x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8), vec4(e9,e10,e11,e12), vec4(e13,e14,e15,e16)) 相同。 |
17.1.2.15. 结构体
| 重载 |
|
| 参数化 | S 是一个可构造结构体类型,其成员的类型为
T1 ... TN。
|
| 描述 | 从成员构造类型为 S 的结构体。
|
17.1.2.16. u32
| 重载 |
|
| 参数化 | T 是标量类型
|
| 描述 |
构造一个u32 值。
如果 |
|
注: 从AbstractInt
到 u32 的重载存在,是为了使诸如 |
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 是运行时大小数组的元素类型。AS 是storage、uniform
或workgroup 之一。访问模式 AM 是read
或read_write,并且
必须对 AS 有效。
注: 仅当 |
| 描述 |
返回NRuntime,即
运行时大小数组中的元素数量。
请参阅§ 13.3.4 缓冲区 绑定确定运行时大小数组的元素数量 当 |
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)的主值,以弧度表示。也就是说,近似求得满足 0 ≤ x ≤ π 且
cos(x) = e 的 x。
当 |
| 标量定义域 | 区间 [−1, 1] |
17.5.3. acosh
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回 x 的反双曲余弦(cosh-1),作为一个双曲
角。也就是说,近似求得满足 0 ≤ a ≤ +∞ 且 cosh(a) = x 的 a。
当 |
| 标量定义域 | 区间 [1, +∞] |
17.5.4. asin
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回 e 的反正弦(sin-1)的主值,以弧度表示。也就是说,近似求得满足 -π/2 ≤ x ≤ π/2 且
sin(x) = e 的 x。
当 |
| 标量定义域 | 区间 [−1, 1] |
17.5.5. asinh
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回 y 的反双曲正弦(sinh-1),作为一个双曲
角。也就是说,近似求得满足 sinh(y) =
a 的 a。
当 |
17.5.6. atan
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回 e 的反正切(tan-1)的主值,以弧度表示。也就是说,近似求得满足 − π/2 ≤ x ≤ π/2 且
tan(x) = e 的 x。
当 |
17.5.7. atanh
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回 t 的反双曲正切(tanh-1),作为一个双曲
角。也就是说,近似求得满足 tanh(a) =
t 的 a。
当 |
| 标量定义域 | 区间 [−1, 1] |
17.5.8. atan2
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回区间 [-π, π] 内以弧度表示的一个角度,其
正切为 y÷x。
结果所选择的象限取决于
注: 结果的误差没有上界:
当 |
17.5.9. ceil
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 e 的向上取整值。
当 T 是向量时逐分量
执行。
|
17.5.10. clamp
| 重载 |
|
| 参数化 | S 是 AbstractInt、AbstractFloat、i32、u32、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
将 e 的值限制在一个范围内。
如果 如果 当 如果
|
17.5.11. cos
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 e 的余弦,其中 e 以弧度表示。
当 T 是向量时逐分量
执行。
|
| 标量定义域 | 区间 (−∞, +∞) |
17.5.12. cosh
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回 a 的双曲余弦,其中 a 是一个双曲
角。
近似纯数学函数 (ea +
e−a)÷2,
但不一定以这种方式计算。
当 |
17.5.13.
countLeadingZeros
| 重载 |
|
| 参数化 | T 是 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 | 当 T 是标量类型时,从 e 的最高有效位
开始的连续 0 位数量。当 T 是向量时逐分量
执行。在某些语言中也称为“clz”。 |
17.5.14.
countOneBits
| 重载 |
|
| 参数化 | T 是 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 | e 的表示中 1 位的数量。也称为“population count”。 当 T 是向量时逐分量
执行。
|
17.5.15.
countTrailingZeros
| 重载 |
|
| 参数化 | T 是 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 | 当 T 是标量类型时,从 e 的最低有效位
开始的连续 0 位数量。当 T 是向量时逐分量
执行。在某些语言中也称为“ctz”。 |
17.5.16. cross
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f32 或 f16
|
| 描述 | 返回 e1 和 e2 的叉积。
|
| 定义域 |
由一种可能实现给出的线性项推导:
|
17.5.17. degrees
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 将弧度转换为度,近似 e1 × 180 ÷ π。
当 T 是向量时逐分量
执行
|
17.5.18.
determinant
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f32 或 f16
|
| 描述 | 返回 e 的行列式。
|
| 定义域 | 由行列式标准 数学定义中的线性项推导。 |
17.5.19. distance
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回 e1 和 e2 之间的距离(例如
length(e1 - e2))。
定义域是所有
减法 e1−e2 有效的向量
(e1,e2)。
也就是说,是所有向量的集合,但对于某个分量 |
17.5.20. dot
| 重载 |
|
| 参数化 | T 是 AbstractInt、AbstractFloat、i32、u32、f32 或 f16
|
| 描述 | 返回 e1 和 e2 的点积。
|
| 定义域 | 由各项 e1[i] × e2[i] 求和的 线性项推导。 |
17.5.21.
dot4U8Packed
| 重载 |
|
| 描述 | e1 和 e2 被解释为具有四个 8 位无符号整数
分量的向量。
返回这两个向量的无符号整数点积。
|
17.5.22.
dot4I8Packed
| 重载 |
|
| 描述 | e1 和 e2 被解释为具有四个 8 位有符号整数
分量的向量。
返回这两个向量的有符号整数点积。每个分量在执行乘法之前会进行符号扩展
到 i32,然后加法操作在 WGSL i32 中执行(加法
不会溢出,因为从数学上保证结果位于
-65024 到 65536 的范围内,而该范围位于
i32 可表示数字的范围内)。
|
17.5.23. exp
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 e1 的自然指数(例如
ee1)。
当 T 是向量时逐分量
执行。
|
17.5.24. exp2
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 2 的 e 次幂(例如 2e)。
当 T 是向量时逐分量
执行。
|
17.5.25.
extractBits(有符号)
| 重载 |
|
| 参数化 | T 是 i32 或 vecN<i32>
|
| 描述 |
从整数中读取位,并进行符号扩展。
当
T 是向量时逐分量
执行。
如果
|
17.5.26.
extractBits(无符号)
| 重载 |
|
| 参数化 | T 是 u32 或 vecN<u32>
|
| 描述 |
从整数中读取位,不进行符号扩展。
当
T 是向量时逐分量
执行。
如果
|
17.5.27.
faceForward
| 重载 |
|
| 参数化 | T 是 vecN<AbstractFloat>、vecN<f32> 或 vecN<f16>
|
| 描述 | 如果 dot(e2, e3) 为负,则返回 e1,否则返回 -e1。
|
| 定义域 | 定义域限制来自 dot(e2,e3) 操作:它们由各项
e2[i] × e3[i] 求和的线性项推导。
|
17.5.28. firstLeadingBit(有符号)
| 重载 |
|
| 参数化 | T 是 i32 或 vecN<i32>
|
| 描述 |
对于标量 T,结果为:
当 |
|
注: 由于有符号整数使用 二进制补码表示, 符号位出现在最高有效位位置。 |
17.5.29. firstLeadingBit(无符号)
| 重载 |
|
| 参数化 | T 是 u32 或 vecN<u32>
|
| 描述 |
对于标量 T,结果为:
T 是向量时逐分量
执行。
|
17.5.30.
firstTrailingBit
| 重载 |
|
| 参数化 | T 是 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 |
对于标量 T,结果为:
T 是向量时逐分量
执行。
|
17.5.31. floor
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 e 的向下取整值。
当 T 是向量时逐分量
执行。
|
17.5.32. fma
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回 e1 * e2 + e3。
当 T 是向量时逐分量
执行。
注: 名称 注:
IEEE-754
|
| 定义域 | 由表达式 e2 × e2 + e3 的线性项推导。 |
17.5.33. fract
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 e 的小数部分,按 e - floor(e) 计算。当 T 是向量时逐分量
执行。
|
|
注: 有效结果位于闭
区间 [0, 1.0] 内。
例如,如果 |
17.5.34. frexp
| 重载 |
|
| 参数化 | T 是 f32
|
| 描述 |
将 e 拆分为尾数和指数。
返回
注: 名称
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 f16
|
| 描述 |
将 e 拆分为尾数和指数。
返回
注: 名称
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 AbstractFloat
|
| 描述 |
将 e 拆分为尾数和指数。
注: 结果为 无穷大或 NaN 的 AbstractFloat 表达式会导致着色器创建错误。 返回
注: 名称
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 vecN<f32>
|
| 描述 |
将 e 的分量 ei 拆分为尾数和指数。
返回
注: 名称
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 vecN<f16>
|
| 描述 |
将 e 的分量 ei 拆分为尾数和指数。
返回
注: 名称
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 vecN<AbstractFloat>
|
| 描述 |
将 e 的分量 ei 拆分为尾数和指数。
注: 结果为 无穷大或 NaN 的 AbstractFloat 表达式会导致着色器创建错误。 返回
注: 名称
|
|
注: 不能显式地
将一个值声明为类型 |
17.5.35.
insertBits
| 重载 |
|
| 参数化 | T 是 i32、u32、vecN<i32> 或 vecN<u32>
|
| 描述 |
设置整数中的位。
当
T 是向量时逐分量
执行。
如果
|
17.5.36.
inverseSqrt
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 sqrt(e) 的倒数。
当 T 是向量时逐分量
执行。
|
| 标量定义域 | 区间 [0, +∞] |
17.5.37. ldexp
| 重载 |
|
| 参数化 |
S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> I 是 AbstractInt、i32、vecN<AbstractInt> 或 vecN<i32>当且仅当 T 是向量时,I 才是向量仅当 I 也是抽象的时,T 才能是抽象的,反之亦然
注: 如果任一参数是具体的, 则另一个参数 将(如果适用)进行自动转换为 一个具体的 类型,并且结果将是一个 具体的 类型。 |
| 描述 |
返回 e1 * 2e2,但以下情况除外:
这里,bias 是浮点格式的指数偏置: 如果 x = ldexp(frexp(x).fract, frexp(x).exp) 当 注: 名称
|
17.5.38. length
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
返回 e 的长度。如果 T 是标量,则求值结果为
e 的绝对值。如果 T 是向量
类型,则求值结果为 sqrt(e[0]2
+ e[1]2 + ...)。
注: 标量情况可以按
|
17.5.39. log
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 e 的自然对数。
当 T 是向量时逐分量
执行。
|
| 标量定义域 | 区间 [0, +∞] |
17.5.40. log2
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 e 的以 2 为底的对数。
当 T 是向量时逐分量
执行。
|
| 标量定义域 | 区间 [0, +∞] |
17.5.41.
max
| 重载 |
|
| 参数化 | S 是 AbstractInt、AbstractFloat、i32、u32、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
如果 e1 小于 e2,则返回 e2,否则返回 e1。
当 T 是向量时逐分量
执行。
如果
|
17.5.42.
min
| 重载 |
|
| 参数化 | S 是 AbstractInt、AbstractFloat、i32、u32、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 |
如果 e2 小于 e1,则返回 e2,否则返回 e1。
当 T 是向量时逐分量
执行。
如果
|
17.5.43. mix
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 e1 和 e2 的线性混合(例如
e1 * (T(1) - e3) + e2 * e3)。
当 T 是向量时逐分量
执行。
|
| 定义域 | 由表达式的线性项推导: e1[i] × (1 − e3[i]) + e2[i] × e3[i]。 e2[i] × e2[i] + e3[i]。 |
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f32 或 f16T2 是 vecN<T>
|
| 描述 | 返回 e1 和 e2 的逐分量线性混合,
对每个分量使用标量混合因子 e3。与 mix(e1, e2, T2(e3)) 相同。
|
| 定义域 | 由表达式的线性项推导: e1[i] × (1 − e3) + e2[i] × e3。 |
17.5.44. modf
| 重载 |
|
| 参数化 | T 是 f32
|
| 描述 |
将 e 拆分为小数部分和整数部分。
整数部分为 trunc( 返回
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 f16
|
| 描述 |
将 e 拆分为小数部分和整数部分。
整数部分为 trunc( 返回
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 AbstractFloat
|
| 描述 |
将 e 拆分为小数部分和整数部分。
整数部分为 trunc( 返回
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 vecN<f32>
|
| 描述 |
将 e 的各分量拆分为小数部分和整数部分。
整数部分和小数部分的第 返回
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 vecN<f16>
|
| 描述 |
将 e 的各分量拆分为小数部分和整数部分。
整数部分和小数部分的第 返回
|
|
注: 不能显式地
将一个值声明为类型 |
| 重载 |
|
| 参数化 | T 是 vecN<AbstractFloat>
|
| 描述 |
将 e 的各分量拆分为小数部分和整数部分。
整数部分和小数部分的第 返回
|
|
注: 不能显式地
将一个值声明为类型 |
17.5.45.
normalize
| 重载 |
|
| 参数化 | T 是 AbstractFloat、f32 或 f16
|
| 描述 |
返回与 e 方向相同的单位向量。
定义域是除 零向量之外的所有向量。 |
17.5.46. pow
| 重载 |
|
| 参数化 | S 是 AbstractFloat、f32 或 f16 T 是 S 或 vecN<S> |
| 描述 | 返回 e1 的 e2 次幂。
当 T 是向量时逐分量
执行。
|
| 标量定义域 |
所有扩展实数对
(x,y) 的集合,但以下情况除外:
此规则源于结果可以按
|
17.5.47.
quantizeToF16
| 重载 |
|
| 参数化 | T 是 f32 或 vecN<f32>
|
| 描述 |
将 32 位浮点值 e 量化,就像先将 e 转换为
IEEE-754 binary16
值,然后
再转换回
IEEE-754 binary32 值。
如果
中间 binary16 值可以刷新为零,即如果中间 binary16 值是次正规值,则最终 结果可能为零。 请参阅§ 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 级别以纹素计的尺寸。
| 参数化 | 重载 |
|---|---|
| ST 是i32、u32 或f32 F 是一个纹素 格式 A 是一个访问 模式 T 是 texture_1d<ST> 或 texture_storage_1d<F,A>
|
|
|
ST 是i32、u32 或f32 T 是 texture_1d<ST>
|
|
| ST 是i32、u32 或f32 F 是一个纹素 格式 A 是一个访问 模式 T 是 texture_2d<ST>、texture_2d_array<ST>、
texture_cube<ST>、
texture_cube_array<ST>、texture_multisampled_2d<ST>、
texture_depth_2d、texture_depth_2d_array、
texture_depth_cube、
texture_depth_cube_array、texture_depth_multisampled_2d、
texture_storage_2d<F,A>、texture_storage_2d_array<F,A>
或 texture_external
|
|
|
ST 是i32、u32 或f32 T 是 texture_2d<ST>、texture_2d_array<ST>、
texture_cube<ST>、
texture_cube_array<ST>、texture_depth_2d、
texture_depth_2d_array、
texture_depth_cube 或 texture_depth_cube_array
|
|
| ST 是i32、u32 或f32 F 是一个纹素 格式 A 是一个访问 模式 T 是 texture_3d<ST> 或 texture_storage_3d<F,A>
|
|
|
ST 是i32、u32 或f32 T 是 texture_3d<ST>
|
|
参数:
t
| 采样、 多重采样、深度、 存储或外部 纹理。 |
level
|
mip 级别,其中级别
0 包含纹理的全尺寸版本。 如果省略,则返回级别 0 的尺寸。 |
返回:
纹理的坐标维度。
也就是说,结果给出了逻辑纹素地址坐标的整数边界, 不包括mip 级别数量、数组大小和采样数量。
对于基于立方体的纹理,结果是立方体每个面的尺寸。 立方体面是正方形,因此结果的 x 和 y 分量相等。
如果 level 位于范围 [0, textureNumLevels(t)) 之外,则可能返回
返回类型的一个不确定
值。
17.7.2. textureGather
纹理聚集 操作从 2D、2D 数组、立方体或立方体数组纹理中读取, 并按如下方式计算一个四分量向量:
-
从mip 级别 0 中,找出在线性过滤采样操作中会使用的四个纹素:
-
使用指定的坐标、数组索引(如果存在)和偏移(如果存在)。
-
考虑其纹理空间坐标 (u,v) 时,这些纹素彼此相邻并形成一个正方形。
-
纹理边缘、立方体面边缘或立方体角处选中的纹素, 按普通纹理采样中的方式处理。
-
-
对于每个纹素,读取一个通道并将其转换为标量值。
-
对于非深度纹理,基于零的
component参数指定要使用的通道。-
如果纹理格式支持指定的通道,即具有多于
component个通道:-
当纹素值为
v时,产生标量值v[component]。
-
-
否则:
-
当
component为 1 或 2 时,产生 0.0。 -
当
component为 3(alpha 通道)时,产生 1.0。
-
-
-
对于深度 纹理,产生纹素值。(深度纹理只有一个通道。)
-
-
产生四分量向量,将上一步生成的标量按照 纹素的相对坐标排列到分量中,如下所示:
-
结果分量 相对纹素坐标 x (umin,vmax) y (umax,vmax) z (umax,vmin) w (umin,vmin)
-
这四个纹素构成采样区域,如 WebGPU 采样器描述符中所述。
| 参数化 | 重载 |
|---|---|
| C 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 A 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 A 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 A 是i32 或u32 ST 是i32、u32 或f32 |
|
| |
| |
| |
| A 是i32 或u32 |
|
| A 是i32 或u32 |
|
| A 是i32 或u32 |
|
参数:
component
|
仅适用于非深度纹理。
要从所选纹素读取的通道索引。 提供时, component 表达式必须是一个const 表达式(例如
1)。其值必须至少为 0 且至多为 3。 超出此范围的值将导致着色器创建 错误。 |
t
| 要从中读取的采样或深度纹理。 |
s
| 采样器类型。 |
coords
| 纹理坐标。 |
array_index
|
基于 0 的纹理数组索引。 此值将被限制到范围 [0, textureNumLayers(t) - 1]。
|
offset
|
在采样纹理之前应用于未归一化纹理坐标的可选纹素偏移。
此偏移在应用任何
纹理环绕模式之前应用。offset 表达式必须是一个const 表达式(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8 且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
一个四分量向量,其分量取自所选纹素的指定通道,如 上文所述。
@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 textureGat:contentReference[oaicite:0]{index=0}- n>dt , s , c ); }
17.7.3.
textureGatherCompare
纹理聚集 比较操作对深度纹理中的四个纹素执行深度比较, 并将结果收集到一个向量中,如下所示:
-
从mip 级别 0 中,找出在使用线性过滤的深度采样操作中会使用的四个纹素:
-
使用指定的坐标、数组索引(如果存在)和偏移(如果存在)。
-
考虑其纹理空间坐标 (u,v) 时,这些纹素彼此相邻并形成一个正方形。
-
纹理边缘、立方体面边缘或立方体角处选中的纹素, 按普通纹理采样中的方式处理。
-
-
对于每个纹素,与深度参考值执行比较, 根据比较采样器参数得到 0.0 或 1.0 值。
-
产生四分量向量,其中各分量是与具有 以下相对纹素坐标的纹素进行比较所得的结果:
-
结果分量 相对纹素坐标 x (umin,vmax) y (umax,vmax) z (umax,vmin) w (umin,vmin)
-
| 参数化 | 重载 |
|---|---|
| |
| |
| A 是i32 或u32 |
|
| A 是i32 或u32 |
|
| |
| A 是i32 或u32 |
|
参数:
t
| 要从中读取的深度纹理。 |
s
| 比较采样器。 |
coords
| 纹理坐标。 |
array_index
|
基于 0 的纹理数组索引。 此值将被限制到范围 [0, textureNumLayers(t) - 1]。
|
depth_ref
| 用于与采样的深度值进行比较的参考值。 |
offset
|
在采样纹理之前应用于未归一化纹理坐标的可选纹素偏移。
此偏移在应用任何
纹理环绕模式之前应用。offset 表达式必须是一个const 表达式(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8 且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
一个四分量向量,其中包含所选纹素的比较结果,如上所述。
@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
从纹理中读取单个纹素,不进行采样或过滤。
| 参数化 | 重载 |
|---|---|
| C 是i32 或u32 L 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 L 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 A 是i32 或u32 L 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 L 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 S 是i32 或u32 ST 是i32、u32 或f32 |
|
| C 是i32 或u32 L 是i32 或u32 |
|
| C 是i32 或u32 A 是i32 或u32 L 是i32 或u32 |
|
| C 是i32 或u32 S 是i32 或u32 |
|
| C 是i32 或u32 |
|
| C 是i32 或u32 AM 是read 或read_write CF 取决于存储纹素格式 F。 有关纹素格式到通道格式的映射,请参阅纹素格式 表。 |
|
| C 是i32 或u32 AM 是read 或read_write CF 取决于存储纹素格式 F。 有关纹素格式到通道格式的映射,请参阅纹素格式 表。 |
|
| C 是i32 或u32 AM 是read 或read_write A 是i32 或u32 CF 取决于存储纹素格式 F。 有关纹素格式到通道格式的映射,请参阅纹素格式 表。 |
|
| C 是i32 或u32 AM 是read 或read_write CF 取决于存储纹素格式 F。 有关纹素格式到通道格式的映射,请参阅纹素格式 表。 |
|
参数:
t
| 采样、 多重采样、 深度、 存储或 外部 纹理 |
coords
| 基于 0 的纹素坐标。 |
array_index
| 基于 0 的纹理数组索引。 |
level
| mip 级别,其中级别 0 包含纹理的全尺寸版本。 |
sample_index
| 多重采样纹理的基于 0 的采样索引。 |
返回:
未过滤的纹素数据。
如果满足以下任一条件,则逻辑纹素 地址无效:
-
coords的任一元素对于相应元素位于范围[0, textureDimensions(t, level))之外,或 -
array_index位于范围[0, textureNumLayers(t))之外,或 -
level位于范围[0, textureNumLevels(t))之外,或 -
sample_index位于范围[0, textureNumSamples(s))之外
如果逻辑纹素地址无效,则内置函数返回以下之一:
-
纹理边界内某个纹素的数据。
-
对于非深度纹理,为以下之一:
-
对于深度纹理,为 0.0。
17.7.5. textureNumLayers
返回数组化纹理的层数(元素数)。
| 参数化 | 重载 |
|---|---|
| F 是一个纹素
格式 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
| 采样、 深度 或 存储 纹理数组纹理。 |
返回:
如果纹理基于立方体,则返回立方体数组纹理中的立方体数量。
否则返回数组化纹理中的层数(同构纹素网格数量)。
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
返回多重采样纹理中每个纹素的采样数量。
| 参数化 | 重载 |
|---|---|
| ST 是i32、u32 或f32 T 是 texture_multisampled_2d<ST>
或 texture_depth_multisampled_2d
|
|
参数:
t
| 多重采样纹理。 |
返回:
17.7.8. textureSample
采样纹理。
如果一致性 分析无法证明对此函数的调用处于一致控制流中, 则会触发一个 derivative_uniformity 诊断。
| 参数化 | 重载 |
|---|---|
| |
| |
| |
| A 是i32 或u32 |
|
| A 是i32 或u32 |
|
T 是 texture_3d<f32> 或 texture_cube<f32>
|
|
| |
| A 是i32 或u32 |
|
| |
| |
| A 是i32 或u32 |
|
| A 是i32 或u32 |
|
| |
| A 是i32 或u32 |
|
参数:
t
| 要采样的采样或深度 纹理。 |
s
| 采样器类型。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的基于 0 的纹理数组索引。 此值将被限制到范围 [0, textureNumLayers(t) - 1]。
|
offset
|
在采样纹理之前应用于未归一化纹理坐标的可选纹素偏移。
此偏移在应用任何
纹理环绕模式之前应用。offset 表达式必须是一个const 表达式(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8 且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
采样值。
17.7.9. textureSampleBias
使用 mip 级别偏置采样纹理。
如果一致性 分析无法证明对此函数的调用位于一致控制流中, 则触发一个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 级别的偏置。 此值将被限制在范围 [-16.0, 15.99] 内。
|
offset
|
在采样纹理之前应用于未归一化纹理坐标的可选纹素偏移。
此偏移在应用任何
纹理环绕模式之前应用。offset 表达式必须是一个const 表达式(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8 且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
采样值。
17.7.10.
textureSampleCompare
采样深度 纹理,并将采样的深度值与参考值进行比较。
如果一致性 分析无法证明对此函数的调用位于一致控制流中, 则触发一个derivative_uniformity诊断。
| 参数化 | 重载 |
|---|---|
| |
| |
| A 是i32 或u32 |
|
| A 是i32 或u32 |
|
| |
| A 是i32 或u32 |
|
参数:
t
| 要采样的深度 纹理。 |
s
| sampler_comparison 类型。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的基于 0 的纹理数组索引。 此值将被限制到范围 [0, textureNumLayers(t) - 1]。
|
depth_ref
| 用于与采样的深度值进行比较的参考值。 |
offset
|
在采样纹理之前应用于未归一化纹理坐标的可选纹素偏移。
此偏移在应用任何
纹理环绕模式之前应用。offset 表达式必须是一个const 表达式(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8 且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
范围 [0.0..1.0] 内的值。
每个采样纹素都使用 sampler_comparison 定义的比较
运算符与参考值进行比较,从而为每个纹素得到 0 或
1
值。
如果采样器使用双线性过滤,则返回值是 这些值经过过滤后的平均值,否则返回单个 纹素的比较结果。
17.7.11.
textureSampleCompareLevel
采样深度 纹理,并将采样的深度值与参考值进行比较。
| 参数化 | 重载 |
|---|---|
| |
| |
| A 是i32 或u32 |
|
| A 是i32 或u32 |
|
| |
| A 是i32 或u32 |
|
参数:
t
| 要采样的深度 纹理。 |
s
| sampler_comparison 类型。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的基于 0 的纹理数组索引。 此值将被限制到范围 [0, textureNumLayers(t) - 1]。
|
depth_ref
| 用于与采样的深度值进行比较的参考值。 |
offset
|
在采样纹理之前应用于未归一化纹理坐标的可选纹素偏移。
此偏移在应用任何
纹理环绕模式之前应用。offset 表达式必须是一个const 表达式(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8 且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
范围 [0.0..1.0] 内的值。
textureSampleCompareLevel 函数与 textureSampleCompare 相同,但
以下方面除外:
-
textureSampleCompareLevel始终从 mip 级别 0 采样纹素。-
该函数不计算导数。
-
不要求在一致控制流中调用
textureSampleCompareLevel。
-
-
textureSampleCompareLevel可以在任何着色器阶段中调用。
17.7.12.
textureSampleGrad
使用显式梯度采样纹理。
| 参数化 | 重载 |
|---|---|
| |
| |
| A 是i32 或u32 |
|
| A 是i32 或u32 |
|
T 是 texture_3d<f32> 或 texture_cube<f32>
|
|
| |
| A 是i32 或u32 |
|
参数:
t
| 要采样的采样纹理。 |
s
| 采样器。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的基于 0 的纹理数组索引。 此值将被限制到范围 [0, textureNumLayers(t) - 1]。
|
ddx
| 用于计算采样位置的 x 方向导数向量。 |
ddy
| 用于计算采样位置的 y 方向导数向量。 |
offset
|
在采样纹理之前应用于未归一化纹理坐标的可选纹素偏移。
此偏移在应用任何
纹理环绕模式之前应用。offset 表达式必须是一个const 表达式(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8 且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
采样值。
17.7.13.
textureSampleLevel
使用显式 mip 级别采样纹理。
| 参数化 | 重载 |
|---|---|
| |
| |
| |
| A 是i32 或u32 |
|
| A 是i32 或u32 |
|
T 是 texture_3d<f32> 或 texture_cube<f32>
|
|
| |
| A 是i32 或u32 |
|
| L 是i32 或u32 |
|
| L 是i32 或u32 |
|
| A 是i32 或u32 L 是i32 或u32 |
|
| A 是i32 或u32 L 是i32 或u32 |
|
| L 是i32 或u32 |
|
| A 是i32 或u32 L 是i32 或u32 |
|
参数:
t
| 要采样的采样或深度纹理。 |
s
| 采样器类型。 |
coords
| 用于采样的纹理坐标。 |
array_index
|
要采样的基于 0 的纹理数组索引。 此值将被限制到范围 [0, textureNumLayers(t) - 1]。
|
level
|
mip 级别,其中级别 0 包含纹理的全尺寸版本。
对于 level 为 f32 的函数,如果格式根据
纹理格式能力可过滤,则小数值可以
在两个级别之间进行插值。
|
offset
|
在采样纹理之前应用于未归一化纹理坐标的可选纹素偏移。
此偏移在应用任何
纹理环绕模式之前应用。offset 表达式必须是一个const 表达式(例如
vec2<i32>(1, 2))。每个 offset 分量必须至少为 -8 且至多为
7。超出
此范围的值将导致着色器创建错误。
|
返回:
采样值。
17.7.14.
textureSampleBaseClampToEdge
在纹理视图的基础级别采样, 并按下述方式将纹理坐标限制到边缘。
| 参数化 | 重载 |
|---|---|
T 是 texture_2d<f32> 或 texture_external
|
|
参数:
t
| 要采样的采样或外部纹理。 |
s
| 采样器类型。 |
coords
|
用于采样的纹理坐标。
在采样之前,给定坐标将被 限制到矩形
其中
注: 半纹素调整确保
无论采样器的 |
返回:
采样值。
17.7.15. textureStore
向纹理写入单个纹素。
| 参数化 | 重载 |
|---|---|
| F 是一个纹素
格式 C 是i32 或u32 AM 是write 或read_write CF 取决于存储纹素格式 F。 有关纹素格式到通道格式的映射,请参阅纹素格式 表。 |
|
| F 是一个纹素
格式 C 是i32 或u32 AM 是write 或read_write CF 取决于存储纹素格式 F。 有关纹素格式到通道格式的映射,请参阅纹素格式 表。 |
|
| F 是一个纹素
格式 C 是i32 或u32 AM 是write 或read_write A 是i32 或u32 CF 取决于存储纹素格式 F。 有关纹素格式到通道格式的映射,请参阅纹素格式 表。 |
|
| F 是一个纹素
格式 C 是i32 或u32 AM 是write 或read_write CF 取决于存储纹素格式 F。 有关纹素格式到通道格式的映射,请参阅纹素格式 表。 |
|
参数:
t
| 只写存储纹理或 读写存储纹理 |
coords
|
基于 0 的纹素坐标。 |
array_index
| 基于 0 的纹理数组索引。 |
value
|
新的纹素值。
value 使用逆通道传递函数进行转换。
|
注:
如果满足以下任一条件,则逻辑纹素 地址无效:
-
coords的任一元素对于相应元素位于范围[0, textureDimensions(t))之外,或 -
array_index位于范围[0, textureNumLayers(t))之外
如果逻辑纹素地址无效,则内置函数将不会执行。
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所指向的原始值。 -
使用值 v 执行函数名称中的操作(例如 max),以获得新值。
-
使用
atomic_ptr存储新值。
每个函数都返回操作之前存储在原子对象中的原始值。
17.8.3.1. atomicAdd
fn atomicAdd ( atomic_ptr : ptr< AS , atomic< T > , read_write> , v : T ) -> T
使用值 v 对 atomic_ptr 所指向的原子对象
以原子方式执行加法操作,并返回操作之前存储在原子对象中的原始
值。
// 所有操作均以原子方式执行 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
使用值 v 对 atomic_ptr 所指向的原子对象
以原子方式执行减法操作,并返回操作之前存储在原子对象中的原始
值。
// 所有操作均以原子方式执行 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
使用值 v 对 atomic_ptr 所指向的原子对象
以原子方式执行最大值操作,并返回操作之前存储在原子对象中的原始
值。
// 所有操作均以原子方式执行 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
使用值 v 对 atomic_ptr 所指向的原子对象
以原子方式执行最小值操作,并返回操作之前存储在原子对象中的原始
值。
// 所有操作均以原子方式执行 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
使用值 v 对 atomic_ptr 所指向的原子对象
以原子方式执行按位 AND 操作,并返回操作之前存储在原子对象中的原始
值。
// 所有操作均以原子方式执行 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
使用值 v 对 atomic_ptr 所指向的原子对象
以原子方式执行按位 OR 操作,并返回操作之前存储在原子对象中的原始
值。
// 所有操作均以原子方式执行 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
使用值 v 对 atomic_ptr 所指向的原子对象
以原子方式执行按位 XOR 操作,并返回操作之前存储在原子对象中的原始
值。
// 所有操作均以原子方式执行 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 中。
|
17.9.8.
pack2x16unorm
| 重载 |
|
| 描述 | 将两个归一化浮点值转换为 16 位无符号整数,然后将
它们
组合为一个 u32 值。输入的分量 e[i] 被转换为 16 位无符号整数值
⌊ 0.5 + 65535 × min(1, max(0, e[i])) ⌋,随后被放置在结果的位
16 × i 到
16 × i + 15 中。
|
17.9.9.
pack2x16float
| 重载 |
|
| 描述 |
将两个浮点值转换为半精度浮点数,然后将
它们组合为一个 u32 值。输入的分量 e[i] 被转换为IEEE-754 binary16
值,随后被
放置在结果的位
16 × i 到
16 × i + 15 中。
请参阅§ 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. 子组内置函数
请参阅§ 15.6.3 子组操作。
对这些函数的调用:
注: 对于计算着色器阶段,一致控制流的作用域是 工作组。 对于片段 着色器阶段,一致控制流的作用域是 绘制命令。 这两个作用域都大于子组。
17.12.1.
subgroupAdd
| 重载 |
|
| 前置条件 | T 是具体的数值标量或具体的数值向量
|
| 描述 |
归约操作。 |
17.12.1.1. subgroupExclusiveAdd
| 重载 |
|
| 前置条件 | T 是具体的数值标量或具体的数值向量
|
| 描述 |
排他前缀扫描操作。 返回子组中所有活动调用里,子组调用 ID 小于当前
调用 ID 的调用的 对于活动调用中 ID 最小的调用,返回的值为
|
17.12.1.2. subgroupInclusiveAdd
| 重载 |
|
| 前置条件 | T 是具体的数值标量或具体的数值向量
|
| 描述 |
包含式前缀扫描操作。 返回子组中所有活动调用里,子组调用 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 分量的位位置 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
|
| 描述 |
将四元组中四元组调用
ID
与 id 匹配的调用的 e 值返回给四元组中的所有活动调用。
注: 与subgroupBroadcast 不同,目前 没有非常量替代方案。 |
17.13.2.
quadSwapDiagonal
| 重载 |
|
| 前置条件 | T 是具体的数值标量或具体的数值向量
|
| 描述 |
返回四元组中具有相对坐标的调用的 e 值。
即:
|
17.13.3.
quadSwapX
| 重载 |
|
| 前置条件 | T 是具体的数值标量或具体的数值向量
|
| 描述 |
返回四元组中共享相同 X 维度的调用的 e
值。
即:
|
17.13.4.
quadSwapY
| 重载 |
|
| 前置条件 | T 是具体的数值标量或具体的数值向量
|
| 描述 |
返回四元组中共享相同 Y 维度的调用的 e
值。
即:
|
17.14. 缓冲区视图内置函数
这些函数都要求buffer_view 语言功能。
这些函数操作指向缓冲区的指针。 bufferView 和 bufferArrayView 将 缓冲区中的不透明数据重新解释为另一种主机可共享数据类型。 然后,结果指针可以像其他指针一样使用。
将 ArrayOffset(T) 定义为:
-
OffsetOfMember(T, lastMemberIndex),如果 T 是一个结构体, 且其 最后一个成员是运行时大小数组(array<E>)
-
否则为 0
将 MinTypeSize(T) 定义为:
-
ArrayOffset(T) + StrideOf(array<E>) ,如果 T 是一个结构体, 且其最后一个成员是运行时大小数组
由于传递给函数调用的缓冲区指针实参可以经过自动 转换为参数的类型,因此实现将 使用过程间分析来检查无效内存引用 并确保结果正确。 用于任何缓冲区视图内置函数的缓冲区大小,是在与该调用一起使用的特定起源变量所遇到的最小 缓冲区大小。 由于这是一个动态概念,实现将确保对于内置函数的特定实例正确确定该值。 该大小将是以下值中的最小值:
注: 如果实现不支持 unrestricted_pointer_parameters 语言功能,则 内置函数实参的根标识符必须是 起源 变量。
注: 当缓冲区大小被传递给函数时,不能变得更大。
17.14.1.
bufferView
| 重载 |
|
| 前置条件 |
AS 是storage、uniform 或
workgroup 之一。AM 是 AS 的有效访问模式。 |
| 描述 |
将从 p 开头起 offset 字节处的内存
视图重新解释为类型
T。
也就是说,如果与 p 关联的内存位置位于
范围 [0, bufferLength(p)) 内,则与结果关联的内存位置
位于范围 [offset, bufferLength(p)) 内,且存储类型为 T。
如果
如果 MinTypeSize( 如果
注: 这与 uniform_buffer_standard_layout 语言功能存在交互。 |
| 重载 |
|
| 前置条件 |
AS 是storage、uniform 或
workgroup 之一。AM 是 AS 的有效访问模式。 |
| 描述 |
将从 p 开头起 offset 字节处的内存
视图重新解释为类型
T。
也就是说,如果与 p 关联的内存位置位于
范围 [0, bufferLength(p)) 内,则与结果关联的内存位置
位于范围 [offset, bufferLength(p)) 内,且存储类型为 T。
如果
如果 MinTypeSize(
如果
注: 这与 uniform_buffer_standard_layout 语言功能存在交互。 |
requires buffer_view ; @group ( 0 ) @binding ( 0 ) var < storage> in : buffer ; @group ( 0 ) @binding ( 1 ) var < storage, read_write> out : buffer ; struct S { a : mat3x3f, b : vec2f, } fn foo ( offsets : array< u32, 4 > ) { // 将部分数据作为 vec4u 读取和写入。 let p1 = bufferView < vec4u> ( & in , offsets [ 0 ]); * bufferView < vec4u> ( & out , offsets [ 1 ]) = * p1 ; // 通过结构体读取和写入部分其他数据。 let p2 = bufferView < array< S , 2 >> ( & in , offsets [ 2 ]); let v = ( * p2 )[ 1 ]. a ; * bufferView < mat3x3f> ( & out , offsets [ 3 ]) = v ; }
17.14.2.
bufferArrayView
| 重载 |
|
| 前置条件 |
AS 是storage、uniform 或
workgroup 之一。AM 是 AS 的有效访问模式。
|
| 描述 |
将从 p 开头起 offset 字节处、长度为 size
字节的内存
视图重新解释为类型 T。
也就是说,如果与 p 关联的内存位置位于
范围 [0, bufferLength(p)) 内,则与结果关联的内存位置
位于范围 [offset, offset + size) 内,且存储
类型为 T。
如果
如果
如果 MinTypeSize(
如果
如果
注: 这与 uniform_buffer_standard_layout 语言功能存在交互。 如果 (
|
| 重载 |
|
| 前置条件 |
AS 是storage、uniform 或
workgroup 之一。AM 是 AS 的有效访问模式。
|
| 描述 |
将从 p 开头起 offset 字节处、长度为 size
字节的内存
视图重新解释为类型 T。
也就是说,如果与 p 关联的内存位置位于
范围 [0, bufferLength(p)) 内,则与结果关联的内存位置
位于范围 [offset, offset + size) 内,且存储
类型为 T。
如果
如果
如果 MinTypeSize(
如果
如果 MinTypeSize(
如果
注: 这与 uniform_buffer_standard_layout 语言功能存在交互。 如果 (
|
requires buffer_view ; const stride = 32 ; const N = 4 ; const size = 2048 ; const_assert stride * N < size ; struct strided_u32 { @size ( stride ) a : u32, } @group ( 0 ) @binding ( 0 ) var < uniform> in : buffer < size > ; @group ( 0 ) @binding ( 1 ) var < storage, read_write> out : array< u32, N > ; fn foo ( offsets : u32) { // 从 in 读取带步幅数组,并无步幅地复制到 out。 var tmp : array< u32, N > ; let view = bufferArrayView < array< strided_u32 >> ( & in , offset , stride * N ); for ( var i = 0 ; i < N ; i ++ ) { tmp [ i ] = ( * view )[ i ]; } out = tmp ; }
17.14.3.
bufferLength
| 重载 |
|
| 前置条件 |
AS 是storage、uniform 或
workgroup 之一。AM 是 AS 的有效访问模式。
|
| 描述 |
返回过程间分析期间遇到的、p 所指向缓冲区的最小大小,
或者,如果只遇到运行时大小缓冲区,则返回 WebGPU GPUBuffer
的大小。
|
requires buffer_view ; requires unrestricted_pointer_parameters ; @group ( 0 ) @binding ( 0 ) var < storage> b1 : buffer < 2048 > ; @group ( 0 ) @binding ( 0 ) var < storage> b2 : buffer < 4096 > ; fn foobar ( p : ptr< storage, buffer > ) -> u32{ return bufferLength ( p ); } fn bar ( p : ptr< storage, buffer < 1024 >> ) -> u32{ return foobar ( p ); } fn foo ( p : ptr< storage, buffer < 256 >> ) -> u32{ return bufferLength ( p ); } @compute @workgroup_size ( 1 ) fn main () { let len1 = bufferLength ( & b1 ); // 2048 let len2 = bufferLength ( & b2 ); // 4096 let len3 = foo ( & b1 ); // 256 let len4 = foo ( & b2 ); // 256 let len5 = bar ( & b1 ); // 1024 let len6 = bar ( & b2 ); // 1024 let len7 = foobar ( & b1 ); // 2048 let len8 = foobar ( & b2 ); // 4096 }
18. 递归下降解析语法
本节为非规范性内容。
WGSL 语法以适用于 LALR(1) 解析器的形式规定。 实现也可能希望改用递归下降解析器。
规范性语法不能直接用于递归下降解析器,因为 其中若干规则是左递归的。 当所定义的非终结符首先出现在它的某个产生式中时,该语法规则就是直接左递归的。
以下是 WGSL 语法,但经过机械转换以:
-
消除直接和间接左递归。
-
避免空产生式。(即避免 epsilon 规则。)
-
将同级产生式之间的公共前缀合并到一起。
不过,它并不是 LL(1)。
对于某些非终结符,多个产生式具有共同的向前看集合。
例如,非终结符 attribute 的所有产生式都以 attr
token 开头。
一个更微妙的例子是 global_decl,其中三个产生式都以
attribute *
短语开头,但随后分别由 token fn、override 和 var 区分。
为简洁起见,这里没有重复许多 token 定义。 请使用本规范主体部分中的 token 定义。
'+'
| '-'
'(' ( expression ( ',' expression )* ',' ? )?
')'
| '='
| '@' 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 ',' ?
')'
| '@' 'subgroup_size' '(' expression ',' ?
')'
'&' unary_expression ( '&'
unary_expression )*
| '^' unary_expression ( '^' unary_expression )*
| '|' unary_expression ( '|' unary_expression )*
'false'
| 'true'
template_elaborated_ident.post.ident
'(' ( expression ( ',' expression )* ',' ? )?
')'
| 'default'
'.' member_ident component_or_swizzle_specifier
?
| '.' swizzle_name component_or_swizzle_specifier
?
| '[' expression ']' component_or_swizzle_specifier
?
| '%='
| '&='
| '*='
| '+='
| '-='
| '/='
| '^='
| '|='
'@' 'compute'
'@' 'const'
/0[iu]?/
| /[1-9][0-9]*[iu]?/
'(' ident_pattern_token ',' diagnostic_rule_name ','
? ')'
unary_expression bitwise_expression.post.unary_expression
| unary_expression relational_expression.post.unary_expression
| unary_expression relational_expression.post.unary_expression
'&&' unary_expression relational_expression.post.unary_expression
( '&&' unary_expression relational_expression.post.unary_expression
)*
| unary_expression relational_expression.post.unary_expression
'||' unary_expression relational_expression.post.unary_expression
( '||' unary_expression relational_expression.post.unary_expression
)*
'@' 'fragment'
'const_assert' ';'
attribute * 'fn' ident '(' ( attribute * ident
':' type_specifier ( ',' param )* ',' ? )? ')' (
'->' attribute * ident template_elaborated_ident.post.ident
)? attribute * '{' statement * '}'
| attribute * 'var' ( _template_args_start expression ( ',' expression )* ',' ? _template_args_end )? optionally_typed_ident (
'=' expression )? ';'
| global_value_decl ';'
| 'alias' ident '=' ident template_elaborated_ident.post.ident
';'
| 'struct' ident '{' attribute * member_ident ':' type_specifier ( ',' attribute * member_ident ':' type_specifier )* ',' ?
'}'
'diagnostic' '(' ident_pattern_token ',' diagnostic_rule_name
',' ? ')' ';'
| 'enable' ident_pattern_token ( ',' ident_pattern_token )* ',' ?
';'
| 'requires' ident_pattern_token ( ',' ident_pattern_token )* ',' ?
';'
attribute * 'override' optionally_typed_ident (
'=' expression )?
| 'const' optionally_typed_ident
'=' expression
'@' '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
- 必需参数
-
不适用
- 可选参数
-
无
- 编码注意事项
-
二进制
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 的实现。预计其中包括 Web 浏览器。
- 片段标识符注意事项
-
无
- 附加信息:
-
魔数:无
文件扩展名:
.wgslMacintosh 文件类型代码:
TEXT - 如需更多信息,请联系的人员及电子邮件地址:
-
David Neto,dneto@google.com,或 WGSL 中列出的编辑。
- 预期用途
-
COMMON
- 作者
-
W3C。请参阅 WGSL 中列出的编辑。
- 变更控制方
-
W3C
- 规范性参考文献
-
[WebGPU] W3C,"WebGPU” W3C 工作草案,2023 年 1 月。https://w3.org/TR/webgpu
WebGPU 着色语言 W3C,"WebGPU 着色语言" W3C 工作草案,2023 年 1 月。 https://w3.org/TR/WGSL