WebGPU 着色语言

W3C 候选推荐标准草案,

有关本文档的更多详细信息
此版本:
https://www.w3.org/TR/2026/CRD-WGSL-20260921/
最新发布版本:
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 问题。 或者,也可以将意见发送到 Web GPU 工作组的邮件列表 public-gpu@w3.org(存档)。 本草案突出显示了一些仍有待工作组讨论的问题。 对这些问题的结果尚未作出任何决定,包括这些问题是否有效。

本文档由 Web GPU 工作 组作为候选推荐标准草案,使用推荐标准轨道发布。本文档 至少在 之前将保持候选推荐标准状态。

工作组预计将证明每项特性至少在两个已部署的浏览器中基于 现代 GPU 系统 API 实现。测试套件将用于构建实现报告。

作为候选推荐标准发布并不意味着得到 W3C 及其成员的认可。候选 推荐标准草案整合了工作组打算纳入后续候选推荐标准快照中的、 相对于上一候选推荐标准的更改。

本文档会随时维护和更新。本文档的某些部分仍在进行中。

本文档由依据 W3C 专利政策运作的工作组制定。W3C 维护着一份与该工作组交付成果有关的所有专利披露的公开列表; 该页面还包含披露专利的说明。任何实际知悉某项专利,并且认为该专利包含必要权利要求的个人,都必须按照 W3C 专利政策第 6 节披露该 信息。

本文档受 2025年8月18日 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 确实提供了一些有限机制,可将标量 类型提升为向量类型。 这也适用于复合类型。

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

但是,这些调用操作的是不同的着色器阶段输入集合,包括内置输入, 这些内置输入提供用于将某个调用与其同级调用区分开的标识值。 每个调用都有自己独立的内存空间,其形式为 private 和function 地址空间中的变量。

着色器阶段内的调用并发执行,并且通常可能并行执行。 着色器作者负责确保着色器阶段中调用的动态行为:

WGSL 有时允许某个给定特性具有多种可能的行为。 这会造成可移植性风险,因为不同实现可能表现出不同的行为。 WGSL 的设计旨在尽量减少这种情况,但受到可行性的约束, 同时还要实现跨广泛设备获得高性能的目标。

行为 要求是实现处理或执行 WGSL 程序时将 执行的操作。它们描述了实现与程序员之间契约中 实现所承担的义务。 当这些义务可能并非显而易见时,本规范会明确说明这些义务。

1.2. 语法表示法

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

1.3. 数学术语和表示法

角度:

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

于是,面积 a 是一个双曲角,使得 x 是 a 的双曲余弦,而 y 是 a 的双曲正弦。

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

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

扩展实数 (也称为仿射扩展实数)是实数集合再加上 +∞ 和 −∞。 计算机使用浮点类型近似表示 扩展实数,包括两个无穷值。 参见§ 15.7 浮点求值。

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

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

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

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

roundUp 函数 对正整数 k 和 n 定义为:

roundDown 函数 对正整数 k 和 n 定义为:

c 列 r 行矩阵 A 的转置 是一个 r 列 c 行矩阵 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 程序及其可能包含的着色器的生命周期中有四个关键事件。 前两个对应于用于准备 WGSL 程序以供执行的 WebGPU API 方法。 后两个是着色器执行的开始和结束。

这些事件是:

  1. 着色器模块 创建

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

  2. 管线 创建

    • 当调用 WebGPU createComputePipeline() 方法 或 WebGPU createRenderPipeline() 方法 时会发生此事件。 这些方法使用一个或多个先前创建的着色器模块以及其他 配置信息。

    • 在创建管线期间,仅考虑指定 GPUProgrammableStage 的指定入口点所构成的着色器代码。 也就是说,与该入口点无关的代码在编译前实际上会被移除。

    • 注: 每个着色器阶段被视为 单独编译,因此可能 包含模块的不同部分。

  3. 着色器 执行开始

  4. 着色器执行 结束

    • 当着色器中的所有工作完成时,会发生此事件:

      • 其所有调用都终止,并且

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

      • 如果存在输出,则将其传递给下游管线阶段。

这些事件按顺序发生,原因是:

2.2. 错误

WebGPU 实现可能因两个原因无法处理着色器:

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

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

每项要求将在最早的机会进行检查。 即:

当从上下文无法明确判断时,本规范会指出 未满足某项特定要求 会导致着色器创建、管线创建还是动态错误。

错误的后果如下:

2.3. 诊断

实现可以在着色器模块创建或管线创建期间生成诊断。 诊断是实现为帮助应用程序作者而生成的消息。

当满足某个特定条件时,会创建或触发一个诊断, 该条件称为触发规则。 源文本中满足该条件的位置,以源文本中的点或范围表示, 称为 触发位置。

诊断具有以下 属性:

诊断的严重程度是以下值之一,按从高到低排序:

错误

该诊断是错误。 这对应于着色器创建错误或管线创建 错误。

警告

该诊断描述值得应用程序开发者关注的异常,但 并非错误。

信息

该诊断描述值得应用程序开发者关注的显著情况,但 既不是错误也不是警告。

关闭

该诊断已禁用。它不会传递给应用程序。

触发规则的名称是以下两种形式之一:

diagnostic_rule_name :

diagnostic_name_token

| diagnostic_name_token '.' diagnostic_name_token

2.3.1. 诊断处理

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

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

    • 如果存在这样的过滤器,则将其应用于 D,更新 D 的严重程度。

    • 否则,D 保持不变。

  2. 丢弃严重程度为关闭的诊断。

  3. 如果至少有一个剩余诊断 DI 的严重程度为信息,则:

    • 具有相同触发规则的其他信息 诊断可以被丢弃,只保留原始 诊断 DI。

  4. 如果至少有一个剩余诊断 DW 的严重程度为警告,则:

    • 具有相同触发规则的其他信息或 警告 诊断可以被丢弃,只保留原始 诊断 DW。

  5. 如果至少有一个剩余诊断具有错误严重程度,则:

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

  7. 如果在管线创建期间进行处理,则错误诊断 会导致验证 GPUProgrammableStage 时 WebGPU 验证失败。

注: 这些规则允许实现一旦检测到错误就停止 处理 WGSL 模块。 此外,针对某种特定警告的分析可以在出现第一个警告时停止, 针对某种特定信息诊断的分析也可以在首次出现时停止。 WGSL 不规定执行不同种类分析的顺序,也不规定单次 分析内部的顺序。 因此,对于同一个 WGSL 模块,不同实现可能报告具有相同严重程度的不同诊断实例。

2.3.2. 可过滤的触发规则

大多数诊断都会无条件 报告给 WebGPU 应用程序。 某些类型的诊断可以过滤,其中一种方式是指定其触发规则的名称。 下表列出了可以过滤的标准触发规则集合。

可过滤的诊断触发规则
可过滤的触发规则 默认严重程度 触发位置 描述
derivative_uniformity 错误 任何计算导数的内置函数的调用 点位置。 即以下任意函数调用的位置: 对内置函数的调用会计算导数,但一致性分析无法 证明该调用发生在一致控制流中。

参见§ 15.2 一致性。

subgroup_uniformity 错误 任何 子组或四元组 内置函数的调用 点位置。 对子组或四元组内置函数的调用,但一致性分析无法 证明该调用发生在一致控制流中。 此外,当一致性分析无法证明以下参数值是一致的时:

参见§ 15.2 一致性。

使用由单个诊断名称词法单元组成的未识别触发规则时,用户代理应 触发警告。

实现可以支持此处未指定的触发规则, 前提是它们使用diagnostic_rule_name 的多词法单元形式拼写。 使用多词法单元形式拼写的未识别触发规则本身可以触发 诊断。

本规范的未来版本可以移除某个特定规则,或减弱其默认严重程度 (即用严重程度较低的默认值替换当前默认值),并仍被视为满足向后 兼容性。 例如,WGSL 的未来版本可以将derivative_uniformity 的默认严重程度从 error 更改为 warning 或 info。 在规范作出此类更改之后,先前有效的程序仍将保持有效。

2.3.3. 诊断过滤

一旦具有可过滤 触发规则的诊断被触发,WGSL 就提供机制来丢弃该 诊断,或修改其严重程度。

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

将诊断过滤器 DF(AR,NS,TR) 应用于诊断 D 会产生 以下效果:

范围诊断 过滤器是一种诊断过滤器,其受影响范围是指定的 源文本范围。 范围诊断过滤器被指定为受影响源范围开始处的 @diagnostic属性, 如下表所示。 @diagnostic 属性不得出现在任何其他位置。

范围诊断过滤器的放置位置
放置位置 受影响范围
复合语句的开头。 该复合语句。
函数声明的开头。 该函数声明。
if 语句的开头。 该 if 语句:if_clause 和所有相关的else_if_clause 与else_clause 子句, 包括所有控制条件表达式。
switch 语句的开头。 该 switch 语句:选择器表达式和switch_body。
switch_body 的开头。 该switch_body。
loop 语句的开头。 该 loop 语句。
while 语句的开头。 该 while 语句:条件表达式和循环体。
for 语句的开头。 该 for 语句:for_header 和循环体。
紧接在 loop、while 或for 循环的循环体左花括号('{')之前。 该循环体。
continuing_compound_statement 的开头。 该continuing_compound_statement。

注: 以下内容也属于复合语句: 函数体、case 子句、独立 default 子句、 while 和for 循环的循环体, 以及if_clause、else_if_clause 和else_clause 的主体。

示例:纹理采样上的范围诊断过滤器
var<private> d: f32;
fn helper() -> vec4<f32> {
  // 在 "if" 的主体中禁用 derivative_uniformity 诊断。
  //
  if (d < 0.5) @diagnostic(off,derivative_uniformity) {
    return textureSample(t,s,vec2(0,0));
  }
  return vec4(0.0);
}

全局 诊断过滤器可用于将诊断过滤器应用于整个 WGSL 模块。

示例:导数一致性的全局诊断过滤器
diagnostic(off,derivative_uniformity);
var<private> d: f32;
fn helper() -> vec4<f32> {
  if (d < 0.5) {
    // 此处的 derivative_uniformity 诊断已由
    // 全局诊断过滤器禁用。
    return textureSample(t,s,vec2(0,0));
  } else {
    // derivative_uniformity 诊断被设置为 'warning' 严重程度。
    @diagnostic(warning,derivative_uniformity) {
      return textureSample(t,s,vec2(0,0));
    }
  }
  return vec4(0.0);
}

两个诊断过滤器 DF(AR1,NS1,TR1) 和 DF(AR2,NS2,TR2) 在以下情况下发生冲突:

诊断过滤器必须不冲突。

注: 当多个全局诊断过滤器 不发生冲突时,允许同时存在。

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
即时数据 变量的最大字节大小。

这会将 WebGPU maxImmediateSize 限制 映射为独立的 WGSL 限制。

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

3. 文本结构

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

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

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

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

3.1. 解析

要解析 WGSL 模块:

  1. 移除注释:

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

    • 重复此操作,直到不再有注释。

  2. 使用§ 3.9 模板列表中的算法查找模板列表。 此步骤用于消除 '<'(U+003C)和 '>'(U+003E)码点 作为模板列表分隔符与作为比较运算符等其他用途之间的歧义。

  3. 解析整个文本,尝试匹配translation_unit 语法规则。 解析使用 LALR(1) 解析器(一个词法单元的前瞻)[DeRemer1969],并进行以下 定制:

    • 词法单元化与解析交错进行,并且具有上下文感知能力。 当解析器请求下一个词法单元时:

如果出现以下情况,则会产生着色器创建 错误:

注:上述过程将 模板列表 发现描述为在基于语法的解析(步骤 3)之前执行的独立阶段(步骤 2)。 这种分离简化了规范。 但是,将上述过程映射到某些工具可能并不方便,因为它在词法单元化之前插入了一个独立的 处理阶段。

另一种方法是将模板列表发现与词法单元化交错进行。 在这种方法中,会在可能出现模板列表的语法规则任意位置放置一个合成词法单元(_disambiguate_template)。 当扫描器尝试匹配_disambiguate_template 词法单元时:

后续词法单元扫描步骤将使用已记录的模板列表分隔符位置,按指示生成 _template_args_start 和_template_args_end 词法单元。

这种替代方法是非规范性的。 规范性语法中包含_disambiguate_template 词法单元,以帮助 使用替代方法的实现。 使用标准方法的解析器可以忽略该合成词法单元,或者等效地始终成功 将它匹配为空字符串。

3.2. 空白和换行

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

换行是一段连续的 空白码点序列, 用于表示一行的结束。 它被定义为 UAX14 第 6.1 节不可定制的换行规则中的LB4 和LB5 所定义的表示“强制换行”的空白。 即,换行是以下任一种:

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

3.3. 注释

注释是一段 不影响 WGSL 程序有效性或含义的文本,但注释可以分隔词法单元。 着色器作者可以使用注释记录其程序。

行尾 注释是一种注释, 由两个码点 //(U+002F 后跟 U+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

浮点 字面量在逻辑上分为两部分:用于表示小数的有效数,以及可选的指数。 粗略地说,字面量的值等于有效数乘以某个底数的指定指数次幂。 如果某个有效数数字非零, 或其左侧和右侧都存在非零有效数数字,则该数字是有效的。 有效数字从左到右计数:第 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 的 Unicode 字符数据库包含 一份非规范性列表, 其中列出了 XID_Start 和 XID_Continue 的所有有效码点。

注: 某些内置函数的返回类型是结构体类型,其名称不能在 WGSL 源代码中使用。 这些结构体类型被描述为仿佛是以两个下划线开头的名称预声明的一样。 结果值可以通过类型推断保存到新声明的 let 或 var 中, 或者立即按名称提取其中一个成员。有关使用示例,请参见 frexp 和 modf 的说明。

3.7.1. 标识符比较

两个 WGSL 标识符相同,当且仅当它们由完全相同的码点序列组成。

注: 本规范不允许为了比较而对值进行 Unicode 规范化。 视觉上和语义上相同但使用不同 Unicode 字符序列的值不会 匹配。 建议内容作者始终使用相同的编码序列,或者在选择值时避免可能 引发问题的字符。有关更多信息,请参见[CHARMOD-NORM]。

注: 当把某个标识符的所有实例替换为该标识符的某个同形异码字符后会改变 WGSL 模块的含义时,用户代理应发出开发者可见的 警告。 (同形异码字符是一段码点序列,对读者而言可能看起来与另一段码点 序列相同。 用于检测同形异码字符的映射示例包括上一段中提到的转换、映射和匹配算法。 如果可以通过 反复将子序列替换为其同形异码字符,使一个标识符转换成另一个序列,则这两个码点序列互为同形异码。)

3.8. 上下文相关名称

上下文相关 名称是一个词法单元,用于命名一个 概念,但仅用于特定的语法上下文中。 该词法单元的拼写可能与标识符相同,但该词法单元不会解析到已声明的对象。 本节列出了用作上下文相关名称的词法单元。 该词法单元不得是关键字或保留字。

3.8.1. 属性名称

参见§ 12 属性。

attribute 名称包括:

3.8.2. 内置值名称

内置值 名称词法单元是一个词法单元,用于 内置值的名称中。

参见§ 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. 扩展名称

有效的启用扩展 名称列于§ 4.1.1 启用扩展中,但通常具有与标识符相同的 形式:

enable_extension_name :

ident_pattern_token

启用扩展 名称包括:

有效的语言 扩展名称列于§ 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.8.9. 深度模式名称

深度模式 名称词法单元是一个词法单元,在支持fragment_depth 特性时,用于 frag_depth 内置值中。

3.9. 模板列表

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

忽略注释和空白时,模板列表是:

模板 参数的形式由下面的模板列表发现算法隐式定义。 通常,它们是名称、表达式或类型。

注: 例如,短语 vec3<f32> 是一个模板参数化,其中 vec3 是被修改的一般概念, 而 <f32> 是包含一个参数,即 f32 类型的模板列表。 它们组合在一起,vec3<f32> 表示一个特定的向量类型。

注: 例如,短语 var<storage,read_write> 使用模板参数 storage 和 read_write 修改一般的 var 概念。

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

