WebGPU 着色语言

W3C 候选推荐草案,

关于本文档的更多信息
此版本:
https://www.w3.org/TR/2025/CRD-WGSL-20250730/
最新发布版本:
https://www.w3.org/TR/WGSL/
编辑草案:
https://gpuweb.github.io/gpuweb/wgsl/
先前版本:
历史记录:
https://www.w3.org/standards/history/WGSL/
反馈:
public-gpu@w3.org 主题行为 “[WGSL] … 消息主题 …” (存档)
GitHub
编辑者:
(Google)
(Google)
前任编辑:
(Apple Inc.)
(Google)
参与方式:
提交议题 (开放议题)
测试套件:
WebGPU CTS shader/

摘要

WebGPU 的着色语言。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 发布的文档列表以及本技术报告的最新修订版可在 W3C 标准与草案索引中找到。

欢迎对本规范提供反馈和评论。关于本规范的讨论,优先推荐使用 GitHub Issues。或者,您也可以将评论发送到 Web GPU 工作组的邮件列表 public-gpu@w3.org存档)。 本草案重点列出了一些仍需在工作组中讨论的待解决问题。这些问题的结果尚未做出决定,包括其是否有效。

本文档由 Web GPU 工作组 以候选推荐标准草案的形式发布,采用了 推荐路径。本文件将在 之前,至少保持为候选推荐标准。

工作组期望在至少两个基于现代 GPU 系统 API 的主流浏览器中展示每项功能的实现。测试套件将用于编写实现报告。

作为候选推荐发布并不表示 W3C 及其成员的认可。候选推荐草案整合了工作组计划纳入后续候选推荐快照的上一版候选推荐中的变更。

本文档可随时维护和更新。本文档的部分内容仍在开发中。

本文档由遵循 W3C 专利政策的工作组编制。W3C 维护着一份与该工作组交付物相关的专利披露公开列表;该页面还包括专利披露说明。若个人实际知晓其认为包含必要声明的专利,必须按照W3C 专利政策第 6 节披露相关信息。

本文档受 2023 年 11 月 3 日 W3C 流程文档 管辖。

1. 简介

WebGPU 着色语言(WGSL)是 [WebGPU] 的着色器语言。 也就是说,使用 WebGPU API 的应用程序会用 WGSL 来编写在 GPU 上运行的程序(称为着色器)。

// 一个使用点光源对贴图几何体进行光照的片元着色器。

// 来自存储缓冲区绑定的光源。
struct PointLight {
  position : vec3f,
  color : vec3f,
}

struct LightStorage {
  pointCount : u32,
  point : array<PointLight>,
}
@group(0) @binding(0) var<storage> lights : LightStorage;

// 纹理和采样器。
@group(1) @binding(0) var baseColorSampler : sampler;
@group(1) @binding(1) var baseColorTexture : texture_2d<f32>;

// 函数参数为顶点着色器传来的值。
@fragment
fn fragmentMain(@location(0) worldPos : vec3f,
                @location(1) normal : vec3f,
                @location(2) uv : vec2f) -> @location(0) vec4f {
  // 从纹理中采样表面基础颜色。
  let baseColor = textureSample(baseColorTexture, baseColorSampler, uv);

  let N = normalize(normal);
  var surfaceColor = vec3f(0);

  // 遍历场景中的点光源。
  for (var i = 0u; i < lights.pointCount; i++) {
    let worldToLight = lights.point[i].position - worldPos;
    let dist = length(worldToLight);
    let dir = normalize(worldToLight);

    // 计算该光源对表面颜色的贡献。
    let radiance = lights.point[i].color * (1 / pow(dist, 2));
    let nDotL = max(dot(N, dir), 0);

    // 累加光照对表面颜色的贡献。
    surfaceColor += baseColor.rgb * radiance * nDotL;
  }

  // 返回累加后的表面颜色。
  return vec4(surfaceColor, baseColor.a);
}

1.1. 概述

WebGPU 以 GPU 命令 的形式向 GPU 下发一项工作任务。 WGSL 关注两种类型的 GPU 命令:

两种管线都使用 WGSL 编写的着色器。

着色器 是 WGSL 程序中在管线中执行 着色器阶段 的部分。 一个着色器包括:

注意: WGSL 程序不要求有 入口点;但此类程序无法被 API 执行,因为创建 GPUProgrammableStage 时需要入口点。

执行着色器阶段时,具体实现会:

一个 WGSL 程序结构包括:

注意: WGSL 程序目前由单个 WGSL 模块 组成。

WGSL 是命令式语言:行为通过要执行的语句序列来指定。 语句可以:

WGSL 是静态类型语言:特定表达式计算的每个值都属于特定类型,并且仅通过检查程序源码即可确定。

WGSL 有描述 布尔 和数值 (整数浮点数)的类型。 这些类型可以聚合为 复合类型向量矩阵数组结构体)。 WGSL 还有特殊类型(例如 原子类型)提供独特操作。 WGSL 以 内存视图 形式描述可以存储在 内存 中的类型。 WGSL 以 纹理和采样器 形式提供常用渲染类型。 这些类型有相应的 内建函数 用于暴露 GPU 硬件通常提供的图形渲染能力。

WGSL 没有从具体 类型的隐式转换或提升,但允许从 抽象类型隐式转换和提升。 从一种具体数值或布尔类型 转换为另一种,需要显式转换值构造器, 或位重解释; 不过,WGSL 提供有限的从标量 类型提升到向量类型的能力。 这同样适用于复合类型。

着色器阶段的工作被划分为一个或多个 调用(invocation), 每个调用都会执行入口点,但条件略有不同。 同一着色器阶段的调用共享对某些变量的访问:

但各调用针对不同的着色器阶段输入(含提供唯一标识值的内建输入)进行操作。 每个调用都拥有独立的内存空间,即 privatefunction 地址空间中的变量。

同一着色器阶段内的调用并发执行,常常是并行运行的。 着色器作者需确保该阶段调用的动态行为:

WGSL 某些特性允许多种行为实现。 这存在可移植性隐患,因为不同实现可能表现出不同行为。 WGSL 设计旨在最小化这种情况,但会受到可行性和跨设备高性能目标的约束。

行为要求 是实现处理或执行 WGSL 程序时将执行的动作。它们描述了 实现与程序员之间契约中的义务。 当义务不够明显时,规范会明确说明这些义务。

1.2. 语法表示法

下列语法表示法描述了 WGSL 句法语法的约定:

1.3. 数学术语与记号

角度

双曲角是一个无量纲面积,不是传统意义上的角度。具体如下:

则面积 a 是一个双曲角,使得 xa 的双曲余弦,ya 的双曲正弦。

正无穷,记为 +∞,是严格大于所有实数的唯一值。

负无穷,记为 −∞,是严格小于所有实数的唯一值。

扩展实数(也称仿射扩展实数)是实数集与 +∞ 和 −∞ 的并集。 计算机用浮点类型近似表示扩展实数,包括两个无穷值。 参见§ 15.7 浮点数运算

区间是有上下界的连续数集。根据上下文,可以是整数、浮点数、实数或扩展实数

向下取整表达式扩展实数 x 定义如下:

向上取整表达式扩展实数 x 定义如下:

截断函数对扩展实数 x 定义如下:

roundUp 函数对正整数 kn 定义为:

转置 一个 cr 行矩阵 A 的转置是 rc 行矩阵 AT,由 A 的各行作为 AT 的各列:

列向量的转置可看作 1 行矩阵的转置,行向量的转置可看作 1 列矩阵的转置。

2. WGSL 模块

一个 WGSL 程序由一个 WGSL 模块组成。

模块是由可选的 指令,后跟 模块作用域声明断言 组成的序列。 一个模块结构包括:

translation_unit :

global_directive * ( global_decl | global_assert | ';' ) *

global_decl :

global_variable_decl ';'

| global_value_decl ';'

| type_alias_decl ';'

| struct_decl

| function_decl

2.1. 着色器生命周期

WGSL 程序及其包含的着色器生命周期中有四个关键事件。 前两个对应于 WebGPU API 用于准备 WGSL 程序执行的方法。 后两个是着色器执行的开始和结束。

这些事件包括:

  1. 着色器模块创建

    • 当调用 WebGPU createShaderModule() 方法时发生。 WGSL 程序的源文本在此时提供。

  2. 管线创建

  3. 着色器执行开始

  4. 着色器执行结束

    • 当着色器的所有工作完成时发生:

      • 所有 调用 终止,并且

      • 所有对 资源 的访问完成,并且

      • 如有输出,传递到下游管线阶段。

事件顺序由以下原因决定:

2.2. 错误

WebGPU 实现处理着色器失败有两种原因:

着色器生命周期的三个阶段中可能出现处理错误:

注意: 例如,数据竞争可能无法检测到。

每项 行为要求 都会在最早的机会被检查。 即:

如上下文不明确,本规范会指明 未满足特定要求时,是着色器创建错误、管线创建错误还是动态错误。

错误的后果如下:

2.3. 诊断

着色器模块创建管线创建期间,实现可以生成诊断诊断是实现为应用开发者产生的信息。

当满足特定条件时会创建或触发诊断, 该条件被称为触发规则。 在源文本中满足条件的位置(以点或区间表示)被称为 触发位置

诊断具有以下属性:

诊断的严重级别有如下几种,由高到低:

错误(error)

诊断为错误。 对应于着色器创建错误管线创建错误

警告(warning)

诊断描述了值得开发者关注的异常,但不是错误。

信息(info)

诊断描述了值得开发者关注的特殊情况,但不是错误或警告。

关闭(off)

诊断被禁用,不会传递给应用。

触发规则的名称可以是:

diagnostic_rule_name :

diagnostic_name_token

| diagnostic_name_token '.' diagnostic_name_token

2.3.1. 诊断处理

已触发的诊断按如下方式处理:

  1. 对于每个诊断 D,查找包含 D 的触发位置、且具有相同触发规则的最小影响范围诊断过滤器

    • 如果存在过滤器,则应用于 D,更新 D严重级别

    • 否则 D 保持不变。

  2. 丢弃严重级别为off的诊断。

  3. 如果至少有一个剩余诊断 DI 严重级别为info,则:

    • 具有相同触发规则的其他info诊断被丢弃,仅保留原始诊断 DI

  4. 如果至少有一个剩余诊断 DW 严重级别为warning,则:

    • 具有相同触发规则的其他infowarning 诊断被丢弃,仅保留原始诊断 DW

  5. 如果至少有一个剩余诊断严重级别为error,则:

  6. 如在着色器模块创建期间处理,则剩余诊断填充 WebGPU GPUCompilationInfo 对象的 messages 成员。

  7. 如在管线创建期间处理,error诊断会导致校验GPUProgrammableStage时 WebGPU 校验失败。

注意: 这些规则允许实现一旦检测到错误就停止处理 WGSL 模块。 此外,某类警告分析可在发现第一个警告时停止,某类 info 诊断分析可在第一次出现时停止。 WGSL 未规定不同类型分析的执行顺序,或单一分析内的顺序。 因此,对同一个 WGSL 模块,不同实现可能报告相同严重级别的不同诊断实例。

2.3.2. 可过滤触发规则

大多数诊断会无条件报告给 WebGPU 应用。 某些诊断类型可通过命名其触发规则等方式过滤。 下表列出了可过滤的标准触发规则集。

可过滤的诊断触发规则
可过滤触发规则 默认严重级别 触发位置 描述
derivative_uniformity error 计算导数的内建函数调用点位置。 即对下列函数的调用位置: 调用内建函数计算导数,但一致性分析无法证明调用发生在一致控制流中。

参见§ 15.2 一致性

subgroup_uniformity error subgroupquad 内建函数的调用点位置。 调用 subgroup 或 quad 内建函数,但一致性分析无法证明调用发生在一致控制流中。 另外,当一致性分析无法证明如下参数值一致时:

参见§ 15.2 一致性

使用单个diagnostic name-token组成的未识别触发规则时,应该触发用户代理的警告。

实现可以支持此处未指定的触发规则,只要它们按diagnostic_rule_name的多 token 形式拼写。 使用多 token 形式拼写的未识别触发规则本身可能触发诊断。

本规范的未来版本可能移除某一规则或降低其默认严重级别(即将当前默认替换为更低的级别),仍视为向后兼容。 例如,WGSL 的未来版本可能将derivative_uniformity的默认严重级别从error改为warninginfo。 修改后,原先有效的程序仍将保持有效。

2.3.3. 诊断过滤

当带有可过滤触发规则诊断触发后,WGSL 提供机制来丢弃诊断或修改其严重级别。

诊断过滤器 DF 有三个参数:

对诊断 D 应用诊断过滤器 DF(AR,NS,TR) 具有如下效果:

区间诊断过滤器诊断过滤器,其影响范围为指定的源文本区间。 区间诊断过滤器以 @diagnostic 属性形式出现在受影响的源区间起始处,具体见下表。 @diagnostic 属性不得出现在其他地方。

区间诊断过滤器的放置位置
放置位置 影响范围
复合语句的开始位置。 该复合语句。
函数声明的开始位置。 该函数声明。
if 语句的开始位置。 该 if 语句:if_clause 及所有相关的 else_if_clauseelse_clause,包括所有控制条件表达式。
switch 语句的开始位置。 该 switch 语句:选择器表达式和 switch_body
switch_body 的开始位置。 switch_body
loop 语句的开始位置。 该 loop 语句。
while 语句的开始位置。 该 while 语句:条件表达式和循环体。
for 语句的开始位置。 该 for 语句:for_header 和循环体。
紧邻 循环体的左大括号('{')之前,适用于 loopwhilefor 循环。 循环体
continuing_compound_statement 的开始位置。 continuing_compound_statement

注意: 下列也是复合语句函数体case 子句default-alone 子句whilefor 循环体, 以及 if_clauseelse_if_clauseelse_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 模块。

示例:对 derivative uniformity 使用全局诊断过滤器
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) 当满足如下条件时冲突

诊断过滤器不得冲突

注意: 只要不冲突,允许有多个全局诊断过滤器

WGSL 的诊断过滤器设计为其影响范围可完美嵌套。 如果 DF1 的影响范围与 DF2 重叠,则要么 DF1 的影响范围完全包含于 DF2,要么反之亦然。

对于源位置 L 和触发规则 TR最近包含诊断过滤器(如存在)是满足以下条件的诊断过滤器 DF(AR,NS,TR)

由于影响范围嵌套,最近包含诊断过滤器:

2.4. 限制

WGSL 实现将会支持满足如下限制的着色器。 WGSL 实现也可以支持超出指定限制的着色器。

注意: 如果不支持超出指定限制的着色器,WGSL 实现应当报错。

可量化着色器复杂度限制
限制 最小支持值
结构体类型的最大成员数 1023
复合类型的最大嵌套深度 15
函数内大括号包围语句的最大嵌套深度 127
函数的最大参数255
switch 语句中 case 选择器值的最大数量。 对每个 case 语句,case 值数量之和,包括default 子句1023
单个着色器中在 private 地址空间内被静态访问的所有变量的总字节大小上限 8192
单个函数中在 function 地址空间声明的所有变量的总字节大小上限 8192
单个着色器中在 workgroup 地址空间内被静态访问的所有变量的总字节大小上限

对此限制,固定占用数组在替换 override 值时视为创建时固定占用数组处理。

此项将 WebGPU maxComputeWorkgroupStorageSize 限制映射为独立 WGSL 限制。

16384
值构造器表达式中数组类型的最大元素数 2047

3. 文本结构

text/wgsl 媒体类型用于标识内容为 WGSL 模块。 参见附录 A:text/wgsl 媒体类型

WGSL 模块是使用 UTF-8 编码的 Unicode 文本,不带字节顺序标记(BOM)。

WGSL 模块文本由一系列 Unicode 码点组成,这些码点被分组成连续且非空的集合,形成:

程序文本不得包含空码点(U+0000)。

3.1. 解析

解析 WGSL 模块的步骤如下:

  1. 去除注释

    • 用空格码点(U+0020)替换第一个注释。

    • 重复此步骤,直到没有注释为止。

  2. 查找模板列表,使用算法,详见§ 3.9 模板列表

  3. 解析整个文本,尝试匹配translation_unit 语法规则。 解析器使用 LALR(1) 解析器(单标记前瞻)[DeRemer1969],并有如下定制:

    • 词法分析与语法分析交错进行,并且与上下文相关。 当解析器请求下一个标记时:

若出现以下情况,则会导致着色器创建错误

3.2. 空白与换行

空白符是指来自 Unicode Pattern_White_Space 属性的一个或多个码点的任意组合。 以下是 Pattern_White_Space 属性的码点集合:

换行是指一连串空白符码点,表示行的结束。 它被定义为信号“强制换行”的空白符,详见 UAX14 第 6.1 节 不可裁剪换行规则 LB4LB5。 即,换行是下列之一:

注意: 以行号报告源文本位置的诊断应使用换行来计数行。

3.3. 注释

注释是一段文本,不影响 WGSL 程序的合法性或语义,只是注释可以分隔标记。 着色器作者可以使用注释记录程序。

行尾注释是一种注释, 由两个码点 //U+002FU+002F)和后续码点组成, 直到但不包括:

块注释是一种注释,包括:

注意: 块注释可以嵌套。 由于块注释要求匹配的起始和结束文本序列,并允许任意嵌套, 块注释无法用正则表达式识别。 这是正则语言抽水引理的结果。

示例:注释
const f = 1.5; // 这是行尾注释。
const g = 2.5; /* 这是一个块注释
                跨越多行。
                /* 块注释可以嵌套。
                 */
                但所有块注释都必须结束。
               */

3.4. 标记

标记是由码点组成的连续序列,其可以是:

3.5. 字面量

字面量是以下之一:

literal :

int_literal

| float_literal

| bool_literal

3.5.1. 布尔字面量

示例:布尔字面量
const a = true;
const b = false;
bool_literal :

'true'

| 'false'

3.5.2. 数值字面量

数值字面量的形式通过模式匹配定义。

整数字面量是:

注意: 非零整数字面量前导零(如 012)是禁止的,以避免与其他语言的八进制表示法混淆。

int_literal :

decimal_int_literal

| hex_int_literal

decimal_int_literal :

/0[iu]?/

| /[1-9][0-9]*[iu]?/

示例:十进制整数字面量
const a = 1u;
const b = 123;
const c = 0;
const d = 0i;
hex_int_literal :

/0[xX][0-9a-fA-F]+[iu]?/

示例:十六进制整数字面量
const a = 0x123;
const b = 0X123u;
const c = 0x3f;

浮点字面量可以是十进制浮点字面量十六进制浮点字面量

float_literal :

decimal_float_literal

| hex_float_literal

浮点字面量有两个逻辑部分:用于表示分数的有效数位(significand),以及可选的指数部分。 大致来说,字面量的值等于有效数位乘以底数的指数幂。 当一个有效数位为非零时为有效,或其左右两侧均有非零有效数位。 有效数位自左至右计数:第 N 个有效数位左侧有 N-1 个有效数位。

十进制浮点字面量为:

decimal_float_literal :

/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;
十进制浮点字面量的数学值按如下方式计算:

注意: 十进制有效数位截断为 20 位,保留约 log(10)/log(2)×20 ≈ 66.4 位有效二进制位。

十六进制浮点字面量为:

hex_float_literal :

/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;
十六进制浮点字面量的数学值按如下方式计算:

注意: 十六进制有效数位截断为 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 :

ident_pattern_token _disambiguate_template

member_ident :

ident_pattern_token

标识符的形式基于 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Кызыл𐰓𐰏𐰇朝焼けسلام검정שָׁלוֹםगुलाबीփիրուզ

有如下例外:

ident_pattern_token :

/([_\p{XID_Start}][\p{XID_Continue}]+)|([\p{XID_Start}])/u

Unicode 14.0.0 版字符数据库包含 所有有效码点的非规范性列表, 包括XID_StartXID_Continue

注意: 某些内建函数返回类型为结构体类型,其名称不能在 WGSL 源中使用。 这些结构体类型被视为以双下划线开头的预声明类型。 可用类型推断将结果值保存至新声明的 letvar,或直接按成员名提取成员。见 frexpmodf 的示例用法。

3.7.1. 标识符比较

仅当两个 WGSL 标识符由相同序列的码点组成时它们才相同。

注意: 本规范不允许对比时进行 Unicode 归一化。 视觉和语义上相同但码点序列不同的值不会匹配。 内容作者应一致使用相同的编码序列,或在选择值时避免有潜在问题的字符。更多信息见[CHARMOD-NORM]

注意: 当将 WGSL 模块中标识符的所有实例替换为同形异义词会改变其语义时,用户代理应向开发者发出可见警告。 (同形异义字是指一段码点序列在视觉上可与另一段码点序列混淆。 检测同形异义字的映射方法包括上文提到的转换、映射与匹配算法。若能通过反复替换子序列为其同形异义字将一个标识符变为另一个,则这两个序列为同形异义词。)

3.8. 上下文相关名称

上下文相关名称是只在特定语法环境下用于命名概念的标记。 该标记的拼写可能与标识符相同,但该标记不会解析为已声明对象。 本节列举了用作上下文相关名称的标记。 该标记不得是关键字保留字

3.8.1. 属性名称

§ 12 属性

属性名称包括:

3.8.2. 内建值名称

内建值名称标记是用于内建值名称的标记

§ 13.3.1.1 内建输入与输出

builtin_value_name :

ident_pattern_token

内建值名称包括:

3.8.3. 诊断规则名称

诊断名称标记是用于诊断触发规则名称的标记

§ 2.3 诊断

diagnostic_name_token :

ident_pattern_token

预定义的诊断规则名称有:

3.8.4. 诊断严重级别控制名称

有效的诊断过滤器严重级别控制名称在§ 2.3 诊断中列出,形式与标识符相同:

severity_control_name :

ident_pattern_token

诊断过滤器严重级别控制名称包括:

3.8.5. 扩展名称

有效的enable-extension名称在§ 4.1.1 启用扩展中列出,但通常形式与标识符相同:

enable_extension_name :

ident_pattern_token

enable-extension名称包括:

有效的语言扩展名称在§ 4.1.2 语言扩展中列出,但通常形式与标识符相同:

language_extension_name :

ident_pattern_token

语言扩展名称包括:

3.8.6. 插值类型名称

插值类型名称标记是用于插值类型名称的标记,用于interpolate_type_name

参见 § 13.3.1.4 插值

插值类型名称包括:

3.8.7. 插值采样名称

插值采样名称标记是用于插值采样名称的标记

参见 § 13.3.1.4 插值

interpolate_sampling_name :

ident_pattern_token

插值采样名称包括:

3.8.8. 分量调换名称

分量调换名称用于向量访问表达式

swizzle_name :

/[rgba]/

| /[rgba][rgba]/

| /[rgba][rgba][rgba]/

| /[rgba][rgba][rgba][rgba]/

| /[xyzw]/

| /[xyzw][xyzw]/

| /[xyzw][xyzw][xyzw]/

| /[xyzw][xyzw][xyzw][xyzw]/

3.9. 模板列表

模板参数化是一种指定用于修饰通用概念的参数的方法。 要编写模板参数化,先写通用概念,后跟一个模板列表

忽略注释空白符模板列表为:

模板参数的形式由下方模板列表发现算法隐式定义。一般来说,它们可以是名称、表达式或类型。

注意: 例如,短语 vec3<f32> 是一个模板参数化,其中 vec3 是被修饰的通用概念, <f32> 是包含一个参数的模板列表,即 f32 类型。 合起来,vec3<f32> 表示一个具体的向量类型。

注意: 例如,短语 var<storage,read_write> 用模板参数 storageread_write 修饰了通用 var 概念。

注意:例如,短语 array<vec4<f32>> 有两个模板参数化:

界定模板列表的 '<'(U+003C)和 '>'(U+003E)码点也用于拼写:

该句法歧义优先解释为模板列表:

模板列表发现算法如下。 它使用以下假设和属性:

  1. 模板参数表达式,因此不会以 '<'(U+003C)或 '='(U+003D)码点开始。

  2. 表达式不包含 ';'(U+003B)、'{'(U+007B)或 ':'(U+003A)码点。

  3. 表达式不包含赋值语句

  4. '='(U+003D)码点只会出现在比较运算中,即 '<=''>=''==''!=' 之一。 否则,'='(U+003D)码点作为赋值出现。

  5. 模板列表分隔符会考虑由圆括号 '(... )' 和数组下标 '[...]' 形成的嵌套表达式。模板列表的开始和结束必须出现在同一级嵌套。

算法: 模板列表发现

输入: 程序源文本。

记录类型:

UnclosedCandidate 为包含:

TemplateList 为包含:

输出: DiscoveredTemplateLists,一个 TemplateList 记录的列表。

过程:

注意:该算法可按如下方式修改以查找模板参数的源码区间:

注意: 该算法会显式跳过字面量,因为某些数字字面量以字母结尾,例如 1.0f。 末尾的 f 不应被误判为ident_pattern_token 的开头。

注意: 短语 A ( B < C, D > ( E ) ) 中,< C, D >模板列表

注意: 算法遵循表达式嵌套:特定模板列表的起止不能出现在不同表达式嵌套层级。 例如 array<i32,select(2,3,a>b)>,该模板列表有三个参数,最后一个为 select(2,3,a>b)a>b 中的 '>' 不会终止模板列表,因为它包裹在 select 函数括号表达式中。

注意: 模板列表两端必须在同一索引表达式内。例如 a[b<d]>() 不包含有效模板列表。

注意: 短语 A<B<<C> 中, B<<C 被解析为 B,左移运算符'<<',和 C。 模板发现算法从 B 后的 '<' 开始检查,发现下一个 '<' 不能作为模板参数开头,因此 B 后的 '<' 不是模板列表起始。 唯一的模板列表定界符是最外层 '<''>',参数为 B<<C

注意: 短语 A<B<=C> 解析方式与前述类似,B<=C 被解析为 B,小于等于运算符'<='C。 算法检查 B'<',发现下一个 '=' 不能作为模板参数开头,因此 B 后的 '<' 不是模板列表起始。 唯一的模板列表定界符为最外层 '<''>',参数为 B<=C

注意: 检查短语 A<(B>=C)> 时,有一个模板列表,起于第一个 '<'(U+003C),止于最后一个 '>'(U+003E),参数为 B>=C。 检查 B 后第一个 '>'(U+003C)后,'=' 需特殊处理,避免被识别为赋值。

注意: 检查短语 A<(B!=C)> 时,有一个模板列表,起于第一个 '<'(U+003C),止于最后一个 '>'(U+003E),参数为 B!=C。 检查 'B' 后的 '!'(U+0021)后,'=' 需特殊处理,避免被识别为赋值。

注意: 检查短语 A<(B==C)> 时,有一个模板列表,起于第一个 '<'(U+003C),止于最后一个 '>'(U+003E),参数为 B==C。 检查 'B' 后第一个 '='(U+003D)后,第二个 '=' 需特殊处理,避免两者被识别为赋值。

模板列表发现完成后, 解析尝试将每个模板列表匹配到template_list语法规则。

template_list :

_template_args_start template_arg_comma_list _template_args_end

template_arg_comma_list :

template_arg_expression ( ',' template_arg_expression ) * ',' ?

template_arg_expression :

expression

4. 指令

指令是一组标记序列,用于修改 WebGPU 实现处理 WGSL 程序的方式。

指令是可选的。 如果存在,所有指令必须出现在任何声明const 断言之前。

global_directive :

diagnostic_directive

| enable_directive

| requires_directive

4.1. 扩展

WGSL 预计会随着时间演进。

扩展是对 WGSL 规范的一组相关修改的命名分组,由以下任意组合组成:

理论上,扩展可以:

扩展分为两类:enable-extensions语言扩展

4.1.1. 启用扩展

enable-extension 是一种扩展,其功能仅在以下条件下可用:

enable-extensions 旨在暴露并非所有硬件都普遍支持的功能。

enable 指令是一种指令,用于开启对一个或多个 enable-extensions 的支持。 如果实现不支持所有列出的 enable-extensions,则会导致着色器创建错误

enable_directive :

'enable' enable_extension_list ';'

enable_extension_list :

enable_extension_name ( ',' enable_extension_name ) * ',' ?

如同其它指令,如果存在enable 指令,必须出现在所有声明const 断言之前。 扩展名不是标识符:它们不会解析声明

有效的enable-extensions如下表所列。

Enable-extensions
WGSL enable-extension WebGPU GPUFeatureName 说明
f16 "shader-f16" 在 WGSL 模块中可以使用 f16 类型。否则,直接或间接使用f16 会导致着色器创建错误
clip_distances "clip-distances" 在 WGSL 模块中可以使用内建变量clip_distances。否则,使用clip_distances会导致着色器创建错误
dual_source_blending "dual-source-blending" 在 WGSL 模块中可以使用属性blend_src。否则,使用 blend_src 会导致 着色器创建错误
subgroups "subgroups" 在 WGSL 模块中可以使用subgroup 内建值subgroup 内建函数quad 内建函数。 否则,任何使用都会导致着色器创建错误
示例:使用假设的 enable-extensions
// 启用假设的任意精度浮点类型扩展。
enable arbitrary_precision_float;
enable arbitrary_precision_float; // 重复 enable 指令是允许的。

// 启用假设的用于控制舍入模式的扩展。
enable rounding_mode;

// 假设 arbitrary_precision_float 启用后可使用:
//    - 类型 f<E,M>
//    - 作为函数返回、形参和 let 声明的类型
//    - 从 AbstractFloat 构造值
//    - 作为除法运算符 / 的操作数
// 假设 @rounding_mode 属性由 rounding_mode enable 指令启用。
@rounding_mode(round_to_even)
fn halve_it(x : f<8, 7>) -> f<8, 7> {
  let two = f<8, 7>(2);
  return x / 2; // 使用 round to even 舍入模式。
}

4.1.2. 语言扩展

语言扩展 是一种扩展,只要实现支持就会自动可用。 程序无需显式请求即可使用。

语言扩展 体现了任何 WebGPU 实现都合理支持的功能。 如果某个特性并非普遍可用,那是因为尚有部分 WebGPU 实现未实现它。

注意: 例如,do-while 循环可以作为一种语言扩展。

WebGPU wgslLanguageFeatures 成员会列出实现所支持的 语言扩展集合。 该成员属于 WebGPU GPU 对象。

requires 指令 是一种指令,用于声明 程序使用了哪些语言扩展。 它不会改变实现所暴露的功能。 如果实现不支持所需扩展之一,则会导致着色器创建错误

WGSL 模块可以使用requires 指令来提示潜在的不移植性,并声明预期的最小可移植性要求。

注意: WebGPU 实现以外的工具可以检查程序用到的语言扩展是否都已在程序中通过requires 指令标明。

requires_directive :

'requires' language_extension_list ';'

language_extension_list :

language_extension_name ( ',' language_extension_name ) * ',' ?

和其他指令一样,如有requires 指令,必须出现在所有声明const 断言之前。 扩展名不是标识符:它们不会解析声明

语言扩展
WGSL 语言扩展 说明
readonly_and_readwrite_storage_textures 允许 storage texture 使用 readread_write 访问模式。 另外,增加 textureBarrier 内建函数。
packed_4x8_integer_dot_product 支持用 32 位整数打包 4 个 8 位整数分量向量作为 dot4U8Packeddot4I8Packed 内建函数的点积输入。 另外,增加了用于 8 位整数打包/解包的内建函数:pack4xI8pack4xU8pack4xI8Clamppack4xU8Clampunpack4xI8unpack4xU8
unrestricted_pointer_parameters 移除了函数限制中关于用户自定义函数的如下限制:

对于用户自定义函数,指针类型参数 必须属于下列地址空间之一:

传递给用户自定义函数的每个指针类型参数 必须与其根标识符具有相同的内存视图

pointer_composite_access 支持复合值分解表达式,其根表达式为指针时,返回引用。

例如,若 p 是指向包含成员 m 的结构体的指针,则 p.m 是对结构体 p 所指内 m 成员的内存引用。

同理,若 pa 是指向数组的指针,则 pa[i] 是对 pa 所指数组第 i 项的内存引用。

注意: 设计目标是 WGSL 随时间推移将语言扩展中业界常见的所有功能都定义为内置扩展。 在requires 指令中,这些扩展可以用作列出所有常用特性的简写。 它们代表不断增强的功能集合,也可视为某种语言版本。

4.2. 全局诊断过滤器

全局诊断过滤器是一种诊断过滤器,其影响范围为整个 WGSL 模块。 它是一条指令,因此出现在所有模块作用域声明之前。 它的拼写形式与属性类似,但没有前导 @(U+0040)码点,且以分号结尾。

diagnostic_directive :

'diagnostic' diagnostic_control ';'

5. 声明与作用域

声明将一个标识符与下列对象之一关联:

换句话说,声明为一个对象引入一个名称

声明位于模块作用域,如果声明出现在程序源码中但不在其他声明的文本内部。

一个函数声明位于模块作用域。 函数声明包含形参声明(如果有的话),并且可以在其函数体内包含变量和常量声明。 这些包含的声明不属于模块作用域。

注意: 唯一能包含其他声明的声明类型是函数声明

某些对象由 WebGPU 实现提供,视为在 WGSL 模块源码开始之前已经声明。 我们称这些对象为预声明对象。 例如,WGSL 预声明了:

作用域是声明在程序源码中所声明标识符可能表示其关联对象的源码位置集合。 我们称这些源码位置的标识符为声明的在作用域内

声明出现的位置决定了其作用域:

同一 WGSL 源程序中的两个声明不得同时:

注意: 预声明对象在 WGSL 源码中没有声明。 因此,用户在模块作用域或函数内声明的对象可以与预声明对象同名。

标识符的用法如下,由语法环境区分:

ident 标记作为名称引用其他地方声明的对象时, 它必须对某个声明在作用域内。 该标识符标记所表示的对象按以下规则确定:

当上述算法用于将标识符映射到声明时,我们称该标识符解析到该声明。 同样,我们也称该标识符解析到被声明对象。

如果任何模块作用域声明是递归的,将导致着色器创建错误。 即,声明之间不能形成环:

考虑如下有向图:

该图不得有环。

注意: 函数体函数声明的一部分,因此函数也不能直接或间接递归。

注意:模块作用域的标识符声明必须出现在其使用之前。

示例:合法与非法声明
// 合法,用户自定义变量可以与内建函数同名。
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 中对应如下不同的值:

WGSL 将这些值视为不同,因为它们的机器表示和操作不同。

类型要么是预声明的,要么通过 WGSL 源码中的声明创建。

某些类型通过模板参数化表示。 类型生成器是一个预声明对象,当与模板列表参数化时,表示一个类型。 例如,类型 atomic<u32> 结合了类型生成器 atomic 与模板列表 <u32>

我们区分 WGSL 中类型的概念和表示该类型的语法。 在许多情况下,本规范中类型的拼写与其 WGSL 语法相同。 例如:

某些 WGSL 类型仅用于分析源程序和确定程序的运行时行为。 本规范将描述这些类型,但它们不会出现在 WGSL 源文本中。

注意: 引用类型不会出现在 WGSL 模块中。见 § 6.4.3 引用和指针类型

6.1. 类型检查

WGSL 值通过求值表达式计算。 表达式是被解析为 WGSL 语法规则之一的源码段,这些规则的名称以 "expression" 结尾。 表达式 E 可以包含子表达式,即完整包含在外层表达式 E 内的表达式。 顶层表达式是自身不是子表达式的表达式。 见 § 8.18 表达式语法汇总

表达式求值所产生的具体值依赖于:

求值特定表达式产生的值总是属于特定 WGSL 类型,称为表达式的静态类型。 WGSL 的规则设计为表达式的静态类型仅依赖于表达式的静态上下文

类型断言是 WGSL 源表达式到 WGSL 类型的映射。 表示法

e : T

是一条类型断言,表示 T 是 WGSL 表达式 e 的静态类型。

注意: 类型断言是关于程序文本事实的陈述。 它不是运行时检查。

语句通常使用表达式,并可能对这些表达式的静态类型施加要求。 例如:

对成功解析的 WGSL 模块进行类型检查是将每个表达式映射到其静态类型并验证每条语句的类型要求是否满足的过程。 如果类型检查失败,结果是一种称为类型错误的特殊着色器创建错误

类型检查可以通过递归应用类型规则到语法短语来完成,其中语法短语可以是表达式语句类型规则描述了静态上下文如何决定语法短语内表达式的静态类型。 一条类型规则有两个部分:

类型规则的前提条件和结论中可能有类型参数。 当类型规则的结论或前提条件包含类型参数时,我们称其为参数化规则。 当它们不包含时,称其为完全展开规则。 我们可以通过为其每个类型参数替换一个类型,使参数化类型规则成为完全展开规则,在规则中每次出现的参数都使用相同类型。 给规则的类型参数分配类型称为替代

例如,这里是逻辑取反(形式为 !e 的表达式)的类型规则:

前提条件 结论
e: T
T 是 bool 或 vecN<bool>
!e: T

这是一个参数化规则,因为它包含类型参数 T,可以表示四种类型之一 boolvec2<bool>vec3<bool>vec4<bool>。 应用将 T 映射为 vec3<bool> 的替代产生完全展开的类型规则:

前提条件 结论
e: vec3<bool>
!e: vec3<bool>

通过应用满足规则其他条件的替代,从参数化规则生成的每条完全展开规则称为该参数化规则的重载。 例如,布尔取反规则有四个重载,因为有四种可能的方式为其类型参数 T 分配类型。

注意: 换句话说,参数化类型规则提供了一个模式,用于生成一组完全展开的类型规则,每条规则由对参数化规则应用不同替代产生。

当以下条件满足时,类型规则适用于某语法短语

如果存在某替代生成的完全展开类型规则适用于表达式,则参数化类型规则适用于该表达式。

考虑表达式 1u+2u。 它有两个字面量子表达式1u2u,均为 u32 类型。 顶层表达式是加法。 参考 § 8.7 算术表达式中的规则,加法的类型规则适用于该表达式,因为:

分析某语法短语时,可能出现以下三种情况:

继续上述示例,仅有一条类型规则适用于表达式 1u+2u,因此类型检查接受该类型规则的结论,即 1u+2u 为 u32 类型。

当以下条件满足时,WGSL 源程序是类型正确的:

否则会发生类型错误,程序不是有效的 WGSL 模块。

WGSL 是一种静态类型语言,因为对 WGSL 模块的类型检查成功或发现类型错误,并且只需检查程序源码文本。

6.1.1. 类型规则表

WGSL 的表达式类型规则按照类型规则表组织, 每条类型规则对应表中的一行。

表达式的语义是对该表达式求值的效果,主要是产出一个结果值。 适用于某个表达式的类型规则的 Description(描述)列会指定该表达式的语义。 语义通常取决于类型规则参数的值,包括任何子表达式的假定值。 有时,表达式的语义还包括产生结果值之外的效果,如其子表达式的副作用。

示例:表达式副作用
fn foo(p : ptr<function, i32>) -> i32 {
  let x = *p;
  *p += 1;
  return x;
}

fn bar() {
  var a: i32;
  let x = foo(&a); // 调用 foo 返回一个值
                   // 同时更新了 a 的值
}

6.1.2. 转换等级

当类型断言 e:T 作为类型规则前提条件使用时,满足如下条件即可:

该规则由 ConversionRank 函数对类型对进行编码,定义见下表。 ConversionRank 函数表达了从一个类型(Src)自动转换到另一个类型(Dest)的优先级和可行性。 等级越低越理想。

可行的自动转换指将值从类型 Src 转换为类型 Dest,且仅当 ConversionRank(Src,Dest) 有限时才允许。 此类转换是保值的,具体限制见 § 15.7 浮点数求值

注意: 自动转换只发生在两种情形。 一是将常量表达式转换为 GPU 可用的对应类型数值。 二是在从内存引用加载时,产生存储在该内存中的值。

注意: 无限等级的转换不可行,即不允许。

注意: 未执行转换时,转换等级为零。

类型间 ConversionRank 转换等级
Src Dest ConversionRank(Src,Dest) 说明
T T 0 恒等。未执行转换。
ref<AS,T,AM>
其中 地址空间AS, 且访问模式 AMreadread_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 等效于 AbstractIntAbstractFloat,再到 AbstractFloat 到 f32
AbstractInt f16 7 等效于 AbstractIntAbstractFloat,再到 AbstractFloat 到 f16
vecN<S> vecN<T> ConversionRank(S,T) 继承分量类型的转换等级。
matCxR<S> matCxR<T> ConversionRank(S,T) 继承分量类型的转换等级。
array<S,N> array<T,N> ConversionRank(S,T) 继承分量类型的转换等级。 注:只有定长数组可有抽象分量类型。
__frexp_result_abstract __frexp_result_f32 1
__frexp_result_abstract __frexp_result_f16 2
__frexp_result_vecN_abstract __frexp_result_vecN_f32 1
__frexp_result_vecN_abstract __frexp_result_vecN_f16 2
__modf_result_abstract __modf_result_f32 1
__modf_result_abstract __modf_result_f16 2
__modf_result_vecN_abstract __modf_result_vecN_f32 1
__modf_result_vecN_abstract __modf_result_vecN_f16 2
S T
如上无适用情况
infinity 不同类型间无自动转换。

如果:

则类型 TS具体化

e 类型为 T 的值的具体化是对 e 应用可行转换,将 T 映射到其具体化类型后的结果值。

注意:转换为 f32 总是优先于 f16,因此 自动转换只会在模块启用了 extension 的情况下才会生成 f16

6.1.3. 重载解析

当多条类型规则适用于某个语法短语时,将使用一个决策流程来确定应采用哪一条规则。 该流程称为重载解析,并假定类型检查已成功为子表达式找到了静态类型。

语法短语 P,所有适用的类型规则。 重载解析算法将这些类型规则称为重载候选。 对每个候选项:

P 的重载解析如下,目标是找到唯一最重载候选

  1. 对每个候选 C,枚举语法短语中子表达式的转换等级。 由于候选的前提已满足,故对于 P 中第 i 个子表达式:

    • 其静态类型已被计算。

    • 存在从该表达式的静态类型到前提中对应类型断言的可行自动转换。 记 C.R(i) 为该转换的ConversionRank

  2. 消除如下候选:若其某一子表达式在可行自动转换后解析为抽象类型,但该候选的另一子表达式不是常量表达式

    注意: 结果是,如果短语中任一子表达式不是常量表达式,则短语中所有子表达式都必须是具体类型。

  3. 候选排序:给定两个重载候选 C1C2,若满足如下条件,则 C1 优于 C2

    • P 中每个表达式位置 i,有 C1.R(i) ≤ C2.R(i)。

      • 即,应用 C1 所需的每个表达式转换至少与应用 C2 所需的对应表达式转换一样优。

    • 至少存在一个表达式位置 i 使得 C1.R(i) < C2.R(i)。

      • 即,应用 C1 至少有一个表达式转换比应用 C2 的对应转换更优。

  4. 如果存在唯一一个候选 C,其优于所有其他候选,则重载解析成功,结果为该类型规则 C。 否则,重载解析失败。

6.2. 普通类型

普通类型是指布尔值、数值、向量、矩阵或这些值的聚合在机器中的表示类型。

普通类型可以是标量类型、原子类型,或复合类型。

注意: WGSL 中的普通类型类似于 C++ 的 Plain-Old-Data 类型,但还包括原子类型和抽象数值类型。

6.2.1. 抽象数值类型

这些类型不能在 WGSL 源中书写。仅用于类型检查

某些表达式在着色器创建时求值,其数值范围和精度可能大于 GPU 直接实现的范围。

WGSL 为这些求值定义了两种抽象数值类型

在这些类型之一中对表达式的求值不得溢出,也不得产生无穷大或 NaN。

若某类型为抽象数值类型或包含抽象数值类型,则其为抽象类型。 若不是抽象类型,则为具体类型。

无后缀的数值字面量表示抽象数值类型的值:

示例:表达式 log2(32) 的分析如下:

示例:表达式 1 + 2.5 的分析如下:

示例:let x = 1 + 2.5;

示例:1u + 2.5 会导致着色器创建错误

示例:-1 * i32(-2147483648) 不会导致着色器创建错误

示例:字面量类型推断
// 显式类型的无符号整数字面量。
var u32_1 = 1u; // 变量为 u32

// 显式类型的有符号整数字面量。
var i32_1 = 1i; // 变量为 i32

// 显式类型的浮点数字面量。
var f32_1 = 1f; // 变量为 f32

// 显式类型的无符号整数字面量不能取负号。
var u32_neg = -1u; // 非法:u32 不支持一元负号

// 若需要具体类型,但语句或表达式无处强制具体类型,整数被解释为 i32:
//   let 声明的初值必须可构造(或为指针)。
//   从 AbstractInt 到可构造类型的最高优先级自动转换为 AbstractInt 到 i32(等级 2),因此 '1' 推断为 i32。
let some_i32 = 1; // 等价于 let some_i32: i32 = 1i;

// 从声明类型推断。
var i32_from_type : i32 = 1; // 变量为 i32。AbstractInt 到 i32,转换等级 2
var u32_from_type : u32 = 1; // 变量为 u32。AbstractInt 到 u32,转换等级 3

// 无后缀整数字面量可按需转换为浮点:
//   自动将 AbstractInt 转为 f32,转换等级 5。
var f32_promotion : f32 = 1; // 变量为 f32

// 非法:浮点不能自动转换为整数
var i32_demotion : i32 = 1.0; // 非法

// 从表达式推断。
var u32_from_expr = 1 + u32_1; // 变量为 u32
var i32_from_expr = 1 + i32_1; // 变量为 i32

// 值必须可表示。
let u32_too_large   : u32 = 1234567890123456890; // 非法,溢出
let i32_too_large   : i32 = 1234567890123456890; // 非法,溢出
let u32_large : u32 = 2147483649; // 合法
let i32_large : i32 = 2147483649; // 非法,溢出
let f32_out_of_range1 = 0x1p500; // 非法,超范围
let f32_hex_lost_bits = 0x1.0000000001p0; // 非法,f32 不能精确表示

// 最小整数:对 AbstractInt 取一元负号,然后推断 i32。
// AbstractInt 到可构造类型(最低转换等级)首选为 i32。
let i32_min = -2147483648;  // 类型为 i32

// 非法。选择 AbstractInt 到 i32,但值超范围。
let i32_too_large_2 = 2147483648; // 非法。

// 子表达式可解析为 AbstractInt 和 AbstractFloat。
// 下例均合法,变量值为 6u。
var u32_expr1 = (1 + (1 + (1 + (1 + 1)))) + 1u;
var u32_expr2 = 1u + (1 + (1 + (1 + (1 + 1))));
var u32_expr3 = (1 + (1 + (1 + (1u + 1)))) + 1;
var u32_expr4 = 1 + (1 + (1 + (1 + (1u + 1))));

// 基于内建函数参数的推断。

// 最优候选是 clamp(i32,i32,i32)->i32
let i32_clamp = clamp(1, -5, 5);
// 最优候选是 clamp(u32,u32,u32)。
// 字面量用自动转换 AbstractInt 到 u32。
let u32_clamp = clamp(5, 0, u32_from_expr);
// 最优候选是 clamp(f32,f32,f32)->f32
// 字面量用自动转换 AbstractInt 到 f32。
let f32_clamp = clamp(0, f32_1, 1);

// 下例均提升为 f32,初值为 10f。
let f32_promotion1 = 1.0 + 2 + 3 + 4;
let f32_promotion2 = 2 + 1.0 + 3 + 4;
let f32_promotion3 = 1f + ((2 + 3) + 4);
let f32_promotion4 = ((2 + (3 + 1f)) + 4);

// 类型规则违规。

// 非法,初值只能解析为 f32:
// AbstractFloat 无法自动转换为 u32。
let mismatch : u32 = 1.0;

// 非法。clamp 不允许混合符号参数的重载。
let ambiguous_clamp = clamp(1u, 0, 1i);

// 推断在语句级别完成。

// let 声明的初值必须可构造(或为指针)。
// AbstractInt 到可构造类型的最高优先级自动转换为 i32(等级 2),所以 '1' 推断为 i32。
let some_i32 = 1; // 等价于 let some_i32: i32 = 1i;

let some_f32 : f32 = some_i32; // 类型错误:i32 不能赋值给 f32

// 另一个溢出情况
let overflow_u32 = (1 -2) + 1u; // 非法,-1 不在 u32 范围内

// 理论值超出 32 位,但又被拉回范围
let out_and_in_again = (0x1ffffffff / 8);

// 类似,但非法
let out_of_range = (0x1ffffffff / 8u); // 需用 32 位计算,0x1ffffffff 超范围

6.2.2. 布尔类型

bool 类型包含 truefalse 两个值。

布尔字面量类型规则
前提条件 结论 说明
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 类型是一组 32 位浮点值,符合 IEEE-754 binary32(单精度)格式。 详情见 § 15.7 浮点数运算

f16 类型是一组 16 位浮点值,符合 IEEE-754 binary16(半精度)格式。如果未包含 enable f16; 指令启用 f16 扩展,则使用 f16 类型会导致着色器创建错误。详情见 § 15.7 浮点数运算

下表列出了浮点类型的部分极值。 每种类型都有对应的负极值。

浮点类型的极值
类型 最小正非规格化数 最小正规格化数 最大正有限值 最大有限 2 的幂
f32 1.40129846432481707092e-45f 1.17549435082228750797e-38f 3.40282346638528859812e+38f 0x1p+127f
0x1p-149f 0x1p-126f 0x1.fffffep+127f
f16 5.9604644775390625e-8h 0.00006103515625h 65504.0h 0x1p+15h
0x1p-24h 0x1p-14h 0x1.ffcp+15h

注意: AbstractFloat 也是一种浮点类型。

6.2.5. 标量类型

标量类型包括 boolAbstractIntAbstractFloati32u32f32f16

数值标量类型包括 AbstractIntAbstractFloati32u32f32f16

整数标量类型包括 AbstractInti32u32

标量转换 指将一个标量类型的值映射为另一标量类型的值。 通常,结果值会尽量接近原值,但受目标类型限制。 标量转换发生在:

6.2.6. 向量类型

向量是由 2、3 或 4 个标量分量组成的有序集合。

类型 说明
vecN<T> NT 类型分量的向量。 N 必须为 {2, 3, 4},T必须标量类型之一。 称 T 为该向量的分量类型

如果一个向量的分量类型为数值标量,则称其为数值向量

向量的常见用途包括:

许多针对向量(和矩阵)的操作是按分量进行的,即结果通过对每个标量分量独立操作得到。

示例:向量
vec2<f32>  // 是由两个 f32 组成的向量。
示例:分量加法
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]

WGSL 还预声明了以下类型别名

预声明别名 原始类型 限制
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> CR 行、类型为 T 的矩阵,其中 CR 都属于 {2, 3, 4},T 必须为 f32f16AbstractFloat。 换言之,可以视为 C 个类型为 vecR<T> 的列向量。

矩阵的主要用途是表达线性变换。 在这种解释下,矩阵的向量被视为列向量。

乘积操作符(*)用于:

参见 § 8.7 算术表达式

示例:矩阵
mat2x3<f32>  // 这是一个 2 列 3 行的 32 位浮点矩阵。
             // 等价于两个 vec3<f32> 列向量。

WGSL 还预声明了以下类型别名

预声明别名 原始类型 限制
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必须u32i32

表达式不得求值为原子类型。

原子类型只能由 workgroup 地址空间的变量,或具有 read_write 访问模式的 存储缓冲区变量实例化。 该类型操作的内存作用域由其实例化的地址空间决定。 workgroup 地址空间中的原子类型内存作用域为 Workgroupstorage 地址空间中的原子类型内存作用域为 QueueFamily

原子修改是指对原子对象的任何操作,该操作设置对象内容。 即使新值与对象原有值相同,该操作也算作修改。

在 WGSL 中,原子修改在每个对象内是互相有序的。 即,在着色器阶段执行期间,对于每个原子对象 A,所有代理观察到的对 A 的修改操作顺序一致。 不同原子对象的排序可能无关,也不暗示因果关系。 注意,workgroup 空间的变量在同一 workgroup 内共享,但不同 workgroup 之间不共享。

6.2.9. 数组类型

数组是一组可索引的元素值序列。

类型 说明
array<E,N> 定长数组,包含 NE 类型的元素。
N 称为该数组的元素数量
array<E> 运行时大小数组,元素类型为 E。 仅可在特定上下文中出现。

数组的第一个元素索引为 0,后续元素依次为下一个整数索引。见 § 8.5.3 数组访问表达式

表达式不得求值为运行时大小数组类型。

定长数组的元素数量表达式 N 须满足以下约束:

注意: 如果 N 依赖于任何override 声明,则元素数量值会在管线创建时确定,否则在着色器模块创建时确定。

注意: 若要类型等价,任何不是常量表达式的 override 表达式必须为标识符。 见 由可重写常量定长的 workgroup 变量

运行时大小数组的元素数量由与对应存储缓冲区变量关联的缓冲区绑定大小决定。 见 § 13.3.4 缓冲区绑定决定运行时大小数组元素数量

数组元素类型必须是以下之一:

注意: 元素类型必须是普通类型

只有在以下所有条件均为真时,两个数组类型才相同:

示例:定长数组类型(非可重写元素数量)
// array<f32,8> 和 array<i32,8> 是不同类型:
// 元素类型不同
var<private> a: array<f32,8>;
var<private> b: array<i32,8>;
var<private> c: array<i32,8u>;  // array<i32,8> 和 array<i32,8u> 是相同类型

const width = 8;
const height = 8;

// array<i32,8>、array<i32,8u> 和 array<i32,width> 是相同类型。
// 它们的元素数量均为 8。
var<private> d: array<i32,width>;

// array<i32,height> 和 array<i32,width> 是相同类型。
var<private> e: array<i32,width>;
var<private> f: array<i32,height>;

注意: 由可重写常量定长的数组类型仅能用作memory view,且仅限于 workgroup 地址空间。 这包括 workgroup 变量的存储类型。 见 § 7 变量和常量声明

示例:由可重写常量定长的 workgroup 变量
override blockSize = 16;

var<workgroup> odds: array<i32,blockSize>;
var<workgroup> evens: array<i32,blockSize>; // 类型相同

// 以下均与 'odds' 和 'evens' 类型不同。

// 类型不同:不是标识符 'blockSize'
var<workgroup> evens_0: array<i32,16>;
// 类型不同:元素数量用表达式。
var<workgroup> evens_1: array<i32,(blockSize * 2 / 2 )>;
// 类型不同:用括号而非标识符。
var<workgroup> evens_2: array<i32,(blockSize)>;

// 非法示例:可重写元素数量只能出现在最外层。
// var<workgroup> both: array<array<i32,blockSize>,2>;

// 非法示例:仅 workgroup 变量允许可重写元素数量。
// var<private> bad_address_space: array<i32,blockSize>;

6.2.10. 结构体类型

结构体是由若干具名成员值组成的具名分组。

类型 说明
struct AStructName {
M1 : T1,
...
MN : TN,
}
结构体类型声明,名称为标识符 AStructName,包含 N 个成员, 其中第 i 个成员名为标识符 Mi,类型为 Ti

N 必须至少为 1。

同一结构体类型的两个成员不得重名。

结构体类型在模块作用域声明。 在程序源码其它地方,结构体类型用其标识符名称表示。 见 § 5 声明与作用域

只有当且仅当结构体类型同名时,两结构体类型才相同。

结构体成员类型必须为以下之一:

注意: 所有用户声明的结构体类型都是具体类型。

注意: 每个成员类型必须为普通类型

结构体成员和数组元素类型限制的部分后果:

示例:结构体
// 一个拥有三个成员的结构体。
struct Data {
  a: i32,
  b: vec2<f32>,
  c: array<i32,10>, // 末尾逗号可选
}

// 声明一个类型为 Data 的变量。
var<private> some_data: Data;
struct_decl :

'struct' ident struct_body_decl

struct_body_decl :

'{' struct_member ( ',' struct_member ) * ',' ? '}'

struct_member :

attribute * member_ident ':' type_specifier

以下属性可应用于结构体成员:

属性 builtinlocationblend_srcinterpolateinvariant 都是IO 属性。 结构体 S 的成员带有 IO 属性 仅在 S 用作形参返回类型(作为入口点)时生效。 见 § 13.3.1 阶段间输入输出接口

属性 alignsize布局属性, 若结构体类型用于定义uniform 缓冲区storage 缓冲区时可能需要。 见 § 14.4 内存布局

示例:结构体声明
struct my_struct {
  a: f32,
  b: vec4<f32>
}
示例:结构体声明缓冲区
// 运行时数组
alias RTArr = array<vec4<f32>>;
struct S {
  a: f32,
  b: f32,
  data: RTArr
}
@group(0) @binding(0) var<storage> buffer: S;

6.2.11. 复合类型

如果一个类型具有用其他类型组合表达的内部结构,则称为复合类型。 其内部各部分不重叠,称为组件。 复合值可以被分解为其组件。见 § 8.5 复合值分解表达式

复合类型包括:

对于复合类型 T,其嵌套深度,记作 NestDepth(T),定义如下:

6.2.12. 可构造类型

许多类型的值可以被创建、加载、存储、传递进函数,也可以作为函数的返回值。 我们称为可构造类型。

如果类型属于以下之一,则称为可构造类型

注意: 所有可构造类型都有创建时固定占用

注意: 原子类型和运行时大小数组类型都不是可构造类型。 包含原子类型或运行时大小数组的复合类型也不是可构造类型。

6.2.13. 固定占用类型

内存占用是指变量用于存储内容所需的内存单元数。 变量的内存占用取决于其存储类型,并将在着色器生命周期的某个时刻最终确定。 大多数变量会在着色器创建时很早就定型。 有些变量可能在管线创建时定型,另一些则晚至着色器执行启动时。

如果某类型的具体化大小可在着色器创建时完全确定,则称其具有创建时固定占用

如果类型的大小可在管线创建时完全确定,则称其具有固定占用

注意: 所有具体的创建时固定占用和固定占用类型都是可存储类型。

注意: 管线创建依赖于着色器创建,因此具有创建时固定占用的类型也具有固定占用

具有创建时固定占用的类型包括:

注意: 可构造类型具有创建时固定占用

具有固定占用普通类型为:

注意: 元素数量为override 表达式且不是常量表达式的定长数组,仅能用作 memory view,且仅限于 workgroup 地址空间。这包括 workgroup 变量的存储类型

注意: 固定占用类型可以直接或间接包含原子类型,而可构造类型则不能。

注意: 固定占用类型不包括运行时大小数组,以及任何包含运行时大小数组的结构体。

6.3. 枚举类型

枚举类型是一组有限的具名值。 枚举用于区分某个特定概念的一组可能性,比如一组有效的纹素格式

枚举项是某个枚举中的一个具名值。 每个枚举项都与其他所有枚举项、以及所有其他类型的值都不同。

WGSL 源码中没有声明新枚举项或新枚举类型的机制。

注意: 枚举项可用作模板参数

注意: 无法复制或为枚举项创建别名:

6.3.1. 预声明枚举项

下表列出了 WGSL 中的枚举类型及其预声明枚举项。 枚举类型实际存在,但不能在 WGSL 源中书写。

预声明枚举项
枚举
(不能在 WGSL 中书写)
预声明枚举项
访问模式 read
write
read_write
地址空间

注意: handle 地址空间不会出现在 WGSL 源码中。

function
private
workgroup
uniform
storage
纹素格式 rgba8unorm
rgba8snorm
rgba8uint
rgba8sint
rgba16unorm
rgba16snorm
rgba16uint
rgba16sint
rgba16float
rg8unorm
rg8snorm
rg8uint
rg8sint
rg16unorm
rg16snorm
rg16uint
rg16sint
rg16float
r32uint
r32sint
r32float
rg32uint
rg32sint
rg32float
rgba32uint
rgba32sint
rgba32float
bgra8unorm
r8unorm
r8snorm
r8uint
r8sint
r16unorm
r16snorm
r16uint
r16sint
r16float
rgb10a2unorm
rgb10a2uint
rg11b10ufloat

6.4. 内存视图

除了对普通值进行计算外,WGSL 程序还经常通过内存访问操作,从内存读取值或向内存写入值。 每次内存访问都是通过内存视图进行的。

内存视图包含:

内存视图的访问模式必须由地址空间支持。见§ 7 变量和常量声明

6.4.1. 可存储类型

变量中包含的值必须可存储类型。 可存储类型可以由 WGSL 明确定义其表示方式(见 § 14.4.4 值的内部布局),也可以是透明类型,比如纹理采样器

如果类型既是具体类型又属于以下之一,则称为可存储类型:

注意: 即,可存储类型包含具体普通类型、纹理类型和采样器类型。

6.4.2. 主机可共享类型

主机可共享类型用于描述主机与 GPU 之间共享的缓冲区内容,或主机和 GPU 间无需格式转换即可拷贝的内容。 用于此目的时,该类型还可以按布局属性进行约束,见 § 14.4 内存布局。 如 § 7.3 var 声明所述,uniform 缓冲区storage 缓冲区变量的存储类型 必须为主机可共享类型。

若类型既为具体类型,又属于以下之一,则称为主机可共享类型:

注意: 关于阶段间输入输出类型的限制见 § 13.3.1 阶段间输入输出接口及后续章节。 这些类型也有大小约束,但计数方式不同。

注意: 纹理和采样器也可以主机与 GPU 共享,但其内容是透明的。 本节中的主机可共享类型专用于storageuniform 缓冲区。

6.4.3. 引用类型与指针类型

WGSL 有两种类型用于表示内存视图引用类型指针类型

约束 类型 说明
AS地址空间,
T可存储类型,
AM访问模式
ref<AS,T,AM> 引用类型 标识一组内存视图,这些视图指向 AS 地址空间中存储 T 类型值的内存位置, 支持由 AM 描述的内存访问。

此处 T存储类型

引用类型不会出现在 WGSL 源中,仅用于分析 WGSL 模块。

AS地址空间,
T可存储类型,
AM访问模式
ptr<AS,T,AM> 指针类型 标识一组内存视图,这些视图指向 AS 地址空间中存储 T 类型值的内存位置, 支持由 AM 描述的内存访问。

此处 T存储类型

指针类型可出现在 WGSL 源中。

只有地址空间、存储类型和访问模式均相同时,两个指针类型才相同。

分析 WGSL 模块时,引用类型和指针类型都完全由地址空间、可存储类型和访问模式参数化。 在本规范的代码示例中,注释都会展示这种完全参数化的形式。

但在 WGSL 源代码文本中:

示例:指针类型
fn my_function(
  /* 'ptr<function,i32,read_write>' 是指向 function 地址空间中存储 i32 值的指针类型。
     这里 'i32' 是存储类型。
     隐含访问模式为 'read_write'。
     访问模式默认见“地址空间”章节。*/
  ptr_int: ptr<function,i32>,

  // 'ptr<private,array<f32,50>,read_write>' 是指向 private 地址空间中存储 50 个 f32 元素数组的指针类型。
  // 这里存储类型为 'array<f32,50>'。
  // 隐含访问模式为 'read_write'。
  // 访问模式默认见“地址空间”章节。
  ptr_array: ptr<private, array<f32, 50>>
) { }

引用类型和指针类型都是一组内存视图: 某个具体内存视图既关联唯一的引用值,也关联唯一的指针值:

每个类型为 ptr<AS,T,AM> 的指针值 p,都对应一个类型为 ref<AS,T,AM> 的唯一引用值 r,反之亦然。pr 描述同一个内存视图。

6.4.4. 有效与无效内存引用

引用值要么是有效的,要么是无效的。

引用的形成方式详见 § 6.4.8 创建引用和指针值。 通常,有效引用的形成方式有:

通常,无效内存引用的形成方式有:

有效指针是指对应于有效引用的指针。 无效指针是指对应于无效内存引用的指针。

6.4.5. 起源变量

起源变量对于引用值 R 的定义如下:

指针值的起源变量定义为其对应引用值的起源变量。

注意: 起源变量是一个动态概念。函数形参的起源变量取决于函数的调用点。 不同的调用点可能传入指向不同起源变量的指针。

有效引用总是对应于某变量的一部分或全部内存位置的非空内存视图。

注意: 引用可对应变量内部的内存位置,但仍可能无效。 例如索引超出被索引类型的范围时,被引用的位置可能在后续同级数据成员内。

在下例中,只有当 i 是 0 或 1 时,the_particle.position[i] 是有效引用。 当 i 为 2 时,该引用为无效内存引用,但它实际上会对应于 the_particle.color_index 的内存位置。

示例:无效内存引用仍位于变量内部
struct Particle {
   position: vec2f,
   velocity: vec2f,
   color_index: i32,
}

@group(0) @binding(0)
var<storage,read_write> the_particle: Particle;

fn particle_velocity_component(p: Particle, i: i32) -> f32 {
  return the_particle.velocity[i]; // 当 i 为 0 或 1 时为有效引用。
}

6.4.6. 越界访问

无效内存引用进行访问的操作是越界访问

越界访问是程序缺陷,因为如果真的按原样执行,通常会:

因此,实现不会按原样执行该访问。 执行越界访问会产生动态错误

注意: 错误解释存储类型的例子见 上一节的示例。 当 i 为 2 时,表达式 the_particle.velocity[i] 类型为 ref<storage,f32,read_write>,即为存储类型为 f32 的内存视图。 但实际内存位置分配给了 color_index 成员,因此实际存储值类型为 i32

注意: 越界访问会导致动态错误,可能出现多种结果。

这些结果包括但不限于:

Trap

着色器调用立即终止,着色器阶段输出被置为零值。

无效加载

从无效引用加载时,可能返回:

无效存储

向无效引用写入时,可能:

如果无效加载或写入被重定向到共享地址空间变量内部的不同位置,可能导致数据竞争。 例如,多个并发执行的调用可能都被重定向到数组的第一个元素。 如果至少有一个访问是写操作,且未同步,则结果是数据竞争,从而导致动态错误。

越界访问会破坏一致性分析的前提。 例如,若调用因越界访问提前终止,则无法再参与集体操作。 特别是,调用 workgroupBarrier 可能导致着色器挂起,求导操作可能产出无效结果。

6.4.7. 引用和指针的用例

引用和指针的区别体现在它们的用法:

这样定义引用可简化变量的使用:

示例:引用类型简化变量的使用
@compute @workgroup_size(1)
fn main() {
  // 'i' 的引用类型为 ref<function,i32,read_write>
  // 'i' 的内存位置存储 i32 值 0。
  var i: i32 = 0;

  // 'i + 1' 只能匹配类型规则,要求 'i' 子表达式为 i32 类型。
  // 所以 'i + 1' 表达式类型为 i32,求值时 'i' 子表达式为 'i' 当前内存存储的 i32 值。
  let one: i32 = i + 1;

  // 更新 'i' 所引用内存位置的值为 2。
  i = one + 1;

  // 更新 'i' 所引用内存位置的值为 5。
  // 右值先求值后再赋值。
  i = i + 3;
}
示例:返回引用即返回通过引用加载的值
var<private> age: i32;
fn get_age() -> i32 {
  // return 语句中的表达式类型必须为 'i32',需要与函数声明的返回类型一致。
  // 'age' 表达式类型为 ref<private,i32,read_write>。
  // 应用加载规则(Load Rule),因为引用存储类型与表达式要求类型一致,无其他类型规则匹配。
  // 在此上下文对 'age' 求值即为当前 'age' 所引用内存中的 i32 值。
  return age;
}

fn caller() {
  age = 21;
  // copy_age 常量将获得 i32 值 21。
  let copy_age: i32 = get_age();
}

这样定义指针可实现两个关键用法:

示例:用指针为变量部分取别名
struct Particle {
  position: vec3<f32>,
  velocity: vec3<f32>
}
struct System {
  active_index: i32,
  timestep: f32,
  particles: array<Particle,100>
}
@group(0) @binding(0) var<storage,read_write> system: System;

@compute @workgroup_size(1)
fn main() {
  // 创建指向 storage 内存中特定 Particle 的指针。
  let active_particle = &system.particles[system.active_index];

  let delta_position: vec3<f32> = (*active_particle).velocity * system.timestep;
  let current_position: vec3<f32>  = (*active_particle).position;
  (*active_particle).position = delta_position + current_position;
}
示例:用指针作为形参
fn add_one(x: ptr<function,i32>) {
  /* 更新 'x' 的内存位置为下一个更大的整数值(或溢出为 i32 的最大负值)。
     左值一元 * 将指针转为可赋值的引用,默认访问模式为 read_write。
     右值:
        - 一元 * 将指针转为引用,访问模式为 read_write。
        - 唯一匹配的类型规则为加法(+),要求 '*x' 类型为 i32,即 '*x' 的存储类型。
        - 应用加载规则,'*x' 求值为 '*x' 当前内存的值,即 i32 的 0。
        - 加 1 得右值为 1。*/
     将 1 存入 '*x' 的内存。*/
  *x = *x + 1;
}

@compute @workgroup_size(1)
fn main() {
  var i: i32 = 0;

  // 修改 'i' 的内容为 1。
  // 用一元 & 获取 'i' 的指针值。
  // 这明确表示被调函数拥有 'i' 的内存访问权,可以修改它。
  add_one(&i);
  let one: i32 = i;  // 'one' 的值为 1。
}

6.4.8. 引用和指针值的生成

引用值通过以下方式之一生成:

在所有情况下,结果的访问模式与原始引用的访问模式相同。

示例:复合引用的分量引用
struct S {
    age: i32,
    weight: f32
}
var<private> person: S;
// 在其他地方,'person' 表示变量底层内存的引用,类型为 ref<private,S,read_write>。

fn f() {
    var uv: vec2<f32>;
    // 该函数体的剩余部分中,'uv' 表示底层内存的引用,类型为 ref<function,vec2<f32>,read_write>。

    // 赋值左值求值过程:
    //   对 'uv.x' 求值以获得引用:
    //   1. 首先对 'uv' 求值,得到 'uv' 变量内存的引用,类型为 ref<function,vec2<f32>,read_write>。
    //   2. 然后应用 '.x' 向量访问短语,得到前一步引用的向量的第一个分量的内存引用。
    //      类型为 ref<function,f32,read_write>。
    // 赋值右值求值结果为 f32 值 1.0。
    // 将 f32 值 1.0 存入 uv.x 的内存位置。
    uv.x = 1.0;

    // 赋值左值求值过程:
    //   对 'uv[1]' 求值以获得引用:
    //   1. 首先对 'uv' 求值,得到 'uv' 变量内存的引用,类型为 ref<function,vec2<f32>,read_write>。
    //   2. 然后应用 '[1]' 数组索引短语,得到前一步引用的向量的第二个分量的内存引用。
    //      类型为 ref<function,f32,read_write>。
    // 赋值右值求值结果为 f32 值 2.0。
    // 将 f32 值 2.0 存入 uv[1] 的内存位置。
    uv[1] = 2.0;

    var m: mat3x2<f32>;
    // 对 'm[2]' 求值:
    // 1. 首先对 'm' 求值,得到 m 变量内存引用,类型为 ref<function,mat3x2<f32>,read_write>。
    // 2. 应用 '[2]' 索引短语,得到第3列向量的内存引用,类型为 ref<function,vec2<f32>,read_write>。
    // let 声明需要初值类型为 vec2<f32>,应用加载规则,初始化值为 'm[2]' 内存当前载入的 vec2<f32>。
    let p_m_col2: vec2<f32> = m[2];

    var A: array<i32,5>;
    // 对 'A[4]' 求值:
    // 1. 首先对 'A' 求值,得到 A 变量内存引用,类型为 ref<function,array<i32,5>,read_write>。
    // 2. 应用 '[4]' 索引短语,得到数组第5个元素的内存引用,类型为 ref<function,i32,read_write>。
    // let 声明要求右值类型为 i32,应用加载规则,初值为 'A[4]' 内存当前载入的 i32 值。
    let A_4_value: i32 = A[4];

    // 对 'person.weight' 求值:
    // 1. 首先对 'person' 求值,得到模块作用域变量 'person' 的内存引用,类型为 ref<private,S,read_write>。
    // 2. 应用 '.weight' 成员访问短语,得到第二个成员的内存引用,类型为 ref<private,f32,read_write>。
    // let 声明要求右值类型为 f32,应用加载规则,初值为 'person.weight' 当前载入的 f32 值。
    let person_weight: f32 = person.weight;

    // 也可以用指针生成引用,语法相同。

    let uv_ptr = &uv;
    // 该函数体剩余部分,'uv_ptr' 表示 'uv' 内存的指针,类型为 ptr<function,vec2<f32>,read_write>。

    // 赋值左值求值:
    //   对 '*uv_ptr' 求值,得到引用:
    //   1. 对 'uv_ptr' 求值,得到 'uv' 变量内存的指针,类型为 ptr<function,vec2<f32>,read_write>。
    //   2. 应用间接寻址表达式运算符,得到 'uv' 的内存引用。
    // 右值为 vec2<f32> 值 (1.0, 2.0),写入 uv 的内存。
    *uv_ptr = vec2f(1.0, 2.0);

    // 赋值左值求值:
    //   对 'uv_ptr.x' 求值,得到引用:
    //   1. 对 'uv_ptr' 求值,得到 'uv' 变量内存的指针,类型为 ptr<function,vec2<f32>,read_write>。
    //   2. 应用 '.x' 向量访问短语,得到第一个分量内存引用,类型为 ref<function,f32,read_write>。
    // 右值为 f32 值 1.0,写入 uv.x 的内存。
    uv_ptr.x = 1.0;

    // 赋值左值求值:
    //   对 'uv_ptr[1]' 求值,得到引用:
    //   1. 对 'uv_ptr' 求值,得到 'uv' 变量内存的指针,类型为 ptr<function,vec2<f32>,read_write>。
    //   2. 应用 '[1]' 索引短语,得到向量第二分量的内存引用,类型为 ref<function,f32,read_write>。
    // 右值为 f32 值 2.0,写入 uv[1] 的内存。
    uv_ptr[1] = 2.0;

    let m_ptr = &m;
    // 对 'm_ptr[2]' 求值:
    // 1. 对 'm_ptr' 求值,得到 m 变量内存的指针,类型为 ptr<function,mat3x2<f32>,read_write>。
    // 2. 应用 '[2]' 索引短语,得到第3列向量内存引用,类型为 ref<function,vec2<f32>,read_write>。
    // let 声明需初值为 vec2<f32>,应用加载规则,初值为 m[2] 当前载入的 vec2<f32>。
    let p_m_col2: vec2<f32> = m_ptr[2];

    let A_ptr = &A;
    // 对 'A[4]' 求值:
    // 1. 对 'A_ptr' 求值,得到 A 变量内存的指针,类型为 ptr<function,array<i32,5>,read_write>。
    // 2. 应用 '[4]' 索引短语,得到数组第5个元素内存引用,类型为 ref<function,i32,read_write>。
    // let 声明需右值为 i32,应用加载规则,初值为 'A[4]' 当前载入的 i32 值。
    let A_4_value: i32 = A_ptr[4];

    let person_ptr = &person;
    // 对 'person.weight' 求值:
    // 1. 对 'person_ptr' 求值,得到模块作用域变量 'person' 的指针,类型为 ptr<private,S,read_write>。
    // 2. 应用 '.weight' 成员访问短语,得到第二个成员的内存引用,类型为 ref<private,f32,read_write>。
    // let 声明需右值为 f32,应用加载规则,初值为 'person.weight' 当前载入的 f32 值。
    let person_weight: f32 = person_ptr.weight;
}

指针值通过以下方式之一生成:

在所有情况下,结果的访问模式与原始指针的访问模式相同。

示例:由变量生成指针
// 在 private 地址空间声明存储 f32 值的变量。
var<private> x: f32;

fn f() {
    // 在 function 地址空间声明存储 i32 值的变量。
    var y: i32;

    // 'x' 解析到模块作用域变量 'x',类型为 ref<private,f32,read_write>。
    // 一元 & 操作将引用转换为指针,访问模式同原变量,完整类型为 ptr<private,f32,read_write>。
    // read_write 为 function 地址空间默认访问模式,可省略。
    let x_ptr: ptr<private,f32> = &x;

    // 'y' 解析到函数作用域变量 'y',类型为 ref<private,i32,read_write>。
    // 一元 & 操作转换为指针,访问模式默认 read_write。
    let y_ptr: ptr<function,i32> = &y;

    // 新变量,与模块作用域变量同名但不同实例。
    var x: u32;

    // 此处 'x' 解析为上面声明的函数作用域变量,类型为 ref<function,u32,read_write>。
    // 一元 & 操作转换为指针,访问模式默认 read_write。
    let inner_x_ptr: ptr<function,u32> = &x;
}

6.4.9. 与其他语言中引用和指针的比较

本节为说明性内容,并非规范要求。

WGSL 中的引用和指针比其他语言受限得多。 具体包括:

注意: 根据上述规则,不可能形成“悬空”指针,即不再引用“活跃”起源变量内存的指针。 内存视图虽然可能是无效内存引用, 但它永远不会访问未关联于 起源变量或缓冲区的内存位置

6.5. 纹理与采样器类型

纹素(texel)是用作纹理中最小独立访问单元的标量或向量。 texeltexture element 的缩写。

纹理是支持用于渲染的特殊操作的一组纹素。 在 WGSL 中,这些操作通过纹理内建函数进行调用。 完整列表见 § 17.7 纹理内建函数

WGSL 的纹理对应于 WebGPU GPUTexture

纹理具有以下特性:

纹素格式

每个纹素的数据表示。见 § 6.5.1 纹素格式

维度

网格坐标的维数,以及坐标的解释方式。 维数为 1、2 或 3。 大多数纹理使用笛卡尔坐标。 立方体纹理有六个正方形面,采样时以三维坐标作为从原点指向以原点为中心立方体的方向向量。

参见 GPUTextureViewDimension

尺寸

每个维度上网格坐标的范围。这是mip level的函数。

mip 层级数

采样纹理深度纹理的 mip 层级数至少为 1,存储纹理的值恒为 1。
Mip level 0 包含完整尺寸的纹理。 每个后续 mip 层级包含上一级经过滤波、尺寸减半(四舍五入)的版本。
采样时,使用显式或隐式计算的细节级别选择 mip 层级,然后通过滤波合成采样值。

数组化

纹理是否为数组。

  • 非数组纹理是一个纹素网格。

  • 数组纹理是一组同构的纹素网格数组。

数组大小

若纹理为数组,则为同构网格数量。

采样数

若纹理为多重采样,则为采样数。

纹理中的每个纹素都关联唯一的逻辑纹素地址, 它是一个整数元组,包括:

纹理的物理组织通常针对渲染操作做了优化。 因此,许多细节对程序员是隐藏的,包括数据布局、数据类型和着色器语言无法直接表达的内部操作。

因此,着色器无法直接访问纹理变量内的纹素内存。 只能通过不透明句柄访问:

这样,纹理类型支持的操作集由是否有带该纹理类型形参的内建函数决定。

注意: 纹理变量存储的句柄无法被着色器更改。 即使底层纹理可变(如写入型存储纹理),该变量也是只读的。

纹理类型为以下各节定义的类型集合:

采样器是不透明句柄,用于控制从采样纹理深度纹理获取纹素的方式。

WGSL 采样器对应 WebGPU GPUSampler

采样器通过多个属性控制纹素访问:

寻址模式

控制纹理边界和越界坐标的处理方式。 每个维度的寻址模式可独立设置。 见 WebGPU GPUAddressMode

滤波模式

控制采样时访问哪些纹素生成最终结果。 滤波可使用最近纹素或在多个纹素间插值。 多个滤波模式可独立设置。 见 WebGPU GPUFilterMode

LOD 限定

控制可访问的细节级别最小和最大值。

比较

控制比较采样器的比较类型。 见 WebGPU GPUCompareFunction

最大各向异性

控制采样器使用的最大各向异性值。

采样器无法在 WGSL 模块中创建,其状态(如上述属性)在着色器内不可变,只能由 WebGPU API 设置。

如使用插值滤波的采样器(即任何插值滤波采样器)与非滤波格式纹理配合,则为管线创建错误

注意: 采样器变量存储的句柄无法被着色器更改。

6.5.1. 纹素格式

在 WGSL 中,某些纹理类型以纹素格式为参数。

纹素格式的特征包括:

通道

每个通道包含一个标量。 一个纹素格式最多有四个通道:rgba, 通常对应红、绿、蓝和 alpha 通道的概念。

通道格式

通道中位数,以及这些位的解释方式。

WGSL 中的每个纹素格式都对应一个同名的 WebGPU GPUTextureFormat

WGSL 源码中只使用特定的纹素格式。 用于定义这些纹素格式的通道格式列于 通道格式表中。 倒数第二列指定了从存储通道位到着色器内值的转换方式, 也称为通道传递函数(CTF)。 最后一列指定了从着色器值到存储通道位的转换方式, 也称为逆通道传递函数(ICTF)。

注意: 8unorm 的通道传递函数将 {0,...,255} 映射到浮点区间 [0.0, 1.0]。

注意: 8snorm 的通道传递函数将 {-128,...,127} 映射到浮点区间 [-1.0, 1.0]。

通道格式
通道格式 存储位数 存储位解释 着色器类型 着色器值(通道传递函数) 写入值 T(逆通道传递函数)
8unorm 8 无符号整数 v ∈ {0,...,255} f32 v ÷ 255 max(0, min(1, T))
8snorm 8 有符号整数 v ∈ {-128,...,127} f32 v ÷ 127 max(-1, min(1, T))
8uint 8 无符号整数 v ∈ {0,...,255} u32 v min(255, T)
8sint 8 有符号整数 v ∈ {-128,...,127} i32 v max(-128, min(127, T))
16unorm 16 无符号整数 v ∈ {0,...,65535} f32 v ÷ 65535 max(0, min(1, T))
16snorm 16 有符号整数 v ∈ {-32768,...,32767} f32 v ÷ 32767 max(-1, min(1, T))
16uint 16 无符号整数 v ∈ {0,...,65535} u32 v min(65535, T)
16sint 16 有符号整数 v ∈ {-32768,...,32767} i32 v max(-32768, min(32767, T))
16float 16 IEEE-754 binary16 16位浮点值 v f32 v quantizeToF16(T)
32uint 32 32位无符号整数值 v u32 v T
32sint 32 32位有符号整数值 v i32 v T
32float 32 IEEE-754 binary32 32位浮点值 v f32 v T
2unorm 2 无符号整数 v ∈ {0,...,3} f32 v ÷ 3 max(0, min(1, T))
2uint 2 无符号整数 v ∈ {0,...,3} u32 v min(3, T)
10unorm 10 无符号整数 v ∈ {0,...,1023} f32 v ÷ 1023 max(0, min(1, T))
10uint 10 无符号整数 v ∈ {0,...,1023} u32 v min(1023, T)
10float 10 10位浮点值:5位偏置指数,5位小数 v f32 v max(0, T)
11float 11 11位浮点值:5位偏置指数,6位小数 v f32 v max(0, T)

下表所列的纹素格式 存储纹理的纹素格式 对应于支持 WebGPU STORAGE_BINDING 用法(至少支持一种访问模式)的WebGPU 普通颜色格式。 这些纹素格式用于参数化 存储纹理类型,定义见 § 6.5.5 存储纹理类型

当纹素格式不包含所有四个通道时:

下表最后一列采用了通道传递函数,其定义见通道格式表。

存储纹理的纹素格式
纹素格式 通道格式 内存顺序通道 对应着色器值
rgba8unorm 8unorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba8snorm 8snorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba8uint 8uint r, g, b, a vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba8sint 8sint r, g, b, a vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16unorm 16unorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16snorm 16snorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16uint 16uint r, g, b, a vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16sint 16sint r, g, b, a vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16float 16float r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rg8unorm 8unorm r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rg8snorm 8snorm r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rg8uint 8uint r, g vec4<u32>(CTF(r), CTF(g), 0u, 1u)
rg8sint 8sint r, g vec4<i32>(CTF(r), CTF(g), 0, 1)
rg16unorm 16unorm r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rg16snorm 16snorm r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rg16uint 16uint r, g vec4<u32>(CTF(r), CTF(g), 0u, 1u)
rg16sint 16sint r, g vec4<i32>(CTF(r), CTF(g), 0, 1)
rg16float 16float r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
r32uint 32uint r vec4<u32>(CTF(r), 0u, 0u, 1u)
r32sint 32sint r vec4<i32>(CTF(r), 0, 0, 1)
r32float 32float r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
rg32uint 32uint r, g vec4<u32>(CTF(r), CTF(g), 0u, 1u)
rg32sint 32sint r, g vec4<i32>(CTF(r), CTF(g), 0, 1)
rg32float 32float r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rgba32uint 32uint r, g, b, a vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba32sint 32sint r, g, b, a vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba32float 32float r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
bgra8unorm 8unorm b, g, r, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
r8unorm 8unorm r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
r8snorm 8snorm r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
r8uint 8uint r vec4<u32>(CTF(r), 0u, 0u, 1u)
r8sint 8sint r vec4<i32>(CTF(r), 0, 0, 1)
r16unorm 16unorm r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
r16snorm 16snorm r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
r16uint 16uint r vec4<u32>(CTF(r), 0u, 0u, 1u)
r16sint 16sint r vec4<i32>(CTF(r), 0, 0, 1)
r16float 16float r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
rgb10a2unorm r, g, b: 10unorm a: 2unorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgb10a2uint r, g, b: 10uint a: 2uint r, g, b, a vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a))
rg11b10ufloat r, g: 11float b: 10float r, g, b vec4<f32>(CTF(r), CTF(g), CTF(b), 1.0)

WGSL 为表中每个纹素格式预声明一个枚举项

6.5.2. 采样纹理类型

采样纹理可以结合采样器 进行访问。 也可以无需采样器进行访问。 采样纹理只允许读访问

纹素格式format 属性,位于绑定到纹理变量的 GPUTexture 上。 WebGPU 校验纹理、本地绑定组布局的 sampleType 以及纹理变量的采样类型的兼容性。

纹理由采样类型参数化,且必须f32i32u32

类型 维度 数组化
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

6.5.3. 多重采样纹理类型

多重采样纹理有一个 采样数,不少于 1。 尽管名为“多重采样”,它不能与采样器一起使用。 如果忽略采样索引,则每个逻辑纹素地址都有效存储多个纹素数据。

纹素格式format 属性,位于绑定到纹理变量的 GPUTexture 上。 WebGPU 校验纹理、本地绑定组布局的 sampleType 以及纹理变量的采样类型的兼容性。

texture_multisampled_2d采样类型参数化,且必须f32i32u32

类型 维度 数组化
texture_multisampled_2d<T> 2D
texture_depth_multisampled_2d 2D

6.5.4. 外部采样纹理类型

外部纹理是一种不透明的二维浮点采样纹理类型,类似于 texture_2d<f32>,但其底层表示可能不同。 可以通过 textureLoadtextureSampleBaseClampToEdge 内建函数读取,这些函数会处理不同的底层表示。

参见 WebGPU § 6.4 GPUExternalTexture

类型 维度 数组化
texture_external 2D

6.5.5. 存储纹理类型

存储纹理支持无需采样器即可访问单个纹素值。

存储纹理类型必须存储纹理的纹素格式之一为参数。 纹素格式决定了转换函数,详见 § 6.5.1 纹素格式

写入存储纹理时,使用转换函数的将着色器值转换为存储纹素。

类型 维度 数组化
texture_storage_1d<Format, Access> 1D
texture_storage_2d<Format, Access> 2D
texture_storage_2d_array<Format, Access> 2D
texture_storage_3d<Format, Access> 3D

6.5.6. 深度纹理类型

深度纹理可以结合sampler_comparison进行访问。 也可以无需采样器进行访问。 深度纹理只允许读访问

深度纹理的纹素格式定义在 GPUTextureBindingLayout 中。

类型 维度 数组化
texture_depth_2d 2D
texture_depth_2d_array 2D
texture_depth_cube Cube
texture_depth_cube_array Cube

6.5.7. 采样器类型

采样器通过以下方式协调对采样纹理深度纹理的访问:

采样器类型包括:

类型 说明
sampler 采样器。协调对采样纹理的访问。
sampler_comparison 比较采样器。 协调对深度纹理的访问。

采样器在 WebGPU API 创建时被参数化。 WGSL 模块不能修改采样器。

采样器只能被纹理内建函数使用。

sampler
sampler_comparison

6.6. AllTypes 类型

AllTypes 类型是所有 WGSL 类型的集合。

无法在 WGSL 源码中写出 AllTypes 类型。

§ 6.9 预声明类型与类型生成器总览,其中列出了所有预声明类型和类型生成器

注意:类型不是普通意义上的值。 它不是着色器在运行时操作的数据。

AllTypes 类型的存在,是为了让类型检查规则适用于任何可能包含普通值的语句。 WGSL 通过把类型定义为一种值,并允许表达式表示类型,使得规则保持一致。

典型场景是模板参数,它在不同上下文中可表示多种事物,包括类型枚举项普通值。 特别是,template_arg_expression 语法规则会扩展为 expression 语法非终结符。

6.7. 类型别名

类型别名为某个已存在类型声明一个新名称。 声明必须出现在模块作用域,其作用域为整个程序。

当类型 T 被定义为结构体类型 S类型别名时, S 的所有成员属性(包括属性)都会传递到 T 的成员上。

type_alias_decl :

'alias' ident '=' type_specifier

示例:类型别名
alias Arr = array<i32, 5>;

alias RTArr = array<vec4<f32>>;

alias single = f32;     // 声明 f32 的别名
const pi_approx: single = 3.1415;
fn two_pi() -> single {
  return single(2) * pi_approx;
}

6.8. 类型说明符语法

§ 8.17 类型表达式

type_specifier :

template_elaborated_ident

template_elaborated_ident :

ident _disambiguate_template template_list ?

注意: 表达式也可以表示类型,通过 primary_expression 语法规则扩展为 template_elaborated_ident, 也可以通过括号表达式

6.9. 预声明类型与类型生成器总览

可以在 WGSL 源码中书写的预声明类型有:

WGSL 还会为 frexpmodf 以及 atomicCompareExchangeWeak 内建函数预声明返回类型, 但这些类型不能在 WGSL 源码中书写。

预声明的类型生成器如下表:

预声明类型生成器
预声明类型生成器 交叉引用
array § 6.2.9 数组类型
atomic § 6.2.8 原子类型
mat2x2 § 6.2.7 矩阵类型,其中也列出了矩阵类型的别名

注意: 这些类型也用于值构造函数表达式中创建矩阵。

mat2x3
mat2x4
mat3x2
mat3x3
mat3x4
mat4x2
mat4x3
mat4x4
ptr § 6.4.3 引用类型与指针类型
texture_1d § 6.5.2 采样纹理类型
texture_2d
texture_2d_array
texture_3d
texture_cube
texture_cube_array
texture_multisampled_2d § 6.5.3 多重采样纹理类型
texture_storage_1d § 6.5.5 存储纹理类型
texture_storage_2d
texture_storage_2d_array
texture_storage_3d
vec2 § 6.2.6 向量类型,其中也列出了向量类型的别名

注意: 这些类型也用于值构造函数表达式中创建向量。

vec3
vec4

7. 变量与值声明

变量声明为数据值提供名称。

值声明 创建一个值的名称,该值在声明后不可变。 有四种值声明:constoverridelet 和形参声明, 详见下文(参见§ 7.2 值声明)。

变量声明为存储值的内存位置创建名称; 如果变量的read_write访问模式允许,存储的值可以被更新。 变量声明只有一种:var,但可以与地址空间访问模式组合使用,详见下文(参见§ 7.3 var 声明)。

注意:值声明不关联内存位置。例如,WGSL 表达式无法形成指向该值的指针。

在任何函数定义之外出现的声明属于模块作用域。 其名称在整个程序中有效

在函数定义内部出现的声明处于函数作用域。 该名称可在声明后紧接的语句及其所在的大括号语句块范围内被使用。 函数作用域声明属于动态上下文

注意:
变量和值声明的整体语法类似。下图为非规范性说明,展现变量和值声明的一般形式,其中 [...] 表示可选项,...* 表示前项可重复零次或多次,...+ 表示前项至少出现一次。具体语法规则见各元素对应章节。
// 具体的值声明。
             const    name [: type]  = initializer ;
[attribute]* override name [:type] [= initializer];
             let      name [: type]  = initializer ;

// 一般变量形式。
[attribute]* var[<address_space[, access_mode]>] name [: type] [= initializer];

// 具体变量声明。
// 函数作用域。
             var[<function>] name [: type] [= initializer];

// 模块作用域。
             var<private>    name [: type] [= initializer];
             var<workgroup>  name : type;
[attribute]+ var<uniform>    name : type;
[attribute]+ var             name : texture_type;
[attribute]+ var             name : sampler_type;
[attribute]+ var<storage[, access_mode]> name : type;

每个这样的声明必须显式指定类型或初始值。 类型和初始值都可以同时指定。 每个这样的声明确定与之关联的数据值的类型,称为该声明的实际值类型(effective-value-type)。其规则如下:

每种值或变量声明可能对初始值表达式的形式(如存在)和实际值类型施加额外约束。

变量和值声明特性总览
声明 可变性 作用域 实际值类型1 初始值支持 初始值表达式2 是否为资源接口的一部分
const 不可变 模块函数 可构造具体抽象必须 const-expression
override 不可变 模块 具体 标量 可选3 const-expressionoverride-expression 4
let 不可变 函数 具体可构造指针类型 必须 const-expressionoverride-expressionruntime expression
var<storage, read>
var<storage>
不可变 模块 具体 主机可共享 不允许 是。
storage buffer
var<storage, read_write>5,6 可变 模块 具体 主机可共享 不允许 是。
storage buffer
var<uniform> 不可变 模块 具体 可构造 主机可共享 不允许 是。
uniform buffer
var6 不可变7 模块 纹理 不允许 是。
texture resource
var 不可变 模块 采样器 不允许 是。
sampler resource
var<workgroup>6,8 可变 模块 具体 普通类型,且具备固定占用9 不允许10
var<private> 可变 模块 具体 可构造 可选10 const-expressionoverride-expression
var<function>
var
可变 函数 具体 可构造 可选10 const-expressionoverride-expressionruntime expression
  1. 只有const 声明可以是抽象类型,并且仅当未显式指定类型时才可。

  2. 表达式类型必须可自动转换实际值类型

  3. 若未指定初始值,则必须在管线创建时提供值。

  4. override 声明属于着色器接口,但不是绑定资源。

  5. 存储缓冲区存储纹理(访问模式非read)不能在 顶点着色器阶段静态访问。 详见 WebGPU createBindGroupLayout()

  6. 原子类型只能出现在可变存储缓冲区或 workgroup 变量中。

  7. 存储纹理中,访问模式为writeread_write访问模式数据是可变的,但只能通过textureStore内建函数修改。 变量本身不能被修改。

  8. workgroup 地址空间中的变量只能在计算着色器阶段静态访问

  9. 最外层数组元素数量可以是override-expression

  10. 若无初始值,变量会默认初始化

7.1. 变量与值

变量声明是 WGSL 模块中唯一可变的数据。 值声明始终是不可变的。 变量可以作为引用类型指针类型值的基础,因为变量有相关联的内存位置, 而值声明不能作为指针或引用值的基础。

通常,使用变量比使用值声明代价更高, 因为对变量的使用需要额外的操作来读取写入变量关联的内存位置

一般来说,建议作者按如下顺序优先使用声明,最优先的在前:

这样通常能获得着色器的最佳整体性能。

7.2. 值声明

标识符解析值声明时,该标识符即代表该值。

WGSL 提供多种值声明。 每种声明的值在着色器生命周期的不同阶段被固定。 不同种类的值声明及其值被固定的时机如下:

注意: 形参§ 11 函数

7.2.1. const 声明

const 声明 为一个在着色器创建时固定的数据值指定名称。 每个 const 声明都需要一个初始值。 const 声明可以在模块函数作用域中声明。 初始值表达式必须const-expression。 const 声明的类型必须是具体抽象可构造类型。 const 声明是唯一允许实际值类型抽象的声明。

注意: 由于抽象数值类型不能在 WGSL 中直接书写,只能通过类型推导使用。

示例:模块作用域下的 const 声明
const a = 4;                  // AbstractInt 值为 4。
const b : i32 = 4;            // i32 值为 4。
const c : u32 = 4;            // u32 值为 4。
const d : f32 = 4;            // f32 值为 4。
const e = vec3(a, a, a);      // vec3 的 AbstractInt,值为 (4, 4, 4)。
const f = 2.0;                // AbstractFloat 值为 2。
const g = mat2x2(a, f, a, f); // mat2x2 的 AbstractFloat,值为:
                              // ((4.0, 2.0), (4.0, 2.0)).
                              // AbstractInt a 自动转为 AbstractFloat。
                              // AbstractFloat 不能转为 AbstractInt。
const h = array(a, f, a, f);  // AbstractFloat 组成的数组,共 4 个分量:
                              // (4.0, 2.0, 4.0, 2.0).

7.2.2. override 声明

override 声明可管线覆盖常量值指定名称。 override 声明只能模块作用域声明。 可管线覆盖常量的值在管线创建时确定。 该值由 WebGPU 管线创建 API 提供(如有),否则为其具体化初始值表达式的值。 override 声明的实际值类型必须是具体 标量类型。

初始值表达式为可选项。 如指定,必须是override-expression,表示可管线覆盖常量的默认值。 若未指定初始值,则在管线创建时未提供值即为管线创建错误

如声明应用了id属性, 该字面量操作数称为管线常量 ID,且必须为 0 至 65535 间唯一整数。 即,两个 override 声明不能使用相同的管线常量 ID。

应用可以在管线创建时为 override 声明指定自己的值。 管线创建 API 接受一个从可覆盖常量到其类型值的映射。 常量通过可管线覆盖常量标识字符串识别, 若指定了管线常量 ID则为其十进制表示,否则为常量声明的名称

示例:模块常量,可管线覆盖
@id(0)    override has_point_light: bool = true;  // 算法控制
@id(1200) override specular_param: f32 = 2.3;     // 数值控制
@id(1300) override gain: f32;                     // 必须覆盖
          override width: f32 = 0.0;              // API 层通过名称 "width" 指定
                                                  // 
          override depth: f32;                    // API 层通过名称 "depth" 指定
                                                  // 必须覆盖。
          override height = 2 * depth;            // 默认值
                                                  // (如 API 层未设置),
                                                  // 依赖于另一可覆盖常量。

7.2.3. let 声明

let 声明 为一个在运行时每次执行该语句时固定的值指定名称。 let 声明只能在函数作用域声明,因此属于动态上下文。 let 声明必须有初始值表达式。 该值为初始值的具体化值。 let 声明的实际值类型必须是具体 可构造类型或指针类型

示例:函数作用域下的 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>。 变量的存储类型总是具体的。

变量声明:

标识符解析为变量声明时, 该标识符是表示该变量内存的内存视图的表达式,其类型为变量的引用类型。 参见 § 8.11 变量标识符表达式

如变量声明的地址空间访问模式在源码中指定,则写作 var 关键字后的模板参数列表

privatestorageuniformworkgrouphandle 地址空间的变量只能在模块作用域声明, function 地址空间变量只能在函数作用域声明。 除 handle 和 function 外,所有地址空间都必须显式指定。 handle 地址空间不得指定。 function 地址空间可省略不写。

访问模式总有默认值,除 storage 地址空间外,不得在 WGSL 源码中指定。参见 § 14.3 地址空间

uniform 地址空间变量为uniform 缓冲区变量。 其存储类型必须为主机可共享可构造类型,并满足地址空间布局约束

storage 地址空间变量为storage 缓冲区变量。 其存储类型必须为主机可共享类型,且满足 地址空间布局约束。 变量可声明为readread_write访问模式,默认 read。

纹理资源实际值类型纹理类型的变量。 只能在模块作用域声明。 它保存一个不透明句柄,用于访问纹理中底层纹素的网格。 该句柄本身在handle地址空间,总是只读。 多数情况下,底层纹素只读,此时纹理变量为不可变;对于只写存储纹理读写存储纹理,底层纹素可变,约定为可变变量。

采样器资源实际值类型采样器类型的变量。 只能在模块作用域声明, 存于handle地址空间,且不可变。

§ 13.3.2 资源接口所述,uniform 缓冲区、storage 缓冲区、纹理和采样器共同构成着色器的资源接口

变量生命周期是着色器执行期间,与变量关联的内存位置的存活期。 模块作用域变量的生命周期为整个着色器阶段的执行期。 privatefunction 地址空间的变量,每次调用各有独立实例。 函数作用域变量是动态上下文。 函数作用域变量的生命周期由其作用域决定:

两个资源变量可能有重叠的内存位置, 但如任一变量可变则为动态错误。 其他变量如生命周期重叠,不会有重叠内存位置。 生命周期结束后,其内存可用于其它变量。

注意:WGSL 保证变量内容仅在变量生命周期内可见。

privatefunctionworkgroup 地址空间的变量被创建时,有初始值。 若未指定初始值,则初始值为默认初始值。 初始值计算如下:

其他地址空间的变量为资源,由绘制命令调度命令绑定设置。

考虑如下 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 片段:

示例:多次读取变量
var x: f32 = 1.0;
let y = x * x + x + 1;
因为 x 是变量,对其的所有访问都转化为 load/store 操作。 但通常浏览器或驱动会优化中间表示,消除冗余的 load 操作。
示例:模块作用域变量声明
var<private> decibels: f32;
var<workgroup> worklist: array<i32,10>;

struct Params {
  specular: f32,
  count: i32
}

// Uniform 缓冲区。始终只读,且有更严格的布局规则。
@group(0) @binding(2)
var<uniform> param: Params;    // 一个 uniform 缓冲区

// 一个可读写的 storage 缓冲区
@group(0) @binding(0)
var<storage,read_write> pbuf: array<vec2<f32>>;

// 纹理和采样器总是在 "handle" 空间。
@group(0) @binding(1)
var filter_params: sampler;
示例:缓冲区的访问模式
// Storage 缓冲区
@group(0) @binding(0)
var<storage,read> buf1: Buffer;       // 只读,不能写。
@group(0) @binding(0)
var<storage> buf2: Buffer;            // 只读,不能写。
@group(0) @binding(1)
var<storage,read_write> buf3: Buffer; // 可读可写。

struct ParamsTable {weight: f32}

// Uniform 缓冲区。始终只读,且有更严格的布局规则。
@group(0) @binding(2)
var<uniform> params: ParamsTable;     // 只读,不能写。
示例:函数作用域变量与常量
fn f() {
   var<function> count: u32;  // 函数地址空间变量。
   var delta: i32;            // 另一个函数地址空间变量。
   var sum: f32 = 0.0;        // 带初值的函数地址空间变量。
   var pi = 3.14159;          // 初值推断为 f32。
}

7.4. 变量和值声明语法总结

variable_or_value_statement :

variable_decl

| variable_decl '=' expression

| 'let' optionally_typed_ident '=' expression

| 'const' optionally_typed_ident '=' expression

variable_decl :

'var' _disambiguate_template template_list ? optionally_typed_ident

optionally_typed_ident :

ident ( ':' type_specifier ) ?

global_variable_decl :

attribute * variable_decl ( '=' expression ) ?

global_value_decl :

'const' optionally_typed_ident '=' expression

| attribute * 'override' optionally_typed_ident ( '=' expression ) ?

8. 表达式

表达式用于指定值的计算方式。

不同类型的值表达式在求值时机与表达能力之间存在权衡。求值越早,可用的操作越受限,但值可用的场景也越多。这种权衡导致不同类型的值声明有不同的灵活性。 const-expressionoverride-expression 在 GPU 执行前就被求值,因此最终 GPU 代码中只需表达式的计算结果。 此外,由于const-expression着色器创建时求值,使用场景多于 override-expression,如用于函数作用域 变量中的数组长度。 运行时表达式是既非 const-expression 也非 override-expression 的表达式。 运行时表达式在着色器执行时于 GPU 上计算。虽然可用语法元素较少,但可由更广泛的表达式组成,如其他运行时值。

8.1. 提前求值表达式

WGSL 定义了两类可在运行前求值的表达式:

8.1.1. const 表达式

可在着色器创建时求值的表达式称为 const-expression。 当且仅当表达式中所有标识符解析为以下内容时,该表达式为 const-expression:

const 表达式的类型必须解析为具备 创建时固定占用的类型。

注意: 抽象类型可以作为 const-expression 的推导类型。

const-expression E 被求值,当且仅当:

注意: 此求值规则意味着短路操作符 &&|| 会保护其右侧子表达式不被求值,除非有子表达式需要求值以确定静态类型

const-expression 可由实现 WebGPU API 的 CPU 求值。 因此对 AbstractFloat 值操作的精度要求不会高于常见 WebGPU 运行环境(如 WebAssembly [WASM-CORE-2] 和 ECMAScript [ECMASCRIPT])的要求。 具体浮点类型(如 f32)的精度要求见 § 15.7.4.1 具体浮点表达式的精度

示例:(42) 分析如下:

示例:-5 分析如下:

示例:-2147483648 分析如下:

示例:const minint = -2147483648; 分析如下:

示例:let minint = -2147483648; 分析如下:

示例:false && (10i < i32(5 * 1000 * 1000 * 1000)) 分析如下:

示例:false && array<u32, 1 + 2>(0, 1, 2)[0] == 0

8.1.2. override 表达式

可在管线创建时求值的表达式称为 override-expression。 当且仅当表达式中所有标识符解析为以下内容时,表达式为 override-expression:

注意:所有const-expression也是 override-expression。

除 const-expression 外的 override-expression 只在管线创建期间校验或求值,且仅在 API 提供的值替换 override 声明后。 如果 override 声明通过 API 被赋值,则其初始值表达式(如有)不会被求值。 否则,override-expression E 被求值,仅当:

注意: 并非所有 override-expression 都可作为override 声明的初始值,因为此类初始值必须解析具体标量类型。

示例:override x = 42; 分析如下:

示例:let y = x + 1; 分析如下:

示例:vec3(x,x,x) 分析如下:

示例:override-expression 导致的着色器创建错误
override a : i32 = 0;
override b = a / 0; // 着色器创建错误,
                    // 无论是否尝试覆盖 c
示例:override-expression 导致的管线创建错误
override a : i32 = 0;
override b = 1 / a;

// b 属于 frag1 着色器的一部分。将 frag1 编译为管线时:
// * 若 b 被覆盖,不会报错。
// * 若 a 被覆盖为非零值,不会报错。
// * 若 a 为 0 且 b 未被覆盖,则会产生管线创建错误。
@fragment
fn frag1() {
  _ = b;
}

// b 不属于 frag2 着色器。将 frag2 编译为管线时:
// 即使 b 未被覆盖且 a 为 0,也不会报错。
@fragment
fn frag2() {
}

8.2. 不确定值

在有限情况下,运行时表达式的求值可能会用到其子表达式的不支持值。

在这种情况下,求值结果是该表达式静态类型不确定值, 即静态类型的任意由实现选取的值。

对于表达式被求值的每个唯一动态上下文,可能会产生不同的不确定值。 例如,如果求值发生在循环的每次迭代中,每次迭代都可能计算出不同的值。

注意: 如果类型为浮点类型且实现支持 NaN 值,则运行时产生的不确定值可能为 NaN。

示例:不确定值示例
fn fun() {
   var extracted_values: array<i32,2>;
   const v = vec2<i32>(0,1);

   for (var i: i32 = 0; i < 2 ; i++) {
      // 在向量索引时使用运行时表达式且索引超界,
      // 会产生该向量分量类型的不确定值。
      let extract = v[i+5];

      // 此时 'extract' 为任意 i32 值。

      // 保存以备后用。
      extracted_values[i] = extract;

      if extract == extract {
         // 此分支总会执行
      }
      if extract < 2 {
         // 该分支可能执行,也可能不执行。
         // 尽管原向量分量为 0 和 1,
         // 提取值可能不是这两个值。
      }
   }
   if extracted_values[0] == extracted_values[1] {
      // 该分支可能执行,也可能不执行。
   }
}

fn float_fun(runtime_index: u32) {
   const v = vec2<f32>(0,1); // 浮点向量
   // 和上例一样,'float_extract' 是不确定值。
   // 因为是浮点类型,可能为 NaN。
   let float_extract: f32 = v[runtime_index+5];

   if float_extract == float_extract {
      // 该分支可能不会执行,因为:
      //  - 'float_extract' 可能为 NaN,且
      //  - NaN 从不等于任何浮点值,哪怕另一个 NaN。
   }
}

8.3. 字面值表达式

标量字面量类型规则
前置条件 结论 说明
true: bool true 布尔值。
false: bool false 布尔值。
e 是无后缀整数字面量 e: AbstractInt 抽象整数字面值。
e 是无后缀浮点字面量 e: AbstractFloat 抽象浮点字面值。
e 是带 i 后缀的整数字面量 e: i32 32 位有符号整数字面值。
e 是带 u 后缀的整数字面量 e: u32 32 位无符号整数字面值。
e 是带 f 后缀的浮点字面量 e: f32 32 位浮点字面值。
e 是带 h 后缀的浮点字面量 e: f16 16 位浮点字面值。

8.4. 括号表达式

括号表达式类型规则
前置条件 结论 说明
e : T ( e ) : T 结果为 e
用括号将表达式与周围文本隔离。

8.5. 复合值分解表达式

本节描述获取组件复合值的表达式, 以及从指向包含复合值的内存视图获取组件引用的表达式。 这里复合值或其内存视图统称为base

有两种方式:

命名组件表达式

base B 的表达式后紧跟一个句点 '.' (U+002D),再接组件名。

  • B向量结构体类型,或指向向量/结构体类型的内存视图时支持。

  • 可用的名称取决于 B 的类型。

索引表达式

base 的表达式后接 '[' (U+005B),再接索引表达式,最后 ']' (U+005D)。

语法上,这两种形式通过component_or_swizzle_specifier 语法规则体现。

索引表达式的索引值 i有效索引,当且仅当 0 ≤ i < N,其中 N 为复合类型的组件(元素)数:

索引值为非有效索引时为越界索引。越界索引通常为编程缺陷,通常会导致错误。详见下文。

此外,向量类型支持分量混合(swizzle)语法,可用另一个向量的分量创建新向量。

8.5.1. 向量分量访问表达式

可以通过以下方式访问向量的分量:

便捷名称通过 . 符号访问。(如 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: T
e.r: T
选择 e 的第一个分量

这是一个单字母分量混合

e: vecN<T>
e.y: T
e.g: T
选择 e 的第二个分量

这是一个单字母分量混合

e: vecN<T>
N is 3 or 4
e.z: T
e.b: T
选择 e 的第三个分量

这是一个单字母分量混合

e: vec4<T> e.w: T
e.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抽象类型
iconst-expression
e[i]: T 选择向量的第 i 个分量
第一个分量下标为 i=0。

i 不在 [0,N-1] 范围内,则为着色器创建错误

注意: 当抽象向量值 e 由非常量表达式索引时,索引前会对向量进行具体化

8.5.1.2. 向量多分量选择

本节中的表达式均为多字母分量混合(swizzle)。 每个表达式都通过另一个向量的分量组成新的向量

多字母swizzle不能出现在赋值语句左侧: 赋值语句左侧必须是引用类型, 而多字母混合表达式总是产生向量类型的值。

向量分解:多分量选择
前置条件 结论 说明
e: vecN<T> 或 ptr<AS,vecN<T,AM>>
Ixyzw 字母之一
Jxyzw 字母之一
AMreadread_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>>
Irgba 字母之一
Jrgba 字母之一
AMreadread_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>>
IJKxyzw
AMreadread_write
e.IJK: vec3<T>
计算一个三分量向量,分量依次为 e.Ie.Je.K
字母 z 仅当 N 为 3 或 4 时有效。
字母 w 仅当 N 为 4 时有效。

e 是指针,先应用间接寻址,再应用加载规则
e: vecN<T> 或 ptr<AS,vecN<T,AM>>
IJKrgba
AMreadread_write
e.IJK: vec3<T>
计算一个三分量向量,分量依次为 e.Ie.Je.K
字母 b 仅当 N 为 3 或 4 时有效。
字母 a 仅当 N 为 4 时有效。

e 是指针,先应用间接寻址,再应用加载规则
e: vecN<T> 或 ptr<AS,vecN<T,AM>>
IJKLxyzw
AMreadread_write
e.IJKL: vec4<T>
计算一个四分量向量,分量依次为 e.Ie.Je.Ke.L
字母 z 仅当 N 为 3 或 4 时有效。
字母 w 仅当 N 为 4 时有效。

e 是指针,先应用间接寻址,再应用加载规则
e: vecN<T> 或 ptr<AS,vecN<T,AM>>
IJKLrgba
AMreadread_write
e.IJKL: vec4<T>
计算一个四分量向量,分量依次为 e.Ie.Je.Ke.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抽象
iconst-expression
e[i]: vecR<T> 结果为 e 的第 i 个列向量。

i 不在 [0,C-1] 范围内,则为着色器创建错误

注意: 当抽象矩阵值 e 由非常量表达式索引时,索引前会对矩阵进行具体化

通过内存视图获取矩阵列向量引用
前置条件 结论 说明
r: ref<AS,matCxR<T>,AM> 或
ptr<AS,matCxR<T>,AM>
i: i32 或 u32
r[i] : ref<AS,vecR<T>,AM> 计算 内存视图 r 所引用矩阵的第 i 个列向量的引用。

i 不在 [0,C-1] 范围内:

结果引用的原始变量r 相同。

8.5.3. 数组访问表达式

数组元素提取
前置条件 结论 说明
e: array<T,N>
i: i32 或 u32
T具体
e[i] : T 结果为数组值 e 的第 i 个元素的值。

i 不在 [0,N-1] 范围内:

e: array<T,N>
i: i32 或 u32
T抽象
iconst-expression
e[i] : T 结果为数组值 e 的第 i 个元素的值。

i 不在 [0,N-1] 范围内,则为着色器创建错误

注意: 当抽象数组值 e 由非常量表达式索引时,索引前会对数组进行具体化

通过内存视图获取数组元素引用
前置条件 结论 说明
r: ref<AS,array<T,N>,AM> 或
ptr<AS,array<T,N>,AM>
i: i32 或 u32
r[i] : ref<AS,T,AM> 计算 内存视图 r 所引用数组的第 i 个元素的引用。

i 不在 [0,N-1] 范围内:

结果引用的原始变量r 相同。

r: ref<AS,array<T>,AM> 或
ptr<AS,array<T>,AM>
i: i32 或 u32
r[i] : ref<AS,T,AM> 计算 内存视图 r 所引用运行时长度数组的第 i 个元素的引用。

若运行时数组有 N 个元素,且 i 不在 [0,N-1] 范围内,则表达式结果为无效内存引用

i 为有符号整数,且 i 小于 0:

结果引用的原始变量r 相同。

8.5.4. 结构体访问表达式

结构体成员提取
前置条件 结论 说明
S 为结构体类型
MS 的成员标识符名,类型为 T
e: S
e.M: T 结果为结构体值 e 中名为 M 的成员的值。
通过结构体内存视图获取成员引用
前置条件 结论 说明
S 为结构体类型
MS 的成员标识符名,类型为 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 逻辑非运算。 当 efalse 时结果为 trueetrue 时结果为 false。 若 T 是向量,则为分量级运算。
二元逻辑表达式
前置条件 结论 说明
e1: bool
e2: bool
e1 || e2: bool 短路“或”。如果 e1e2 为 true,则结果为 true; 仅当 e1 为 false 时才会对 e2 求值。
e1: bool
e2: bool
e1 && e2: bool 短路“与”。只有当 e1e2 都为 true 时结果为 true; 仅当 e1 为 true 时才会对 e2 求值。
e1: T
e2: T
T 为 bool 或 vecN<bool>
e1 | e2: T 逻辑“或”。若 T 是向量,则为分量级运算。对 e1e2 都会求值。
e1: T
e2: T
T 为 bool 或 vecN<bool>
e1 & e2: T 逻辑“与”。若 T 是向量,则为分量级运算。对 e1e2 都会求值。

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),但不包括:

  • (0,−∞)

  • (0,+∞)

  • (−∞, 0)

  • (+∞, 0)

e1 : T
e2 : T
S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 为 S 或 vecN<S>
e1 / e2 : T 除法。当 T 为向量时为分量级操作。

T 为有符号整型标量类型,计算如下:

注意: 为保证截断行为,实现可能需要比无符号除法更多运算。 当两个操作数已知同号时,建议用无符号除法。

T 为无符号整型标量类型,计算如下:

T 为浮点类型,标量定义域 是所有扩展实数对 (x,y),但不包括:

  • (0,0)

  • (−∞,−∞)

  • (−∞,+∞)

  • (+∞,−∞)

  • (+∞,+∞)

e1 : T
e2 : T
S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 为 S 或 vecN<S>
e1 % e2 : T 取余。当 T 为向量时为分量级操作。

T 为有符号整型标量类型,e1e2 各求值一次,结果为:

注意: 非零时,结果与 e1 同号。

注意: 保证一致性可能使实现比无符号余数运算需要更多操作。

T 为无符号整型标量类型,结果为:

T 为浮点类型,结果等于:
e1 - e2 * trunc(e1 / e2)。

T 为浮点类型,标量定义域 是所有扩展实数对 (x,y),但不包括:

  • 超出 x / y 定义域的情况:

    • (0,0)

    • (−∞,−∞)

    • (−∞,+∞)

    • (+∞,−∞)

    • (+∞,+∞)

  • 超出 y * trunc(x / y) 定义域的额外情况:

    • y 为无穷,x 有限,trunc(x / y) 为 0。

    • y 为 0,x 为无穷,trunc(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
矩阵算术
前置条件 结论 语义
e1e2: 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
TS 或 vecN<S>
TBT 是向量,则为 vecN<bool>,
否则 TB 为 bool
e1 == e2: TB 等值。当 T 是向量时为分量级比较。
e1: T
e2: T
S 为 AbstractInt、AbstractFloat、bool、i32、u32、f32 或 f16
TS 或 vecN<S>
TBT 是向量,则为 vecN<bool>,
否则 TB 为 bool
e1 != e2: TB 不等值。当 T 是向量时为分量级比较。
e1: T
e2: T
S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 为 S 或 vecN<S>
TBT 是向量,则为 vecN<bool>,
否则 TB 为 bool
e1 < e2: TB 小于。当 T 是向量时为分量级比较。
e1: T
e2: T
S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 为 S 或 vecN<S>
TBT 是向量,则为 vecN<bool>,
否则 TB 为 bool
e1 <= e2: TB 小于等于。当 T 是向量时为分量级比较。
e1: T
e2: T
S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 为 S 或 vecN<S>
TBT 是向量,则为 vecN<bool>,
否则 TB 为 bool
e1 > e2: TB 大于。当 T 是向量时为分量级比较。
e1: T
e2: T
S 为 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 为 S 或 vecN<S>
TBT 是向量,则为 vecN<bool>,
否则 TB 为 bool
e1 >= e2: TB 大于等于。当 T 是向量时为分量级比较。

8.9. 位运算表达式

一元位运算
前置条件 结论 说明
e: T
S 为 AbstractInt、i32 或 u32
T 为 S 或 vecN<S>
~e : T 按位取反。 结果每一位都与 e 对应位相反。 若 T 是向量,则为分量级操作。
二元位运算
前置条件 结论 说明
e1: T
e2: T
S 为 AbstractInt、i32 或 u32
T 为 S 或 vecN<S>
e1 | e2: T 按位或。若 T 是向量,则为分量级操作。
e1: T
e2: T
S 为 AbstractInt、i32 或 u32
T 为 S 或 vecN<S>
e1 & e2: T 按位与。若 T 是向量,则为分量级操作。
e1: T
e2: T
S 为 AbstractInt、i32 或 u32
T 为 S 或 vecN<S>
e1 ^ e2: T 按位异或。若 T 是向量,则为分量级操作。
位移表达式
前置条件 结论 说明
e1: T
e2: TS
S 为 i32 或 u32
TS 或 vecN<S>
TSTS,则为 u32,否则为 vecN<u32>
e1 << e2: T 左移(被移值为具体类型):

e1 左移,在低位补零,高位溢出丢弃。

移位位数为 e2 的值,对 e1 位宽取模。
e2 大于等于 e1 的位宽,则:

e1e2 均在着色器执行开始前已知,结果不得溢出:

分量级操作,若 T 为向量。

e1: T
e2: TS
T 为 AbstractInt 或 vecN<AbstractInt>
TST 为 AbstractInt,则为 u32,否则为 vecN<u32>
e1 << e2: T 左移(被移值为抽象类型):

e1 左移,在低位补零,高位溢出丢弃。

移位位数为 e2 的值。

e1 的高 e2+1 位必须全相同。 否则会溢出。

注意: 该条件意味着所有被丢弃位必须与原值的符号位一致,且与结果符号位一致。

分量级操作,若 T 为向量。

e1: T
e2: TS
S 为 i32 或 u32
TS 或 vecN<S>
TSTS,则为 u32,否则为 vecN<u32>
e1 >> e2: T 右移(被移值为具体类型)。

e1 右移,低位丢弃。

S 为无符号类型,高位补零。

S 为有符号类型:

  • e1 为负,高位补1,结果也为负数。

  • 否则高位补0。

移位位数为 e2 的值,对 e1 位宽取模。

e2 大于等于 e1 位宽,则:

分量级操作,若 T 为向量。

e1: T
e2: TS
T 为 AbstractInt 或 vecN<AbstractInt>
TST 为 AbstractInt,则为 u32,否则为 vecN<u32>
e1 >> e2: T 右移(抽象类型)。

e1 右移,低位丢弃。

e1 为负,高位补1,结果为负;否则高位补0。

移位位数为 e2 的值。

分量级操作,若 T 为向量。

8.10. 函数调用表达式

函数调用表达式会执行一次函数调用,被调用的函数具有返回类型。 如果被调用的函数没有返回值,应使用函数调用语句。 参见 § 9.5 函数调用语句

8.11. 变量标识符表达式

通过变量名获取引用
前置条件 结论 说明
v标识符解析在作用域内的变量, 该变量声明于地址空间 AS, 存储类型为T, 访问模式为AM v: ref<AS,T,AM> 结果是命名变量 v 内存的引用。

8.12. 形式参数表达式

获取作为函数形式参数声明的标识符的值
前置条件 结论 说明
a标识符解析在作用域内的形式参数声明,类型为 T a: T 结果是函数调用时在调用点传递给该参数的值。

8.13. 取地址表达式

取地址操作符将引用转换为其对应的指针。

通过引用获取指针
前置条件 结论 说明
r: ref<AS,T,AM> &r: ptr<AS,T,AM> 结果是与引用值 r 对应的 相同内存视图的指针值。

如果 r无效内存引用,则结果指针也为无效内存引用。

AShandle 地址空间,则为着色器创建错误

r向量分量的引用,则为着色器创建错误

8.14. 间接寻址表达式

间接寻址操作符将指针转换为其对应的引用。

通过指针获取引用
前置条件 结论 说明
p: ptr<AS,T,AM> *p: ref<AS,T,AM> 结果是与指针值 p 对应的 相同内存视图的引用值。

如果 p无效内存引用,则结果引用也是无效内存引用。

8.15. 值声明的标识符表达式

获取constoverridelet声明标识符的值
前置条件 结论 说明
c标识符解析在作用域内const 声明,类型为 T c: T 结果为初始化表达式计算得到的值。 该表达式为const-expression,在着色器创建时求值。
c标识符解析在作用域内override 声明,类型为 T c: T 如果管线创建时为常量 ID指定了值, 则结果为该值。 不同管线实例该值可能不同。

否则,结果为初始化表达式计算得到的值。 管线可重写常量出现在模块作用域,因此求值发生在着色器执行前。

注意: 若 API 调用未指定初始值且 let 声明无初始值,管线创建会失败。

c标识符解析在作用域内let 声明,类型为 T c: T 结果为初始化表达式计算得到的值。 let 声明出现在函数体内,其初始值每次控制流到达声明处都会重新求值。

8.16. 枚举表达式

枚举表达式
前置条件 结论 说明
e标识符解析预声明枚举项 属于枚举类型 E e : E 参见 § 6.3.1 预声明枚举项

8.17. 类型表达式

类型表达式
前置条件 结论 说明
t标识符解析预声明类型 t : AllTypes 参见 § 6.9 预声明类型与类型生成器汇总
a标识符解析类型别名a : AllTypes 此外,a 表示它所别名的类型。
s标识符解析结构体类型的声明。 s : AllTypes 此外,s 表示该结构体类型。
tg标识符解析类型生成器

e1: T1
...
eN: TN

tg _template_args_start
e1,
...,
eN
_template_args_end
: AllTypes
每个类型生成器对其所需和可接受的模板参数有自己的要求, 并定义模板参数如何决定最终类型。

表达式 e1eN 是该类型生成器的模板参数

例如,类型表达式 vec2<f32> 表示含有两个 f32 元素的向量

§ 6.9 预声明类型与类型生成器汇总 获取预声明类型生成器列表。

注意: 这两种写法仅区别于 eN 后是否有逗号。

tg _template_args_start
e1,
...,
eN,
_template_args_end
: AllTypes

8.18. 表达式语法总结

标识符call_phrase中的第一个词法单元时,其含义有:

声明与作用域规则保证这些名称总是唯一的。

primary_expression :

template_elaborated_ident

| call_expression

| literal

| paren_expression

call_expression :

call_phrase

注意: call_expression 规则确保对调用表达式进行类型检查

call_phrase :

template_elaborated_ident argument_expression_list

paren_expression :

'(' expression ')'

argument_expression_list :

'(' expression_comma_list ? ')'

expression_comma_list :

expression ( ',' expression ) * ',' ?

component_or_swizzle_specifier :

'[' expression ']' component_or_swizzle_specifier ?

| '.' member_ident component_or_swizzle_specifier ?

| '.' swizzle_name component_or_swizzle_specifier ?

unary_expression :

singular_expression

| '-' unary_expression

| '!' unary_expression

| '~' unary_expression

| '*' unary_expression

| '&' unary_expression

singular_expression :

primary_expression component_or_swizzle_specifier ?

lhs_expression :

core_lhs_expression component_or_swizzle_specifier ?

| '*' lhs_expression

| '&' lhs_expression

core_lhs_expression :

ident _disambiguate_template

| '(' lhs_expression ')'

multiplicative_expression :

unary_expression

| multiplicative_expression multiplicative_operator unary_expression

multiplicative_operator :

'*'

| '/'

| '%'

additive_expression :

multiplicative_expression

| additive_expression additive_operator multiplicative_expression

additive_operator :

'+'

| '-'

shift_expression :

additive_expression

| unary_expression _shift_left unary_expression

| unary_expression _shift_right unary_expression

relational_expression :

shift_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

| shift_expression '==' shift_expression

| shift_expression '!=' shift_expression

short_circuit_and_expression :

relational_expression

| short_circuit_and_expression '&&' relational_expression

short_circuit_or_expression :

relational_expression

| short_circuit_or_expression '||' relational_expression

binary_or_expression :

unary_expression

| binary_or_expression '|' unary_expression

binary_and_expression :

unary_expression

| binary_and_expression '&' unary_expression

binary_xor_expression :

unary_expression

| binary_xor_expression '^' unary_expression

bitwise_expression :

binary_and_expression '&' unary_expression

| binary_or_expression '|' unary_expression

| binary_xor_expression '^' unary_expression

expression :

relational_expression

| short_circuit_or_expression '||' relational_expression

| short_circuit_and_expression '&&' relational_expression

| bitwise_expression

8.19. 运算符优先级与结合性

本小节全部为非规范性内容。

WGSL 右值表达式中的运算符优先级与结合性由其语法总结自然而然地体现出来。右值表达式将运算符分组以组织它们,具体见下图:

运算符优先级与结合性图

为了通过冗余提升可读性,以下分组不会与其他分组结合:

以下分组不会与自己结合:

上述分组若需结合,必须使用括号明确关系。以下示例注释中说明了这些规则导致表达式无效的情况:

示例:运算符优先级特殊情况
let a = x & (y ^ (z | w)); // 无效:x & y ^ z | w
let b = (x + y) << (z >= w); // 无效:x + y << z >= w
let c = x < (y > z); // 无效:x < y > z
let d = x && (y || z); // 无效:x && y || z

推导出的优先级决定了表达式中隐含括号的位置,优先级高的运算符会像被括号包围一样与优先级低的运算符结合。例如,乘法优先级高于加法,所以表达式 a + b * c 推导为 (a + (b * c))。类似地,结合性决定了这些隐含括号的方向。例如,左结合会把 a + b + c 推导为 ((a + b) + c),而右结合会把 * * a 推导为 (* (* a))

下表总结了运算符优先级、结合性和绑定关系,自上而下从强到弱排列。绑定列指明了该运算符可包含的更强表达式。例如,“All above” 表示该运算符可包含任意比其强的表达式;而“Unary”表示只有比一元更弱、但比本行更强的表达式需要括号与该运算符结合。该列用于线性列出运算符的必要性。

右值表达式的运算符优先级、结合性与绑定关系,自强到弱排序
名称 运算符 结合性 绑定
括号 (...)
主表达式 a(), a[], a.b 左结合
一元 -a, !a, ~a, *a, &a 右结合 以上全部
乘法 a*b, a/b, a%b 左结合 以上全部
加法 a+b, a-b 左结合 以上全部
移位 a<<b, a>>b 需要括号 一元
关系 a<b, a>b, a<=b, a>=b, a==b, a!=b 需要括号 以上全部
按位与 a&b 左结合 一元
按位异或 a^b 左结合 一元
按位或 a|b 左结合 一元
短路与 a&&b 左结合 关系
短路或 a||b 左结合 关系

9. 语句

语句是一个控制执行的程序片段。 语句通常按顺序执行;但控制流语句可能导致程序以非顺序方式执行。

9.1. 复合语句

复合语句 是由大括号包围的零个或多个语句序列。 当声明是其中的一个语句时,其标识符 从下一个语句开始直到复合语句结束都在作用域内

compound_statement :

attribute * '{' statement * '}'

continuing_compound_statement 是复合语句的一种特殊形式, 作为continuing语句的主体,并允许在末尾有一个可选的break-if语句。

9.2. 赋值语句

赋值会计算一个表达式, 并可选地将其存储到内存中(从而更新变量的内容)。

assignment_statement :

lhs_expression ( '=' | compound_assignment_operator ) expression

| '_' '=' expression

运算符左边的文本称为左值(left-hand side), 运算符右边的表达式称为右值(right-hand side)

9.2.1. 简单赋值

赋值左值为表达式,且运算符为等号('=')时,就是简单赋值。 在这种情况下,右值的值会写入左值引用的内存中。

前置条件 语句 说明
e: T,
T具体可构造类型,
r: ref<AS,T,AM>,
AS 为可写地址空间,
访问模式 AMwriteread_write
r = e 先计算r,再计算e,然后将e计算得到的值写入r引用的内存位置中。

注意: 如果该引用是无效内存引用,写操作可能不会执行,或者会写到意外的内存位置。

最简单的情况,左值是变量名。 其他情况参见§ 6.4.8 引用和指针值的形成

示例:赋值
struct S {
    age: i32,
    weight: f32
}
var<private> person: S;

fn f() {
    var a: i32 = 20;
    a = 30;           // 用 30 替换 'a' 的内容。

    person.age = 31;  // 将 31 写入 person 变量的 age 字段。

    var uv: vec2<f32>;
    uv.y = 1.25;      // 把 1.25 放到 uv 的第二个分量。

    let uv_x_ptr: ptr<function,f32> = &uv.x;
    *uv_x_ptr = 2.5;  // 把 2.5 放到 uv 的第一个分量。

    var sibling: S;
    // 把 'person' 变量的内容拷贝到 'sibling' 变量中。
    sibling = person;
}

9.2.2. 占位赋值(Phony Assignment)

赋值左值是下划线('_')时,就是占位赋值。 此时右值会被计算,然后被忽略。

前置条件 语句 说明
e: T,
T可构造类型指针类型纹理类型采样器类型
_ = e 计算e

注意: 结果值不会被存储。 _ 并不是标识符,因此不能在表达式中使用。

占位赋值常用于:

示例:用占位赋值丢弃不需要的函数返回值
var<private> counter: i32;

fn increment_and_yield_previous() -> i32 {
  let previous = counter;
  counter = counter + 1;
  return previous;
}

fn user() {
  // 递增计数器,但不关心返回值。
  _ = increment_and_yield_previous();
}
示例:用占位赋值占用绑定,但不使用
struct BufferContents {
    counter: atomic<u32>,
    data: array<vec4<f32>>
}
@group(0) @binding(0) var<storage> buf: BufferContents;
@group(0) @binding(1) var t: texture_2d<f32>;
@group(0) @binding(2) var s: sampler;

@fragment
fn shade_it() -> @location(0) vec4<f32> {
  // 用占位赋值声明 buf、t、s 是着色器接口的一部分,但不做任何操作。
  _ = &buf;
  _ = t;
  _ = s;
  return vec4<f32>();
}

9.2.3. 复合赋值

赋值左值是表达式,且运算符为复合赋值运算符之一时,就是复合赋值

compound_assignment_operator :

'+='

| '-='

| '*='

| '/='

| '%='

| '&='

| '|='

| '^='

| _shift_right_assign

| _shift_left_assign

每个语句的类型要求、语义和行为如表所示地展开,区别在于:

语句 展开
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。
}
注意: 复合赋值可以重写为使用简单赋值的不同 WGSL 代码。 方法是用一个指针保存引用的计算结果。
例如, 当 e1 不是向量内某个分量的引用时,
e1+=e2;
可以重写为
{ let p = &(e1); *p = *p + (e2); }
其中标识符 p 要与程序中其它标识符不同。
e1 是向量内分量的引用,上述做法需调整,因为 WGSL 不允许取分量地址。 例如,若 ev 是向量的引用,语句
ev[c] += e2;
可以重写为
{ let p = &(ev); let c0 = c; (*p)[c0] = (*p)[c0] + (e2); }
其中 c0p 也要与其他标识符不同。

9.3. 自增和自减语句

自增语句将变量的内容加 1。 自减语句将变量的内容减 1。

increment_statement :

lhs_expression '++'

decrement_statement :

lhs_expression '--'

表达式必须计算为具有具体整型标量存储类型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)
示例:自增与自减
fn f() {
    var a: i32 = 20;
    a++;
    // 此时 a 的值为 21
    a--;
    // 此时 a 的值为 20
}

9.4. 控制流

控制流语句可能导致程序以非顺序方式执行。

9.4.1. If 语句

if 语句根据条件表达式的结果,有条件地执行最多一个复合语句

一个 if 语句包含一个 if 子句,后面跟零个或多个 else if 子句,最后可选跟一个 else 子句。

if_statement :

attribute * if_clause else_if_clause * else_clause ?

if_clause :

'if' expression compound_statement

else_if_clause :

'else' 'if' expression compound_statement

else_clause :

'else' compound_statement

类型规则前置条件: 每个 ifelse if 子句中的表达式必须bool类型。

if 语句的执行流程如下:

9.4.2. Switch 语句

switch 语句根据选择器表达式的结果,将控制流转移到一组case 子句之一,或default 子句

switch_statement :

attribute * 'switch' expression switch_body

switch_body :

attribute * '{' switch_clause + '}'

switch_clause :

case_clause

| default_alone_clause

case_clause :

'case' case_selectors ':' ? compound_statement

default_alone_clause :

'default' ':' ? compound_statement

case_selectors :

case_selector ( ',' case_selector ) * ',' ?

case_selector :

'default'

| expression

case 子句'case'关键字后跟逗号分隔的选择器列表和一个复合语句主体。

default-alone 子句'default'关键字后跟一个复合语句主体。

default 子句可以是:

每个 switch 语句必须有且仅有一个default 子句

'default'关键字不得在同一个case_selector列表中出现多次。

类型规则前置条件: 对于同一 switch 语句,选择器表达式和所有 case 选择器表达式必须是相同的具体整型标量类型。

case_selectors中的表达式必须常量表达式

同一 switch 语句中,任意两个 case 选择器表达式不能有相同的值。

如果选择器值等于某个case_selector列表中的表达式值, 则控制流转到该case 子句的主体。 如果选择器值与所有 case 选择器值都不相等,则控制流转到default 子句的主体。

当控制流到达某个子句主体末尾时,控制流转到 switch 语句后的第一个语句。

当子句主体中的某个语句是声明时, 它遵循作用域生命周期的正常规则。 即,主体是语句序列,如果其中有声明,则该声明的作用域从下一个语句开始直到主体结束。 声明在执行时创建一个新的变量实例,并初始化它。

示例:WGSL 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;
  }
}
示例:带 default 合并的 WGSL Switch
const c = 2;
var a : i32;
let x : i32 = generateValue();
switch x {
  case 0: {
    a = 1;
  }
  case 1, c {       // case 选择器可用常量表达式
    a = 3;
  }
  case 3, default { // default 关键字可与其他选择器同用
    a = 4;
  }
}

9.4.3. 循环语句

loop_statement :

attribute * 'loop' attribute * '{' statement * continuing_statement ? '}'

loop 语句会重复执行一次循环体; 循环体由复合语句指定。 每次循环体执行称为一次迭代

这种重复可被breakreturn语句中断。

循环体的最后一条语句可以可选为 continuing 语句。

如果loop会执行无限多次迭代,则会发生动态错误。 这可能导致循环提前终止、其它非局部影响,甚至设备丢失

若循环体中的某条语句是声明, 则遵循作用域生命周期的常规规则。 即,循环体是语句序列,如其中有声明,则声明的作用域从下一个语句起至循环体结束。 每次到达声明时都会执行,因而每次新迭代都创建新的变量实例,并重新初始化。

注意: loop 语句是一种专用结构,你可能更想用 forwhile 语句。loop 语句是与其他着色器语言最大不同之处之一。

此设计直接表达了编译代码中常见的循环习惯用法。 特别是,将循环更新语句放在循环体结尾,可以自然地使用循环体中定义的值。

示例:for 循环
var a: i32 = 2;
for (var i: i32 = 0; i < 4; i++) {
  a *= 2;
}
示例:loop
var a: i32 = 2;
var i: i32 = 0;      // <1>
loop {
  if i >= 4 { break; }

  a = a * 2;

  i++;
}
示例:带 continue 的 for 循环
var a: i32 = 2;
let step: i32 = 1;
for (var i: i32 = 0; i < 4; i += step) {
  if (i % 2 == 0) { continue; }
  a *= 2;
}
示例:带 continue 的 loop
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;
}
示例:带 continue 和 continuing 的 loop
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;
  }
}

9.4.4. For 语句

for_statement :

attribute * 'for' '(' for_header ')' compound_statement

for_header :

for_init ? ';' expression ? ';' for_update ?

for_init :

variable_or_value_statement

| variable_updating_statement

| func_call_statement

for_update :

variable_updating_statement

| func_call_statement

for 语句形式为 for (initializer; condition; update_part) { body },其本质是对具有相同bodyloop语句的语法糖。 此外:

for 循环的initializer在循环体执行前只执行一次。 如果初始化器里有声明,其标识符在作用域内直到body结束。 与body里的声明不同,初始化器声明不会在每次迭代时重新初始化。

conditionbodyupdate_part按顺序执行,构成一次循环迭代body复合语句的特殊形式。 body中声明的标识符在作用域内,从下一个语句开始直到body结束。 声明每次到达时都会执行,每次新迭代都会新建变量或常量实例并重新初始化。

示例:For 到 Loop 的转换:转换前
var a: i32 = 2;
for (var i: i32 = 0; i < 4; i++) {
  if a == 0 {
    continue;
  }
  a = a + 2;
}

转换为:

示例:For 到 Loop 的转换:转换后
var a: i32 = 2;
{ // 为循环变量 i 引入新作用域
  var i: i32 = 0;
  loop {
    if !(i < 4) {
      break;
    }

    if a == 0 {
      continue;
    }
    a = a + 2;

    continuing {
      i++;
    }
  }
}

如果for循环会执行无限多次迭代,则会发生动态错误。 这可能导致循环提前终止、其它非局部影响,甚至设备丢失

9.4.5. While 语句

while_statement :

attribute * 'while' expression compound_statement

while 语句是一种带条件的循环。 每次循环迭代开始时,会计算一个布尔条件。 如果条件为假,则 while 循环结束执行。 否则,执行本次迭代剩余部分。

类型规则前置条件:条件必须bool类型。

while 循环可视为loopfor语句的语法糖。 下列三种写法等价:

如果while循环会执行无限多次迭代,则会发生动态错误。 这可能导致循环提前终止、其它非局部影响,甚至设备丢失

9.4.6. Break 语句

break_statement :

'break'

break 语句将控制流转移到最近包裹的循环体或switch语句体之后,从而结束循环或 switch 语句的执行。

break 语句只能用于loopforwhileswitch语句内。

break 语句不能放在会跳出循环continuing语句的位置。 应使用break-if语句代替。

示例:WGSL 循环 continuing 子句中 break 非法
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_statement :

'break' 'if' expression ';'

break-if 语句会计算一个布尔条件; 如果条件为 true,则控制流转到最近包裹的loop 语句体之后,结束该循环的执行。

类型规则前置条件:条件必须bool类型。

注意: break-if 语句只能作为continuing 语句体的最后一条语句出现。

示例:WGSL 合法 loop 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;
    break if i >= 4;
  }
}

9.4.8. Continue 语句

continue_statement :

'continue'

continue 语句在最近包裹的loop中:

continue 语句只能用于loopforwhile语句内。 continue 语句不能用于将控制流跳转到包裹的continuing语句。(跳转到 continuing 语句是前向跳转。)

continue 语句不能放在会跳过 continuing 语句中声明的变量的地方。

注意: continue 只能在 continuing 语句内用于跳转到嵌套在 continuing 内的其他循环的控制流。也就是说,continue 不能用于跳转到当前 continuing 语句的开始。

示例:无效 continue 跳过声明
var i: i32 = 0;
loop {
  if i >= 4 { break;
  }
  if i % 2 == 0 { continue;
  } // <3>

  let step: i32 = 2;

  continuing {
    i = i + step;
  }
}

9.4.9. Continuing 语句

continuing_statement :

'continuing' continuing_compound_statement

continuing_compound_statement :

attribute * '{' statement * break_if_statement ? '}'

continuing 语句指定每次循环迭代结束时要执行的复合语句。 该结构是可选的。

该复合语句不能包含任意层级的return

9.4.10. Return 语句

return_statement :

'return' expression ?

return 语句结束当前函数的执行。 如果该函数为入口点, 则当前着色器调用被终止。 否则,计算会继续到当前函数调用调用点之后的下一个表达式或语句。

如果函数没有返回类型,则return语句是可选的。若此类函数提供了 return 语句,不能有返回值。 否则,表达式必须存在,并称为返回值。 此时该函数调用点的值即为返回值。 返回值的类型必须与函数的返回类型一致。

9.4.11. Discard 语句

discard 语句会将当前调用转换为辅助调用并丢弃片元。 discard 语句只能用于fragment着色器阶段。

更准确地说,执行 discard 语句将会

只有在 discard 语句之前执行的语句产生可观察到的效果。

注意: discard 语句可以被 fragment 阶段的任意函数执行,其效果相同:该片元会被丢弃。

示例:用 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);

  // 只有未被 discard 的片元才会设置 will_emit_color = 1 并输出红色。
  will_emit_color = 1;
  return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

9.5. 函数调用语句

func_call_statement :

call_phrase

函数调用语句会执行一次函数调用

如果被调用函数带有must_use属性,则会导致着色器创建错误

注意: 如果函数有返回值, 且未带有must_use属性, 则该返回值会被忽略。

9.6. 语句语法总结

statement 规则匹配可以在函数体内大多数位置使用的语句。

statement :

';'

| return_statement ';'

| if_statement

| switch_statement

| loop_statement

| for_statement

| while_statement

| func_call_statement ';'

| variable_or_value_statement ';'

| break_statement ';'

| continue_statement ';'

| 'discard' ';'

| variable_updating_statement ';'

| compound_statement

| assert_statement ';'

variable_updating_statement :

assignment_statement

| increment_statement

| decrement_statement

此外,某些语句仅能用于特定上下文:

9.7. 语句行为分析

9.7.1. 规则

某些影响控制流的语句只在特定上下文中有效。 例如,continueloopforwhile 之外无效。 此外,一致性分析(见§ 15.2 一致性)需要知道控制流何时会以多种方式退出某语句。

这两个目标都通过一种语句执行行为汇总系统实现。行为分析将每个语句映射为该语句求值完毕后执行可继续的方式集合。 类似于值和表达式的类型分析,行为分析自底向上进行:先确定某些基本语句的行为,再通过组合规则确定更高级结构的行为。

行为是一个集合,元素可以为:

这些都对应于退出复合语句的一种方式:要么通过关键字,要么“落到”下一个语句("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
B1B2
loop {s1 continuing {s2}} s1: B1
s2: B2
{Continue, Return} 均不在 B2
Break 不在 (B1B2) 中
(B1B2)∖{Continue, Next}
s1: B1
s2: B2
{Continue, Return} 均不在 B2
Break 在 (B1B2) 中
(B1B2 ∪ {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 循环缺少初始化或更新语句时。

分析时:

每个内建函数行为为{Next}。 其他未在上表列出的运算符应用,其行为等同于以相同操作数调用行为为{Next}的函数。

函数的行为必须满足上述规则。

注意: 不必分析表达式的行为,因为它们 总为 {Next} 或已由先前分析的函数导致错误。

9.7.2. 说明

本节为信息性内容,非规范性。

行为分析会导致程序因以下原因被拒绝(重述上文要求):

该分析可线性时间完成,自下而上分析调用图(因为函数调用的行为可依赖于函数的实际代码)。

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}
}
示例:if/then 等价于带空 else
fn if_example() {
  var a: i32 = 0;
  loop {
    if a == 5 {
      break;      // 行为: {Break}
    }             // 整个 if 复合语句行为: {Break, Next},
                  //   因为 if 有隐式空 else
    a = a + 1;    // 有效,因前一语句行为包含 Next
  }
}
示例:if/then/else 结合两侧行为
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}
  }
}
示例:if/else if/else 等价于嵌套 if/else
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}
示例:switch 里的 break 变为 Next
fn switch_example() {
  var a: i32 = 0;
  switch a {
    default: {
      break;   // 行为: {Break}
    }
  }            // 行为: {Next},因为 switch 会用 Next 替换 Break
  a = 5;       // 有效,因为前一语句行为包含 Next
}
示例:明显的无限循环
fn invalid_infinite_loop() {
  loop { }     // 行为: { }。因为空无效。
}
示例:discard 不会终止循环
fn invalid_infinite_loop() {
  loop {
    discard; // 行为 { Next }。
  }          // 无效,整个 loop 行为为 { }。
}
示例:带 continuing 的条件 continue
fn conditional_continue() {
  var a: i32;
  loop {
    if a == 5 { break; } // 行为: {Break, Next}
    if a % 2 == 1 {      // 有效,因前一语句行为包含 Next
      continue;          // 行为: {Continue}
    }                    // 行为: {Continue, Next}
    a = a * 2;           // 有效,因前一语句行为包含 Next
    continuing {         // 有效,continuing 行为为 {Next}
                         //  不包含 {Break, Continue, Return}
      a = a + 1;
    }
  }                      // loop 整体行为 {Next},
                         //  吸收 "Continue" 和 "Next",
                         //  然后将 "Break" 替换成 "Next"
}
示例:带 continuing 的冗余 continue
fn redundant_continue_with_continuing() {
  var a: i32;
  loop {
    if a == 5 { break; }
    continue;   // 有效。此为冗余,直接跳到下条语句。
    continuing {
      a = a + 1;
    }
  }
}
示例:循环体末尾的 continue
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" 行为。
示例:带返回类型的函数必须 return
fn missing_return () -> i32 {
  var a: i32 = 0;
  if a == 42 {
    return a;       // 行为: {Return}
  }                 // 行为: {Next, Return}
}                   // 错误:Next 不允许出现在带返回类型函数体
                    //   中
示例:continue 必须在循环内
fn continue_out_of_loop () {
  var a: i32 = 0;
  if a > 0  {
    continue;       // 行为: {Continue}
  }                 // 行为: {Next, Continue}
}                   // 错误:Continue 在函数体中无效
同理,如果将 continue 换成 break,该例同样无效。

10. 断言

断言是用于确保布尔条件成立的检查。

global_assert :

const_assert ';'

WGSL 定义了一种断言常量断言

const_assert :

'const_assert' expression

类型规则前置条件: 表达式必须bool类型。

10.1. 常量断言语句

常量断言语句是一种断言, 如果表达式的值为false,则会产生着色器创建错误。 表达式必须常量表达式。 该语句可以满足着色器中的静态访问条件,否则对编译后的着色器没有影响。 常量断言可以出现在模块作用域或作为函数作用域语句

assert_statement :

const_assert

示例:静态断言用法
const x = 1;
const y = 2;
const_assert x < y; // 在模块作用域有效。
const_assert(y != 0); // 括号可选。

fn foo() {
  const z = x + y - 2;
  const_assert z > 0; // 在函数内有效。
  let a  = 3;
  const_assert a != 0; // 无效,表达式必须为常量表达式。
}

11. 函数

函数在被调用时执行计算任务。

函数可以通过以下方式之一被调用:

WGSL 中的函数可以以任意顺序定义,包括可以在用到它之后才定义。 因此不需要函数原型或前置声明,也没有办法做前置声明。

函数分为两类:

11.1. 声明用户自定义函数

函数声明用于创建用户自定义函数,需要指定:

函数声明只能出现在模块作用域。 函数名在整个程序中在作用域内

注意: 每个用户自定义函数只有一个 重载

形式参数 声明为调用函数时需要提供的值指定标识符和类型。 形式参数可带属性。 参见 § 11.2 函数调用。 标识符的作用域函数体。 同一函数的两个形式参数不能同名。

注意: 某些内建函数允许参数为抽象数值类型; 但用户自定义函数目前不支持这种功能。

返回类型如有指定,必须可构造类型

WGSL 定义了以下可用于函数声明的属性:

WGSL 定义了以下可用于函数参数和返回类型的属性:

function_decl :

attribute * function_header compound_statement

function_header :

'fn' ident '(' param_list ? ')' ( '->' attribute * template_elaborated_ident ) ?

param_list :

param ( ',' param ) * ',' ?

param :

attribute * ident ':' type_specifier

示例:简单函数
// 声明 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. 函数调用

函数调用是调用函数的语句或表达式。

包含函数调用的函数称为调用方函数,或调用者。 被调用的函数称为被调用方函数,或被调用者

函数调用:

函数调用必须提供与被调用方函数形式参数数量一致的实参值。 每个实参值必须按顺序与对应形式参数类型相同。

总结,调用函数时:

  1. 调用方函数的执行被挂起。

  2. 被调用方函数开始执行,直到返回

  3. 调用方函数恢复执行。

被调用方函数返回的方式如下:

具体来说,执行函数调用时会发生以下步骤:

  1. 计算函数调用的实参值,求值顺序为从左到右。

  2. 调用方函数的执行被挂起。 所有函数作用域的变量和常量保持当前值。

  3. 如果被调用方函数是用户自定义函数, 则为其函数作用域变量分配内存。

  4. 为被调用方函数的形式参数赋值, 按顺序与调用点的实参一一对应。例如,被调用方函数的第一个形式参数取调用点第一个实参的值。

  5. 控制流转移到被调用方函数。 若为用户自定义函数,则从函数体第一条语句开始执行。

  6. 执行被调用方函数,直到其返回

  7. 控制流回到调用方函数,被调用方函数的执行结束挂起。 如果被调用方函数返回一个值,该值将作为函数调用表达式的结果。

函数调用的位置称为调用点,具体为解析出的call_phrase语法规则实例的第一个token所在位置。 调用点属于动态上下文。 因此,相同文本位置可能对应多个调用点。

注意: 如果 fragment 着色器中的所有 quad 调用都被 discard,则有可能某个函数调用永远不会返回。在这种情况下,控制流不会回到调用方函数。

11.3. const 函数

带有const属性声明的函数可以在着色器创建时求值。 这些函数称为常量函数。 对这些函数的调用可以作为常量表达式的一部分。

如果该函数包含任何不是常量表达式的表达式,或包含任何不是const 声明的声明,则会导致着色器创建错误

注意: const 属性不能用于用户自定义函数。

示例:常量函数
const first_one = firstLeadingBit(1234 + 4567); // 求值为 12
                                                // first_one 类型为 i32,因为
                                                // firstLeadingBit 不能操作 AbstractInt

@id(1) override x : i32;
override y = firstLeadingBit(x); // 常量表达式可用于 override 表达式
                                 // firstLeadingBit(x) 在此不是常量表达式

fn foo() {
  var a : array<i32, firstLeadingBit(257)>; // 若所有参数为常量表达式,常量函数可用于常量表达式
}

11.4. 函数限制

注意: 不允许递归,因为各类声明之间不允许有环。

示例:指针参数有效与无效
fn bar(p : ptr<function, f32>) {
}

fn baz(p : ptr<private, i32>) {
}

fn bar2(p : ptr<function, f32>) {
  let a = &*&*(p);

  bar(p); // 有效
  bar(a); // 有效
}

fn baz2(p : ptr<storage, f32>) {
}

struct S {
  x : i32
}

@group(0) @binding(0)
var<storage> ro_storage : f32;
@group(0) @binding(1)
var<storage, read_write> rw_storage : f32;

var usable_priv : i32;
var unusable_priv : array<i32, 4>;
fn foo() {
  var usable_func : f32;
  var unusable_func : S;
  var i32_func : i32;

  let a_priv = &usable_priv;
  let b_priv = a_priv;
  let c_priv = &*&usable_priv;
  let d_priv = &(unusable_priv.x);
  let e_priv = d_priv;

  let a_func = &usable_func;
  let b_func = &unusable_func;
  let c_func = &(*b_func)[0];
  let d_func = c_func;
  let e_func = &*a_func;

  baz(&usable_priv); // 有效,变量取地址。
  baz(a_priv);       // 有效,本质上是变量取地址。
  baz(b_priv);       // 有效,本质上是变量取地址。
  baz(c_priv);       // 有效,本质上是变量取地址。
  baz(d_priv);       // 有效,内存视图已变。
  baz(e_priv);       // 有效,内存视图已变。
  baz(&i32_func);    // 无效,地址空间不匹配。

  bar(&usable_func); // 有效,变量取地址。
  bar(c_func);       // 有效,内存视图已变。
  bar(d_func);       // 有效,内存视图已变。
  bar(e_func);       // 有效,本质上是变量取地址。

  baz2(&ro_storage); // 有效,变量取地址。
  baz2(&rw_storage); // 无效,访问模式不匹配。
}

11.4.1. 别名分析

11.4.1.1. 根标识符

内存位置可以通过内存视图在函数执行期间被访问。 在一个函数内部,每个内存视图都有一个特定的根标识符,该标识符命名了首次在该函数中提供访问该内存的变量或形式参数。

本地派生的引用类型或 指针类型的表达式可能为某个根标识符引入新名字, 但每个表达式都有静态可确定的根标识符。

给定一个指针引用类型的表达式E,其 根标识符为如下找到的原始变量指针类型形式参数

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 的接口。

通常从语言角度来说,属性在类型和语义检查时可以被忽略。 此外,属性名是上下文相关名称, 某些属性参数也是上下文相关名称。

attribute :

'@' ident_pattern_token argument_expression_list ?

| align_attr

| binding_attr

| blend_src_attr

| builtin_attr

| const_attr

| diagnostic_attr

| group_attr

| id_attr

| interpolate_attr

| invariant_attr

| location_attr

| must_use_attr

| size_attr

| workgroup_size_attr

| vertex_attr

| fragment_attr

| compute_attr

除非某属性描述中明确允许,否则一个属性不能在同一对象或类型上指定多次。

12.1. align

align_attr :

'@' 'align' '(' expression ',' ? ')'

align 属性
描述 约束结构体成员在内存中的位置。

只能用于结构体类型的成员。

此属性影响封闭结构体类型的值在内存中的出现方式: 它约束了结构体自身及其成员在内存中的字节地址。

align(n)应用于类型为T的结构体S的成员, 且S可作为地址空间AS中变量的存储类型, 其中AS不为uniform, 则n必须满足:
n = k × RequiredAlignOf(T,AS) 其中k为正整数。

对齐和大小的规则是互递归的。 但上述约束是良定义的,因为它依赖于嵌套类型的必需对齐, 而类型的嵌套深度是有界的。

参见§ 14.4 内存布局

参数 必须常量表达式解析i32u32类型。
必须为正数。
必须为2的幂。

12.2. binding

binding_attr :

'@' 'binding' '(' expression ',' ? ')'

binding 属性
描述 指定资源在绑定中的绑定号。 参见§ 13.3.2 资源接口

只能用于资源变量。

参数 必须常量表达式解析i32u32类型。
必须为非负数。

12.3. blend_src

blend_src_attr :

'@' 'blend_src' '(' expression ',' ? ')'

blend_src 属性
描述 当启用特性dual_source_blending时, 指定片元输出的一部分。 参见§ 13.3.1.3 输入输出 location

只能用于带有location属性的结构体类型成员。 只能用于声明数值标量数值向量类型对象。 不能用于着色器阶段输入不能用于着色器阶段输出声明, 除非是片元着色器阶段。

参数 必须常量表达式解析01i32u32值。

12.4. builtin

builtin_attr :

'@' 'builtin' '(' builtin_value_name ',' ? ')'

builtin 属性
描述 指定关联对象是由指定token标注的内建值。 参见§ 13.3.1.1 内建输入与输出

只能用于入口点函数参数、入口点返回类型,或结构体成员。

参数 必须为某内建值内建值名 token

12.5. const

const_attr :

'@' 'const'

const 属性
描述 指定函数可以作为常量函数使用。 该属性不能应用于用户自定义函数。

只能用于函数声明。

注意: 此属性作为记号惯例用于描述哪些内建函数可以用于常量表达式

参数

12.6. diagnostic

diagnostic_attr :

'@' 'diagnostic' diagnostic_control

diagnostic_control :

'(' severity_control_name ',' diagnostic_rule_name ',' ? ')'

diagnostic 属性
描述 指定一个范围诊断过滤器。参见§ 2.3 诊断

同一个语法结构可以指定多个diagnostic属性, 但它们必须指定不同的触发规则

参数 第一个参数是severity_control_name

第二个参数是diagnostic_rule_name token, 指定一个触发规则

12.7. group

group_attr :

'@' 'group' '(' expression ',' ? ')'

group 属性
描述 指定资源所在的绑定组。 参见§ 13.3.2 资源接口

只能用于资源变量。

参数 必须常量表达式解析i32u32类型。
必须为非负数。

12.8. id

id_attr :

'@' 'id' '(' expression ',' ? ')'

id 属性
描述 可管线覆盖的常量指定一个数值标识符作为别名。

只能用于override 声明,且类型为标量类型。

参数 必须常量表达式解析i32u32
必须为非负数。

12.9. interpolate

interpolate_attr :

'@' 'interpolate' '(' interpolate_type_name ',' ? ')'

| '@' 'interpolate' '(' interpolate_type_name ',' interpolate_sampling_name ',' ? ')'

interpolate_type_name :

ident_pattern_token

interpolate 属性
描述 指定用户自定义 IO 必须如何插值。 该属性仅对用户自定义顶点输出和片元输入有效。 参见§ 13.3.1.4 插值

只能用于带有location属性的声明。

参数 第一个参数必须为某插值类型插值类型名 token

如果有第二个参数,则必须为该插值采样插值采样名 token

12.10. invariant

invariant_attr :

'@' 'invariant'

invariant 属性
描述 当应用于顶点着色器的position 内建输出值 时,其结果的计算在不同程序和相同入口点的不同调用间保持不变。 即,如果两个入口点的 position 输出数据和控制流一致,则结果值必然相同。 对 position 内建输入值无影响。

只能用于 position 内建值。

注意: 此属性对应 HLSL 的 precise 修饰符和 GLSL 的 invariant 修饰符。

参数

12.11. location

location_attr :

'@' 'location' '(' expression ',' ? ')'

location 属性
描述 指定入口点的用户自定义 IO 的一部分。 参见§ 13.3.1.3 输入输出 location

只能用于入口点函数参数、入口点 返回类型,或结构体类型成员。 只能用于声明数值标量数值向量类型对象。 不能用于计算着色器输入

参数 必须常量表达式解析i32u32
必须为非负数。

12.12. must_use

must_use_attr :

'@' 'must_use'

must_use 属性
描述 指定对此函数的调用必须作为表达式使用。 即,对该函数的调用不能单独作为函数调用语句

只能应用于带有返回类型函数声明。

注意: 许多函数返回值且无副作用。将这类函数调用作为函数调用语句通常是编程缺陷。带有此属性的内建函数声明为 @must_use,用户自定义函数也可以有 @must_use 属性。

注意: 如需特意绕过 @must_use 规则,可用虚赋值或利用函数调用作为初始化器声明一个值。

参数

12.13. size

size_attr :

'@' 'size' '(' expression ',' ? ')'

size 属性
描述 指定结构体成员预留的字节数。

该数值必须不小于成员类型的字节大小

size(n)应用于类型为T的成员,则SizeOf(T) ≤ n

参见§ 14.4 内存布局

只能用于结构体类型的成员。 成员类型必须创建时固定占用

参数 必须常量表达式解析i32u32
必须为正数。

12.14. workgroup_size

workgroup_size_attr :

'@' 'workgroup_size' '(' expression ',' ? ')'

| '@' 'workgroup_size' '(' expression ',' expression ',' ? ')'

| '@' 'workgroup_size' '(' expression ',' expression ',' expression ',' ? ')'

workgroup_size 属性
描述 指定计算着色器的工作组网格的 x、y、z 维度。

第一个参数指定 x 维度。 如果提供第二个参数,则指定 y 维度,否则默认为 1。 如果提供第三个参数,则指定 z 维度,否则默认为 1。

只能用于计算着色器入口点函数。 不能用于其他对象。

参数 可带一、二或三个参数。

每个参数必须常量表达式override 表达式。 所有参数必须为同一类型,即i32u32

如果任一参数为常量表达式且其值不为正数,则会导致着色器创建错误

如果参数值不为正数、超过 WebGPU API 指定的上限,或参数值乘积超过 WebGPU API 指定的上限(参见 WebGPU § 3.6.2 Limits),则会导致管线创建错误

12.15. 着色器阶段属性

下列着色器阶段属性 用于将函数指定为某一特定着色器阶段入口点。 这些属性只能用于函数声明, 且每个函数至多只能有一个。 它们不带参数。

12.15.1. vertex

vertex_attr :

'@' 'vertex'

vertex 属性声明该函数为渲染管线顶点着色器阶段入口点

12.15.2. fragment

fragment_attr :

'@' 'fragment'

fragment 属性声明该函数为渲染管线片元着色器阶段入口点

12.15.3. compute

compute_attr :

'@' 'compute'

compute 属性声明该函数为计算管线计算着色器阶段入口点

13. 入口点

入口点是一个用户自定义函数,用于 执行特定着色器阶段的工作。

13.1. 着色器阶段

WebGPU 以 drawdispatch 命令的形式向 GPU 下达工作。 这些命令会在含有一组着色器阶段输入输出,以及附加的 资源的上下文中执行一个管线。

管线描述了要在 GPU 上执行的工作,是各阶段的序列,其中有些阶段是可编程的。 在 WebGPU 中,管线会在调度 draw 或 dispatch 命令执行前创建。 管线分为两类:GPUComputePipeline 和 GPURenderPipeline。

dispatch 命令使用 GPUComputePipeline 以可控并行度在逻辑点网格上运行 计算着色器阶段, 同时读取并可能写入缓冲区和图像资源。

draw 命令使用 GPURenderPipeline 运行多阶段流程, 其中包含两个可编程阶段以及其他固定功能阶段:

WebGPU 规范对管线有更详细描述。

WGSL 定义了三种着色器阶段,对应管线的可编程部分:

每个着色器阶段都有各自的特性和约束,详见其他章节。

13.2. 入口点声明

要创建一个入口点,需要声明一个带有着色器阶段属性用户自定义函数

在 WebGPU API 配置管线时, 入口点的函数名会映射到 WebGPU GPUProgrammableStage 对象的 entryPoint 属性。

入口点的形式参数表示该阶段的着色器阶段输入。 入口点的返回值(如有)表示该阶段的着色器阶段输出

每个形式参数类型和入口点返回类型必须为以下之一:

结构体类型可以用来将用户自定义输入与其他输入及可选的内建输入组合。 结构体类型也可以作为返回类型,将用户自定义输出与其他输出及可选的内建输出组合。

注意: bool 类型不允许用于用户自定义输入/输出, 仅允许用于front_facing 内建值

注意: 计算入口点不能有返回类型。

示例:入口点
@vertex
fn vert_main() -> @builtin(position) vec4<f32> {
  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}

@fragment
fn frag_main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
  return vec4<f32>(coord_in.x, coord_in.y, 0.0, 1.0);
}

@compute @workgroup_size(1)
fn comp_main() { }

着色器阶段中的函数集合为以下内容的并集:

该并集会反复应用直到稳定为止。 它将在有限步内稳定。

13.2.1. 入口点的函数属性

WGSL 定义了以下可用于入口点声明的属性:

示例:workgroup_size 属性
@compute @workgroup_size(8,4,1)
fn sorter() { }

@compute @workgroup_size(8u)
fn reverser() { }

// 使用可管线覆盖的常量。
@id(42) override block_width = 12u;
@compute @workgroup_size(block_width)
fn shuffler() { }

// 错误:workgroup_size 必须用于 compute shader 上
@compute
fn bad_shader() { }

13.3. 着色器接口

着色器接口是着色器用于读写管线外部数据的对象集合,以及用于配置着色器的可管线覆盖常量。 接口包括:

当出现如下情况时,声明 D 被着色器静态访问

注意:静态访问是递归定义的,需考虑以下情况:

现在可以精确定义着色器的接口包含:

13.3.1. 阶段间输入与输出接口

着色器阶段输入是由管线上传递到本阶段的数据项。 每个数据项要么为内建输入值,要么为用户自定义输入

着色器阶段输出是本阶段要传递到管线下游的数据项。 每个数据项要么为内建输出值,要么为用户自定义输出

IO 属性用于将对象标记为着色器阶段输入着色器阶段输出, 或进一步描述输入/输出的属性。 IO 属性包括:

13.3.1.1. 内建输入与输出

内建输入值用于访问系统生成的控制信息。 入口点不得包含重复的内建输入。

阶段 S 的名为 X 类型为 TX 的内建输入通过以下两种方式之一在 着色器阶段S入口点形式参数访问:

  1. 参数具有属性 builtin(X),类型为 TX

  2. 参数为结构体类型,其中某结构体成员具有属性 builtin(X),且类型为 TX

反过来,当入口点的参数或参数成员带有builtin属性时, 对应的 builtin必须是该入口点着色器阶段的输入。

内建输出值由着色器用于向管线后续处理步骤传递控制信息。 入口点不得包含重复的内建输出。

阶段 S 的名为 Y 类型为 TY 的内建输出通过以下两种方式之一在着色器阶段 S入口点返回值设置:

  1. 入口点返回类型具有属性 builtin(Y),且类型为 TY

  2. 入口点返回类型为结构体类型,其中某结构体成员具有属性 builtin(Y),且类型为 TY

反过来,当入口点的返回类型或返回类型成员带有builtin属性时, 对应的 builtin必须是该入口点着色器阶段的输出。

注意: position builtin 既是顶点着色器的输出,也是片元着色器的输入。

内建输入值和内建输出值统称为内建值

下表总结了可用的内建值。 每项为某内建值内建值名 token。具体细节见后续章节。

内建输入与输出值
名称 阶段 方向 类型 扩展
vertex_index vertex input u32
instance_index vertex input u32
clip_distances vertex output array<f32, N> (N8) clip_distances
position vertex output vec4<f32>
fragment input vec4<f32>
front_facing fragment input bool
frag_depth fragment output f32
sample_index fragment input u32
sample_mask fragment input u32
fragment output u32
local_invocation_id compute input vec3<u32>
local_invocation_index compute input u32
global_invocation_id compute input vec3<u32>
workgroup_id compute input vec3<u32>
num_workgroups compute input vec3<u32>
subgroup_invocation_id compute input u32 subgroups
fragment
subgroup_size compute input u32 subgroups
fragment
示例:声明内建值
 struct VertexOutput {
   @builtin(position) my_pos: vec4<f32>,
   @builtin(clip_distances) my_clip_distances: array<f32, 8>,
 }

 @vertex
 fn vs_main(
   @builtin(vertex_index) my_index: u32,
   @builtin(instance_index) my_inst_index: u32,
 ) -> VertexOutput {}

 struct FragmentOutput {
   @builtin(frag_depth) depth: f32,
   @builtin(sample_mask) mask_out: u32
 }

 @fragment
 fn fs_main(
   @builtin(front_facing) is_front: bool,
   @builtin(position) coord: vec4<f32>,
   @builtin(sample_index) my_sample_index: u32,
   @builtin(sample_mask) mask_in: u32,
 ) -> FragmentOutput {}

 @compute @workgroup_size(64)
 fn cs_main(
   @builtin(local_invocation_id) local_id: vec3<u32>,
   @builtin(local_invocation_index) local_index: u32,
   @builtin(global_invocation_id) global_id: vec3<u32>,
) {}
13.3.1.1.1. clip_distances
名称 clip_distances
阶段 vertex
类型 array<f32, N>
方向 输出
描述 数组中的每个值表示到用户自定义裁剪平面的距离。clip distance 为 0 表示顶点在平面上,正值表示顶点在裁剪半空间内部,负值表示顶点在裁剪半空间外部。clip_distances 的数组大小必须8。 参见 WebGPU § 23.2.4 原始裁剪
13.3.1.1.2. frag_depth
名称 frag_depth
阶段 fragment
类型 f32
方向 输出
描述 片元在视口深度范围内的更新后的深度值。

参见 WebGPU § 3.3 坐标系统

13.3.1.1.3. front_facing
名称 front_facing
阶段 fragment
类型 bool
方向 输入
描述 当前片元位于正面图元上时为 true,否则为 false。
13.3.1.1.4. global_invocation_id
名称 global_invocation_id
阶段 compute
类型 vec3<u32>
方向 输入
描述 当前调用的全局调用ID,即其在计算着色器网格中的位置。global_invocation_id 的值等于 workgroup_id * workgroup_size + local_invocation_id
13.3.1.1.5. instance_index
名称 instance_index
阶段 vertex
类型 u32
方向 输入
描述 当前顶点在本次 API 级 draw 命令中的实例索引。

第一个实例的索引等于 draw 的 firstInstance 参数(无论是直接还是间接提供)。 每增加一个实例,索引递增 1。

13.3.1.1.6. local_invocation_id
名称 local_invocation_id
阶段 compute
类型 vec3<u32>
方向 输入
描述 当前调用的局部调用ID,即其在工作组网格中的位置。
13.3.1.1.7. local_invocation_index
名称 local_invocation_index
阶段 compute
类型 u32
方向 输入
描述 当前调用的局部调用索引,即其在工作组网格中的线性化序号。
13.3.1.1.8. num_workgroups
名称 num_workgroups
阶段 compute
类型 vec3<u32>
方向 输入
描述 由 API 分派大小vec3<u32>(group_count_x, group_count_y, group_count_z),分派到计算着色器上。 参见 WebGPU §3.6.3
13.3.1.1.9. position
名称 position
阶段 vertex
类型 vec4<f32>
方向 输出
描述 当前顶点的裁剪坐标, 以裁剪空间坐标给出。

输出值 (x,y,z,w) 映射为 WebGPU 归一化设备坐标中的 (x/w, y/w, z/w)。

参见 WebGPU § 3.3 坐标系统WebGPU § 23.2.4 原始裁剪

如果 w 分量为零,会发生动态错误

名称 position
阶段 fragment
类型 vec4<f32>
方向 输入
描述
当前片元的输入 position。

fp 为片元的输入 position。
rp 为片元的 RasterizationPoint
vp 为 draw 命令生效的 [[viewport]]

则形式化:

fp.xy = rp.destination.position
fp.z = rp.depth
fp.w = rp.perspectiveDivisor

更详细地:

  • fp.x 和 fp.y 是当前片元在帧缓冲区中的插值后的 x 和 y 坐标。

    帧缓冲区是一个二维像素网格,左上角为 (0.0,0.0),右下角为 (vp.width, vp.height)。每个像素在 x 和 y 方向上都占 1.0 单位,像素中心从整数坐标偏移 (0.5,0.5)。

  • fp.z 是当前片元的插值深度。例如:

    • 归一化设备坐标中的深度 0 对应 fp.z = vp.minDepth,

    • 归一化设备坐标中的深度 1 对应 fp.z = vp.maxDepth。

  • fp.w 是片元的透视除数,即 1.0 ÷ vertex_w 的插值,vertex_w 是顶点着色器 position 输出的 w 分量。

参见 WebGPU § 3.3 坐标系统WebGPU § 23.2.5 光栅化

13.3.1.1.10. sample_index
名称 sample_index
阶段 fragment
类型 u32
方向 输入
描述 当前片元的采样索引。其值至少为 0,至多为 sampleCount - 1,其中 sampleCount 是为 GPU 渲染管线指定的 MSAA 采样 count。 当应用该属性时,如果片元着色器的效果会根据 sample_index 的值变化,则片元着色器会对每个采样点各调用一次。

参见 WebGPU § 10.3 GPURenderPipeline

13.3.1.1.11. sample_mask
名称 sample_mask
阶段 fragment
类型 u32
方向 输入
描述 当前片元的采样覆盖掩码。它包含一个位掩码,指示该片元中哪些采样点被渲染图元覆盖。

参见 WebGPU § 23.2.11 采样掩码

名称 sample_mask
阶段 fragment
类型 u32
方向 输出
描述 当前片元的采样覆盖掩码控制。最后写入该变量的值将成为着色器输出掩码。写入值中的为 0 的位会导致颜色附件中对应采样点被丢弃。

参见 WebGPU § 23.2.11 采样掩码

13.3.1.1.12. vertex_index
名称 vertex_index
阶段 vertex
类型 u32
方向 输入
描述 当前顶点在本次 API 级 draw 命令中的索引,与实例化无关。

非索引绘制时,第一个顶点的索引等于 draw 的 firstVertex 参数(无论直接还是间接提供)。 每增加一个顶点,索引递增 1。

索引绘制时,索引等于索引缓冲区当前顶点的条目加上 draw 的 baseVertex 参数(无论直接还是间接提供)。

13.3.1.1.13. workgroup_id
名称 workgroup_id
阶段 compute
类型 vec3<u32>
方向 输入
描述 当前调用的工作组ID,即该工作组在整个计算着色器网格中的位置。

同一工作组中的所有调用具有相同的工作组ID。

工作组ID 范围为 (0,0,0) 到 (group_count_x - 1, group_count_y - 1, group_count_z - 1)。

13.3.1.1.14. subgroup_invocation_id
名称 subgroup_invocation_id
阶段 computefragment
类型 u32
方向 输入
描述 当前调用的子组调用ID

该ID的范围为 [0, subgroup_size - 1]。

13.3.1.1.15. subgroup_size
名称 subgroup_size
阶段 computefragment
类型 u32
方向 输入
描述 当前调用所在子组的子组大小
13.3.1.2. 用户自定义输入与输出

用户自定义数据可以作为输入传递到管线起点,在管线各阶段之间传递,或者作为输出从管线末端输出。

每个用户自定义输入用户自定义输出 必须

计算着色器不能有用户自定义输入或输出。

13.3.1.3. 输入输出位置

每个输入输出位置最多可存储 16 字节的数据。 类型的字节大小由§ 14.4.1 对齐与大小中的 SizeOf 列定义。 例如,四分量浮点向量占用一个位置。

IO 位置通过location属性指定。

每个用户自定义输入输出必须显式指定 IO 位置。 入口点 IO 的每个结构体成员必须是内建值(见§ 13.3.1.1 内建输入与输出)或被分配一个位置。

对于 WGSL 模块中定义的每个入口点,令 inputs 为其着色器阶段输入集合(即形式参数或结构体类型形式参数成员的位置)。
对于 WGSL 模块中定义的每个结构体类型 S(不仅限于用于着色器阶段输入或输出的结构体), 令 membersS 中带有location属性的成员集合。

注意: 输入和输出的位置编号互不影响:入口点着色器阶段输入的位置编号不会与输出的位置编号冲突。

注意: 不需要其他规则防止入口点输出内的位置重叠。如果输出是结构体,前述规则已防止重叠。否则输出为标量或向量,只能分配单个位置。

注意: 入口点可用的位置数量由 WebGPU API 定义。

示例:应用 location 属性
struct A {
  @location(0) x: f32,
  // 尽管 location 为 16 字节,x 和 y 不能共享同一位置
  @location(1) y: f32
}

// in1 占用位置 0 和 1。
// in2 占用位置 2。
// 返回值占用位置 0。
@fragment
fn fragShader(in1: A, @location(2) in2: f32) -> @location(0) vec4<f32> {
 // ...
}

用户自定义 IO 可以与内建值在同一结构体中混用。例如:

示例:混合内建值与用户自定义 IO
// 混合内建值与用户自定义输入。
struct MyInputs {
  @location(0) x: vec4<f32>,
  @builtin(front_facing) y: bool,
  @location(1) @interpolate(flat) z: u32
}

struct MyOutputs {
  @builtin(frag_depth) x: f32,
  @location(0) y: vec4<f32>
}

@fragment
fn fragShader(in1: MyInputs) -> MyOutputs {
  // ...
}
示例:无效的位置分配
struct A {
  @location(0) x: f32,
  // 无效,x 和 y 不能共享同一位置。
  @location(0) y: f32
}

struct B {
  @location(0) x: f32
}

struct C {
  // 无效,含用户自定义 IO 的结构体不可嵌套。
  b: B
}

struct D {
  x: vec4<f32>
}

@fragment
// 无效,不可将 location 应用于结构体类型。
fn fragShader1(@location(0) in1: D) {
  // ...
}

@fragment
// 无效,in1 和 in2 不能共享同一位置。
fn fragShader2(@location(0) in1: f32, @location(0) in2: f32) {
  // ...
}

@fragment
// 无效,location 不能应用于结构体。
fn fragShader3(@location(0) in1: vec4<f32>) -> @location(0) D {
  // ...
}
13.3.1.4. 插值

作者可以通过使用interpolate属性,控制用户自定义 IO 数据的插值方式。 WGSL 提供了插值的两个方面进行控制:插值类型和插值采样。

插值类型 必须为以下某个 插值类型名token

perspective

以透视正确的方式进行插值。

linear

以线性、非透视正确的方式进行插值。

flat

不进行插值。

插值采样 必须为以下某个 插值采样名token

center

在像素中心进行插值。

centroid

在当前 primitive 内所有被片元覆盖的采样点的内部某一点进行插值。这个值在整个 primitive 的所有采样点中是相同的。

sample

按采样点插值。 当应用此属性时,片元着色器会对每个采样点各调用一次。

first

值由 primitive 的第一个顶点提供。

either

值由 primitive 的第一个或最后一个顶点提供,由实现决定具体是哪一个。

对于标量或向量浮点类型的用户自定义 IO:

标量或向量整型的用户自定义顶点输出和片元输入必须始终指定插值类型为 flat

阶段间接口校验会检查在渲染管线内, 每个用户自定义片元输入的插值属性与同一location分配的顶点输出插值属性是否一致。 否则会产生管线创建错误

13.3.2. 资源接口

资源是一个对象,用于访问某着色器阶段外部的数据, 且不是override 声明,也不是着色器阶段输入或输出。 资源由着色器的所有调用共享。

资源分为四类:

着色器的资源接口是该阶段中 着色器阶段函数 静态访问到的所有模块级资源变量的集合。

每个资源变量必须同时声明groupbinding 属性。 这些属性与着色器阶段一起标识了资源在管线中的绑定地址。 参见 WebGPU § 8.3 GPUPipelineLayout

同一个着色器中,两个不同的资源变量不能有相同的groupbinding值组合。

13.3.3. 资源布局兼容性

WebGPU 要求着色器的资源接口与使用该着色器的管线布局匹配。

如果 WGSL 资源接口中的变量绑定到不兼容的 WebGPU binding memberbinding type,则会发生管线创建错误。兼容性定义见下表。

WebGPU 绑定类型兼容性
WGSL 资源 WebGPU binding member WebGPU binding type
uniform buffer(均匀缓冲区) buffer GPUBufferBindingType "uniform"
storage buffer(存储缓冲区) 带有 read_write 访问权限 "storage"
storage buffer(存储缓冲区) 带有 read 访问权限 "read-only-storage"
sampler sampler GPUSamplerBindingType "filtering"
"non-filtering"
sampler_comparison "comparison"
sampled texturedepth texturemultisampled texture texture GPUTextureSampleType "float"
"unfilterable-float"
"sint"
"uint"
"depth"
write-only storage texture storageTexture GPUStorageTextureAccess "write-only"
read-write storage texture "read-write"
read-only storage texture "read-only"
external texture externalTexture (不适用)

接口验证要求见WebGPU API规范。

13.3.4. 缓冲区绑定决定运行时数组元素数量

storage buffer变量包含运行时大小的数组时,该数组元素数量由对应resource的大小决定:

更详细地,类型为 RAT 的运行时数组的 NRuntime 为:

truncate((EBBS − array_offset) ÷ array_stride),其中:

着色器可以通过NRuntimearrayLength 内建函数计算运行时数组长度。

注意: 此算法无歧义: 当运行时数组作为更大类型的一部分时,只能出现在结构体的最后一个成员,且该结构体不能嵌套在数组或其他结构体中。

NRuntime 由对应缓冲区绑定的大小决定,每次drawdispatch命令都可能不同。

WebGPU 验证规则保证 1 ≤ NRuntime

下面的代码示例:
示例:简单运行时数组的元素数量
@group(0) @binding(1) var<storage> weights: array<f32>;

下表展示了 weights 变量在不同有效缓冲区绑定大小下的 NRuntime 示例:

简单运行时数组的元素数量示例
有效缓冲区绑定大小 weights 变量的 NRuntime 计算
1024 256 truncate( 1024 ÷ 4 )
1025 256 truncate( 1025 ÷ 4 )
1026 256 truncate( 1026 ÷ 4 )
1027 256 truncate( 1027 ÷ 4 )
1028 257 truncate( 1028 ÷ 4 )
下面的代码示例:
示例:复杂运行时数组的元素数量
struct PointLight {                          //             align(16) size(32)
  position : vec3f,                          // offset(0)   align(16) size(12)
  // -- implicit member alignment padding -- // offset(12)            size(4)
  color : vec3f,                             // offset(16)  align(16) size(12)
  // -- implicit struct size padding --      // offset(28)            size(4)
}

struct LightStorage {                        //             align(16)
  pointCount : u32,                          // offset(0)   align(4)  size(4)
  // -- implicit member alignment padding -- // offset(4)             size(12)
  point : array<PointLight>,                 // offset(16)  align(16) elementsize(32)
}

@group(0) @binding(1) var<storage> lights : LightStorage;

下表展示了 lights 变量的 point 成员在不同有效缓冲区绑定大小下的 NRuntime 示例:

复杂运行时数组的元素数量示例
有效缓冲区绑定大小 lights 变量 point 成员的 NRuntime 计算
1024 31 truncate( ( 1024 - 16 ) ÷ 32) )
1025 31 truncate( ( 1025 - 16 ) ÷ 32) )
1039 31 truncate( ( 1039 - 16 ) ÷ 32) )
1040 32 truncate( ( 1040 - 16 ) ÷ 32) )

14. 内存

在 WGSL 中,可存储类型的值可被存入内存,供后续取用。 本节描述内存的结构,以及访问内存操作的语义。 可存入内存的值类型和用于内存访问的类型,见§ 6.4 内存视图

14.1. 内存位置

内存由一组不同的内存位置组成。 每个内存位置大小为 8 位。 影响内存的操作会与一组一个或多个内存位置交互。 对复合类型的内存操作不会访问填充内存位置。 因此,操作所访问的内存位置集合可能不是连续的。

如果两组内存位置的交集非空,则称这两组内存位置重叠

14.2. 内存访问模式

内存访问是对内存位置的操作。

一次操作可以进行读、写,或同时进行读写。

特定的内存位置可能只支持某些类型的访问,这通过内存的访问模式来表达。

访问模式
访问模式 支持的访问类型
read 支持读访问,不支持写访问。
write 支持写访问,不支持读访问。
read_write 支持读和写访问。

WGSL 预声明了枚举值enumerant readwriteread_write

14.3. 地址空间

内存位置被划分为地址空间。 每个地址空间具有决定其可变性、可见性、可包含值类型及变量用法的独特属性。 更多细节见§ 7 变量与值声明

给定内存视图的访问模式通常由上下文决定:

storage(存储)地址空间支持读写访问模式。 其他地址空间仅支持一种访问模式。 每个地址空间的默认访问模式如下表所示。

地址空间
地址空间 调用间共享 默认访问模式 备注
function 仅同一调用 read_write
private 仅同一调用 read_write
workgroup 同一计算着色器工作组内的调用 read_write 最外层数组的元素数量可为可管线覆盖常量。
uniform 同一着色器阶段内的调用 read 针对均匀缓冲区变量
storage 同一着色器阶段内的调用 read 针对存储缓冲区变量
handle 同一着色器阶段的调用 read 针对采样器纹理变量。

WGSL 预声明了每个地址空间的枚举值,但 handle 地址空间除外。

workgroup 地址空间中的变量 只能计算着色器阶段静态访问

storage 地址空间(存储缓冲区)中的变量,只能被顶点着色器阶段静态访问,且访问模式为read。 存储类型为storage texture、且访问模式为writeread_write的变量,不可被顶点着色器阶段静态访问。 参见 WebGPU createBindGroupLayout()

注意: 各地址空间的性能特性可能不同。

在 WGSL 源码中编写变量声明指针类型时:

14.4. 内存布局

WGSL 中类型的布局与地址空间无关。 严格来说,该布局只有主机可共享 缓冲区才能被观测到。 均匀缓冲区存储缓冲区变量 用于以内存中的字节序列共享大批量数据。 缓冲区可以在 CPU 与 GPU 之间共享,或在同一管线的不同着色器阶段间共享,或在不同管线间共享。

由于缓冲区数据共享时不会重新格式化或转换,如果缓冲区的生成方和消费方在内存布局(即缓冲区字节如何组织成 WGSL 类型值的描述)上不一致,则会产生动态错误。 这些字节是相对于某一公共基址的值的内存位置

缓冲区变量的存储类型 必须主机可共享类型,且需完全展开内存布局,具体如下所述。

每个缓冲区变量必须声明在 uniformstorage 地址空间中。

只有在以下情况评估表达式时,类型的内存布局才有意义:

8 位字节是主机可共享内存的最基本单位。 本节定义的术语均以 8 位字节计数。

记号说明如下,其中 T主机可共享定足迹类型, S 为主机可共享或定足迹结构体类型, A 为主机可共享或定足迹数组或运行时数组类型:

14.4.1. 对齐与大小

每种主机可共享定足迹数据类型T都有对齐与大小。

对齐是类型在内存中可放置位置的约束,表现为一个整数: 类型的对齐必须整除该类型值起始内存位置的字节地址。 对齐有助于使用更高效的硬件指令访问值,或满足特定地址空间更严格的硬件要求。(参见地址空间布局约束)。

注意: 每个对齐值始终是 2 的幂。

字节大小是类型或结构体成员在主机可共享内存中为存储其值所保留的连续字节数。 大小可能包括类型末尾的不可寻址填充。 因此,对一个值的加载和存储可能访问的内存位置少于该值的大小。

主机可共享定足迹类型的对齐与大小递归定义如下表:

主机可共享与定足迹类型的对齐与大小
主机可共享或定足迹类型 T AlignOf(T) SizeOf(T)
bool
说明
4 4
i32, u32, 或 f32 4 4
f16 2 2
atomic<T> 4 4
vec2<T>, Tbooli32u32f32 8 8
vec2<f16> 4 4
vec3<T>,Tbooli32u32、 或 f32 16 12
vec3<f16> 8 6
vec4<T>,Tbooli32u32、 或 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 的运行时元素数量
注意: 许多 GPU 无法在不引入潜在数据竞争的情况下实现单字节写入。 规定 bool 值占用 4 字节且对齐为 4 字节,可以支持内存中相邻布尔值而不会引入数据竞争。

14.4.2. 结构体成员布局

结构体的内部布局由其成员的大小和对齐计算得出。 默认情况下,成员按顺序紧密排列,不重叠,并满足成员的对齐要求。

这种默认的内部布局可以通过布局属性进行覆盖,这些属性包括:

结构体类型S的第i个成员有大小和对齐,分别用SizeOfMember(S, i) 和 AlignOfMember(S, i) 表示。 成员的大小和对齐用于计算每个成员距离结构体起始的字节偏移, 详见§ 14.4.4 值的内部布局

SizeOfMember(S, i) 为 k,如果S的第i个成员有 size(k) 属性。 否则为 SizeOf(T),T为成员类型。
AlignOfMember(S, i) 为 k,如果S的第i个成员有 align(k) 属性。 否则为 AlignOf(T),T为成员类型。

如果结构体成员应用了size属性,则该值必须 不小于成员类型的大小:

SizeOfMember(S, i) ≥ SizeOf(T)
其中 TS 的第i个成员的类型。

结构体的第一个成员总是从结构体起始的零字节偏移:

OffsetOfMember(S, 1) = 0

后续每个成员放在满足成员类型对齐要求且不与前一成员重叠的最小偏移处。 对于每个成员索引 i > 1:

OffsetOfMember(S, i) = roundUp(AlignOfMember(S, i ), OffsetOfMember(S, i-1) + SizeOfMember(S, i-1))
示例:使用隐式成员大小和对齐的结构体布局
struct A {                                     //             align(8)  size(24)
    u: f32,                                    // offset(0)   align(4)  size(4)
    v: f32,                                    // offset(4)   align(4)  size(4)
    w: vec2<f32>,                              // offset(8)   align(8)  size(8)
    x: f32                                     // offset(16)  align(4)  size(4)
    // -- implicit struct size padding --      // offset(20)            size(4)
}

struct B {                                     //             align(16) size(160)
    a: vec2<f32>,                              // offset(0)   align(8)  size(8)
    // -- implicit member alignment padding -- // offset(8)             size(8)
    b: vec3<f32>,                              // offset(16)  align(16) size(12)
    c: f32,                                    // offset(28)  align(4)  size(4)
    d: f32,                                    // offset(32)  align(4)  size(4)
    // -- implicit member alignment padding -- // offset(36)            size(4)
    e: A,                                      // offset(40)  align(8)  size(24)
    f: vec3<f32>,                              // offset(64)  align(16) size(12)
    // -- implicit member alignment padding -- // offset(76)            size(4)
    g: array<A, 3>,    // element stride 24       offset(80)  align(8)  size(72)
    h: i32                                     // offset(152) align(4)  size(4)
    // -- implicit struct size padding --      // offset(156)           size(4)
}

@group(0) @binding(0)
var<storage,read_write> storage_buffer: B;
示例:带显式成员大小和对齐的结构体布局
struct A {                                     //             align(8)  size(32)
    u: f32,                                    // offset(0)   align(4)  size(4)
    v: f32,                                    // offset(4)   align(4)  size(4)
    w: vec2<f32>,                              // offset(8)   align(8)  size(8)
    @size(16) x: f32                           // offset(16)  align(4)  size(16)
}

struct B {                                     //             align(16) size(208)
    a: vec2<f32>,                              // offset(0)   align(8)  size(8)
    // -- implicit member alignment padding -- // offset(8)             size(8)
    b: vec3<f32>,                              // offset(16)  align(16) size(12)
    c: f32,                                    // offset(28)  align(4)  size(4)
    d: f32,                                    // offset(32)  align(4)  size(4)
    // -- implicit member alignment padding -- // offset(36)            size(12)
    @align(16) e: A,                           // offset(48)  align(16) size(32)
    f: vec3<f32>,                              // offset(80)  align(16) size(12)
    // -- implicit member alignment padding -- // offset(92)            size(4)
    g: array<A, 3>,    // element stride 32       offset(96)  align(8)  size(96)
    h: i32                                     // offset(192) align(4)  size(4)
    // -- implicit struct size padding --      // offset(196)           size(12)
}

@group(0) @binding(0)
var<uniform> uniform_buffer: B;

14.4.3. 数组布局示例

示例:定长数组布局
// 数组:
//   - 对齐为 4 = AlignOf(f32)
//   - 元素步长为 4 = roundUp(AlignOf(f32),SizeOf(f32)) = roundUp(4,4)
//   - 大小为 32 = stride * number_of_elements = 4 * 8
var small_stride: array<f32, 8>;

// 数组:
//   - 对齐为 16 = AlignOf(vec3<f32>) = 16
//   - 元素步长为 16 = roundUp(AlignOf(vec3<f32>), SizeOf(vec3<f32>))
//                          = roundUp(16,12)
//   - 大小为 128 = stride * number_of_elements = 16 * 8
var bigger_stride: array<vec3<f32>, 8>;
示例:运行时数组布局
// 数组:
//   - 对齐为 4 = AlignOf(f32)
//   - 元素步长为 4 = roundUp(AlignOf(f32),SizeOf(f32)) = 4
// 若 B 为 draw/dispatch 命令对应绑定的有效缓冲区大小,则元素个数为:
//   N_runtime = floor(B / element stride) = floor(B / 4)
@group(0) @binding(0)
var<storage> weights: array<f32>;

// 数组:
//   - 对齐为 16 = AlignOf(vec3<f32>) = 16
//   - 元素步长为 16 = roundUp(AlignOf(vec3<f32>), SizeOf(vec3<f32>))
//                          = roundUp(16,12)
// 若 B 为 draw/dispatch 命令对应绑定的有效缓冲区大小,则元素个数为:
//   N_runtime = floor(B / element stride) = floor(B / 16)
var<storage> directions: array<vec3<f32>>;

14.4.4. 值的内部布局

本节描述当假定整体值已放入缓冲区某字节位置时,主机可共享值的内部如何映射到缓冲区字节。 这些布局依赖于值的类型,以及结构体成员上的 alignsize 属性。

值放置到缓冲区某字节偏移处时,必须满足类型的对齐要求:若类型为T的值放在缓冲区偏移k,则k = c × AlignOf(T),c为非负整数。

数据在所有地址空间表现一致

注意: bool 类型不是 主机可共享类型。 WGSL 规定 bool 的大小和对齐均为 4 字节,但未规定其内部布局。

当类型为 u32i32 的值 V 放在主机共享缓冲区字节偏移 k 时:

注意: i32 使用补码表示,符号位在第 31 位。

64 位整数布局: WebGPU API 某些特性会将 64 位无符号整数写入缓冲区。若此值 V 放在主机共享缓冲区字节偏移 k,则:

注意: WGSL 没有具体64 位整数类型。

类型为f32的值VIEEE-754 binary32格式表示。 1 位符号,8 位指数,23 位尾数。 放在主机共享缓冲区字节偏移 k 时:

类型为f16的值VIEEE-754 binary16格式表示。 1 位符号,5 位指数,10 位尾数。 放在主机共享缓冲区字节偏移 k 时:

注意: 上述规则表明主机共享缓冲区中的数值以小端序存储。

类型为atomic<T>的原子类型V放入主机共享缓冲区,其内部布局与基础类型T一致。

类型为 vecN<T> 的向量类型V放在主机共享缓冲区字节偏移 k 时:

类型为 matCxR<T> 的矩阵类型V放在主机共享缓冲区字节偏移 k 时:

类型为 A数组类型值放在主机共享内存缓冲区字节偏移 k 时:

类型为 S结构体类型值放在主机共享内存缓冲区字节偏移 k 时:

14.4.5. 地址空间布局约束

storageuniform 地址空间 拥有不同的缓冲区布局约束,详见本节。

除了 uniform 外, 所有地址空间都与 storage 地址空间具有相同约束。

变量直接或间接引用的所有结构体和数组类型 必须遵守该变量地址空间的约束。 违反地址空间约束会导致着色器创建错误

本节定义 RequiredAlignOf(S, C) 表示 主机可共享或定足迹类型 S 在地址空间 C 下的字节偏移对齐要求。

主机可共享或定足迹类型在地址空间 C 下的对齐要求
主机可共享或定足迹类型 S(假设 S 可出现在 C 中) RequiredAlignOf(S, C),
Cuniform
RequiredAlignOf(S, C),
Cuniform
booli32u32f32、 或 f16 AlignOf(S) AlignOf(S)
atomic<T> AlignOf(S) AlignOf(S)
vecN<T> AlignOf(S) AlignOf(S)
matCxR<T> AlignOf(S) AlignOf(S)
array<T, N> AlignOf(S) roundUp(16, AlignOf(S))
array<T> AlignOf(S) 不适用
struct S AlignOf(S) roundUp(16, AlignOf(S))

类型为 T 的结构体成员 必须具有从结构体起始处为 RequiredAlignOf(T, C) 的整数倍的字节偏移(C为地址空间):

OffsetOfMember(S, i) = k × RequiredAlignOf(T, C)
其中 k 为非负整数,结构体 S 的第 i 个成员类型为 T

元素类型为 T 的数组 必须具有为 RequiredAlignOf(T, C) 整数倍的元素步长C为地址空间):

StrideOf(array<T, N>) = k × RequiredAlignOf(T, C)
StrideOf(array<T>) = k × RequiredAlignOf(T, C)
其中 k 为正整数

uniform 地址空间还要求:

注意: 下例展示如何在结构体成员上使用 alignsize 属性 来满足 uniform 缓冲区的布局要求。 这些方法尤其可用于将带 std140 布局的 GLSL 缓冲区机械转换为 WGSL。

示例:满足 uniform 地址空间的偏移要求
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;
示例:满足 uniform 地址空间的 stride 要求
struct small_stride {
  a: array<f32,8> // stride 4
}
// 无效,stride 必须为 16 的倍数
@group(0) @binding(0) var<uniform> invalid: small_stride;

struct wrapped_f32 {
  @size(16) elem: f32
}
struct big_stride {
  a: array<wrapped_f32,8> // stride 16
}
@group(0) @binding(1) var<uniform> valid: big_stride;     // 有效

14.5. 内存模型

通常,WGSL 遵循 Vulkan 内存模型。 本节剩余部分描述了 WGSL 程序如何映射到 Vulkan 内存模型。

注意:Vulkan 内存模型是 形式化 Alloy 模型 的文本版本。

14.5.1. 内存操作

在 WGSL 中,读取访问 等价于 Vulkan 内存模型中的内存读取操作。 在 WGSL 中,写入访问 等价于 Vulkan 内存模型中的内存写入操作。

当调用执行以下操作之一时,会发生读取访问

当调用执行以下操作之一时,会发生写入访问

原子读-修改-写内置函数执行一次内存操作,该操作既是读取访问也是写入访问

在任何其他情况下都不会发生读取或写入访问。 读取和写入访问统称为 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. 内存模型引用

每个模块级作用域的资源变量,为唯一的内存模型引用(对应唯一的groupbinding对)。 其它变量(即 functionprivateworkgroup 地址空间内的变量)会在变量生命周期内形成唯一的内存模型引用

14.5.3. 作用域操作

当调用执行作用域操作时,会影响一组或两组调用。 这些集合分别是内存作用域和执行作用域。内存作用域指定了哪些调用能够看到该操作影响的内存内容的更新。 对于同步内置函数,这还意味着所有在该函数之前按程序顺序执行的受影响内存操作,对于该函数之后按程序顺序执行的受影响操作都是可见的。 执行作用域 指定了哪些调用可以参与操作(见§ 15.6 集体操作)。

原子内置函数映射到 原子操作,其内存作用域如下:

同步内置函数映射为控制屏障,其执行作用域和内存作用域均为 Workgroup

隐式和显式导数具有隐含的quad执行作用域。

注意:如果生成的着色器未启用 Vulkan 内存模型,应使用 Device 作用域代替 QueueFamily

14.5.4. 内存语义

所有原子内置函数都使用 Relaxed 内存语义,因此没有存储类语义。

注意:WGSL 中的地址空间等价于 SPIR-V 中的存储类。

workgroupBarrier 使用 AcquireRelease 内存语义WorkgroupMemory 语义。 storageBarrier 使用 AcquireRelease 内存语义UniformMemory 语义。 textureBarrier 使用 AcquireRelease 内存语义ImageMemory 语义。

注意:组合使用 workgroupBarrierstorageBarrier 时,使用 AcquireRelease 顺序语义,以及 WorkgroupMemoryUniformMemory 内存语义。

注意:没有原子或同步内置函数使用 MakeAvailableMakeVisible 语义。

14.5.5. 私有与非私有

所有在storageworkgroup 地址空间中的非原子读取访问都被视为 非私有,对应于带有 NonPrivatePointer | MakePointerVisible 内存操作数和 Workgroup 作用域的读取操作。

所有在storageworkgroup 地址空间中的非原子写入访问都被视为 非私有,对应于带有 NonPrivatePointer | MakePointerAvailable 内存操作数和 Workgroup 作用域的写入操作。

所有在handle 地址空间中的非原子读取访问都被视为 非私有,对应于带有 NonPrivateTexel | MakeTexelVisible 内存操作数和 Workgroup 作用域的读取操作。

所有在handle 地址空间中的非原子写入访问都被视为 非私有,对应于带有 NonPrivateTexel | MakeTexelAvailable 内存操作数和 Workgroup 作用域的写入操作。

15. 执行

§ 1.1 概述描述了着色器如何被调用以及如何被划分为调用。 本节进一步描述了调用在单独和集体执行时的限制。

15.1. 单次调用内的程序顺序

在 WGSL 模块中,每条语句在执行期间可以被执行零次或多次。 对于给定的调用,每次执行某条语句都代表一个唯一的 动态语句实例

当语句包含表达式时,语句的语义决定:

表达式的嵌套定义了完成求值必须满足的数据依赖。 即,嵌套表达式必须先于外层表达式被求值。 在 WGSL 中,表达式各操作数的求值顺序是从左到右。 例如,foo() + bar() 必须先求值 foo(),再求值 bar()。 参见§ 8 表达式

WGSL 模块中的语句按控制流顺序执行。 参见§ 9 语句§ 11.2 函数调用

15.2. 一致性

集体操作 (例如 barrier、导数,或依赖隐式求导的纹理操作) 需要在 GPU 上并发运行的不同调用之间进行协调。 当所有调用同时执行该操作,即处于一致控制流时,该操作能够正确且可移植地执行。

相反,当仅一部分调用执行该操作(即处于非一致控制流)时,会发生不正确或不可移植的行为。 通俗而言,只有部分调用到达集体操作,其他调用因非一致控制依赖未能到达或未能同时到达。 非一致控制依赖源于控制流语句,其行为依赖于非一致值

例如,当不同调用为 ifbreak-ifwhilefor 的条件表达式,switch 的选择器,或短路二元操作符(&&||)的左操作数计算出不同的值时,就会出现非一致控制依赖。

这些非一致值通常可以追溯到一些未被静态证明为一致的源头。 这些源头包括但不限于:

为确保正确且可移植的行为,WGSL 实现 执行静态一致性分析,尝试证明每个集体操作都在一致控制流中执行。 后续小节描述了该分析方法。

一致性分析无法证明某个集体操作一致控制流中执行时,将触发一致性失败

15.2.1. 术语与概念

以下定义仅为说明性,旨在帮助直观理解下一小节分析所计算的内容。 真正定义这些概念,以及何时程序有效或违反一致性规则的,是后面的分析。

对于给定的一组调用:

15.2.2. 一致性分析概述

接下来的小节规定了一种静态分析方法,用于验证集体操作仅在一致控制流中执行。

分析假定不会发生动态错误。 无论一致性分析结果如何,包含动态错误的着色器阶段本就不可移植。

注意:此分析具有以下理想特性:

每个函数都会被分析,需确保两点:

如果这两项检查有任一未通过,则会触发一致性失败

作为这项工作的组成部分,分析会计算有关函数的元数据,以协助分析其调用者。 这意味着首先要构建调用图,并从叶子节点(即只调用标准库外无其他函数的函数)开始向上传递分析,直到入口点。 这样,每当分析某个函数时,其所有被调用者的元数据都已计算好。 由于语言中禁止递归,因此不会陷入循环分析的风险。

注意:换句话说,就是对函数按“可能被(直接或间接)调用”这个偏序做拓扑排序,并按此顺序分析。

此外,对于每一次函数调用,分析会计算并传播该调用若无法证明处于一致控制流时会触发的触发规则集合(如有)。 我们称这为该调用的潜在触发集(potential-trigger-set)。 该集合的元素来自如下几种可能:

15.2.3. 分析函数的一致性要求

每个函数的分析分为两个阶段。

第一阶段遍历函数的语法结构,并根据后续小节中的规则沿途构建一个有向图。 第二阶段对该图进行探索,计算调用此函数的约束,并有可能触发一致性失败

注意:除了四个特殊节点 RequiredToBeUniform.errorRequiredToBeUniform.warningRequiredToBeUniform.infoMayBeNonUniform 外,每个节点都可以理解为捕获下述断言之一的真值:

一条边可以理解为其源节点对应断言到目标节点对应断言的蕴含关系。

例如,一个一致性要求是 workgroupBarrier 内置函数只能在一致控制流中被调用。 表达该要求时,我们从 RequiredToBeUniform.error 到对应 workgroupBarrier 调用点 的节点添加一条边。 可以这样理解:RequiredToBeUniform.error 对应命题 True, 所以 RequiredToBeUniform.error -> X 就等价于 X 是真。

反之,为表达无法保证某内容一致性(例如持有线程 id 的变量),我们从对应节点到 MayBeNonUniform 添加一条边。 这样可以理解为 MayBeNonUniform 对应命题 False,X -> MayBeNonUniform 等价于 X 为假。

这种解释的一个结果是,所有能从 RequiredToBeUniform.error 可达的节点都对应于程序要合法必须一致的内容,而所有能到达 MayBeNonUniform 的节点都对应于无法保证一致性的内容。 因此,如果存在从 RequiredToBeUniform.errorMayBeNonUniform 的路径,就会发生一致性违规,触发一致性失败

节点 RequiredToBeUniform.warningRequiredToBeUniform.info 用法类似, 但用于帮助判断何时应触发警告信息诊断

§ 2.3 诊断所述,如果已产生更高严重性的诊断,则较低严重性的诊断可能被丢弃。

对每个函数,会计算两个标签:

对每个函数的形参,会计算一个或两个标签:

调用点标签(Call site tag)取值
调用点标签 描述
CallSiteRequiredToBeUniform.S,
其中 S 是一种严重性:errorwarninginfo
该函数只能在一致控制流中被调用。 否则将触发严重性为 S 的诊断。

关联一个潜在触发集

CallSiteNoRestriction 该函数可以在非一致控制流中被调用。
函数标签(Function tag)取值
函数标签 描述
ReturnValueMayBeNonUniform 该函数的返回值可能是非一致的。
NoRestriction 该函数不会引入非一致性。
参数标签(Parameter tag)取值
参数标签 描述
ParameterRequiredToBeUniform.S,
其中 S 是一种严重性:errorwarninginfo
该参数必须是一致值。 如果参数类型为指针,则要求内存视图一致,但内容未必一致。 否则将触发严重性为 S 的诊断。

关联一个潜在触发集

ParameterContentsRequiredToBeUniform.S,
其中 S 是一种严重性:errorwarninginfo
指针参数所指内存中存储的值必须是一致值。 否则将触发严重性为 S 的诊断。

关联一个潜在触发集

ParameterNoRestriction 该参数值没有一致性要求。
参数返回标签(Parameter return tag)取值
参数返回标签 描述
ParameterReturnContentsRequiredToBeUniform 该参数必须一致值,对应函数返回值才能为一致值。 如果参数为指针,则指针所指内存中的值也必须一致
ParameterReturnNoRestriction 该参数值没有一致性要求。
指针参数标签(Pointer parameter tag)取值
指针参数标签 描述
PointerParameterMayBeNonUniform 指针参数所指内存中的值在函数调用后可能为非一致
PointerParameterNoRestriction 指针参数所指内存中值的一致性不受该函数调用影响。

下面的算法描述了如何计算给定函数的这些标签:

注意:此时可销毁整个图。上述标签就是分析调用该函数所需的全部信息。 不过,图中还包含可用于输出更详细诊断的信息。 例如,某个函数中的值无法被证明是一致的, 这会导致另一个函数中触发一致性失败。 更有用的诊断会描述该非一致值以及诊断触发位置处的函数调用。

15.2.4. 指针消糖

每个function地址空间内的指针类型参数 会被消糖为一个局部变量声明,其初始值等价于对参数进行解引用。 也就是说,function 地址空间的指针被视为对局部变量声明的别名。 初始值赋值会为第i个参数(即V(e)param_i_contents)创建一条到param_i_contents的边。

每个let 声明 L,如果其有效值类型指针类型,则消糖如下:

这种消糖方式通过在每次使用时直接暴露指针的根标识符,简化了后续的分析。

注意:为了一致性分析的目的,类型检查要求在消糖前后都要执行。

示例:一致性分析中的指针
fn foo(p : ptr<function, array<f32, 4>>, i : i32) -> f32 {
  let p1 = p;
  var x = i;
  let p2 = &((*p1)[x]);
  x = 0;
  *p2 = 5;
  return (*p1)[x];
}

// 下方是分析用的 foo 的等价实现。
fn foo_for_analysis(p : ptr<function, array<f32, 4>>, i : i32) -> f32 {
  var p_var = *p;            // 为 p 引入变量。
  let p1 = &p_var;           // p1 用变量
  var x = i;
  let x_tmp1 = x;            // 捕获 x 的值
  let p2 = &(p_var[x_tmp1]); // 替换 p1 的初始化
  x = 0;
  *(&(p_var[x_tmp1])) = 5;   // 替换 p2 的初始化
  return (*(&p_var))[x];     // 替换 p1 的初始化
}

15.2.5. 函数作用域变量值分析

可以通过到达某语句的赋值以及可能的初始值来分析每个函数作用域变量在某条语句的值。

如果满足以下条件,则赋值为完全赋值

否则,赋值为部分赋值

完整引用 是一种引用类型表达式,具体包括如下几种情况:

完整指针 是一种指针类型表达式,具体包括如下几种情况:

注意:在本分析中,我们不需要考虑指针类型的形式参数可能是完整指针的情况。

完整引用,以及类似的 完整指针,是对应源变量 x所有内存位置的内存视图

不是完整引用的引用称为部分引用。 因此,部分引用要么是:

注意:部分引用也可能覆盖和完全引用相同的所有内存位置,即变量声明用到的全部位置。 例如当存储类型是仅有一个成员的结构体,或者存储类型是只含一个元素的数组时会出现这种情况。

考虑一个只有一个成员的结构体类型以及一个存储该类型的变量:

struct S { member: i32; }
fn foo () {
   var v: S;
}

此时 v 是完全引用,v.member 是部分引用。 它们的内存视图覆盖相同的内存位置,但 v 的存储类型为 Sv.s 的存储类型为 i32

类似地,只有一个元素的数组也会出现这种情况:

fn foo () {
   var arr: array<i32,1>;
}

此时 arr 是完全引用,arr[0] 是部分引用。 它们的内存视图覆盖相同的内存位置,但 arr 的存储类型为 array<i32,1>arr[0] 的存储类型为 i32

为简化分析,任何类型的部分引用上的赋值都被视为不会修改源变量的所有内存位置。 这会导致分析趋于保守,可能对比实际必要的更多程序触发一致性失败

通过完全引用进行赋值为完全赋值

通过部分引用进行赋值为部分赋值

后续章节一致性规则中提到函数作用域变量用作RHSValue时的值,是指在对 RHSValue 表达式求值前变量的值。 用作LHSValue时,是指表达式所在语句执行后的变量值。

由于控制流语句或部分赋值,变量的多次赋值可能都会到达该变量的某次使用点。 分析通过对每个控制流出口联合到达的赋值集合,将多个到达的赋值合并。

下表描述了合并赋值的规则。 在一致性图中,每次合并为从结果节点到表示值来源的节点的边。 用于任意变量 x,记号如下:

合并对函数作用域变量的多次赋值的规则。
语句 结果 结果的来源边
var x; Vin(next) V(0)
var x = e;
Vin(next) V(e)

注意:这是一次对 x完全赋值

x = e;
r = e;
其中 r 是对变量 x完全引用
r = e;
其中 r 是对变量 x部分引用
Vout(S) V(e), V(prev)

注意:这是一次对 x部分赋值

注意:部分赋值会带上原值。 赋值只写入了存储分量的子集,或者写入值的类型与变量的存储类型 不同。

s1 s2
其中 Next 属于 s1 的行为。

注意: s1 通常以分号结尾。

Vin(s2) Vout(s1)
if e s1 else s2
其中 Next 属于 s1s2 的行为
Vin(next) Vout(s1), Vout(s2)
if e s1 else s2
其中 Next 属于 s1 的行为,但不属于 s2
Vin(next) Vout(s1)
if e s1 else s2
其中 Next 属于 s2 的行为,但不属于 s1
Vin(next) Vout(s2)
loop { s1 continuing { s2 } } Vin(s1) Vout(prev), Vout(s2)
loop { s1 continuing { s2 } } Vin(s2) Vout(s1),
Vout(si)
s1 中所有行为为 {Continue} 并转移控制到 s2si
loop { s1 continuing { s2 } } Vin(next) Vout(s2),
Vout(si)
s1 中所有行为为 {Break} 并转移控制到 nextsi
switch e {
case _: s1
case _: s2
...
case _: s3
}
Vin(si) Vout(prev)
switch e {
case _: s1
case _: s2
...
case _: s3
}
Vin(next) Vout(si),
对所有行为包含 NextBreaksi,以及
Vout(sj)
sj 中所有行为为 {Break} 并转移控制到 next 的语句

对所有其它语句(函数调用除外),Vin(next) 等价于 Vout(prev)。

注意:语句行为分析章节,消糖规则同样适用。

15.2.6. 语句一致性规则

分析语句的规则以语句本身和对应于其开始时控制流的节点(下文记作 "CF")为参数,并返回以下两项:

在下表中,(CF1, S) => CF2 表示“以控制流 CF1 为起点分析 S,对图应用所需更改,并将结果控制流命名为 CF2”。 类似地,(CF1, E) => (CF2, V) 表示“以控制流 CF1 为起点分析表达式 E,对图应用所需更改,并将结果控制流节点命名为 CF2,结果值节点命名为 V”(分析表达式详见下一节)。 这种表达式求值用于所有不属于左值的表达式,并称为RHSValue

对于属于左值的表达式,也有类似的规则,LHSValue,记作 LHSValue: (CF, E) => (CF, L)。它不是计算值的一致性节点,而是计算我们要访问的变量的一致性节点。

注意:LHSValue 包括 自增语句自减语句中的表达式。

注意:RHSValue 包括赋值语句右值部分的表达式,以及不属于赋值、自增或自减语句的表达式。

当需要创建多条边时,我们用 X -> {Y, Z} 作为 X -> Y, X -> Z 的简写。

语句一致性规则
语句 新节点 递归分析 结果控制流节点 新边
{s} (CF, s) => CF' CF'
s1 s2,
Next 属于 s1 行为

注意: s1 通常以分号结尾。

(CF, s1) => CF1
(CF1, s2) => CF2
CF2
s1 s2,
Next 不属于 s1 行为

注意: s1 通常以分号结尾。

(CF, s1) => CF1

注意: s2 静态不可达,不递归分析,不参与一致性分析。

CF1
if e s1 else s2
行为为 {Next}
(CF, e) => (CF', V)
(V, s1) => CF1
(V, s2) => CF2
CF
if e s1 else s2
其它行为
CFend CFend CFend -> {CF1, CF2}
loop {s1 continuing {s2}}
行为为 {Next}
CF' (CF', s1) => CF1
(CF1, s2) => CF2
CF CF' -> {CF2, CF}
loop {s1 continuing {s2}}
其它行为
CF'
loop {s1}
行为为 {Next}
CF' (CF', s1) => CF1 CF CF' -> {CF1, CF}
loop {s1}
其它行为
CF'
switch e case _: s_1 .. case _: s_n
行为为 {Next}
(CF, e) => (CF', V)
(V, s_1) => CF_1
...
(V, s_n) => CF_n
CF
switch e case _: s_1 .. case _: s_n
其它行为
CFend CFend CFend -> {CF_1, ..., CF_n}
var x: T; CF 注意:若 x 是function 地址空间变量,CF 作为值分析中的零值初值。
break;
continue;
break if e; (CF, e) => (CF', V) CF'
return; CF 对每个function 地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(见§ 15.2.5 函数作用域变量值分析
return e; (CF, e) => (CF', V) CF' Value_return -> V

对每个function 地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(见§ 15.2.5 函数作用域变量值分析

e1 = e2; LHSValue: (CF, e1) => (CF1, LV)
(CF1, e2) => (CF2, RV)
CF2 LV -> RV

注意: LV值分析的结果值。

_ = e (CF, e) => (CF', V) CF'
let x = e; (CF, e) => (CF', V) CF'
var x = e; (CF, e) => (CF', V) CF' 注意:若 x 是function 地址空间变量, V 作为值分析中的结果值。

forwhile 循环的分析来自它们对loop语句的消糖转换。

switch中,default-alone 子句块在一致性上与case 子句完全等同处理。

为了最大化性能,实现通常会尽量减少非一致控制流的数量。 然而,在哪些点上可认为调用是一致的会因多种因素而异。WGSL 的静态分析保守地假设在 ifswitchloop 语句末尾会恢复到一致控制流(如果 行为为 {Next})。 这在上表中体现为结果控制流节点与输入控制流节点相同。

15.2.7. 函数调用一致性规则

函数调用的规则最为复杂:

注意:Vout(call)的定义见§ 15.2.5 函数作用域变量值分析

大多数内置函数具有如下标签:

下列为例外情况:

注意:WGSL 实现会确保如果函数调用前的控制流是一致的,则调用后也会是一致的。

15.2.8. 表达式一致性规则

分析表达式的规则以表达式本身和对应于其开始时控制流的节点(下文记作 "CF")为参数,并返回以下内容:

RHSValue 表达式的一致性规则
表达式 新节点 递归分析 结果控制流节点, 值节点 新边
e1 || e2 (CF, e1) => (CF1, V1)
(V1, e2) => (CF2, V2)
CF, V2
e1 && e2
字面量 CF, CF
identifier 解析到函数作用域变量 "x",该标识符作为根标识符出现在内存视图表达式 MVE 中,并且加载规则MVE 上于类型检查期间被调用 Result X 是在包含此表达式的语句入口时 "x" 的值节点 CF, Result Result -> {CF, X}

注意: X 等价于 "x" 的 Vout(prev)
(见§ 15.2.5 函数作用域变量值分析

identifier 解析到函数作用域变量 "x",其中 "x" 是消糖后的指针参数 i,并且该标识符作为根标识符出现在内存视图表达式 MVE 中,且加载规则MVE 上于类型检查期间未被调用 CF, param_i
identifier 解析到函数作用域变量 "x",该标识符作为根标识符出现在内存视图表达式 MVE 中,且加载规则MVE 上于类型检查期间未被调用 CF, CF
identifier 解析const 声明override 声明let 声明,或非指针类型形参 "x" Result X 为 "x" 的节点 CF, Result Result -> {CF, X}
identifier 解析指针类型形参,位于 storageworkgroup、 或 private 地址空间,且访问模式非只读,标识符作为根标识符 出现在内存视图表达式 MVE 中,且加载规则MVE 上于类型检查期间被调用 CF, MayBeNonUniform
identifier 解析指针类型形参,位于 storageworkgroup、 或 private 地址空间,且访问模式非只读,标识符作为根标识符 出现在内存视图表达式 MVE 中,且加载规则MVE 上于类型检查期间未被调用 CF, CF
identifier 解析指针类型形参,位于除function外的地址空间,且访问模式为只读 CF, CF
identifier 解析到一致内置值 "x" CF, CF
identifier 解析到非一致内置值 "x" CF,
MayBeNonUniform
identifier 解析到只读模块级作用域变量 "x" CF, CF
identifier 解析到 非只读模块级变量 "x",标识符作为根标识符出现在内存视图表达式 MVE 中,且加载规则MVE 上于类型检查期间被调用 CF,
MayBeNonUniform
identifier 解析到 非只读模块级变量 "x",标识符作为根标识符出现在内存视图表达式 MVE 中,且加载规则MVE 上于类型检查期间未被调用 CF,CF
op e,
op 为一元操作符
(CF, e) => (CF', V) CF', V
e.field
e1 op e2,
op 为非短路二元操作符
Result (CF, e1) => (CF1, V1)
(CF1, e2) => (CF2, V2)
CF2, Result Result -> {V1, V2}
e1[e2]

以下内置输入变量被视为一致:

所有其他(见内置值)都视为非一致。

注意:作者应避免将一致性内置值与其他非常量输入分组,因为分析不会分别分析复合类型的各分量

LHSValue 表达式的一致性规则
表达式 新节点 递归分析 结果控制流节点, 变量节点 新边
identifier 解析到函数作用域变量 "x" Result X 是在包含此表达式的语句输出时 "x" 的值节点。 CF, Result Result -> {CF, X}

注意: X 等价于 "x" 的 Vin(next)
(见§ 15.2.5 函数作用域变量值分析

identifier 解析const 声明override 声明let 声明形参 "x" X 是 "x" 的节点 CF, X
identifier 解析到模块级变量 "x" CF,
MayBeNonUniform
e.field LHSValue: (CF, e) => (CF1, L1) CF1, L1
*e
&e
e1[e2] LHSValue: (CF, e1) => (CF1, L1)
(CF1, e2) => (CF2, V2)
CF2, L1 L1 -> V2

15.2.9. 为控制流中每一点注释一致性

本小节为非规范性内容。

如果实现者希望为开发者提供一个诊断模式,显示整个着色器控制流中每个点是否一致(以及是否可以在此处调用需要一致性的函数),我们建议如下:

这些可达性分析未访问到的节点,都可以被分析证明是一致的(因此在这些点调用导数函数或类似函数是安全的)。

注意:仍然需要自底向上的分析,因为它让我们知道在遇到调用时应向图中添加哪些边。

15.2.10. 示例

后续示例中的图使用如下节点约定:

15.2.10.1. 无效的 textureSample 函数调用

此示例展示了对textureSample 内置函数调用的无效用法。 函数调用发生在 if 语句中,其条件依赖于非一致值(即内置值 position)。 无效的依赖链以红色高亮显示。

示例:WGSL 无效 textureSample
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;

@fragment
fn main(@builtin(position) pos : vec4<f32>) {
  if (pos.x < 0.5) {
    // 无效的 textureSample 函数调用。
    _ = textureSample(t, s, pos.xy);
  }
}
一致性图

本例还展示了 if 语句之后的控制流一致性与 if 语句之前相同(CF_return 连接到CF_start)。 即,在 if 语句后控制流再次恢复为一致(因为在入口点开始时保证为一致控制流)。 如果textureSample函数调用移到 if 语句外,程序就会合法。 同样,如果 if 语句的条件是一个一致值(例如每个调用都从uniform buffer中读取同一个值),程序也会合法。

15.2.10.2. 函数作用域变量一致性

本例展示了依赖于函数作用域变量的 barrier函数调用的合法与非法用法。 workgroupBarrier是非法的,因为x的值来自可变的模块级变量astorageBarrier是合法的,因为x的值来自不可变的模块级变量b。 本例突出了值分析 能够区分函数作用域变量生命周期中不同一致性区段的能力。 此外还清楚地展示了在第一个if语句结束后控制流再次恢复为一致。 我们知道这一点是因为图的该部分与第二个 if 语句无关。

示例:WGSL 使用函数变量
@group(0) @binding(0) var<storage, read_write> a : i32;
@group(0) @binding(1) var<uniform> b : i32;

@compute @workgroup_size(16,1,1)
fn main() {
  var x : i32;
  x = a;
  if x > 0 {
    // 非法的 barrier 函数调用。
    workgroupBarrier();
  }
  x = b;
  if x < 0 {
    // 合法的 barrier 函数调用。
    storageBarrier();
  }
}
一致性图

注意:子图仅为便于理解而包含在示例中。

15.2.10.3. 复合值分析的局限性

一致性分析的一个限制是,它不会独立跟踪复合值的各分量。 也就是说,任何一个分量值只要是非常量,都会导致分析将整个复合值视为非常量。 下面的例子说明了这个问题,以及着色器作者可以采用的一种规避该限制的方法。

示例:无效复合值 WGSL
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.SMayBeNonUniform无路径体现。

示例:合法的可选 WGSL
@compute @workgroup_size(16,1,1)
fn main(@builtin(workgroup_id) wgid : vec3<u32>,
        @builtin(local_invocation_index) lid : u32) {
  // 一致性分析现在可以正确判定此比较总是一致。
  if wgid.x == 1 {
    // 合法的 barrier 函数调用。
    workgroupBarrier();
  }
}
合法可选一致性图
15.2.10.4. 循环中的一致性

在本例中,循环中有一个无效的 workgroupBarrier 函数调用。 非一致内置值 local_invocation_index 是根本原因,尽管它出现在循环体内 barrier 之后。 这是因为在后续迭代中,工作组中的某些调用已经提前退出循环,而其他调用试图执行 barrier。 分析将迭代间依赖建模为一条边,即循环体开始处的控制流(CF_loop_body)依赖于循环体结束处的控制流(CF_after_if)。

示例:循环一致性 WGSL
@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。 这导致 mainscale 函数调用的返回值与 position 内置值之间形成一条路径。 该路径是从RequiredToBeUniform.SMayBeNonUniform 的无效路径的子路径。

示例:用户自定义函数调用一致性 WGSL
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);
  }
}
scale 的一致性图
main 的一致性图

注意:子图仅为便于理解而包含在示例中。

15.3. 计算着色器与工作组

工作组(workgroup)是一组并发执行同一计算着色器阶段入口点的调用, 并共享对workgroup地址空间中着色器变量的访问。

计算着色器的工作组网格是整数坐标点集(i,j,k),满足:

其中 (workgroup_size_x, workgroup_size_y, workgroup_size_z) 是入口点的workgroup_size属性指定的值。

工作组网格的每个点恰好对应一个调用。

调用的本地调用 ID即该调用在工作组网格中的坐标三元组 (i,j,k)。

当调用有本地调用 ID时,其 本地调用索引

i + (j * workgroup_size_x) + (k * workgroup_size_x * workgroup_size_y)

注意,如果一个工作组有 W 个调用,则该工作组的每个调用 I 都有唯一的本地调用索引 L(I) 满足 0 ≤ L(I) < W, 且整个范围都被覆盖。

当 WebGPU 实现从队列中移除一个分派(dispatch)命令并开始在 GPU 上执行指定工作时,计算着色器开始执行。 分派命令指定一个分派尺寸(dispatch size), 即整数三元组(group_count_xgroup_count_ygroup_count_z) 表示要执行的工作组数量,如下所述。

某次分派的计算着色器网格是整数坐标点集(CSi,CSj,CSk),满足:

其中 workgroup_size_xworkgroup_size_yworkgroup_size_z 如前所述为计算着色器入口点对应的值。

计算着色器分派的工作是:对计算着色器网格的每个点,恰好执行一次入口点的调用。

调用的全局调用 ID即该调用在计算着色器网格中的坐标三元组。

这些调用被组织为工作组,使得每个调用的全局调用 ID (CSi, CSj, CSk)映射到 唯一的工作组,其 ID 为workgroup ID

( ⌊ CSi ÷ workgroup_size_x ⌋, ⌊ CSj ÷ workgroup_size_y ⌋, ⌊ CSk ÷ workgroup_size_z ⌋)

以及该工作组内的唯一调用,其本地 ID 为local invocation ID

( CSi mod workgroup_size_x , CSj mod workgroup_size_y , CSk mod workgroup_size_z ).

注意:工作组 ID 范围从 (0,0,0) 到 (group_count_x - 1, group_count_y - 1, group_count_z - 1)。

WebGPU 不保证以下内容:

15.4. 片元着色器与辅助调用

片元着色器阶段中的调用被划分为 X 和 Y 方向上具有相邻位置的 2x2 网格。 每个这样的网格称为一个quad(象限)。 quad 可在某些集体操作中协作(见§ 15.6.2 导数)。 某个调用的quad 调用 ID是在该 quad 内的唯一 ID,其中:

注意:没有用于 quad ID 的内置值访问器。

通常,片元处理为每个 RasterizationPoint 创建一个片元着色器调用,该 RasterizationPoint 由 光栅化生成。 有时,可能没有足够的 RasterizationPoint 填满一个 quad,例如在图元边界处。 当一个 quad 只有 1、2 或 3 个调用对应 RasterizationPoint 时,片元处理为 quad 中每个未填充位置 创建一个辅助调用(helper invocation)

辅助调用不会产生可观察的副作用,除非用于帮助计算导数。 因此,辅助调用受到如下限制:

如果 quad 中所有调用都变为辅助调用(例如因为执行了discard语句),quad 的执行可以终止;但这种终止不被认为产生非一致控制流

15.5. 子组(Subgroups)

子组(subgroup)是一组并发执行计算片元着色器阶段入口点的调用,这些调用可以高效地共享数据并协同计算结果。 在计算或片元着色器中,每个调用恰好属于一个子组。 在计算着色器中,每个子组是某个特定工作组的子集。 在片元着色器中,一个子组可能包含来自多个绘制命令的调用。 每个quad都包含在一个子组中。

子组大小(subgroup size)是子组中最大调用数。 在着色器中,可通过subgroup_size内置值访问。 子组大小在dispatch 命令以及工作组内为一致值,但在绘制命令内未必一致。 所有子组大小为 [4, 128] 范围内的 2 的幂,针对特定设备编译的着色器的值会落在该设备支持的 subgroupMinSizesubgroupMaxSize 之间(见 WebGPU § 4.3 GPUAdapter)。 实际大小取决于着色器、设备属性和设备编译器。 每个设备支持可能范围的一个子集(可能只有一个值)。 设备编译器会用多种启发式方法在支持范围内选择大小。 每个子组可能包含的调用数少于报告的子组大小(如实际发起的调用少于子组大小时)。

调用的子组调用 ID(subgroup invocation ID)是该子组内的唯一 ID。 该 ID 可通过subgroup_invocation_id内置值访问,范围为 [0, subgroup_size - 1]。 subgroup_invocation_idlocal_invocation_index 没有定义的关系。 为避免不可移植的代码,着色器作者不应假设这两个值有特定映射关系。

当同一子组内的调用执行不同的控制流路径时,称子组执行已发散(diverged)。 这是非一致控制流的一种特殊情况。 发散会影响子组操作的语义。 子组中并发执行子组操作的调用为该操作的活动调用。 其它为非活动调用。 当子组大小大于实际调用数时,多出来的假想调用被视为非活动。 辅助调用在某些操作中可以是活动或非活动的。 也就是说,在某些设备上辅助调用可能参与子组操作,另一些设备则不参与。

注意:在非一致控制流下,不同底层设备的表现差异很大,设备编译器常会对这类代码做激进优化。 结果是子组实际活动的调用集可能与着色器作者预期不同。

15.6. 集体操作

15.6.1. 屏障(Barrier)

屏障是一种同步内置函数,用于对程序中的内存操作进行排序。 控制屏障(control barrier)由同一工作组内的所有调用共同执行,就好像它们是并发执行的一样。 因此,控制屏障必须只在一致控制流中执行(在 计算着色器中)。

15.6.2. 导数(Derivatives)

偏导数(partial derivative) 是某个值沿某一轴的变化率。 同一个quad内的片元着色器调用协作以计算近似的偏导数。

计算导数的内置函数包括:

片元坐标的偏导数在以下内置函数的操作过程中隐式计算:

对于这些函数,导数用于确定所采样纹素的 mip 级别;对于 textureSampleCompare,则用于采样并与参考值比较。

调用方指定值的偏导数由 § 17.6 导数内置函数中描述的内置函数计算:

由于相邻调用协作计算导数,这些函数应只在片元着色器的一致控制流中调用。 若一次此类函数调用无法通过一致性分析证明其处于一致控制流,则触发derivative_uniformity诊断

如果在非一致控制流中调用这些函数,则结果为不确定值

注意:导数本质是一种quad 操作。 使用导数不需要subgroups扩展。

15.6.3. 子组操作(Subgroup Operations)

子组内置函数允许同一子组中的调用高效通信与计算。 子组操作为单指令多线程(SIMT)操作。

子组中的活动调用协作决定结果。 因此,当这些函数在所有调用都处于活动状态(即在子组级别一致控制流)时,可移植性最佳。

15.6.4. quad 操作(Quad Operations)

quad 内置函数quad的调用之间操作。 它们用于 quad 内各调用间的数据通信。

quad 内的活动调用协作决定结果。 因此,当这些函数在所有调用都处于活动状态(即在 quad 级别一致控制流)时,可移植性最佳。

15.7. 浮点数运算

WGSL 的浮点特性基于IEEE-754 浮点标准, 但功能有所精简,反映了 GPU 的权衡,并增加了一些便于移植的安全措施。

15.7.1. IEEE-754 概述

WGSL 浮点类型基于IEEE-754 二进制浮点类型。

一种 IEEE-754 二进制浮点类型近似 扩展实数数轴如下:

浮点类型的有限范围是区间 [low, high], 其中 low 是该类型的最小有限值,high 是最大有限值。

IEEE-754 标准中主要关注的浮点类型有:

下述算法将浮点值的位表示映射为其对应的扩展实数值或 NaN:

算法: 浮点位解释

输入Bits,二进制浮点类型的位表示。

输出: F,由Bits表示的浮点值。

过程:

浮点运算的定义域为该运算在扩展实数输入中定义良好的集合。

舍入扩展实数x映射为浮点类型的值 x'。 若 x 已为浮点类型,则舍入后 x = x'。 若 x 超出该类型有限范围,舍入可能溢出。 否则 x' 是大于 x 的最小浮点值,或小于 x 的最大浮点值; 舍入模式决定如何选择。

一般来说,带有NaN输入的运算会产生NaN输出。 例外包括:

IEEE-754 定义了五类异常(exceptions)

15.7.2. 与 IEEE-754 的差异

WGSL 遵循IEEE-754标准,但有如下不同:

15.7.3. 浮点数舍入与溢出

溢出运算可以舍入为无穷大,也可以舍入为最近的有限值。 最终结果取决于溢出中间结果的大小,以及是在着色器模块创建管线创建还是着色器执行期间求值。

对于浮点类型 T,定义 MAX(T)T 的最大正有限值, 2EMAX(T)T 可表示的最大 2 的幂。 特别地,EMAX(f32) = 127,EMAX(f16) = 15。

X 为浮点运算产生的无限精度中间结果。 表达式的最终值通过两个阶段、两个中间结果 X'X'' 确定:

对于 X,用 T 类型的舍入得到 X'

X',计算表达式最终值 X'',或检测程序错误:

15.7.4. 浮点数精度

x 为用无限精度计算时操作的精确实数或无穷结果。 某浮点类型 T 的该操作的正确舍入(correctly rounded)结果为:

即,结果可以向上或向下舍入: WGSL 不指定舍入模式

注意: 浮点类型包括正无穷和负无穷,因此正确舍入的结果可能是有限值也可能是无穷。

注意: 用无限精度计算操作的结果,可能需要超过 double 精度的精度。 例如 x - y,其中 x=1.0 y=1.17e-38(最小正正规单精度 float)。 这两个数的指数相差 126IEEE-754binary64(double precision)只有 52 位尾数, 做减法时 y 的所有有效位都丢失。 根据舍入模式,对于这种以及许多 y 很小但非零的情况,WGSL 表达式 x - y 可能得到和 x 相同的值。 注意 [ECMASCRIPT] 等价于 IEEE-754 的 roundTiesToEven 舍入模式。

最后一位单位(unit in the last place, ULP)的定义如下 [Muller2005]

某操作的精度有以下五种可能:

当操作的精度只对某输入范围规定时,超出该范围的输入精度未定义。

若允许的结果超出结果类型的有限范围,则适用 § 15.7.3 浮点数舍入与溢出的规则。

15.7.4.1. 具体浮点表达式的精度
具体浮点运算的精度
表达式 f32 精度 f16 精度
x + y 正确舍入
x - y 正确舍入
x * y 正确舍入
x / y 2.5 ULP,|y| 范围 [2-126, 2126] 2.5 ULP,|y| 范围 [2-14, 214]
x % y 继承自 x - y * trunc(x/y)
-x 正确舍入
x == y 正确结果
x != y 正确结果
x < y 正确结果
x <= y 正确结果
x > y 正确结果
x >= y 正确结果
具体浮点内置函数的精度
内置函数 f32 精度 f16 精度
abs(x) 正确舍入
acos(x) 以下两者较差者:
  • 绝对误差 6.77×10-5

  • 继承自 atan2(sqrt(1.0 - x * x), x)

以下两者较差者:
  • 绝对误差 3.91×10-3

  • 继承自 atan2(sqrt(1.0 - x * x), x)

acosh(x) 继承自 log(x + sqrt(x * x - 1.0))
asin(x) 以下两者较差者:
  • 绝对误差 6.81×10-5

  • 继承自 atan2(x, sqrt(1.0 - x * x))

以下两者较差者:
  • 绝对误差 3.91×10-3

  • 继承自 atan2(x, sqrt(1.0 - x * x))

asinh(x) 继承自 log(x + sqrt(x * x + 1.0))
atan(x) 4096 ULP 5 ULP
atan2(y, x) 4096 ULP,|x| 范围 [2-126, 2126],且 y 有限且正规 5 ULP,|x| 范围 [2-14, 214],且 y 有限且正规
atanh(x) 继承自 log( (1.0 + x) / (1.0 - x) ) * 0.5
ceil(x) 正确舍入
clamp(x,low,high) 正确舍入。

无限精度结果可按 min(max(x,low),high) 计算,或用三值中位方式。 当 low > high 时两者可能不同。

xlowhigh 之一为 非正规,结果可为任一 非正规值。 这源自 minmax非正规输入的可能结果。

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]),其中 ij
degrees(x) 继承自 x * 57.295779513082322865
determinant(m:mat2x2<T>)
determinant(m:mat3x3<T>)
determinant(m:mat4x4<T>)
无限 ULP。
注意:WebGPU 实现应提供实际可用的行列式函数。

理想数学中,行列式用加、减、乘运算计算。

但 GPU 使用浮点运算,GPU 上的行列式实现更偏向速度和简单性,而非溢出和误差鲁棒性。

例如,2x2 行列式的朴素计算 (m[0][0] * m[1][1] - m[1][0] * m[0][1]) 无法防止灾难性抵消误差。 为 2x2 行列式提供更严格的误差界限是较新的研究课题 [Jeannerod2013]。 随矩阵增大,挑战快速增加。

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 上实现为不同调用间的差值(fwidth 为绝对值差)。

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) 正确舍入

xy 都为 非正规,结果可为任一输入。

min(x, y) 正确舍入。

xy 都为 非正规,结果可为任一输入。

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) 取以下两者中较差者:
  • 绝对误差 1.0×10-5

  • 继承自 sinh(x) / cosh(x)

transpose(x) 正确舍入
trunc(x) 正确舍入
unpack4x8snorm(x) 3 ULP N/A
unpack4x8unorm(x) 3 ULP N/A
unpack2x16snorm(x) 3 ULP N/A
unpack2x16unorm(x) 3 ULP N/A
unpack2x16float(x) 正确舍入 N/A
subgroupBroadcast(x, i) 正确舍入
subgroupBroadcastFirst(x) 正确舍入
subgroupAdd(x) 继承自子组内所有活动调用的 x 求和
subgroupExclusiveAdd(x) 继承自子组内所有活动调用中 子组调用 ID 小于当前调用的 x 求和
subgroupInclusiveAdd(x) 继承自子组内所有活动调用中 子组调用 ID 小于等于当前调用的 x 求和
subgroupMul(x) 继承自子组内所有活动调用的 x 连乘
subgroupExclusiveMul(x) 继承自子组内所有活动调用中 子组调用 ID 小于 i 的 x 连乘
subgroupInclusiveMul(x) 继承自子组内所有活动调用中 子组调用 ID 小于等于 i 的 x 连乘
subgroupMax(x) 继承自子组内所有活动调用的 max(x)
subgroupMin(x) 继承自子组内所有活动调用的 min(x)
subgroupShuffle(x, id) 正确舍入
subgroupShuffleDown(x, delta) 正确舍入
subgroupShuffleUp(x, delta) 正确舍入
subgroupShuffleXor(x, mask) 正确舍入
quadBroadcast(x, id) 正确舍入
quadSwapDiagonal(x) 正确舍入
quadSwapX(x) 正确舍入
quadSwapY(x) 正确舍入
15.7.4.2. AbstractFloat 表达式的精度

AbstractFloat 运算的精度如下:

注意:将绝对误差用 ULP 表示时,其数值非常依赖底层浮点类型。

ULPAbstractFloat 值的定义,假设 AbstractFloat 等价于 IEEE-754binary64 类型。

对于 f32 值,1 个 ULP 大小是 IEEE-754 binary64 值 1 ULP 的 229 倍, 因为 binary64 尾数比 f32 多 29 位。

例如,假设某运算真实值为 x,实际计算为 x'。 若其误差 x-x' 在 f32 中为 3 ULP,则同样绝对误差 x-x' 在 AbstractFloat 中为 3·229 ULP。

15.7.5. 重结合与融合

重结合(reassociation)指在表达式中重新排列运算顺序,使得精确计算时答案不变。例如:

但用浮点数计算时结果可能不同。 重结合后的结果可能因近似而不准确,也可能因中间结果溢出或变为 NaN。

实现可以重结合运算。

实现还可以融合运算,只要变换后的表达式精度不低于原表达式。 例如,某些融合乘加(fused multiply-add)的实现比单独乘法再加法更精确。

15.7.6. 浮点数转换

本节描述标量转换中,源或目标为浮点类型的细节。

本节中,浮点类型可以是:

注意: 回顾:f32 WGSL 类型对应 IEEE-754 的 binary32f16 对应 binary16

浮点数到整数标量转换算法如下:

将浮点标量值 X 转换为 整数标量类型 T 的步骤:

注意: 换言之,非 NaN 情况下,浮点转整数先裁剪到目标类型范围,然后向零舍入。 这种裁剪要求是 WGSL 明确规定的,而在 C/C++ 下会导致未定义行为, IEEE-754 则会产生无效操作异常和 NaN。

注意: 例如:

数值标量转浮点算法如下:

算法: 数值标量转换为浮点

输入:

输出: XOut,即 X 转为 T 类型的结果,或产生错误。

过程:

注意: 某些整数值可能介于两个相邻可表示浮点值之间。 特别是,f32 类型有 23 位显式小数位。 且当浮点值在正规范围时(指数不极端),尾数为所有小数位加最高位(第 23 位)上的 1。 例如,整数 228 和 1+228 都映射到同一浮点值:最末 1 位的差异无法表示。 这种碰撞发生在绝对值至少 225 的相邻整数对之间。

注意: 当原类型为 i32u32,目标类型为 f32 时,原值始终在目标类型范围内。

注意: 当源类型为指数和尾数位数小于目标类型的浮点类型时,原值也始终在目标类型范围内。

15.7.7. 浮点表达式和内置函数的定义域

前述章节描述了在浮点表达式被求值于其定义域之外时的预期行为。

§ 8.7 算术表达式§ 17.5 数值内置函数分别定义了浮点表达式和内置函数的定义域。 若某操作未列出限制,则其定义域为全体:包括所有有限与无穷输入。 否则会明确列出定义域。

在许多情况下,WGSL 操作与 IEEE-754 定义的操作对应时,二者的定义域一致。 例如 WGSL 和 IEEE-754 的 acos 操作定义域都是 [−1,1]。

对于显式列出定义域的分量操作 WGSL 操作,仅描述了标量情形。向量情形由分量语义推导。

某些 WGSL 操作可能会用其它 WGSL 表达式实现。 § 15.7.4 浮点数精度中,对这类操作列为继承自的精度。 当列出此类操作的定义域时,有两种情况:

例如:dot(a,b) 函数对 2 元向量 ab 的精度继承自表达式 a[0] * b[0] + a[1] * b[1]。 这用到两次浮点乘法,一次浮点加法。

16. 关键字与记号摘要

16.1. 关键字摘要

16.2. 保留字

保留字是一种记号,为将来使用保留。 WGSL 模块不得包含保留字。

以下为保留字:

_reserved :

| '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. 语法记号

语法记号是一串特殊码点,用于:

语法记号包括:

17. 内置函数

某些函数是预声明的,由实现提供, 因此在 WGSL 模块中总是可用。 这些被称为内置函数

一个内置函数是一组同名的函数, 但通过其形式参数的数量、顺序和类型加以区分。 这些不同的变体称为重载

注意:每个用户自定义函数只有一个重载

每个重载如下描述:

调用内置函数时,所有参数都会在函数求值开始前先被求值。 见 § 11.2 函数调用

17.1. 构造器内置函数

值构造器内置函数用于显式创建某种类型的值。

WGSL 为所有预声明类型和所有可构造结构体类型提供值构造器。 构造器内置函数的拼写与类型相同。 在使用此类内置函数的地方,该标识符 必须在类型的作用域内,且该标识符 不得解析为其他声明。

注意:frexpmodfatomicCompareExchangeWeak 返回的结构体类型不能在 WGSL 模块中书写。

注意:类型的值声明需要在 WGSL 文本的该语句处有效。

WGSL 提供两类值构造器:

17.1.1. 零值内置函数

每个具体可构造类型 T 都有唯一的零值, 以及对应的内置函数,WGSL 写作类型名后跟空括号:T ()抽象数值类型也有零值,但没有内置函数可访问它们。

零值如下:

注意:WGSL 不为原子类型运行时大小数组或其他不可构造类型提供零值内置函数。

重载
@const @must_use fn T() -> T
参数化 T具体 可构造类型。
描述 构造类型 T零值

注意:AbstractInt 的零填充向量可写作 vec2()vec3()vec4()

示例:零值向量
vec2<f32>()                 // 两个 f32 分量的零值向量。
vec2<f32>(0.0, 0.0)         // 同样的值,显式写出。

vec3<i32>()                 // 三个 i32 分量的零值向量。
vec3<i32>(0, 0, 0)          // 同样的值,显式写出。
示例:零值数组
array<bool, 2>()               // 两个布尔值的零值数组。
array<bool, 2>(false, false)   // 同样的值,显式写出。
示例:零值结构体
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
重载
@const @must_use fn array<T, N>(e1 : T, ..., eN : T) -> array<T, N>
参数化 T具体可构造
描述 从元素构造数组

注意: array<T,N> 是可构造的,因为其元素数量 等于构造器参数个数,因此在着色器创建时即可确定。

重载
@const @must_use fn array(e1 : T, ..., eN : T) -> array<T, N>
参数化 T可构造
描述 从元素构造数组

分量类型由元素类型推断。 数组大小由元素个数决定。

17.1.2.2. bool
重载
@const @must_use fn bool(e : T) -> bool
参数化 T标量 类型。
描述 构造一个bool值。

Tbool,则为恒等操作。
否则为布尔类型强制转换。 若 e零值(对浮点型还包括 -0.0),结果为 false,否则为 true

17.1.2.3. f16
重载
@const @must_use fn f16(e : T) -> f16
参数化 T标量类型
描述 构造一个f16值。

Tf16,则为恒等操作。
T数值标量(非 f16),则 e 转为 f16(包括无效转换)。
Tbool,则 etrue 时结果为 1.0h,否则为 0.0h

17.1.2.4. f32
重载
@const @must_use fn f32(e : T) -> f32
参数化 T具体 标量类型
描述 构造一个f32值。

Tf32,则为恒等操作。
T数值标量(非 f32),则 e 转为 f32(包括无效转换)。
Tbool,则 etrue 时结果为 1.0f,否则为 0.0f

17.1.2.5. i32
重载
@const @must_use fn i32(e : T) -> i32
参数化 T标量类型
描述 构造一个i32值。

Ti32,则为恒等操作。
Tu32,则为位重解释(即结果为 i32 中与 e 位模式相同的唯一值)。
T浮点类型,则 e浮点转整型转换为 i32舍入朝零。
Tbool,则 etrue 时结果 1i,否则为 0i
TAbstractInt,若 e 能在 i32 表示则为恒等操作,否则产生着色器创建错误

17.1.2.6. mat2x2
重载
@const @must_use fn mat2x2<T>(e : mat2x2<S>) -> mat2x2<T>
@const @must_use fn mat2x2(e : mat2x2<S>) -> mat2x2<S>
参数化 Tf16f32
SAbstractFloatf16f32
描述 构造 2x2 列主序矩阵

TS 不同,则进行类型转换

重载
@const @must_use fn mat2x2<T>(v1 : vec2<T>, v2 : vec2<T>) -> mat2x2<T>
@const @must_use fn mat2x2(v1 : vec2<T>, v2 : vec2<T>) -> mat2x2<T>
参数化 TAbstractFloatf16f32
描述 从列向量构造 2x2 列主序矩阵
重载
@const @must_use fn mat2x2<T>(e1 : T, e2 : T, e3 : T, e4 : T) -> mat2x2<T>
@const @must_use fn mat2x2(e1 : T, e2 : T, e3 : T, e4 : T) -> mat2x2<T>
参数化 TAbstractFloatf16f32
描述 从元素构造 2x2 列主序矩阵

等价于 mat2x2(vec2(e1,e2), vec2(e3,e4))。

17.1.2.7. mat2x3
重载
@const @must_use fn mat2x3<T>(e : mat2x3<S>) -> mat2x3<T>
@const @must_use fn mat2x3(e : mat2x3<S>) -> mat2x3<S>
参数化 Tf16f32
SAbstractFloatf16f32
描述 构造一个 2x3 列主序矩阵

如果 TS 不同,则发生类型转换

重载
@const @must_use fn mat2x3<T>(v1 : vec3<T>, v2 : vec3<T>) -> mat2x3<T>
@const @must_use fn mat2x3(v1 : vec3<T>, v2 : vec3<T>) -> mat2x3<T>
参数化 TAbstractFloatf16f32
描述 从列向量构造 2x3 列主序矩阵
重载
@const @must_use fn mat2x3<T>(e1 : T, ..., e6 : T) -> mat2x3<T>
@const @must_use fn mat2x3(e1 : T, ..., e6 : T) -> mat2x3<T>
参数化 TAbstractFloatf16f32
描述 从元素构造 2x3 列主序矩阵

等价于 mat2x3(vec3(e1,e2,e3), vec3(e4,e5,e6))。

17.1.2.8. mat2x4
重载
@const @must_use fn mat2x4<T>(e : mat2x4<S>) -> mat2x4<T>
@const @must_use fn mat2x4(e : mat2x4<S>) -> mat2x4<S>
参数化 Tf16f32
SAbstractFloatf16f32
描述 构造一个 2x4 列主序矩阵

如果 TS 不同,则发生类型转换

重载
@const @must_use fn mat2x4<T>(v1 : vec4<T>, v2 : vec4<T>) -> mat2x4<T>
@const @must_use fn mat2x4(v1 : vec4<T>, v2 : vec4<T>) -> mat2x4<T>
参数化 TAbstractFloatf16f32
描述 从列向量构造 2x4 列主序矩阵
重载
@const @must_use fn mat2x4<T>(e1 : T, ..., e8 : T) -> mat2x4<T>
@const @must_use fn mat2x4(e1 : T, ..., e8 : T) -> mat2x4<T>
参数化 TAbstractFloatf16f32
描述 从元素构造 2x4 列主序矩阵

等价于 mat2x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8))。

17.1.2.9. mat3x2
重载
@const @must_use fn mat3x2<T>(e : mat3x2<S>) -> mat3x2<T>
@const @must_use fn mat3x2(e : mat3x2<S>) -> mat3x2<S>
参数化 Tf16f32
SAbstractFloatf16f32
描述 构造一个 3x2 列主序矩阵

如果 TS 不同,则发生类型转换

重载
@const @must_use fn mat3x2<T>(v1 : vec2<T>,
                              v2 : vec2<T>,
                              v3 : vec2<T>) -> mat3x2<T>
@const @must_use fn mat3x2(v1 : vec2<T>,
                           v2 : vec2<T>,
                           v3 : vec2<T>) -> mat3x2<T>
参数化 TAbstractFloatf16f32
描述 从列向量构造 3x2 列主序矩阵
重载
@const @must_use fn mat3x2<T>(e1 : T, ..., e6 : T) -> mat3x2<T>
@const @must_use fn mat3x2(e1 : T, ..., e6 : T) -> mat3x2<T>
参数化 TAbstractFloatf16f32
描述 从元素构造 3x2 列主序矩阵

等价于 mat3x2(vec2(e1,e2), vec2(e3,e4), vec2(e5,e6))。

17.1.2.10. mat3x3
重载
@const @must_use fn mat3x3<T>(e : mat3x3<S>) -> mat3x3<T>
@const @must_use fn mat3x3(e : mat3x3<S>) -> mat3x3<S>
参数化 Tf16f32
SAbstractFloatf16f32
描述 构造一个 3x3 列主序矩阵

如果 TS 不同,则发生类型转换

重载
@const @must_use fn mat3x3<T>(v1 : vec3<T>,
                              v2 : vec3<T>,
                              v3 : vec3<T>) -> mat3x3<T>
@const @must_use fn mat3x3(v1 : vec3<T>,
                           v2 : vec3<T>,
                           v3 : vec3<T>) -> mat3x3<T>
参数化 TAbstractFloatf16f32
描述 从列向量构造 3x3 列主序矩阵
重载
@const @must_use fn mat3x3<T>(e1 : T, ..., e9 : T) -> mat3x3<T>
@const @must_use fn mat3x3(e1 : T, ..., e9 : T) -> mat3x3<T>
参数化 TAbstractFloatf16f32
描述 从元素构造 3x3 列主序矩阵

等价于 mat3x3(vec3(e1,e2,e3), vec3(e4,e5,e6), vec3(e7,e8,e9))。

17.1.2.11. mat3x4
重载
@const @must_use fn mat3x4<T>(e : mat3x4<S>) -> mat3x4<T>
@const @must_use fn mat3x4(e : mat3x4<S>) -> mat3x4<S>
参数化 Tf16f32
SAbstractFloatf16f32
描述 构造一个 3x4 列主序矩阵

如果 TS 不同,则发生类型转换

重载
@const @must_use fn mat3x4<T>(v1 : vec4<T>,
                              v2 : vec4<T>,
                              v3 : vec4<T>) -> mat3x4<T>
@const @must_use fn mat3x4(v1 : vec4<T>,
                           v2 : vec4<T>,
                           v3 : vec4<T>) -> mat3x4<T>
参数化 TAbstractFloatf16f32
描述 从列向量构造 3x4 列主序矩阵
重载
@const @must_use fn mat3x4<T>(e1 : T, ..., e12 : T) -> mat3x4<T>
@const @must_use fn mat3x4(e1 : T, ..., e12 : T) -> mat3x4<T>
参数化 TAbstractFloatf16f32
描述 从元素构造 3x4 列主序矩阵

等价于 mat3x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8), vec4(e9,e10,e11,e12))。

17.1.2.12. mat4x2
重载
@const @must_use fn mat4x2<T>(e : mat4x2<S>) -> mat4x2<T>
@const @must_use fn mat4x2(e : mat4x2<S>) -> mat4x2<S>
参数化 Tf16f32
SAbstractFloatf16f32
描述 构造一个4x2列主序矩阵

如果 TS 不同,则会发生类型转换

重载
@const @must_use fn mat4x2<T>(v1 : vec2<T>,
                              v2 : vec2<T>,
                              v3 : vec2<T>,
                              v4: vec2<T>) -> mat4x2<T>
@const @must_use fn mat4x2(v1 : vec2<T>,
                           v2 : vec2<T>,
                           v3 : vec2<T>,
                           v4: vec2<T>) -> mat4x2<T>
参数化 TAbstractFloatf16f32
描述 从列向量构造一个4x2列主序矩阵
重载
@const @must_use fn mat4x2<T>(e1 : T, ..., e8 : T) -> mat4x2<T>
@const @must_use fn mat4x2(e1 : T, ..., e8 : T) -> mat4x2<T>
参数化 TAbstractFloatf16f32
描述 从元素构造一个4x2列主序矩阵

等价于 mat4x2(vec2(e1,e2), vec2(e3,e4), vec2(e5,e6), vec2(e7,e8))。

17.1.2.13. mat4x3
重载
@const @must_use fn mat4x3<T>(e : mat4x3<S>) -> mat4x3<T>
@const @must_use fn mat4x3(e : mat4x3<S>) -> mat4x3<S>
参数化 Tf16f32
SAbstractFloatf16f32
描述 构造一个4x3列主序矩阵

如果 TS 不同,则会发生类型转换

重载
@const @must_use fn mat4x3<T>(v1 : vec3<T>,
                              v2 : vec3<T>,
                              v3 : vec3<T>,
                              v4 : vec3<T>) -> mat4x3<T>
@const @must_use fn mat4x3(v1 : vec3<T>,
                           v2 : vec3<T>,
                           v3 : vec3<T>,
                           v4 : vec3<T>) -> mat4x3<T>
参数化 TAbstractFloatf16f32
描述 从列向量构造一个4x3列主序矩阵
重载
@const @must_use fn mat4x3<T>(e1 : T, ..., e12 : T) -> mat4x3<T>
@const @must_use fn mat4x3(e1 : T, ..., e12 : T) -> mat4x3<T>
参数化 TAbstractFloatf16f32
描述 从元素构造一个4x3列主序矩阵

等价于 mat4x3(vec3(e1,e2,e3), vec3(e4,e5,e6), vec3(e7,e8,e9), vec3(e10,e11,e12))。

17.1.2.14. mat4x4
重载
@const @must_use fn mat4x4<T>(e : mat4x4<S>) -> mat4x4<T>
@const @must_use fn mat4x4(e : mat4x4<S>) -> mat4x4<S>
参数化 Tf16f32
SAbstractFloatf16f32
描述 构造一个4x4列主序矩阵

如果 TS 不同,则会发生类型转换

重载
@const @must_use fn mat4x4<T>(v1 : vec4<T>,
                              v2 : vec4<T>,
                              v3 : vec4<T>,
                              v4 : vec4<T>) -> mat4x4<T>
@const @must_use fn mat4x4(v1 : vec4<T>,
                           v2 : vec4<T>,
                           v3 : vec4<T>,
                           v4 : vec4<T>) -> mat4x4<T>
参数化 TAbstractFloatf16f32
描述 从列向量构造一个4x4列主序矩阵
重载
@const @must_use fn mat4x4<T>(e1 : T, ..., e16 : T) -> mat4x4<T>
@const @must_use fn mat4x4(e1 : T, ..., e16 : T) -> mat4x4<T>
参数化 TAbstractFloatf16f32
描述 从元素构造一个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. 结构体
重载
@const @must_use fn S(e1 : T1, ..., eN : TN) -> S
参数化 S 是一个可构造结构体类型,其成员类型为 T1 ... TN
描述 从成员构造类型为 S结构体
17.1.2.16. u32
重载
@const @must_use fn u32(e : T) -> u32
参数化 T标量类型
描述 构造一个u32值。

如果 Tu32,这是恒等操作。
如果 Ti32,这是位重解释(即结果是 u32 中具有与 e 相同位模式的唯一值)。
如果 T浮点类型e 会以转换u32向零舍入
如果 Tbool,结果为 1u 如果 etrue,否则为 0u
如果 TAbstractInt,如果 e 能被表示为 u32,则为恒等操作,否则会产生着色器创建错误

注意:AbstractInt 的重载存在,使得诸如 u32(4*1000*1000*1000) 这样的表达式可以创建一个本来会溢出的 u32 值。如果没有该重载,重载解析会选择 u32(i32) 重载,AbstractInt 表达式会自动转换为 i32,从而因溢出导致着色器创建错误。

17.1.2.17. vec2
重载
@const @must_use fn vec2<T>(e : T) -> vec2<T>
@const @must_use fn vec2(e : S) -> vec2<S>
参数化 T具体标量
S标量
描述 构造一个以 e 作为两个分量的二维向量
重载
@const @must_use fn vec2<T>(e : vec2<S>) -> vec2<T>
@const @must_use fn vec2(e : vec2<S>) -> vec2<S>
参数化 T具体标量
S标量
描述 分量方式构造二维向量,分量为 e.xe.y

如果 TS 不同,则进行类型转换,分量为 T(e.x)T(e.y)

重载
@const @must_use fn vec2<T>(e1 : T, e2 : T) -> vec2<T>
@const @must_use fn vec2(e1 : T, e2 : T) -> vec2<T>
参数化 T标量
描述 分量方式构造二维向量,分量为 e1e2
重载
@const @must_use fn vec2() -> vec2<T>
参数化 T 是 AbstractInt
描述 返回值 vec2(0,0)
17.1.2.18. vec3
重载
@const @must_use fn vec3<T>(e : T) -> vec3<T>
@const @must_use fn vec3(e : S) -> vec3<S>
参数化 T 是一个 具体 标量
S标量
描述 构造一个所有分量均为 e 的三分量向量
重载
@const @must_use fn vec3<T>(e : vec3<S>) -> vec3<T>
@const @must_use fn vec3(e : vec3<S>) -> vec3<S>
参数化 T 是一个 具体 标量
S标量
描述 分量方式 构造一个三分量向量,其分量分别为 e.xe.ye.z

如果 T 不等于 S,会进行类型转换,分量分别为 T(e.x)T(e.y)T(e.z)

重载
@const @must_use fn vec3<T>(e1 : T, e2 : T, e3 : T) -> vec3<T>
@const @must_use fn vec3(e1 : T, e2 : T, e3 : T) -> vec3<T>
参数化 T标量
描述 分量方式 构造一个三分量向量,其分量为 e1e2e3
重载
@const @must_use fn vec3<T>(v1 : vec2<T>, e1 : T) -> vec3<T>
@const @must_use fn vec3(v1 : vec2<T>, e1 : T) -> vec3<T>
参数化 T标量
描述 分量方式 构造一个三分量向量,其分量为 v1.xv1.ye1
重载
@const @must_use fn vec3<T>(e1 : T, v1 : vec2<T>) -> vec3<T>
@const @must_use fn vec3(e1 : T, v1 : vec2<T>) -> vec3<T>
参数化 T标量
描述 分量方式 构造一个三分量向量,其分量为 e1v1.xv1.y
重载
@const @must_use fn vec3() -> vec3<T>
参数化 T 是 AbstractInt
描述 返回值为 vec3(0,0,0)
17.1.2.19. vec4
重载
@const @must_use fn vec4<T>(e : T) -> vec4<T>
@const @must_use fn vec4(e : S) -> vec4<S>
参数化 T 是一个 具体 标量
S标量
描述 构造一个所有分量均为 e 的四分量向量
重载
@const @must_use fn vec4<T>(e : vec4<S>) -> vec4<T>
@const @must_use fn vec4(e : vec4<S>) -> vec4<S>
参数化 T 是一个 具体 标量
S标量
描述 分量方式 构造一个四分量向量,其分量分别为 e.xe.ye.ze.w

如果 T 不等于 S,会进行类型转换,分量分别为 T(e.x)T(e.y)T(e.z)T(e.w)

重载
@const @must_use fn vec4<T>(e1 : T, e2 : T, e3 : T, e4 : T) -> vec4<T>
@const @must_use fn vec4(e1 : T, e2 : T, e3 : T, e4 : T) -> vec4<T>
参数化 T标量
描述 分量方式 构造一个四分量向量,其分量为 e1e2e3e4
重载
@const @must_use fn vec4<T>(e1 : T, v1 : vec2<T>, e2 : T) -> vec4<T>
@const @must_use fn vec4(e1 : T, v1 : vec2<T>, e2 : T) -> vec4<T>
参数化 T标量
描述 分量方式 构造一个四分量向量,其分量为 e1v1.xv1.ye2
重载
@const @must_use fn vec4<T>(e1 : T, e2 : T, v1 : vec2<T>) -> vec4<T>
@const @must_use fn vec4(e1 : T, e2 : T, v1 : vec2<T>) -> vec4<T>
参数化 T标量
描述 分量方式 构造一个四分量向量,其分量为 e1e2v1.xv1.y
重载
@const @must_use fn vec4<T>(v1 : vec2<T>, v2 : vec2<T>) -> vec4<T>
@const @must_use fn vec4(v1 : vec2<T>, v2 : vec2<T>) -> vec4<T>
参数化 T标量
描述 分量方式 构造一个四分量向量,其分量为 v1.xv1.yv2.xv2.y
重载
@const @must_use fn vec4<T>(v1 : vec2<T>, e1 : T, e2 : T) -> vec4<T>
@const @must_use fn vec4(v1 : vec2<T>, e1 : T, e2 : T) -> vec4<T>
参数化 T标量
描述 分量方式 构造一个四分量向量,其分量为 v1.xv1.ye1e2
重载
@const @must_use fn vec4<T>(v1 : vec3<T>, e1 : T) -> vec4<T>
@const @must_use fn vec4(v1 : vec3<T>, e1 : T) -> vec4<T>
参数化 T标量
描述 分量方式 构造一个四分量向量,其分量为 v1.xv1.yv1.ze1
重载
@const @must_use fn vec4(e1 : T, v1 : vec3<T>) -> vec4<T>
@const @must_use fn vec4(e1 : T, v1 : vec3<T>) -> vec4<T>
参数化 T标量
描述 分量方式 构造一个四分量向量,其分量为 e1v1.xv1.yv1.z
重载
@const @must_use fn vec4() -> vec4<T>
参数化 T 是 AbstractInt
描述 返回值为 vec4(0,0,0,0)

17.2. 位重解释内置函数

17.2.1. bitcast

bitcast 内置函数用于将一个类型的值的位表示重新解释为另一个类型的值。

内部布局规则见 § 14.4.4 值的内部布局

重载
@const @must_use fn bitcast<T>(e : T) -> T
参数化 T具体 数值标量具体 数值向量
描述 恒等变换。
T向量 时为分量方式
结果为 e
重载
@const @must_use fn bitcast<T>(e : S) -> T
参数化 S 为 i32、u32 或 f32
T 不为 S,且为 i32、u32 或 f32
描述 作为 T 的位重解释。
结果为将 e 的位以 T 类型值解释的结果。
重载
@const @must_use fn bitcast<vecN<T>>(e : vecN<S>) -> vecN<T>
参数化 S 为 i32、u32 或 f32
T 不为 S,且为 i32、u32 或 f32
描述 分量方式 的位重解释为 T
结果为将 e 的位以 vecN<T> 类型值解释的结果。
重载
@const @must_use fn bitcast<u32>(e : AbstractInt) -> u32
@const @must_use fn bitcast<vecN<u32>>(e : vecN<AbstractInt>) -> vecN<u32>
参数化
描述 如果 e 可以表示为 u32,则为恒等操作, 否则会产生着色器创建错误。 即,结果与 u32(e) 相同。

e 是向量时,分量方式

重载
@const @must_use fn bitcast<T>(e : vec2<f16>) -> T
参数化 T 为 i32、u32 或 f32
描述 分量方式 的位重解释为 T
结果为将 e 的 32 位按照内部布局规则解释为 T 类型值的结果。
重载
@const @must_use fn bitcast<vec2<T>>(e : vec4<f16>) -> vec2<T>
参数化 T 为 i32、u32 或 f32
描述 分量方式 的位重解释为 T
结果为将 e 的 64 位按照内部布局规则解释为 T 类型值的结果。
重载
@const @must_use fn bitcast<vec2<f16>>(e : T) -> vec2<f16>
参数化 T 为 i32、u32 或 f32
描述 分量方式 的位重解释为 f16。
结果为将 e 的 32 位按照内部布局规则解释为 f16 类型值的结果。
重载
@const @must_use fn bitcast<vec4<f16>>(e : vec2<T>) -> vec4<f16>
参数化 T 为 i32、u32 或 f32
描述 分量方式 的位重解释为 vec2<f16>
结果为将 e 的 64 位按照内部布局规则解释为 f16 类型值的结果。

17.3. 逻辑内置函数

17.3.1. all

重载
@const @must_use fn all(e: vecN<bool>) -> bool
描述 e 的所有分量都为 true 时返回 true。
重载
@const @must_use fn all(e: bool) -> bool
描述 返回 e

17.3.2. any

重载
@const @must_use fn any(e: vecN<bool>) -> bool
描述 e 的任意一个分量为 true 时返回 true。
重载
@const @must_use fn any(e: bool) -> bool
描述 返回 e

17.3.3. select

重载
@const @must_use fn select(f: T,
                           t: T,
                           cond: bool) -> T
参数化 T标量向量
描述 cond 为 true 时返回 t,否则返回 f
重载
@const @must_use fn select(f: vecN<T>,
                           t: vecN<T>,
                           cond: vecN<bool>) -> vecN<T>
参数化 T标量
描述 分量方式 选择。结果分量 i 的计算为 select(f[i], t[i], cond[i])

17.4. 数组内置函数

17.4.1. arrayLength

重载
@must_use fn arrayLength(p: ptr<storage, array<E>, AM>) -> u32
参数化 E 是一个运行时大小数组的元素类型,
访问模式 AMreadread_write
描述 返回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

重载
@const @must_use fn abs(e: T ) -> T
参数化 S 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16
T 为 S,或 vecN<S>
描述 e 的绝对值。 当 T 是向量时,分量方式 计算。

如果 e 是浮点类型,则结果为正号的 e。 如果 e 是无符号整数标量类型,则结果为 e 本身。 如果 e 是有符号整数标量类型且取最大负值,则结果为 e 本身。

17.5.2. acos

重载
@const @must_use fn acos(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的反余弦(cos-1)的主值(弧度)。
即,近似 x(0 ≤ x ≤ π),使得 cos(x) = e

T 是向量时,分量方式 计算。

标量 定义域 区间 [−1, 1]

17.5.3. acosh

重载
@const @must_use fn acosh(x: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 x 的反双曲余弦(cosh-1),以双曲角表示。
即,近似 a(0 ≤ a ≤ +∞),使得 cosh(a) = x

T 是向量时,分量方式 计算。

标量 定义域 区间 [1, +∞]

17.5.4. asin

重载
@const @must_use fn asin(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的反正弦(sin-1)的主值(弧度)。
即,近似 x(-π/2 ≤ x ≤ π/2),使得 sin(x) = e

T 是向量时,分量方式 计算。

标量 定义域 区间 [−1, 1]

17.5.5. asinh

重载
@const @must_use fn asinh(y: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 y 的反双曲正弦(sinh-1),以双曲角表示。
即,近似 a,使得 sinh(y) = a

T 是向量时,分量方式 计算。

17.5.6. atan

重载
@const @must_use fn atan(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的反正切(tan-1)的主值(弧度)。
即,近似 x(− π/2 ≤ x ≤ π/2),使得 tan(x) = e

T 是向量时,分量方式 计算。

17.5.7. atanh

重载
@const @must_use fn atanh(t: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 t 的反双曲正切(tanh-1),以双曲角表示。
即,近似 a,使得 tanh(a) = t

T 是向量时,分量方式 计算。

标量 定义域 区间 [−1, 1]

17.5.8. atan2

重载
@const @must_use fn atan2(y: T,
                          x: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回一个 角度,单位为弧度,范围为 [−π, π],其正切值为 y÷x

结果所在象限取决于 yx 的符号。 例如,该函数的实现可能如下:

  • x > 0 时,atan(y/x)

  • 当 (x < 0) 且 (y > 0) 时,atan(y/x) + π

  • 当 (x < 0) 且 (y < 0) 时,atan(y/x) - π

注意: 结果的误差是无界的:
  • abs(x) 非常小时,例如对于其类型为非规格化数

  • 在原点 (x,y) = (0,0),或

  • y 是非规格化数或无穷大时。

T 是向量时,分量方式 计算。

17.5.9. ceil

重载
@const @must_use fn ceil(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的上取整值。 当 T 是向量时,分量方式 计算。

17.5.10. clamp

重载
@const @must_use fn clamp(e: T,
                          low: T,
                          high: T) -> T
参数化 S 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16
T 为 S,或 vecN<S>
描述 限制 e 的值在指定范围内。

如果 T 是整数类型,则结果为 min(max(e, low), high)

如果 T 是浮点类型,则结果为 min(max(e, low), high),或者三个值 elowhigh 的中值。

T 是向量时,分量方式 计算。

如果 low 大于 high,则:

17.5.11. cos

重载
@const @must_use fn cos(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的余弦值,其中 e 的单位为弧度。 当 T 是向量时,分量方式 计算。
标量 定义域 区间 (−∞, +∞)

17.5.12. cosh

重载
@const @must_use fn cosh(a: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 a 的双曲余弦值,其中 a双曲角。 近似于纯数学函数 (ea + e−a)÷2, 但不一定以该方式计算。

T 是向量时,分量方式 计算。

17.5.13. countLeadingZeros

重载
@const @must_use fn countLeadingZeros(e: T) -> T
参数化 T 为 i32、u32、vecN<i32> 或 vecN<u32>
描述 T 为标量类型时,返回 e 从最高有效位开始连续 0 的个数。
T 为向量时,分量方式 计算。
在某些语言中也称为 "clz"。

17.5.14. countOneBits

重载
@const @must_use fn countOneBits(e: T) -> T
参数化 T 为 i32、u32、vecN<i32> 或 vecN<u32>
描述 返回 e 表示中 1 的数量。
也称为 "population count"。
T 为向量时,分量方式 计算。

17.5.15. countTrailingZeros

重载
@const @must_use fn countTrailingZeros(e: T) -> T
参数化 T 为 i32、u32、vecN<i32> 或 vecN<u32>
描述 T 为标量类型时,返回 e 从最低有效位开始连续 0 的个数。
T 为向量时,分量方式 计算。
在某些语言中也称为 "ctz"。

17.5.16. cross

重载
@const @must_use fn cross(a: vec3<T>,
                          b: vec3<T>) -> vec3<T>
参数化 T 为 AbstractFloat、f32 或 f16
描述 返回 e1e2 的叉积。
定义域 由可能的实现给出的线性项隐含
  • a[1] × b[2] − a[2] × b[1]

  • a[2] × b[0] − a[0] × b[2]

  • a[0] × b[1] − a[1] × b[0]

17.5.17. degrees

重载
@const @must_use fn degrees(e1: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 将弧度转换为角度,近似为 e1 × 180 ÷ π。 当 T 是向量时,分量方式 计算。

17.5.18. determinant

重载
@const @must_use fn determinant(e: matCxC<T>) -> T
参数化 T 为 AbstractFloat、f32 或 f16
描述 返回 e 的行列式。
定义域 标准数学定义中线性项隐含

17.5.19. distance

重载
@const @must_use fn distance(e1: T,
                             e2: T) -> S
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e1e2 之间的距离(如 length(e1 - e2))。

定义域为所有能进行 e1e2 的向量对(e1,e2)。 即,除非某个分量 ie1[i]e2[i] 都为同一个无穷大,否则都有效。

17.5.20. dot

重载
@const @must_use fn dot(e1: vecN<T>,
                        e2: vecN<T>) -> T
参数化 T 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16
描述 返回 e1e2 的点积。
定义域 为所有项 e1[i] × e2[i] 求和的线性项隐含

17.5.21. dot4U8Packed

重载
@const @must_use fn dot4U8Packed(e1: u32,
                                 e2: u32) -> u32
描述 e1e2 被解释为含有四个 8 位无符号整型分量的向量。 返回这两个向量的无符号整型点积。

17.5.22. dot4I8Packed

重载
@const @must_use fn dot4I8Packed(e1: u32,
                                 e2: u32) -> i32
描述 e1e2 被解释为包含四个 8 位有符号整数分量的向量。 返回这两个向量的有符号整数点积。每个分量在乘法前都被符号扩展为 i32,然后加法操作在 WGSL 的 i32 中完成(由于结果在 -65024 到 65536 范围内,数学上保证不会溢出,这也在 i32 可表示的范围内)。

17.5.23. exp

重载
@const @must_use fn exp(e1: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e1 的自然指数(如 ee1)。 当 T 是向量时,分量方式 计算。

17.5.24. exp2

重载
@const @must_use fn exp2(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 2 的 e 次幂(如 2e)。 当 T 是向量时,分量方式 计算。

17.5.25. extractBits(有符号)

重载
@const @must_use fn extractBits(e: T,
                                offset: u32,
                                count: u32) -> T
参数化 T 为 i32 或 vecN<i32>
描述 从整数中读取位,带符号扩展。

T 为标量类型时:

  • wT 的位宽
  • o = min(offset, w)
  • c = min(count, w - o)
  • c 为 0,结果为 0。
  • 否则,结果的第 0..c - 1 位从 e 的第 o..o + c - 1 位复制。 结果的其它位与结果的第 c - 1 位相同。
T 为向量时,分量方式 计算。

如果 count + offset 大于 w,则:

17.5.26. extractBits(无符号)

重载
@const @must_use fn extractBits(e: T,
                                offset: u32,
                                count: u32) -> T
参数化 T 为 u32 或 vecN<u32>
描述 从整数中读取位,不进行符号扩展。

T 为标量类型时:

  • wT 的位宽
  • o = min(offset, w)
  • c = min(count, w - o)
  • c 为 0,则结果为 0。
  • 否则,结果的第 0..c - 1 位从 e 的第 o..o + c - 1 位复制。 结果的其他位为 0。
T 为向量时,分量方式 计算。

如果 count + offset 大于 w,则:

17.5.27. faceForward

重载
@const @must_use fn faceForward(e1: T,
                                e2: T,
                                e3: T) -> T
参数化 T 为 vecN<AbstractFloat>、vecN<f32> 或 vecN<f16>
描述 如果 dot(e2, e3) 为负,返回 e1,否则返回 -e1
定义域 定义域受 dot(e2,e3) 运算限制:由所有项 e2[i] × e3[i] 的线性项隐含。

17.5.28. firstLeadingBit(有符号)

重载
@const @must_use fn firstLeadingBit(e: T) -> T
参数化 T 为 i32 或 vecN<i32>
描述 T 为标量,则结果为:
  • e 为 0 或 -1,结果为 -1。
  • 否则为 e 中与符号位不同的最高有效位的位置。

分量方式 计算,当 T 为向量类型时。

注意: 有符号整数使用二进制补码表示, 符号位在最高有效位。

17.5.29. firstLeadingBit(无符号)

重载
@const @must_use fn firstLeadingBit(e: T) -> T
参数化 T 为 u32 或 vecN<u32>
描述 T 为标量,则结果为:
  • e 为 0,结果为 T(-1)
  • 否则为 e 中最高有效的 1 位的位置。
T 为向量时,分量方式 计算。

17.5.30. firstTrailingBit

重载
@const @must_use fn firstTrailingBit(e: T) -> T
参数化 T 为 i32、u32、vecN<i32> 或 vecN<u32>
描述 T 为标量,则结果为:
  • e 为 0,结果为 T(-1)
  • 否则为 e 中最低有效的 1 位的位置。
T 为向量时,分量方式 计算。

17.5.31. floor

重载
@const @must_use fn floor(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的下取整值。 当 T 是向量时,分量方式 计算。

17.5.32. fma

重载
@const @must_use fn fma(e1: T,
                        e2: T,
                        e3: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e1 * e2 + e3。 当 T 是向量时,分量方式 计算。

注意:fma 代表“融合乘加”。

注意: IEEE-754fusedMultiplyAdd 操作会以无界范围和精度计算中间结果,只对最终结果四舍五入 到目标类型。 但 § 15.7.4 浮点精度 关于 fma 的规则允许实现为先普通乘法再普通加法。 此时中间结果可能溢出或精度丢失,整体操作并非真正“融合”。

定义域 为表达式 e2 × e2 + e3 的线性项隐含

17.5.33. fract

重载
@const @must_use fn fract(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的小数部分,计算方式为 e - floor(e)
T 是向量时,分量方式 计算。

注意: 有效结果在闭区间 [0, 1.0]。 例如,若 e 是极小的负数,则 fract(e) 可能为 1.0。

17.5.34. frexp

重载
@const @must_use fn frexp(e: T) -> __frexp_result_f32
参数化 T 为 f32
描述 e 拆分成小数部分和指数部分。
  • e 为 0 时,小数部分为 0。

  • e 为非零正常数时,e = fraction * 2exponent,其中 fraction 在 [0.5, 1.0) 或 (-1.0, -0.5]。

  • 否则,e非规格化、NaN 或无穷大。结果的小数和指数为未定值

返回 __frexp_result_f32 内置结构体,定义如下:

struct __frexp_result_f32 {
  fract : f32, // 小数部分
  exp : i32    // 指数部分
}

注意: frexp 代表“fraction and exponent”。

示例:frexp 用法
// 推断结果类型
let fraction_and_exponent = frexp(1.5);
// fraction_only 被设置为 0.75
let fraction_only = frexp(1.5).fract;

注意: 不能显式声明类型为 __frexp_result_f32 的值,但可以推断类型。

重载
@const @must_use fn frexp(e: T) -> __frexp_result_f16
参数化 T 为 f16
描述 e 拆分成小数部分和指数部分。
  • e 为 0 时,小数部分为 0。

  • e 为非零正常数时,e = fraction * 2exponent,其中 fraction 在 [0.5, 1.0) 或 (-1.0, -0.5]。

  • 否则,e非规格化、NaN 或无穷大。结果的小数和指数为未定值

返回 __frexp_result_f16 内置结构体,定义如下:

struct __frexp_result_f16 {
  fract : f16, // 小数部分
  exp : i32    // 指数部分
}

注意: frexp 代表“fraction and exponent”。

注意: 不能显式声明类型为 __frexp_result_f16 的值,但可以推断类型。

重载
@const @must_use fn frexp(e: T) -> __frexp_result_abstract
参数化 T 为 AbstractFloat
描述 e 拆分成小数部分和指数部分。
  • e 为 0 时,小数部分为 0。

  • e 为非零正常数时,e = fraction * 2exponent,其中 fraction 在 [0.5, 1.0) 或 (-1.0, -0.5]。

  • e非规格化,小数和指数都具有无界误差。 fraction 可为任意 AbstractFloat,exponent 可为任意 AbstractInt。

注意: AbstractFloat 计算结果为无穷大或 NaN 时会导致着色器创建错误

返回 __frexp_result_abstract 内置结构体,定义如下:

struct __frexp_result_abstract {
  fract : AbstractFloat, // 小数部分
  exp : AbstractInt      // 指数部分
}

注意: frexp 代表“fraction and exponent”。

示例:abstract frexp 用法
// 推断结果类型
const fraction_and_exponent = frexp(1.5);
// fraction_only 设置为 0.75
const fraction_only = frexp(1.5).fract;

注意: 不能显式声明类型为 __frexp_result_abstract 的值,但可以推断类型。

重载
@const @must_use fn frexp(e: T) -> __frexp_result_vecN_f32
参数化 T 为 vecN<f32>
描述 e 的每个分量 ei 拆分为小数和指数部分。
  • ei 为 0 时,小数部分为 0。

  • ei 为非零正常数时,ei = fraction * 2exponent,其中 fraction 在 [0.5, 1.0) 或 (-1.0, -0.5]。

  • 否则,ei 为 NaN 或无穷大。结果的小数和指数为未定值

返回 __frexp_result_vecN_f32 内置结构体,定义如下:

struct __frexp_result_vecN_f32 {
  fract : vecN<f32>, // 小数部分
  exp : vecN<i32>    // 指数部分
}

注意: frexp 代表“fraction and exponent”。

注意: 不能显式声明类型为 __frexp_result_vecN_f32 的值,但可以推断类型。

重载
@const @must_use fn frexp(e: T) -> __frexp_result_vecN_f16
参数化 T 为 vecN<f16>
描述 e 的每个分量 ei 拆分为小数和指数部分。
  • ei 为 0 时,小数部分为 0。

  • ei 为非零正常数时,ei = fraction * 2exponent,其中 fraction 在 [0.5, 1.0) 或 (-1.0, -0.5]。

  • 否则,ei 为 NaN 或无穷大。结果的小数和指数为未定值

返回 __frexp_result_vecN_f16 内置结构体,定义如下:

struct __frexp_result_vecN_f16 {
  fract : vecN<f16>, // 小数部分
  exp : vecN<i32>    // 指数部分
}

注意: frexp 代表“fraction and exponent”。

注意: 不能显式声明类型为 __frexp_result_vecN_f16 的值,但可以推断类型。

重载
@const @must_use fn frexp(e: T) -> __frexp_result_vecN_abstract
参数化 T 为 vecN<AbstractFloat>
描述 e 的每个分量 ei 拆分为小数和指数部分。
  • ei 为 0 时,小数部分为 0。

  • ei 为非零正常数时,ei = fraction * 2exponent,其中 fraction 在 [0.5, 1.0) 或 (-1.0, -0.5]。

  • ei非规格化,小数和指数都具有无界误差。 fraction 可为任意 AbstractFloat,exponent 可为任意 AbstractInt。

注意: AbstractFloat 计算结果为无穷大或 NaN 时会导致着色器创建错误

返回 __frexp_result_vecN_abstract 内置结构体,定义如下:

struct __frexp_result_vecN_abstract {
  fract : vecN<AbstractFloat>, // 小数部分
  exp : vecN<AbstractInt>      // 指数部分
}

注意: frexp 代表“fraction and exponent”。

注意: 不能显式声明类型为 __frexp_result_vecN_abstract 的值,但可以推断类型。

17.5.35. insertBits

重载
@const @must_use fn insertBits(e: T,
                              newbits: T,
                              offset: u32,
                              count: u32) -> T
参数化 T 为 i32、u32、vecN<i32> 或 vecN<u32>
描述 设置整数中的位。

T 为标量类型时:

  • wT 的位宽
  • o = min(offset, w)
  • c = min(count, w - o)
  • c 为 0,结果为 e
  • 否则, 结果的第 o..o + c - 1 位取自 newbits 的第 0..c - 1 位。 结果的其它位取自 e
T 为向量时,分量方式 计算。

如果 count + offset 大于 w,则:

17.5.36. inverseSqrt

重载
@const @must_use fn inverseSqrt(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 sqrt(e) 的倒数。 当 T 是向量时,分量方式 计算。
标量 定义域 区间 [0, +∞]

17.5.37. ldexp

重载
@const @must_use fn ldexp(e1: T,
                          e2: I) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
I 为 AbstractInt、i32、vecN<AbstractInt> 或 vecN<i32>
I 为向量当且仅当 T 为向量
T 仅当 I 也为 抽象类型时可为抽象类型,反之亦然

注意: 若任一参数为 具体类型 则另一参数会自动转换为具体类型(如适用),结果也为具体类型。

描述 返回 e1 * 2e2,但有例外:

其中,bias 是浮点格式的指数偏移:

  • f16 为 15

  • f32 为 127

  • AbstractFloat 为 1023(当 AbstractFloat 是 IEEE-754 binary64 时)

x 为 0 或其类型的有限正常值,则:

x = ldexp(frexp(x).fract, frexp(x).exp)

T 为向量时,分量方式 计算。

注意: ldexp 记忆法为“load exponent”。名称可能来源于 PDP-11 浮点运算单元的同名指令。

17.5.38. length

重载
@const @must_use fn length(e: T) -> S
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的长度。
T标量,结果为 e 的绝对值。
T 为向量类型,结果为 sqrt(e[0]2 + e[1]2 + ...)

注意: 标量情况可实现为 sqrt(e * e),这可能导致不必要的溢出或精度损失。

17.5.39. log

重载
@const @must_use fn log(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的自然对数值。 当 T 是向量时,分量方式 计算。
标量 定义域 区间 [0, +∞]

17.5.40. log2

重载
@const @must_use fn log2(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的以 2 为底的对数值。 当 T 是向量时,分量方式 计算。
标量 定义域 区间 [0, +∞]

17.5.41. max

重载
@const @must_use fn max(e1: T,
                        e2: T) -> T
参数化 S 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16
T 为 S,或 vecN<S>
描述 e1 小于 e2,则返回 e2,否则返回 e1。 当 T 是向量时,分量方式 计算。

e1e2 都是浮点值,则:

  • 如果 e1e2 都是非规格化数,结果可以是任意一个值。

17.5.42. min

重载
@const @must_use fn min(e1: T,
                        e2: T) -> T
参数化 S 为 AbstractInt、AbstractFloat、i32、u32、f32 或 f16
T 为 S,或 vecN<S>
描述 e2 小于 e1,则返回 e2,否则返回 e1。 当 T 是向量时,分量方式 计算。

e1e2 都是浮点值,则:

  • 如果 e1e2 都是非规格化数,结果可以是任意一个值。

17.5.43. mix

重载
@const @must_use fn mix(e1: T,
                        e2: T,
                        e3: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e1e2 的线性混合(如 e1 * (T(1) - e3) + e2 * e3)。 当 T 是向量时,分量方式 计算。
定义域 由表达式 e1[i] × (1 − e3[i]) + e2[i] × e3[i] 的线性项隐含。 e2[i] × e2[i] + e3[i]。
重载
@const @must_use fn mix(e1: T2,
                        e2: T2,
                        e3: T) -> T2
参数化 T 为 AbstractFloat、f32 或 f16
T2 为 vecN<T>
描述 返回 e1e2 的分量线性混合, 每个分量都用标量混合因子 e3
等价于 mix(e1, e2, T2(e3))
定义域 由表达式 e1[i] × (1 − e3) + e2[i] × e3 的线性项隐含。

17.5.44. modf

重载
@const @must_use fn modf(e: T) -> __modf_result_f32
参数化 T 为 f32
描述 e 拆分为小数部分和整数部分。

整数部分为 trunc(e),小数部分为 e - trunc(e)。

返回 __modf_result_f32 内置结构体,定义如下:

struct __modf_result_f32 {
  fract : f32, // 小数部分
  whole : f32  // 整数部分
}
示例:modf 用法
// 推断结果类型
let fract_and_whole = modf(1.5);
// fract_only 被设置为 0.5
let fract_only = modf(1.5).fract;
// whole_only 被设置为 1.0
let whole_only = modf(1.5).whole;

注意: 不能显式声明类型为 __modf_result_f32 的值,但可以推断类型。

重载
@const @must_use fn modf(e: T) -> __modf_result_f16
参数化 T 为 f16
描述 e 拆分为小数部分和整数部分。

整数部分为 trunc(e),小数部分为 e - trunc(e)。

返回 __modf_result_f16 内置结构体,定义如下:

struct __modf_result_f16 {
  fract : f16, // 小数部分
  whole : f16  // 整数部分
}

注意: 不能显式声明类型为 __modf_result_f16 的值,但可以推断类型。

重载
@const @must_use fn modf(e: T) -> __modf_result_abstract
参数化 T 为 AbstractFloat
描述 e 拆分为小数部分和整数部分。

整数部分为 trunc(e),小数部分为 e - trunc(e)。

返回 __modf_result_abstract 内置结构体,定义如下:

struct __modf_result_abstract {
  fract : AbstractFloat, // 小数部分
  whole : AbstractFloat  // 整数部分
}
示例:modf abstract 用法
// 推断结果类型
const fract_and_whole = modf(1.5);
// fract_only 被设置为 0.5
const fract_only = modf(1.5).fract;
// whole_only 被设置为 1.0
const whole_only = modf(1.5).whole;

注意: 不能显式声明类型为 __modf_result_abstract 的值,但可以推断类型。

重载
@const @must_use fn modf(e: T) -> __modf_result_vecN_f32
参数化 T 为 vecN<f32>
描述 e 的各分量拆分为小数部分和整数部分。

i 个分量的整数和小数部分等于 modf(e[i]) 的整数和小数。

返回 __modf_result_vecN_f32 内置结构体,定义如下:

struct __modf_result_vecN_f32 {
  fract : vecN<f32>, // 小数部分
  whole : vecN<f32>  // 整数部分
}

注意: 不能显式声明类型为 __modf_result_vecN_f32 的值,但可以推断类型。

重载
@const @must_use fn modf(e: T) -> __modf_result_vecN_f16
参数化 T 为 vecN<f16>
描述 e 的各分量拆分为小数部分和整数部分。

i 个分量的整数和小数部分等于 modf(e[i]) 的整数和小数。

返回 __modf_result_vecN_f16 内置结构体,定义如下:

struct __modf_result_vecN_f16 {
  fract : vecN<f16>, // 小数部分
  whole : vecN<f16>  // 整数部分
}

注意: 不能显式声明类型为 __modf_result_vecN_f16 的值,但可以推断类型。

重载
@const @must_use fn modf(e: T) -> __modf_result_vecN_abstract
参数化 T 为 vecN<AbstractFloat>
描述 e 的各分量拆分为小数部分和整数部分。

i 个分量的整数和小数部分等于 modf(e[i]) 的整数和小数。

返回 __modf_result_vecN_abstract 内置结构体,定义如下:

struct __modf_result_vecN_abstract {
  fract : vecN<AbstractFloat>, // 小数部分
  whole : vecN<AbstractFloat>  // 整数部分
}

注意: 不能显式声明类型为 __modf_result_vecN_abstract 的值,但可以推断类型。

17.5.45. normalize

重载
@const @must_use fn normalize(e: vecN<T> ) -> vecN<T>
参数化 T 为 AbstractFloat、f32 或 f16
描述 返回和 e 方向相同的单位向量。

定义域为所有非零向量。

17.5.46. pow

重载
@const @must_use fn pow(e1: T,
                        e2: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e1e2 次幂。 当 T 是向量时,分量方式 计算。
标量 定义域 所有扩展实数对 (x,y),但不包括:
  • x < 0。

  • x 为 1 且 y 为无穷大。

  • x 为无穷大且 y 为 0。

此规则基于结果可能被计算为 exp2(y * log2(x))

17.5.47. quantizeToF16

重载
@const @must_use fn quantizeToF16(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 将 32 位浮点数 e 量化,等价于将 eIEEE-754 binary16 转换为 IEEE-754 binary32 再转回。

e 超出 binary16 的有限范围

中间 binary16 值可能会冲洗为零,即若中间值为非规格化数,则最终结果可能为 0。

参见 § 15.7.6 浮点数转换

T 是向量时,分量方式 计算。

注意: vec2<f32> 情况等价于 unpack2x16float(pack2x16float(e))

17.5.48. radians

重载
@const @must_use fn radians(e1: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 将角度转为弧度,近似为 e1 × π ÷ 180。 当 T 是向量时,分量方式 计算。

17.5.49. reflect

重载
@const @must_use fn reflect(e1: T,
                            e2: T) -> T
参数化 T 为 vecN<AbstractFloat>、vecN<f32> 或 vecN<f16>
描述 对于入射向量 e1 和表面法线 e2,返回反射方向 e1 - 2 * dot(e2, e1) * e2

17.5.50. refract

重载
@const @must_use fn refract(e1: T,
                            e2: T,
                            e3: I) -> T
参数化 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

重载
@const @must_use fn reverseBits(e: T) -> T
参数化 T 为 i32、u32、vecN<i32> 或 vecN<u32>
描述 翻转 e 中的比特:结果的第 k 位等于 e 的第 31-k 位。
T 为向量时,分量方式 计算。

17.5.52. round

重载
@const @must_use fn round(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 结果为最接近 e 的整数 k(以浮点数表示)。
e 正好在整数 kk + 1 的中间时, 如果 k 为偶数,结果为 k,如果 k 为奇数,结果为 k + 1
T 为向量时,分量方式 计算。

17.5.53. saturate

重载
@const @must_use fn saturate(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 clamp(e, 0.0, 1.0)。 当 T 为向量时,分量方式 计算。

17.5.54. sign

重载
@const @must_use fn sign(e: T) -> T
参数化 S 为 AbstractInt、AbstractFloat、i32、f32 或 f16
T 为 S,或 vecN<S>
描述 结果为:
  • e > 0 时为 1
  • e = 0 时为 0
  • e < 0 时为 -1

分量方式 计算,当 T 为向量时。

17.5.55. sin

重载
@const @must_use fn sin(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的正弦值,e 单位为弧度。 当 T 为向量时,分量方式 计算。
标量 定义域 区间 (−∞, +∞)

17.5.56. sinh

重载
@const @must_use fn sinh(a: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 a 的双曲正弦值,其中 a双曲角。 近似于纯数学函数 (eae−a)÷2, 但不一定以此方式计算。

分量方式 计算,当 T 为向量时。

17.5.57. smoothstep

重载
@const @must_use fn smoothstep(edge0: T,
                               edge1: T,
                               x: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 0 到 1 之间的平滑 Hermite 插值。 当 T 为向量时,分量方式 计算。

对于标量 T,结果为 t * t * (3.0 - 2.0 * t)
其中 t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0)

定性来说:

  • edge0 < edge1 时,x 小于 edge0 时函数为 0,然后平滑上升, 当 x 达到 edge1 后保持 1。

  • edge0 > edge1 时,x 小于 edge1 时函数为 1,然后平滑下降, 当 x 达到 edge0 后保持 0。

如果 edge0 = edge1

17.5.58. sqrt

重载
@const @must_use fn sqrt(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的平方根。 当 T 为向量时,分量方式 计算。
标量 定义域 区间 [0, +∞]

17.5.59. step

重载
@const @must_use fn step(edge: T,
                         x: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 edgex,返回 1.0,否则返回 0.0。 当 T 为向量时,分量方式 计算。

17.5.60. tan

重载
@const @must_use fn tan(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 e 的正切值,e 单位为弧度。 当 T 为向量时,分量方式 计算。
标量 定义域 区间 (−∞, +∞)

17.5.61. tanh

重载
@const @must_use fn tanh(a: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 a 的双曲正切值,其中 a双曲角。 近似为纯数学函数 (eae−a) ÷ (ea + e−a) 但不一定以该方式计算。

分量方式 计算,当 T 为向量时。

17.5.62. transpose

重载
@const @must_use fn transpose(e: matRxC<T>) -> matCxR<T>
参数化 T 为 AbstractFloat、f32 或 f16
描述 返回 e 的转置。

17.5.63. trunc

重载
@const @must_use fn trunc(e: T) -> T
参数化 S 为 AbstractFloat、f32 或 f16
T 为 S 或 vecN<S>
描述 返回 truncate(e),即绝对值不大于 e 的最近整数。 当 T 为向量时,分量方式 计算。

17.6. 导数内置函数

参见 § 15.6.2 导数

对此类函数的调用:

17.6.1. dpdx

重载
@must_use fn dpdx(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 e 关于窗口 x 坐标的偏导数。 结果与 dpdxFine(e)dpdxCoarse(e) 相同。

如果在非一致控制流中调用,返回未定值

17.6.2. dpdxCoarse

重载
@must_use fn dpdxCoarse(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 使用局部差分返回 e 关于窗口 x 坐标的偏导数。 这可能导致比 dpdxFine(e) 更少的不重复位置。

如果在非一致控制流中调用,返回未定值

17.6.3. dpdxFine

重载
@must_use fn dpdxFine(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 返回 e 关于窗口 x 坐标的偏导数。

如果在非一致控制流中调用,返回未定值

17.6.4. dpdy

重载
@must_use fn dpdy(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 e 关于窗口 y 坐标的偏导数。 结果与 dpdyFine(e)dpdyCoarse(e) 相同。

如果在非一致控制流中调用,返回未定值

17.6.5. dpdyCoarse

重载
@must_use fn dpdyCoarse(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 使用局部差分返回 e 关于窗口 y 坐标的偏导数。 这可能导致比 dpdyFine(e) 更少的不重复位置。

如果在非一致控制流中调用,返回未定值

17.6.6. dpdyFine

重载
@must_use fn dpdyFine(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 返回 e 关于窗口 y 坐标的偏导数。

如果在非一致控制流中调用,返回未定值

17.6.7. fwidth

重载
@must_use fn fwidth(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 返回 abs(dpdx(e)) + abs(dpdy(e))

如果在非一致控制流中调用,返回未定值

17.6.8. fwidthCoarse

重载
@must_use fn fwidthCoarse(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 返回 abs(dpdxCoarse(e)) + abs(dpdyCoarse(e))

如果在非一致控制流中调用,返回未定值

17.6.9. fwidthFine

重载
@must_use fn fwidthFine(e: T) -> T
参数化 T 为 f32 或 vecN<f32>
描述 返回 abs(dpdxFine(e)) + abs(dpdyFine(e))

如果在非一致控制流中调用,返回未定值

17.7. 纹理内置函数

参数值必须对相应的纹理类型有效。

17.7.1. textureDimensions

返回纹理或纹理某个 mip 级别的尺寸(以 texel 为单位)。

参数化 重载
STi32u32f32
Ftexel 格式
A访问模式

Ttexture_1d<ST>texture_storage_1d<F,A>
@must_use fn textureDimensions(t: T) -> u32
STi32u32f32

Ttexture_1d<ST>

Li32u32

@must_use fn textureDimensions(t: T,
                               level: L) -> u32
STi32u32f32
Ftexel 格式
A访问模式

Ttexture_2d<ST>texture_2d_array<ST>texture_cube<ST>texture_cube_array<ST>texture_multisampled_2d<ST>texture_depth_2dtexture_depth_2d_arraytexture_depth_cubetexture_depth_cube_arraytexture_depth_multisampled_2dtexture_storage_2d<F,A>texture_storage_2d_array<F,A>、 或 texture_external
@must_use fn textureDimensions(t: T) -> vec2<u32>
STi32u32f32

Ttexture_2d<ST>texture_2d_array<ST>texture_cube<ST>texture_cube_array<ST>texture_depth_2dtexture_depth_2d_arraytexture_depth_cubetexture_depth_cube_array

Li32u32

@must_use fn textureDimensions(t: T,
                               level: L) -> vec2<u32>
STi32u32f32
Ftexel 格式
A访问模式

Ttexture_3d<ST>texture_storage_3d<F,A>
@must_use fn textureDimensions(t: T) -> vec3<u32>
STi32u32f32

Ttexture_3d<ST>

Li32u32

@must_use fn textureDimensions(t: T,
                               level: L) -> vec3<u32>

参数:

t 采样多采样深度存储外部 纹理。
level mip 级别,级别 0 表示完整尺寸的纹理。
如果省略,则返回级别 0 的尺寸。

返回:

纹理的坐标尺寸。

也就是说,结果提供了逻辑 texel 地址坐标的整型边界, 不包括mip 级别数数组大小采样数

对于基于立方体的纹理,结果为立方体每个面的尺寸。 立方体的每个面都是正方形,因此结果的 x 和 y 分量相等。

如果 level 超出范围 [0, textureNumLevels(t)),则返回类型的未定值

17.7.2. textureGather

纹理 gather 操作为 2D、2D 数组、立方体或立方体数组纹理读取, 计算一个四分量向量,具体如下:

这四个 texel 形成的采样区域与 WebGPU 采样器描述符中描述一致。

参数化 重载
Ci32u32
STi32u32f32
@must_use fn textureGather(component: C,
                           t: texture_2d<ST>,
                           s: sampler,
                           coords: vec2<f32>) -> vec4<ST>
Ci32u32
STi32u32f32
@must_use fn textureGather(component: C,
                           t: texture_2d<ST>,
                           s: sampler,
                           coords: vec2<f32>,
                           offset: vec2<i32>) -> vec4<ST>
Ci32u32
Ai32u32
STi32u32f32
@must_use fn textureGather(component: C,
                           t: texture_2d_array<ST>,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A) -> vec4<ST>
Ci32u32
Ai32u32
STi32u32f32
@must_use fn textureGather(component: C,
                           t: texture_2d_array<ST>,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A,
                           offset: vec2<i32>) -> vec4<ST>
Ci32u32
STi32u32f32
@must_use fn textureGather(component: C,
                           t: texture_cube<ST>,
                           s: sampler,
                           coords: vec3<f32>) -> vec4<ST>
Ci32u32
Ai32u32
STi32u32f32
@must_use fn textureGather(component: C,
                           t: texture_cube_array<ST>,
                           s: sampler,
                           coords: vec3<f32>,
                           array_index: A) -> vec4<ST>
@must_use fn textureGather(t: texture_depth_2d,
                           s: sampler,
                           coords: vec2<f32>) -> vec4<f32>
@must_use fn textureGather(t: texture_depth_2d,
                           s: sampler,
                           coords: vec2<f32>,
                           offset: vec2<i32>) -> vec4<f32>
@must_use fn textureGather(t: texture_depth_cube,
                           s: sampler,
                           coords: vec3<f32>) -> vec4<f32>
Ai32u32
@must_use fn textureGather(t: texture_depth_2d_array,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A) -> vec4<f32>
Ai32u32
@must_use fn textureGather(t: texture_depth_2d_array,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A,
                           offset: vec2<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureGather(t: texture_depth_cube_array,
                           s: sampler,
                           coords: vec3<f32>,
                           array_index: A) -> vec4<f32>

参数:

component 仅适用于非深度纹理。
指定要读取的通道索引。
如提供,component 表达式必须常量表达式(如 1)。
取值必须在 0 到 3 之间。 超出此范围会导致着色器创建错误
t 采样深度纹理。
s 采样器类型
coords 纹理坐标。
array_index 数组纹理的 0 基索引。
此值被限制在 [0, textureNumLayers(t) - 1] 范围内。
offset 可选的 texel 偏移,在采样前加到未归一化纹理坐标上。此偏移在应用任何 纹理环绕模式之前加上。
offset 表达式必须常量表达式(如 vec2<i32>(1, 2))。
每个 offset 分量必须-87 之间。超出 此范围会导致着色器创建错误

返回:

一个四分量向量,分量从选定 texel 的指定通道提取,如上所述。

示例:从 2D 纹理 texel gather 分量
@group(0) @binding(0) var t: texture_2d<f32>;
@group(0) @binding(1) var dt: texture_depth_2d;
@group(0) @binding(2) var s: sampler;

fn gather_x_components(c: vec2<f32>) -> vec4<f32> {
  return textureGather(0,t,s,c);
}
fn gather_y_components(c: vec2<f32>) -> vec4<f32> {
  return textureGather(1,t,s,c);
}
fn gather_z_components(c: vec2<f32>) -> vec4<f32> {
  return textureGather(2,t,s,c);
}
fn gather_depth_components(c: vec2<f32>) -> vec4<f32> {
  return textureGather(dt,s,c);
}

17.7.3. textureGatherCompare

纹理 gather compare 操作会对深度纹理中的四个 texel 执行深度比较,并将结果收集为一个向量,具体如下:

参数化 重载
@must_use fn textureGatherCompare(t: texture_depth_2d,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  depth_ref: f32) -> vec4<f32>
@must_use fn textureGatherCompare(t: texture_depth_2d,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  depth_ref: f32,
                                  offset: vec2<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureGatherCompare(t: texture_depth_2d_array,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  array_index: A,
                                  depth_ref: f32) -> vec4<f32>
Ai32u32
@must_use fn textureGatherCompare(t: texture_depth_2d_array,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  array_index: A,
                                  depth_ref: f32,
                                  offset: vec2<i32>) -> vec4<f32>
@must_use fn textureGatherCompare(t: texture_depth_cube,
                                  s: sampler_comparison,
                                  coords: vec3<f32>,
                                  depth_ref: f32) -> vec4<f32>
Ai32u32
@must_use fn textureGatherCompare(t: texture_depth_cube_array,
                                  s: sampler_comparison,
                                  coords: vec3<f32>,
                                  array_index: A,
                                  depth_ref: f32) -> vec4<f32>

参数:

t 深度纹理。
s 比较采样器
coords 纹理坐标。
array_index 0 基数组纹理索引。
此值被限制在 [0, textureNumLayers(t) - 1] 范围。
depth_ref 用于和采样深度值比较的参考值。
offset 可选的 texel 偏移,在采样纹理前加到未归一化纹理坐标上。此偏移在应用任何纹理环绕模式前加上。
offset 表达式必须常量表达式(如 vec2<i32>(1, 2))。
每个 offset 分量必须-87 之间。超出此范围会导致着色器创建错误

返回:

四分量向量,分量为选定 texel 的比较结果,如上所述。

示例:gather 深度比较
@group(0) @binding(0) var dt: texture_depth_2d;
@group(0) @binding(1) var s: sampler;

fn gather_depth_compare(c: vec2<f32>, depth_ref: f32) -> vec4<f32> {
  return textureGatherCompare(dt,s,c,depth_ref);
}

17.7.4. textureLoad

从纹理中读取单个 texel,不进行采样和过滤。

参数化 重载
Ci32u32
Li32u32
STi32u32f32
@must_use fn textureLoad(t: texture_1d<ST>,
                         coords: C,
                         level: L) -> vec4<ST>
Ci32u32
Li32u32
STi32u32f32
@must_use fn textureLoad(t: texture_2d<ST>,
                         coords: vec2<C>,
                         level: L) -> vec4<ST>
Ci32u32
Ai32u32
Li32u32
STi32u32f32
@must_use fn textureLoad(t: texture_2d_array<ST>,
                        coords: vec2<C>,
                        array_index: A,
                        level: L) -> vec4<ST>
Ci32u32
Li32u32
STi32u32f32
@must_use fn textureLoad(t: texture_3d<ST>,
                         coords: vec3<C>,
                         level: L) -> vec4<ST>
Ci32u32
Si32u32
STi32u32f32
@must_use fn textureLoad(t: texture_multisampled_2d<ST>,
                         coords: vec2<C>,
                         sample_index: S)-> vec4<ST>
Ci32u32
Li32u32
@must_use fn textureLoad(t: texture_depth_2d,
                         coords: vec2<C>,
                         level: L) -> f32
Ci32u32
Ai32u32
Li32u32
@must_use fn textureLoad(t: texture_depth_2d_array,
                         coords: vec2<C>,
                         array_index: A,
                         level: L) -> f32
Ci32u32
Si32u32
@must_use fn textureLoad(t: texture_depth_multisampled_2d,
                         coords: vec2<C>,
                         sample_index: S)-> f32
Ci32u32
@must_use fn textureLoad(t: texture_external,
                         coords: vec2<C>) -> vec4<f32>
Ci32u32
AMreadread_write
CF 取决于存储 texel 格式 F见 texel 格式表获取 texel 格式到通道格式的映射。
@must_use fn textureLoad(t : texture_storage_1d<F, AM>,
                         coords : C) -> vec4<CF>
Ci32u32
AMreadread_write
CF 取决于存储 texel 格式 F见 texel 格式表获取 texel 格式到通道格式的映射。
@must_use fn textureLoad(t : texture_storage_2d<F, AM>,
                         coords : vec2<C>) -> vec4<CF>
Ci32u32
AMreadread_write
Ai32u32
CF 取决于存储 texel 格式 F见 texel 格式表获取 texel 格式到通道格式的映射。
@must_use fn textureLoad(t : texture_storage_2d_array<F, AM>,
                         coords : vec2<C>,
                         array_index : A) -> vec4<CF>
Ci32u32
AMreadread_write
CF 取决于存储 texel 格式 F参见 texel 格式表,了解 texel 格式到通道格式的映射。
@must_use fn textureLoad(t : texture_storage_3d<F, AM>,
                         coords : vec3<C>) -> vec4<CF>

参数:

t 采样型、 多采样型、 深度型、 存储型,或 外部 纹理
coords 以 0 为起点的 texel 坐标。
array_index 以 0 为起点的纹理数组索引。
level mip 级别,级别 0 表示完整尺寸的纹理。
sample_index 多采样纹理的 0 基采样索引。

返回:

未经过滤的 texel 数据。

当以下情况之一发生时,逻辑 texel 地址无效:

如果逻辑 texel 地址无效,该内置函数的返回值可能为以下之一:

17.7.5. textureNumLayers

返回数组纹理的层(元素)数量。

参数化 重载
Ftexel 格式
A访问模式
STi32u32f32

Ttexture_2d_array<ST>texture_cube_array<ST>texture_depth_2d_arraytexture_depth_cube_array, 或 texture_storage_2d_array<F,A>
@must_use fn textureNumLayers(t: T) -> u32

参数:

t 采样型、 深度型, 或 存储 数组纹理。

返回:

如果纹理为立方体数组,则返回立方体的数量。

否则返回数组纹理的层(同质 texel 网格)数量。

17.7.6. textureNumLevels

返回纹理的 mip 级别数量。

参数化 重载
STi32u32f32

Ttexture_1d<ST>texture_2d<ST>texture_2d_array<ST>texture_3d<ST>texture_cube<ST>texture_cube_array<ST>texture_depth_2dtexture_depth_2d_arraytexture_depth_cubetexture_depth_cube_array
@must_use fn textureNumLevels(t: T) -> u32

参数:

t 采样型或 深度型纹理。

返回:

该纹理的mip 级别数

17.7.7. textureNumSamples

返回多采样纹理每个 texel 的采样数。

参数化 重载
STi32u32f32

Ttexture_multisampled_2d<ST>texture_depth_multisampled_2d
@must_use fn textureNumSamples(t: T) -> u32

参数:

t 多采样纹理

返回:

多采样纹理的采样数。

17.7.8. textureSample

对纹理进行采样。

必须只用于片元着色器阶段。

如果一致性分析无法证明对该函数的调用处于一致控制流中, 则derivative_uniformity 诊断会被触发

参数化 重载
@must_use fn textureSample(t: texture_1d<f32>,
                           s: sampler,
                           coords: f32) -> vec4<f32>
@must_use fn textureSample(t: texture_2d<f32>,
                           s: sampler,
                           coords: vec2<f32>) -> vec4<f32>
@must_use fn textureSample(t: texture_2d<f32>,
                           s: sampler,
                           coords: vec2<f32>,
                           offset: vec2<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureSample(t: texture_2d_array<f32>,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A) -> vec4<f32>
Ai32u32
@must_use fn textureSample(t: texture_2d_array<f32>,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A,
                           offset: vec2<i32>) -> vec4<f32>
Ttexture_3d<f32>texture_cube<f32>
@must_use fn textureSample(t: T,
                           s: sampler,
                           coords: vec3<f32>) -> vec4<f32>
@must_use fn textureSample(t: texture_3d<f32>,
                           s: sampler,
                           coords: vec3<f32>,
                           offset: vec3<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureSample(t: texture_cube_array<f32>,
                           s: sampler,
                           coords: vec3<f32>,
                           array_index: A) -> vec4<f32>
@must_use fn textureSample(t: texture_depth_2d,
                           s: sampler,
                           coords: vec2<f32>) -> f32
@must_use fn textureSample(t: texture_depth_2d,
                           s: sampler,
                           coords: vec2<f32>,
                           offset: vec2<i32>) -> f32
Ai32u32
@must_use fn textureSample(t: texture_depth_2d_array,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A) -> f32
Ai32u32
@must_use fn textureSample(t: texture_depth_2d_array,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A,
                           offset: vec2<i32>) -> f32
@must_use fn textureSample(t: texture_depth_cube,
                           s: sampler,
                           coords: vec3<f32>) -> f32
Ai32u32
@must_use fn textureSample(t: texture_depth_cube_array,
                           s: sampler,
                           coords: vec3<f32>,
                           array_index: A) -> f32

参数:

t 进行采样的采样型或 深度 纹理。
s 采样器类型。
coords 用于采样的纹理坐标。
array_index 要采样的 0 基数组纹理索引。
此值被限制在 [0, textureNumLayers(t) - 1] 范围内。
offset 可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。
offset 表达式必须常量表达式(如 vec2<i32>(1, 2))。
每个 offset 分量必须-87 之间。超出 此范围会导致着色器创建错误

返回:

采样值。

如果在非一致控制流中调用,则结果为未定值

17.7.9. textureSampleBias

对纹理采样并对 mip 级别应用 bias 偏移。

必须仅用于片元着色器阶段。

如果一致性分析无法证明该函数调用处于一致控制流中, 会触发derivative_uniformity 诊断

参数化 重载
@must_use fn textureSampleBias(t: texture_2d<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               bias: f32) -> vec4<f32>
@must_use fn textureSampleBias(t: texture_2d<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               bias: f32,
                               offset: vec2<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureSampleBias(t: texture_2d_array<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               array_index: A,
                               bias: f32) -> vec4<f32>
Ai32u32
@must_use fn textureSampleBias(t: texture_2d_array<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               array_index: A,
                               bias: f32,
                               offset: vec2<i32>) -> vec4<f32>
Ttexture_3d<f32>texture_cube<f32>
@must_use fn textureSampleBias(t: T,
                               s: sampler,
                               coords: vec3<f32>,
                               bias: f32) -> vec4<f32>
@must_use fn textureSampleBias(t: texture_3d<f32>,
                               s: sampler,
                               coords: vec3<f32>,
                               bias: f32,
                               offset: vec3<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureSampleBias(t: texture_cube_array<f32>,
                               s: sampler,
                               coords: vec3<f32>,
                               array_index: A,
                               bias: f32) -> vec4<f32>

参数:

t 要采样的采样纹理
s 采样器类型。
coords 用于采样的纹理坐标。
array_index 要采样的 0 基数组纹理索引。
此值被限制在 [0, textureNumLayers(t) - 1] 范围内。
bias 在采样前对 mip 级别应用的 bias 偏移。
此值被限制在 [-16.0, 15.99] 范围内。
offset 可选的 texel 偏移,在采样前加到未归一化纹理坐标上。该偏移在应用任何纹理环绕模式前加上。
offset 表达式必须常量表达式(如 vec2<i32>(1, 2))。
每个 offset 分量必须-87 之间。超出 此范围会导致着色器创建错误

返回:

采样值。

17.7.10. textureSampleCompare

深度纹理进行采样,并将采样深度值与参考值进行比较。

必须仅用于片元着色器阶段。

如果一致性分析无法证明该函数调用处于一致控制流中, 会触发derivative_uniformity 诊断

参数化 重载
@must_use fn textureSampleCompare(t: texture_depth_2d,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  depth_ref: f32) -> f32
@must_use fn textureSampleCompare(t: texture_depth_2d,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  depth_ref: f32,
                                  offset: vec2<i32>) -> f32
Ai32u32
@must_use fn textureSampleCompare(t: texture_depth_2d_array,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  array_index: A,
                                  depth_ref: f32) -> f32
Ai32u32
@must_use fn textureSampleCompare(t: texture_depth_2d_array,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  array_index: A,
                                  depth_ref: f32,
                                  offset: vec2<i32>) -> f32
@must_use fn textureSampleCompare(t: texture_depth_cube,
                                  s: sampler_comparison,
                                  coords: vec3<f32>,
                                  depth_ref: f32) -> f32
Ai32u32
@must_use fn textureSampleCompare(t: texture_depth_cube_array,
                                  s: sampler_comparison,
                                  coords: vec3<f32>,
                                  array_index: A,
                                  depth_ref: f32) -> f32

参数:

t 要采样的深度纹理
s sampler_comparison 类型。
coords 用于采样的纹理坐标。
array_index 要采样的 0 基数组纹理索引。
此值被限制在 [0, textureNumLayers(t) - 1] 范围内。
depth_ref 用于与采样深度值比较的参考值。
offset 可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。
offset 表达式必须常量表达式(如 vec2<i32>(1, 2))。
每个 offset 分量必须-87 之间。超出 此范围会导致着色器创建错误

返回:

区间 [0.0..1.0] 内的值。

每个采样 texel 会与参考值用 sampler_comparison 定义的比较操作符比较,结果为 01

若采样器使用双线性过滤,则返回值为这些结果的滤波平均,否则为单个 texel 的比较结果。

如果在非一致控制流中调用,结果为未定值

17.7.11. textureSampleCompareLevel

深度纹理进行采样,并将采样深度值与参考值进行比较。

参数化 重载
@must_use fn textureSampleCompareLevel(t: texture_depth_2d,
                                       s: sampler_comparison,
                                       coords: vec2<f32>,
                                       depth_ref: f32) -> f32
@must_use fn textureSampleCompareLevel(t: texture_depth_2d,
                                       s: sampler_comparison,
                                       coords: vec2<f32>,
                                       depth_ref: f32,
                                       offset: vec2<i32>) -> f32
Ai32u32
@must_use fn textureSampleCompareLevel(t: texture_depth_2d_array,
                                       s: sampler_comparison,
                                       coords: vec2<f32>,
                                       array_index: A,
                                       depth_ref: f32) -> f32
Ai32u32
@must_use fn textureSampleCompareLevel(t: texture_depth_2d_array,
                                       s: sampler_comparison,
                                       coords: vec2<f32>,
                                       array_index: A,
                                       depth_ref: f32,
                                       offset: vec2<i32>) -> f32
@must_use fn textureSampleCompareLevel(t: texture_depth_cube,
                                       s: sampler_comparison,
                                       coords: vec3<f32>,
                                       depth_ref: f32) -> f32
Ai32u32
@must_use fn textureSampleCompareLevel(t: texture_depth_cube_array,
                                       s: sampler_comparison,
                                       coords: vec3<f32>,
                                       array_index: A,
                                       depth_ref: f32) -> f32

参数:

t 要采样的深度纹理
s sampler_comparison 类型。
coords 用于采样的纹理坐标。
array_index 要采样的 0 基数组纹理索引。
此值被限制在 [0, textureNumLayers(t) - 1] 范围内。
depth_ref 用于与采样深度值比较的参考值。
offset 可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。
offset 表达式必须常量表达式(如 vec2<i32>(1, 2))。
每个 offset 分量必须-87 之间。超出 此范围会导致着色器创建错误

返回:

区间 [0.0..1.0] 内的值。

textureSampleCompareLevel 函数与 textureSampleCompare 类似,不同之处在于:

17.7.12. textureSampleGrad

使用显式梯度对纹理采样。

参数化 重载
@must_use fn textureSampleGrad(t: texture_2d<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               ddx: vec2<f32>,
                               ddy: vec2<f32>) -> vec4<f32>
@must_use fn textureSampleGrad(t: texture_2d<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               ddx: vec2<f32>,
                               ddy: vec2<f32>,
                               offset: vec2<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureSampleGrad(t: texture_2d_array<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               array_index: A,
                               ddx: vec2<f32>,
                               ddy: vec2<f32>) -> vec4<f32>
Ai32u32
@must_use fn textureSampleGrad(t: texture_2d_array<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               array_index: A,
                               ddx: vec2<f32>,
                               ddy: vec2<f32>,
                               offset: vec2<i32>) -> vec4<f32>
Ttexture_3d<f32>texture_cube<f32>
@must_use fn textureSampleGrad(t: T,
                               s: sampler,
                               coords: vec3<f32>,
                               ddx: vec3<f32>,
                               ddy: vec3<f32>) -> vec4<f32>
@must_use fn textureSampleGrad(t: texture_3d<f32>,
                               s: sampler,
                               coords: vec3<f32>,
                               ddx: vec3<f32>,
                               ddy: vec3<f32>,
                               offset: vec3<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureSampleGrad(t: texture_cube_array<f32>,
                               s: sampler,
                               coords: vec3<f32>,
                               array_index: A,
                               ddx: vec3<f32>,
                               ddy: vec3<f32>) -> vec4<f32>

参数:

t 要采样的采样纹理
s 采样器
coords 用于采样的纹理坐标。
array_index 要采样的 0 基数组纹理索引。
此值被限制在 [0, textureNumLayers(t) - 1] 范围内。
ddx 用于计算采样位置的 x 方向导数向量。
ddy 用于计算采样位置的 y 方向导数向量。
offset 可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。
offset 表达式必须常量表达式(如 vec2<i32>(1, 2))。
每个 offset 分量必须-87 之间。超出 此范围会导致着色器创建错误

返回:

采样值。

17.7.13. textureSampleLevel

使用显式 mip 级别对纹理采样。

参数化 重载
@must_use fn textureSampleLevel(t: texture_1d<f32>,
                                s: sampler,
                                coords: f32,
                                level: f32) -> vec4<f32>
@must_use fn textureSampleLevel(t: texture_2d<f32>,
                                s: sampler,
                                coords: vec2<f32>,
                                level: f32) -> vec4<f32>
@must_use fn textureSampleLevel(t: texture_2d<f32>,
                                s: sampler,
                                coords: vec2<f32>,
                                level: f32,
                                offset: vec2<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureSampleLevel(t: texture_2d_array<f32>,
                                s: sampler,
                                coords: vec2<f32>,
                                array_index: A,
                                level: f32) -> vec4<f32>
Ai32u32
@must_use fn textureSampleLevel(t: texture_2d_array<f32>,
                                s: sampler,
                                coords: vec2<f32>,
                                array_index: A,
                                level: f32,
                                offset: vec2<i32>) -> vec4<f32>
Ttexture_3d<f32>texture_cube<f32>
@must_use fn textureSampleLevel(t: T,
                                s: sampler,
                                coords: vec3<f32>,
                                level: f32) -> vec4<f32>
@must_use fn textureSampleLevel(t: texture_3d<f32>,
                                s: sampler,
                                coords: vec3<f32>,
                                level: f32,
                                offset: vec3<i32>) -> vec4<f32>
Ai32u32
@must_use fn textureSampleLevel(t: texture_cube_array<f32>,
                                s: sampler,
                                coords: vec3<f32>,
                                array_index: A,
                                level: f32) -> vec4<f32>
Li32u32
@must_use fn textureSampleLevel(t: texture_depth_2d,
                                s: sampler,
                                coords: vec2<f32>,
                                level: L) -> f32
Li32u32
@must_use fn textureSampleLevel(t: texture_depth_2d,
                                s: sampler,
                                coords: vec2<f32>,
                                level: L,
                                offset: vec2<i32>) -> f32
Ai32u32
Li32u32
@must_use fn textureSampleLevel(t: texture_depth_2d_array,
                                s: sampler,
                                coords: vec2<f32>,
                                array_index: A,
                                level: L) -> f32
Ai32u32
Li32u32
@must_use fn textureSampleLevel(t: texture_depth_2d_array,
                                s: sampler,
                                coords: vec2<f32>,
                                array_index: A,
                                level: L,
                                offset: vec2<i32>) -> f32
Li32u32
@must_use fn textureSampleLevel(t: texture_depth_cube,
                                s: sampler,
                                coords: vec3<f32>,
                                level: L) -> f32
Ai32u32
Li32u32
@must_use fn textureSampleLevel(t: texture_depth_cube_array,
                                s: sampler,
                                coords: vec3<f32>,
                                array_index: A,
                                level: L) -> f32

参数:

t 要采样的采样型或 深度型纹理。
s 采样器类型。
coords 用于采样的纹理坐标。
array_index 要采样的 0 基数组纹理索引。
此值被限制在 [0, textureNumLayers(t) - 1] 范围内。
level mip 级别,0 表示完整尺寸的纹理。 当 levelf32 类型时,若格式可过滤,可在两个级别间插值,详见 Texture Format Capabilities
offset 可选的 texel 偏移,在采样前加到未归一化的纹理坐标上。该偏移在应用任何纹理环绕模式前加上。
offset 表达式必须常量表达式(如 vec2<i32>(1, 2))。
每个 offset 分量必须-87 之间。超出 此范围会导致着色器创建错误

返回:

采样值。

17.7.14. textureSampleBaseClampToEdge

在基准级别(base level)采样纹理视图,纹理坐标如下所述被夹紧到边缘。

参数化 重载
Ttexture_2d<f32>texture_external
@must_use fn textureSampleBaseClampToEdge(t: T,
                                          s: sampler,
                                          coords: vec2<f32>) -> vec4<f32>

参数:

t 要采样的采样型或外部纹理。
s 采样器类型。
coords 用于采样的纹理坐标。

在采样前,传入的坐标被夹紧到矩形

[ half_texel, 1 - half_texel ]

其中

half_texel = vec2(0.5) / vec2<f32>(textureDimensions(t))

注意: half-texel 调整可确保无论采样器的 寻址过滤 模式如何,均不会发生环绕。 也就是说,在边缘附近采样时,采样的 texel 只会在该边或邻近该边,不会选自相对的另一边。

返回:

采样值。

17.7.15. textureStore

向纹理写入一个 texel。

参数化 重载
Ftexel 格式
Ci32u32
AMwriteread_write
CF 取决于存储 texel 格式 F参见 texel 格式表,了解 texel 格式到通道格式的映射。
fn textureStore(t: texture_storage_1d<F,AM>,
                coords: C,
                value: vec4<CF>)
Ftexel 格式
Ci32u32
AMwriteread_write
CF 取决于存储 texel 格式 F参见 texel 格式表,了解 texel 格式到通道格式的映射。
fn textureStore(t: texture_storage_2d<F,AM>,
                coords: vec2<C>,
                value: vec4<CF>)
Ftexel 格式
Ci32u32
AMwriteread_write
Ai32u32
CF 取决于存储 texel 格式 F参见 texel 格式表,了解 texel 格式到通道格式的映射。
fn textureStore(t: texture_storage_2d_array<F,AM>,
                coords: vec2<C>,
                array_index: A,
                value: vec4<CF>)
Ftexel 格式
Ci32u32
AMwriteread_write
CF 取决于存储 texel 格式 F参见 texel 格式表,了解 texel 格式到通道格式的映射。
fn textureStore(t: texture_storage_3d<F,AM>,
                coords: vec3<C>,
                value: vec4<CF>)

参数:

t 只写存储纹理读写存储纹理
coords 以 0 为起点的 texel 坐标。
array_index 以 0 为起点的纹理数组索引。
value 新的 texel 值。 value 会用逆通道传输函数进行转换。

注意:

当下列情况之一发生时,逻辑 texel 地址无效:

如果逻辑 texel 地址无效,该内置函数不会被执行。

17.8. 原子内置函数

原子内置函数可用于读取/写入/读-修改-写原子对象。它们是§ 6.2.8 原子类型上唯一允许的操作。

所有原子内置函数都使用relaxed内存顺序。这意味着同步和顺序保证只适用于针对同一内存位置的原子操作。对于原子与非原子访问、或针对不同内存位置的原子访问,不保证同步或有序。

原子内置函数不得顶点着色器阶段中使用。

所有原子内置函数中atomic_ptr参数的地址空间AS必须storageworkgroup

T必须u32i32

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. 原子读-修改-写算术与逻辑函数

每个函数都以原子方式执行以下步骤:

  1. 加载atomic_ptr指向的原值。

  2. 用函数名描述的操作(如max)与值v计算新值。

  3. atomic_ptr存储新值。

每个函数都返回操作前原子对象中存储的原值。

17.8.3.1. atomicAdd
fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T

以原子方式对atomic_ptr指向的原子对象与值v进行加法操作,并返回操作前原子对象中存储的原值。

示例:原子加法的函数实现
// 所有操作均以原子方式执行
fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v : T) -> T {
  let old = *atomic_ptr;
  *atomic_ptr = old + v;
  return old;
}
17.8.3.2. atomicSub
fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T

以原子方式对atomic_ptr指向的原子对象与值v进行减法操作,并返回操作前原子对象中存储的原值。

示例:原子减法的函数实现
// 所有操作均以原子方式执行
fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v : T) -> T {
  let old = *atomic_ptr;
  *atomic_ptr = old - v;
  return old;
}
17.8.3.3. atomicMax
fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T

以原子方式对atomic_ptr指向的原子对象与值v进行最大值操作,并返回操作前原子对象中存储的原值。

示例:原子最大值的函数实现
// 所有操作均以原子方式执行
fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v : T) -> T {
  let old = *atomic_ptr;
  *atomic_ptr = max(old, v);
  return old;
}
17.8.3.4. atomicMin
fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T

以原子方式对atomic_ptr指向的原子对象与值v进行最小值操作,并返回操作前原子对象中存储的原值。

示例:原子最小值的函数实现
// 所有操作均以原子方式执行
fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v : T) -> T {
  let old = *atomic_ptr;
  *atomic_ptr = min(old, v);
  return old;
}
17.8.3.5. atomicAnd
fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T

以原子方式对atomic_ptr指向的原子对象与值v进行按位与操作,并返回操作前原子对象中存储的原值。

示例:原子按位与的函数实现
// 所有操作均以原子方式执行
fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v : T) -> T {
  let old = *atomic_ptr;
  *atomic_ptr = old & v;
  return old;
}
17.8.3.6. atomicOr
fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T

以原子方式对atomic_ptr指向的原子对象与值v进行按位或操作,并返回操作前原子对象中存储的原值。

示例:原子按位或的函数实现
// 所有操作均以原子方式执行
fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v : T) -> T {
  let old = *atomic_ptr;
  *atomic_ptr = old | v;
  return old;
}
17.8.3.7. atomicXor
fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T

以原子方式对atomic_ptr指向的原子对象与值v进行按位异或操作,并返回操作前原子对象中存储的原值。

示例:原子按位异或的函数实现
// 所有操作均以原子方式执行
fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v : T) -> T {
  let old = *atomic_ptr;
  *atomic_ptr = old ^ v;
  return old;
}

17.8.4. atomicExchange

fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T

以原子方式将值v写入atomic_ptr指向的原子对象,并返回操作前原子对象中存储的原值。

示例:原子交换函数的实现
// 所有操作均以原子方式执行
fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v : T) -> T {
  let old = *atomic_ptr;
  *atomic_ptr = v;
  return old;
}

17.8.5. atomicCompareExchangeWeak

fn atomicCompareExchangeWeak(
      atomic_ptr: ptr<AS, atomic<T>, read_write>,
      cmp: T,
      v: T) -> __atomic_compare_exchange_result<T>

struct __atomic_compare_exchange_result<T> {
  old_value : T,   // 原子中的旧值
  exchanged : bool // 如果发生了交换则为 true
}

注意: 不能显式声明类型为__atomic_compare_exchange_result,但值可推断类型。

以原子方式执行以下步骤:

  1. 加载atomic_ptr指向的原值。

  2. 用等号操作符将原值与cmp进行比较。

  3. 仅当比较结果为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

重载
@const @must_use fn pack4x8snorm(e: vec4<f32>) -> u32
描述 将四个归一化浮点值转换为 8 位有符号整数,再组合成一个u32值。

输入的第e[i]分量会被转换为 8 位补码整数 ⌊ 0.5 + 127 × min(1, max(-1, e[i])) ⌋,然后被放入结果的 8 × i 到 8 × i + 7 这 8 个比特中。

17.9.2. pack4x8unorm

重载
@const @must_use fn pack4x8unorm(e: vec4<f32>) -> u32
描述 将四个归一化浮点值转换为 8 位无符号整数,再组合成一个u32值。

输入的第e[i]分量会被转换为 8 位无符号整数 ⌊ 0.5 + 255 × min(1, max(0, e[i])) ⌋,然后被放入结果的 8 × i 到 8 × i + 7 这 8 个比特中。

17.9.3. pack4xI8

重载
@const @must_use fn pack4xI8(e: vec4<i32>) -> u32
描述 e每个分量的低 8 位打包为一个u32值,丢弃未用的高位比特。

输入的第e[i]分量被映射到结果的 8 × i 到 8 × i + 7 这 8 个比特。

17.9.4. pack4xU8

重载
@const @must_use fn pack4xU8(e: vec4<u32>) -> u32
描述 e每个分量的低 8 位打包为一个u32值,丢弃未用的高位比特。

输入的第e[i]分量被映射到结果的 8 × i 到 8 × i + 7 这 8 个比特。

17.9.5. pack4xI8Clamp

重载
@const @must_use fn pack4xI8Clamp(e: vec4<i32>) -> u32
描述 e每个分量限制在 [-128, 127] 区间后,取低 8 位并打包为一个u32值。

输入的第e[i]分量被映射到结果的 8 × i 到 8 × i + 7 这 8 个比特。

17.9.6. pack4xU8Clamp

重载
@const @must_use fn pack4xU8Clamp(e: vec4<u32>) -> u32
描述 e的每个分量限制在 [0, 255] 区间后,取低 8 位并打包为一个u32值。

输入的第e[i]分量被映射到结果的 8 × i 到 8 × i + 7 这 8 个比特。

17.9.7. pack2x16snorm

重载
@const @must_use fn pack2x16snorm(e: vec2<f32>) -> u32
描述 将两个归一化浮点值转换为 16 位有符号整数,并合并为一个 u32 值。
输入的第 e[i] 分量会被转换为 16 位补码整数 ⌊ 0.5 + 32767 × min(1, max(-1, e[i])) ⌋,然后被放入结果的 16 × i 到 16 × i + 15 这 16 个比特中。

17.9.8. pack2x16unorm

重载
@const @must_use fn pack2x16unorm(e: vec2<f32>) -> u32
描述 将两个归一化浮点值转换为 16 位无符号整数,并合并为一个 u32 值。
输入的第 e[i] 分量会被转换为 16 位无符号整数 ⌊ 0.5 + 65535 × min(1, max(0, e[i])) ⌋,然后被放入结果的 16 × i 到 16 × i + 15 这 16 个比特中。

17.9.9. pack2x16float

重载
@const @must_use fn pack2x16float(e: vec2<f32>) -> u32
描述 将两个浮点值转换为半精度浮点数(half-precision float),并合并为一个 u32 值。
输入的第 e[i] 分量会被转换为 IEEE-754 binary16 类型,然后放入结果的 16 × i 到 16 × i + 15 这 16 个比特中。 详见 § 15.7.6 浮点转换

如果 e[0]e[1] 超出 binary16 的有限范围,则:

17.10. 数据解包内置函数

数据解包内置函数可用于解码那些与WGSL类型不直接对应的数据格式的值。 这样可以让程序从内存中读取大量密集打包的值,从而减少着色器的内存带宽需求。

每个内置函数会将输入值拆分成多个通道,然后对每个通道应用通道传输函数

注意: 对unorm值解包时,归一化浮点结果在区间[0.0, 1.0]内。

注意: 对snorm值解包时,归一化浮点结果在区间[-1.0, 1.0]内。

17.10.1. unpack4x8snorm

重载
@const @must_use fn unpack4x8snorm(e: u32) -> vec4<f32>
描述 将32位数值分解为四个8位块,然后将每个块重新解释为有符号归一化浮点值。
结果的第i个分量为max(v ÷ 127, -1),其中ve的 8×i到8×i + 7比特按补码有符号整数解释的值。

17.10.2. unpack4x8unorm

重载
@const @must_use fn unpack4x8unorm(e: u32) -> vec4<f32>
描述 将32位数值分解为四个8位块,然后将每个块重新解释为无符号归一化浮点值。
结果的第i个分量为v ÷ 255,其中ve的 8×i到8×i + 7比特按无符号整数解释的值。

17.10.3. unpack4xI8

重载
@const @must_use fn unpack4xI8(e: u32) -> vec4<i32>
描述 e被解释为具有四个8位有符号整数分量的向量。将e解包为带符号扩展的vec4<i32>。

17.10.4. unpack4xU8

重载
@const @must_use fn unpack4xU8(e: u32) -> vec4<u32>
描述 e被解释为具有四个8位无符号整数分量的向量。将e解包为带零扩展的vec4<u32>。

17.10.5. unpack2x16snorm

重载
@const @must_use fn unpack2x16snorm(e: u32) -> vec2<f32>
描述 将32位数值分解为两个16位块,然后将每个块重新解释为有符号归一化浮点值。
结果的第i个分量为max(v ÷ 32767, -1),其中ve的 16×i到16×i + 15比特按补码有符号整数解释的值。

17.10.6. unpack2x16unorm

重载
@const @must_use fn unpack2x16unorm(e: u32) -> vec2<f32>
描述 将32位数值分解为两个16位块,然后将每个块重新解释为无符号归一化浮点值。
结果的第i个分量为v ÷ 65535,其中ve的 16×i到16×i + 15比特按无符号整数解释的值。

17.10.7. unpack2x16float

重载
@const @must_use fn unpack2x16float(e: u32) -> vec2<f32>
描述 将32位数值分解为两个16位块,并将每个块重新解释为浮点值。
结果的第i个分量是v的f32表示,其中ve的 16×i到16×i + 15比特按IEEE-754 binary16类型解释的值。 详见§ 15.7.6 浮点转换

17.11. 同步内置函数

所有同步函数都会以 Acquire/Release 内存顺序执行控制屏障。 即,所有同步函数以及受影响的内存和原子操作都按照程序顺序相对于同步函数有序。 此外,程序顺序上在同步函数之前的受影响内存和原子操作,必须在工作组内的任意成员执行同步函数之后的受影响内存或原子操作之前,对该工作组的所有其他线程可见。

所有同步函数使用Workgroup 内存作用域
所有同步函数具有Workgroup 执行作用域
所有同步函数必须仅用于计算着色器阶段。 所有同步函数必须仅在一致控制流中调用。

17.11.1. storageBarrier

重载
fn storageBarrier()
描述 执行一个控制屏障同步函数,影响 storage地址空间的内存和原子操作。

17.11.2. textureBarrier

重载
fn textureBarrier()
描述 执行一个控制屏障同步函数,影响 handle地址空间的内存操作。

17.11.3. workgroupBarrier

重载
fn workgroupBarrier()
描述 执行一个控制屏障同步函数,影响 workgroup地址空间的内存和原子操作。

17.11.4. workgroupUniformLoad

重载
@must_use fn workgroupUniformLoad(p : ptr<workgroup, T>) -> T
参数化 T具体可构造类型。
描述 返回由 p 指向的值,结果对工作组内所有调用一致。 返回值是一致的。 p必须一致值

执行一个控制屏障同步函数,影响 workgroup地址空间的内存和原子操作。

重载
@must_use fn workgroupUniformLoad(p : ptr<workgroup, atomic<T>, read_write>) -> T
描述 以原子方式加载 p 指向的值,并返回给工作组内所有调用。 返回值是一致的。 p必须一致值

执行一个控制屏障同步函数,影响 workgroup地址空间的内存和原子操作。

17.12. 子组内置函数

参见§ 15.6.3 子组操作

调用这些函数时:

注意: 对于计算着色器阶段,一致控制流的作用域是 工作组。 对于片元着色器阶段,一致控制流的作用域是 绘制命令。 这两个作用域都比子组更大。

17.12.1. subgroupAdd

重载
@must_use fn subgroupAdd(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 归约操作。

返回活动子组中所有e的和。

17.12.1.1. subgroupExclusiveAdd
重载
@must_use fn subgroupExclusiveAdd(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 独占前缀扫描(exclusive prefix scan)操作。

返回活动子组中,子组调用ID小于当前调用ID的所有e的和。

在活动调用中ID最小的调用返回值为T(0)

17.12.1.2. subgroupInclusiveAdd
重载
@must_use fn subgroupInclusiveAdd(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 包含性前缀扫描(inclusive prefix scan)操作。

返回活动子组中,子组调用ID小于等于当前调用ID的所有e的和。

注意:等价于subgroupExclusiveAdd(x) + x

17.12.2. subgroupAll

重载
@must_use fn subgroupAll(e : bool) -> bool
描述 如果 活动 子组中的所有调用的 e 都为 true,则返回 true

17.12.3. subgroupAnd

重载
@must_use fn subgroupAnd(e : T) -> T
前置条件 T 是 i32、u32、vecN<i32> 或 vecN<u32>
描述 归约操作。

返回 活动 子组中所有 e 的按位与(&)结果。

17.12.4. subgroupAny

重载
@must_use fn subgroupAny(e : bool) -> bool
描述 如果 活动 子组中任意调用的 etrue,则返回 true

17.12.5. subgroupBallot

重载
@must_use fn subgroupBallot(pred : bool) -> vec4<u32>
描述 返回 活动 子组predtrue 的调用的位掩码。

返回值的 x 分量包含调用 0 到 31。
y 分量包含调用 32 到 63。
z 分量包含调用 64 到 95。
w 分量包含调用 96 到 127。

在每个分量内,ID 按比特位升序排列(例如,ID 32 在 y 分量的 bit 0 位置)。

17.12.6. subgroupBroadcast

重载
@must_use fn subgroupBroadcast(e : T, id : I) -> T
前置条件 T具体 数值标量数值向量
Iu32i32
描述 返回子组中 子组调用IDid 匹配的调用的 e 值,返回给所有 活动 子组调用。

id 必须是区间 [0, 128) 内的常量表达式

如果 id 没有选择一个 活动 调用,则为动态错误

注意:如果需要非常量版本的 id,请使用 subgroupShuffle

17.12.6.1. subgroupBroadcastFirst
重载
@must_use fn subgroupBroadcastFirst(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 返回 子组调用ID 最小的 活动 调用的 e 值,返回给子组内所有活动调用。

17.12.7. subgroupElect

重载
@must_use fn subgroupElect() -> bool
描述 如果当前调用在活动子组中具有最小的子组调用ID,则返回true

17.12.8. subgroupMax

重载
@must_use fn subgroupMax(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 归约操作。

返回活动子组中所有e的最大值。

17.12.9. subgroupMin

重载
@must_use fn subgroupMin(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 归约操作。

返回活动子组中所有e的最小值。

17.12.10. subgroupMul

重载
@must_use fn subgroupMul(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 归约操作。

返回活动子组中所有e的乘积。

17.12.10.1. subgroupExclusiveMul
重载
@must_use fn subgroupExclusiveMul(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 独占前缀扫描操作。

返回活动 子组中, 子组调用ID小于当前调用ID的所有e的乘积。

活动调用中ID最小的调用返回T(1)

17.12.10.2. subgroupInclusiveMul
重载
@must_use fn subgroupInclusiveMul(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 包含性前缀扫描操作。

返回活动 子组中, 子组调用ID小于等于当前调用ID的所有e的乘积。

注意:等价于subgroupExclusiveMul(x) * x

17.12.11. subgroupOr

重载
@must_use fn subgroupOr(e : T) -> T
前置条件 T 是 i32、u32、vecN<i32> 或 vecN<u32>
描述 归约操作。

返回活动 子组中所有e的按位或(|)结果。

17.12.12. subgroupShuffle

重载
@must_use fn subgroupShuffle(e : T, id : I) -> T
前置条件 T具体 数值标量数值向量
Iu32i32
描述 返回其子组调用ID 等于id的调用的e值。

如果id超出区间[0, 128),则:

如果id没有选择活动调用,则返回未定值

17.12.12.1. subgroupShuffleDown
重载
@must_use fn subgroupShuffleDown(e : T, delta : u32) -> T
前置条件 T具体 数值标量数值向量
描述 返回其子组调用ID 等于当前调用的subgroup_invocation_id + delta的调用的e值。

如果delta大于127,则:

如果delta不是一致值,则subgroup_uniformity 诊断触发。 如果subgroup_invocation_id + delta没有选择活动调用,或delta在子组内不是一致值,则返回未定值

17.12.12.2. subgroupShuffleUp
重载
@must_use fn subgroupShuffleUp(e : T, delta : u32) -> T
前置条件 T具体 数值标量数值向量
描述 返回其子组调用ID 等于当前调用的subgroup_invocation_id - delta的调用的e值。

如果delta大于127,则:

如果delta不是一致值,则subgroup_uniformity 诊断触发。 如果subgroup_invocation_id - delta没有选择活动调用,或delta在子组内不是一致值,则返回未定值

17.12.12.3. subgroupShuffleXor
重载
@must_use fn subgroupShuffleXor(e : T,  mask : u32) -> T
前置条件 T具体 数值标量数值向量
描述 返回其子组调用ID 等于当前调用的subgroup_invocation_id ^ mask的调用的e值。

如果mask大于127,则:

如果mask不是一致值,则subgroup_uniformity 诊断触发。 如果mask没有选择活动调用,或mask在子组内不是一致值,则返回未定值

17.12.13. subgroupXor

重载
@must_use fn subgroupXor(e : T) -> T
前置条件 T 是 i32、u32、vecN<i32> 或 vecN<u32>
描述 归约操作。

返回活动 子组中所有e的按位异或(^)结果。

17.13. 四元组操作

参见 § 15.6.4 四元组操作

调用这些函数时:

注意: 对于计算着色器阶段,一致控制流的作用域是 工作组。 对于片元着色器阶段,一致控制流的作用域是 绘制命令。 这两个作用域都比四元组更大。

17.13.1. quadBroadcast

重载
@must_use fn quadBroadcast(e : T, id : I) -> T
前置条件 T具体 数值标量数值向量
Iu32i32
描述 返回四元组中quad 调用IDid 匹配的调用的 e 的值,返回给该四元组内所有活动调用。

id 必须是区间 [0, 4) 内的常量表达式

如果id没有选择活动调用,则返回未定值

注意:subgroupBroadcast不同,目前没有非常量替代方案。

17.13.2. quadSwapDiagonal

重载
@must_use fn quadSwapDiagonal(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 返回四元组中对角线上的调用的e值。 即:
  • ID 0 和 3 交换。

  • ID 1 和 2 交换。

17.13.3. quadSwapX

重载
@must_use fn quadSwapX(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 返回四元组中具有相同X维度的调用的e值。 即:
  • ID 0 和 1 交换。

  • ID 2 和 3 交换。

17.13.4. quadSwapY

重载
@must_use fn quadSwapY(e : T) -> T
前置条件 T具体 数值标量数值向量
描述 返回四元组中具有相同Y维度的调用的e值。 即:
  • ID 0 和 2 交换。

  • ID 1 和 3 交换。

18. 递归下降解析器的语法

本节为非规范性内容。

WGSL 语法以适合 LALR(1) 解析器的形式给出。 实现时也可以考虑使用递归下降解析器。

规范语法不能直接用于递归下降解析器,因为 其中有若干规则是左递归的。 如果被定义的非终结符在某个产生式的首位出现,则称该语法规则为直接左递归。

以下是 WGSL 语法,但经过了机械转换:

然而,它并不是 LL(1)。 对于某些非终结符,多个产生式具有相同的前瞻集。 例如,attribute 非终结符的所有产生式都以 attr 记号开头。 更微妙的例子是 global_decl,其中三个产生式都以 attribute * 短语开头,但随后分别以 fnoverridevar 记号区分。

为简洁起见,许多记号定义未在此重复。 记号定义请参见规范的主体部分。

additive_operator:

| '+'

| '-'

argument_expression_list:

| '(' ( expression ( ',' expression )* ',' ? )? ')'

assignment_statement/0.1:

| compound_assignment_operator

| '='

attribute:

| compute_attr

| const_attr

| fragment_attr

| interpolate_attr

| invariant_attr

| must_use_attr

| vertex_attr

| workgroup_size_attr

| '@' 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 ',' ? ')'

bitwise_expression.post.unary_expression:

| '&' unary_expression ( '&' unary_expression )*

| '^' unary_expression ( '^' unary_expression )*

| '|' unary_expression ( '|' unary_expression )*

bool_literal:

| 'false'

| 'true'

builtin_value_name: ident_pattern_token
case_selector:

| expression

| 'default'

component_or_swizzle_specifier:

| '.' member_ident component_or_swizzle_specifier ?

| '.' swizzle_name component_or_swizzle_specifier ?

| '[' expression ']' component_or_swizzle_specifier ?

compound_assignment_operator:

| shift_left_assign

| shift_right_assign

| '%='

| '&='

| '*='

| '+='

| '-='

| '/='

| '^='

| '|='

compound_statement:

| attribute * '{' statement * '}'

compute_attr:

| '@' 'compute'

const_attr:

| '@' 'const'

core_lhs_expression:

| ident

| '(' lhs_expression ')'

decimal_float_literal:

| /0[fh]/

| /[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?[fh]?/

| /[0-9]+[eE][+-]?[0-9]+[fh]?/

| /[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?[fh]?/

| /[1-9][0-9]*[fh]/

decimal_int_literal:

| /0[iu]?/

| /[1-9][0-9]*[iu]?/

diagnostic_control:

| '(' ident_pattern_token ',' diagnostic_rule_name ',' ? ')'

diagnostic_rule_name:

| ident_pattern_token

| ident_pattern_token '.' ident_pattern_token

expression:

| 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 )*

float_literal:

| decimal_float_literal

| hex_float_literal

for_init:

| ident func_call_statement.post.ident

| variable_or_value_statement

| variable_updating_statement

for_update:

| ident func_call_statement.post.ident

| variable_updating_statement

fragment_attr:

| '@' 'fragment'

func_call_statement.post.ident:

| template_elaborated_ident.post.ident '(' ( expression ( ',' expression )* ',' ? )? ')'

global_assert:

| 'const_assert' ';'

global_decl:

| 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 )* ',' ? '}'

global_directive:

| 'diagnostic' '(' ident_pattern_token ',' diagnostic_rule_name ',' ? ')' ';'

| 'enable' ident_pattern_token ( ',' ident_pattern_token )* ',' ? ';'

| 'requires' ident_pattern_token ( ',' ident_pattern_token )* ',' ? ';'

global_value_decl:

| attribute * 'override' optionally_typed_ident ( '=' expression )?

| 'const' optionally_typed_ident '=' expression

hex_float_literal:

| /0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+([pP][+-]?[0-9]+[fh]?)?/

| /0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fh]?/

| /0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*([pP][+-]?[0-9]+[fh]?)?/

ident:

| ident_pattern_token

int_literal:

| decimal_int_literal

| hex_int_literal

interpolate_attr:

| '@' 'interpolate' '(' ident_pattern_token ',' ? ')'

| '@' 'interpolate' '(' ident_pattern_token ',' ident_pattern_token ',' ? ')'

invariant_attr:

| '@' 'invariant'

lhs_expression:

| core_lhs_expression component_or_swizzle_specifier ?

| '&' lhs_expression

| '*' lhs_expression

literal:

| bool_literal

| float_literal

| int_literal

member_ident: ident_pattern_token
multiplicative_operator:

| '%'

| '*'

| '/'

must_use_attr:

| '@' 'must_use'

optionally_typed_ident:

| ident ( ':' type_specifier )?

param:

| attribute * ident ':' type_specifier

primary_expression:

| ident template_elaborated_ident.post.ident

| ident template_elaborated_ident.post.ident argument_expression_list

| literal

| '(' expression ')'

relational_expression.post.unary_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

shift_expression.post.unary_expression:

| ( multiplicative_operator unary_expression )* ( additive_operator unary_expression ( multiplicative_operator unary_expression )* )*

| shift_left unary_expression

| shift_right unary_expression

statement:

| 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

| 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 ? ';'

switch_clause:

| 'case' case_selector ( ',' case_selector )* ',' ? ':' ? compound_statement

| 'default' ':' ? compound_statement

swizzle_name:

| /[rgba]/

| /[rgba][rgba]/

| /[rgba][rgba][rgba]/

| /[rgba][rgba][rgba][rgba]/

| /[xyzw]/

| /[xyzw][xyzw]/

| /[xyzw][xyzw][xyzw]/

| /[xyzw][xyzw][xyzw][xyzw]/

template_arg_expression: expression
template_elaborated_ident.post.ident:

| ( _template_args_start template_arg_expression ( ',' expression )* ',' ? _template_args_end )?

translation_unit:

| global_directive * ( global_decl | global_assert | ';' ) *

translation_unit/0.1/0/0.0:

| global_assert

| global_decl

| ';'

type_specifier:

| ident ( _template_args_start template_arg_expression ( ',' expression )* ',' ? _template_args_end )?

unary_expression:

| primary_expression component_or_swizzle_specifier ?

| '!' unary_expression

| '&' unary_expression

| '*' unary_expression

| '-' unary_expression

| '~' unary_expression

variable_decl:

| 'var' ( _template_args_start expression ( ',' expression )* ',' ? _template_args_end )? optionally_typed_ident

variable_or_value_statement:

| variable_decl

| variable_decl '=' expression

| 'const' optionally_typed_ident '=' expression

| 'let' optionally_typed_ident '=' expression

variable_updating_statement:

| lhs_expression ( '=' | compound_assignment_operator ) expression

| lhs_expression '++'

| lhs_expression '--'

| '_' '=' expression

vertex_attr:

| '@' 'vertex'

workgroup_size_attr:

| '@' 'workgroup_size' '(' expression ',' ? ')'

| '@' 'workgroup_size' '(' expression ',' expression ',' ? ')'

| '@' 'workgroup_size' '(' expression ',' expression ',' expression ',' ? ')'

附录A:text/wgsl 媒体类型

互联网号码分配局(IANA)维护着媒体类型注册表,见 [IANA-MEDIA-TYPES]

以下是 WGSL 模块的 text/wgsl 媒体类型定义。 该类型已经在 IANA 注册, 并可在 https://www.iana.org/assignments/media-types/text/wgsl 上查阅。

类型名称

text

子类型名称

wgsl

必需参数

N/A

可选参数

编码注意事项

二进制

WGSL 是使用 UTF-8 编码的 Unicode 文本,不包含字节序标记(BOM)。 参见 § 3 文本结构

安全注意事项

WebGPU 着色语言(WGSL)是一种用于在 WebGPU API 上下文中执行的 GPU 代码的编程语言。安全注意事项见 [WebGPU] 第2.1节 安全注意事项。 隐私注意事项见 [WebGPU] 第2.2节 隐私注意事项。

互操作性注意事项

WebGPU 的实现可能具有不同的能力,这些差异可能会影响 WGSL 程序能够使用的功能。参见 [WebGPU] 第3.6节 可选能力, 以及 § 4.1.2 语言扩展

预期实现会将本注册适用于 WGSL 的后续版本,其发布规范参考也会随时更新。虽然这种预期在媒体类型注册中较为少见,但符合业界广泛惯例。

发布规范

WebGPU 着色语言

使用此媒体类型的应用

WebGPU 的实现。预计包括网页浏览器。

片段标识符注意事项

附加信息

魔数(Magic number):无

文件扩展名:.wgsl

Macintosh 文件类型代码:TEXT

联系人及邮箱

David Neto, dneto@google.com,或 WGSL 中列出的编辑。

预期用途

COMMON

作者

W3C。见 WGSL 中列出的编辑。

变更控制者

W3C

规范性引用

[WebGPU] W3C,“WebGPU” W3C 工作草案,2023年1月。https://w3.org/TR/webgpu

Webgpu 着色语言 W3C,“WebGPU Shading Language” W3C 工作草案,2023年1月。 https://w3.org/TR/WGSL

符合性

文档约定

符合性要求以描述性断言与 RFC 2119 术语相结合的方式表达。 本规范规范性部分中的关键词 “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY” 和 “OPTIONAL” 应按照 RFC 2119 中的说明进行解释。 但为了可读性,本规范没有将这些词全部大写。

本规范的所有文本都是规范性的, 除非明确标记为非规范性、示例和注释的部分。[RFC2119]

本规范中的示例由“例如”引出, 或用class="example"与规范性文本区分开来, 如下所示:

这是一个信息性示例。

信息性注释以“注意”一词开头, 并用class="note"与规范性文本区分开来, 如下所示:

注意,这是一条信息性注释。

符合规范的算法

以命令式短语描述的算法要求 (如“去除所有前导空格字符”或“返回 false 并中止这些步骤”), 应按照引入算法时所用关键词(如“must”、“should”、“may”等)所表达的含义进行解释。

以算法或具体步骤表述的符合性要求, 可以用任何方式实现, 只要最终结果等价即可。 特别地,本规范中定义的算法旨在易于理解, 并不考虑性能。 鼓励实现者进行优化。

索引

本规范定义的术语

引用文献中定义的术语

参考文献

规范性引用

[DeRemer1969]
LR(k) 语言的实用翻译器。1969年10月24日。URL: http://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-065.pdf
[ECMASCRIPT]
ECMAScript 语言规范。URL: https://tc39.es/ecma262/multipage/
[IEEE-754]
浮点运算 IEEE 标准。2008年8月29日。URL: http://ieeexplore.ieee.org/servlet/opac?punumber=4610933
[Muller2005]
关于 ulp(x) 的定义。2005年2月。URL: https://inria.hal.science/inria-00070503
[RFC2119]
S. Bradner. 用于指示需求级别的 RFC 关键词。1997年3月。最佳当前实践。URL: https://datatracker.ietf.org/doc/html/rfc2119
[UAX14]
Robin Leroy. Unicode 换行算法。2024年9月2日。Unicode 标准附录 #14。URL: https://www.unicode.org/reports/tr14/tr14-53.html
[UAX31]
Mark Davis; Robin Leroy. Unicode 标识符与语法。2024年9月2日。Unicode 标准附录 #31。URL: https://www.unicode.org/reports/tr31/tr31-41.html
[UnicodeVersion14]
Unicode 标准,第 14.0.0 版。URL: http://www.unicode.org/versions/Unicode14.0.0/
[VanWyk2007]
Eric R. Van Wyk; August C. Schwerdfeger. 用于可扩展语言解析的上下文感知扫描。2007年。URL: https://dl.acm.org/doi/10.1145/1289971.1289983
[VulkanMemoryModel]
Jeff Bolz 等。Vulkan 内存模型。URL: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#memory-model
[WebGPU]
Kai Ninomiya; Brandon Jones; Myles C. Maxfield. WebGPU。 工作草案。URL: https://w3.org/TR/webgpu

补充性引用

[CHARMOD-NORM]
Addison Phillips 等。万维网字符模型:字符串匹配。2021年8月11日。NOTE。URL: https://www.w3.org/TR/charmod-norm/
[CSS-TABLES-3]
François Remy; Greg Whitworth; David Baron. CSS 表格模块 3 级。2019年7月27日。WD。URL: https://www.w3.org/TR/css-tables-3/
[IANA-MEDIA-TYPES]
媒体类型。URL: https://www.iana.org/assignments/media-types/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准。实时标准。URL: https://infra.spec.whatwg.org/
[Jeannerod2013]
Claude-Pierre Jeannerod; Nicolas Louvet; Jean-Michel Muller. Kahan 算法对 2x2 行列式精确计算的进一步分析。URL: https://www.ams.org/journals/mcom/2013-82-284/S0025-5718-2013-02679-8/S0025-5718-2013-02679-8.pdf
[WASM-CORE-2]
Andreas Rossberg. WebAssembly 核心规范。2025年6月16日。CRD。URL: https://www.w3.org/TR/wasm-core-2/