用于界定模板列表的 '<'(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,然后是 '<'(U+003C),但随后 发现下一个 '<'(U+003C)码点不能开始模板参数,因此 紧接在 B 后面的 '<' 不是模板列表的开始。 最初的 '<' 和最后的 '>' 是唯一的模板列表分隔符,并且它具有 模板参数 B<<C。

注: 短语 A<B<=C> 的分析 与上一条注释类似,因此短语 B<=C 被解析为 B,后跟 小于或等于运算符'<=',再后跟 C。 模板发现算法开始检查 B,然后是 '<'(U+003C),但随后 发现下一个 '='(U+003D)码点不能开始模板参数,因此 紧接在 B 后面的 '<' 不是模板列表的开始。 最初的 '<' 和最后的 '>' 是唯一的模板列表分隔符,并且它具有 模板参数 B<=C。

注: 检查短语 A<(B>=C)> 时,存在一个模板列表,它从第一个 '<'(U+003C) 码点开始,到最后一个 '>'(U+003E)码点结束,并具有模板参数 B>=C。 检查第一个 '>'(U+003C)码点(位于 B 之后)后,需要特别识别 '='(U+003D)码点,以免将其假定为赋值的一部分。

注: 检查短语 A<(B!=C)> 时,存在一个模板列表,它从第一个 '<''(U+003C) 码点开始,到最后一个 '>'(U+003E)码点结束,并具有模板参数 B!=C。 检查 '!'(U+0021)码点(位于 'B' 之后)后,需要特别识别 '=' (U+003D)码点,以免将其假定为赋值的一部分。

注: 检查短语 A<(B==C)> 时,存在一个模板列表,它从第一个 '<'(U+003C) 码点开始,到最后一个 '>'(U+003E)码点结束,并具有模板参数 B==C。 检查第一个 '='(U+003D)码点(位于 'B' 之后)后,需要特别识别第二个 '='(U+003D)码点,以免将它们中的任何一个假定为 赋值的一部分。

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

template_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 规范的一组 具有名称且相互协调的 修改集合,由以下内容的任意组合组成:

假设而言,扩展可以:

扩展有两种:启用扩展和语言扩展。

4.1.1. 启用扩展

启用扩展是 一个扩展,其功能仅在满足以下条件时 可用:

启用扩展旨在 公开并非普遍受支持的硬件功能。

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

enable_directive :

'enable' enable_extension_list ';'

enable_extension_list :

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

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

有效的启用扩展列于下表。

启用扩展
WGSL 启用扩展 WebGPU GPUFeatureName 描述
f16 "shader-f16" f16 类型可以在 WGSL 模块中使用。否则,使用f16 (直接或间接)将导致着色器创建错误。
clip_distances "clip-distances" 内置变量clip_distances 可以在 WGSL 模块中使用。否则,使用clip_distances 将导致 着色器创建错误。
dual_source_blending "dual-source-blending" 属性blend_src 可以在 WGSL 模块中使用。 否则,使用 blend_src 将导致着色器创建 错误。
subgroups "subgroups" 可以在 WGSL 模块中使用子组内置值、 子组内置函数和 四元组内置函数。 否则,任何使用都会导致着色器创建错误。
primitive_index "primitive-index" 内置变量primitive_index 可以在 WGSL 模块中使用。否则,使用primitive_index 将导致 着色器创建错误。
subgroup_size_control "subgroup-size-control" 属性subgroup_size 可以在 WGSL 模块中使用。 否则,使用subgroup_size 将导致着色器创建错误。 启用 subgroup_size_control 时,subgroups将自动启用。
atomic_vec2u_min_max "atomic-vec2u-min-max" 内置函数atomicStoreMin 和 atomicStoreMax 可以用于 storage 地址空间中的atomic<vec2<u32>> 类型。否则,使用这些函数将导致 着色器创建错误。
示例:使用假设的启用扩展
// 启用用于任意精度浮点类型的假设扩展。
enable arbitrary_precision_float;
enable arbitrary_precision_float; // 冗余的 enable 指令也是允许的。

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

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

4.1.2. 语言扩展

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

语言扩展 包含理论上任何 WebGPU 实现都可以支持的功能。 如果某个特性尚未得到普遍支持,那是因为某些 WebGPU 实现尚未 实现它。

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

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

requires 指令 是一种指令,用于记录 程序对一个或多个语言扩展的使用。 它不会改变实现公开的功能。 如果实现不支持其中一个必需扩展,则会产生着色器创建 错误。

WGSL 模块可以使用requires 指令表明可能存在不可移植性, 并表明预期的最低可移植性要求。

注: WebGPU 实现之外的工具可以 检查程序使用的所有语言扩展是否都由程序中的requires 指令覆盖。

requires_directive :

'requires' language_extension_list ';'

language_extension_list :

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

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

语言扩展
WGSL 语言扩展 描述
readonly_and_readwrite_storage_textures 允许将read 和read_write访问模式 用于存储纹理。 此外,添加 textureBarrier 内置函数。
packed_4x8_integer_dot_product 支持使用打包 4 分量 8 位整数向量的 32 位整数标量作为点积指令的输入, 并配合 dot4U8Packed 和 dot4I8Packed 内置函数。 此外,还添加使用打包的 4 分量 8 位整数向量进行打包和解包的指令,包括 pack4xI8、pack4xU8、 pack4xI8Clamp、pack4xU8Clamp、 unpack4xI8 和 unpack4xU8 内置函数。
unrestricted_pointer_parameters 从用户定义函数中移除以下限制:

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

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

pointer_composite_access 支持根表达式为指针的复合值 分解表达式,并产生引用。

例如,如果 p 是指向具有成员 m 的结构体的指针,则 p.m 是对 p 所指向结构体内部 m 的 内存位置的引用。

类似地,如果 pa 是指向数组的指针,则 pa[i] 是 对 pa 所指向数组的第 i 个元素的 内存位置的引用。

uniform_buffer_standard_layout 允许uniform 地址空间中的缓冲区使用与 其他地址空间相同的内存布局约束。
subgroup_id 当启用subgroups 扩展时,允许使用subgroup_id 和num_subgroups 内置值。
subgroup_uniformity 为一致控制流添加一个额外的作用域,即子组,使 子组和 四元组内置函数的作用域为同一子组中的所有调用。

注: 使用子组和四元组 内置函数需要启用 subgroups 扩展。

texture_and_sampler_let 允许let 声明的有效值类型为 纹理或采样器类型。
texture_formats_tier1 支持额外的纹素 格式: rgba16unorm、rgba16snorm、rg8unorm、 rg8snorm、rg8uint、rg8sint、 rg16unorm、rg16snorm、rg16uint、 rg16sint、rg16float、r8unorm、 r8snorm、r8uint、r8sint、 r16unorm、r16snorm、r16uint、 r16sint、r16float、rgb10a2unorm、 rgb10a2uint、rg11b10ufloat
linear_indexing 支持global_invocation_index 和 workgroup_index内置值。
immediate_address_space 启用immediate 地址空间,从而允许变量 使用 var<immediate> 声明,并绑定到通过 WebGPU API 由命令编码器直接传递的少量频繁更新的数据。
fragment_depth 为frag-depth 内置值引入新的depth_mode 内置参数,其模式为 less 和greater, 当片元着色器声明其只会写入保证 小于(或大于)现有深度的深度值时使用这些模式。
buffer_view 启用buffer 类型以及 buffer_view 内置函数。

允许声明具有不透明存储类型的变量,该类型可以被 重新解释为其他主机可共享类型。

swizzle_assignment 支持重排视图 类型。

这会启用重排赋值: 单个赋值语句可以更新向量的多个分量,而无需 更新整个向量。

例如,如果变量 v 是一个 4 元素向量,则 v.xz = vec2(1,2); 是 v.x = 1; v.z = 2; 的简写方式,同时执行一次读取和一次 写入。

如果同时支持pointer_composite_access, 那么这也适用于指针。如果 p 是指向至少有 3 个 元素的向量的指针,则 p.xz = vec2(1,2); 是 (*p).x = 1; (*p).z = 2; 的简写。

atomic_vec2u_min_max 支持在 atomic<vec2<u32>> 对象上使用 atomicStoreMin 和 atomicStoreMax 内置函数。

注: 其意图是,随着时间推移,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 程序中直接检查、分解或操作。

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

某些类型表示为模板参数化。 类型生成器是一个 预声明对象,当使用模板 列表进行参数化时,它表示一个类型。 例如,类型 atomic<u32> 将类型生成器 atomic 与 模板列表 <u32> 组合。

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

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

注: 引用类型不会写入 WGSL 模块中。参见§ 6.5.3 引用和指针类型。

注: 重排视图类型不会写入 WGSL 模块中。参见§ 6.5.4 重排视图类型。

6.1. 类型检查

WGSL 值通过对表达式求值来计算。 表达式是一段 源文本, 被解析为名称以 "expression" 结尾的 WGSL 语法规则之一。 表达式 E 可以包含子表达式,这些表达式被适当地包含在 外层表达式 E 中。 顶层 表达式是自身并非子表达式的表达式。 参见§ 8.19 表达式语法摘要。

表达式求值所产生的具体值取决于:

对特定表达式求值可能得到的值始终属于某个特定的 WGSL 类型, 称为该表达式的静态类型。 WGSL 的规则被设计为使表达式的静态类型仅取决于 表达式的静态 上下文。

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

e : T

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

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

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

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

类型检查可以通过递归地将类型规则 应用于语法短语来执行,其中语法短语是表达式或语句。 类型规则描述 语法短语的静态上下文 如何确定该短语中所含表达式的静态类型。 类型规则有两个部分:

类型规则可以在其前置条件和结论中具有类型 参数。 当类型规则的结论或前置条件包含类型参数时, 我们称它是参数化的。 如果不包含,则称该规则是完全具体化的。 我们可以通过为参数化类型规则的每个类型参数替换一个类型, 并对规则中给定参数的所有出现位置使用同一类型, 从而得到完全具体化的类型规则。 将类型分配给规则类型参数的过程 称为替换。

例如,以下是逻辑非的类型规则 (形式为 !e 的表达式):

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

这是一个参数化规则,因为它包含类型参数 T, 该参数可以表示以下四种类型中的任一种: bool、vec2<bool>、 vec3<bool> 或 vec4<bool>。 应用将 T 映射到 vec3<bool> 的替换 会产生以下完全具体化的类型规则:

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

通过应用某种满足规则其他条件的替换, 从参数化规则能够产生的每条完全具体化规则 称为该参数化规则的一个重载。 例如,布尔非规则有四个重载, 因为可以用四种方式之一为其类型参数 T 分配类型。

注: 换句话说,参数化类型规则为 一组完全具体化的类型规则提供模式, 每个规则都是通过对参数化规则应用不同替换得到的。

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

如果存在一个替换, 产生适用于该表达式的完全 具体化类型规则,则参数化类型规则 适用于该表达式。

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

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

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

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

否则会产生类型错误, 并且该源程序不是有效的 WGSL 模块。

WGSL 是一种静态类型语言, 因为对 WGSL 模块进行类型检查时,仅需检查程序源文本,就将要么成功,要么 发现类型错误。

6.1.1. 类型规则表

用于表达式的 WGSL 类型规则组织在类型规则 表中, 每条类型规则占一行。

表达式的 语义是对该表达式求值所产生的效果, 其主要效果是产生一个结果值。 适用于某个表达式的类型规则中的描述列将指定该表达式的 语义。 该语义通常取决于类型规则参数的值,包括 任何子表达式的假定值。 有时表达式的语义还包括产生 结果值以外的效果,例如其子表达式的非结果值效果。

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

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

6.1.2. 转换等级

当类型断言 e:T 用作类型规则前置条件时,在以下情况下满足该断言:

该规则通过下面表中定义的、作用于类型对的ConversionRank 函数进行形式化。 ConversionRank 函数 表达将一种类型(Src)的值自动转换为 另一种类型(Dest)时的偏好程度和可行性。 等级越低越优先。

可行的 自动转换将值从类型 Src 转换为类型 Dest,并且在 ConversionRank(Src,Dest) 为有限值时允许进行。 此类转换会保留值,但受§ 15.7 浮点求值中所述限制的约束。

注: 自动转换只发生在两种 情况下。 第一种,是将const 表达式转换为其对应的、可 在 GPU 上使用的有类型数值。 第二种,是从内存引用加载时,得到存储在该内存中的值。

注: 转换等级为无穷大的转换不可行,即 不允许进行。

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

从一种类型到另一种类型的 ConversionRank
Src Dest ConversionRank(Src,Dest) 描述
T T 0 恒等转换。不执行转换。
ref<AS,T,AM>
对于地址空间 AS, 且访问模式 AM 为read 或read_write。
T 0 应用加载规则,从 内存引用加载一个值。
swizzle<AS,S,N,K>
vecK<S> 0 应用重排视图加载规则,从 重排视图加载向量值。
AbstractFloat f32 1 参见§ 15.7.6 浮点转换
AbstractFloat f16 2 参见§ 15.7.6 浮点转换
AbstractInt i32 3 如果值位于 i32 范围内,则为恒等转换。 否则产生着色器创建错误。
AbstractInt u32 4 如果值位于 u32 范围内,则为恒等转换。 否则产生着色器创建错误。
AbstractInt AbstractFloat 5 参见§ 15.7.6 浮点转换
AbstractInt f32 6 行为等同于先将AbstractInt 转换为AbstractFloat, 然后再将AbstractFloat 转换为 f32
AbstractInt f16 7 行为等同于先将AbstractInt 转换为AbstractFloat, 然后再将AbstractFloat 转换为 f16
vecN<S> vecN<T> ConversionRank(S,T) 从分量类型继承转换等级。
matCxR<S> matCxR<T> ConversionRank(S,T) 从分量类型继承转换等级。
array<S,N> array<T,N> ConversionRank(S,T) 从分量类型继承转换等级。 注:只有固定大小数组可以具有抽象分量类型。
__frexp_result_abstract __frexp_result_f32 1
__frexp_result_abstract __frexp_result_f16 2
__frexp_result_vecN_abstract __frexp_result_vecN_f32 1
__frexp_result_vecN_abstract __frexp_result_vecN_f16 2
__modf_result_abstract __modf_result_f32 1
__modf_result_abstract __modf_result_f16 2
__modf_result_vecN_abstract __modf_result_vecN_f32 1
__modf_result_vecN_abstract __modf_result_vecN_f16 2
ptr<AS, buffer<N1>, AM>
其中 Src 是函数调用的某个参数的类型
ptr<AS, buffer<N2>, AM>
其中 N1 和 N2 都是const 表达式,且 Dest 是 Src 中该参数对应的形式参数类型
如果 N2 < N1,则为 1,
否则为 infinity
将固定大小缓冲区指针参数转换为大小更小的 指针参数。1
ptr<AS, buffer<N>, AM>
其中 Src 是函数调用的某个参数的类型
ptr<AS, buffer, AM>
其中 Dest 是 Src 中该参数对应的形式参数类型
1 将固定大小缓冲区指针参数转换为运行时大小缓冲区指针参数。1
S T
其中以上情况均不适用
infinity 其他类型之间不存在自动转换。
  1. 这些转换要求unrestricted_pointer_parameters 和buffer_view 语言特性。

如果满足以下条件,则类型 T 是类型 S 的具体化:

类型为 T 的值 e 的具体化 值,是将把 T 映射到 T 的具体化类型的可行转换 应用于 e 后得到的值。

注: 转换为 f32 始终比转换为 f16 更优先,因此 自动转换只会在模块中启用了 f16 扩展时产生 f16 值。

6.1.3. 重载解析

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

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

对 P 的重载解析按如下步骤进行,目标是找到唯一一个最优先的重载 候选:

  1. 对于每个候选 C,枚举语法短语中各子表达式的转换等级。 候选的前置条件已经满足,因此对于 P 中第 i 个子表达式:

    • 其静态类型已经计算出来。

    • 存在从该表达式的静态类型到前置条件中相应类型断言所要求类型的可行自动转换。 令 C.R(i) 为该转换的ConversionRank。

  2. 如果某个候选的某个子表达式在可行自动转换后解析为 抽象类型, 但该候选的另一个子表达式不是const 表达式,则淘汰该候选。

    注: 因此,如果短语中的任何子表达式 不是const 表达式, 那么该短语中的所有子表达式都必须具有具体类型。

  3. 对候选排序:给定两个重载候选 C1 和 C2,如果满足以下条件,则 C1 比 C2 更优先:

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

      • 也就是说,将 C1 应用于 P 所需的每个表达式转换 至少与将 C2 应用于 P 所需的相应表达式转换一样优先。

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

      • 也就是说,至少存在一个应用 C1 所需的表达式转换, 严格优先于应用 C2 所需的相应转换。

  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 到可构造类型最优先的转换(转换
// 等级最低)是 AbstractInt 到 i32。
let i32_min = -2147483648;  // 类型为 i32

// 无效。与上面一样选择 AbstractInt 到 i32,但该值超出
// 范围,因此产生着色器创建错误。
let i32_too_large_2 = 2147483648; // 无效。

// 子表达式可以解析为 AbstractInt 和 AbstractFloat。
// 以下示例都有效,并且变量的值都是 6u。
var u32_expr1 = (1 + (1 + (1 + (1 + 1)))) + 1u;
var u32_expr2 = 1u + (1 + (1 + (1 + (1 + 1))));
var u32_expr3 = (1 + (1 + (1 + (1u + 1)))) + 1;
var u32_expr4 = 1 + (1 + (1 + (1 + (1u + 1))));

// 根据内置函数参数进行推断。

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

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

// 类型规则违规。

// 无效,初始化器只能解析为 f32:
// 不存在从 AbstractFloat 到 u32 的可行自动转换。
let mismatch : u32 = 1.0;

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

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

// let 声明的初始化器必须是可构造类型(或指针)。
// 从 AbstractInt 到可构造类型最优先的自动转换
// 是 AbstractInt 到 i32,转换等级为 2。因此 '1' 被推断为 i32。
let some_i32 = 1; // 类似 let some_i32: i32 = 1i;

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

// 另一个溢出情况
let overflow_u32 = (1 -2) + 1u; // 无效,-1 超出 u32 的范围

// 理想值超出 32 位范围,但又回到范围内
let out_and_in_again = (0x1ffffffff / 8);

// 类似,但无效
let out_of_range = (0x1ffffffff / 8u); // 要求以 32 位进行计算,
                                       // 使 0x1ffffffff 超出范围。

6.2.2. 布尔类型

bool 类型包含值 true 和 false。

布尔字面量类型规则
前置条件 结论 描述
true: bool true 值。
false: bool false 值。

6.2.3. 整数类型

u32 类型是 32 位 无符号整数的集合。

i32 类型是 32 位 有符号整数的集合。 它使用二进制补码表示,符号位位于最高有效位位置。

对表达式中的具体整数类型进行会溢出的运算时,产生的结果按 2bitwidth 取模

整数类型的极值
类型 最小值 最大值
i32 i32(-2147483648) 2147483647i
i32(-0x80000000) 0x7fffffffi
u32 0u 4294967295u
0x0u 0xffffffffu

注: AbstractInt 也是整数类型。

6.2.4. 浮点类型

f32 类型是 IEEE-754 binary32(单精度)格式的 32 位 浮点值集合。 有关详细信息,请参见§ 15.7 浮点求值。

f16 类型是 IEEE-754 binary16(半精度)格式的 16 位 浮点值集合。如果程序未包含 enable f16; 指令来启用 f16 扩展,却使用了 f16 类型,则会产生着色器创建 错误。有关详细信息,请参见§ 15.7 浮点求值。

下表列出了浮点类型的某些极值。 每个值都有对应的负值。

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

注: AbstractFloat 也是浮点类型。

6.2.5. 标量类型

标量类型包括bool、AbstractInt、 AbstractFloat、i32、u32、f32 和f16。

数值标量类型 包括AbstractInt、 AbstractFloat、i32、u32、f32 和f16。

整数标量类型 包括AbstractInt、i32 和 u32。

标量转换 将一种标量类型中的值映射到另一种标量类型中的值。 通常,结果值在目标类型限制范围内接近原始值。 标量转换通过以下任一种方式发生:

6.2.6. 向量类型

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

类型 描述
vecN<T> 由 N 个 T 类型分量组成的向量。 N 必须属于 {2, 3, 4},并且 T 必须是标量类型之一。 我们称 T 为向量的分量类型。

如果向量的分量类型是数值标量,则该向量是数值 向量。

向量的主要使用场景包括:

许多对向量(以及矩阵)的操作按逐分量方式执行,即 结果通过对 每个标量分量独立执行操作形成。

示例:向量
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> 一个具有 C 列和 R 行、类型为 T 的矩阵,其中 C 和 R 都属于 {2, 3, 4},且 T 必须是f32、f16 或AbstractFloat。 等价地,可以将其视为 C 个类型为 vecR<T> 的列向量。

矩阵的主要使用场景是表示线性变换。 在这种解释中,矩阵的向量被视为列向量。

乘积运算符(*)用于以下任一种情况:

参见§ 8.8 算术表达式。

示例:矩阵
mat2x3<f32>  // 这是一个由 32 位浮点数组成的 2 列 3 行矩阵。
             // 等价地,它是两个 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 必须是u32 或i32。
atomic<vec2<u32>> 类型为 vec2<u32> 的原子类型。 该类型的值表示无符号64 位整数类型,其中最低有效的 32 位位于 向量 分量 0 中,而最高有效的 32 位位于向量分量 1 中。

由atomic_vec2u_min_max 扩展启用。

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

原子类型只能由workgroup 地址空间中的变量,或具有read_write 访问模式的存储 缓冲区变量实例化。 如果存储类型是或包含 atomic<vec2<u32>>,则该变量必须 位于storage地址空间中。

对该类型执行操作时的内存作用域由实例化该类型所在的地址空间 决定。 workgroup 地址空间中的原子类型具有 Workgroup 的内存作用域,而 storage 地址空间中的原子类型具有 QueueFamily 的内存作用域。

原子 修改是任何 会设置原子对象内容的操作。 即使新值与 对象现有值相同,该操作仍计为一次修改。

在 WGSL 中,对于每个对象,原子修改彼此有序。 也就是说,在着色器阶段执行期间,对于每个原子对象 A,所有 代理都观察到应用于 A 的修改操作具有相同的顺序。 不同原子对象之间的顺序可能完全无关;这并不 暗示任何因果关系。 请注意,workgroup 空间中的变量在一个 工作组内共享,但不同 工作组之间不共享。

6.2.9. 数组类型

数组是可通过索引访问的元素值序列。

类型 描述
array<E,N> 一个具有 N 个 E 类型元素的固定大小 数组。
N 称为数组的元素数量。
array<E> 一个由 E 类型元素组成的运行时大小数组。 这些数组只能出现在特定上下文中。

数组中的第一个元素位于索引 0,后续每个元素位于下一个整数索引。 参见§ 8.5.3 数组访问表达式。

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

固定大小数组的元素数量表达式 N 受以下约束:

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

注: 为了满足类型等价条件,任何不是 const 表达式的 override 表达式都必须是标识符。 参见由可重写 常量确定大小的工作组变量

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

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

注: 数组元素类型不能是缓冲区类型。

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

两个数组类型相同,当且仅当以下所有条件均为真:

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

const width = 8;
const height = 8;

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

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

注: 由可重写常量确定大小的数组类型,唯一有效的用途是 作为workgroup 地址空间中的内存视图。 这包括工作组变量的存储类型。 参见§ 7 变量和值声明。

示例:由可重写常量确定大小的工作组变量
override blockSize = 16;

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

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

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

// 一个无效示例,因为可重写的元素数量只能出现在
// 最外层。
// var<workgroup> both: array<array<i32,blockSize>,2>;

// 一个无效示例,因为可重写的元素数量只对
// 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

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

属性 builtin、location、blend_src、 interpolate 和invariant 是 IO 属性。 结构体 S 成员上的IO 属性仅在 S 被用作入口点的形式参数类型或返回类型时有效。 参见§ 13.3.1 阶段间输入和输出接口。

属性 align 和size 是布局属性, 如果结构体类型用于定义uniform 缓冲区或 存储缓冲区, 则可能需要这些属性。 参见§ 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,T 的嵌套深度,记作 NestDepth(T),定义为:

6.2.12. 可构造类型

许多类型的值都可以被创建、加载、存储、传入函数 以及从函数返回。 我们称这些值为可构造的。

如果一个类型是以下之一,则它是可构造类型:

注: 所有可构造类型都具有创建时固定占用空间。

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

6.2.13. 固定占用空间类型

变量的内存占用空间 是用于存储变量内容的内存位置数量。 变量的内存占用空间取决于其存储类型,并会在着色器生命周期中的某个时刻 最终确定。 大多数变量的大小很早就在着色器创建时确定。 某些变量可能稍后在管线创建时确定大小, 还有一些最晚在着色器执行开始时确定。

如果一个类型的具体化大小在着色器创建时已经完全确定,则该类型具有创建时固定占用空间。

如果一个类型的大小在管线创建 时已经完全确定,则该类型具有固定 占用空间。

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

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

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

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

具有固定 占用空间的类型包括以下任一种:

注: 元素数量是一个 非const 表达式的override 表达式的固定大小数组,其唯一有效用途是作为 workgroup 地址空间中的内存视图。 这包括工作组变量的存储类型。

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

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

6.3. 缓冲区类型

这些类型要求buffer_view 语言特性。

buffer 值表示其内容可以解释为 另一种主机可共享类型的内存。 在其他方面,缓冲区值是不透明的。 当变量中的数据需要以多种 不同类型访问,或着色器作者希望将一个变量细分为 多个逻辑变量时,它们很有用。

缓冲区类型不是可构造的,并且只能通过 buffer_view 内置函数将其数据解释为非不透明的主机可共享类型后进行访问。

类型 描述
buffer<N> 一个具有 N 字节存储空间的固定大小 缓冲区。
N 必须是override 表达式。
buffer 一个运行时大小缓冲区。

表达式不得求值得到缓冲区 类型。

固定大小缓冲区只能由storage、uniform 和workgroup 地址空间中的变量实例化。 在 storage 和 uniform 地址空间中,大小必须是const 表达式。 运行时大小缓冲区只能由 storage 地址空间中的变量实例化。

如果 N 不大于 0:

如果启用了f16 扩展 且 N 不能被 2 整除:

如果未启用f16 扩展 且 N 不能被 4 整除:

两个缓冲区类型相同,当且仅当以下任一条件成立:

6.4. 枚举类型

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

枚举项是枚举中的一个 具名值。 每个枚举项都不同于所有 其他枚举项,也不同于所有其他种类的值。

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

注: 枚举项用作模板参数。

注: 无法复制枚举项,也无法为枚举项创建替代名称:

6.4.1. 预声明枚举项

下表列出了 WGSL 中的枚举类型、它们预声明的枚举项,以及这些枚举类型所需的语言扩展。 枚举类型确实存在,但不能在 WGSL 源代码中写出。

预声明枚举项
枚举
(不能在 WGSL 中写出)
预声明枚举项 所需语言扩展
访问 模式 read
write
read_write
地址空间

注: handle 地址 空间永远不会写在 WGSL 源代码中。

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

6.5. 内存视图

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

内存视图包括:

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

6.5.1. 可存储类型

变量中包含的值必须属于 可存储类型。 可存储类型可以具有由 WGSL 定义的显式表示, 如§ 14.4.4 值的内部布局中所述, 也可以是不透明的,例如纹理和采样器。

如果一个类型既是具体类型,又是以下之一,则它是可存储类型:

注: 也就是说,可存储类型是具体的普通类型、 纹理类型、采样器类型和缓冲区类型。

6.5.2. 主机可共享类型

主机可共享类型用于描述主机与 GPU 之间共享的缓冲区内容, 或在主机与 GPU 之间复制而无需格式转换的内容。 用于此目的时,该类型还可以按照§ 14.4 内存布局中的说明应用布局属性。 如§ 7.3 var 声明中所述,uniform 缓冲区和存储缓冲区 变量的存储类型必须是主机可共享的。

如果一个类型既是具体类型,又是以下之一,则它是主机可共享的:

注: 阶段间输入 和输出类型的限制 在§ 13.3.1 阶段间输入和输出接口及 后续各节中说明。 这些类型也有大小,但计算方式不同。

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

6.5.3. 引用和指针类型

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

约束 类型 描述
AS 是一个地址空间,
T 是可存储类型,
AM 是一个访问模式
ref<AS,T,AM> 引用 类型 与以下内存视图集合对应:位于 AS 中、保存 T 类型值的内存位置, 并支持由模式 AM 描述的内存访问。

这里,T 是存储类型。

引用类型不会写在 WGSL 源代码中; 而是用于分析 WGSL 模块。

AS 是一个地址空间,
T 是可存储类型,
AM 是一个访问模式
ptr<AS,T,AM> 指针 类型 与以下内存视图集合对应:位于 AS 中、保存 T 类型值的内存位置, 并支持由模式 AM 描述的内存访问。

这里,T 是存储类型。

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

两个指针类型相同,当且仅当它们具有相同的地址空间、存储类型和访问模式。

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

但是,在 WGSL 源文本中:

示例:指针类型
fn my_function(
  /* 'ptr<function,i32,read_write>' 是一种指针值类型,它引用
     用于保存 'i32' 值的内存,并使用 'function'
     地址空间中的内存位置。这里 'i32' 是存储类型。
     隐含的访问模式是 'read_write'。
     默认值参见“地址空间”一节。 */
  ptr_int: ptr<function,i32>,

  // 'ptr<private,array<f32,50>,read_write>' 是一种指针值类型,它
  // 引用用于保存 50 个 'f32' 类型元素数组的内存,并使用
  // 'private' 地址空间中的内存位置。
  // 这里的存储类型是 'array<f32,50>'。
  // 隐含的访问模式是 'read_write'。
  // 默认值参见“地址空间”一节。
  ptr_array: ptr<private, array<f32, 50>>
) { }

引用类型和指针类型都是内存视图的集合: 特定内存视图与唯一的引用值以及唯一的指针值关联:

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

6.5.4. 重排视图类型

重排视图可用于 访问内存中向量分量的一个子集, 读取时对其进行排列,写入时将其分散写入。

重排视图由swizzle_assignment 语言扩展启用。

重排视图是一个二元组 (p,IndexList),其中:

算法: SwizzleViewRead

生成一个向量,该向量通过选择并可选地重新排列经由指针 p 读取的向量分量而形成。

注: 通过重排视图读取时,会从内存中读取整个 向量,即使某些 向量分量未出现在结果中。

输入:类型为 swizzle<AS,S,N,K> 的重排 视图 (p, IndexList)

输出:类型为 vecK<S> 的向量值 v,即读取 (p, IndexList) 的结果。

过程:

  1. 按照约定,IndexList = « Idx0, ..., IdxK−1 »

  2. 从 p 所引用的内存中读取向量 u。u 的类型为 vecN<S>。

  3. 形成类型为 vecK<S> 的值 v,其中:

    • v 的分量 i 等于 u 的分量 Idxi 的值。 使用索引语法: v[i] = u[Idxi]

算法: SwizzleViewWrite

将 update 向量的分量写入指针 p 所引用内存中的向量的指定分量。

注: 通过重排视图写入时,会写入内存中的整个向量, 即使某些分量值在此过程中未发生变化。

有关重排视图写入如何与一致性分析和 内存模型交互的讨论,请参见下文。

输入:

IndexList 中的所有索引必须彼此不同。 此要求意味着 K ≤ N。

变量:

过程:

  1. 按照约定,IndexList = « Idx0, ..., IdxK−1 »

  2. 将变量 new 初始化为 old_contents。

  3. 对于 0..K-1 中的每个 i:

    • 将 new 的分量 Idxi 设置为 update 的分量 i 的值。 使用索引语法: new[Idxi] = update[i]。

  4. 将 new 写入 p 所引用的内存。

注: 要求所有 Idxi 都彼此不同 可保证步骤 3 最多只更新 new 的每个分量一次。

注: 步骤 2 和步骤 3 可以替换为以下声明式规则:

形成 new,它是类型为 vecN<S> 的向量,其中 new 的分量 i 为:

  • 如果对于 0...K−1 中的某个 j,i = Idxj, 则为 update 的分量 j。

  • 否则,为 old_contents 的分量 i。

重排视图具有重排视图类型:

约束 类型 描述
S 是具体标量类型,
N 和 K 是整数, 使得 vecN<S> 和 vecK<S> 都是有效的向量类型。
ptr<AS,vecN<S>,read_write> 是有效的指针 类型。
swizzle<AS,S,N,K> 重排 视图类型 与以下重排视图 (p, IndexList) 集合对应,其中:
  • p 的类型为 ptr<AS,vecN<S>,read_write>

  • IndexList = « Idx0, ..., IdxK−1 »

  • (p,IndexList) 支持重排视图读取 操作, 生成类型为 vecK<S> 的向量。

  • (p,IndexList) 支持重排视图 写入操作, 写入类型为 vecK<S> 的向量。

重排视图类型不会写在 WGSL 源代码中; 而是用于分析 WGSL 模块。

重排视图通过以下方式形成:

根据这些规则,重排视图中的 IndexList 部分在 着色器 创建时完全确定。

有关详细信息,请参见§ 8.6 重排视图表达式。

示例:通过引用上的重排视图进行读取和写入
fn swizzle_read_and_write() {
  var v: vec4u;

  // 等同于:v.y = 1; v.z = 2;
  // 但是,向量只会从内存读取一次并写入内存一次。
  v.yz = vec2u(1,2);

  // 等同于:let u = vec3(v.a, v.g, v.b);
  // 表达式 v.agb 是类型为 swizzle<function,u32,4,3> 的重排视图。
  // let 声明的有效值类型必须是具体可构造类型
  // 或指针类型。因此,会通过调用重排视图加载规则将重排视图自动转换为 vec3u
  //。
  let u = v.agb;

  // 重排可以链接。
  // 等同于:v.zy = vec2(99,100);
  v.yz.yx = vec2(99,100);

  // 在重排视图上应用单个字母会得到对
  // 底层向量某个标量分量的引用。
  // 等同于:v.z = 99;
  v.yz.y = 99;

  // 对重排视图进行索引会得到对向量某个
  // 分量的引用。其行为如同形成单字母向量访问,
  // 但索引会选择重排名称中的哪个字母。
  v.zy[1] = 50; // 等同于 v.y = 50;
}
示例:通过指针上的重排视图进行读取和写入
// 此示例假定除 swizzle_assignment 外还支持 pointer_composite_access。
requires pointer_composite_access;

fn swizzle_read_and_write_via_pointer(p: ptr<function,vec4u>) {
  // 等同于:(*p).y = 1; (*p).z = 2;
  p.yz = vec2u(1,2);

  // 等同于:let u = vec3((*p).a, (*p).g, (*p).b);
  let u = p.agb;

  // 重排可以链接。
  // 等同于:p.zy = vec2(99,100);
  p.yz.yx = vec2(99,100);

  // 可以从指针构造重排视图。
  // 在重排视图上应用单个字母会得到对
  // 底层向量某个标量分量的引用。
  // 等同于:(*p).z = 99;
  p.yz.y = 99;

  // 对重排视图进行索引会得到对向量某个
  // 分量的引用。其行为如同形成单字母向量访问,
  // 但索引会选择重排名称中的哪个字母。
  p.zy[1] = 50; // 等同于 (*p).y = 50;
}
示例:无效的重排视图
fn invalid_swizzle_cases() {
  var v: vec2u;

  // 无效:不能赋值给重复分量的重排
  v.xx = vec2u(1,2);

  // 无效:'z' 超出目标向量的边界。
  v.xz = vec2u(1,2);
  let u = v.xz;

  // 无效:不得混用 xyzw 和 rgba 字母方案
  v.xr = vec2u(1,2);

  // 无法获取重排视图的地址。
  // 这里,v.xy 是重排视图 (&v,`01`)
  // & 运算符不适用于重排视图,因此类型检查失败。
  let p = &v.xy;
}

出于一致性分析的目的, 重排视图写入 只能影响被更新分量的值一致性。 这促成了完整重排视图和部分重排视图之间的区分。

但是,写入底层向量的所有分量对于内存模型具有重要意义。 两个不同调用并发更新内存中同一向量的不同分量 会引发数据竞争。

示例:对不同向量分量的重排写入可能发生竞争
var<workgroup> w: vec4u;

@compute @workgroup_size(2)
fn this_races(@builtin(local_invocation_index) gid: u32) {
  if (gid == 0) {
    w.xy = vec2u(0,1);  // 写入整个向量,与其他调用发生竞争。
  } else {
    w.zw = vec2u(2,3);  // 写入整个向量,与其他调用发生竞争。
  }
}

6.5.5. 有效和无效的内存引用

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

引用按照§ 6.5.9 形成 引用和指针值中的详细说明形成。 通常,有效 引用通过以下方式形成:

通常,无效内存引用通过以下方式形成:

无效指针通过以下方式 形成:

有效指针是 与有效引用相对应的指针。

有效重排视图 是指其指针值有效的重排视图。 无效重排 视图是指其指针值无效的重排视图。

6.5.6. 来源变量

引用值 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.5.7. 越界访问

访问 无效 内存引用或无效重排视图的操作是越界访问。

越界访问是一种程序缺陷,因为如果按照原样执行,它通常会:

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

注: 错误解释存储类型的一个示例出现在 上一节的示例中。 当 i 为 2 时,表达式 the_particle.velocity[i] 的 类型是 ref<storage,f32,read_write>,这意味着它是一个以 f32 作为 存储类型的内存视图。 但是,这些内存位置分配给了 color_index 成员,因此存储的值实际上属于i32 类型。

注:越界访问会导致动态错误, 因而允许出现许多可能的结果。

这些结果包括但不限于以下情况:

陷阱

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

无效 加载

从无效引用执行的加载 可以返回以下之一:

无效 存储

向无效引用执行的存储可以执行以下之一:

如果无效加载或存储被重定向到共享地址空间中某个变量内部的不同 位置,则可能发生数据竞争。 例如,多个并发执行调用的访问可能被重定向 到数组中的第一个元素。 如果至少有一个访问是写入,并且这些访问没有以其他方式同步, 则结果是数据竞争,因此也是动态错误。

越界访问会使一致性分析的假设失效。 例如,如果某个调用因越界访问而提前终止,则它 无法再参与集体操作。 特别是,对 workgroupBarrier 的调用可能导致着色器挂起, 导数也可能产生无效结果。

6.5.8. 引用和指针的使用场景

引用和指针根据其使用方式加以区分:

以这种方式定义引用,可以简单而惯用地使用变量:

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

  // 'i + 1' 只能匹配 'i' 子表达式类型为 i32 的类型规则。
  // 因此表达式 'i + 1' 的类型为 i32,并且在求值时,'i' 子表达式
  // 求值得到求值时存储在 'i' 的内存位置中的 i32 值
  //。
  let one: i32 = i + 1;

  // 更新 'i' 所引用位置中的值,使其保存值 2。
  i = one + 1;

  // 更新 'i' 所引用位置中的值,使其保存值 5。
  // 右侧的求值发生在赋值生效之前。
  i = i + 3;
}
示例:返回引用会返回通过该引用加载的值
var<private> age: i32;
fn get_age() -> i32 {
  // return 语句中表达式的类型必须是 'i32',因为它
  // 必须与函数声明的返回类型匹配。
  // 'age' 表达式的类型为 ref<private,i32,read_write>。
  // 应用加载规则,因为引用的存储类型与
  // 表达式所需的类型匹配,并且没有其他类型规则适用。
  // 在此上下文中对 'age' 求值,会得到在执行 return 语句时
  // 从 'age' 所引用内存位置加载的
  // i32 值。
  return age;
}

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

以这种方式定义指针,可以实现两个主要使用场景:

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

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

  let delta_position: vec3<f32> = (*active_particle).velocity * system.timestep;
  let current_position: vec3<f32>  = (*active_particle).position;
  (*active_particle).position = delta_position + current_position;
}
示例:使用指针作为形式参数
fn add_one(x: ptr<function,i32>) {
  /* 更新 'x' 的位置,使其包含下一个更大的整数值,
     (或者回绕到最大的负 i32 值)。
     在左侧,一元 '*' 将指针转换为引用,
     随后可以对该引用赋值。默认情况下,它具有 read_write 访问模式。
     /* 在右侧:
        - 一元 '*' 将指针转换为引用,其访问模式为 read_write。
        - 唯一匹配的类型规则是加法 (+),并要求 '*x' 的
          类型为 i32,而这正是 '*x' 的存储类型。因此应用加载规则,
          '*x' 求值得到求值时存储在 '*x' 内存中的值,
          即 i32 值 0。
        - 将 1 加到 0,得到右侧的最终值 1。 */
     将 1 存入 '*x' 的内存中。 */
  *x = *x + 1;
}

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

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

6.5.9. 形成引用和指针值

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

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

示例:从复合引用获得分量引用
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]' 数组索引短语,得到对
    //    前一步引用值所指向矩阵第三列向量
    //    内存的引用。
    //    因此 'm[2]' 表达式的类型为 ref<function,vec2<f32>,read_write>。
    // 'let' 声明的类型为 vec2<f32>,因此声明
    // 语句要求初始化器的类型为 vec2<f32>。
    // 应用加载规则(因为没有其他类型规则可以适用),并且
    // 对初始化器求值会得到在执行声明时
    // 从 'm[2]' 所引用内存位置加载的 vec2<f32> 值
    //。
    let p_m_col2: vec2<f32> = m[2];

    var A: array<i32,5>;
    // 对 'A[4]' 求值时
    // 1. 首先对 'A' 求值,得到对
    //    'A' 变量内存的引用。结果类型为 ref<function,array<i32,5>,read_write>。
    // 2. 然后应用 '[4]' 数组索引短语,得到对
    //    前一步引用值所引用数组的第五个元素
    //    内存的引用。
    //    结果值的类型为 ref<function,i32,read_write>。
    // let 声明要求右侧类型为 i32。
    // 应用加载规则(因为没有其他类型规则可以适用),并且
    // 对初始化器求值会得到在执行声明时
    // 从 'A[4]' 所引用内存位置加载的 i32 值
    //。
    let A_4_value: i32 = A[4];

    // 对 'person.weight' 求值时
    // 1. 首先对 'person' 求值,得到对
    //    在模块作用域声明的 'person' 变量内存的引用。
    //    结果类型为 ref<private,S,read_write>。
    // 2. 然后应用 '.weight' 成员访问短语,得到对
    //    前一步引用值所引用内存中第二个成员
    //    内存的引用。
    //    结果类型为 ref<private,f32,read_write>。
    // let 声明要求右侧类型为 f32。
    // 应用加载规则(因为没有其他类型规则可以适用),并且
    // 对初始化器求值会得到在执行声明时
    // 从 'person.weight' 所引用内存位置加载的 f32 值
    //。
    let person_weight: f32 = person.weight;

    // 或者,也可以使用相同的语法从指针形成引用。

    let uv_ptr = &uv;
    // 在此函数主体的剩余部分中,'uv_ptr' 表示指向
    // 'uv' 底层内存的指针,其类型为
    // ptr<function,vec2<f32>,read_write>。

    // 对赋值的左侧求值:
    //   对 '*uv_ptr' 求值得到引用:
    //   1. 首先对 'uv_ptr' 求值,得到指向
    //      'uv' 变量内存的指针。结果类型为 ptr<function,vec2<f32>,read_write>。
    //   2. 然后应用间接表达式运算符,得到
    //      对 'uv' 内存的引用。
    // 对赋值右侧求值得到 vec2<f32> 值 (1.0, 2.0)。
    // 将值 (1.0, 2.0) 存入 uv 所引用的存储内存位置。
    *uv_ptr = vec2f(1.0, 2.0);

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

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

    let m_ptr = &m;
    // 对 'm_ptr[2]' 求值时:
    // 1. 首先对 'm_ptr' 求值,得到指向
    //    'm' 变量内存的指针。结果类型为 ptr<function,mat3x2<f32>,read_write>。
    // 2. 然后应用 '[2]' 数组索引短语,得到对
    //    前一步引用值所指向第三列向量
    //    内存的引用。
    //    因此 'm[2]' 表达式的类型为 ref<function,vec2<f32>,read_write>。
    // 'let' 声明的类型为 vec2<f32>,因此声明
    // 语句要求初始化器的类型为 vec2<f32>。
    // 应用加载规则(因为没有其他类型规则可以适用),并且
    // 对初始化器求值会得到在执行声明时
    // 从 'm[2]' 所引用内存位置加载的 vec2<f32> 值
    //。
    let p_m_col2: vec2<f32> = m_ptr[2];

    let A_ptr = &A;
    // 对 'A[4]' 求值时
    // 1. 首先对 'A' 求值,得到指向
    //    'A' 变量内存的指针。结果类型为 ptr<function,array<i32,5>,read_write>。
    // 2. 然后应用 '[4]' 数组索引短语,得到对
    //    前一步引用值所引用数组的第五个元素
    //    内存的引用。
    //    结果值的类型为 ref<function,i32,read_write>。
    // let 声明要求右侧类型为 i32。
    // 应用加载规则(因为没有其他类型规则可以适用),并且
    // 对初始化器求值会得到在执行声明时
    // 从 'A[4]' 所引用内存位置加载的 i32 值
    //。
    let A_4_value: i32 = A_ptr[4];

    let person_ptr = &person;
    // 对 'person.weight' 求值时
    // 1. 首先对 'person_ptr' 求值,得到指向
    //    在模块作用域声明的 'person' 变量内存的指针。
    //    结果类型为 ptr<private,S,read_write>。
    // 2. 然后应用 '.weight' 成员访问短语,得到对
    //    前一步引用值所引用内存中第二个成员
    //    内存的引用。
    //    结果类型为 ref<private,f32,read_write>。
    // let 声明要求右侧类型为 f32。
    // 应用加载规则(因为没有其他类型规则可以适用),并且
    // 对初始化器求值会得到在执行声明时
    // 从 'person.weight' 所引用内存位置加载的 f32 值
    //。
    let person_weight: f32 = person_ptr.weight;
}

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

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

示例:从变量获得指针
// 在 private 地址空间中声明一个变量,用于存储 f32 值。
var<private> x: f32;

fn f() {
    // 在 function 地址空间中声明一个变量,用于存储 i32 值。
    var y: i32;

    // 名称 'x' 解析到模块作用域变量 'x',
    // 并具有引用类型 ref<private,f32,read_write>。
    // 应用一元 '&' 运算符将引用转换为指针。
    // 访问模式与原始变量的访问模式相同,因此
    // 完整指定的类型是 ptr<private,f32,read_write>。但 read_write
    // 是 function 地址空间的默认访问模式,因此在这种情况下
    // 无需写出 read_write
    let x_ptr: ptr<private,f32> = &x;

    // 名称 'y' 解析到函数作用域变量 'y',
    // 并具有引用类型 ref<private,i32,read_write>。
    // 应用一元 '&' 运算符将引用转换为指针。
    // 访问模式默认为 'read_write'。
    let y_ptr: ptr<function,i32> = &y;

    // 一个新变量,与模块作用域中声明的变量不同。
    var x: u32;

    // 这里,名称 'x' 解析到上一条语句中声明的函数作用域变量 'x',
    // 并具有类型 ref<function,u32,read_write>。
    // 应用一元 '&' 运算符将引用转换为指针。
    // 访问模式默认为 'read_write'。
    let inner_x_ptr: ptr<function,u32> = &x;
}

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

本节为非规范性内容。

WGSL 中的引用和指针比其他语言中的限制更多。 特别是:

注: 根据上述规则,不可能形成 “悬空”指针, 即不引用某个“存活” 来源变量内存的指针。 内存视图可以是无效 内存引用,但它 绝不会访问 与来源 变量或缓冲区无关的内存 位置。

6.6. 纹理和采样器类型

纹素是一个标量或向量, 用作纹理中可独立访问的最小元素。 单词 texel 是 texture element(纹理元素)的缩写。

纹理是支持 对渲染有用的特殊操作的纹素集合。 在 WGSL 中,这些操作通过纹理内置函数调用。 完整列表参见§ 17.7 纹理内置函数。

WGSL 纹理对应于 WebGPU GPUTexture。

纹理具有以下特性:

纹素格式

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

维数

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

参见 GPUTextureViewDimension。

大小

沿每个维度的网格坐标范围。它是mip 级别的函数。

mip 级别数量

对于采样纹理和深度纹理,mip 级别数量至少为 1; 对于存储 纹理,则等于 1。
Mip 级别 0 包含 纹理的完整大小版本。 每个后续 mip 级别包含上一 mip 级别的过滤版本, 其大小为上一 mip 级别的一半(考虑舍入)。
对纹理采样时,会使用显式或隐式计算的细节级别 选择要从中读取纹素数据的 mip 级别。然后通过 过滤将这些数据组合起来,产生采样值。

数组化

纹理是否数组化。

  • 非数组化纹理是纹素网格。

  • 数组化纹理是纹素网格的同质数组。

数组大小

如果纹理是数组化的,则表示同质网格的数量。

采样数量

如果纹理是多重采样的,则表示采样数量。

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

纹理的物理组织通常针对渲染操作进行优化。 为实现这一点,许多细节对程序员隐藏,包括数据布局、数据类型以及 无法直接在着色语言中表达的内部操作。

因此,着色器无法直接访问纹理变量中的纹素内存。 相反,访问通过一个不透明句柄进行:

这样一来,纹理类型所支持的操作集合 由具有该纹理类型形式 参数的纹理内置函数是否可用决定。

注: 着色器无法更改纹理变量存储的句柄。 也就是说,该变量是只读的,即使它所提供访问的底层纹理 可能是可变的(例如只写的存储纹理)。

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

采样器是一个不透明句柄,用于控制如何从采样 纹理或深度纹理访问纹素。

WGSL 采样器映射到 WebGPU GPUSampler。

纹素访问由采样器的多个属性控制:

寻址模式

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

过滤模式

控制访问哪些纹素以产生最终结果。 过滤可以使用最近的纹素,也可以在多个 纹素之间插值。 可以独立设置多个过滤模式。 参见 WebGPU GPUFilterMode。

LOD 限制

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

比较

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

最大各向异性

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

不能在 WGSL 模块中创建采样器,并且其状态(例如 上面列出的属性)在着色器内部不可变,只能由 WebGPU API 设置。

如果过滤采样器(即使用 插值过滤的任何采样器)与具有不可过滤 格式的纹理一起使用,则会产生管线创建错误。

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

6.6.1. 纹素格式

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

纹素格式具有以下特征:

通道

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

通道 格式

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

WGSL 中的每种纹素格式都对应于同名的 WebGPU GPUTextureFormat。

只有某些纹素格式用于 WGSL 源代码中。 用于定义这些纹素格式的通道格式列于 通道格式表中。 倒数第二列指定从存储的通道位转换为着色器中所用值的方式。 这也称为通道传递函数,即 CTF。 第三列指定从着色器值转换为存储通道位的方式。 这也称为逆通道传递函数,即 ICTF。 最后一列指定该纹素格式所需的语言扩展。

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

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

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

存储纹理的纹素格式表中列出的纹素格式 对应于支持 WebGPU STORAGE_BINDING 用法且至少支持一种访问 模式的 WebGPU 普通颜色格式。 这些纹素格式用于参数化 § 6.6.5 存储纹理类型中定义的存储纹理类型。

当纹素格式不具有全部四个通道时:

下表的最后一列使用通道格式表中针对具体格式的 通道 传递函数。

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

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

6.6.2. 采样纹理类型

采样纹理能够与采样器结合进行访问。 它也可以不使用采样器进行访问。 采样纹理只允许读 访问。

纹素格式是绑定到纹理变量的 GPUTexture 的 format 属性。 WebGPU 会验证纹理、 绑定组布局的 sampleType 与纹理变量的采样类型之间的兼容性。

纹理由采样类型参数化,并且 必须是 f32、i32 或 u32。

类型 维数 数组化
texture_1d<T> 1D 否
texture_2d<T> 2D 否
texture_2d_array<T> 2D 是
texture_3d<T> 3D 否
texture_cube<T> Cube 否
texture_cube_array<T> Cube 是

6.6.3. 多重采样纹理类型

多重采样纹理的 采样数量为 1 或更多。 尽管名称如此,它不能与采样器一起使用。 如果忽略采样索引,它实际上会为每个 逻辑纹素 地址存储相当于多个纹素的数据。

纹素格式是绑定到纹理变量的 GPUTexture 的 format 属性。 WebGPU 会验证纹理、 绑定组布局的 sampleType 与纹理变量的采样类型之间的兼容性。

texture_multisampled_2d 由采样类型参数化,并且 必须是 f32、i32 或 u32。

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

6.6.4. 外部采样纹理类型

外部纹理是一种不透明的 二维浮点采样纹理类型,类似于 texture_2d<f32>, 但可能具有不同的表示。 可以使用 textureLoad 或 textureSampleBaseClampToEdge 内置 函数读取它,这些函数会处理这些不同的表示。

参见 WebGPU § 6.4 GPUExternalTexture。

类型 维数 数组化
texture_external 2D 否

6.6.5. 存储纹理类型

存储纹理支持在不使用采样器的情况下访问 各个纹素值。

存储纹理类型必须由某种 存储纹理的 纹素格式参数化。 纹素格式按照§ 6.6.1 纹素格式中的规定确定转换函数。

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

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

6.6.6. 深度纹理类型

深度纹理能够与sampler_comparison 结合进行访问。 它也可以在不使用采样器的情况下访问。 深度纹理只允许读 访问。

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

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

6.6.7. 采样器类型

采样器通过执行以下操作的组合, 调节对采样纹理 或 深度纹理的访问:

采样器类型包括:

类型 描述
sampler 采样器。调节对采样纹理的访问。
sampler_comparison 比较采样器。 调节对深度纹理的访问。

采样器在 WebGPU API 中创建时进行参数化。 WGSL 模块不能修改它们。

采样器只能由纹理内置函数使用。

sampler
sampler_comparison

6.7. AllTypes 类型

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

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

所有预声明类型和类型生成器的列表参见 § 6.10 预声明类型和类型生成器摘要。

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

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

其动机案例是模板参数,它在不同上下文中可以表示 多种事物, 包括类型、枚举项或普通值。 特别是,template_arg_expression 语法规则会展开为 expression 语法 非终结符。

6.8. 类型别名

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

当类型 T 被定义为结构体类型 S 的类型 别名时, S 各成员的所有属性,包括特性,都会继承到 T 的成员。

注: 如果被设置别名的类型支持值构造器 内置函数, 那么当类型别名位于作用域内,且调用该函数在其他方面有效时, 可以通过别名而不是原始 类型说明符名称调用这些函数。

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.9. 类型说明符语法

参见§ 8.18 类型表达式。

type_specifier :

template_elaborated_ident

template_elaborated_ident :

ident template_list ?

注: 表达式也可以表示类型,方式是通过primary_expression 语法规则展开为template_elaborated_ident, 以及通过括号化实现。

6.10. 预声明类型和类型生成器摘要

可以在 WGSL 源代码中写出的预声明类型包括:

WGSL 还为 frexp、 modf 和 atomicCompareExchangeWeak 内置 函数预声明返回类型。 但是,它们不能在 WGSL 源代码中写出。

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

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

注: 这些还用于值 构造器表达式 以创建矩阵。

mat2x3
mat2x4
mat3x2
mat3x3
mat3x4
mat4x2
mat4x3
mat4x4
ptr 参见§ 6.5.3 引用和指针类型
texture_1d 参见§ 6.6.2 采样纹理类型
texture_2d
texture_2d_array
texture_3d
texture_cube
texture_cube_array
texture_multisampled_2d 参见§ 6.6.3 多重采样纹理类型
texture_storage_1d 参见§ 6.6.5 存储纹理类型
texture_storage_2d
texture_storage_2d_array
texture_storage_3d
vec2 参见§ 6.2.6 向量类型,其中还列出了 向量类型的预声明别名。

注: 这些还用于值 构造器表达式 以创建向量。

vec3
vec4

7. 变量和值声明

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

值声明 为一个值创建名称,并且该 值一旦声明后就是不可变的。 值声明共有四种:const、override、let 和 形式参数声明, 下文将进一步说明(参见§ 7.2 值声明)。

变量 声明为用于存储值的内存位置 创建名称;如果变量具有 read_write 访问 模式,则存储在其中的值可以更新。 变量声明只有一种,即 var,但它具有 地址空间和访问模式的多种组合选项, 具体说明见下文(参见§ 7.3 var 声明)。

注: 值声明没有与之关联的 内存位置。例如,没有任何 WGSL 表达式可以形成指向该值的指针。

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

出现在函数定义内部的声明位于函数作用域。 该名称可从声明之后紧接着的语句开始使用, 直到直接包含该声明的由花括号界定的语句列表末尾。 函数作用域声明是一个动态上下文。

注:
变量和值声明具有相似的整体语法。以下非规范性 示例展示变量和值声明的一般形式,其中 [...] 表示 可选部分,...* 表示前一项重复零次或多次,而 ...+ 表示前一项重复一次或 多次。有关具体语法规则,请参见各元素对应的小节。
// 特定值声明。
             const    name [: type]  = initializer ;
[attribute]* override name [: type] [= initializer];
             let      name [: type]  = initializer ;

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

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

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

每个此类声明必须具有显式 指定的类型或初始化器。 可以同时指定类型和初始化器。 每个此类声明都会确定关联数据值的类型,该类型称为声明的有效值类型。 声明的有效值类型为:

每种值或变量声明都可以对 初始化器表达式的形式(如果存在)以及 有效值类型施加额外约束。

变量和值声明特性摘要。
声明 可变性 作用域 有效值类型1 初始化器支持 初始化器表达式2 资源接口的一部分
const 不可变 模块或函数 可构造(具体或抽象) 必需 const 表达式 否
override 不可变 模块 具体标量 可选3 const 表达式或override 表达式 否4
let 不可变 函数 具体可构造类型或 指针类型。 此外,如果支持texture_and_sampler_let 特性, 则还可以是纹理或采样器类型。 必需 const 表达式、override 表达式或 运行时 表达式 否
var<storage, read>
var<storage>
不可变 模块 具体主机可共享 不允许 是。
存储 缓冲区
var<storage, read_write>5,6 可变 模块 具体主机可共享 不允许 是。
存储 缓冲区
var<uniform> 不可变 模块 具体可构造主机可共享类型 或固定大小 缓冲区 不允许 是。
uniform 缓冲区
var<immediate> 不可变 模块 具体可构造主机可共享类型, 不包括数组和包含数组成员的结构体 不允许 是。
即时 数据
var5 不可变7 模块 纹理 不允许 是。
纹理 资源
var 不可变 模块 采样器 不允许 是。
采样器 资源
var<workgroup>6,8 可变 模块 具有固定 占用空间的具体普通类型,或固定大小缓冲区9 不允许10 否
var<private> 可变 模块 具体可构造 可选10 const 表达式或override 表达式 否
var<function>
var
可变 函数 具体可构造 可选10 const 表达式、override 表达式或 运行时 表达式 否
  1. 只有 const 声明可以是抽象类型,并且仅当 未显式指定类型时才可以。

  2. 表达式的类型必须能够可行地 转换为有效值类型。

  3. 如果未指定初始化器,则必须在管线创建时提供一个值。

  4. 覆盖声明是着色器接口的一部分, 但不是 绑定资源。

  5. 存储缓冲区和 存储 纹理的访问模式若不是 read,则禁止对其进行以下阶段的静态 访问:顶点着色器阶段。 请参阅 WebGPU createBindGroupLayout()。

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

  7. 存储纹理中具有write 或 read_write 访问模式的数据是可变的, 但只能通过 textureStore 内置函数进行 修改。 变量本身不能被修改。

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

  9. 元素数量(对于最外层的 数组而言)或 缓冲区的大小可以是覆盖表达式。

  10. 如果没有初始化器,则变量会被默认 初始化。

7.1. 变量与值

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

使用变量通常比使用值声明的开销更高, 因为使用变量需要额外操作,以对与变量关联的内存位置进行读取或 写入。

一般来说,作者应按以下 顺序优先使用声明,最优先的选项列在最前面:

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

7.2. 值声明

当标识符解析到值声明时,该标识符 表示该值。

WGSL 提供多种值声明。 每种声明的值都会在 着色器生命周期中的不同时间点确定。 不同类型的值声明及其值确定的时间为:

注: 形式参数在§ 11 函数中说明。

7.2.1. const 声明

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

注: 由于抽象数值类型不能在 WGSL 中 写出,因此只能 通过类型推断使用它们。

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

7.2.2. override 声明

override 声明为一个 管线可重写常量值指定名称。 override 声明必须仅在 模块作用域声明。 管线可重写常量的值在 管线创建 时确定。 如果已指定,该值就是 WebGPU 管线创建方法提供的值; 否则就是其初始化器表达式的具体化值。 override 声明的有效值类型必须是具体 标量类型。

初始化器表达式是可选的。 如果存在,它必须是override 表达式,并表示管线可重写 常量默认值。 如果未指定初始化器,并且在管线创建时未提供值, 则会产生管线创建错误。

如果声明应用了id 属性,则字面量 操作数称为管线常量 ID,并且必须是 0 到 65535(含)之间的唯一整数。 也就是说,两个 override 声明不得使用相同的管线常量 ID。

应用程序可以在管线创建 时为 override 声明指定自己的值。 管线创建 API 接受从可重写常量到 该常量类型值的映射。 常量由管线可重写常量 标识符字符串标识;如果指定了管线常量 ID,则该字符串是其十进制表示, 否则是该常量声明的名称。

示例:可由管线重写的模块常量
@id(0)    override has_point_light: bool = true;  // 算法控制
@id(1200) override specular_param: f32 = 2.3;     // 数值控制
@id(1300) override gain: f32;                     // 必须被重写
          override width: f32 = 0.0;              // 在 API 层使用
                                                  // 名称 "width" 指定。
          override depth: f32;                    // 在 API 层使用
                                                  // 名称 "depth" 指定。
                                                  // 必须被重写。
          override height = 2 * depth;            // 默认值
                                                  // (如果未在 API 层设置)
                                                  // 取决于另一个
                                                  // 可重写常量。

7.2.3. let 声明

let 声明 为一个值指定名称,该值会在运行时每次执行该语句时 固定下来。 let 声明必须仅在函数作用域声明,因此它是 一个动态上下文。 let 声明必须具有初始化器表达式。 该值是初始化器的具体化值。 let 声明的有效值类型必须是具体可构造类型或 指针类型。 如果支持texture_and_sampler_let 特性, 则有效值类型也可以是纹理或采样器类型。

注: 由于缓冲区类型不是可构造的,因此只有指向 缓冲区的指针才能用于 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.12 变量标识符表达式。

如果在程序源代码中指定了变量声明的地址空间或访问模式, 则它们写作 var 关键字之后的模板列表:

private、storage、 uniform、workgroup 和handle 地址空间中的变量必须仅 在模块作用域声明,而 function 地址空间中的变量必须仅 在函数作用域声明。 除 handle 和 function 之外,所有地址空间都必须指定地址空间。 handle 地址空间不得指定。 指定 function 地址空间是可选的。

访问模式始终具有 默认值,并且除 storage 地址空间中的变量之外,不得在 WGSL 源代码中指定访问模式。 参见§ 14.3 地址空间。

uniform 地址空间中的变量是uniform 缓冲区变量。 其存储类型必须是主机可共享可构造类型或 固定大小缓冲区 类型,并且必须满足 地址空间布局约束。

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

immediate 地址空间中的变量是即时数据变量。 其存储类型必须是主机可共享可构造类型, 不包括数组和包含数组成员的结构体。 每个入口点必须静态访问 最多一个即时数据变量。 即时变量的值通过由 WebGPU API 命令编码器记录的 setImmediates 命令设置, 并在着色器执行期间保持不变。 变量大小受管线布局的immediateSize 配置限制。

AccessibleSlots 不包括结构体成员之间以及结构体末尾的填充字节。 在着色器访问即时数据变量之前,只需通过 WebGPU API 初始化可访问的槽位。

纹理资源是 有效值类型为纹理类型的变量。 它在模块作用域声明。 它保存一个不透明句柄,用于 访问纹理中底层的纹素网格。 句柄本身位于handle 地址空间,并且始终只读。 在许多情况下,底层纹素是只读的,我们称纹理变量不可变。 对于只写存储纹理和读写存储 纹理,底层纹素是可变的,因此按照约定 我们称纹理变量可变。

采样器资源是 有效值类型为采样器类型的变量。 它在模块作用域声明, 存在于handle 地址空间, 并且不可变。

如§ 13.3.2 资源接口中所述,uniform 缓冲区、存储 缓冲区、 纹理和采样器构成着色器的资源接口。

变量的生命周期是 着色器执行期间内存位置与变量保持关联的时间段。 模块作用域 变量的生命周期是整个 着色器阶段的执行期间。 对于每个调用,private 和function 地址空间中的变量都有独立版本。 函数作用域变量是 一个动态上下文。 函数作用域变量的生命周期由其作用域决定:

两个资源变量的内存位置可以重叠, 但如果其中任一变量可变,则会产生动态错误。 其他生命周期重叠的变量不会 具有重叠的内存位置。 当变量的生命周期结束时,其内存可以用于另一个变量。

注: WGSL 确保变量的内容只能在 变量生命周期内被观察到。

当private、function 或workgroup 地址空间中的变量被创建时,它将 具有初始值。 如果未指定初始化器,则初始值为默认 初始值。 初始值按如下方式计算:

其他地址空间中的变量是通过绘制命令或分派命令中的绑定设置的资源。

考虑以下 WGSL 片段:

示例:变量初始值
var i: i32;         // 初始值为 0。不推荐的风格。
loop {
  var twice: i32 = 2 * i;   // 每次迭代都会重新求值。
  i++;
  if i == 5 { break; }
}
循环体将执行六次。 变量 i 将依次取值 0、1、2、3、4、5,而变量 twice 将依次取值 0、2、4、6、8。

考虑以下 WGSL 片段:

示例:多次读取变量
var x: f32 = 1.0;
let y = x * x + x + 1;
由于 x 是变量,对它的所有访问都会转换为加载和存储操作。 但是,预期浏览器或驱动程序会优化该中间表示, 以消除冗余加载。
示例:模块作用域变量声明
var<private> decibels: f32;
var<workgroup> worklist: array<i32,10>;

struct Params {
  specular: f32,
  count: i32
}

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

// 一个用于读取和写入的存储缓冲区
@group(0) @binding(0)
var<storage,read_write> pbuf: array<vec2<f32>>;

// 纹理和采样器始终位于 "handle" 空间。
@group(0) @binding(1)
var filter_params: sampler;
示例:缓冲区的访问模式
// 存储缓冲区
@group(0) @binding(0)
var<storage,read> buf1: Buffer;       // 可以读取,不能写入。
@group(0) @binding(0)
var<storage> buf2: Buffer;            // 可以读取,不能写入。
@group(0) @binding(1)
var<storage,read_write> buf3: Buffer; // 可以读取和写入。

struct ParamsTable {weight: f32}

// Uniform 缓冲区。始终只读,并具有更严格的布局规则。
@group(0) @binding(2)
var<uniform> params: ParamsTable;     // 可以读取,不能写入。
示例:函数作用域变量和常量
fn f() {
   var<function> count: u32;  // function 地址空间中的变量。
   var delta: i32;            // function 地址空间中的另一个变量。
   var sum: f32 = 0.0;        // 带有初始化器的 function 地址空间变量。
   var pi = 3.14159;          // 从初始化器推断 f32 存储类型。
}

7.4. 变量和值声明语法摘要

variable_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 表达式和 override 表达式会在 GPU 上执行之前求值,因此最终 GPU 代码中只需要 表达式的计算结果。 此外,由于const 表达式在着色器创建时求值,因此它们可以 用于比override 表达式更多的场景,例如为函数作用域 变量中的数组确定大小。 运行时 表达式是既不是 const 表达式也不是 override 表达式的表达式。 运行时表达式在着色器执行期间由 GPU 计算。 虽然运行时表达式可用于较少的语法元素,但它们可以 从更大范围的表达式计算得到,例如其他运行时值。

8.1. 提前求值表达式

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

8.1.1. const 表达式

能够在着色器创建 时求值的表达式称为const 表达式。 如果表达式的所有标识符都解析到以下对象,则该表达式是 const 表达式:

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

注: 抽象类型可以是 const 表达式的推断类型。

当且仅当满足以下条件之一时,const 表达式 E 将被求值:

注: 求值规则意味着短路 运算符 && 和 || 会阻止对其右侧 子表达式求值,除非存在一个子表达式,必须对其求值才能确定 静态类型。

const 表达式可以由实现 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 表达式。 如果表达式的所有标识符都解析到以下对象,则该表达式是 override 表达式:

注: 所有const 表达式也都是 override 表达式。

除 const 表达式之外的 override 表达式只会在 管线 创建期间验证或求值,并且只会在任何 API 提供的 值替换override 声明之后进行。 如果override 声明的值通过 API 被替换,则其 初始化器表达式(如果存在)不会被求值。 否则,当且仅当满足以下条件时,override 表达式 E 将被 求值:

注: 并非所有 override 表达式都可用作 override 声明的初始化器,因为此类初始化器必须解析 为具体标量类型。

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

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

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

示例:由 override 表达式导致的着色器创建错误
override a : i32 = 0;
override b = a / 0; // 着色器创建错误,
                    // 无论是否尝试重写 c
示例:由 override 表达式导致的管线创建错误
override a : i32 = 0;
override b = 1 / a;

// b 是 frag1 着色器的一部分。将 frag1 编译进管线时
// 可能出现以下情况:
// * 如果 b 被重写,则不会发生错误。
// * 如果 a 被重写为非零值,则不会发生错误。
// * 如果 a 为 0 且 b 未被重写,则会发生管线创建错误。
@fragment
fn frag1() {
  _ = b;
}

// b 不是 frag2 着色器的一部分。将 frag2 编译进管线时
// 即使 b 未被重写且 a 的值为 0,也不会发生错误。
@fragment
fn frag2() {
}

8.2. 不确定值

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

在这种情况下,该次求值的结果是 表达式静态类型的不确定 值, 即实现从该静态类型中任意选择的某个值。

表达式在每个唯一的动态上下文中求值时,都可能产生不同的值。 例如,如果每次循环迭代都会进行一次求值,则每次循环迭代 都可能计算出不同的值。

注: 如果类型是浮点类型,并且 实现支持 NaN 值,那么 运行时产生的不确定值可以是 NaN 值。

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

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

      // 此时 'extract' 可以是 i32 类型的任意值。

      // 保存它以供稍后使用。
      extracted_values[i] = extract;

      if extract == extract {
         // 这里始终会执行
      }
      if extract < 2 {
         // 这里可能会执行,也可能不会执行。
         // 即使原始向量分量是 0 和 1,
         // 提取出的值也可能不是其中任何一个。
      }
   }
   if extracted_values[0] == extracted_values[1] {
      // 这里可能会执行,也可能不会执行。
   }
}

fn float_fun(runtime_index: u32) {
   const v = vec2<f32>(0,1); // 一个浮点值向量

   // 与前面的示例一样,'float_extract' 是一个不确定值。
   // 由于它是浮点类型,因此它可能是 NaN。
   let float_extract: f32 = v[runtime_index+5];

   if float_extract == float_extract {
      // 这里*可能不会*执行,因为:
      //  -  'float_extract' 可能是 NaN,并且
      //  -  NaN 永远不等于任何其他浮点数,
      //     即使另一个也是 NaN。
   }
}

8.3. 字面量值表达式

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

8.4. 括号表达式

括号表达式类型规则
前置条件 结论 描述
e : T ( e ) : T 求值得到 e。
使用括号将表达式与周围文本隔离。

8.5. 复合值分解表达式

本节描述用于以下目的的表达式:

在以下讨论中,复合值、指向复合值的内存视图或重排视图 称为基。

有两种形成此类表达式的方法:

具名 分量表达式

基 B 的表达式后跟一个句点 '.' (U+002D),然后是分量名称。

  • 当 B 是向量或结构体类型、指向向量或结构体 类型的内存视图,或重排视图时,支持这种形式。

  • 有效名称取决于 B 的类型。

索引 表达式

基的表达式后跟 '['(U+005B),然后是 索引表达式,再后跟 ']'(U+005D)。

从语法上看,这两种形式体现为对component_or_swizzle_specifier 语法规则的使用。

索引表达式的索引值 i 是一个范围内索引,如果 0 ≤ i < N,其中 N 是复合类型的分量(元素)数量:

当索引值不是范围内索引时,它是一个越界索引。 越界索引通常是编程缺陷,并且通常会导致错误。 详情见下文。

此外,向量类型支持重排语法,用于从另一个向量的分量创建新的向量值。

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 为 3 或 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 是抽象类型
i 是const 表达式
e[i]: T 选择向量的第 i 个分量
第一个分量位于索引 i=0。

如果 i 超出范围 [0,N-1],则会产生着色器创建错误。

注: 当抽象向量值 e 由一个不是const 表达式的表达式建立索引时,向量会在应用索引之前 被具体化。

8.5.1.2. 向量多分量选择

本节中的表达式将多字母重排应用于内部表达式:

注: 如果不支持swizzle_assignment 语言扩展, 则多字母重排不能出现在 赋值的左侧: 赋值的左侧必须是引用类型, 但在没有该扩展时,多字母重排表达式始终产生向量类型的值。

向量分解:多分量选择
前置条件 结论 描述
e: vecN<T>
I 是字母 x、y、z 或 w
J 是字母 x、y、z 或 w
e.IJ: vec2<T>
计算一个双分量向量,其第一个分量为 e.I,第二个 分量为 e.J。
字母 z 仅当 N 为 3 或 4 时有效。
字母 w 仅当 N 为 4 时有效。
e: vecN<T>
I 是字母 r、g、b 或 a
J 是字母 r、g、b 或 a
e.IJ: vec2<T>
计算一个双分量向量,其第一个分量为 e.I,第二个 分量为 e.J。
字母 b 仅当 N 为 3 或 4 时有效。
字母 a 仅当 N 为 4 时有效。
e: vecN<T>
I 是字母 x、y、z 或 w
J 是字母 x、y、z 或 w
K 是字母 x、y、z 或 w
e.IJK: vec3<T>
计算一个三分量向量,其第一个分量为 e.I,第二个 分量为 e.J,第三个分量为 e.K。
字母 z 仅当 N 为 3 或 4 时有效。
字母 w 仅当 N 为 4 时有效。
e: vecN<T>
I 是字母 r、g、b 或 a
J 是字母 r、g、b 或 a
K 是字母 r、g、b 或 a
e.IJK: vec3<T>
计算一个三分量向量,其第一个分量为 e.I,第二个 分量为 e.J,第三个分量为 e.K。
字母 b 仅当 N 为 3 或 4 时有效。
字母 a 仅当 N 为 4 时有效。
e: vecN<T>
I 是字母 x、y、z 或 w
J 是字母 x、y、z 或 w
K 是字母 x、y、z 或 w
L 是字母 x、y、z 或 w
e.IJKL: vec4<T>
计算一个四分量向量,其第一个分量为 e.I,第二个 分量为 e.J,第三个分量为 e.K,第四个 分量为 e.L。
字母 z 仅当 N 为 3 或 4 时有效。
字母 w 仅当 N 为 4 时有效。
e: vecN<T>
I 是字母 r、g、b 或 a
J 是字母 r、g、b 或 a
K 是字母 r、g、b 或 a
L 是字母 r、g、b 或 a
e.IJKL: vec4<T>
计算一个四分量向量,其第一个分量为 e.I,第二个 分量为 e.J,第三个分量为 e.K,第四个 分量为 e.L。
字母 b 仅当 N 为 3 或 4 时有效。
字母 a 仅当 N 为 4 时有效。
向量内存视图分解:不支持重排赋值时的多分量选择
前置条件 结论 描述
不支持swizzle_assignment

pr: ref<AS,vecN<T,AM>> 或 ptr<AS,vecN<T,AM>>
I 是字母 x、y、z 或 w
J 是字母 x、y、z 或 w
AM 是 read 或 read_write

pr.IJ: vec2<T>
计算 e.IJ,其中 e 是求值时存储在 pr 所引用内存位置中的向量。

字母 z 仅当 N 为 3 或 4 时有效。
字母 w 仅当 N 为 4 时有效。

不支持swizzle_assignment

pr: ref<AS,vecN<T,AM>> 或 ptr<AS,vecN<T,AM>>
I 是字母 r、g、b 或 a
J 是字母 r、g、b 或 a
AM 是 read 或 read_write

pr.IJ: vec2<T>
计算 e.IJ,其中 e 是求值时存储在 pr 所引用内存位置中的向量。

字母 b 仅当 N 为 3 或 4 时有效。
字母 a 仅当 N 为 4 时有效。

不支持swizzle_assignment

pr: ref<AS,vecN<T,AM>> 或 ptr<AS,vecN<T,AM>>
I 是字母 x、y、z 或 w
J 是字母 x、y、z 或 w
K 是字母 x、y、z 或 w
AM 是 read 或 read_write

pr.IJK: vec3<T>
计算 e.IJK,其中 e 是求值时 存储在 pr 所引用内存位置中的向量。

字母 z 仅当 N 为 3 或 4 时有效。
字母 w 仅当 N 为 4 时有效。

不支持swizzle_assignment

pr: ref<AS,vecN<T,AM>> 或 ptr<AS,vecN<T,AM>>
I 是字母 r、g、b 或 a
J 是字母 r、g、b 或 a
K 是字母 r、g、b 或 a
AM 是 read 或 read_write

pr.IJK: vec3<T>
计算 e.IJK,其中 e 是求值时 存储在 pr 所引用内存位置中的向量。

字母 b 仅当 N 为 3 或 4 时有效。
字母 a 仅当 N 为 4 时有效。

不支持swizzle_assignment

pr: ref<AS,vecN<T,AM>> 或 ptr<AS,vecN<T,AM>>
I 是字母 x、y、z 或 w
J 是字母 x、y、z 或 w
K 是字母 x、y、z 或 w
L 是字母 x、y、z 或 w
AM 是 read 或 read_write

pr.IJKL: vec4<T>
计算 e.IJKL,其中 e 是 求值时存储在 pr 所引用内存位置中的向量。

字母 z 仅当 N 为 3 或 4 时有效。
字母 w 仅当 N 为 4 时有效。

不支持swizzle_assignment

pr: ref<AS,vecN<T,AM>> 或 ptr<AS,vecN<T,AM>>
I 是字母 r、g、b 或 a
J 是字母 r、g、b 或 a
K 是字母 r、g、b 或 a
L 是字母 r、g、b 或 a
AM 是 read 或 read_write

pr.IJKL: vec4<T>
计算 e.IJKL,其中 e 是 求值时存储在 pr 所引用内存位置中的向量。

字母 b 仅当 N 为 3 或 4 时有效。
字母 a 仅当 N 为 4 时有效。

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 的来源变量相同。

从重排视图获取分量引用
前置条件 结论 描述
sv : swizzle<AS,S,N,K>
sv.x : ref<AS,S,read_write>
sv.r : ref<AS,S,read_write>
令 sv 为重排视图 (p, « Idx0,...,IdxK−1 »)。

结果是对指针 p 的内存视图中 索引为 Idx0 的分量的引用。

结果引用的来源变量 与 sv 的来源变量相同。

sv : swizzle<AS,S,N,K>
sv.y : ref<AS,S,read_write>
sv.g : ref<AS,S,read_write>
令 sv 为重排视图 (p, « Idx0,...,IdxK−1 »)。

结果是对指针 p 的内存视图中 索引为 Idx1 的分量的引用。

结果引用的来源变量 与 sv 的来源变量相同。

sv : swizzle<AS,S,N,K>
其中 K ≥ 3
sv.z : ref<AS,S,read_write>
sv.b : ref<AS,S,read_write>
令 sv 为重排视图 (p, « Idx0,...,IdxK−1 »)。

结果是对指针 p 的内存视图中 索引为 Idx2 的分量的引用。

结果引用的来源变量 与 sv 的来源变量相同。

sv : swizzle<AS,S,N,K>
其中 K = 4
sv.w : ref<AS,S,read_write>
sv.a : ref<AS,S,read_write>
令 sv 为重排视图 (p, « Idx0,...,IdxK−1 »)。

结果是对指针 p 的内存视图中 索引为 Idx3 的分量的引用。

结果引用的来源变量 与 sv 的来源变量相同。

sv : swizzle<AS,S,N,K>
i : i32 或 u32
sv[i] : ref<AS,S,read_write>
令 sv 为重排视图 (p, « Idx0,...,IdxK−1 »)。

如果 i 位于 [0, K),则该表达式是引用

(*p)[ Idxi ]

注: 如果 sv 是无效重排视图,则结果是无效内存 引用。

如果 i 位于 [0,K) 之外:

结果引用的来源变量 与 sv 的来源变量相同。

8.5.2. 矩阵访问表达式

列向量提取
前置条件 结论 描述
e: matCxR<T>
i: i32 或 u32
T 是具体类型
e[i]: vecR<T> 结果是 e 的第 i 个列向量。

如果 i 超出范围 [0,C-1]:

e: matCxR<T>
i: i32 或 u32
T 是抽象类型
i 是const 表达式
e[i]: vecR<T> 结果是 e 的第 i 个列向量。

如果 i 超出范围 [0,C-1],则会产生着色器创建错误。

注: 当抽象矩阵值 e 由一个不是const 表达式的表达式建立索引时,矩阵会在应用索引之前 被具体化。

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

如果 i 超出范围 [0,C-1]:

结果引用的来源变量 与 r 的来源变量相同。

8.5.3. 数组访问表达式

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

如果 i 超出范围 [0,N-1]:

e: array<T,N>
i: i32 或 u32
T 是抽象类型
i 是const 表达式
e[i] : T 结果是数组值 e 的第 i 个元素的值。

如果 i 超出范围 [0,N-1],则会产生着色器创建错误。

注: 当抽象数组值 e 由一个不是const 表达式的表达式建立索引时,数组会在应用索引之前 被具体化。

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

如果 i 超出范围 [0,N-1]:

结果引用的来源变量 与 r 的来源变量相同。

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

如果运行时数组具有 N 个元素,并且 i 超出范围 [0,N-1],则该表达式求值得到无效内存 引用。

如果 i 是有符号整数,且 i 小于 0:

结果引用的来源变量 与 r 的来源变量相同。

8.5.4. 结构体访问表达式

结构体成员提取
前置条件 结论 描述
S 是结构体类型
M 是 S 中某个成员的标识符名称,该成员类型为 T
e: S
e.M: T 结果是结构体值 e 中名称为 M 的成员值。
从指向结构体的内存视图获取结构体成员引用
前置条件 结论 描述
S 是结构体类型
M 是 S 中某个成员的标识符名称,该成员类型为 T
r: ref<AS,S,AM> 或
ptr<AS,S,AM>
r.M: ref<AS,T,AM> 给定指向结构体的内存视图,结果是对标识符名称为 M 的结构体成员的引用。
结果引用的来源变量 与 r 的来源变量相同。

8.6. 重排视图表达式

重排视图表达式由 swizzle_assignment 语言扩展启用。

重排视图通过以下方式形成:

根据这些规则,重排视图的索引列表部分在 着色器创建时完全确定。

在本节中,重排索引函数 SI 定义为:

如果满足以下条件,我们称重排名称 Z 表示 K 个索引 « Idx0, ..., IdxK−1 »:

注: 例如,重排 wzyx 表示四个索引 «3, 2, 1, 0»,重排 rb 表示两个索引 «0, 2»。 重排 rgbz 不表示任何索引序列,因为它混用了 rgba 和 xyzw 字母。

从引用形成重排视图
前置条件 结论 描述
支持 swizzle_assignment

r: ref<AS,T,read_write>
T 是 vecN<S>
重排名称 Z 表示 K 个索引 I = « Idx0, ..., IdxK−1 »
K 属于 {2,3,4}

r.Z : swizzle<AS,S,N,K> 结果是重排 视图 (&r, I)。

I 中的每个索引必须小于 N。

如果 r 是无效内存引用,则结果是 无效重排视图。

从指针形成重排视图
前置条件 结论 描述
支持 swizzle_assignment
支持 pointer_composite_access

p: ptr<AS,T,read_write>
T 是 vecN<S>
重排名称 Z 表示 K 个索引 I = « Idx0, ..., IdxK−1 »
K 属于 {2,3,4}

p.Z : swizzle<AS,S,N,K> 结果是重排 视图 (p, I)

I 中的每个索引必须小于 N。

如果 p 是无效指针,则结果是无效重排视图。

从重排视图形成重排视图
前置条件 结论 描述
sv: swizzle<AS,S,N,Kin>
重排名称 Z 表示 KZ 个索引 IZ = « IZ [0], ..., IZ [KZ −1] »
KZ 属于 {2,3,4}
sv.Z : swizzle< AS,S,N,KZ > 令 sv 为重排视图 (p, Iin)。 根据定义,序列 Iin 有 Kin 个项。

令 Iout 为序列
« Iin [IZ [0]], ..., Iin [IZ [KZ -1]] »。

表达式结果是重排视图 (p, Iout)。

IZ 中的每个索引必须小于 Kin。

如果 sv 是无效重排视图,则结果是无效重排视图。

8.7. 逻辑表达式

一元逻辑操作
前置条件 结论 描述
e: T
T 是 bool 或 vecN<bool>
!e: T 逻辑非。 当 e 为 false 时结果为 true,当 e 为 true 时结果为 false。 当 T 是向量时逐分量执行。
二元逻辑表达式
前置条件 结论 描述
e1: bool
e2: bool
e1 || e2: bool 短路“或”。如果 e1 或 e2 任一个为 true,则产生 true; 仅当 e1 为 false 时才对 e2 求值。
e1: bool
e2: bool
e1 && e2: bool 短路“与”。如果 e1 和 e2 都为 true,则产生 true; 仅当 e1 为 true 时才对 e2 求值。
e1: T
e2: T
T 是 bool 或 vecN<bool>
e1 | e2: T 逻辑“或”。当 T 是向量时逐分量执行。会对 e1 和 e2 两者都求值。
e1: T
e2: T
T 是 bool 或 vecN<bool>
e1 & e2: T 逻辑“与”。当 T 是向量时逐分量执行。会对 e1 和 e2 两者都求值。

8.8. 算术表达式

一元算术表达式
前置条件 结论 描述
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 是有符号整数标量类型,则对 e1 和 e2 各求值一次,并求值得到:

注: 当结果非零时,其符号与 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
矩阵算术
前置条件 结论 语义
e1, e2: matCxR<T>
T 是 AbstractFloat、f32 或 f16
e1 + e2: matCxR<T>
矩阵加法:结果逐分量计算,结果的第 i 列为 e1[i] + e2[i]
e1 - e2: matCxR<T> 矩阵减法:结果逐分量计算,结果的第 i 列为 e1[i] - e2[i]
m: matCxR<T>
s: T
T 是 AbstractFloat、f32 或 f16
m * s: matCxR<T>
逐分量 缩放:(m * s)[i][j] 为 m[i][j] * s
s * m: matCxR<T>
逐分量 缩放:(s * m)[i][j] 为 m[i][j] * s
m: matCxR<T>
v: vecC<T>
T 是 AbstractFloat、f32 或 f16
m * v: vecR<T>
线性代数矩阵-列向量乘积: 结果的分量 i 为 dot(transpose(m)[i],v)
m: matCxR<T>
v: vecR<T>
T 是 AbstractFloat、f32 或 f16
v * m: vecC<T>
线性代数行向量-矩阵乘积:
transpose(transpose(m) * transpose(v))
e1: matKxR<T>
e2: matCxK<T>
T 是 AbstractFloat、f32 或 f16
e1 * e2: matCxR<T>
线性代数矩阵乘积。

8.9. 比较表达式

比较
前置条件 结论 描述
e1: T
e2: T
S 是 AbstractInt、AbstractFloat、bool、i32、u32、f32 或 f16
T 是 S 或 vecN<S>
如果 T 是向量,则 TB 是 vecN<bool>,
否则 TB 是 bool
e1 == e2: TB 相等。当 T 是向量时逐分量执行。
e1: T
e2: T
S 是 AbstractInt、AbstractFloat、bool、i32、u32、f32 或 f16
T 是 S 或 vecN<S>
如果 T 是向量,则 TB 是 vecN<bool>,
否则 TB 是 bool
e1 != e2: TB 不相等。当 T 是向量时逐分量执行。
e1: T
e2: T
S 是 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 是 S 或 vecN<S>
如果 T 是向量,则 TB 是 vecN<bool>,
否则 TB 是 bool
e1 < e2: TB 小于。当 T 是向量时逐分量执行。
e1: T
e2: T
S 是 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 是 S 或 vecN<S>
如果 T 是向量,则 TB 是 vecN<bool>,
否则 TB 是 bool
e1 <= e2: TB 小于或等于。当 T 是向量时逐分量执行。
e1: T
e2: T
S 是 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 是 S 或 vecN<S>
如果 T 是向量,则 TB 是 vecN<bool>,
否则 TB 是 bool
e1 > e2: TB 大于。当 T 是向量时逐分量执行。
e1: T
e2: T
S 是 AbstractInt、AbstractFloat、 i32、u32、f32 或 f16
T 是 S 或 vecN<S>
如果 T 是向量,则 TB 是 vecN<bool>,
否则 TB 是 bool
e1 >= e2: TB 大于或等于。当 T 是向量时逐分量执行。

8.10. 位表达式

一元位操作
前置条件 结论 描述
e: T
S 是 AbstractInt、i32 或 u32
T 是 S 或 vecN<S>
~e : T 对 e 进行按位补码。 结果中的每一位都与 e 中对应位相反。 当 T 是向量时逐分量执行。
二元位操作
前置条件 结论 描述
e1: T
e2: T
S 是 AbstractInt、i32 或 u32
T 是 S 或 vecN<S>
e1 | e2: T 按位或。当 T 是向量时逐分量执行。
e1: T
e2: T
S 是 AbstractInt、i32 或 u32
T 是 S 或 vecN<S>
e1 & e2: T 按位与。当 T 是向量时逐分量执行。
e1: T
e2: T
S 是 AbstractInt、i32 或 u32
T 是 S 或 vecN<S>
e1 ^ e2: T 按位异或。当 T 是向量时逐分量执行。
位移表达式
前置条件 结论 描述
e1: T
e2: TS
S 是 i32 或 u32
T 是 S 或 vecN<S>
当 T 是 S 时,TS 是 u32,否则 TS 是 vecN<u32>
e1 << e2: T 左移(被移位值是具体值):

将 e1 左移,在最低有效位位置插入零位, 并丢弃最高有效位。

要移位的位数是 e2 的值对 e1 位宽取模后的结果。
如果 e2 大于或等于 e1 的位宽,则:

当 e1 和 e2 都在着色器执行 开始之前已知时, 结果不得溢出:

当 T 是向量时逐分量执行。

e1: T
e2: TS
T 是 AbstractInt 或 vecN<AbstractInt>
当 T 是 AbstractInt 时,TS 是 u32,否则 TS 是 vecN<u32>
e1 << e2: T 左移(被移位值是抽象值):

将 e1 左移,在最低有效位位置插入零位, 并丢弃最高有效位。

要移位的位数是 e2 的值。

e1 的最高有效 e2+1 位必须具有 相同的位值。 否则会发生溢出。

注: 此条件意味着所有 被丢弃的位都必须与原始值的符号位相同, 并且与最终值的符号位相同。

当 T 是向量时逐分量执行。

e1: T
e2: TS
S 是 i32 或 u32
T 是 S 或 vecN<S>
当 T 是 S 时,TS 是 u32,否则 TS 是 vecN<u32>
e1 >> e2: T 右移(被移位值是具体值)。

将 e1 右移,丢弃最低有效位。

如果 S 是无符号类型,则在最高有效位位置插入零位。

如果 S 是有符号类型:

  • 如果 e1 为负,则每个插入位为 1,因此结果也为 负。

  • 否则,每个插入位为 0。

要移位的位数是 e2 的值对 e1 位宽取模后的结果。

如果 e2 大于或等于 e1 的位宽,则:

当 T 是向量时逐分量执行。

e1: T
e2: TS
T 是 AbstractInt 或 vecN<AbstractInt>
当 T 是 AbstractInt 时,TS 是 u32,否则 TS 是 vecN<u32>
e1 >> e2: T 右移(抽象值)。

将 e1 右移,丢弃最低有效位。

如果 e1 为负,则每个插入位为 1,因此结果也为负。 否则,每个插入位为 0。

要移位的位数是 e2 的值。

当 T 是向量时逐分量执行。

8.11. 函数调用表达式

函数调用表达式执行一次函数调用,其中被调用的 函数具有返回类型。 如果被调用函数不返回值,则应改用函数调用语句。 参见§ 9.5 函数调用语句。

8.12. 变量标识符表达式

从变量名称获取引用
前置条件 结论 描述
v 是一个标识符,解析到 在地址 空间 AS 中声明的一个作用域内变量, 其存储类型为 T,访问 模式为 AM v: ref<AS,T,AM> 结果是对具名变量 v 的内存的引用。

8.13. 形式参数表达式

获取声明为函数形式参数的标识符的值
前置条件 结论 描述
a 是一个标识符,解析到 类型为 T 的作用域内形式参数 声明 a: T 结果是在调用此函数实例的调用点,为相应函数调用操作数提供的值。

8.14. 取地址表达式

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

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

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

如果 AS 是 handle 地址空间,则会产生着色器创建错误。

如果 r 是对向量分量的引用,则会产生着色器创建错误。

8.15. 间接表达式

间接运算符 将指针转换为其对应的引用。

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

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

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

获取由 const、override 或 let 声明的 标识符的值
前置条件 结论 描述
c 是一个标识符,解析到 类型为 T 的作用域内const 声明 c: T 结果是为初始化器表达式计算出的值。 该表达式是const 表达式,并在着色器创建时求值。
c 是一个标识符,解析到 类型为 T 的作用域内override 声明 c: T 如果管线创建为常量 ID指定了值, 则结果为该值。 对于不同的管线实例,该值可能不同。

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

注: 如果 API 调用中未 指定初始值, 且 let 声明没有初始化器表达式,则管线创建失败。

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

8.17. 枚举表达式

枚举表达式
前置条件 结论 描述
e 是一个标识符,解析到属于 枚举类型 E 的预声明枚举项 e : E 参见§ 6.4.1 预声明枚举项

8.18. 类型表达式

类型表达式
前置条件 结论 描述
t 是一个标识符,解析到预声明类型 t : AllTypes 参见§ 6.10 预声明类型和类型生成器摘要
a 是一个标识符,解析到类型别名。 a : AllTypes 此外,a 表示其所设置别名的类型。
s 是一个标识符,解析到结构体类型的声明。 s : AllTypes 此外,s 表示该结构体类型。
tg 是一个标识符,解析到类型生成器

e1: T1
...
eN: TN

tg _template_args_start
e1,
...,
eN
_template_args_end
: AllTypes
每个类型生成器对其要求并接受的模板 参数都有自己的要求, 并定义模板参数如何帮助确定结果类型。

表达式 e1 到 eN 是类型生成器的模板参数。

例如,类型表达式 vec2<f32> 是由两个f32 元素组成的向量。

预声明类型生成器列表参见§ 6.10 预声明类型和类型生成器摘要。

注: 此处两个变体的唯一区别 在于 eN 之后是否有末尾逗号。

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

8.19. 表达式语法摘要

当标识符是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

| '(' lhs_expression ')'

| call_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.20. 运算符优先级和结合性

整个本小节均为非规范性内容。

WGSL 右侧表达式中的运算符优先级和结合性 由其摘要语法自然产生。右侧表达式将运算符分组以组织它们, 如下图所示:

运算符优先级和结合性图

为了通过更明确的写法提高可读性,以下组不与其他组结合:

以下各组也不与自身结合:

上述两组中的结合都需要使用括号显式设置关系。 以下示例通过注释展示这些规则会在哪些情况下使表达式无效:

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

自然产生的优先级控制表达式的隐式括号,其中结合力更强的 运算符与优先级更弱的运算符同时出现时,其行为如同被括号包围。 例如,乘法运算符的结合力强于加法运算符,因此会从表达式 a + b * c 推导出 (a + (b * c))。类似地,自然产生的结合性控制 这些隐式括号的方向。例如,从左到右结合会从表达式 a + b + c 推导出 ((a + b) + c),而从右到左结合 会从表达式 * * a 推导出 (* (* a))。

下表总结了运算符优先级、结合性和绑定关系,并按 从最强到最弱的顺序排列。绑定列包含给定运算符所能绑定的更强表达式, 例如,如果值为“All above”,则该运算符 可以包含任何更强的表达式。但例如,如果值为“Unary”, 则任何弱于一元表达式但强于该行运算符的表达式都需要使用括号 才能与该运算符绑定。为了线性列出运算符,需要此列。

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

9. 语句

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

9.1. 复合语句

复合语句 是由花括号包围的零个或多个语句的序列。 当声明是 这些语句之一时,其标识符从 下一条语句的开始处到复合语句末尾都位于作用域内。

compound_statement :

attribute * '{' statement * '}'

continuing_compound_statement 是一种特殊形式 的复合 语句,它构成continuing 语句的主体,并允许在 末尾使用可选的break-if 语句。

9.2. 赋值语句

赋值对表达式求值, 并可选地将结果存储到内存中(从而更新变量的内容)。

assignment_statement :

lhs_expression ( '=' | compound_assignment_operator ) expression

| '_' '=' expression

运算符词法单元左侧的文本是左侧, 而 运算符词法单元右侧的表达式是右侧。

9.2.1. 简单赋值

当赋值的 左侧是引用表达式, 且运算符是等号('=')词法单元时,该赋值是 简单赋值。 在这种情况下,右侧的值会写入左侧所引用的内存。

前置条件 语句 描述
e: T,
T 是具体可构造类型,
r: ref<AS,T,AM>,
AS 是可写地址空间,
访问模式 AM 是write 或read_write
r = e 先对 r 求值,然后对 e 求值,再将为 e 计算出的值写入 r 所引用的内存 位置。

注: 如果该引用是无效内存引用,则写入 可能不会执行,或可能写入与 预期不同的内存位置。

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

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

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

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

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

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

    var sibling: S;
    // 将 'person' 变量的内容复制到 'sibling' 变量中。
    sibling = person;
}

9.2.2. 重排赋值

本节仅在支持swizzle_assignment 语言扩展时 适用。

当赋值的 左侧是重排视图, 且运算符是等号('=')词法单元时,该赋值是 重排 赋值。 在这种情况下,会对左侧执行重排 视图写入, 并使用右侧的值作为更新向量。

前置条件 语句 描述
sv : swizzle<AS,S,N,K>
e : vecK<S>
地址空间 AS 可写,
sv = e 按约定 sv = (p, IndexList)

约束:

  • IndexList 中的索引必须 彼此全部不同。

注: 重排视图表达式规则 确保 IndexList 中的每个索引都位于 [0,N-1]。

执行以下步骤。

  1. 对 p 求值。

  2. 对 e 求值,得到向量 update。

  3. 读取 p 所引用的内存,得到类型为 vecN<S> 的向量 old_contents。

  4. 执行 SwizzleViewWrite(p, IndexList, old_contents, update)

注: 如果重排视图是无效重排视图,则步骤 3 和 4 是越界访问,因此可能不会 执行,或者可能写入 与预期不同的内存位置。

注: 在读取 p 的旧内容之前 对 e 求值看起来可能不太自然。 但是,第 4 步中的重排写入必须保留 IndexList 中未列出的向量分量, 而这些分量本身可能已在对 e 求值期间被修改。

示例:重排赋值的求值顺序
var<private> v: array<vec4u,1>;

fn update_and_index() -> u32 {
  v[0] += vec4u(2,4,8,10);
  return 0;
}

fn update_and_rhs() -> vec2u {
  v[0].x = 900;
  v[0].z = 100;
  return vec2u(55,66);
}

fn eval_order_swizzle_assignment() {
  v[0] = vec4u(0,0,0,0);

  // 1. 对指针 p 求值,这会对
  //    左侧的索引求值,
  //    并作为副作用设置 v[0] = vec4u(2,4,8,10)
  //    同时产生 0 作为索引。
  // 2. 对右侧求值,设置
  //     v[0].x = 900
  //     v[0].z = 100
  //    并产生 vec2u(55,66)。
  // 3. 随后重排赋值仅更新 v[0].x 和 v[0].y,
  //    保留 v[0].z 和 v[0].w。

  v[update_and_index()].yx = update_and_rhs();

  // 此时 v[0] 等于 vec4u(66,55,100,10);
}

9.2.3. 虚假赋值

当赋值的 左侧是 下划线('_')词法单元时,该赋值是 虚假赋值。 在这种情况下,会对右侧求值, 然后忽略其结果。

前置条件 语句 描述
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.4. 复合赋值

当赋值的 左侧是引用表达式或 重排视图, 且运算符是compound_assignment_operators 之一时,该赋值是 复合 赋值。

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。
}
注: 当 e1 是重排 视图时,按照规定,e1 会求值一次以计算其对应的指针,但其底层内存 会被访问三次:
示例:复合重排赋值的求值顺序
var<private> v: array<vec4u,1>;

fn update_and_index() -> u32 {
  v[0] += vec4u(2,4,8,10);
  return 0;
}

fn update_and_rhs() -> vec2u {
  v[0].x = 900;
  v[0].z = 100;
  return vec2u(55,66);
}

fn eval_order_compound_swizzle_assignment() {
  v[0] = vec4u(0,0,0,0);

  // 1. 对指针 p 求值,这会对
  //    左侧的索引求值,
  //    并作为副作用设置 v[0] = vec4u(2,4,8,10)
  //    同时产生 0 作为索引。
  // 2. 重排名称是 .yx,因此 IndexList 是列表 1,0。
  //    执行 SwizzleViewRead(p,IndexList),
  //    得到 view_contents = vec2u(4,2)。
  // 3. 对右侧求值,设置
  //     v[0].x = 900
  //     v[0].z = 100
  //    并产生 vec2u(55,66)。
  // 4. 计算更新向量为
  //           view_contents + vec2u(55,66)
  //         = vec2u(4,2) + vec2u(55,66)
  //         = vec2u(59,68)
  // 5. 随后重排赋值仅更新 v[0].x 和 v[0].y,
  //    保留 v[0].z 和 v[0].w。

  v[update_and_index()].yx += update_and_rhs();

  // 此时 v[0] 等于 vec4u(68,59,100,10);
}
注: 对引用执行的复合赋值 可以重写为使用简单赋值的其他 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); }
其中标识符 c0 和 p 被选择为与程序中所有其他 标识符不同。

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

类型规则 前置条件: 每个 if 和 else 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' 词法单元 后跟以逗号分隔的case 选择器列表,以及 形式为复合语句的主体。

独立 default 子句是'default' 词法单元后跟形式为复合语句的主体。

default 子句是 以下之一:

每个 switch 语句必须恰好有一个default 子句。

'default' 词法单元在单个case_selector 列表中不得出现超过一次。

类型规则 前置条件: 对于单个 switch 语句,选择器表达式和所有 case 选择器表达式必须具有 相同的具体整数标量类型。

case_selectors 中的表达式必须 是const 表达式。

同一个 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;
  }
}
示例:WGSL Switch 与 default 合并
const c = 2;
var a : i32;
let x : i32 = generateValue();
switch x {
  case 0: {
    a = 1;
  }
  case 1, c {       // const 表达式可用于 case 选择器
    a = 3;
  }
  case 3, default { // default 关键字可与其他子句一起使用
    a = 4;
  }
}

9.4.3. Loop 语句

loop_statement :

attribute * 'loop' attribute * '{' statement * continuing_statement ? '}'

loop 语句重复执行一个循环体; 循环体被指定为一个复合语句。 循环体的每次执行称为一次迭代。

这种重复可以被break 或 return 语句中断。

可选地,循环体中的最后一条语句可以是 continuing 语句。

如果loop 将执行无界数量的迭代,则会发生动态错误。 这可能导致循环提前终止、其他非局部效应,甚至设备丢失。

当循环体中的某条语句是声明时, 它遵循复合语句中声明的正常作用域和生命周期规则,即复合语句中的声明规则。 也就是说,循环体是一个语句序列,如果其中某条是声明, 则该声明的作用域从序列中下一条语句的开始处延伸 到循环体末尾。 每次执行到该声明时都会执行它,因此每次新的迭代 都会创建变量或值的新实例,并重新初始化它。

注: loop 语句是一种专用构造,通常 更适合使用 for 或 while 语句。loop 语句是 WGSL 与 其他着色语言最大的差异之一。

这种设计直接表达编译代码中常见的循环惯用形式。 特别是,将循环更新语句放在循环体末尾 使其能够自然地使用循环体中定义的值。

示例: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 语句是包含loop 语句的复合语句的语法糖。 一般而言,for 语句采用以下形式

for ( initializer ; condition ; update_part ) { body }

存在条件表达式时,for 语句会脱糖为以下形式的循环:

{
initializer ;
loop {
if !(condition) { break; }
body
continuing { update_part }
}
}
不存在条件表达式时,for 语句会脱糖为以下形式的循环:
{
initializer ;
loop {
body
continuing { update_part }
}
}

此外:

for 循环的 initializer 在执行循环之前执行一次。 当声明出现在 初始化器中时,其标识符一直位于作用域内,直到 body 末尾。 与 body 中的声明不同,该声明不会在每次迭代时重新初始化。

condition、body 和 update_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 到 Loop 转换:转换前
var a: i32 = 2;
for (var i: i32 = 0; ; i++) {
  if a == 0 {
    continue;
  }
  if i == 4 { break; }
  a = a + 2;
}

转换为:

示例:不带条件的 For 到 Loop 转换:转换后
var a: i32 = 2;
{ // 为循环变量 i 引入新的作用域
  var i: i32 = 0;
  loop {
    // 注意:脱糖不会在此处引入 if 子句。

    if a == 0 {
      continue;
    }
    if i == 4 { break; }
    a = a + 2;

    continuing {
      i++;
    }
  }
}

如果for 循环将执行无界数量的迭代,则会发生动态错误。 这可能导致循环提前终止、其他非局部效应,甚至设备丢失。

9.4.5. While 语句

while_statement :

attribute * 'while' expression compound_statement

while 语句是一种由条件参数化的循环。 在每次循环迭代开始时, 会对布尔条件求值。 如果条件为 false,则 while 循环结束执行。 否则,执行迭代的其余部分。

类型规则 前置条件:条件必须是bool 类型。

while 循环可以视为loop 或for 语句之上的语法糖。 以下语句形式等价:

如果while 循环将执行无界数量的迭代,则会发生动态错误。 这可能导致循环提前终止、其他非局部效应,甚至设备丢失。

9.4.6. Break 语句

break_statement :

'break'

break 语句将控制转移到最近外围循环主体 或switch 语句之后, 从而结束循环或 switch 语句的执行。

break 语句必须仅用于loop、for、while 和switch 语句中。

break 语句不得放置在会使其退出循环的continuing 语句的位置。 请改用break-if 语句。

示例:WGSL 从 continuing 子句中无效地跳出循环
var a: i32 = 2;
var i: i32 = 0;
loop {
  let step: i32 = 1;

  if i % 2 == 0 { continue; }

  a = a * 2;

  continuing {
    i = i + step;
    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 从 continuing 子句中使用有效的 loop 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 语句必须仅用于loop、for 或while 语句中。 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 语句必须仅用于片段着色器阶段。

更准确地说,执行 discard 语句将:

只有在 discard 语句之前执行的语句将产生可观察效果。

注: discard 语句可以由 片段阶段中的任何函数执行,并且效果相同: 片段输出将被丢弃。

示例:使用 discard 语句丢弃片段
@group(0) @binding(0)
var<storage, read_write> will_emit_color : u32;

fn discard_if_shallow(pos: vec4<f32>) {
  if pos.z < 0.001 {
    // 如果执行这里,那么 will_emit_color 变量将
    // 永远不会被设置为 1,因为辅助调用不会写入
    // 共享内存。
    discard;
  }
  will_emit_color = 1;
}

@fragment
fn main(@builtin(position) coord_in: vec4<f32>)
  -> @location(0) vec4<f32>
{
  discard_if_shallow(coord_in);

  // 将值设置为 1 并输出红色,但仅当辅助函数
  // 未执行 discard 语句时。
  will_emit_color = 1;
  return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

9.5. 函数调用语句

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. 规则

某些影响控制流的语句只在某些上下文中有效。 例如,continue 在loop、 for 或while 之外无效。 此外,一致性分析(参见§ 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
B1 ∪ B2
loop {s1 continuing {s2}} s1: B1
s2: B2
B1 = {Return}
{Continue, Return} 均不属于 B2
{Return}
s1: B1
s2: B2
B1 ≠ {Return}
{Continue, Return} 均不属于 B2
Break 不属于 (B1 ∪ B2)
(B1 ∪ B2)∖{Continue, Next}
s1: B1
s2: B2
B1 ≠ {Return}
{Continue, Return} 均不属于 B2
Break 属于 (B1 ∪ B2)
(B1 ∪ B2 ∪ {Next})∖{Break, Continue}
switch e {case c1: s1 ... case cn: sn} s1: B1
...
sn: Bn
Break 不属于 (B1 ∪ ... ∪ Bn)
B1 ∪ ... ∪ Bn
s1: B1
...
sn: Bn
Break 属于 (B1 ∪ ... ∪ Bn)
(B1 ∪ ... ∪ Bn ∪ {Next})∖Break

注: ∪ 是集合并运算,∖ 是集合 差运算。

注: 当 loop 的主体为空,或 for 循环缺少初始化或更新 语句时,会出现空语句情况。

出于此分析的目的:

每个内置 函数的行为都是 {Next}。 上表中未列出的每个运算符应用,其行为与使用相同 操作数、且函数行为 为 {Next} 的函数调用相同。

函数的行为必须满足上述规则。

注: 上述规则意味着循环的行为 是 {Next}、{Return} 或 {Next,Return}。

注: 无需分析 表达式的行为,因为它们 始终为 {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 将 Break 替换为 Next
  a = 5;       // 有效,因为前一条语句的行为中包含 Next
}
示例:明显的无限循环
fn invalid_infinite_loop() {
  loop { }     // 行为:{ }。无效,因为它为空。
}
示例:Discard 不会终止循环
fn invalid_infinite_loop() {
  loop {
    discard; // 行为 { Next }。
  }          // 无效,整个循环的行为为 { }。
}
示例:具有 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;
    }
  }                      // 整个循环的行为为 {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 断言。

const_assert :

'const_assert' expression

类型规则 前置条件: 表达式必须是bool 类型。

10.1. Const 断言语句

const 断言语句是一种断言, 如果表达式求值得到 false,则产生着色器创建错误。 该表达式必须是const 表达式。 该语句可以满足着色器中的静态访问条件, 但除此之外不会影响编译后的着色器。 const 断言可以出现在 模块作用域,或作为函数作用域中的语句。

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; // 无效,表达式必须是 const 表达式。
}

11. 函数

函数在被调用时执行计算工作。

函数通过以下方式之一调用:

WGSL 中的函数可以按任意顺序定义,包括在源代码中晚于其使用位置定义。 因此不需要函数原型或前向声明,也无法这样做。

函数有两种:

11.1. 声明用户定义函数

函数 声明通过指定以下内容创建用户定义函数:

函数声明必须仅出现在模块作用域。 函数名称在整个程序中都位于作用域内。

注: 每个用户定义函数只有一个 重载。

形式参数声明为一个在调用函数时必须提供的值指定标识符名称和类型。 入口 点函数的形式参数可以带有属性。 参见§ 11.2 函数调用。 标识符的作用域是函数主体。 给定函数的两个形式参数不得具有相同名称。

注: 某些内置函数可能允许参数为 抽象数值 类型; 但是,用户声明的函数当前不支持此功能。

如果指定了返回类型,它必须是可构造类型。

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 语法规则实例中第一个词法单元的位置。 调用点是动态 上下文。 因此,相同的文本位置可以表示多个调用点。

注: 如果一个四元组中的所有调用都被丢弃, 则片段着色器中的函数调用可能永远不会 返回。 在这种情况下,控制不会转移回调用函数。

11.3. const 函数

使用const 属性声明的函数可以在着色器创建时 求值。 这些函数称为const 函数。 对这些函数的调用可以成为const 表达式的一部分。

如果函数包含任何不是const 表达式的表达式,或任何不是 const 声明的声明, 则会产生着色器创建错误。

注: const 属性不能应用于 用户声明的函数。

示例:const 函数
const first_one = firstLeadingBit(1234 + 4567); // 求值得到 12
                                                // first_one 的类型为 i32,因为
                                                // firstLeadingBit 不能对
                                                // AbstractInt 进行操作

@id(1) override x : i32;
override y = firstLeadingBit(x); // const 表达式可以
                                 // 用于 override 表达式。
                                 // 在此上下文中 firstLeadingBit(x) 不是
                                 // const 表达式。

fn foo() {
  var a : array<i32, firstLeadingBit(257)>; // 如果所有参数都是 const 表达式,
                                            // 则 const 函数可以用于
                                            // const 表达式。
}

11.4. 函数限制

注: 不允许递归,因为任何种类的 声明之间都不允许存在环。

示例:有效和无效的指针参数
fn bar(p : ptr<function, f32>) {
}

fn baz(p : ptr<private, i32>) {
}

fn bar2(p : ptr<function, f32>) {
  let a = &*&*(p);

  bar(p); // 有效
  bar(a); // 有效
}

fn baz2(p : ptr<storage, f32>) {
}

struct S {
  x : i32
}

@group(0) @binding(0)
var<storage> ro_storage : f32;
@group(0) @binding(1)
var<storage, read_write> rw_storage : f32;

var usable_priv : i32;
var unusable_priv : array<i32, 4>;
fn foo() {
  var usable_func : f32;
  var unusable_func : S;
  var i32_func : i32;

  let a_priv = &usable_priv;
  let b_priv = a_priv;
  let c_priv = &*&usable_priv;
  let d_priv = &(unusable_priv.x);
  let e_priv = d_priv;

  let a_func = &usable_func;
  let b_func = &unusable_func;
  let c_func = &(*b_func)[0];
  let d_func = c_func;
  let e_func = &*a_func;

  baz(&usable_priv); // 有效,对变量取地址。
  baz(a_priv);       // 有效,实际上是对变量取地址。
  baz(b_priv);       // 有效,实际上是对变量取地址。
  baz(c_priv);       // 有效,实际上是对变量取地址。
  baz(d_priv);       // 有效,内存视图已改变。
  baz(e_priv);       // 有效,内存视图已改变。
  baz(&i32_func);    // 无效,地址空间不匹配。

  bar(&usable_func); // 有效,对变量取地址。
  bar(c_func);       // 有效,内存视图已改变。
  bar(d_func);       // 有效,内存视图已改变。
  bar(e_func);       // 有效,实际上是对变量取地址。

  baz2(&ro_storage); // 有效,对变量取地址。
  baz2(&rw_storage); // 无效,访问模式不匹配。
}

11.4.1. 别名分析

11.4.1.1. 根标识符

在函数执行期间,可以使用内存视图或重排视图访问内存位置。 在函数内部,每个内存 视图都有一个特定的根标识符,它命名 在该函数中最初提供对该内存访问的变量或形式参数。 类似地,重排 视图的根 标识符是该重排视图指针的根标识符。

局部派生的引用或 指针类型表达式可能为 特定根标识符引入新名称, 但每个表达式都有一个可静态确定的根标识符。

给定一个指针或引用类型的表达式 E, 根标识符是来源 变量或形式 参数中的指针 类型, 按如下方式查找:

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

| subgroup_size_attr

| vertex_attr

| fragment_attr

| compute_attr

除非属性的描述中明确允许,否则每个对象或类型上同一个属性不得指定超过 一次。

12.1. align

align_attr :

'@' 'align' '(' expression ',' ? ')'

align 属性
描述 约束结构体成员在内存中的放置位置。

必须仅应用于结构体类型的成员。

此属性会影响外围结构体类型的值如何出现在内存中: 它约束结构体本身及其组成成员可以出现的字节地址。

如果 align(n) 应用于 S 中类型为 T 的成员, 并且 S 可以作为地址空间 AS 中变量的存储类型, 其中 AS 不是uniform, 则 n 必须满足:
n = k × RequiredAlignOf(T,AS) 对某个正整数 k 成立。

对齐和大小规则是相互递归的。 但是,上述约束有明确定义,因为它取决于 嵌套类型所需的对齐,并且类型具有有限的嵌套深度。

如果绑定到uniform 或 storage 缓冲区的内存起始地址具有 比属性中指定值更低的对齐,则最终对齐将是 两者中的较小值。 实现只需相对于变量的基 地址满足该属性。

参见§ 14.4 内存布局。

参数 必须是一个const 表达式,其解析为i32 或u32。
必须为正数。
必须是 2 的幂。

12.2. binding

binding_attr :

'@' 'binding' '(' expression ',' ? ')'

binding 属性
描述 指定资源在绑定组中的绑定编号。 参见§ 13.3.2 资源接口。

必须仅应用于资源变量。

参数 必须是一个const 表达式,其解析为i32 或u32。
必须为非负数。

12.3. blend_src

blend_src_attr :

'@' 'blend_src' '(' expression ',' ? ')'

blend_src 属性
描述 当启用dual_source_blending 特性时,指定片段输出的一部分。 参见§ 13.3.1.3 输入输出位置。

必须仅应用于具有 location 属性的结构体类型的 成员。 必须仅应用于具有数值标量 或数值 向量类型的对象声明。 不得包含在着色器 阶段输入中。 不得包含在着色器阶段输出中, 片段 着色器阶段除外。

参数 必须是一个const 表达式,其解析为值为 0 或 1 的i32 或u32。

12.4. builtin

builtin_attr :

'@' 'builtin' '(' builtin_value_name ',' ? ')'

builtin 属性
描述 指定关联对象是由指定词法单元表示的内置值。 参见§ 13.3.1.1 内置输入和输出。

必须仅应用于入口点 函数参数、入口点返回类型或结构体的成员。

参数 必须是内置值的内置值 名称词法单元。

12.5. const

const_attr :

'@' 'const'

const 属性
描述 指定函数可以用作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 词法单元, 用于指定触发规则。

12.7. group

group_attr :

'@' 'group' '(' expression ',' ? ')'

group 属性
描述 指定资源的绑定组。 参见§ 13.3.2 资源接口。

必须仅应用于资源变量。

参数 必须是一个const 表达式,其解析为i32 或u32。
必须为非负数。

12.8. id

id_attr :

'@' 'id' '(' expression ',' ? ')'

id 属性
描述 指定一个数字标识符,作为 管线可重写常量的备用名称。

必须仅应用于标量类型的override 声明。

参数 必须是一个const 表达式,其解析为i32 或u32。
必须为非负数。

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 属性的声明。

存在时,该属性只对用户定义的顶点输出 和片段输入有意义。

参数 第一个参数必须是 插值类型的插值类型名称词法单元。

第二个参数(如果存在)必须是 插值采样的插值采样名称词法单元。

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 输入输出位置。

必须仅应用于入口点 函数参数、入口点 返回类型或结构体类型的成员。 必须仅应用于具有数值标量 或数值 向量类型的对象声明。 不得包含在计算着色器 阶段输入中。

参数 必须是一个const 表达式,其解析为i32 或u32。
必须为非负数。

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 内存布局。

必须仅应用于结构体类型的成员。 成员类型必须具有创建时固定 占用空间。

参数 必须是一个const 表达式,其解析为i32 或u32。
必须为正数。

12.14. subgroup_size

subgroup_size_attr :

'@' 'subgroup_size' '(' expression ',' ? ')'

subgroup_size 属性
描述 指定计算着色器调用的子组大小。

必须仅应用于计算着色器入口点函数。

要求 仅当启用subgroup_size_control 扩展时才必须使用。
参数 必须是一个const 表达式或override 表达式,其解析为i32 或u32。

如果值不是 2 的幂,则:

如果入口点 workgroup_size 的 x 维度不是 subgroup_size 值的倍数,则会产生管线创建错误。

如果该值大于 subgroupMaxSize 或小于 subgroupMinSize,则会产生管线创建错误。

即使该值位于 subgroupMinSize 和 subgroupMaxSize 之间,管线创建也可能由于寄存器压力 或特定硬件限制而产生未分类错误。

12.15. 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。

必须仅应用于计算着色器入口点函数。 不得应用于任何其他对象。

参数 接受一个、两个或三个参数。

每个参数必须是const 表达式或override 表达式。 所有参数必须具有相同类型,即i32 或u32。

如果任一指定参数是求值得到非正值的 const 表达式,则会产生着色器创建错误。

如果任一指定参数求值得到 非正值或超过 WebGPU API 指定的上限,或者参数值的乘积超过 WebGPU API 指定的上限,则会产生管线创建错误(参见WebGPU § 3.6.2 限制)。

12.16. 着色器阶段属性

下面的着色器阶段 属性 将函数指定为特定着色器阶段的入口点。 这些属性必须仅应用于函数声明, 并且给定函数上最多只能存在其中一个。 它们不接受参数。

12.16.1. vertex

vertex_attr :

'@' 'vertex'

vertex 属性声明该函数是渲染 管线的顶点着色器 阶段的入口点。

12.16.2. fragment

fragment_attr :

'@' 'fragment'

fragment 属性声明该函数是渲染 管线的片段着色器 阶段的入口点。

12.16.3. compute

compute_attr :

'@' 'compute'

compute 属性声明该函数是计算管线的计算着色器 阶段的入口点。

13. 入口点

入口点是一个用户定义 函数,用于执行 特定着色器 阶段的工作。

13.1. 着色器阶段

WebGPU 以绘制或分派命令的形式向 GPU 发出工作。 这些命令在一组着色器阶段 输入、输出和附加 资源的上下文中执行管线。

管线描述要在 GPU 上执行的工作,它由一系列 阶段组成,其中一些是可编程的。 在 WebGPU 中,会在调度绘制或分派命令执行之前创建管线。 有两种管线:GPUComputePipeline 和 GPURenderPipeline。

分派命令使用 GPUComputePipeline 在具有可控并行度的逻辑 点网格上运行计算着色器 阶段, 同时读取并可能更新缓冲区和图像资源。

绘制命令使用GPURenderPipeline 运行一个多阶段过程,其中在其他固定功能阶段之间包含 两个可编程阶段:

WebGPU 规范对管线进行了更详细的描述。

WGSL 定义了三个着色器 阶段,对应于 管线的可编程部分:

每个着色器阶段都有自己的一组特性和约束,这些内容在其他位置描述。

13.2. 入口点声明

要创建入口点,请声明 一个具有着色器阶段属性的用户定义 函数。

在 WebGPU API 中配置管线时, 入口点的函数名称映射到 WebGPU GPUProgrammableStage 对象的 entryPoint 属性。

入口点的形式 参数表示该阶段的着色器阶段输入。 可以使用结构体类型将用户定义输入彼此分组,并可选择与内置 输入一起分组。 每个参数必须是着色器阶段输入, 或者它必须 声明为结构体类型,其中 每个结构体成员都是着色器阶段输入。

如果指定,入口点的返回 类型及其属性表示该阶段的着色器阶段输出。 可以使用结构体类型将用户定义输出彼此分组,并可选择与内置 输出一起分组。 如果存在,则返回类型及其属性 必须是 着色器阶段输出, 或者它必须 是结构体类型,其中每个结构体成员都是着色器阶段输出。

注: 计算入口点绝不会具有返回类型。

示例:入口点
@vertex
fn vert_main() -> @builtin(position) vec4<f32> {
  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}

@fragment
fn frag_main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
  return vec4<f32>(coord_in.x, coord_in.y, 0.0, 1.0);
}

@compute @workgroup_size(1)
fn comp_main() { }

着色器阶段中的函数集合是以下项的并集:

重复应用并集运算,直到结果稳定。 它将在有限步数内稳定。

13.2.1. 入口点的函数属性

WGSL 定义了以下可应用于入口点声明的属性:

示例: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
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 内置值既是 顶点着色器的输出,也是片段着色器的输入。

内置输入值和内置输出值统称为内置值。

下表总结了可用的内置值。 每一项都是某个内置值的内置值名称词法单元。 后续各节将详细描述每一项。

内置输入和输出值
名称 阶段 方向 类型 扩展
vertex_index 顶点 输入 u32
instance_index 顶点 输入 u32
clip_distances 顶点 输出 array<f32, N> (N ≤ 8) clip_distances
position 顶点 输出 vec4<f32>
片段 输入 vec4<f32>
front_facing 片段 输入 bool
frag_depth 片段 输出 f32
primitive_index 片段 输入 u32 primitive_index
sample_index 片段 输入 u32
sample_mask 片段 输入 u32
片段 输出 u32
local_invocation_id 计算 输入 vec3<u32>
local_invocation_index 计算 输入 u32
global_invocation_id 计算 输入 vec3<u32>
global_invocation_index 计算 输入 u32 linear_indexing
workgroup_id 计算 输入 vec3<u32>
workgroup_index 计算 输入 u32 linear_indexing
num_workgroups 计算 输入 vec3<u32>
subgroup_invocation_id 计算 输入 u32 subgroups
片段
subgroup_size 计算 输入 u32 subgroups
片段
subgroup_id 计算 输入 u32 subgroups 和subgroup_id
num_subgroups 计算 输入 u32 subgroups 和subgroup_id
示例:声明内置值
 struct VertexOutput {
   @builtin(position) my_pos: vec4<f32>,
   @builtin(clip_distances) my_clip_distances: array<f32, 8>,
 }

 @vertex
 fn vs_main(
   @builtin(vertex_index) my_index: u32,
   @builtin(instance_index) my_inst_index: u32,
 ) -> VertexOutput {}

 struct FragmentOutput {
   @builtin(frag_depth) depth: f32,
   @builtin(sample_mask) mask_out: u32
 }

 @fragment
 fn fs_main(
   @builtin(front_facing) is_front: bool,
   @builtin(position) coord: vec4<f32>,
   @builtin(sample_index) my_sample_index: u32,
   @builtin(sample_mask) mask_in: u32,
 ) -> FragmentOutput {}

 @compute @workgroup_size(64)
 fn cs_main(
   @builtin(local_invocation_id) local_id: vec3<u32>,
   @builtin(local_invocation_index) local_index: u32,
   @builtin(global_invocation_id) global_id: vec3<u32>,
) {}
13.3.1.1.1. clip_distances
名称 clip_distances
阶段 顶点
类型 array<f32, N>
方向 输出
描述 数组中的每个值表示到用户定义裁剪平面的距离。裁剪距离为 0 表示顶点位于该平面上,正距离表示顶点位于裁剪 半空间内部,而负距离表示顶点位于裁剪半空间外部。clip_distances 的数组 大小必须 ≤ 8。 参见WebGPU § 23.2.4 图元裁剪。
13.3.1.1.2. frag_depth
名称 frag_depth
阶段 片段
类型 f32
方向 输出
描述 片段更新后的深度,位于视口深度范围内。

参见WebGPU § 3.3 坐标系。

如果支持fragment_depth 特性, 则 builtin 属性可以具有可选的第二个 参数,用于指定 深度模式:

  • 指定depth_mode less 是一种承诺:如果片段着色器 返回frag_depth(即没有 discard),则返回值 将小于或等于该片段的原始深度。

  • 指定depth_mode greater 是一种承诺:如果片段着色器 返回frag_depth(即没有 discard),则返回值 将大于或等于该片段的原始深度。

片段的原始深度是该片段 RasterizationPoint 的 depth 属性。

如果着色器返回违反深度模式承诺的深度值,则可以改用 不确定的深度值。

@fragment
fn main() -> @builtin(frag_depth, greater) f32 {
    return 1.0f;
}
13.3.1.1.3. front_facing
名称 front_facing
阶段 片段
类型 bool
方向 输入
描述 当当前片段位于正面朝向的图元上时为 True。 否则为 False。
13.3.1.1.4. global_invocation_id
名称 global_invocation_id
阶段 计算
类型 vec3<u32>
方向 输入
描述 当前调用的全局调用 ID,即其在 计算 着色器网格中的位置。global_invocation_id 的值 等于workgroup_id * workgroup_size + local_invocation_id。
13.3.1.1.5. global_invocation_index
名称 global_invocation_index
阶段 计算
类型 u32;
方向 输入
描述 当前调用的全局调用索引,即其在 计算着色器网格中的线性位置。global_invocation_index 的值 等于:

global_invocation_id.x +
(global_invocation_id.y * workgroup_size.x * num_workgroups.x) +
(global_invocation_id.z * workgroup_size.x * num_workgroups.x * workgroup_size.y * num_workgroups.y)

注: 如果分派的 工作组数量会导致该值超出u32 类型的范围,则分派将失败:
  • 对于 dispatchWorkgroups,GPUComputePassEncoder 将失效。

  • 对于 dispatchWorkgroupsIndirect,分派将不会执行。

13.3.1.1.6. instance_index
名称 instance_index
阶段 顶点
类型 u32
方向 输入
描述 当前 API 级绘制命令中当前顶点的实例索引。

第一个实例的索引等于绘制命令的 firstInstance 参数, 无论该参数是直接还是间接提供的。 绘制中的每个后续实例都会使索引递增一。

13.3.1.1.7. local_invocation_id
名称 local_invocation_id
阶段 计算
类型 vec3<u32>
方向 输入
描述 当前调用的局部调用 ID,即其在 工作组 网格中的位置。
13.3.1.1.8. local_invocation_index
名称 local_invocation_index
阶段 计算
类型 u32
方向 输入
描述 当前调用的局部调用索引,即 调用在工作组网格内位置的线性化索引。
13.3.1.1.9. num_workgroups
名称 num_workgroups
阶段 计算
类型 vec3<u32>
方向 输入
描述 API 分派大小 vec3<u32>(group_count_x, group_count_y, group_count_z),用于 API 分派的计算 着色器。
13.3.1.1.10. position
名称 position
阶段 顶点
类型 vec4<f32>
方向 输出
描述 当前顶点的裁剪位置, 以裁剪空间坐标表示。

输出值 (x,y,z,w) 将映射到 WebGPU 标准化设备坐标中的 (x/w, y/w, z/w)。

参见WebGPU § 3.3 坐标系和WebGPU § 23.2.4 图元裁剪。

如果 w 坐标为零,则会发生动态错误。

名称 position
阶段 片段
类型 vec4<f32>
方向 输入
描述
当前片段光栅化点的输入位置。

令 rp 为该片段的RasterizationPoint。
令 vp 为绘制命令当前生效的 [[viewport]]。
则 fp 是片段光栅化点计算得到的输入位置。

示意如下:

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.11. primitive_index
名称 primitive_index
阶段 片段
类型 u32
方向 输入
描述 基于自当前绘制操作开始以来为当前实例处理的图元数量的逐图元索引。 从 0 开始,并在每处理一个点、线或三角形图元后递增 1。每绘制一个实例后重置为 0。 使用图元重启值重新启动条带图元不会影响 图元索引。该索引在图元的所有片段中保持一致。
13.3.1.1.12. sample_index
名称 sample_index
阶段 片段
类型 u32
方向 输入
描述 当前片段光栅化点的采样索引。 该值最小为 0,最大为 sampleCount-1,其中 sampleCount 是为 GPU 渲染管线指定的采样 count。 应用此属性时,如果片段着色器的效果会根据sample_index 的值而变化,则片段着色器将 每个采样调用一次。

参见WebGPU § 10.3 GPURenderPipeline。

13.3.1.1.13. sample_mask
名称 sample_mask
阶段 片段
类型 u32
方向 输入
描述 当前片段的采样覆盖位掩码。

各位通过半开区间 [0, sampleCount) 中的采样索引进行索引, 其中 sampleCount 是为 GPU 渲染管线指定的采样 count。

某一位仅当对应采样被正在渲染的图元覆盖时才会设置为 1。 索引为 sampleCount 及以上的位始终设置为零。

位掩码有两种可能的值:

  • 位掩码对片段覆盖的每个采样都设置一个 1 位。 在这种情况下,位掩码等于片段的 RasterizationPoint coverageMask。

  • 位掩码仅设置一个 1 位,对应于当前片段着色器调用正在处理的采样。 也就是说,位掩码为 (1 << sample_index),如同使用 sample_index 内置输入一样。

注: 当 sampleCount = 1 时,这两种情况相同。

注: 当 sampleCount > 1 时,这是一个已知的可移植性风险。 某些设备会产生完整覆盖掩码。其他设备会产生单个位掩码。

参见WebGPU § 23.2.10 逐采样着色和WebGPU § 23.2.11 采样 掩码。

名称 sample_mask
阶段 片段
类型 u32
方向 输出
描述 当前片段的采样覆盖掩码控制。最后一次 写入此变量的值会成为着色器输出掩码。写入值中的零位 会导致颜色附件中对应的采样被丢弃。

参见WebGPU § 23.2.11 采样掩码。

13.3.1.1.14. vertex_index
名称 vertex_index
阶段 顶点
类型 u32
方向 输入
描述 当前 API 级绘制命令中当前顶点的索引, 与绘制实例化无关。

对于非索引绘制,第一个顶点的索引等于绘制的 firstVertex 参数, 无论该参数是直接还是间接提供的。 绘制实例中的每个后续顶点都会使索引递增一。

对于索引绘制,该索引等于顶点对应的索引缓冲区条目, 再加上绘制的 baseVertex 参数,无论该参数是直接还是 间接提供的。

13.3.1.1.15. workgroup_id
名称 workgroup_id
阶段 计算
类型 vec3<u32>
方向 输入
描述 当前调用的工作组 ID,即 工作组在整体计算着色器网格中的位置。

同一工作组中的所有调用具有相同的工作组 ID。

工作组 ID 的范围从 (0,0,0) 到 (group_count_x - 1, group_count_y - 1, group_count_z - 1)。

13.3.1.1.16. workgroup_index
名称 workgroup_index
阶段 计算
类型 u32
方向 输入
描述 当前调用的工作组索引,即 工作组在整体计算着色器网格中的线性位置。

同一工作组中的所有调用具有相同的工作组索引。

注: 如果分派的 工作组数量会导致该值超出u32 类型的范围,则分派将失败:
  • 对于 dispatchWorkgroups,GPUComputePassEncoder 将失效。

  • 对于 dispatchWorkgroupsIndirect,分派将不会执行。

13.3.1.1.17. subgroup_invocation_id
名称 subgroup_invocation_id
阶段 计算或片段
类型 u32
方向 输入
描述 当前调用的子组调用 ID。

该 ID 位于范围 [0, subgroup_size - 1] 内。

在计算着色器中,ID 从零开始并且连续。 也就是说,当计算着色器开始执行时,在每个子组内:

  • 一个调用将具有 subgroup_invocation_id = 0,并且

  • 如果子组中存在一个 subgroup_invocation_id = k 的调用, 那么对于所有 0 ≤ j < k, 该子组中也存在一个 subgroup_invocation_id = j 的调用。

注: 片段着色器中的子组调用索引 可能不是连续的。实现 可能将某些较小编号的 ID 分配给辅助调用。

13.3.1.1.18. subgroup_size
名称 subgroup_size
阶段 计算或片段
类型 u32
方向 输入
描述 当前调用所在子组的子组大小。

对于具有subgroup_size 属性的计算着色器,此值 等于指定的属性值。

13.3.1.1.19. subgroup_id
名称 subgroup_id
阶段 计算
类型 u32
方向 输入
描述 当前调用的子组在 工作组内的子组 ID。

该 ID 位于范围 [0, num_subgroups - 1] 内。

13.3.1.1.20. num_subgroups
名称 num_subgroups
阶段 计算
类型 u32
方向 输入
描述 当前调用的工作组中的子组数量。
13.3.1.2. 用户定义的输入和输出

用户定义的数据可以作为输入传递到管线起始处、在 管线各阶段之间传递,或者从管线末端输出。

每个用户定义 输入数据和 用户定义 输出数据必须:

计算着色器不得具有用户定义的 输入或输出。

13.3.1.3. 输入输出位置

每个输入输出位置可以存储大小最多为 16 字节的值。 类型的字节大小使用§ 14.4.1 对齐和大小中的 SizeOf 列定义。 例如,由四个浮点值分量组成的向量占用一个位置。

IO 位置通过location 属性指定。

每个用户定义的输入和输出必须具有 显式指定的 IO 位置。 入口点 IO 中的每个结构体成员必须要么是内置值 (参见§ 13.3.1.1 内置输入和输出),要么被分配一个位置。

对于 WGSL 模块中定义的每个入口点,令 inputs 为其着色器阶段输入集合 (即其形式参数的位置,或者其结构体类型形式参数成员的位置)。
对于 WGSL 模块中定义的每个结构体类型 S(不仅仅是用于着色器阶段输入或 输出的结构体), 令 members 为 S 中具有location 属性的成员集合。

注: 输入和 输出的位置编号彼此独立: 入口点着色器阶段输入的位置编号不会与 入口点着色器阶段输出的位置编号冲突。

注: 无需额外规则来防止入口点输出内的位置 重叠。 当输出是结构体时,上述第一条规则会防止重叠。 否则,输出是标量或向量,并且只能为其分配单个位置。

注: 入口点可用的位置数量 由 WebGPU API 定义。

示例:应用 location 属性
struct A {
  @location(0) x: f32,
  // 尽管位置为 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 提供两个可控制的插值方面:插值 类型和插值采样。

插值类型必须是以下 插值类型名称词法单元之一:

perspective

值以透视校正方式进行插值。

linear

值以线性、非透视校正方式进行插值。

flat

值不进行插值。

插值 采样必须是以下 插值采样名称词法单元之一:

center

在像素中心执行插值。

centroid

在位于当前图元内片段所覆盖全部 采样中的某一点执行插值。 对图元中的所有采样,此值相同。

sample

逐采样执行插值。 应用此属性时,片段着色器 每个采样调用一次。

first

该值由图元的第一个顶点提供。

either

该值由图元的第一个顶点或最后一个顶点提供。 该值来自第一个还是最后一个顶点取决于实现。

对于标量或向量浮点类型的用户定义 IO:

标量或向量整数类型的用户定义顶点输出和片段输入必须始终指定插值类型 flat。

阶段间接口验证检查 在渲染 管线中, 每个用户定义片段输入的插值属性是否与 具有相同location 分配的顶点输出的插值属性相匹配。 如果不匹配,则管线创建错误将发生。

13.3.2. 资源接口

资源是一个提供对着色器阶段外部数据访问的对象, 并且它不是override 声明,不是即时数据变量,也不是着色器阶段输入或输出。 资源由着色器的所有调用共享。

有四种资源:

着色器的资源 接口是被着色器阶段中的函数 静态访问的模块作用域 资源变量集合。

每个资源变量必须同时使用group 和binding 属性进行声明。 它们与着色器阶段一起标识资源在着色器管线上的绑定地址。 参见WebGPU § 8.3 GPUPipelineLayout。

即时数据变量不使用group 或binding 属性。

着色器中的两个不同资源变量不得 在作为一对考虑时具有相同的group 和binding 值。

13.3.3. 资源布局兼容性

WebGPU 要求着色器的资源接口与使用该着色器的管线布局相匹配。

如果资源接口中的 WGSL 变量绑定到了不兼容的 WebGPU 绑定成员或绑定类型, 则会产生管线创建错误, 其中兼容性由下表定义。

WebGPU 绑定类型兼容性
WGSL 资源 WebGPU 绑定成员 WebGPU 绑定类型
uniform 缓冲区 buffer GPUBufferBindingType "uniform"
具有read_write 访问的存储缓冲区 "storage"
具有read 访问的存储缓冲区 "read-only-storage"
sampler sampler GPUSamplerBindingType "filtering"
"non-filtering"
sampler_comparison "comparison"
采样纹理、 深度 纹理或 多重采样纹理 texture GPUTextureSampleType "float"
"unfilterable-float"
"sint"
"uint"
"depth"
只写存储纹理 storageTexture GPUStorageTextureAccess "write-only"
读写存储纹理 "read-write"
只读存储纹理 "read-only"
外部纹理 externalTexture (不适用)

有关接口验证要求,请参见 WebGPU API 规范。

13.3.4. 缓冲区绑定确定运行时大小数组的元素数量

当存储缓冲区 变量包含运行时大小数组时,该数组中的元素数量 由对应 resource 的大小确定:

更详细地说,类型为 RAT 的运行时大小数组的NRuntime 为:

truncate((EBBS − array_offset) ÷ array_stride),其中:

着色器可以通过arrayLength 内置函数计算NRuntime。

注: 此算法没有歧义: 当运行时大小数组是更大类型的一部分时,它只能作为结构体的最后一个元素出现, 而该结构体本身不能作为外围数组或结构体的一部分。

NRuntime 由 对应缓冲区绑定的大小确定,并且该值对于每个绘制 或分派命令可能不同。

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 {                          //             对齐(16) 大小(32)
  position : vec3f,                          // 偏移(0)   对齐(16) 大小(12)
  // -- 隐式成员对齐填充 -- // 偏移(12)            大小(4)
  color : vec3f,                             // 偏移(16)  对齐(16) 大小(12)
  // -- 隐式结构体大小填充 --      // 偏移(28)            大小(4)
}

struct LightStorage {                        //             对齐(16)
  pointCount : u32,                          // 偏移(0)   对齐(4)  大小(4)
  // -- 隐式成员对齐填充 -- // 偏移(4)             大小(12)
  point : array<PointLight>,                 // 偏移(16)  对齐(16) 元素大小(32)
}

@group(0) @binding(1) var<storage> lights : LightStorage;

下表展示了 lights 变量中 point 成员的NRuntime 示例。

复杂运行时大小数组的元素数量示例
有效缓冲区绑定大小 lights 变量中 point 成员的 NRuntime 计算
1024 31 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.5 内存视图。

14.1. 内存位置

内存由一组不同的内存位置组成。 每个内存位置的大小为 8 位。 影响内存的操作会与一个或多个内存位置的集合交互。 对复合类型执行的内存操作不会 访问填充内存位置。 因此,一个操作所访问的内存位置集合可能并不连续。

如果两个内存位置集合的交集 非空,则这两个集合重叠。

14.2. 内存访问模式

内存访问是一个 作用于内存位置的操作。

单个操作可以读取、写入,或同时读取和写入。

特定内存位置可能只支持某些类型的访问,这由 内存的访问 模式表示。

访问模式
访问模式 支持的访问
read 支持读访问,但不支持写访问。
write 支持写访问,但不支持读访问。
read_write 同时支持读访问和写访问。

WGSL 预声明枚举项enumerants read、 write 和 read_write。

14.3. 地址空间

内存位置被划分到不同的地址空间中。 每个地址空间都有独特的属性,用于确定 可变性、可见性、它可以包含的值, 以及如何使用其中的变量。 有关更多详细信息,请参见§ 7 变量和值声明。

给定内存 视图的访问模式通常由上下文确定:

storage 地址空间同时支持read 和 read_write 访问 模式。 其他每个地址空间仅支持一种访问模式。 每个地址空间的默认访问模式在下表中描述。

地址空间
地址空间 调用之间的共享 默认访问模式 注释
function 仅同一调用 read_write
private 仅同一调用 read_write
workgroup 同一计算着色器工作组中的调用 read_write 最外层数组的元素数量 可以是管线可重写常量。
uniform 同一着色器阶段中的调用 read 用于uniform 缓冲区变量
storage 同一着色器阶段中的调用 read 用于存储 缓冲区变量
immediate 同一着色器阶段中的调用 read 用于即时 数据变量。
每个入口点最多可以 静态访问一个 immediate 变量。
handle 同一着色器阶段中的调用 read 用于采样器 和纹理 变量。

WGSL 为每个地址空间预声明一个枚举项, handle 地址空间除外。

workgroup 地址空间中的变量 必须仅在计算着色器阶段中被静态 访问。

storage 地址空间中的变量(存储缓冲区)仅在访问模式为read 时,才能被顶点着色器阶段静态 访问。 如果变量的存储类型是具有 write 或read_write访问模式的存储纹理, 则不能被顶点着色器阶段静态 访问。 参见 WebGPU createBindGroupLayout()。

注: 每个地址空间可能具有不同的性能 特征。

在 WGSL 源代码中编写变量声明或指针类型时:

14.4. 内存布局

WGSL 中类型的布局与地址空间无关。 但严格来说,该布局只能通过主机可共享 缓冲区观察到。 Uniform 缓冲区和存储缓冲区变量 用于共享 以内存中字节序列形式组织的大块数据。 缓冲区可在 CPU 和 GPU 之间、管线中的不同着色器阶段之间, 或不同管线之间共享。

由于缓冲区数据在共享时不会重新格式化或转换,如果缓冲区 生产者和消费者对内存布局没有达成一致,则会发生动态错误;内存布局描述了 缓冲区中的字节如何组织为有类型的 WGSL 值。 这些字节是相对于共同基 位置的值的内存 位置。

缓冲区变量的存储类型 必须是 主机可共享类型,并具有 如下所述的完全展开内存布局。

每个缓冲区变量必须声明在 uniform 或 storage 地址空间中。

只有在对具有以下内容的表达式求值时,类型的内存布局才有意义:

8 位字节是主机可共享内存的最基本单位。 本节定义的术语表示 8 位字节的数量。

我们将使用以下记号,其中 T 是主机可共享或固定占用空间类型, S 是主机可共享或固定占用空间结构体类型, 而 A 是主机可共享或固定占用空间数组或运行时大小数组:

14.4.1. 对齐和大小

每个主机可共享或固定占用空间数据类型 T 都有大小。 除buffer 类型外,每个 主机可共享或固定占用空间类型都有对齐。

类型的对齐是一个 对该类型值在内存中可放置位置的约束,以整数表示: 类型的对齐必须整除 该类型值起始内存位置的字节地址。 对齐可让硬件使用更高效的指令访问这些值, 或满足某些地址空间更严格的硬件要求。 (参见地址空间布局约束)。

注: 按构造方式,每个对齐值始终是 2 的幂。

类型或结构体成员的字节大小 是主机可共享内存中 为存储该类型或结构体成员的值而保留的连续字节数。 大小可以包含类型末尾不可寻址的填充。 因此,加载和存储一个值时实际访问的内存位置 可能少于该值的大小。

主机可共享和固定占用空间类型的对齐和大小在下表中递归定义:

主机可共享和固定占用空间类型的对齐和大小
主机可共享或固定占用空间类型 T AlignOf(T) SizeOf(T)
bool
参见注释。
4 4
i32、u32 或f32 4 4
f16 2 2
atomic<T> 4 4
vec2<T>, T 是bool、i32、u32 或f32 8 8
vec2<f16> 4 4
vec3<T>, T 是bool、i32、u32 或f32 16 12
vec3<f16> 8 6
vec4<T>, T 是bool、i32、u32 或f32 16 16
vec4<f16> 8 8
matCxR (列主序)

(一般形式)

AlignOf(vecR) SizeOf(array<vecR, C>)
mat2x2<f32> 8 16
mat2x2<f16> 4 8
mat3x2<f32> 8 24
mat3x2<f16> 4 12
mat4x2<f32> 8 32
mat4x2<f16> 4 16
mat2x3<f32> 16 32
mat2x3<f16> 8 16
mat3x3<f32> 16 48
mat3x3<f16> 8 24
mat4x3<f32> 16 64
mat4x3<f16> 8 32
mat2x4<f32> 16 32
mat2x4<f16> 8 16
mat3x4<f32> 16 48
mat3x4<f16> 8 24
mat4x4<f32> 16 64
mat4x4<f16> 8 32
具有成员 M1...MN 的struct S max(AlignOfMember(S,1), ... , AlignOfMember(S,N))
roundUp(AlignOf(S), justPastLastMember)

其中 justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N)
array<E, N>
AlignOf(E) N × roundUp(AlignOf(E), SizeOf(E))
array<E>
AlignOf(E) NRuntime × roundUp(AlignOf(E),SizeOf(E))

其中 NRuntime 是运行时确定的 T 元素数量
buffer<N> 不适用 N
buffer 不适用 所附加 WebGPU GPUBuffer 的大小

注: buffer 类型的对齐无关紧要,因为它们不能 包含在任何其他类型中。

注: 许多 GPU 无法实现单字节写入而不引入潜在的数据竞争。 通过规定 bool 值占用 4 字节并具有 4 字节对齐, 实现可以支持内存中相邻的布尔值而不引入数据竞争。

14.4.2. 结构体成员布局

结构体的内部布局 根据其成员的大小和对齐计算。 默认情况下,成员按顺序紧密排列而不重叠,同时满足成员对齐 要求。

可以使用布局属性覆盖此默认内部布局,这些属性是:

结构体类型 S 的第 i 个成员具有大小和对齐,分别表示 为SizeOfMember(S, i) 和AlignOfMember(S, i)。 成员大小和对齐用于计算每个成员相对于结构体起始处的字节偏移量, 如§ 14.4.4 值的内部布局中所述。

如果 S 的第 i 个成员具有属性size(k), 则SizeOfMember(S, i) 为 k。 否则,它是 SizeOf(T),其中 T 是该成员的类型。
如果 S 的第 i 个成员具有属性align(k), 则AlignOfMember(S, i) 为 k。 否则,它是AlignOf(T), 其中 T 是该成员的类型。

如果结构体成员应用了size 属性,该值必须 至少与成员类型的大小一样大:

SizeOfMember(S, i) ≥ SizeOf(T)
其中 T 是 S 第 i 个成员的类型。

第一个结构体成员相对于结构体起始处的字节偏移量始终为零:

OffsetOfMember(S, 1) = 0

后续每个成员都放置在满足成员类型对齐且 避免与前一个成员重叠的最低偏移量处。 对于每个成员索引 i > 1:

OffsetOfMember(S, i) = roundUp(AlignOfMember(S, i ), OffsetOfMember(S, i-1) + SizeOfMember(S, i-1))
示例:使用隐式成员大小和对齐的结构体布局
struct A {                                     //             对齐(8)  大小(24)
    u: f32,                                    // 偏移(0)   对齐(4)  大小(4)
    v: f32,                                    // 偏移(4)   对齐(4)  大小(4)
    w: vec2<f32>,                              // 偏移(8)   对齐(8)  大小(8)
    x: f32                                     // 偏移(16)  对齐(4)  大小(4)
    // -- 隐式结构体大小填充 --      // 偏移(20)            大小(4)
}

struct B {                                     //             对齐(16) 大小(160)
    a: vec2<f32>,                              // 偏移(0)   对齐(8)  大小(8)
    // -- 隐式成员对齐填充 -- // 偏移(8)             大小(8)
    b: vec3<f32>,                              // 偏移(16)  对齐(16) 大小(12)
    c: f32,                                    // 偏移(28)  对齐(4)  大小(4)
    d: f32,                                    // 偏移(32)  对齐(4)  大小(4)
    // -- 隐式成员对齐填充 -- // 偏移(36)            大小(4)
    e: A,                                      // 偏移(40)  对齐(8)  大小(24)
    f: vec3<f32>,                              // 偏移(64)  对齐(16) 大小(12)
    // -- 隐式成员对齐填充 -- // 偏移(76)            大小(4)
    g: array<A, 3>,    // 元素步幅 24       偏移(80)  对齐(8)  大小(72)
    h: i32                                     // 偏移(152) 对齐(4)  大小(4)
    // -- 隐式结构体大小填充 --      // 偏移(156)           大小(4)
}

@group(0) @binding(0)
var<storage,read_write> storage_buffer: B;
示例:具有显式成员大小和对齐的结构体布局
struct A {                                     //             对齐(8)  大小(32)
    u: f32,                                    // 偏移(0)   对齐(4)  大小(4)
    v: f32,                                    // 偏移(4)   对齐(4)  大小(4)
    w: vec2<f32>,                              // 偏移(8)   对齐(8)  大小(8)
    @size(16) x: f32                           // 偏移(16)  对齐(4)  大小(16)
}

struct B {                                     //             对齐(16) 大小(208)
    a: vec2<f32>,                              // 偏移(0)   对齐(8)  大小(8)
    // -- 隐式成员对齐填充 -- // 偏移(8)             大小(8)
    b: vec3<f32>,                              // 偏移(16)  对齐(16) 大小(12)
    c: f32,                                    // 偏移(28)  对齐(4)  大小(4)
    d: f32,                                    // 偏移(32)  对齐(4)  大小(4)
    // -- 隐式成员对齐填充 -- // 偏移(36)            大小(12)
    @align(16) e: A,                           // 偏移(48)  对齐(16) 大小(32)
    f: vec3<f32>,                              // 偏移(80)  对齐(16) 大小(12)
    // -- 隐式成员对齐填充 -- // 偏移(92)            大小(4)
    g: array<A, 3>,    // 元素步幅 32       偏移(96)  对齐(8)  大小(96)
    h: i32                                     // 偏移(192) 对齐(4)  大小(4)
    // -- 隐式结构体大小填充 --      // 偏移(196)           大小(12)
}

@group(0) @binding(0)
var<uniform> uniform_buffer: B;

14.4.3. 数组布局示例

示例:固定大小数组布局示例
// 数组,其中:
//   - 对齐为 4 = AlignOf(f32)
//   - 元素步幅为 4 = roundUp(AlignOf(f32),SizeOf(f32)) = roundUp(4,4)
//   - 大小为 32 = 步幅 * 元素数量 = 4 * 8
var small_stride: array<f32, 8>;

// 数组,其中:
//   - 对齐为 16 = AlignOf(vec3<f32>) = 16
//   - 元素步幅为 16 = roundUp(AlignOf(vec3<f32>), SizeOf(vec3<f32>))
//                          = roundUp(16,12)
//   - 大小为 128 = 步幅 * 元素数量 = 16 * 8
var bigger_stride: array<vec3<f32>, 8>;
示例:运行时大小数组布局示例
// 数组,其中:
//   - 对齐为 4 = AlignOf(f32)
//   - 元素步幅为 4 = roundUp(AlignOf(f32),SizeOf(f32)) = 4
// 如果 B 是绘制或分派命令中该绑定的有效缓冲区绑定大小,
// 则元素数量为:
//   N_runtime = floor(B / 元素步幅) = floor(B / 4)
@group(0) @binding(0)
var<storage> weights: array<f32>;

// 数组,其中:
//   - 对齐为 16 = AlignOf(vec3<f32>) = 16
//   - 元素步幅为 16 = roundUp(AlignOf(vec3<f32>), SizeOf(vec3<f32>))
//                          = roundUp(16,12)
// 如果 B 是绘制或分派命令中该绑定的有效缓冲区绑定大小,
// 则元素数量为:
//   N_runtime = floor(B / 元素步幅) = floor(B / 16)
var<storage> directions: array<vec3<f32>>;

14.4.4. 值的内部布局

本节描述在假定整体值放置位置的前提下,主机可共享值的内部 如何放置到缓冲区的字节位置中。 这些布局取决于值的类型, 以及结构体成员上的align 和size 属性。 如下所述,这些规则适用于非不透明类型;buffer 类型的值没有内部 结构,也不受这些规则约束。

放置值的缓冲区字节偏移量必须满足类型对齐要求:如果类型为 T 的值 放置在缓冲区偏移量 k 处,那么 k = c × AlignOf(T),其中 c 是某个非负整数。

无论地址空间如何,数据都将以完全相同的形式出现。

注: bool 类型不是主机可共享类型。 WGSL 规定 bool 值的大小和 对齐均为 4 字节, 但未规定 bool 值的内部布局。

当类型为u32 或i32 的值 V 放置在 主机共享缓冲区的字节偏移量 k 处时:

注: 回想一下,i32 使用二进制补码表示,因此符号位 位于第 31 位。

64 位整数布局: WebGPU API 的某些特性会将 64 位 无符号整数值写入缓冲区。当这样的值 V 出现在主机共享缓冲区的字节 偏移量 k 处时:

注: WGSL 没有具体的64 位整数类型。

类型为f32 的值 V 使用 IEEE-754 binary32 格式表示。 它包含一个符号位、8 个指数位和 23 个尾数位。 当 V 放置在主机共享缓冲区的字节偏移量 k 处时:

类型为f16 的值 V 使用 IEEE-754 binary16 格式表示。 它包含一个符号位、5 个指数位和 10 个尾数位。 当 V 放置在主机共享缓冲区的字节偏移量 k 处时:

注: 上述规则意味着主机共享缓冲区中的 数值 以小端格式存储。

当原子 类型 atomic<T> 的值 V 放置在主机共享缓冲区中时, 它具有与底层类型 T 的值相同的内部布局。

当向量类型 vecN<T> 的值 V 放置在 主机共享缓冲区的字节偏移量 k 处时:

当矩阵类型 matCxR<T> 的值 V 放置在 主机共享缓冲区的字节偏移量 k 处时:

当数组类型 A 的值 放置在主机共享内存缓冲区的字节偏移量 k 处时:

当结构体类型 S 的值放置在主机共享内存缓冲区的字节偏移量 k 处时:

14.4.5. 地址空间布局约束

storage 和uniform 地址空间 具有不同的布局约束,本节对此进行描述。

除uniform 外的所有地址空间 都具有与storage 地址空间相同的 约束。

变量直接或间接引用的所有结构体和数组类型 必须遵守 该变量地址空间的约束。 违反地址空间约束会导致着色器创建错误。

在本节中,我们将 RequiredAlignOf(S, C) 定义为 主机可共享或固定占用空间类型 S 的值在地址空间 C 中使用时的 字节偏移量对齐要求。

地址空间 C 中主机可共享或固定占用空间类型的对齐要求
主机可共享或固定占用空间类型 S, 假定 S 可以出现在 C 中 RequiredAlignOf(S, C),
支持uniform_buffer_standard_layout 或 C 不是uniform
RequiredAlignOf(S, C),
不支持uniform_buffer_standard_layout 且 C 是uniform
bool、i32、u32、f32 或f16 AlignOf(S) AlignOf(S)
atomic<T> AlignOf(S) AlignOf(S)
vecN<T> AlignOf(S) AlignOf(S)
matCxR<T> AlignOf(S) AlignOf(S)
array<T, N> AlignOf(S) roundUp(16, AlignOf(S))
array<T> AlignOf(S)
如果 C 是uniform,则不允许使用,但 buffer_view 内置函数的结果除外
roundUp(16, AlignOf(S))
不允许使用,但 buffer_view 内置函数的结果除外
struct S AlignOf(S) roundUp(16, AlignOf(S))

类型为 T 的结构体成员必须具有相对于结构体起始处的字节偏移量, 该偏移量是地址空间 C 的RequiredAlignOf(T, C) 的倍数:

OffsetOfMember(S, i) = k × RequiredAlignOf(T, C)
其中 k 是非负整数,并且结构体 S 的第 i 个成员的 类型为 T

元素类型为 T 的数组必须具有一个元素步幅,该步幅是地址空间 C 的RequiredAlignOf(T, C) 的倍数:

StrideOf(array<T, N>) = k × RequiredAlignOf(T, C)
StrideOf(array<T>) = k × RequiredAlignOf(T, C)
其中 k 是正整数

当不支持uniform_buffer_standard_layout 时, uniform 地址空间要求:

注: 以下示例展示如何在结构体成员上使用align 和size 属性, 以满足 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 地址空间的步幅要求
struct small_stride {
  a: array<f32,8> // 步幅 4
}
// 无效,步幅必须是 16 的倍数
@group(0) @binding(0) var<uniform> invalid: small_stride;

struct wrapped_f32 {
  @size(16) elem: f32
}
struct big_stride {
  a: array<wrapped_f32,8> // 步幅 16
}
@group(0) @binding(1) var<uniform> valid: big_stride;     // 有效

14.5. 内存模型

一般而言,WGSL 遵循Vulkan 内存模型。 本节其余部分描述 WGSL 程序如何映射到 Vulkan 内存模型。

注: Vulkan 内存模型是一个 形式化 Alloy 模型的文本版本。

14.5.1. 内存操作

在 WGSL 中,读访问 等价于 Vulkan 内存模型中的内存读取操作。 在 WGSL 中,写访问 等价于 Vulkan 内存模型中的内存写入操作。

当调用执行以下任一项时,会发生读访问:

当调用执行以下任一项时,会发生写访问:

原子读-修改-写内置函数执行单个 内存操作,该操作同时是读 访问和写 访问。

在任何其他情况下都不会发生读访问和写访问。 在 Vulkan 内存模型中,读访问和写访问统称为内存操作。

通过内存 视图进行的内存操作通常会恰好访问与该内存视图关联的位置集合。 例外情况是,对向量某个分量的写访问可能访问与该向量关联的所有内存 位置。

通过重排 视图进行的内存操作将访问与底层向量关联的所有内存位置。 详细信息参见重排视图读取 和重排视图 写入。

示例:访问内存位置
struct S {
  a : f32,
  b : u32,
  c : f32
}

@group(0) @binding(0)
var<storage> v : S;

fn foo() {
  let x = v.b; // 不访问 v.a 或 v.c 的内存位置。
}

14.5.2. 内存模型引用

每个模块作用域资源变量 为唯一的group 和binding 对形成一个内存模型引用。 其他每个变量(即function、 private 和workgroup 地址空间中的变量) 在变量生命周期内形成唯一的内存模型引用。

14.5.3. 有作用域操作

当调用执行有作用域操作时,它会影响一组或两组 调用。 这些集合是内存作用域和执行作用域。内存作用域指定将看到该操作所影响内存内容任何 更新的调用集合。 对于同步内置函数,这还 意味着在程序顺序上位于该函数之前的所有受影响内存操作 对在程序顺序上位于该函数之后的受影响操作可见。 执行作用域 指定可以参与某个操作的调用集合 (参见§ 15.6 集体操作)。

原子内置函数映射到原子操作,其内存作用域为:

同步内置函数映射到控制 屏障,其执行和内存作用域都是 Workgroup。

隐式和显式导数具有隐式四元组 执行作用域。

注: 如果生成的着色器中未启用 Vulkan 内存模型, 则应使用 Device 作用域代替 QueueFamily。

14.5.4. 内存语义

所有原子内置函数都使用 Relaxed 内存语义,因此不使用存储类 语义。

注: WGSL 中的地址空间等价于 SPIR-V 中的存储 类。

workgroupBarrier 使用 AcquireRelease内存语义和 WorkgroupMemory 语义。 storageBarrier 使用 AcquireRelease内存语义和 UniformMemory 语义。 textureBarrier 使用 AcquireRelease内存语义和 ImageMemory 语义。

注: 组合使用 workgroupBarrier 和 storageBarrier 时会使用 AcquireRelease 排序语义,以及 WorkgroupMemory 和 UniformMemory 两种内存 语义。

注: 没有任何原子或同步内置函数 使用 MakeAvailable 或 MakeVisible 语义。

14.5.5. 私有与非私有

storage 或 workgroup 地址空间中的所有非原子读访问都被视为 非私有,并对应于带有 NonPrivatePointer | MakePointerVisible 内存操作数、作用域为 Workgroup 的读取操作。

storage 或 workgroup 地址空间中的所有非原子写访问都被视为 非私有,并对应于 带有 NonPrivatePointer | MakePointerAvailable 内存操作数、 作用域为 Workgroup 的写入操作。

handle 地址空间中的所有非原子读访问都被视为非私有,并对应于 带有 NonPrivateTexel | MakeTexelVisible 内存操作数、 作用域为 Workgroup 的读取操作。

handle 地址空间中的所有非原子写访问都被视为非私有,并对应于 带有 NonPrivateTexel | MakeTexelAvailable 内存操作数、 作用域为 Workgroup 的写入操作。

15. 执行

§ 1.1 概述描述了着色器如何被调用,以及如何划分为调用。 本节进一步描述调用如何单独和集体执行时的约束。

15.1. 调用内的程序顺序

WGSL 模块中的每条语句在执行期间都可能执行零次或多次。 对于给定调用,给定语句的每次执行都表示一个唯一的 动态语句 实例。

当语句包含表达式时,语句的语义决定:

表达式嵌套定义了完成求值时必须满足的数据依赖关系。 也就是说,嵌套表达式必须先于其外围表达式求值。 WGSL 中表达式操作数的求值顺序是从左到右。 例如,foo() + bar() 必须先求值 foo(),再求值 bar()。 参见§ 8 表达式。

WGSL 模块中的语句按控制流顺序执行。 参见§ 9 语句和§ 11.2 函数调用。

15.2. 一致性

集体操作 (例如屏障、导数,或依赖隐式计算导数的纹理操作) 需要在 GPU 上并发运行的不同调用之间进行协调。 当所有调用并发执行该操作,即处于一致控制流中时,该操作才能正确且可移植地执行。

相反,当只有严格子集的调用执行该操作,即处于非一致控制流中时, 会产生不正确或不可移植的行为。 非正式地说,由于非一致控制依赖, 某些调用到达集体操作,而另一些没有到达, 或者没有在同一时间到达。 非一致控制依赖来自控制流 语句,其行为依赖于非一致值。

例如,当不同调用为 if、break-if、while 或for 的条件计算出 不同值, 为switch 的选择器计算出不同值, 或为短路二元运算符(&& 或 ||)的左操作数计算出不同值时, 就会产生非一致控制依赖。

这些非一致值通常可以追溯到某些未被 静态证明为一致的源。 这些源包括但不限于:

为了确保正确且可移植的行为,WGSL 实现将 执行静态一致性分析,尝试证明每个集体操作 都在一致 控制流中执行。 后续小节描述该分析。

当一致性分析无法证明某个特定集体操作 在一致 控制流中执行时,一致性失败将被 触发。

15.2.1. 术语和概念

以下定义仅供参考,旨在直观说明下一小节中的分析正在计算什么。 真正定义这些概念以及程序何时有效或违反一致性规则的是该分析本身。

对于给定的一组调用:

15.2.2. 一致性分析概述

其余小节规定一种静态分析,用于验证 集体操作只在一致控制流中执行。 如果支持subgroup_uniformity 特性,则 会存在多个一致性 作用域。 该分析会针对每个作用域执行一次。

注: 虽然分析被描述为针对每个作用域执行一次, 但实现可以执行一次包含所有作用域的分析。 工作组和绘制 一致性作用域实际上是等价的,因为它们作用于不同的 着色器阶段,并代表着色器阶段中最大的一致性作用域。

该分析假定动态 错误不会发生。 无论一致性分析结果如何,具有动态 错误的着色器阶段本身就已经不可移植。

注:此分析具有以下理想属性:

每个函数都会被分析,以尝试确保两件事:

如果这两项检查中的任意一项失败,则会触发一致性失败。

作为此工作的一部分,分析会计算有关函数的元数据,以便随后帮助分析其调用方。 这意味着必须先构建调用图,并且必须从叶节点向上分析函数, 即从不调用标准库之外任何函数的函数开始,朝入口点方向分析。 这样,每当分析某个函数时,其所有被调用方的元数据都已经计算完成。 不存在陷入循环的风险,因为语言中禁止递归。

注: 另一种等价说法是,我们对函数按照 “是某函数的(可能间接)被调用方”这一偏序进行拓扑排序,并 按该顺序分析它们。

此外,对于每个函数调用,如果该调用无法被证明处于一致控制流中, 分析会计算并传播将被触发的触发规则集合(如果有)。 我们称其为该调用的潜在触发集合。 该集合的元素取自以下可能项:

15.2.3. 分析函数的一致性要求

每个函数分两个阶段分析。

第一阶段遍历函数的语法,并根据后续小节中的 规则沿途构建一个有向图。 第二阶段探索该图,计算调用此函数的约束, 并可能触发一致性失败。

注:除四个特殊节点 RequiredToBeUniform.error、 RequiredToBeUniform.warning、 RequiredToBeUniform.info 和MayBeNonUniform 外,每个节点都可以理解为 表示以下某个陈述的真值:

一条边可以理解为从其源节点对应陈述到 其目标节点对应陈述的蕴含关系。

例如,一项一致性要求是 workgroupBarrier 内置函数必须 只在一致控制流中调用。 为表达这一点,我们从RequiredToBeUniform.error 到 workgroupBarrier调用点对应的节点添加一条边。 一种理解方式是,RequiredToBeUniform.error 对应命题 True, 因此 RequiredToBeUniform.error -> X 等价于说 X 为 true。

反过来,为表达我们无法确保某个事物的一致性(例如保存线程 id 的变量), 我们从对应节点添加一条指向MayBeNonUniform 的边。 一种理解方式是,MayBeNonUniform 对应命题 False,因此 X -> MayBeNonUniform 等价于说 X 为 false。

这种解释的一个结果是,从RequiredToBeUniform.error 可达的每个节点都对应 一个为了使程序有效而要求一致的事物,而从中可以到达 MayBeNonUniform 的每个节点 都对应一个我们无法保证其一致性的事物。 因此,如果存在从RequiredToBeUniform.error 到MayBeNonUniform 的任意 路径,就存在一致性违规,从而触发一致性失败。

节点RequiredToBeUniform.warning 和RequiredToBeUniform.info 以类似方式使用, 但它们用于帮助确定何时应触发警告或信息诊断:

如§ 2.3 诊断中所述,如果也生成了更高严重程度的诊断, 则较低严重程度的诊断可能会被丢弃。

对于每个函数,会计算两个标签:

对于函数的每个形式 参数,会计算一个或两个标签:

调用点标签值
调用点标签 描述
CallSiteRequiredToBeUniform.S,
其中 S 是以下严重程度之一:错误、警告或信息。
该函数必须仅从一致控制流中调用。 否则,将触发严重程度为 S 的诊断。

与一个潜在触发集合关联。

CallSiteNoRestriction 可以从非一致控制流中调用该函数。
函数标签值
函数标签 描述
ReturnValueMayBeNonUniform 函数的返回值可能是非一致的。
NoRestriction 该函数不会引入非一致性。
参数标签值
参数标签 描述
ParameterRequiredToBeUniform.S,
其中 S 是以下严重程度之一:错误、警告或信息。
参数必须是一致值。 如果参数类型是指针,则内存视图必须一致,但其 内容不一定必须一致。 否则,将触发严重程度为 S 的诊断。

与一个潜在触发集合关联。

ParameterContentsRequiredToBeUniform.S,
其中 S 是以下严重程度之一:错误、警告或信息。
存储在指针参数所指向内存中的值必须是一致值。 否则,将触发严重程度为 S 的诊断。

与一个潜在触发集合关联。

ParameterNoRestriction 参数值没有一致性要求。
参数 返回标签值
参数返回标签 描述
ParameterReturnContentsRequiredToBeUniform 为了使返回值成为一致值,参数必须是一致值。 如果参数是指针,则存储在该指针所指向内存中的值 也必须保持一致。
ParameterReturnNoRestriction 参数值没有一致性要求。
指针 参数标签值
指针参数标签 描述
PointerParameterMayBeNonUniform 函数调用之后,存储在指针参数所指向内存中的值可能是非一致的。
PointerParameterNoRestriction 函数调用不会影响存储在指针参数所指向内存中的值的一致性。

以下算法描述如何为给定函数计算这些标签:

注: 此时可以销毁整个图。 上述标签就是分析此函数调用方时需要记住的全部内容。 但是,该图包含可用于提供更丰富诊断的信息。 例如,一个函数中的某个值可能无法被证明为一致, 而这又会促成另一个函数中的一致性失败。 信息丰富的诊断会描述该非一致值,以及诊断 触发位置处的函数调用。

15.2.4. 指针脱糖

function 地址空间中指针类型的每个参数 都会被脱糖为一个局部变量声明, 其初始值等价于对该参数进行解引用。 也就是说,function 地址空间指针被视为局部 变量声明的别名。 初始值赋值会为 第 i个参数生成一条指向param_i_contents 的边(即 V(e) 是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 的存储类型是 S,而 v.s 的存储类型是 i32。

具有单个元素的数组也会出现类似情况:

fn foo () {
   var arr: array<i32,1>;
}

那么 arr 是完整引用,而 arr[0] 是部分引用。 它们的内存视图覆盖相同的内存 位置,但 arr 的存储类型是 array<i32,1>,而 arr[0] 的存储类型是 i32。

为简化分析,通过任何种类的部分引用进行的赋值都被视为 不会修改关联来源变量中的每个内存位置。 这使分析趋于保守,可能对比严格必要情况更多的程序触发一致性失败。

完整重排视图 是一个求值得到重排视图 sv = (p, IndexList) 的表达式,其中:

注: 通过完整重排视图进行赋值将覆盖 内存中底层向量的所有元素。

部分重排 视图是重排视图类型的表达式,但不是完整重排视图。

注: 完整重排视图可以在 着色器创建时确定,因为它只取决于 底层指针表达式是否完整以及 IndexList,二者在 着色器创建时都是已知的。 即使链接多个重排视图表达式,这一点仍然成立。

通过完整 引用或完整重排视图进行的赋值是完整赋值。

通过部分引用或部分重排视图进行的赋值是部分赋值。

当后续各节中的一致性规则提到作为RHSValue 使用的 函数作用域变量的值时,指的是对 RHSValue 表达式求值 之前该变量的值。 当后续各节中的一致性规则提到作为LHSValue 使用的 函数作用域变量的值时,指的是执行该表达式所在语句 之后该变量的值。

由于控制流语句或部分赋值, 对一个变量的多个赋值可能会到达该变量的某次使用。 分析通过对每个控制流出口处到达的赋值集合取并集, 将从控制流语句流出的多个赋值连接起来。

下表描述了连接赋值的规则。 在一致性图中,每个连接都是从结果节点到 表示值来源的节点的一条边。 它使用任意变量 x 来表述,并采用以下 记号:

将多个赋值连接到函数作用域变量的规则。
语句 结果 从结果发出的边
var x; Vin(next) V(0)
var x = e;
Vin(next) V(e)

注: 这是对 x 的完整赋值。

x = e;
r = e;
其中 r 是变量 x 的完整引用
sv = e;
其中 sv 是来源变量为 x 的完整重排视图
r = e;
其中 r 是变量 x 的部分引用
Vout(S) V(e), Vout(prev)

注: 这是对 x 的部分赋值。

注: 部分赋值包含 先前的值。 该赋值要么只写入所存储分量的一个子集, 要么写入值的类型不同于来源变量的存储类型。

sv = e;
其中 sv 是来源变量 x 的部分重排视图
s1 s2
其中 Next 位于 s1 的行为中。

注: s1 通常以 分号结尾。

Vin(s2) Vout(s1)
if e s1 else s2
其中 Next 位于 s1 和 s2 两者的行为中
Vin(next) Vout(s1), Vout(s2)
if e s1 else s2
其中 Next 位于 s1 的行为中,但不位于 s2 的行为中
Vin(next) Vout(s1)
if e s1 else s2
其中 Next 位于 s2 的行为中,但不位于 s1 的行为中
Vin(next) Vout(s2)
loop { s1 continuing { s2 } } Vin(s1) Vout(prev),
如果 s1 的行为与 {Next,Continue} 相交,则为 Vout(s2)
loop { s1 continuing { s2 } } Vin(s2) 如果 Next 位于 s1 的行为中,则为 Vout(s1),
对于 s1 中行为为 {Continue} 且将 控制转移到 s2 的所有 si,为 Vout(si)
loop { s1 continuing { s2 } } Vin(next) 如果 Break 位于 s1 的行为中,则为 Vout(s2),
对于 s1 中行为为 {Break} 且将 控制转移到 next 的所有 si,为 Vout(si)
switch e {
case _: s1
case _: s2
...
case _: s3
}
Vin(si) Vout(prev)
switch e {
case _: s1
case _: s2
...
case _: s3
}
Vin(next) Vout(si),
对于行为包含 Next 或 Break 的所有 si,以及
Vout(sj)
对于 sj 内部行为为 {Break} 且将 控制转移到 next 的所有语句

对于所有其他语句(函数调用除外),Vin(next) 等价于 Vout(prev)。

注: 这里应用与语句行为分析中相同的脱糖。

15.2.6. 语句的一致性规则

分析语句的规则同时以语句本身和语句开始处对应的 控制流节点(下文记为“CF”)作为参数,并返回以下两项:

在下表中,(CF1, S) => CF2 表示“以控制流 CF1 开始 对 S 运行分析,对图应用所需更改,并将 结果控制流节点命名为 CF2”。 类似地,(CF1, E) => V 表示“对表达式 E 运行分析, 以控制流 CF1 开始,对图应用所需更改,并将 结果值节点命名为 V”(有关表达式分析,请参见下一节)。 这种表达式求值用于任何不属于 赋值左侧的表达式,并称为 RHSValue。

对于属于 赋值左侧的表达式,也有一组类似规则,即 LHSValue, 我们将其记为 LHSValue: (CF, E) => L。它不计算 与值的一致性对应的节点,而是计算 与被赋值变量的一致性对应的节点。

注: LHSValue 包括递增和 递减 语句中的表达式。

注: RHSValue 包括作为赋值语句右侧一部分的表达式, 或不属于赋值、递增或递减语句的表达式。

当必须创建多条边时,我们使用 X -> {Y, Z} 作为 X -> Y, X -> Z 的简写。

分析循环时,我们使用以下模式:

语句的一致性规则
语句 新节点 递归分析 结果控制流节点 新边
空语句 CF
{s} (CF, s) => CF' CF'
s1 s2,
s1 的行为中包含 Next

注: s1 通常以 分号结尾。

(CF, s1) => CF1
(CF1, s2) => CF2
CF2
s1 s2,
s1 的行为中不包含 Next

注: s1 通常以 分号结尾。

(CF, s1) => CF1

注: s2 在静态上 不可达,因此不会被递归分析。 s2 不参与一致性分析。

CF1
if e s1 else s2
行为为 {Next}
(CF, e) => V
(V, s1) => CF1
(V, s2) => CF2
CF
if e s1 else s2
具有其他行为
CFend CFend CFend -> {CF1, CF2}
loop {s1}
s1 的行为中不包含 Return
CF' (CF', s1) => CF1 CF CF' -> {CF1, CF},
如果 s1 的行为与 {Next,Continue} 相交
CF' -> CF,
如果 s1 的行为不与 {Next,Continue} 相交
loop {s1}
s1 的行为中包含 Return
CF' (CF', s1) => CF1 CF1 CF' -> {CF1, CF}
如果 s1 的行为与 {Next,Continue} 相交
CF' -> CF
如果 s1 的行为不与 {Next,Continue} 相交
loop {s1 continuing {s2}}
s1 的行为为 {Break}
(CF, s1) => CF1 CF

注: 循环只执行一次 迭代,因此不需要额外的边。

loop {s1 continuing {s2}}
s1 的行为为 {Return} 或 {Break,Return}
(CF, s1) => CF1 CF1
loop {s1 continuing {s2}}
s1 的行为与 {Next,Continue} 相交
CF' (CF', s1) => CF1
(CF1, s2) => CF2
CF
如果 s1 的行为中不包含 Return
CF' -> {CF2, CF}
CF'
如果 s1 的行为中包含 Return
switch e case _: s_1 .. case _: s_n
行为 为 {Next}
(CF, e) => V
(V, s_1) => CF_1
...
(V, s_n) => CF_n
CF
switch e case _: s_1 .. case _: s_n
具有其他 行为
CFend CFend CFend -> {CF_1, ..., CF_n}
var x: T; CF

注: 如果 x 是function 地址空间变量,则 CF 在值分析中用作零值初始化器。

break;
continue;
break if e; CFend (CF, e) => V CFend CFend -> V

注: 从 CFend 到 V 的边表示以下事实:当 条件值非一致时,此 break if 语句产生的控制流 也非一致。

return; CF 对于每个function 地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(参见§ 15.2.5 函数作用域 变量值分析)
return e; (CF, e) => V CF Value_return -> V

对于每个function 地址空间指针参数 i, Value_return_i_contents -> Vin(prev)(参见§ 15.2.5 函数作用域 变量值分析)

e1 = e2; LHSValue: (CF, e1) => LV
(CF, e2) => RV
CF LV -> RV

注: LV 是 值分析的结果值。

_ = e (CF, e) => V CF
let x = e; (CF, e) => V CF
var x = e; (CF, e) => V CF

注: 如果 x 是function 地址空间变量, 则 V 在值 分析中用作结果值。

f()
无参数的函数调用语句
调用函数调用分析: (CF, f()) => Result CF
f(e1,...,eN)
带参数的函数调用语句
调用函数调用分析: (CF, f(e1,...,eN)) => Result CF

为了此分析:

为了最大化性能,实现通常会尝试尽量减少 非一致控制 流的数量。 但是,可认为调用达到一致状态的位置会因 多种因素而异。WGSL 的静态分析保守地 假定当if、switch 和loop 语句的 行为为 {Next} 时,会在这些语句末尾恢复一致控制流。 在前面的表格中,这通过将结果控制流节点 设为与输入控制流节点相同来建模。

15.2.7. 函数调用的一致性规则

最复杂的规则用于函数调用:

注: 有关 Vout(call) 的定义,请参见§ 15.2.5 函数作用域变量值分析。

大多数内置函数具有以下标签:

以下是例外情况列表:

注: WGSL 实现将确保,如果函数调用 之前的控制流对于特定作用域是一致的, 那么函数调用之后也将保持一致。

15.2.8. 表达式的一致性规则

分析表达式的规则同时以表达式本身以及表达式开始处对应的 控制流节点(下文记为“CF”)作为参数,并返回以下内容:

RHSValue 表达式的一致性规则
表达式 新节点 递归分析 结果值节点 新边
e1 || e2 (CF, e1) => V1
(V1, e2) => V2
V2
e1 && e2
字面量 CF
标识符解析到 函数作用域变量 "x", 其中该标识符作为内存视图 表达式 MVE 的根标识符出现,并且在 类型检查期间对 MVE 调用加载规则 Result X 是与包含此表达式的语句输入处 "x" 的值对应的节点 Result Result -> {CF, X}

注: X 等价于 "x" 的 Vout(prev)
(参见§ 15.2.5 函数作用域变量值分析)

标识符解析到 函数作用域变量 "x", 其中该标识符作为重排 视图表达式 SVE 的根标识符出现,并且在 类型检查期间对 SVE 调用重排视图加载规则
标识符解析到 函数作用域变量 "x", 其中 "x" 是脱糖后的指针参数 i,并且 该标识符作为内存视图 表达式 MVE 的根标识符出现,并且在 类型检查期间 未对 MVE 调用加载规则 param_i
标识符解析到 函数作用域变量 "x", 其中 "x" 是脱糖后的指针参数 i,并且 该标识符作为重排 视图表达式 SVE 的根标识符出现,并且在 类型检查期间 对 SVE 调用重排视图加载规则
标识符解析到 函数作用域变量 "x", 其中该标识符作为内存视图 表达式 MVE 的根标识符出现,并且在 类型检查期间 未对 MVE 调用加载规则 CF
标识符解析到 函数作用域变量 "x", 其中该标识符作为重排 视图表达式 SVE 的根标识符出现,并且在 类型检查期间 未对 SVE 调用重排视图加载规则
标识符解析到 const 声明、override 声明、 let 声明,或非内置的非指针类型形式参数 "x" Result X 是与 "x" 对应的节点 Result Result -> {CF, X}
标识符解析到一个 位于storage、workgroup 或private地址空间中、具有非只读 访问模式的指针类型形式 参数,其中该标识符作为内存视图 表达式 MVE 的根标识符出现,并且在 类型检查期间对 MVE 调用加载规则 MayBeNonUniform
标识符解析到一个 位于storage、workgroup 或private地址空间中、具有非只读 访问模式的指针类型形式 参数,其中该标识符作为重排视图表达式 SVE 的根标识符出现,并且在类型检查期间 对 SVE 调用重排视图加载规则
标识符解析到一个 位于storage、workgroup 或private地址空间中、具有非只读 访问模式的指针类型形式 参数,其中该标识符作为内存视图 表达式 MVE 的根标识符出现,并且在类型检查期间未 对 MVE 调用加载规则 CF
标识符解析到一个 位于storage、workgroup 或private地址空间中、具有非只读 访问模式的指针类型形式 参数,其中该标识符作为重排视图表达式 SVE 的根标识符出现,并且在类型检查期间未 对 SVE 调用重排视图加载规则
标识符解析到 位于function 以外的地址空间中、具有只读访问模式的指针类型形式 参数 CF
标识符解析到 一致的内置值 "x" CF
标识符解析到 非一致的内置值 "x" MayBeNonUniform
标识符解析到 只读模块作用域变量 "x" CF
标识符解析到 非只读模块作用域 变量 "x",其中该标识符作为内存视图 表达式 MVE 的根标识符出现,并且在 类型检查期间对 MVE 调用加载规则 MayBeNonUniform
标识符解析到 非只读模块作用域 变量 "x",其中该标识符作为重排 视图表达式 SVE 的根标识符出现,并且在 类型检查期间 对 SVE 调用重排视图加载规则
标识符解析到 非只读模块作用域 变量 "x",其中该标识符作为内存视图 表达式 MVE 的根标识符出现,并且在 类型检查期间 未对 MVE 调用加载规则 CF
标识符解析到 非只读模块作用域 变量 "x",其中该标识符作为重排 视图表达式 SVE 的根标识符出现,并且在 类型检查期间 未对 SVE 调用重排视图加载规则
( e ) (CF, e) => V V
op e,
其中 op 是一元运算符
e.field,其中 field 是结构体成员名称
e.swiz,其中 swiz 是重排名称
e1 op e2,
其中 op 是非短路二元 运算符
Result (CF, e1) => V1
(CF, e2) => V2
Result Result -> {V1, V2}
e2[e1]
f()
无参数的函数调用表达式
调用函数调用分析:
(CF, f()) => Result
Result
f(e1,...,eN)
带参数的函数调用表达式
调用函数调用分析: (CF, f(e1,...,eN)) => Result Result

以下内置输入变量被视为一致:

在子组 一致性作用域中,以下内置 输入变量也被视为一致:

所有其他内置值(参见内置 值)都被视为非一致。

注: 作者应避免将一致的 内置值与其他非一致输入组合在一起,因为分析不会分别分析复合类型的分量。

LHSValue 表达式的一致性规则
表达式 新节点 递归分析 结果变量节点 新边
标识符解析到 函数作用域变量 "x" Result X 是与包含此表达式的语句输出处 "x" 的值 对应的节点。 Result Result -> {CF, X}

注: X 等价于 "x" 的 Vin(next)
(参见§ 15.2.5 函数作用域变量值分析)

标识符解析到 const 声明、override 声明、 let 声明或形式参数 "x" X 是与 "x" 对应的节点 X
标识符解析到 模块作用域变量 "x" MayBeNonUniform
e.field,其中 field 是结构体成员名称 LHSValue: (CF, e) => L1 L1
e.swiz,其中 swiz 是重排名称
*e
&e
e2[e1] (CF, e1) => V1
LHSValue: (CF, e2) => L2
L2 L2 -> V1
f()
无参数的函数调用表达式
调用函数调用分析:
LHSValue: (CF, f()) => Result
Result
f(e1,...,eN)
带参数的函数调用表达式
调用函数调用分析: LHSValue: (CF, f(e1,...,eN)) => Result Result

15.2.9. 标注控制流中每个点的一致性

整个小节都是非规范性的。

如果实现者希望向开发者提供一种诊断模式,用来显示整个着色器 控制流中的每个点是否一致(从而表明在那里调用一个 要求一致性的函数是否有效),我们建议如下:

任何未被这些可达性分析访问的节点都可以由分析证明为一致 (因此在那里调用导数或类似函数是安全的)。

注: 自底向上的分析仍然是必需的,因为它 让我们知道在遇到调用时应向图中添加哪些边。

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 缓冲区读取相同的值),程序也将 有效。

15.2.10.2. 函数作用域变量一致性

此示例展示了一个有效和一个无效的 屏障函数调用,它们依赖于 函数作用域变量的值。 workgroupBarrier 无效,因为 x 的值派生自 可变模块作用域变量 a。 storageBarrier 有效,因为 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 {
    // 无效的屏障函数调用。
    workgroupBarrier();
  }
  x = b;
  if x < 0 {
    // 有效的屏障函数调用。
    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.S 到 MayBeNonUniform 的路径 可以看出这一点。

示例:有效的替代 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 {
    // 有效的屏障函数调用。
    workgroupBarrier();
  }
}
有效的替代一致性图
15.2.10.4. 循环中的一致性

在此示例中,循环内有一个无效的 workgroupBarrier 函数调用。 尽管非一致的内置值 local_invocation_index 出现在循环中的屏障之后, 它仍然是最终原因。 这是因为,在后续迭代中,工作组中的某些调用 会提前退出循环,而其他调用则尝试执行 屏障。 分析使用一条边对迭代间依赖进行建模,其中循环体开始处的控制 (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。 这导致 main 中在 scale 函数调用返回值与 position 内置值之间形成路径。 该路径是从RequiredToBeUniform.S 到 MayBeNonUniform 的整体无效路径的子路径。

示例:用户定义函数调用一致性 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 地址空间中着色器变量的访问。

计算着色器的工作组网格是 具有整数坐标 (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 实现 从队列中移除分派命令并开始在 GPU 上执行指定工作时,计算着色器开始执行。 分派命令指定一个分派大小, 它是一个整数三元组(group_count_x、group_count_y、group_count_z), 表示要执行的工作组数量,如下所述。

特定分派的计算着色器 网格是 具有整数坐标 (CSi,CSj,CSk) 且满足以下条件的点集合:

其中 workgroup_size_x、 workgroup_size_y 和 workgroup_size_z 如上所述,由计算着色器入口点指定。

计算着色器分派要执行的工作,是针对计算着色器网格中的每个点 恰好执行一次入口点调用。

调用的全局 调用 ID是与该调用对应的计算着色器网格点的 坐标三元组。

调用被组织成工作组,使每个调用的 全局调用 ID (CSi, CSj, CSk) 映射到 由工作组 ID标识的单个工作组:

( ⌊ CSi ÷ workgroup_size_x ⌋, ⌊ CSj ÷ workgroup_size_y ⌋, ⌊ CSk ÷ workgroup_size_z ⌋)

以及该工作组中由局部调用 ID标识的单个调用:

( CSi mod workgroup_size_x , CSj mod workgroup_size_y , CSk mod workgroup_size_z ).

注: 工作组 ID 的范围从 (0,0,0) 到 (group_count_x - 1, group_count_y - 1, group_count_z - 1)。

如果支持linear_indexing 特性,则当 工作组具有工作组 ID (WGi, WGj, WGk) 时,其 工作组索引为:

WGi + ( WGj × group_count_x ) + ( WGk × group_count_x × group_count_y )

如果支持linear_indexing 特性,则当一个 调用具有全局调用 ID (CSi, CSj, CSk) 时,其 全局调用 索引为:

CSi + ( CSj × workgroup_size_x × group_count_x ) + ( CSk × workgroup_size_x × group_count_x × workgroup_size_y × group_count_y )

WebGPU 不保证以下任何事项:

15.4. 片段着色器和辅助调用

片段着色器阶段中的调用会被划分为 2x2 调用网格, 这些调用在 X 和 Y 维度具有相邻的位置。 每个这样的网格称为一个四元组。 四元组可以在某些集体操作中协作(参见§ 15.6.2 导数)。 调用的四元组 调用 ID是四元组内的唯一 ID,其中:

注: 没有用于四元组 ID 的内置值访问器。

通常,片段处理会为 光栅化产生的每个 RasterizationPoint 创建一次片段着色器调用。 有时 RasterizationPoint 的数量可能不足以完全填满 四元组,例如在图形图元的边缘。 当一个四元组只有 1、2 或 3 个与 RasterizationPoint 对应的调用时,片段处理将为 四元组中每个未填充的位置创建一个辅助调用。

辅助调用具有有限的可观察效果。 它们有助于计算导数,并且可以参与 子组操作。 因此,辅助调用受到以下限制:

如果四元组中的所有调用都变成辅助调用(例如由于 执行discard 语句),则该四元组的执行可能 被终止;但是,这种终止不被认为会产生非一致控制流。

15.5. 子组

子组是一组 并发执行计算或片段着色器阶段入口点的调用,并且它们可以高效地共享 数据和集体计算结果。 计算或片段着色器中的每个调用恰好属于一个子组。 在计算着色器中,每个子组都是特定工作组的子集。 在片段着色器中,一个子组可能包含来自多个绘制命令的调用。 每个四元组都将包含在单个 子组中。

子组大小是 一个子组中的最大调用数量。 在着色器中,可以通过subgroup_size 内置值访问该值。 子组大小在一个分派命令内是一致 值,因此在一个工作组内也是如此, 但它在一个绘制命令内可能不是一致值。 所有子组大小都是范围 [4, 128] 内的 2 的幂,并且针对特定设备编译的着色器 的值将位于该WebGPU § 4.3 GPUAdapter 的 [subgroupMinSize, subgroupMaxSize] 范围内。 实际大小取决于着色器、设备属性和设备编译器。 每个设备支持可能的子组大小范围的一个子集(可能只有一个值)。 设备编译器使用多种启发式方法从支持的大小中选择一个。 每个子组包含的调用数可能少于所报告的子组大小 (例如,启动的调用少于子组大小时)。

调用的子组调用 ID是子组内的唯一 ID。 此 ID 可通过subgroup_invocation_id 内置值访问,并位于范围 [0, subgroup_size - 1] 内。

如果支持subgroup_id 特性,则在 计算着色器中,子组 ID是该 子组在工作组内的唯一 ID。 此 ID 可通过subgroup_id 内置值访问, 并位于范围 [0, num_subgroups - 1] 内。

子组值(即 subgroup_invocation_id 和 subgroup_id)与local_invocation_index 之间没有定义的关系。 为避免不可移植的代码,着色器作者不应假定 这两种值之间存在特定映射。

当同一子组中的调用执行不同的控制流路径时,我们 称子组执行发生了分歧。 这是非一致控制流的一种特殊情况。 分歧会影响子组操作的语义。 子组中并发执行某个子组操作的调用 对该操作是活跃的。 子组中的其他调用对该操作是非活跃的。 当子组大小超过一个子组中的调用数量时, 额外的假想调用被视为非活跃。 辅助调用在操作中可能 是活跃的,也可能是非活跃的。 也就是说,在某些设备上,辅助调用可能参与子组操作, 而在其他设备上则可能不会。

注: 在非一致控制流中 运行时,底层设备之间存在相当大的不可移植性,而且设备编译器通常会积极 优化此类代码。 结果是,子组可能包含与着色器作者预期不同的一组活跃 调用。

15.6. 集体操作

15.6.1. 屏障

屏障是一种同步内置函数, 用于对程序中的内存操作进行排序。 控制屏障由 同一工作组中的所有调用执行,就好像它们是并发执行的一样。 因此,控制屏障必须仅在计算着色器的一致控制流中执行。

15.6.2. 导数

偏导数 是一个值沿某个轴的变化率。 同一四元组内的片段着色器调用 会协作计算 近似偏导数。

计算导数的内置函数包括:

片段坐标的偏导数会作为 以下内置函数操作的一部分被隐式计算:

对于这些函数,导数有助于确定要采样的纹素 mip 层级,或者对于 textureSampleCompare,确定要采样并与参考值比较的纹素。

调用指定的值的偏导数由 § 17.6 导数内置 函数中描述的内置函数计算:

由于相邻调用会协作计算导数,因此这些 函数应仅在片段着色器的一致控制流中调用。 对这些函数的每次调用,如果一致性分析无法证明该调用发生在一致控制流中,则会触发derivative_uniformity诊断。

如果这些函数之一在非一致控制流中调用,则结果为不确定 值。

注: 导数是一种隐式的四元组操作。 使用它们不需要subgroups 扩展。

15.6.3. 子组操作

子组内置函数允许子组中的调用之间进行高效 通信和计算。 子组操作是单指令多线程(SIMT)操作。

子组中的活跃调用 会相互通信以确定结果。 因此,当所有 调用都处于活跃状态时(即在子组 级别的一致控制流中)调用这些函数,可最大程度提高可移植性。

15.6.4. 四元组操作

四元组内置函数作用于一个调用四元组。 它们对于四元组内部的数据通信很有用。

四元组中的活跃调用 会相互通信以确定结果。 因此,当所有 调用都处于活跃状态时(即在四元组级别的一致控制流中)调用这些函数,可最大程度提高可移植性。

15.7. 浮点评估

WGSL 浮点特性基于IEEE-754 浮点标准, 但减少了一些功能,以反映 GPU 所作的折衷,并增加了一些 用于可移植性的额外保护措施。

15.7.1. IEEE-754 概述

WGSL 浮点类型基于IEEE-754 二进制浮点类型。

IEEE-754 二进制浮点类型按如下方式近似 扩展实数数轴:

浮点类型的有限范围是 区间 [low, high], 其中 low 是该类型中的最低有限值,而 high 是该类型中的最高有限值。

这里关注的 IEEE-754 浮点类型包括:

以下算法将浮点值的位表示映射到其对应的扩展实数值或 NaN:

算法: 浮点位表示解释

输入:Bits,二进制浮点 类型中某个值的位表示。

输出: F,由 Bits 表示的浮点值。

过程:

浮点操作的定义域是 使该操作定义良好的扩展 实数输入集合。

舍入将一个扩展实数值 x 映射到浮点类型中的值 x'。 当 x 属于该浮点类型时,舍入将 x 映射到其自身:x = x'。 当 x 位于该类型的有限范围之外时,舍入可能溢出。 否则,x' 要么是大于 x 的最低浮点值,要么是 小于 x 的最高浮点值; 舍入模式决定选择哪一个。

一般而言,具有NaN 输入的操作将产生 NaN 输出。 例外包括:

IEEE-754 定义了五种异常:

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,该操作的正确 舍入结果为:

也就是说,结果可以向上或 向下舍入: WGSL 不指定舍入模式。

注: 浮点类型包括正无穷和负 无穷,因此 正确舍入后的结果可能是有限值,也可能是无穷值。

注: 使用无限 精度计算某个操作的结果时,可能需要超过 double 的精度。 例如 x - y 中 x=1.0 且 y=1.17e-38( 最小正正规单精度浮点数)。 这些数的指数相差 126。IEEE-754 binary64(双 精度)格式的有效数字只有 52 位, 因此在执行减法时,y 的所有有效位都会丢失。 根据舍入模式,对于此情况以及许多其他 y 很小但非零的情况, WGSL 表达式 x - y 可能产生与 x 相同的值。 注意,[ECMASCRIPT] 使用等价于IEEE-754 roundTiesToEven 的舍入模式。

浮点 数 x 的末位单位,ULP, 定义如下[Muller2005]:

操作的精度由以下五种 可能形式之一给出:

当某项操作的精度是在一个输入范围内指定时, 对于该范围之外的输入值,精度未定义。

如果允许的结果位于结果类型的有限范围之外,则应用 § 15.7.3 浮点舍入和 溢出中的规则。

15.7.4.1. 具体浮点表达式的精度
具体浮点操作的精度
表达式 f32 的精度 f16 的精度
x + y 正确舍入
x - y 正确舍入
x * y 正确舍入
x / y 当 |y| 位于范围 [2-126, 2126] 内时为 2.5 ULP 当 |y| 位于范围 [2-14, 214] 内时为 2.5 ULP
x % y 继承自 x - y * trunc(x/y)
-x 正确舍入
x == y 正确结果
x != y 正确结果
x < y 正确结果
x <= y 正确结果
x > y 正确结果
x >= y 正确结果
具体浮点内置函数的精度
内置函数 f32 的精度 f16 的精度
abs(x) 正确舍入
acos(x) 以下两者中较差者:
  • 绝对误差 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) 当 |x| 位于范围 [2-126, 2126] 内,且 y 是有限正规数时为 4096 ULP 当 |x| 位于范围 [2-14, 214] 内,且 y 是 有限正规数时为 5 ULP
atanh(x) 继承自 log( (1.0 + x) / (1.0 - x) ) * 0.5
ceil(x) 正确舍入
clamp(x,low,high) 正确舍入。

无限精确结果计算为 min(max(x,low),high),或者 使用三个值取中位数的形式。 当 low > high 时,它们可能不同。

如果 x 与 low 或 high 中任一个都是次正规数,则结果可以是任意次正规值。 这是由 min 和 max 函数在次正规输入上的可能结果所导致的。

cos(x) 当 x 位于区间 [-π, π] 内时,绝对误差至多为 2-11 当 x 位于区间 [-π, π] 内时,绝对误差至多为 2-7
cosh(x) 继承自 (exp(x) + exp(-x)) * 0.5
cross(x, x) 继承自 (x[i] * y[j] - x[j] * y[i]), 其中 i ≠ j
degrees(x) 继承自 x * 57.295779513082322865
determinant(m:mat2x2<T>)
determinant(m:mat3x3<T>)
determinant(m:mat4x4<T>)
无限 ULP。
注:WebGPU 实现应提供一个在实践中 有用的 determinant 函数。

在理想数学中,行列式使用加法、减法和乘法操作计算。

但是,GPU 使用浮点数学,而 GPU 的 determinant 实现 更偏向速度和简单性,而不是防范溢出和误差的稳健性。

例如,即使是 2x2 行列式的朴素计算 (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) 正确舍入

如果 x 和 y 都是次正规数,则结果可以 是任一输入。

min(x, y) 正确舍入。

如果 x 和 y 都是次正规数,则结果可以 是任一输入。

mix(x, y, z) 继承自 x * (1.0 - z) + y * z
modf(x) 正确舍入
normalize(x) 继承自 x / length(x)
pack4x8snorm(x) 正确舍入的中间结果 值。正确结果。
pack4x8unorm(x) 正确舍入的中间结果 值。正确结果。
pack2x16snorm(x) 正确舍入的中间结果 值。正确结果。
pack2x16unorm(x) 正确舍入的中间结果 值。正确结果。
pack2x16float(x) 正确舍入的中间结果 值。正确结果。
pow(x, y) 继承自 exp2(y * log2(x))
quantizeToF16(x) 正确舍入
radians(x) 继承自 x * 0.017453292519943295474
reflect(x, y) 继承自 x - 2.0 * dot(x, y) * y
refract(x, y, z) 继承自 z * x - (z * dot(y, x) + sqrt(k)) * y,
其中 k = 1.0 - z * z * (1.0 - dot(y, x) * dot(y, x))
如果 k < 0.0, 结果恰好为 0.0
round(x) 正确舍入
sign(x) 正确舍入
sin(x) 当 x 位于区间 [-π, π] 内时,绝对误差至多为 2-11 当 x 位于区间 [-π, π] 内时,绝对误差至多为 2-7
sinh(x) 继承自 (exp(x) - exp(-x)) * 0.5
saturate(x) 正确舍入
smoothstep(edge0, edge1, x) 继承自 t * t * (3.0 - 2.0 * t),
其中 t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0)
sqrt(x) 继承自 1.0 / inverseSqrt(x)
step(edge, x) 正确舍入
tan(x) 继承自 sin(x) / cos(x)
tanh(x) 以下两者中较差者:
  • 绝对误差 1.0×10-5

  • 继承自 sinh(x) / cosh(x)

transpose(x) 正确舍入
trunc(x) 正确舍入
unpack4x8snorm(x) 3 ULP 不适用
unpack4x8unorm(x) 3 ULP 不适用
unpack2x16snorm(x) 3 ULP 不适用
unpack2x16unorm(x) 3 ULP 不适用
unpack2x16float(x) 正确舍入 不适用
subgroupBroadcast(x, i) 正确舍入
subgroupBroadcastFirst(x) 正确舍入
subgroupAdd(x) 继承自子组中所有活跃调用的 x 之和
subgroupExclusiveAdd(x) 继承自子组中所有活跃调用的 x 之和,其中这些调用的子组调用 ID小于当前 调用的 ID。
subgroupInclusiveAdd(x) 继承自子组中所有活跃调用的 x 之和,其中这些调用的子组调用 ID小于或等于 当前调用的 ID。
subgroupMul(x) 继承自子组中所有活跃调用的 x 之积
subgroupExclusiveMul(x) 继承自子组中所有活跃调用的 xi 之积, 其中这些调用的子组调用 ID小于第 i 个 调用的 ID
subgroupInclusiveMul(x) 继承自子组中所有活跃调用的 xi 之积, 其中这些调用的子组调用 ID小于或等于第 i 个 调用的 ID
subgroupMax(x) 继承自子组中所有活跃调用的 max(x)
subgroupMin(x) 继承自子组中所有活跃调用的 min(x)
subgroupShuffle(x, id) 正确舍入
subgroupShuffleDown(x, delta) 正确舍入
subgroupShuffleUp(x, delta) 正确舍入
subgroupShuffleXor(x, mask) 正确舍入
quadBroadcast(x, id) 正确舍入
quadSwapDiagonal(x) 正确舍入
quadSwapX(x) 正确舍入
quadSwapY(x) 正确舍入
15.7.4.2. AbstractFloat 表达式的精度

AbstractFloat 操作的精度如下:

注:给定的绝对误差界限,当以 ULP 量化时, 会关键地取决于底层浮点类型。

AbstractFloat 值的ULP 假定 AbstractFloat 与IEEE-754 binary64 类型相同。

f32 值的一个ULP 比 IEEE-754 binary64 值的 1 ULP 大 229 倍, 因为 binary64 格式中的有效数字比 f32 类型中的有效数字长 29 位。

例如,假设某项操作的真实结果值为 x,但计算结果为 x'。 如果其误差 x-x' 在 f32 中为 3 ULP,则相同的绝对误差 x-x' 在 AbstractFloat 中为 3·229 ULP。

15.7.5. 重新结合和融合

重新结合是 对表达式中的操作重新排序,使得在精确计算时答案保持相同。例如:

但是,使用浮点计算时,结果可能不同。 重新结合后的结果可能由于近似而不准确,或者在计算中间结果时触发 溢出或 NaN。

实现可以重新结合操作。

如果变换后的表达式至少与原始形式一样精确, 实现可以融合操作。 例如,一些融合乘加实现可以比 先执行乘法再执行加法更精确。

15.7.6. 浮点转换

本节描述源类型或目标类型之一是 浮点类型时标量转换的详细信息。

在本节中,浮点类型可以是以下任一种:

注: 回想一下,WGSL 的f32 类型对应于 IEEE-754 binary32 格式,而 WGSL 的f16 类型对应于 IEEE-754 binary16 格式。

标量浮点到整数转换 算法如下:

要将浮点标量值 X 转换为整数标量类型 T:

注: 换句话说,对于非 NaN 情况,浮点 到整数转换 先将值限制在目标类型的范围内,然后向零舍入。 此限制要求是 WGSL 强制要求产生有意义结果的情形之一, 而在 C 和 C++ 中会产生未定义行为, 在IEEE-754 中则要求产生无效操作异常 和 NaN 结果。

注: 例如:

数值标量到浮点的转换 算法如下:

算法: 数值标量转换到浮点

输入:

输出: XOut,将 X 转换为类型 T 的结果,或 生成错误。

过程:

注: 一个整数值可能位于两个相邻 可表示浮点值之间。 特别是,f32 类型使用 23 个显式 小数位。 此外,当浮点值位于正规范围内(指数不是任一极值)时, 有效数字 是这些小数位再加上最高有效位置位 23 处的额外 1 位。 例如,整数 228 和 1+228 都映射到相同的浮点值: 最低有效 1 位的差异无法由浮点格式表示。 对于大小至少为 225 的相邻整数对,会发生这种碰撞。

注: 当 原始类型是i32 或u32 之一,而目标类型是f32 时,原始值始终位于 目标类型的范围内。

注: 当 源类型是指数位和有效数字位都少于目标浮点类型的浮点类型时,原始值始终位于 目标类型的范围内。

15.7.7. 浮点表达式和内置 函数的定义域

前面的各节描述了浮点表达式在其定义域之外求值时的预期行为。

§ 8.8 算术表达式和§ 17.5 数值内置函数分别定义了浮点表达式和 内置函数的定义域。 如果某个给定操作没有列出限制,则其定义域是全集:定义域包括所有有限值 和无穷值输入。 否则会明确列出定义域。

在许多 WGSL 操作与IEEE-754 定义的操作相对应的情况下,它们将具有 相同的定义域。 例如,WGSL 和 IEEE-754 的 acos 操作都具有 [−1,1] 的定义域。

对于显式列出定义域的逐分量 WGSL 操作,只描述标量情况。向量情况根据逐分量语义推导。

某些 WGSL 操作可以用其他 WGSL 表达式来实现。 § 15.7.4 浮点精度将这些操作列为具有继承自精度。 列出这些操作之一的定义域时,要么:

例如:针对两个 2 元素向量 a 和 b 的 dot(a,b) 函数, 其精度继承自表达式 a[0] * b[0] + a[1] * b[1]。 这使用两次浮点乘法和一次浮点加法。

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 为所有预声明类型和所有可构造结构体类型提供值构造器。 这样的构造器内置函数与类型具有相同拼写,或者与该类型的类型别名具有相同拼写。 无论在哪里使用这样的内置函数,该标识符 必须位于该类型或类型 别名的作用域内,并且该标识符 不得解析到其他声明。

注: frexp、 modf 和atomicCompareExchangeWeak 返回的结构体类型无法 在 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 值。

如果 T 是bool,则这是 恒等操作。
否则,这是布尔强制转换。 如果 e 是零值(对于浮点 类型也包括 -0.0),则结果为 false,否则为 true。

17.1.2.3. f16
重载
@const @must_use fn f16(e : T) -> f16
参数化 T 是标量类型
描述 构造一个f16 值。

如果 T 是f16,则这是 恒等操作。
如果 T 是数值标量(f16 除外),则将 e 转换为f16(包括无效 转换)。
如果 T 是bool,则 当 e 为 true 时结果为 1.0h,否则为 0.0h。

17.1.2.4. f32
重载
@const @must_use fn f32(e : T) -> f32
参数化 T 是具体标量类型
描述 构造一个f32 值。

如果 T 是f32,则这是 恒等操作。
如果 T 是数值标量(f32 除外),则将 e 转换为f32(包括无效 转换)。
如果 T 是bool,则 当 e 为 true 时结果为 1.0f,否则为 0.0f。

17.1.2.5. i32
重载
@const @must_use fn i32(e : T) -> i32
参数化 T 是标量类型
描述 构造一个i32 值。

如果 T 是i32,则这是 恒等操作。
如果 T 是u32,则这是 位的重新解释(即结果是i32 中与 e 具有相同位模式的唯一值)。
如果 T 是浮点类型, 则将 e转换为i32,并舍入到零。
如果 T 是bool,则 当 e 为 true 时结果为 1i,否则为 0i。
如果 T 是AbstractInt,且 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>
参数化 T 是f16 或f32
S 是AbstractFloat、f16 或f32
描述 2x2 列主序矩阵的构造器。

如果 T 与 S 不匹配,则会发生转换。

重载
@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>
参数化 T 是AbstractFloat、f16 或f32
描述 从列向量构造一个 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>
参数化 T 是AbstractFloat、f16 或f32
描述 从元素构造一个 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>
参数化 T 是f16 或f32
S 是AbstractFloat、f16 或f32
描述 2x3 列主序矩阵的构造器。

如果 T 与 S 不匹配,则会发生转换。

重载
@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>
参数化 T 是AbstractFloat、f16 或f32
描述 从列向量构造一个 2x3 列主序矩阵。
重载
@const @must_use fn mat2x3<T>(e1 : T, ..., e6 : T) -> mat2x3<T>
@const @must_use fn mat2x3(e1 : T, ..., e6 : T) -> mat2x3<T>
参数化 T 是AbstractFloat、f16 或f32
描述 从元素构造一个 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>
参数化 T 是f16 或f32
S 是AbstractFloat、f16 或f32
描述 2x4 列主序矩阵的构造器。

如果 T 与 S 不匹配,则会发生转换。

重载
@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>
参数化 T 是AbstractFloat、f16 或f32
描述 从列向量构造一个 2x4 列主序矩阵。
重载
@const @must_use fn mat2x4<T>(e1 : T, ..., e8 : T) -> mat2x4<T>
@const @must_use fn mat2x4(e1 : T, ..., e8 : T) -> mat2x4<T>
参数化 T 是AbstractFloat、f16 或f32
描述 从元素构造一个 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>
参数化 T 是f16 或f32
S 是AbstractFloat、f16 或f32
描述 3x2 列主序矩阵的构造器。

如果 T 与 S 不匹配,则会发生转换。

重载
@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>
参数化 T 是AbstractFloat、f16 或f32
描述 从列向量构造一个 3x2 列主序矩阵。
重载
@const @must_use fn mat3x2<T>(e1 : T, ..., e6 : T) -> mat3x2<T>
@const @must_use fn mat3x2(e1 : T, ..., e6 : T) -> mat3x2<T>
参数化 T 是AbstractFloat、f16 或f32
描述 从元素构造一个 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>
参数化 T 是f16 或f32
S 是AbstractFloat、f16 或f32
描述 3x3 列主序矩阵的构造器。

如果 T 与 S 不匹配,则会发生转换。

重载
@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>
参数化 T 是AbstractFloat、f16 或f32
描述 从列向量构造一个 3x3 列主序矩阵。
重载
@const @must_use fn mat3x3<T>(e1 : T, ..., e9 : T) -> mat3x3<T>
@const @must_use fn mat3x3(e1 : T, ..., e9 : T) -> mat3x3<T>
参数化 T 是AbstractFloat、f16 或f32
描述 从元素构造一个 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>
参数化 T 是f16 或f32
S 是AbstractFloat、f16 或f32
描述 3x4 列主序矩阵的构造器。

如果 T 与 S 不匹配,则会发生转换。

重载
@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>
参数化 T 是AbstractFloat、f16 或f32
描述 从列向量构造一个 3x4 列主序矩阵。
重载
@const @must_use fn mat3x4<T>(e1 : T, ..., e12 : T) -> mat3x4<T>
@const @must_use fn mat3x4(e1 : T, ..., e12 : T) -> mat3x4<T>
参数化 T 是AbstractFloat、f16 或f32
描述 从元素构造一个 3x4 列主序矩阵。

与 mat3x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8), vec4(e9,e10,e11,e12)) 相同。

17.1.2.12. mat4x2
重载
@const @must_use fn mat4x2<T>(e : mat4x2<S>) -> mat4x2<T>
@const @must_use fn mat4x2(e : mat4x2<S>) -> mat4x2<S>
参数化 T 是f16 或f32
S 是AbstractFloat、f16 或f32
描述 4x2 列主序矩阵的构造器。

如果 T 与 S 不匹配,则会发生转换。

重载
@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>
参数化 T 是AbstractFloat、f16 或f32
描述 从列向量构造一个 4x2 列主序矩阵。
重载
@const @must_use fn mat4x2<T>(e1 : T, ..., e8 : T) -> mat4x2<T>
@const @must_use fn mat4x2(e1 : T, ..., e8 : T) -> mat4x2<T>
参数化 T 是AbstractFloat、f16 或f32
描述 从元素构造一个 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>
参数化 T 是f16 或f32
S 是AbstractFloat、f16 或f32
描述 4x3 列主序矩阵的构造器。

如果 T 与 S 不匹配,则会发生转换。

重载
@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>
参数化 T 是AbstractFloat、f16 或f32
描述 从列向量构造一个 4x3 列主序矩阵。
重载
@const @must_use fn mat4x3<T>(e1 : T, ..., e12 : T) -> mat4x3<T>
@const @must_use fn mat4x3(e1 : T, ..., e12 : T) -> mat4x3<T>
参数化 T 是AbstractFloat、f16 或f32
描述 从元素构造一个 4x3 列主序矩阵。

与 mat4x3(vec3(e1,e2,e3), vec3(e4,e5,e6), vec3(e7,e8,e9), vec3(e10,e11,e12)) 相同。

17.1.2.14. mat4x4
重载
@const @must_use fn mat4x4<T>(e : mat4x4<S>) -> mat4x4<T>
@const @must_use fn mat4x4(e : mat4x4<S>) -> mat4x4<S>
参数化 T 是f16 或f32
S 是AbstractFloat、f16 或f32
描述 4x4 列主序矩阵的构造器。

如果 T 与 S 不匹配,则会发生转换。

重载
@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>
参数化 T 是AbstractFloat、f16 或f32
描述 从列向量构造一个 4x4 列主序矩阵。
重载
@const @must_use fn mat4x4<T>(e1 : T, ..., e16 : T) -> mat4x4<T>
@const @must_use fn mat4x4(e1 : T, ..., e16 : T) -> mat4x4<T>
参数化 T 是AbstractFloat、f16 或f32
描述 从元素构造一个 4x4 列主序矩阵。

与 mat4x4(vec4(e1,e2,e3,e4), vec4(e5,e6,e7,e8), vec4(e9,e10,e11,e12), vec4(e13,e14,e15,e16)) 相同。

17.1.2.15. 结构体
重载
@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 值。

如果 T 是u32,则这是 恒等操作。
如果 T 是i32,则这是 位的重新解释(即结果是u32 中与 e 具有相同位模式的唯一值)。
如果 T 是浮点类型, 则将 e转换为u32,并舍入到零。
如果 T 是bool,则 当 e 为 true 时结果为 1u,否则为 0u。
如果 T 是AbstractInt,且 e 可以用u32 表示,则这是恒等操作,否则会产生着色器创建 错误。

注: 从AbstractInt 到该类型的重载存在,是为了让 u32(4*1000*1000*1000) 这样的表达式能够创建一个 原本会使 i32 类型溢出的 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.x 和 e.y。

如果 T 与 S 不匹配,则使用转换,分量为 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 是标量
描述 逐分量 构造一个双分量向量,其分量为 e1 和 e2。
重载
@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.x、e.y 和 e.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 是标量
描述 逐分量 构造一个三分量向量,其分量为 e1、e2 和 e3。
重载
@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.x、v1.y 和 e1。
重载
@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 是标量
描述 逐分量 构造一个三分量向量,其分量为 e1、v1.x 和 v1.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.x、e.y、e.z 和 e.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 是标量
描述 逐分量 构造一个四分量向量,其分量为 e1、e2、e3 和 e4。
重载
@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 是标量
描述 逐分量 构造一个四分量向量,其分量为 e1、v1.x、v1.y 和 e2。
重载
@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 是标量
描述 逐分量 构造一个四分量向量,其分量为 e1、e2、v1.x 和 v1.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.x、v1.y、 v2.x 和 v2.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.x、v1.y、e1 和 e2。
重载
@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.x、v1.y、 v1.z 和 e1。
重载
@const @must_use fn vec4<T>(e1 : T, v1 : vec3<T>) -> vec4<T>
@const @must_use fn vec4(e1 : T, v1 : vec3<T>) -> vec4<T>
参数化 T 是标量
描述 逐分量 构造一个四分量向量,其分量为 e1、v1.x、v1.y 和 v1.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<AS, array<E>, AM>) -> u32
参数化 E 是运行时大小数组的元素类型。
AS 是storage、uniform 或workgroup 之一。
访问模式 AM 是read 或read_write,并且 对 AS必须有效。

注: 只有当 p 派生自 bufferView 或 bufferArrayView 内置函数的结果时,AS 才能为uniform 或workgroup。

描述 返回NRuntime,即 运行时大小数组中的元素数量。

参见§ 13.3.4 缓冲区 绑定确定运行时大小数组元素数量

当 p 派生自bufferView 或 bufferArrayView 内置函数时, NRuntime 基于 buffer 的大小, 该大小由§ 17.14 缓冲区视图内置 函数中的分析确定。

示例:获取运行时大小数组中的元素数量
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)的主值,以弧度表示。
也就是说,近似计算满足 0 ≤ x ≤ π 且 cos(x) = e 的 x。

当 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),作为双曲 角。
也就是说,近似计算满足 0 ≤ a ≤ +∞ 且 cosh(a) = x 的 a。

当 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)的主值,以弧度表示。
也就是说,近似计算满足 -π/2 ≤ x ≤ π/2 且 sin(x) = e 的 x。

当 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),作为双曲 角。
也就是说,近似计算满足 sinh(y) = a 的 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)的主值,以弧度表示。
也就是说,近似计算满足 − π/2 ≤ x ≤ π/2 且 tan(x) = e 的 x。

当 T 是向量时逐分量。

17.5.7. atanh

重载
@const @must_use fn atanh(t: T) -> T
参数化 S 是 AbstractFloat、f32 或 f16
T 是 S 或 vecN<S>
描述 返回 t 的反双曲正切(tanh-1),作为双曲 角。
也就是说,近似计算满足 tanh(a) = t 的 a。

当 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。

结果所选择的象限取决于 y 和 x 的符号。 例如,该函数可以实现为:

  • 当 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),要么是三个值 e、 low、high 的中位数。

当 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
描述 返回 e1 和 e2 的叉积。
定义域 由线性项推导,给定一种可能 实现:
  • 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>
描述 返回 e1 与 e2 之间的距离(例如 length(e1 - e2))。

定义域是所有满足 减法 e1−e2 有效的向量 (e1,e2)。 也就是说,除某个分量 i 中 e1[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
描述 返回 e1 和 e2 的点积。
定义域 由项 e1[i] × e2[i] 的和中的线性项推导。

17.5.21. dot4U8Packed

重载
@const @must_use fn dot4U8Packed(e1: u32,
                                 e2: u32) -> u32
描述 e1 和 e2 被解释为具有四个 8 位无符号整数 分量的向量。 返回这两个向量的无符号整数点积。

17.5.22. dot4I8Packed

重载
@const @must_use fn dot4I8Packed(e1: u32,
                                 e2: u32) -> i32
描述 e1 和 e2 被解释为具有四个 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 是标量类型时:

  • w 是 T 的位宽
  • 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 是标量类型时:

  • w 是 T 的位宽
  • 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 中与 e 的符号位不同的 最高有效位的位置。

当 T 是向量时逐分量。

注: 由于有符号整数使用 二进制补码表示, 符号位出现在最高有效位位置。

17.5.29. firstLeadingBit(无符号)

重载
@const @must_use fn firstLeadingBit(e: T) -> T
参数化 T 是 u32 或 vecN<u32>
描述 对于标量 T,结果为:
  • 如果 e 为零,则为 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 为零,则为 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-754 fusedMultiplyAdd 操作计算中间结果时, 就像具有无限范围和精度一样,并且仅将最终结果舍入 为目标类型中的值。 但是,fma 的§ 15.7.4 浮点精度规则 允许实现 先执行到目标类型的普通乘法,再执行普通加法。 在这种情况下,中间结果值可能溢出或损失 精度,并且整个 操作根本不是“融合”的。

定义域 由表达式 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 为零时,小数部分为零。

  • 当 e 非零且为正规数时,e = fraction * 2exponent,其中 小数部分位于范围 [0.5, 1.0) 或 (-1.0, -0.5]。

  • 否则,e 是次正规数、NaN 或无穷。结果的 小数部分和指数为不确定值。

返回 __frexp_result_f32 内置结构体,其定义如下:

struct __frexp_result_f32 {
  fract : f32, // 小数部分
  exp : i32    // 指数部分
}

注: 名称 frexp 的助记方式是“fraction(小数部分)和 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 为零时,小数部分为零。

  • 当 e 非零且为正规数时,e = fraction * 2exponent,其中 小数部分位于范围 [0.5, 1.0) 或 (-1.0, -0.5]。

  • 否则,e 是次正规数、NaN 或无穷。结果的 小数部分和指数为不确定值。

返回 __frexp_result_f16 内置结构体,其定义效果如下:

struct __frexp_result_f16 {
  fract : f16, // 小数部分
  exp : i32    // 指数部分
}

注: 名称 frexp 的助记方式是“fraction(小数部分)和 exponent(指数)”。

注: 值不能显式 声明为类型 __frexp_result_f16, 但值可以推断该类型。

重载
@const @must_use fn frexp(e: T) -> __frexp_result_abstract
参数化 T 是 AbstractFloat
描述 将 e 拆分为小数部分和指数。
  • 当 e 为零时,小数部分为零。

  • 当 e 非零且为正规数时,e = fraction * 2exponent,其中 小数部分位于范围 [0.5, 1.0) 或 (-1.0, -0.5]。

  • 当 e 是次正规数时,小数部分和指数 的误差没有界限。 小数部分可以是任意 AbstractFloat 值,而指数可以是任意 AbstractInt 值。

注: 产生无穷或 NaN 的 AbstractFloat 表达式 会导致着色器创建错误。

返回 __frexp_result_abstract 内置结构体,其定义如下:

struct __frexp_result_abstract {
  fract : AbstractFloat, // 小数部分
  exp : AbstractInt      // 指数部分
}

注: 名称 frexp 的助记方式是“fraction(小数部分)和 exponent(指数)”。

示例:抽象 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 为零时,小数部分为零。

  • 当 ei 非零且为正规数时,ei = fraction * 2exponent,其中 小数部分位于范围 [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(小数部分)和 exponent(指数)”。

注: 值不能显式 声明为类型 __frexp_result_vecN_f32, 但值可以推断该类型。

重载
@const @must_use fn frexp(e: T) -> __frexp_result_vecN_f16
参数化 T 是 vecN<f16>
描述 将 e 的分量 ei 拆分为小数部分和指数。
  • 当 ei 为零时,小数部分为零。

  • 当 ei 非零且为正规数时,ei = fraction * 2exponent,其中 小数部分位于范围 [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(小数部分)和 exponent(指数)”。

注: 值不能显式 声明为类型 __frexp_result_vecN_f16, 但值可以推断该类型。

重载
@const @must_use fn frexp(e: T) -> __frexp_result_vecN_abstract
参数化 T 是 vecN<AbstractFloat>
描述 将 e 的分量 ei 拆分为小数部分和指数。
  • 当 ei 为零时,小数部分为零。

  • 当 ei 非零且为正规数时,ei = fraction * 2exponent,其中 小数部分位于范围 [0.5, 1.0) 或 (-1.0, -0.5]。

  • 当 ei 是次正规数时,小数部分和指数 的误差没有界限。 小数部分可以是任意 AbstractFloat 值,而指数可以是任意 AbstractInt 值。

注: 产生无穷或 NaN 的 AbstractFloat 表达式 会导致着色器创建错误。

返回 __frexp_result_vecN_abstract 内置结构体,其定义如下:

struct __frexp_result_vecN_abstract {
  fract : vecN<AbstractFloat>, // 小数部分
  exp : vecN<AbstractInt>      // 指数部分
}

注: 名称 frexp 的助记方式是“fraction(小数部分)和 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 是标量类型时:

  • w 是 T 的位宽
  • 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>
当且仅当 T 是向量时,I 是向量
仅当 I 也是抽象类型时,T 才能是抽象类型,反之亦然

注: 如果任一参数是具体类型, 则另一个参数 将(如果适用)进行自动转换到 具体 类型,并且结果将是 具体 类型。

描述 返回 e1 * 2e2,但以下情况除外:

这里,bias 是浮点格式的指数偏置:

  • 对于 f16 为 15

  • 对于 f32 为 127

  • 当 AbstractFloat 是IEEE-754 binary64 时,对于 AbstractFloat 为 1023

如果 x 对其类型而言为零或有限正规值,则:

x = ldexp(frexp(x).fract, frexp(x).exp)

当 T 是向量时逐分量。

注: 名称 ldexp 的助记方式是“加载指数”。 该名称可能取自 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 是向量时逐分量 。

如果 e1 和 e2 是浮点值,则:

  • 如果 e1 和 e2 都是次正规数,则 结果可以是任一值。

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 是向量时逐分量 。

如果 e1 和 e2 是浮点值,则:

  • 如果 e1 和 e2 都是次正规数,则 结果可以是任一值。

17.5.43. mix

重载
@const @must_use fn mix(e1: T,
                        e2: T,
                        e3: T) -> T
参数化 S 是 AbstractFloat、f32 或 f16
T 是 S 或 vecN<S>
描述 返回 e1 和 e2 的线性混合(例如 e1 * (T(1) - e3) + e2 * e3)。 当 T 是向量时逐分量 。
定义域 由表达式的线性项推导: e1[i] × (1 − e3[i]) + e2[i] × e3[i]。 e2[i] × e2[i] + e3[i]。
重载
@const @must_use fn mix(e1: T2,
                        e2: T2,
                        e3: T) -> T2
参数化 T 是 AbstractFloat、f32 或 f16
T2 是 vecN<T>
描述 返回 e1 和 e2 的逐分量线性混合, 对每个分量使用标量混合因子 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 抽象用法
// 推断结果类型
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>
描述 返回 e1 的 e2 次幂。 当 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 量化,就好像 e 被转换为 IEEE-754 binary16 值,然后 再转换回 IEEE-754 binary32 值。

如果 e 位于 binary16 的有限范围之外,则:

中间 binary16 值可以刷新为零,即如果中间 binary16 值是次正规数,则最终 结果可以为零。

参见§ 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 恰好位于整数 k 和 k + 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 是双曲角。 近似于纯数学函数 (ea − e−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 时,对于低于 edge0 的 x,函数值为 0,然后平滑上升, 直到 x 到达 edge1,之后保持为 1。

  • 当 edge0 > edge1 时,对于低于 edge1 的 x,函数值为 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>
描述 如果 edge ≤ x,则返回 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 是双曲 角。 近似于纯数学函数 (ea − e−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 层级的纹素尺寸。

参数化 重载
ST 是i32、u32 或f32
F 是纹素 格式
A 是访问 模式

T 是 texture_1d<ST> 或 texture_storage_1d<F,A>
@must_use fn textureDimensions(t: T) -> u32
ST 是i32、u32 或f32

T 是 texture_1d<ST>

L 是i32 或u32

@must_use fn textureDimensions(t: T,
                               level: L) -> u32
ST 是i32、u32 或f32
F 是纹素 格式
A 是访问 模式

T 是 texture_2d<ST>、texture_2d_array<ST>、 texture_cube<ST>、 texture_cube_array<ST>、texture_multisampled_2d<ST>、 texture_depth_2d、texture_depth_2d_array、 texture_depth_cube、 texture_depth_cube_array、texture_depth_multisampled_2d、 texture_storage_2d<F,A>、texture_storage_2d_array<F,A>、 或 texture_external
@must_use fn textureDimensions(t: T) -> vec2<u32>
ST 是i32、u32 或f32

T 是 texture_2d<ST>、texture_2d_array<ST>、 texture_cube<ST>、 texture_cube_array<ST>、texture_depth_2d、 texture_depth_2d_array、 texture_depth_cube 或 texture_depth_cube_array

L 是i32 或u32

@must_use fn textureDimensions(t: T,
                               level: L) -> vec2<u32>
ST 是i32、u32 或f32
F 是纹素 格式
A 是访问 模式

T 是 texture_3d<ST> 或 texture_storage_3d<F,A>
@must_use fn textureDimensions(t: T) -> vec3<u32>
ST 是i32、u32 或f32

T 是 texture_3d<ST>

L 是i32 或u32

@must_use fn textureDimensions(t: T,
                               level: L) -> vec3<u32>

参数:

t 采样、 多重采样、深度、 存储或外部 纹理。
level mip 层级,其中层级 0 包含纹理的完整尺寸版本。
如果省略,则返回层级 0 的尺寸。

返回:

纹理的坐标尺寸。

也就是说,结果提供逻辑纹素地址坐标的整数边界, 不包括mip 层级数量、数组大小和采样数量。

对于基于立方体的纹理,结果是立方体每个面的尺寸。 立方体面是正方形,因此结果的 x 和 y 分量相等。

如果 level 位于范围 [0, textureNumLevels(t)) 之外,则可能返回 返回类型的不确定 值 。

17.7.2. textureGather

纹理聚集 操作从 2D、2D 数组、立方体或立方体数组纹理中读取, 并按如下方式计算一个四分量向量:

这四个纹素构成 WebGPU 采样器描述符中描述的采样区域。

参数化 重载
C 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureGather(component: C,
                           t: texture_2d<ST>,
                           s: sampler,
                           coords: vec2<f32>) -> vec4<ST>
C 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureGather(component: C,
                           t: texture_2d<ST>,
                           s: sampler,
                           coords: vec2<f32>,
                           offset: vec2<i32>) -> vec4<ST>
C 是i32 或u32
A 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureGather(component: C,
                           t: texture_2d_array<ST>,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A) -> vec4<ST>
C 是i32 或u32
A 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureGather(component: C,
                           t: texture_2d_array<ST>,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A,
                           offset: vec2<i32>) -> vec4<ST>
C 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureGather(component: C,
                           t: texture_cube<ST>,
                           s: sampler,
                           coords: vec3<f32>) -> vec4<ST>
C 是i32 或u32
A 是i32 或u32
ST 是i32、u32 或f32
@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>
A 是i32 或u32
@must_use fn textureGather(t: texture_depth_2d_array,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A) -> vec4<f32>
A 是i32 或u32
@must_use fn textureGather(t: texture_depth_2d_array,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A,
                           offset: vec2<i32>) -> vec4<f32>
A 是i32 或u32
@must_use fn textureGather(t: texture_depth_cube_array,
                           s: sampler,
                           coords: vec3<f32>,
                           array_index: A) -> vec4<f32>

参数:

component 仅适用于非深度纹理。
要从所选纹素读取的通道索引。
提供时,component 表达式必须是const 表达式(例如 1)。
其值必须至少为 0 且至多为 3。 超出此范围的值将导致着色器创建 错误。
t 要读取的采样或深度纹理。
s 采样器类型。
coords 纹理坐标。
array_index 基于 0 的纹理数组索引。
此值将被限制到范围 [0, textureNumLayers(t) - 1]。
offset 在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。 此偏移在应用任何 纹理环绕模式之前应用。
offset 表达式必须是const 表达式(例如 vec2<i32>(1, 2))。
每个 offset 分量必须至少为 -8 且至多为 7。超出 此范围的值将导致着色器创建错误。

返回:

一个四分量向量,其分量从所选纹素的指定通道中提取,如 上文所述。

示例:从 2D 纹理中的纹素聚集分量
@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

纹理聚集 比较操作对深度 纹理中的四个纹素执行深度比较,并将结果收集到单个向量中,如下所示:

参数化 重载
@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>
A 是i32 或u32
@must_use fn textureGatherCompare(t: texture_depth_2d_array,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  array_index: A,
                                  depth_ref: f32) -> vec4<f32>
A 是i32 或u32
@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>
A 是i32 或u32
@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 在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。 此偏移在应用任何 纹理环绕模式之前应用。
offset 表达式必须是const 表达式(例如 vec2<i32>(1, 2))。
每个 offset 分量必须至少为 -8 且至多为 7。超出 此范围的值将导致着色器创建错误。

返回:

一个四分量向量,其中包含所选纹素的比较结果,如上所述。

示例:聚集深度比较
@group(0) @binding(0) var dt: texture_depth_2d;
@group(0) @binding(1) var s: sampler;

fn gather_depth_compare(c: vec2<f32>, depth_ref: f32) -> vec4<f32> {
  return textureGatherCompare(dt,s,c,depth_ref);
}

17.7.4. textureLoad

从纹理中读取单个纹素,不进行采样或过滤。

参数化 重载
C 是i32 或u32
L 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureLoad(t: texture_1d<ST>,
                         coords: C,
                         level: L) -> vec4<ST>
C 是i32 或u32
L 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureLoad(t: texture_2d<ST>,
                         coords: vec2<C>,
                         level: L) -> vec4<ST>
C 是i32 或u32
A 是i32 或u32
L 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureLoad(t: texture_2d_array<ST>,
                        coords: vec2<C>,
                        array_index: A,
                        level: L) -> vec4<ST>
C 是i32 或u32
L 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureLoad(t: texture_3d<ST>,
                         coords: vec3<C>,
                         level: L) -> vec4<ST>
C 是i32 或u32
S 是i32 或u32
ST 是i32、u32 或f32
@must_use fn textureLoad(t: texture_multisampled_2d<ST>,
                         coords: vec2<C>,
                         sample_index: S)-> vec4<ST>
C 是i32 或u32
L 是i32 或u32
@must_use fn textureLoad(t: texture_depth_2d,
                         coords: vec2<C>,
                         level: L) -> f32
C 是i32 或u32
A 是i32 或u32
L 是i32 或u32
@must_use fn textureLoad(t: texture_depth_2d_array,
                         coords: vec2<C>,
                         array_index: A,
                         level: L) -> f32
C 是i32 或u32
S 是i32 或u32
@must_use fn textureLoad(t: texture_depth_multisampled_2d,
                         coords: vec2<C>,
                         sample_index: S)-> f32
C 是i32 或u32
@must_use fn textureLoad(t: texture_external,
                         coords: vec2<C>) -> vec4<f32>
C 是i32 或u32
AM 是read 或read_write
CF 取决于存储纹素格式 F。 有关纹素 格式到通道格式的映射,请参见纹素格式 表。
@must_use fn textureLoad(t : texture_storage_1d<F, AM>,
                         coords : C) -> vec4<CF>
C 是i32 或u32
AM 是read 或read_write
CF 取决于存储纹素格式 F。 有关纹素 格式到通道格式的映射,请参见纹素格式 表。
@must_use fn textureLoad(t : texture_storage_2d<F, AM>,
                         coords : vec2<C>) -> vec4<CF>
C 是i32 或u32
AM 是read 或read_write
A 是i32 或u32
CF 取决于存储纹素格式 F。 有关纹素 格式到通道格式的映射,请参见纹素格式 表。
@must_use fn textureLoad(t : texture_storage_2d_array<F, AM>,
                         coords : vec2<C>,
                         array_index : A) -> vec4<CF>
C 是i32 或u32
AM 是read 或read_write
CF 取决于存储纹素格式 F。 有关纹素 格式到通道格式的映射,请参见纹素格式 表。
@must_use fn textureLoad(t : texture_storage_3d<F, AM>,
                         coords : vec3<C>) -> vec4<CF>

参数:

t 采样、 多重采样、 深度、 存储或 外部 纹理
coords 基于 0 的纹素坐标。
array_index 基于 0 的纹理数组索引。
level mip 层级,其中层级 0 包含纹理的完整尺寸版本。
sample_index 多重采样纹理的基于 0 的采样索引。

返回:

未过滤的纹素数据。

如果以下任一条件成立,则逻辑纹素 地址无效:

如果逻辑纹素地址无效,则内置函数返回以下之一:

17.7.5. textureNumLayers

返回数组化纹理的层(元素)数量。

参数化 重载
F 是纹素 格式
A 是访问 模式
ST 是i32、u32 或f32

T 是 texture_2d_array<ST>、texture_cube_array<ST>、 texture_depth_2d_array、texture_depth_cube_array, 或 texture_storage_2d_array<F,A>
@must_use fn textureNumLayers(t: T) -> u32

参数:

t 采样、 深度 或 存储 纹理数组纹理。

返回:

如果纹理基于立方体,则返回立方体数组化纹理中的立方体数量。

否则,返回数组化纹理中的层(同质纹素网格)数量。

17.7.6. textureNumLevels

返回纹理的 mip 层级数量。

参数化 重载
ST 是i32、u32 或f32

T 是 texture_1d<ST>、texture_2d<ST>、 texture_2d_array<ST>、texture_3d<ST>、 texture_cube<ST>、texture_cube_array<ST>、 texture_depth_2d、texture_depth_2d_array、 texture_depth_cube 或 texture_depth_cube_array
@must_use fn textureNumLevels(t: T) -> u32

参数:

t 采样或深度纹理。

返回:

纹理的mip 层级 数量。

17.7.7. textureNumSamples

返回多重采样纹理中每个纹素的采样数量。

参数化 重载
ST 是i32、u32 或f32

T 是 texture_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>
A 是i32 或u32
@must_use fn textureSample(t: texture_2d_array<f32>,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A) -> vec4<f32>
A 是i32 或u32
@must_use fn textureSample(t: texture_2d_array<f32>,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A,
                           offset: vec2<i32>) -> vec4<f32>
T 是 texture_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>
A 是i32 或u32
@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
A 是i32 或u32
@must_use fn textureSample(t: texture_depth_2d_array,
                           s: sampler,
                           coords: vec2<f32>,
                           array_index: A) -> f32
A 是i32 或u32
@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
A 是i32 或u32
@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 在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。 此偏移在应用任何 纹理环绕模式之前应用。
offset 表达式必须是const 表达式(例如 vec2<i32>(1, 2))。
每个 offset 分量必须至少为 -8 且至多为 7。超出 此范围的值将导致着色器创建错误。

返回:

采样值。

如果在非一致控制流中调用,则结果为不确定 值。

17.7.9. textureSampleBias

使用 mip 层级偏置采样纹理。

必须仅 在片段着色器阶段中使用。

如果一致性 分析无法证明对此函数的调用位于一致控制流中, 则会触发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>
A 是i32 或u32
@must_use fn textureSampleBias(t: texture_2d_array<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               array_index: A,
                               bias: f32) -> vec4<f32>
A 是i32 或u32
@must_use fn textureSampleBias(t: texture_2d_array<f32>,
                               s: sampler,
                               coords: vec2<f32>,
                               array_index: A,
                               bias: f32,
                               offset: vec2<i32>) -> vec4<f32>
T 是 texture_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>
A 是i32 或u32
@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 层级的偏置。
此值将被限制在范围 [-16.0, 15.99] 内。
offset 在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。 此偏移在应用任何 纹理环绕模式之前应用。
offset 表达式必须是const 表达式(例如 vec2<i32>(1, 2))。
每个 offset 分量必须至少为 -8 且至多为 7。超出 此范围的值将导致着色器创建错误。

返回:

采样值。

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
A 是i32 或u32
@must_use fn textureSampleCompare(t: texture_depth_2d_array,
                                  s: sampler_comparison,
                                  coords: vec2<f32>,
                                  array_index: A,
                                  depth_ref: f32) -> f32
A 是i32 或u32
@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
A 是i32 或u32
@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 在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。 此偏移在应用任何 纹理环绕模式之前应用。
offset 表达式必须是const 表达式(例如 vec2<i32>(1, 2))。
每个 offset 分量必须至少为 -8 且至多为 7。超出 此范围的值将导致着色器创建错误。

返回:

范围 [0.0..1.0] 内的值。

使用 sampler_comparison 定义的比较 运算符,将每个采样纹素与参考值进行比较,从而为每个纹素得到 0 或 1 值。

如果采样器使用双线性过滤,则返回值是 这些值经过过滤后的平均值,否则返回单个 纹素的比较结果。

如果在非一致控制流中调用,则结果为不确定 值。

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
A 是i32 或u32
@must_use fn textureSampleCompareLevel(t: texture_depth_2d_array,
                                       s: sampler_comparison,
                                       coords: vec2<f32>,
                                       array_index: A,
                                       depth_ref: f32) -> f32
A 是i32 或u32
@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
A 是i32 或u32
@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 在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移。 此偏移在应用任何 纹理环绕模式之前应用。
offset 表达式必须是const 表达式(例如 vec2<i32>(1, 2))。
每个 offset 分量必须至少为 -8 且至多为 7。超出 此范围的值将导致着色器创建错误。

返回:

范围 [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>
A 是i32 或u32
@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>
A 是i32 或u32
@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>
T 是 texture_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>
A 是i32 或u32
@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 在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移 。此偏移在应用任何 纹理环绕模式之前应用。
offset 表达式必须是const 表达式(例如 vec2<i32>(1, 2))。
每个 offset 分量必须至少为 -8 且至多为 7。超出 此范围的值将导致着色器创建错误。

返回:

采样值。

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>
A 是i32 或u32
@must_use fn textureSampleLevel(t: texture_2d_array<f32>,
                                s: sampler,
                                coords: vec2<f32>,
                                array_index: A,
                                level: f32) -> vec4<f32>
A 是i32 或u32
@must_use fn textureSampleLevel(t: texture_2d_array<f32>,
                                s: sampler,
                                coords: vec2<f32>,
                                array_index: A,
                                level: f32,
                                offset: vec2<i32>) -> vec4<f32>
T 是 texture_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>
A 是i32 或u32
@must_use fn textureSampleLevel(t: texture_cube_array<f32>,
                                s: sampler,
                                coords: vec3<f32>,
                                array_index: A,
                                level: f32) -> vec4<f32>
L 是i32 或u32
@must_use fn textureSampleLevel(t: texture_depth_2d,
                                s: sampler,
                                coords: vec2<f32>,
                                level: L) -> f32
L 是i32 或u32
@must_use fn textureSampleLevel(t: texture_depth_2d,
                                s: sampler,
                                coords: vec2<f32>,
                                level: L,
                                offset: vec2<i32>) -> f32
A 是i32 或u32
L 是i32 或u32
@must_use fn textureSampleLevel(t: texture_depth_2d_array,
                                s: sampler,
                                coords: vec2<f32>,
                                array_index: A,
                                level: L) -> f32
A 是i32 或u32
L 是i32 或u32
@must_use fn textureSampleLevel(t: texture_depth_2d_array,
                                s: sampler,
                                coords: vec2<f32>,
                                array_index: A,
                                level: L,
                                offset: vec2<i32>) -> f32
L 是i32 或u32
@must_use fn textureSampleLevel(t: texture_depth_cube,
                                s: sampler,
                                coords: vec3<f32>,
                                level: L) -> f32
A 是i32 或u32
L 是i32 或u32
@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 包含纹理的完整尺寸版本。 对于 level 为 f32 的函数,如果根据 纹理格式能力该格式可过滤,则小数值可以 在两个层级之间进行插值 。
offset 在采样纹理之前应用于非归一化纹理坐标的可选纹素偏移 。此偏移在应用任何 纹理环绕模式之前应用。
offset 表达式必须是const 表达式(例如 vec2<i32>(1, 2))。
每个 offset 分量必须至少为 -8 且至多为 7。超出 此范围的值将导致着色器创建错误。

返回:

采样值。

17.7.14. textureSampleBaseClampToEdge

在纹理视图的基础层级采样, 并按下述方式将纹理坐标限制到边缘。

参数化 重载
T 是 texture_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))

注: 半纹素调整可确保 无论采样器的 寻址 和 过滤 模式如何, 都不会发生环绕。 也就是说,在边缘附近采样时,所采样的纹素 将位于该边缘上或与其相邻,而不会从相对的边缘选取。

返回:

采样值。

17.7.15. textureStore

将单个纹素写入纹理。

参数化 重载
F 是纹素 格式
C 是i32 或u32
AM 是write 或read_write
CF 取决于存储纹素格式 F。 有关纹素 格式到通道格式的映射,请参见纹素格式 表。
fn textureStore(t: texture_storage_1d<F,AM>,
                coords: C,
                value: vec4<CF>)
F 是纹素 格式
C 是i32 或u32
AM 是write 或read_write
CF 取决于存储纹素格式 F。 有关纹素 格式到通道格式的映射,请参见纹素格式 表。
fn textureStore(t: texture_storage_2d<F,AM>,
                coords: vec2<C>,
                value: vec4<CF>)
F 是纹素 格式
C 是i32 或u32
AM 是write 或read_write
A 是i32 或u32
CF 取决于存储纹素格式 F。 有关纹素 格式到通道格式的映射,请参见纹素格式 表。
fn textureStore(t: texture_storage_2d_array<F,AM>,
                coords: vec2<C>,
                array_index: A,
                value: vec4<CF>)
F 是纹素 格式
C 是i32 或u32
AM 是write 或read_write
CF 取决于存储纹素格式 F。 有关纹素 格式到通道格式的映射,请参见纹素格式 表。
fn textureStore(t: texture_storage_3d<F,AM>,
                coords: vec3<C>,
                value: vec4<CF>)

参数:

t 只写存储纹理或 读写存储纹理
coords 基于 0 的纹素坐标。
array_index 基于 0 的纹理数组索引。
value 新的纹素值。 value 使用逆通道传递函数进行转换。

注:

如果以下任一条件成立,则逻辑纹素 地址无效:

如果逻辑纹素地址无效,则内置函数将不会执行。

17.8. 原子内置函数

原子内置函数可用于读取/写入/读取-修改-写入原子 对象。它们是§ 6.2.8 原子类型上唯一允许的操作。

所有原子内置函数都使用 relaxed 内存 顺序。这意味着同步和顺序保证仅适用于 作用于相同内存位置的原子操作。原子和非原子内存访问之间,或 作用于不同内存位置的原子访问之间,不提供同步 或顺序保证。

原子内置函数不得在顶点着色器阶段中使用。

所有原子内置函数中 atomic_ptr 参数的地址空间 AS 必须是storage 或workgroup 之一。

除非另有规定,T 必须是u32 或i32 之一。

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. 通过对值 v 执行函数 名称所表示的操作(例如 max)获得新值。

  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);
}

注: 在某些实现中,相等比较可能发生伪失败。也 就是说,即使结果结构体的 old_value 成员等于 cmp, exchanged 成员也可能为 false。

17.8.6. 原子存储最小值和最大值函数

这些函数都需要atomic_vec2u_min_max 语言特性。

这些函数不返回值。

17.8.6.1. atomicStoreMin
fn atomicStoreMin(atomic_ptr: ptr<storage, atomic<vec2<u32>>, read_write>,
                  v: vec2<u32>)

对 atomic_ptr 所指向的原子对象与值 v 以原子方式执行 64 位无符号整数最小值操作。

以原子方式执行以下步骤:

  1. 从 atomic_ptr 所引用的内存中加载原始vec2u 值 old。

  2. 计算 64 位无符号整数值:

    • old64 = 232 × old[1] + old[0]

    • v64 = 232 × v[1] + v[0]

  3. 计算值 new64,其为 old64 和 v64 中的最小值。

  4. 计算类型为 vec2<u32> 的值 new,其中:

    • new[0] 包含 new64 的最低有效 32 位,而

    • new[1] 包含 new64 的最高有效 32 位。

  5. 将 new 存储到 atomic_ptr 所引用的内存中。

17.8.6.2. atomicStoreMax
fn atomicStoreMax(atomic_ptr: ptr<storage, atomic<vec2<u32>>, read_write>, v: vec2<u32>)

对 atomic_ptr 所指向的原子对象与值 v 以原子方式执行 64 位无符号整数最大值操作。

以原子方式执行以下步骤:

  1. 从 atomic_ptr 所引用的内存中加载原始vec2u 值 old。

  2. 计算 64 位无符号整数值:

    • old64 = 232 × old[1] + old[0]

    • v64 = 232 × v[1] + v[0]

  3. 计算值 new64,其为 old64 和 v64 中的最大值。

  4. 计算类型为 vec2<u32> 的值 new,其中:

    • new[0] 包含 new64 的最低有效 32 位,而

    • new[1] 包含 new64 的最高有效 32 位。

  5. 将 new 存储到 atomic_ptr 所引用的内存中。

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 中。

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 中。

17.9.3. pack4xI8

重载
@const @must_use fn pack4xI8(e: vec4<i32>) -> u32
描述 将 e 的每个分量的低 8 位打包到一个u32 值中,并丢弃所有未使用的位。

输入的分量 e[i] 被映射到结果的位 8 × i 到 8 × i + 7。

17.9.4. pack4xU8

重载
@const @must_use fn pack4xU8(e: vec4<u32>) -> u32
描述 将 e 的每个分量的低 8 位打包到一个u32 值中,并丢弃所有未使用的位。

输入的分量 e[i] 被映射到结果的位 8 × i 到 8 × i + 7。

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。

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。

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 中。

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 中。

17.9.9. pack2x16float

重载
@const @must_use fn pack2x16float(e: vec2<f32>) -> u32
描述 将两个浮点值转换为半精度浮点数,然后将 它们组合成一个 u32 值。
输入的分量 e[i] 被转换为IEEE-754 binary16 值,然后将其 放入结果的位 16 × i 到 16 × i + 15 中。 参见§ 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),其中 v 是将 e 的位 8×i 到 8×i + 7 解释为二进制补码有符号 整数所得的值。

17.10.2. unpack4x8unorm

重载
@const @must_use fn unpack4x8unorm(e: u32) -> vec4<f32>
描述 将 32 位值分解为四个 8 位块,然后将 每个块重新解释为无符号归一化浮点值。
结果的分量 i 为 v ÷ 255,其中 v 是将 e 的位 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),其中 v 是将 e 的位 16×i 到 16×i + 15 解释为二进制补码 有符号整数所得的值。

17.10.6. unpack2x16unorm

重载
@const @must_use fn unpack2x16unorm(e: u32) -> vec2<f32>
描述 将 32 位值分解为两个 16 位块,然后将 每个块重新解释为无符号归一化浮点值。
结果的分量 i 为 v ÷ 65535,其中 v 是将 e 的位 16×i 到 16×i + 15 解释为无符号整数所得的值。

17.10.7. unpack2x16float

重载
@const @must_use fn unpack2x16float(e: u32) -> vec2<f32>
描述 将 32 位值分解为两个 16 位块,并将每个块 重新解释为浮点值。
结果的分量 i 是 v 的 f32 表示, 其中 v 是将 e 的位 16×i 到 16×i + 15 解释为IEEE-754 binary16 值所得的值。 参见§ 15.7.6 浮点转换。

17.11. 同步内置函数

所有同步函数都以 Acquire/Release 内存顺序执行控制屏障。 也就是说,所有同步函数以及受影响的内存和原子 操作,相对于该同步函数按照程序顺序排序。 此外,在程序顺序上位于 同步函数之前的受影响内存和原子操作,必须在工作组的任一成员执行 程序顺序上位于同步函数之后的任何受影响内存或原子操作之前, 对工作组中的所有其他线程可见。

所有同步函数都使用 Workgroup 内存作用域。
所有同步函数都具有 Workgroup 执行作用域。
所有同步函数必须仅在 计算着色器阶段中使用。 所有同步函数必须仅在 一致控制 流中调用。

17.11.1. storageBarrier

重载
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 是具体数值标量或具体数值向量
描述 排他前缀扫描操作。

返回 子组中所有活跃调用中,其子组调用 ID 小于 当前 调用 ID 的调用的 e 之和。

活跃调用中 ID 最小的调用返回的值为 T(0)。

17.12.1.2. subgroupInclusiveAdd
重载
@must_use fn subgroupInclusiveAdd(e : T) -> T
前置条件 T 是具体数值标量或具体数值向量
描述 包含前缀扫描操作。

返回 子组中所有活跃调用中,其子组调用 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
描述 如果子组中的任一活跃调用的 e 为 true,则返回 true。

17.12.5. subgroupBallot

重载
@must_use fn subgroupBallot(pred : bool) -> vec4<u32>
描述 返回子组中 pred 为 true 的活跃调用的位掩码。

返回值的 x 分量包含调用 0 到 31。
返回值的 y 分量包含调用 32 到 63。
返回值的 z 分量包含调用 64 到 95。
返回值的 w 分量包含调用 96 到 127。

在每个分量中,ID 按位位置升序排列 (例如 ID 32 位于 y 分量的位位置 0)。

17.12.6. subgroupBroadcast

重载
@must_use fn subgroupBroadcast(e : T, id : I) -> T
前置条件 T 是具体数值标量或具体数值向量
I 是u32 或i32
描述 将子组中子组调用 ID 与 id 匹配的调用的 e 值返回给子组中的所有活跃调用。

id 必须是范围 [0, 128) 内的const 表达式。

如果 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 是具体数值标量或具体数值向量
I 是u32 或i32
描述 返回子组调用 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 是具体数值标量或具体数值向量
I 是u32 或i32
描述 将四元组中四元组调用 ID 与 id 匹配的调用的 e 值返回给四元组中的所有活跃调用。

id 必须是范围 [0, 4) 内的const 表达式。

如果 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 交换。

17.14. 缓冲区视图内置函数

这些函数都需要buffer_view 语言特性。

这些函数对指向缓冲区的指针进行操作。 bufferView 和 bufferArrayView 将缓冲区中的不透明数据重新解释为 另一种主机可共享数据类型。 生成的指针随后可以像其他指针一样使用。

将 ArrayOffset(T) 定义为:

将 MinTypeSize(T) 定义为:

由于缓冲区指针实参在函数调用中可以进行自动 转换为形参类型,因此实现将 使用过程间分析检查无效内存引用 并确保结果正确。 任何缓冲区视图内置函数所使用的缓冲区大小,是与该调用一起使用的特定来源变量所遇到的最小 缓冲区大小。 由于这是一个动态概念,实现将确保针对 内置函数的特定实例正确确定该值。 该大小将是以下值中的最小值:

注: 如果实现不支持 unrestricted_pointer_parameters 语言特性,则 传给内置函数的实参的根标识符必须是 来源 变量。

注: 缓冲区大小在传递给函数时不能变大。

17.14.1. bufferView

重载
@must_use fn bufferView<T>(p : ptr<AS, buffer, AM>, offset: I) -> ptr<AS, T, AM>
前置条件 AS 是storage、uniform 或 workgroup 之一。
AM 是对 AS 有效的访问模式。

T 是主机可共享类型。
T 不得是或包含原子或缓冲区类型。
T 必须满足 AS 的地址空间布局约束。

I 是u32 或i32。

描述 将从 p 起始位置偏移 offset 字节处的内存 视图重新解释为类型 T。 也就是说,如果与 p 关联的内存位置位于范围 [0, bufferLength(p)) 中,则与结果关联的内存位置 位于范围 [offset, bufferLength(p)) 中,其 存储类型为 T。

如果 I 是i32,则:

如果 MinTypeSize(T) + offset > bufferLength(p),则返回无效 指针。

如果 offset % RequiredAlignOf(T, AS) != 0, 则:

注: 这会与 uniform_buffer_standard_layout 语言特性交互。

重载
@must_use fn bufferView<T>(p : ptr<AS, buffer<N>, AM>, offset: I) -> ptr<AS, T, AM>
前置条件 AS 是storage、uniform 或 workgroup 之一。
AM 是对 AS 有效的访问模式。

T 是主机可共享类型。
T 不得是或包含原子或缓冲区类型。
T 必须满足 AS 的地址空间布局约束。

I 是u32 或i32。

描述 将从 p 起始位置偏移 offset 字节处的内存 视图重新解释为类型 T。 也就是说,如果与 p 关联的内存位置位于范围 [0, bufferLength(p)) 中,则与结果关联的内存位置 位于范围 [offset, bufferLength(p)) 中,其 存储类型为 T。

如果 I 是i32,则:

如果 MinTypeSize(T) + offset > N,则返回无效内存引用。

如果 offset % RequiredAlignOf(T, AS) != 0,则:

注: 这会与 uniform_buffer_standard_layout 语言特性交互。

示例:WGSL bufferView 用法
requires buffer_view;

@group(0) @binding(0) var<storage> in : buffer;
@group(0) @binding(1) var<storage, read_write> out : buffer;

struct S {
  a : mat3x3f,
  b : vec2f,
}

fn foo(offsets : array<u32, 4>) {
  // 以 vec4u 读取和写入部分数据。
  let p1 = bufferView<vec4u>(&in, offsets[0]);
  *bufferView<vec4u>(&out, offsets[1]) = *p1;

  // 通过结构体读取和写入其他一些数据。
  let p2 = bufferView<array<S, 2>>(&in, offsets[2]);
  let v = (*p2)[1].a;
  *bufferView<mat3x3f>(&out, offsets[3]) = v;
}

17.14.2. bufferArrayView

重载
@must_use fn bufferArrayView<T>(p : ptr<AS, buffer, AM>,
                                offset: OI, size: SI) -> ptr<AS, T, AM>
前置条件 AS 是storage、uniform 或 workgroup 之一。
AM 是对 AS 有效的访问模式。

T 是没有固定占用空间的主机可共享类型。
T 不得是或包含原子 或缓冲区类型。
T 必须满足 AS 的地址空间布局约束。

OI 是u32 或i32。
SI 是u32 或i32。

描述 将从 p 起始位置偏移 offset 字节、长度为 size 字节的内存 视图重新解释为类型 T。 也就是说,如果与 p 关联的内存位置位于范围 [0, bufferLength(p)) 中,则与结果关联的内存位置 位于范围 [offset, offset + size) 中,其 存储 类型为 T。

如果 OI 是i32,则:

如果 SI 是i32,则:

如果 MinTypeSize(T) > size,则返回无效内存引用。

如果 offset + size > bufferLength(p),则返回无效内存引用。

如果 offset % RequiredAlignOf(T, AS) != 0,则:

注: 这会与 uniform_buffer_standard_layout 语言特性交互。

如果 (size - ArrayOffset(T)) % StrideOf(array<E>) != 0, 则:

重载
@must_use fn bufferArrayView<T>(p : ptr<AS, buffer<N>, AM>,
                                offset: OI, size: SI) -> ptr<AS, T, AM>
前置条件 AS 是storage、uniform 或 workgroup 之一。
AM 是对 AS 有效的访问模式。

T 是没有固定占用空间的主机可共享类型。
T 不得是或包含原子 或缓冲区类型。
T 必须满足 AS 的地址空间布局约束。

OI 是u32 或i32。
SI 是u32 或i32。

描述 将从 p 起始位置偏移 offset 字节、长度为 size 字节的内存 视图重新解释为类型 T。 也就是说,如果与 p 关联的内存位置位于范围 [0, bufferLength(p)) 中,则与结果关联的内存位置 位于范围 [offset, offset + size) 中,其 存储 类型为 T。

如果 OI 是i32,则:

如果 SI 是i32,则:

如果 MinTypeSize(T) > size,则返回无效内存引用。

如果 offset + size > N,则返回无效内存引用。

如果 MinTypeSize(T) > N,则:

如果 offset % RequiredAlignOf(T, AS) != 0,则:

注: 这会与 uniform_buffer_standard_layout 语言特性交互。

如果 (size - ArrayOffset(T)) % StrideOf(array<E>) != 0, 则:

示例:WGSL bufferArrayView 用法
requires buffer_view;

const stride = 32;
const N = 4;
const size = 2048;
const_assert stride * N < size;

struct strided_u32 {
  @size(stride) a : u32,
}

@group(0) @binding(0) var<uniform> in : buffer<size>;
@group(0) @binding(1) var<storage, read_write> out: array<u32, N>;

fn foo(offsets : u32) {
  // 从 in 读取一个有步幅数组,并无步幅地复制到 out。
  var tmp : array<u32, N>;
  let view = bufferArrayView<array<strided_u32>>(&in, offset, stride * N);
  for (var i = 0; i < N; i++) {
    tmp[i] = (*view)[i];
  }
  out = tmp;
}

17.14.3. bufferLength

重载
@must_use fn bufferLength(p : ptr<AS, buffer,    AM>) -> u32
@must_use fn bufferLength(p : ptr<AS, buffer<N>, AM>) -> u32
前置条件 AS 是storage、uniform 或 workgroup 之一。
AM 是对 AS 有效的访问模式。
描述 返回过程间分析期间遇到的 p 所指向缓冲区的最小大小, 如果仅遇到运行时大小缓冲区,则返回 WebGPU GPUBuffer 的大小。
示例:bufferLength 值
requires buffer_view;
requires unrestricted_pointer_parameters;

@group(0) @binding(0) var<storage> b1 : buffer<2048>;
@group(0) @binding(0) var<storage> b2 : buffer<4096>;

fn foobar(p : ptr<storage, buffer>) -> u32 {
  return bufferLength(p);
}
fn bar(p : ptr<storage, buffer<1024>>) -> u32 {
  return foobar(p);
}
fn foo(p : ptr<storage, buffer<256>>) -> u32 {
  return bufferLength(p);
}

@compute @workgroup_size(1)
fn main() {
  let len1 = bufferLength(&b1); // 2048
  let len2 = bufferLength(&b2); // 4096

  let len3 = foo(&b1); // 256
  let len4 = foo(&b2); // 256

  let len5 = bar(&b1); // 1024
  let len6 = bar(&b2); // 1024

  let len7 = foobar(&b1); // 2048
  let len8 = foobar(&b2); // 4096
}

18. 递归下降解析语法

本节为非规范性内容。

WGSL 语法以适合 LALR(1) 解析器的形式规定。 实现也可能希望改用递归下降解析器。

规范性语法不能直接用于递归下降解析器,因为 其中若干规则是左递归的。 当所定义的非终结符首先出现在其某个产生式中时, 该语法规则就是直接左递归的。

以下是 WGSL 语法,但通过机械方式转换为:

但是,它不是 LL(1)。 对于某些非终结符,多个产生式具有公共的向前查看集合。 例如,attribute 非终结符的所有产生式都以 attr token 开头。 一个更微妙的例子是 global_decl,其中三个产生式都以 attribute * 短语开头,但随后分别由 token fn、override 和 var 区分。

为简洁起见,许多 token 定义未重复给出。 请使用规范主体中的 token 定义。

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 ',' ? ')'

| '@' 'subgroup_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
call_expression.post.ident:

template_elaborated_ident.post.ident '(' ( expression ( ',' expression )* ',' ? )? ')'

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

| ident call_expression.post.ident

| '(' lhs_expression ')'

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 call_expression.post.ident

| variable_or_value_statement

| variable_updating_statement

for_update:

ident call_expression.post.ident

| variable_updating_statement

fragment_attr:

'@' 'fragment'

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

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

可选参数

无

编码注意事项

binary

WGSL 是使用 UTF-8 编码的 Unicode 文本,不含字节顺序标记(BOM)。 参见§ 3 文本结构。

安全注意事项:

WebGPU 着色语言(WGSL)是一种用于 GPU 代码的编程语言,该代码在 WebGPU API 的上下文中执行。有关安全 注意事项,请参见[WebGPU] 第 2.1 节“安全注意事项”。 有关隐私注意事项,请参见[WebGPU] 第 2.2 节“隐私 注意事项”。

互操作性注意事项:

WebGPU 的实现可能具有不同的能力,而 这些差异可能会影响 WGSL 程序可使用哪些功能。 参见[WebGPU] 第 3.6 节“可选能力”, 以及§ 4.1.2 语言扩展。

预计实现将表现得仿佛此 注册也适用于 WGSL 的后续版本,并且其已发布的 规范引用可以相应地不时更新。尽管这一预期 在媒体类型注册中并不常见,但它符合广泛采用的行业惯例。

已发布的规范:

WebGPU 着色语言

使用此媒体类型的应用:

WebGPU 的实现。预计包括 Web 浏览器。

片段标识符注意事项

无

其他信息:

魔数:无

文件扩展名:.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 着色语言” W3C 工作草案,2023 年 1 月。 https://w3.org/TR/WGSL

一致性

文档 约定

一致性要求通过 描述性断言 与 RFC 2119 术语的组合来表达。 本文档规范性部分中的关键字“必须”、“不得”、“必需”、“应”、“不得”、“应当”、“不应”、“建议”、 “可以”和“可选” 应按照 RFC 2119 中的说明进行解释。 但是,为了提高可读性, 本规范中的这些词并不全部以大写字母形式出现。

本规范的所有文本均为规范性内容, 明确标记为非规范性的章节、示例和注释除外。[RFC2119]

本规范中的示例以“例如”等字样引入, 或使用 class="example" 与规范性文本分隔开来, 如下所示:

这是一个信息性示例。

信息性注释以“注”一词开头, 并使用 class="note" 与规范性文本分隔开来, 如下所示:

注:这是一个信息性注释。

符合规范的 算法

算法中以祈使句形式表述的要求 (例如“去除所有前导空格字符” 或“返回 false 并中止这些步骤”) 应按照引入该算法时所使用的关键字 (“必须”、“应当”、“可以”等) 的含义进行解释。

以算法或特定步骤形式表述的一致性要求 可以采用任何方式实现, 只要最终结果等价即可。 特别是,本规范中定义的算法 旨在易于理解, 并不以高性能为目标。 鼓励实现者进行优化。

索引

本规范定义的 术语

由引用定义的 术语

参考文献

规范性参考文献

[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS 值与单位 模块第 4 级。2024 年 3 月 12 日。WD。URL:https://www.w3.org/TR/css-values-4/
[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 换行 算法。2025 年 9 月 5 日。Unicode 标准附录 #14。URL:https://www.unicode.org/reports/tr14/tr14-55.html
[UAX31]
Mark Davis; Robin Leroy. Unicode 标识符与语法。2025 年 8 月 20 日。Unicode 标准附录 #31。URL:https://www.unicode.org/reports/tr31/tr31-43.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; et al. 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; et al. 万维网字符模型: 字符串匹配。2026 年 7 月 16 日。FPWD。URL:https://www.w3.org/TR/charmod-norm/
[IANA-MEDIA-TYPES]
媒体类型。URL:https://www.iana.org/assignments/media-types/
[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 核心 规范。2026 年 8 月 12 日。CRD。URL:https://www.w3.org/TR/wasm-core-2/