WebGPU Shading Language

Editor’s Draft,

More details about this document
This version:
https://gpuweb.github.io/gpuweb/wgsl/
Latest published version:
https://www.w3.org/TR/WGSL/
Feedback:
GitHub
Editors:
(Google)
(Apple Inc.)
Former Editor:
(Google)
Participate:
File an issue (open issues)
Tests:
WebGPU CTS shader/
Translator:
赵凌云,lingyun.zhao@orillusion.com
薛沛,pei.xue@orillusion.com

Abstract

Shading language for WebGPU. WGSL 翻译文档。本翻译非官方翻译。

Status of this document

This specification was published by the GPU for the Web Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. 介绍

WebGPU Shading Language (WGSL)是为 [WebGPU] 设计的着色器语言。也就是说,使用 WebGPU API 的应用程序使用 WGSL 来表达在 GPU 上运行的程序(称为着色器)。

@fragment
fn main() -> @location(0) vec4<f32> {
    return vec4<f32>(0.4, 0.4, 0.8, 1.0);
}

1.1. 技术概览

WebGPU 以GPU command的形式向 GPU 发出一个工作单元。WGSL关注两种 GPU 命令:

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

shader是WGSL程序的一部分,用于在管线中执行shader stage。着色器包括:

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

一个 WGSL 程序被组织成:

WGSL 是一种命令式语言:行为被指定为要执行的语句序列。声明:

WGSL 是静态类型的:由特定表达式计算的每个值都属于特定类型,仅通过检查程序源来确定。

WGSL 具有以数组和结构的形式描述布尔值、数字、向量、矩阵和由它们组成的数组和结构。其他类型描述内存。

WGSL 没有数字或布尔类型之间的隐式转换或提升。 将值从一种数字或布尔类型转换为另一种需要显式转换(conversion)、构造(construction)或重新解释位(reinterpretation of bits)。这也适用于向量类型。

WGSL 有纹理和采样器类型。连同相关的内置函数,这些支持通常用于图形渲染的功能,并且通常由GPU提供。

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

但是,调用作用于不同的管线输入集,包括提供识别值以将调用与其对等调用区分开的内置输入。 此外,每个调用在 privatefunction 地址空间中以变量的形式拥有自己的独立内存空间。

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

WGSL 有时允许给定特征的几种可能行为。这是一种可移植性危害,因为不同的实现可能会表现出不同的行为。WGSL的设计旨在最大限度地减少此类情况,但受到可行性和在各种设备上实现高性能的目标的限制。

1.2. 符号

floor expression在实数x上定义:

ceiling expression在实数x上定义:

roundUp函数在正整数kn上定义:

cr行矩阵A的转置(transpose)是将A的行复制为AT的列形成的rc行矩阵AT

列向量的转置是通过将列向量解释为1行矩阵来定义的。类似地,行向量的转置是通过将行向量解释为1列矩阵来定义的。

2. 着色器生命周期

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

事件为:

  1. Shader module creation

  2. Pipeline creation

  3. Shader execution start

  4. Shader execution end

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

      • 所有调用(invocations)完结。

      • 所有对资源(resources)的访问完结。

      • 输出(如果有)已经传递给下游管线阶段。

事件按照以下方式排序:

2.1. 处理错误

一个WebGPU实现可能由于两个原因对着色器处理失败:

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

注意:比如,一个竞争条件可能不会被检测到。

每个要求将被尽早检查。也就是:

当上下文不清楚时,本规范会指出未能满足特定要求是否会导致着色器创建、管线创建或动态错误。

WebGPU规范描述了每种错误的后果。

TODO:更新WebGPU规范,参考这里定义的三种错误。

3. 文本结构

WGSL程序为文本。本规范并未规定该文本的特定编码。但是,UTF-8始终是WGSL程序的有效编码。

注意:像这样推广 UTF-8 的目的是为了简化WGSL程序的交换并鼓励工具之间的互操作性。

WGSL程序文本由一系列双字节对字符 code points组成,分组为连续的非空集形成:

程序文本不得包含空字符。

3.1. 解析

要解析一个WGSL程序:
  1. 去掉注释comments

    • 用一个space字符编码(U+0020)替换第一个注释。

    • 重复直到没有注释。

  2. 从头到尾扫描,将剩余的字符编码分组为标记tokens和空格blankspaces

    • 下一组由剩余未分组字符编码的最长非空前缀组成

  3. 删除空格,只保留标记。

  4. 解析token序列,尝试匹配 translation_unit 语法规则。

在以下情况下会导致shader-creation error

3.2. 空格和换行符

Blankspace 是 Unicode 中的一个或多个代码点的任意组合。 以下是 Pattern_White_Space 中的代码点集:

_blankspace

| /[\u0020\u0009\u000a\u000b\u000c\u000d\u0085\u200e\u200f\u2028\u2029]/uy

line break 是一个连续的 blankspace 代码点序列,指示行尾。 它被定义为表示“强制中断”的空格,定义为 UAX14 第 6.1 节不可定制的换行规则 LB4LB5。 也就是说,换行符是以下任何一种:

注意:根据行号报告源文本位置的诊断应使用 line breaks数行。

3.3. 注释

注释(comment)是一块不影响WGSL程序有效性和含义的文本,只是注释可以分隔tokens。着色器作者可以使用注释去给程序添加文档信息。

行尾注释(line-ending comment)是一种由两个字符编码 // (U+002F followed by U+002F) 和后面的字符编码组成的注释,直到但不包括:

block comment是一种comment,由以下内容组成:

注意:块注释可以嵌套。 由于块注释需要匹配的开始和结束文本序列,并允许任意嵌套,因此不能用正则表达式识别块注释。 这是常规语言的抽水引理的结果。

EXAMPLE: Comments
const f = 1.5;   // 这是行尾注释。
const g = 2.5;   /* 这是一个块注释
                  跨越几行。
                  /* 块注释可以嵌套
                   */
                  但是所有的块注释必须完结。
                */

3.4. 标记

一个标记(token)是一系列连续字符编码构成以下内容:

3.5. 文字

文字(literal)为一下之一:

EXAMPLE: boolean literals
const a = true;
const b = false;
bool_literal :

| true

| false

numeric literal的形式是通过模式匹配定义 integer literal 是:

EXAMPLE: integer literals
const a = 0x123;
const b = 0X123u;
const c = 1u;
const d = 123;
const e = 0;
const f = 0i;
const g = 0x3f;
int_literal :

| /(0[xX][0-9a-fA-F]+|0|[1-9][0-9]*)[iu]?/

floating point literal十进制浮点文字l十六进制浮点文字

EXAMPLE: floating point literals
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;
const i = 0xa.fp+2;
const j = 0x1P+4f;
const k = 0X.3;
const l = 0x3p+2h;
const m = 0X1.fp-4;
const n = 0x3.2p+2h;
float_literal :

| decimal_float_literal

| hex_float_literal

decimal_float_literal :

| /((([0-9]*\.[0-9]+|[0-9]+\.[0-9]*)([eE](\+|-)?[0-9]+)?)|([0-9]+[eE](\+|-)?[0-9]+))[fh]?|0[fh]|[1-9][0-9]*[fh]/

hex_float_literal :

| /0[xX]((([0-9a-fA-F]*\.[0-9a-fA-F]+|[0-9a-fA-F]+\.[0-9a-fA-F]*)([pP](\+|-)?[0-9]+[fh]?)?)|([0-9a-fA-F]+[pP](\+|-)?[0-9]+[fh]?))/

const_literal :

| int_literal

| float_literal

| bool_literal

numeric literal 具有后缀时,该文字表示特定 scalar 类型中的值。 否则,文字表示下面定义的 abstract numeric types 之一的值。

将文字映射到类型
文字 后缀 类型 例子
integer literal i i32 42i
integer literal u u32 42u
integer literal AbstractInt 124
floating point literal f f32 42f 1e5f 1.2f 0x1.0p10f
floating point literal h f16 42h 1e5h 1.2h 0x1.0p10h
floating point literal AbstractFloat 1e5 1.2 0x1.0p10

如果出现以下情况,则会导致 shader-creation error

Note: The hexadecimal float value 0x1.00000001p0 requires 33 mantissa bits to be represented exactly, but f32 only has 23 explicit mantissa bits.

注意:如果要使用 f 后缀来强制十六进制浮点字面量为类型,则字面量还必须使用二进制指数。 例如,写“0x1p0f”。 相比之下,0x1f 是一个十六进制整数文字。

3.6. 关键字

一个关键字(keyword)是一个token,总是指代被预定义的语言概念。有关WGSL关键字列表,请参阅§ 14.1 关键字摘要

3.7. 标识符

标识符(identifier)是一种用作名称的token,请参阅§ 3.10 声明和范围§ 3.9 指令

标识符的形式基于 Unicode 标准附件 #31 用于 Unicode 版本 14.0.0, 以下详细说明。

标识符使用根据 UAX31 Grammar 描述的以下配置文件:

<Identifier> := <Start> <Continue>* (<Medial> <Continue>+)*

<Start> := XID_Start + U+005F
<Continue> := <Start> + XID_Continue
<Medial> :=

这意味着具有此类非 ASCII 代码点的标识符是 有效:Δέλτα, réflexion, Кызыл, 𐰓𐰏𐰇, 朝焼け, سلام, 검정, שָׁלוֹם, गुलाबी, փրոզ

除以下例外:

ident :

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

Unicode 版本 14.0.0 的 Unicode 字符数据库 包括非规范列表,其中包含 XID_StartXID_Continue

注意:某些 built-in functionsreturn type 是其名称不能用于WGSL源的结构类型。 这些结构类型被描述为 predeclared,名称以两个下划线开头。 结果值可以使用类型推断保存到新声明的 letvar 中,或者立即通过名称立即提取其成员之一。 参见 frexpmodf 描述中的示例用法。

3.7.1. 标识符比较

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

特别是,两个标识符在 WGSL 中可能是不同的,但在常规规范化下被认为是相同的, 映射和匹配算法,例如:

注意:当 WGSL 程序的含义发生改变时,用户代理应该发出开发人员可见的警告,如果 标识符的所有实例都替换为该标识符的同形异义词之一。 (同形文字是一个代码点序列,在读者看来可能与另一个代码点序列相同。 用于检测同形文字的映射示例是中提到的转换、映射和匹配算法 上一段。如果标识符可以通过以下方式将一个代码点转换为另一个,则两个代码点序列是同形异义词 反复用同形文字替换一个子序列。)

3.8. 属性

属性(attribute)修改对象或类型。WGSL为应用属性提供了统一的语法。属性用于多种目的,例如指定带有API的接口。一般来说,从语言的角度来看,出于类型和语义检查的目的,可以忽略属性。

对每个对象或类型,属性不能被多次指定。

attribute :

| attr ident paren_left ( literal_or_ident comma ) * literal_or_ident comma ? paren_right

| attr ident

literal_or_ident :

| float_literal

| int_literal

| ident

Attributes defined in WGSL/caption>
Attribute Valid Values Description
align 正i32文字 必须仅应用于结构(structure)类型的成员。

必须是2的幂,并且必须满足成员类型的对齐要求:

如果 align(n) 应用于 S 的成员 类型为 TSstore type 或包含在地址空间 C 中变量的存储类型中, 那么n 必须满足: n = k × RequiredAlignOf(T,C) 其中k是某个正整数。

见内存布局§ 4.4.7 内存布局

binding 非负i32文字 必须仅应用于资源(resource)变量。

指定绑定组(group)中资源的绑定号。见§ 9.3.2 资源接口

builtin 内置值的标识符名称 只能应用于入口点函数参数、入口点返回类型或结构(structure)成员。

声明一个内置变量。见§ 15 内置值

const 只能应用于函数声明。

指定该函数可用作 creation-time function。 如果此属性应用于 用户自定义函数。

注意:此属性用作符号约定来描述可以在 creation-time expressions 中使用哪些内置函数。

group 非负i32文字 必须仅应用于资源(resource)变量。

指定资源的绑定组。见§ 9.3.2 资源接口

id 非负 i32 字面量 只能应用于 scalar 类型的 override declaration

指定一个数字标识符作为 pipeline-overridable 常量的备用名称。

interpolate 一个或两个参数。

第一个参数必须是插值类型(interpolation type)。 第二个参数(如果存在)必须指定插值采样(interpolation sampling)。

只能应用于用 location 属性修饰的声明。

指定用户定义的IO如何必须插入。该属性仅对用户定义的顶点(vertex)输出和片元(fragment)输入有意义。见§ 9.3.1.2 插值

invariant None

仅应用于 position 内置值。

当应用于顶点着色器的位置内置输出变量(position built-in output value)时,结果的计算在不同程序和同一入口点的不同调用之间是不变的。 也就是说,如果数据和控制流在不同入口点的两个位置输出匹配,则结果值保证相同。对位置内置输入变量(position built-in input value)没有影响。

注意:此属性映射到 HLSL 中的 precise 限定符和 GLSL 中的 invariant 限定符

location 非负i32文字 仅应用于入口点函数参数、入口点返回类型或结构(structure)类型的成员。只能应用于数值标量(numeric scalar)或数值向量(numeric vector)类型的声明。不得与计算(compute)着色器阶段一起使用。

指定入口点的用户定义IO的一部分。见§ 9.3.1.3 输入输出位置

size 正i32文字 仅应用于结构(structure)类型的成员。

在结构中为此成员保留的字节数。 此数字必须至少是成员类型的 byte-size

如果 size(n) 应用于类型为 T 的成员,则 SizeOf(T) ≤ n

§ 4.4.7 内存布局

workgroup_size

一个,两个,或三个参数。

每个参数是文字常量或模块范围常量(module-scope constant)。所有参数的类型必须相同,或者是 i32 or u32。

必须应用于计算着色器(compute shader)入口点函数。不得应用于任何其他对象。

指定计算着色器的工作组网格(workgroup grid)的x、y和z维度。

第一个参数指定x维度。第二个参数(如果提供)指定y维度,否则假设为1。第三个参数(如果提供)指定z维度,否则假设为1。每个维度必须至少为 1,最多为一个上限由WebGPU API指定的边界。

下面的管道阶段属性 将函数指定为特定 shader stageentry point。 这些属性只能应用于 function declarations, 并且在给定的函数上最多可以存在一个。 它们不带参数。

<表类=数据> 流水线阶段属性 <头> 属性描述 vertex
将函数声明为 vertex shader stageentry point render pipelinefragment
将函数声明为 fragment shader stageentry point render pipelinecompute
将函数声明为 compute shader stageentry point compute pipeline

3.9. 指令

指令(directive)是一个token序列,它修改了WebGPU实现对WGSL程序的处理方式。见§ 10.1 启用指令

global_directive :

| enable_directive

3.10. 声明和范围

声明(declaration)将标识符(identifier)与以下类型的对象之一相关联:

换句话说,声明引入了对象的名称(name)。

声明的范围(scope)是一组程序位置,其中使用声明的标识符可能表示其关联对象。我们说标识符在那些源位置的(声明的)范围内(in scope)。

使用标识符时,对于某些声明,它必须是 in scope 或作为指令的一部分。 当标识符用于该名称的一个或多个声明的范围内时,该标识符将表示最接近该用途的非 module-scope 声明的对象,或者如果是模块范围声明 范围内没有其他声明。 我们说标识符使用 resolves 到那个声明。

声明出现的位置决定了它的范围。 通常,范围是在声明结束后立即开始的一段文本。 module scope 处的声明是例外,如下所述。

当该标识符已经在范围内且与该名称的另一个实例具有相同的范围结束时,声明不得引入该名称。

某些对象由 WebGPU 实现提供,并被视为已被每个 WGSL 程序声明。 我们说这样的对象是预先声明的(predeclared)。他们的范围是整个WGSL程序。预先声明的对象的示例为:

如果声明出现在任何其他声明的文本之外,则声明在模块范围。 整个程序的模块范围声明是 in scope。 也就是说,模块范围内的声明可以被该声明之后或之前的源文本引用。

如果任何模块范围声明是递归的,则为 shader-creation error。 也就是说,声明之间不能有循环:

考虑有向图,其中:

此图不得有环。

注意:function bodyfunction declaration 的一部分,因此 函数不得直接或间接递归。

注意:非module scope 标识符的使用必须遵循文本中该标识符的声明。 然而,对于 module scope 声明来说,情况并非如此,它们可能在文本中被乱序引用。

注意:只有 function declaration 可以包含其他声明。

EXAMPLE: Valid and invalid declarations
// 无效,无法重用内置函数名称。
var<private> modf: f32 = 0.0;

// 有效,foo_1 在整个程序的范围内。
var<private> foo: f32 = 0.0; // foo_1

// 有效,my_func_1 在整个程序的范围内。
var<private> bar: u32 = 0u; // bar_1

// 有效,my_func_1 在程序结束前一直在作用域内。
// 有效,foo_2 一直在作用域内直到函数结束。
fn my_func(foo: f32) { // my_func_1, foo_2
  // 对 'foo' 的任何引用都解析为函数参数。

  // 无效,foo_2 的作用域结束于函数的 。
  var foo: f32; // foo_3

  // 有效,bar_2 一直在作用域内直到函数结束。
  var bar: u32; // bar_2
  // 对 'bar' 的引用解析为 bar_2
  {
    // 有效,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++ ) { // i_1
      // 无效,i_2 与 i_1 具有相同的结束范围。
      var i: i32 = 1; // i_2.
    }
  }

  // 无效,bar_5 与 bar_2 具有相同的结束范围。
  var bar: u32; // bar_5

  // 有效的模块范围声明在整个程序的范围内。
  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;

4. 类型

程序计算值。

在WGSL中,类型(type)是一组值,每个值都属于一种类型。值的类型决定了可以对该值执行的操作的语法和语义。

例如,数学数字 1 对应于 WGSL 中的这些不同值:

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

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

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

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

注意:WGSL reference types 未写在WGSL程序中。见§ 4.5 内存视图类型

4.1. 类型检查

WGSL值是通过计算表达式来计算的。表达式(expression) 是解析为名称以“_expression”结尾的 WGSL 语法规则之一。 表达式 E 可以包含 subexpressions,它们是正确包含在外部表达式 E 中的表达式。 顶级表达式 是一个本身不是子表达式的表达式。 参见 § 6.17 表达式语法总结

表达式求值产生的特定值取决于:

计算特定表达式可能产生的值将始终属于特定的WGSL类型,称为表达式的静态类型(static type)。WGSL的规则被设计为表达式的静态类型仅取决于表达式的静态上下文。

类型断言(type assertion)是从一些WGSL源表达式到WGSL类型的映射。符号

e : T

是一个意思为T为WGSL表达式e的静态类型的断言。

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

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

类型检查 一个成功解析的WGSL程序是映射过程 每个表达式为其静态类型, 并验证是否满足每个语句的类型要求。 如果类型检查失败,则会产生一个 shader-creation error 的特殊情况,称为 type error

可以通过递归应用 type rules 来执行类型检查 到句法短语,其中 句法短语expressionstatement类型规则 描述 syntactic phrasestatic context 如何 确定该短语中包含的表达式的静态类型。 type rule 有两个部分:

类型规则的每个不同类型参数化称为 overload。 例如,unary negation-e 形式的表达式)有十二个重载,因为它的类型规则由类型 T 参数化。 可以是以下任何一种:

在以下情况下,类型规则适用于句法短语

考虑表达式,1u+2u。 它有两个 literal 子表达式1u2u,都是 u32 类型。 顶级表达式 是一个附加项。 参考 § 6.8 算术表达式 规则,标量 u32 加法的类型规则适用于表达式,因为:

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

继续上面的例子,只有一个类型规则适用于表达式 1u+2u,所以类型检查 接受该类型规则的结论,即 1u+2u 是 u32 类型。

一个WGSL源程序为well-typed当:

否则会出现类型错误(type error)并且源程序不是有效的WGSL程序。

WGSL是一种静态类型语言(statically typed language),因为 WGSL 程序的类型检查要么成功要么发现类型错误,而只需要检查程序源文本。

4.1.1. 类型规则表

对于表达式WGSL类型规则(type rules)被组织成类型规则表(type rule tables),每个类型规则占一行。

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

TODO:示例:非结果值效应是函数调用子表达式的任何副作用。

4.1.2. 转化排名

当一个类型断言 e:T用作 type rule precondition,满足以下条件:

该规则由 ConversionRank 函数对类型对进行编码,在下表中定义。 ConversionRank 函数表示自动将一种类型 (Src) 的值转换为另一种类型 (Dest) 的偏好和可行性。 较低的等级更可取。

可行的自动转换 将值从 Src 类型转换为 Dest 类型,并且当 ConversionRank(Src,Dest) 是有限的时允许。 这种转换是保值的,受 § 12.5 浮点计算 中描述的限制。

注意:自动转换仅在两种情况下发生。 首先,将 creation-time constant 转换为可在 GPU 上使用的相应类型数值时。 其次,当发生来自内存引用的加载时,会产生存储在该内存中的值。

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

注意:当不进行转换时,转换等级为零。

ConversionRank from one type to another
Src Dest ConversionRank(Src,Dest) Notes
T T 0 Identity. No conversion performed.
ref<S,T,A>
where A is read or read_write
T 0 Apply the Load Rule to load a value from a memory reference.
AbstractFloat f32 1
AbstractFloat f16 2
AbstractInt i32 3
AbstractInt u32 4
AbstractInt AbstractFloat 5
AbstractInt f32 6 Behaves as AbstractInt to AbstractFloat, and then AbstractFloat to f32
AbstractInt f16 7 Behaves as AbstractInt to AbstractFloat, and then AbstractFloat to f16
S T
where above cases don’t apply
infinity There are no automatic conversions between other types.

4.1.3. 过载分辨率

当多个 type 规则适用于句法短语 时,使用平局程序 来决定哪一个应该生效。 此过程称为重载解析, 并假设类型检查已经成功找到 subexpressions 的静态类型。

考虑一个 syntactic phrase P,并且所有 type 规则,这些规则将 应用于 P。 重载解析算法将这些类型规则称为重载候选。 对于每个候选人:

P 的重载分辨率如下进行,目标是找到一个最 preferable overload Candidate

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

  2. 给候选者排序:给定两个重载候选者C1C2C1 首选优于C2如果:

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

      • 即每次表达式转换都需要应用C1P至少与应用 C2 所需的相应表达式转换一样可取到 P

    • 至少有一个表达式位置 i其中C1.R(i) < C2.R(i)。

      • 即应用 C1 至少需要进行一次表达式转换这比应用 C2 所需的相应转换更可取。

  3. 如果只有一个候选人 C这是 preferred 高于所有其他,然后重载解析成功,产生候选类型规则 C。 否则,重载解析失败。

    TODO: Examples

4.2. 创建时间常量的类型

某些表达式在 shader-creation time 时计算, 并且具有可能比 GPU 直接实现的数值范围和精度更大的数值范围和精度。

WGSL 为这些评估定义了两个 抽象数字类型

对其中一种类型的表达式的求值不得溢出或产生未定义的结果。 否则,结果是 shader-creation error

这些类型不能在 WGSL 源代码中拼写。它们仅由 type checks 使用。

不是抽象数字类型也不包含抽象数字的类型 类型称为 concrete

没有后缀的 numeric literal 表示 abstract numeric type 中的值:

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

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

示例:让 x = 1 + 2.5;

示例:1u + 2.5 导致 shader-creation error

4.3. 普通类型

普通类型(Plain types) 是布尔值、数字、向量、矩阵或这些值的机器表示类型,。

普通类型(plain type)是一个标量(scalar)类型,一个原子(atomic)类型,或一个复合(composite)类型。

注意:WGSL中的普通类型类似于C++中的Plain-Old-Data类型,但也包括原子类型。

4.3.1. 布尔类型

布尔(bool)类型包括值 truefalse

Boolean literal type rules
Precondition Conclusion Description
true: bool The true value.
OpConstantTrue %bool
false: bool The false value.
OpConstantFalse %bool

4.3.2. 整数类型

u32类型为一组32-位无符号整数的集合。

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

4.3.3. 浮点类型

f32类型是一组IEEE-754 binary32 (单精度)格式的32-位浮点型数值。详见§ 12.5 浮点计算f16 类型是 IEEE-754 binary16(半精度)格式的 16 位浮点值集。 如果使用 f16 类型,则为 shader-creation error,除非程序包含“enable f16;” 启用 f16 extension 的指令。 有关详细信息,请参阅 § 12.5 浮点计算

4.3.4. 标量类型

标量(scalar)类型有bool, i32, u32, f32, 和 f16

数字标量(numeric scalar)类型有i32, u32, f32, 和 f16

整数标量(integer scalar)有i32u32

4.3.5. 向量类型

向量(vector)是一组由2,3,或4个标量(scalar)或者abstract numeric type组件构成的序列。

Type Description
vecN<T> N个类型T元素构成的向量。N必须为{2, 3, 4}中的值,T必须为一种标量(scalar)或者abstract numeric type类型。我们称T为向量的组件类型(component type)。

如果向量的组件类型是 numeric scalar,则向量是 numeric vector

一个向量是一个抽象向量,如果它的组件类型是一个abstract numeric type

向量的关健用例包括:

向量上的许多操作都是按组件(component-wise)进行的,即结果向量是通过对每个组件独立操作而形成的。

EXAMPLE: Vector
vec2<f32>  // 两个f32组成的向量。
EXAMPLE: Component-wise addition
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]

4.3.6. 矩阵类型

矩阵(matrix)是一组由2,3,或4个浮点型向量组成的序列,

Type Description
matCxR<T> CR行矩阵,NM都为{2, 3, 4}中的值。并且 T必须是f32, f16, or AbstractFloat。也就是说,它同样可以被看作C列vecR<f32>类型的向量。

矩阵的关键用例是体现线性变换。在这种解释中,矩阵的向量被视为列向量。

乘积操作符(*)可用于:

§ 6.8 算术表达式

EXAMPLE: Matrix
mat2x3<f32>  // 此为 2 列 3 行的 32-位浮点数矩阵。
             // 等价地,此为 2 列类型 vec3<f32> 构成的向量。

4.3.7. 原子类型

原子类型(atomic type)封装了一个整数标量integer scalar类型,使得:

Type Description
atomic<T> 类型T的原子。T必须为u32i32

表达式不得计算为原子类型。

原子类型只能由 workgroup 地址空间中的变量或具有 read_write 访问模式的 storage buffer 变量实例化。 对类型的操作的 memory scope 由实例化的 address space 确定。 workgroup 地址空间中的原子类型的内存范围为 Workgroup,而 storage 地址空间中的原子类型的内存范围为 QueueFamily

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

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

4.3.8. 数组类型

数组(array)是一组可索引的元素值。

Type Description
array<E,N> N个类型为E的元素组成的固定大小数组(fixed-size array
N被称为数组的元素计数(element count)。
array<E> 由类型E元素构成的runtime-sized数组。它们仅出现在特定上下文中。

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

表达式不得计算为运行时大小的数组类型。

元素计数表达式 N 固定大小的数组必须:

注意:元素计数在pipeline creation时已被完全确定。

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

注意:元素类型必须是 plain type

当且仅当以下所有条件都为真时,两种数组类型是相同的:

EXAMPLE: Example fixed-size array types, non-overridable element count
// array<f32,8> and array<i32,8> are different types:
// different element types
var<private> a: array<f32,8>;
var<private> b: array<i32,8>;
var<private> c: array<i32,8u>;  // array<i32,8> and array<i32,8u> are the same type

const width = 8;
const height = 8;

// array<i32,8>, array<i32,8u>, and array<i32,width> are the same type.
// Their element counts evaluate to 8.
var<private> d: array<i32,width>;

// array<i32,height> and array<i32,width> are the same type.
var<private> e: array<i32,width>;
var<private> f: array<i32,height>;

注意:由可覆盖常量确定大小的数组的唯一有效使用是作为 workgroup 空间中变量的存储类型。

EXAMPLE: Workgroup variables sized by overridable constants
override blockSize = 16;

var<workgroup> odds: array<i32,blockSize>;
var<workgroup> evens: array<i32,blockSize>;

 // 一个无效的例子,因为可覆盖的元素计数可能只发生
 // 在外层。
 // var<workgroup> both: array<array<i32,blockSize>,2>;
 // 一个无效的例子,因为可覆盖的元素数量只有
 // 对工作组变量有效。
 // var<private> bad_storage_space: array<i32,blockSize>;
array_type_decl :

| array less_than type_decl ( comma element_count_expression ) ? greater_than

element_count_expression :

| int_literal

| uint_literal

| bitwise_expression

4.3.9. 结构类型

structure是一组已命名的成员值。

Type Description
struct<T1,...,TN> 类型为T1TNN个成员的有序元组,其中N是大于0的整数。结构类型声明为每个成员指定标识符(identifier)名称。相同结构类型的两个成员不得具有相同的名称。

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

注意:任何成员类型必须为普通类型(plain type)。

限制结构成员和数组元素类型的一些后果是:

EXAMPLE: Structure
// 拥有四个成员的结构
struct Data {
  a: i32,
  b: vec2<f32>,
  c: array<i32,10>,
  d: array<f32>, // 最后这个逗号可以不写
};
struct_decl :

| attribute * struct ident struct_body_decl

struct_body_decl :

| brace_left ( struct_member comma ) * struct_member comma ? brace_right

struct_member :

| attribute_list * variable_ident_decl

WGSL定义了以下可应用于结构成员的属性:

注意:如果结构类型用于定义统一缓冲区(uniform buffer)或存储缓冲区(storage buffer),则可能需要布局属性。见§ 4.4.7 内存布局

EXAMPLE: Structure declaration
struct my_struct {
  a: f32,
  b: vec4<f32>
}
EXAMPLE: Structure WGSL
// TODO: runtime-sized array syntax may have changed
// Runtime Array
type RTArr = array<vec4<f32>>;
struct S {
  a: f32,
  b: f32,
  data: RTArr
}
@group(0) @binding(0) var<storage> buffer: S;

4.3.10. 复合类型

如果一个类型内部结构表达为其他类型的复合,则其为复合的(composite)。内部部分不重叠,并称为组件(components)。

复合类型为:

对于一个复合类型TTnesting depth,写作NestDepth(T)为:

4.3.11. 可构造类型

多种类型的数值可以被创建,加载,存储,传递至函数,以及作为函数返回值。 我们称之为可构造的(constructible)。

一个类型为可构造的(constructible)如果其为以下之一:

注意:所有可构造类型为普通类型(plain)并有 creation-fixed footprint

注意:原子类型和runtime-sized数组类型是不可构造的。包含原子和runtime-sized数组的复合类型是不可构造的。

4.3.12. 固定占用类型

变量的内存占用memory footprint是用于存储变量内容的memory locations的数量。 变量的内存占用取决于它的 store type,并在 shader 生命周期 中的某个时刻最终确定。 大多数变量在 着色器创建 时间很早就确定了大小。 一些变量可能会在 pipeline creation 时间稍后调整大小, 而其他变量可能会在 start of shader execution 时调整大小。

如果 plain type 的大小在 shader creation 时完全确定, 则 plain type 具有创建固定的占用空间creation-fixed footprint

如果 plain type 的大小在 pipeline creation 时完全确定,则其具有固定占用空间 fixed footprint

注意:管道的创建依赖于着色器的创建,因此具有 creation-fixedfootprint 的类型也具有 fixedfootprint

带有 creation-fixedfootprint 的普通类型是:

注意:constructible 类型具有 creation-fixed footprint

带有 fixed footprint 的普通类型是以下任何一种:

注意:固定大小数组的唯一有效用途是元素计数是 override expression 而不是 creation-time expressionstore type for a workgroup 变量。

注意:固定足迹类型可以直接或间接包含 atomic 类型,而 constructible 类型不能。

注意:固定足迹类型不包括 runtime-sized 数组,以及任何包含 runtime-sized 数组的结构或数组,递归。

4.4. 内存

WGSL中,可存储类型的值可以存储在内存中,以供以后检索。本节介绍内存的结构,以及如何使用WGSL类型来描述内存的内容。

4.4.1. 内存位置

内存由一组不同的内存位置(memory locations)组成。每个内存位置的大小为8位。影响内存的操作与一组由一个或多个组成的内存位置交互。

如果两组存储器位置的交集非空,则两组存储器位置重叠(overlap)。每个变量声明都有一组内存位置,不会与任何其他变量声明的内存位置集重叠。对结构和数组的内存操作可以访问元素之间的填充,但不得访问结构或数组末尾的填充。

4.4.2. 内存访问模式

内存访问(memory access)是一种作用于内存位置的操作。

单个操作可以为读,写,或读与写。

特定的内存位置可能只支持某些类型的访问,表示为内存的访问模式(access mode):

read

支持读访问,但不是写。

write

支持写访问,但不是读。

read_write

读写访问都支持。

access_mode :

| 'read'

| 'write'

| 'read_write'

4.4.3. 可存储类型

变量(variable)中包含的值必须是可存储(storable)类型。可存储类型可能具有WGSL定义的显式表示,如§ 4.4.7.4 值的内部布局中所述,或者它可能是不透明的,例如纹理和采样器。

如果一个类型为以下之一,其为可存储的(storable

注意:也就是说,可存储类型是普通类型(plain types)、纹理类型和采样器类型。

4.4.4. IO可共享类型

管线输入和输出值必须为IO可共享类型。

一个类型是IO可共享的(IO-shareable)当其为以下之一:

以下类型的值必须是IO可共享类型:

注意:只有内置管线输入可能具有布尔类型。用户输入或输出数据属性不得为bool类型或包含bool类型。见§ 9.3.1 内置输入和输出

4.4.5. 主机可共享类型

主机可共享类型用于描述在主机和GPU之间共享的缓冲区内容,或者在主机和GPU之间复制而无需格式转换的内容。 用于此目的时,该类型必须额外使用布局属性进行修饰,如§ 4.4.7 内存布局中所述。 我们将在§ 5.3 模块作用域变量中看到统一缓冲区(uniform buffer)和存储缓冲区(storage buffer)变量的存储类型(store type)必须是主机可共享的。

类型为主机可共享的(host-shareable)当其为以下之一:

WGSL定义了以下影响内存布局的属性:

注意:当IO-shareable类型T不是bool并且不包含bool时,其为主机可共享的。许多类型为主机可共享的,但不是IO可共享的,包括原子类型(atomic types),runtime-sized数组,以及任何包含它们的复合类型。

注意:IO可共享类型和主机可共享类型都具有具体大小,但是各自计数。IO可共享类型的大小由位置计数指标决定,见§ 9.3.1.3 输入输出位置。主机可共享类型的大小由字节计数指标决定,见§ 4.4.7 内存布局

4.4.6. 地址空间

内存位置被划分为存储类(address spaces)。每个地址空间都具有确定可变性、可见性、它可能包含的值以及如何使用变量的单独属性。

Address Space
Storage class Sharing among invocations Supported access modes Variable scope Restrictions on stored values Notes
function 仅相同调用 read_write Function scope Constructible类型
private 仅相同调用 read_write Module scope Constructible类型
workgroup 相同compute shader workgroup中的调用 read_write Module scope 带有fixed footprintPlain type 最外层数组的 element count 可能是 pipeline-overridable 常量。
uniform 相同shader stage中的调用 read Module scope Constructible host-shareable类型 uniform buffer变量
storage 相同shader stage中的调用 read_write, read (default) Module scope Host-shareable storage buffer变量
handle 相同着色器阶段中的调用 read Module scope Sampler类型或texture类型 sampler和纹理变量

注意:标记 handle 被保留:它在WGSL程序中从不被使用。

注意:纹理变量包含一个不透明的句柄,用于访问底层的纹素网格。句柄本身始终是只读的。在大多数情况下,底层纹素是只读的。对于只写存储纹理,底层纹素是只写的。

address_space :

| function

| private

| workgroup

| uniform

| storage

4.4.7. 内存布局

统一缓冲区(Uniform buffer)和存储缓冲区(storage buffer)变量用于共享在内存中组织为字节序列的批量数据。缓冲区在 CPU 和 GPU 之间共享,或在管线中的不同着色器阶段之间,或在不同管线之间共享。

由于缓冲区数据无需重新格式化或转换即可共享,缓冲区生产者和消费者必须就memory layout达成一致, 这是如何将缓冲区中的字节组织成类型化的WGSL值的描述。

缓冲区变量的存储类型(store type)必须是主机可共享的(host-shareable),具有完全详细的内存布局,如下所述。

每个缓冲区变量必须在uniformstorage地址空间中声明。

类型的内存布局仅在评估具有以下内容的表达式时才有意义:

一个8位字节是host-shareable内存的最基本单位。本节中定义的术语表示 8 位字节的计数。

我们将使用以下符号:

4.4.7.1. 对齐和大小

每个host-shareable数据类型T都有对齐和大小。

类型的 alignment 是对该类型的值可以放置在内存中何处的约束,以整数表示: 一个类型的对齐方式必须平均划分该类型值的起始 memory location 的字节地址。 对齐允许使用更有效的硬件指令来访问这些值,或者满足对某些地址空间的更严格的硬件要求。 (参见 address space layout constraints)。

注意:根据构造,每个对齐值始终是2的幂。

类型或结构成员的 byte-size 是为存储类型或结构成员的值而保留在主机可共享内存中的连续字节数。

主机可共享类型的对齐方式和大小在下表中递归定义:

Alignment and size for host-shareable types
Host-shareable type T AlignOf(T) SizeOf(T)
i32, u32, or f32 4 4
f16 2 2
atomic<|T|> 4 4
vec2<T>, T is i32, u32, or f32 8 8
vec2<f16> 4 4
vec3<T>, T is i32, u32, or f32 16 12
vec3<f16> 8 6
vec4<T>, T is i32, u32, or f32 16 16
vec4<f16> 8 8
matCxR (col-major)

(General form)

AlignOf(vecR) SizeOf(array<vecR, C>)
mat2x2<f32> 8 16
mat2x2<f16> 4 8
mat3x2<f32> 8 24
mat3x2<f16> 4 12
mat4x2<f32> 8 32
mat4x2<f16> 4 16
mat2x3<f32> 16 32
mat2x3<f16> 8 16
mat3x3<f32> 16 48
mat3x3<f16> 8 24
mat4x3<f32> 16 64
mat4x3<f16> 8 32
mat2x4<f32> 16 32
mat2x4<f16> 8 16
mat3x4<f32> 16 48
mat3x4<f16> 8 24
mat4x4<f32> 16 64
mat4x4<f16> 8 32
struct S with members M1...MN max(AlignOfMember(S,1), ... , AlignOfMember(S,N))
roundUp(AlignOf(S), justPastLastMember)

where 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))

where Nruntime is the runtime-determined number of elements of T
4.4.7.2. 结构成员布局

结构 S 的第 i 个成员 具有大小和对齐方式,分别用 SizeOfMember(S, i) 和 AlignOfMember(S, i) 表示。 成员大小和对齐方式用于计算每个成员从结构开始的字节偏移量,如 § 4.4.7.4 值的内部布局 中所述。

SizeOfMember(S, i) 是 k 如果 S 的第 i 个成员 具有属性 size(k)。 否则,它是 SizeOf(T) 其中 T 是成员的类型。

AlignOfMember(S, i) 是 k 如果第 i 个成员具有属性 align(k)。 否则,它是 AlignOf(T) 其中 T 是成员的类型。

如果结构成员使用 size 属性修饰,则该值必须至少与成员类型的大小一样大:

SizeOfMember(S, i) ≥ SizeOf(T)
哪里TS 的第 i 个成员的类型。

第一个结构成员总是从结构的开头有一个零字节偏移:

OffsetOfMember(S, 1) = 0

每个后续成员都放置在满足成员类型对齐的最低偏移量处,并且避免与前一个成员重叠。 对于每个成员索引 i > 1:

OffsetOfMember(S, i) = roundUp(AlignOfMember(S, i ), OffsetOfMember(S, i-1) + SizeOfMember(S, i-1))

EXAMPLE: Layout of structures using implicit member sizes and alignments
struct A {                                     //             align(8)  size(24)
    u: f32,                                    // offset(0)   align(4)  size(4)
    v: f32,                                    // offset(4)   align(4)  size(4)
    w: vec2<f32>,                              // offset(8)   align(8)  size(8)
    x: f32                                    // offset(16)  align(4)  size(4)
    // -- implicit struct size padding --      // offset(20)            size(4)
}

struct B {                           //             align(16) size(160)
    a: vec2<f32>,                              // offset(0)   align(8)  size(8)
    // -- implicit member alignment padding -- // offset(8)             size(8)
    b: vec3<f32>,                              // offset(16)  align(16) size(12)
    c: f32,                                    // offset(28)  align(4)  size(4)
    d: f32,                                    // offset(32)  align(4)  size(4)
    // -- implicit member alignment padding -- // offset(36)            size(4)
    e: A,                                      // offset(40)  align(8)  size(24)
    f: vec3<f32>,                              // offset(64)  align(16) size(12)
    // -- implicit member alignment padding -- // offset(76)            size(4)
    g: array<A, 3>,    //element stride 24     // offset(80)  align(8)  size(72)
    h: i32                                    // offset(152) align(4)  size(4)
    // -- implicit struct size padding --      // offset(156)           size(4)
}

@group(0) @binding(0)
var<storage,read_write> storage_buffer: B;
EXAMPLE: Layout of structures with explicit member sizes and alignments
struct A {                                     //             align(8)  size(32)
    u: f32,                                    // offset(0)   align(4)  size(4)
    v: f32,                                    // offset(4)   align(4)  size(4)
    w: vec2<f32>,                              // offset(8)   align(8)  size(8)
    @size(16) x: f32                       // offset(16)  align(4)  size(16)
};

struct B {                           //             align(16) size(208)
    a: vec2<f32>,                              // offset(0)   align(8)  size(8)
    // -- implicit member alignment padding -- // offset(8)             size(8)
    b: vec3<f32>,                              // offset(16)  align(16) size(12)
    c: f32,                                    // offset(28)  align(4)  size(4)
    d: f32,                                    // offset(32)  align(4)  size(4)
    // -- implicit member alignment padding -- // offset(36)            size(12)
    @align(16) e: A,                        // offset(48)  align(16) size(32)
    f: vec3<f32>,                              // offset(80)  align(16) size(12)
    // -- implicit member alignment padding -- // offset(92)            size(4)
    g: array<A, 3>,    //element stride 32       // offset(96)  align(8)  size(96)
    h: i32                                    // offset(192) align(4)  size(4)
    // -- implicit struct size padding --      // offset(196)           size(12)
}

@group(0) @binding(0)
var<uniform> uniform_buffer: B;
4.4.7.3. 数组布局示例
EXAMPLE: Fixed-size array layout examples
// Array where:
//   - alignment is 4 = AlignOf(f32)
//   - element stride is 4 = roundUp(AlignOf(f32),SizeOf(f32)) = roundUp(4,4)
//   - size is 32 = stride * number_of_elements = 4 * 8
var small_stride: array<f32, 8>;

// Array where:
//   - alignment is 16 = AlignOf(vec3<f32>) = 16
//   - element stride is 16 = roundUp(AlignOf(vec3<f32>), SizeOf(vec3<f32>))
//                          = roundUp(16,12)
//   - size is 128 = stride * number_of_elements = 16 * 8
var bigger_stride: array<vec3<f32>, 8>;
EXAMPLE: Runtime-sized array layout examples
// Array where:
//   - alignment is 4 = AlignOf(f32)
//   - element stride is 4 = roundUp(AlignOf(f32),SizeOf(f32)) = 4
// 如果 B 是绑定的有效缓冲区绑定大小
// 绘制或调度命令,元素个数为:
//   N_runtime = floor(B / element stride) = floor(B / 4)
@group(0) @binding(0)
var<storage> weights: array<f32>;

// Array where:
//   - alignment is 16 = AlignOf(vec3<f32>) = 16
//   - element stride is 16 = roundUp(AlignOf(vec3<f32>), SizeOf(vec3<f32>))
//                          = roundUp(16,12)
// 如果 B 是绑定的有效缓冲区绑定大小
// 绘制或调度命令,元素个数为:
//   N_runtime = floor(B / element stride) = floor(B / 16)
var<uniform> directions: array<vec3<f32>>;
4.4.7.4. 值的内部布局

本节描述了如何将值的内部结构放置在缓冲区的字节位置中,给定一个假设的整个值的位置。 这些布局取决于值的类型,以及结构成员的 alignsize 属性。

放置值的缓冲区字节偏移量必须满足类型对齐要求: 如果 T 类型的值 被放置在缓冲区偏移 k,然后 k = c × AlignOf(T),对于一些非负整数c

无论地址空间如何,数据都会以相同的方式出现。

u32i32类型的值V放置在主机共享缓冲区的字节偏移量k处时,则:

注意:回想一下,i32使用二进制补码表示,因此符号位位于第31位。

f32类型的值VIEEE-754 binary32格式表示。它有1个符号位、8个指数位和23个分数位。当V被放置在主机共享缓冲区的字节偏移 k处时,则:

一个值 V f16 类型的以 IEEE-754 binary16 格式表示。 它有 1 个符号位、5 个指数位和 10 个小数位。 当 V 被放置在字节偏移 k 主机共享缓冲区,然后:

注意:上述规则隐含表明主机共享缓冲区中的数值以little-endian格式存储。

当原子类型(atomic typeatomic<T> 的值V放置在主机共享缓冲区中时,它具有与基础类型T的值相同的内部布局。

当向量类型 vector type vecN<T> 的值 V 放置在主机共享缓冲区的字节偏移量 k 处时,则:

当一个值 V vector type matCxR<T> 被放置在字节偏移 k 主机共享缓冲区,然后:

当数组类型 array type A 的值放置在主机共享内存缓冲区的字节偏移量 k 处时,则:

当结构类型 structure type S 的值被放置在主机共享内存缓冲区的字节偏移量 k 处时,则:

4.4.7.5. 地址空间布局约束

地址空间(storage)和(uniform)具有不同的缓冲区布局约束,本节将对此进行介绍。

变量直接或间接引用的所有结构和数组类型都必须遵守变量地址空间的约束。 违反地址空间约束会导致 shader-creation error

在本节中,我们将 RequiredAlignOf(S, C) 定义为主机可共享类型 S 的值的字节偏移 alignment 要求 在地址空间 C 中使用时。

storageuniform存储类的主机可共享类型的对齐要求。
Host-shareable type S RequiredAlignOf(S, storage) RequiredAlignOf(S, uniform)
i32, u32, f32, or 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) roundUp(16, AlignOf(S))
struct S AlignOf(S) roundUp(16, AlignOf(S))

T 类型的结构成员 必须有一个字节偏移量从结构的开头开始,该结构是地址空间 CRequiredAlignOf(T, C) 的倍数:

OffsetOfMember(S, M) = k × RequiredAlignOf(T, C)
其中 k 为一个正整数,且 M 为具有类型 T 的结构 S 的成员。

元素类型 T 的数组的元素步幅(element stride)必须是对地址空间CRequiredAlignOf(T, C) 的倍数:

StrideOf(array<T, N>) = k × RequiredAlignOf(T, C)
StrideOf(array<T>) = k × RequiredAlignOf(T, C)
其中k 是一个正整数

注意:RequiredAlignOf(T, C) 不会对对齐(align)修饰允许的值施加任何额外限制,也不会影响 AlignOf(T) 的规则。数据按照前面部分中定义的规则进行布局,然后根据 RequiredAlignOf(T, C) 规则验证生成的布局。

统一(uniform)存储类还要求:

注意:以下示例展示了如何在结构成员上使用 alignsize 属性来满足统一缓冲区的布局要求。 特别是,可以使用这些技术将具有 std140 布局的 GLSL 缓冲区机械地转换为 WGSL。

EXAMPLE: Satisfying offset requirements for uniform address space
struct S {
  x: f32,
}
struct Invalid {
  a: S,
  b: f32 // 无效:a 和 b 之间的偏移量为 4 字节,但必须至少为 16
}
struct Valid {
  a: S,
  [[align(16)]] b: f32 // 有效:a 和 b 之间的偏移量为 16 字节
}
@group(0) @binding(0) var<uniform> invalid: Invalid;
@group(0) @binding(1) var<uniform> valid: Valid;
EXAMPLE: Satisfying stride requirements for uniform address space
struct small_stride {
  a: array<f32,8> // stride 4
}
@group(0) @binding(0) var<uniform> invalid: small_stride; // Invalid

struct wrapped_f32 {
  @size(16) elem: f32
}
struct big_stride {
  a: array<wrapped_f32,8> // stride 16
}
@group(0) @binding(1) var<uniform> valid: big_stride;     // Valid

4.5. 内存视图类型

除了使用普通(plain)值进行计算之外,WGSL程序还经常通过内存访问(memory access)操作从内存中读取值或将值写入内存。 每个内存访问都是通过内存视图(memory view)执行的。

内存视图(memory view)由以下内容组成:

内存视图的访问方式必须被存储类支持。见§ 4.4.6 地址空间

WGSL 有两种表示内存视图的类型:引用类型(reference types)和指针类型(pointer types)。

Constraint Type Description
S 是一个存储类(address space),
T是一个可存储(storable)类型,
A 是访问模式(access mode
ref<S,T,A> 引用类型(reference type)由一组对 SC 中的内存位置持有类型 T 的值的内存视图(memory views)标识,支持模式 A 中描述的内存访问。
在这种情况下,T 被称为存储类型(store type)。
参考类型没有写在 WGSL 源程序中;相反它们用于分析 WGSL 程序。
S是一个存储类(address space),
T是一个可存储(storable)类型,
A 是一个访问模式(access mode
ptr<S,T,A> 指针类型(pointer type)由一组对 S 中内存位置持有类型 T 的值的内存视图(memory views)标识,支持模式 A 中描述的内存访问。
在这种情况下,T 被称为指针类型(pointee type)。
指针类型可能出现在 WGSL 程序源中。

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

但是,在 WGSL 文本中:

EXAMPLE: Pointer type
fn my_function(
  // 'ptr<function,i32,read_write>' 是指针值的类型,它使用’function' 地址空间中的内存位置来引用内存以保持’i32' 值。
  // 这里 'i32' 是指针类型。
  // 隐含的访问模式为 'read_write' ,访问模式的默认值见下文。
  ptr_int: ptr<function,i32>,

  // 'ptr<private,array<f32,50>,read_write>' 是指针值的类型,
  // 它指的是使用 'private' 存储类中的内存位置保存 50 个类型为 'f32' 元素的数组的存储。
  // 这里的指针类型是’array<f32,50>'。 隐含的访问模式是“read_write”。见下面的访问模式默认值。
  ptr_array: ptr<private, array<f32, 50>>
) { }

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

每个类型ptr<S,T,A>的指针值 p 对应于一个类型ref<S,T,A>的单独参考值r,反之亦然,其中 pr 描述相同的内存视图。

4.5.1. 访问模式默认值

内存视图的访问模式通常由上下文决定:

当在 WGSL 源码中写入变量声明(variable declaration)或指针类型(pointer type)时:

4.5.2. 原始变量

在 WGSL 中,参考值始终对应于某些变量的部分或全部内存位置的内存视图。这定义了参考值的原始变量(originating variable)。

一个指针值总是对应一个引用值,因此指针的原始变量与相应引用的原始变量相同。

注意:原始变量是一个动态概念。函数形式参数的原始变量取决于函数的调用位置(call sites)。 不同的调用位置可以提供指向不同起始变量的指针。

如果一个引用或指针访问出界,则产生一个无效内存引用(invalid memory reference)。

从无效引用中加载(Loads)返回以下内容之一:

对无效引用的存储(Stores)可能:

对无效内存引用进行操作的读-修改-写原子(Read-modify-write atomics)必须从相同的内存位置(memory locations)加载和存储,如果它们访问内存。

4.5.3. 引用和指针用例

引用和指针通过使用方法来区分:

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

EXAMPLE: Reference types enable simple use of variables
@compute
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;
}
EXAMPLE: Returning a reference returns the value loaded via the reference
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();
}

以这种方式定义指针可以实现两个关键用例:

注意:以下示例使用了本规范稍后解释的 WGSL 功能。

EXAMPLE: Using a pointer as a short name for part of a variable
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
fn main() {
  // 在存储内存中形成一个指向特定粒子的指针。
  let active_particle: ptr<storage,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;
}
EXAMPLE: Using a pointer as a formal parameter
fn add_one(x: ptr<function,i32>) {
  // 更新 'x' 的位置以包含下一个更高的整数值,(或环绕到最大的负 i32 值)。
  // 在左侧,一元“*”将指针转换为随后可以分配给的引用。
  // 默认情况下,它具有 read_write 访问模式。
  // 在右侧:
  // - 一元'*'将指针转换为引用,具有 read_write 访问模式。
  // - 唯一匹配的类型规则是加法 (+) 并且要求 '*x' 具有类型 i32,这是 '*x' 的存储类型。
  //   所以负载规则应用并且 '*x' 计算为在计算时为 '*x' 存储在内存中的值,即 0 的 i32 值。
  // - 将 1 添加到 0,以生成右侧的最终值 1 - 手边。 将 1 存储到 '*x' 的内存中。
  *x = *x + 1;
}

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

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

4.5.4. 形成引用和指针值

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

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

EXAMPLE: Component reference from a composite reference
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>。
    //    加载规则适用(因为没有其他类型规则可以适用),以及初始化器的计算产生 vec2<f32> 值,
    //    该值是在执行声明时从 'm[2]' 引用的内存位置加载的。
    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>。 他让声明要求右手边是 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;
}

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

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

EXAMPLE: Pointer from a variable
// 在私有地址空间中声明一个变量,用于存储 f32 值。
var<private> x: f32;

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

    // 名称’x'解析为模块范围变量’x',并且具有引用类型 ref<private,f32,read_write>。
    // 应用一元“&”运算符将引用转换为指针。
    // 访问方式与原变量的访问方式相同,所以完全指定的类型为ptr<private,f32,read_write>。
    // 但是read_write是函数地址空间的默认访问方式,所以这种情况下不需要拼写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;
}

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

本节是信息性的,是不规范的。

WGSL中的引用和指针相较于其他语言受更多的限制。具体为:

注意:根据上述规则,不可能形成“悬空”指针,即不引用有效的(或“活动的”)原始变量的内存的指针。

4.6. 纹理和采样器类型

纹素(texel)是用作纹理的最小可独立访问元素的标量或向量。texel这个词是纹理元素的缩写。

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

WGSL 纹理对应于 WebGPU GPUTexture

纹理可以是数组形式的,也可以是非数组的:

纹理具有以下特性:

纹素格式

每个纹素中的数据,见§ 4.6.1 纹素格式

维度

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

大小

沿每个维度的网格坐标的范围

mip 级别计数

采样纹理的 mip 级别计数至少为 1,存储纹理的 mip 级别计数至少为 1。Mip level 0 包含纹理的全尺寸版本。每个连续的 mip 级别都包含前一个 mip 级别的过滤版本,大小为前一个 mip 级别的一半(在舍入范围内)。在对纹理进行采样时,使用显式或隐式计算的细节级别来选择从中读取纹素数据的 mip 级别。 然后通过过滤将它们组合起来以产生采样值。

数组化

纹理是否为数组化

数组大小(array size

齐次网格的数量,如果纹理为数组化的

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

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

这样,纹理类型支持的操作集由接受该纹理类型作为第一个参数的纹理内置函数的可用性决定。

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

采样器是一个不透明的句柄,它控制如何从采样纹理访问 texels

WGSL 采样器映射到 WebGPU GPUSampler

Texel 访问是通过采样器的几个属性控制的:

寻址方式

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

过滤模式

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

LOD 钳位

控制访问的详细信息的最小和最大级别。

比较

控制为 comparison sampler 所做的比较类型。 参见 WebGPU GPUCompareFunction

最大各向异性

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

采样器不能在 WGSL 程序中创建,它们的状态(例如上面列出的属性)在着色器中是不可变的,只能由 WebGPU API 设置。

如果过滤采样器(即任何采样器 使用插值过滤)与具有不可过滤格式的纹理一起使用。

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

4.6.1. 纹素格式

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

纹素格式(texel format)被归类为:

频道(channels

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

频道格式(channel format

通道中的位数,以及如何解释这些位。

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

WGSL 源代码中仅使用某些纹素格式。用于定义这些纹素格式的频道格式在频道格式(Channel Formats)表中列出。 最后一列指定从存储的频道位到着色器中使用的值的转换。这也称为频道传递函数(channel transfer function)或 CTF。

Channel Formats
Channel format Number of stored bits Interpetation of stored bits Shader type Shader value (Channel Transfer Function)
8unorm 8 无符号整数 v ∈ {0,...,255} f32 v ÷ 255
8snorm 8 有符号整数 v ∈ {-128,...,127} f32 max(-1, v ÷ 127)
8uint 8 无符号整数 v ∈ {0,...,255} u32 v
8sint 8 有符号整数 v ∈ {-128,...,127} i32 v
16uint 16 无符号整数 v ∈ {0,...,65535} u32 v
16sint 16 有符号整数 v ∈ {-32768,...,32767} i32 v
16float 16 IEEE-754 binary16 16-位浮点值 v, 1 符号位,5指数位,10尾数位 f32 v
32uint 32 32-位无符号整数值 v u32 v
32sint 32 32-位有符号整数值 v i32 v
32float 32 IEEE-754 binary32 32-位浮点值 v f32 v

存储纹理的纹素格式(Texel Formats for Storage Textures)表中列出的纹素格式对应于支持 WebGPU STORAGE 使用的 WebGPU 纯色格式(WebGPU plain color formats)。这些纹素格式用于参数化在§ 4.6.5 存储纹理类型中定义的存储纹理类型。

当纹素格式没有所有四个频道时,则:

下表中的最后一列使用了频道格式(channel formats)表中特定于格式的频道传输函数(channel transfer function)。

存储纹理的纹素格式
Texel format Channel format Channels in memory order Corresponding shader value
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))
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))
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), 0.0, 1.0)
rg32sint 32sint r, g vec4<i32>(CTF(r), CTF(g), 0.0, 1.0)
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))

4.6.2. 采样纹理类型

texture_1d<type>

texture_2d<type>

texture_2d_array<type>

texture_3d<type>

texture_cube<type>

texture_cube_array<type>

4.6.3. 多重采样纹理类型

texture_multisampled_2d<type>

4.6.4. 外部采样纹理类型

texture_external

texture_external 是一种类似于 texture_2d<f32> 的不透明 2d 浮点采样纹理类型,但可能具有不同的表示。可以使用 textureLoadtextureSampleLevel 读取它,它们不透明地处理这些不同的表示。

详见WebGPU § GPUExternalTexture

4.6.5. 存储纹理类型

存储纹理(storage texture

存储纹理类型必须由一种存储纹理的纹素格式(texel formats for storage textures)进行参数化。纹素格式决定了 § 4.6.1 纹素格式 中指定的转换函数。

对于只写存储纹理,转换函数的函数用于将着色器值转换为存储的纹素。

§ 16.8 纹理内置函数

TODO(dentro):将转换的描述移动到实际进行读取的内置函数中。

texture_storage_1d<texel_format,access>

texture_storage_2d<texel_format,access>

texture_storage_2d_array<texel_format,access>

texture_storage_3d<texel_format,access>

4.6.6. 深度纹理类型

texture_depth_2d

texture_depth_2d_array

texture_depth_cube

texture_depth_cube_array

texture_depth_multisampled_2d

4.6.7. 采样器类型

采样器(sampler)通过执行以下组合来调节对采样纹理或深度纹理的访问:

Type Description
sampler 采样器。调节对采样纹理的访问。
sampler_comparison 比较采样器。调节对深度纹理的访问。

采样器在被 WebGPU API 创建时参数化。它们在 WGSL 程序中不能被修改。

采样器仅能通过texture builtin functions被使用。

sampler

sampler_comparison

4.6.8. 纹理类型语法

texture_sampler_types :

| sampler_type

| depth_texture_type

| sampled_texture_type less_than type_decl greater_than

| multisampled_texture_type less_than type_decl greater_than

| storage_texture_type less_than texel_format comma access_mode greater_than

sampler_type :

| sampler

| sampler_comparison

sampled_texture_type :

| texture_1d

| texture_2d

| texture_2d_array

| texture_3d

| texture_cube

| texture_cube_array

multisampled_texture_type :

| texture_multisampled_2d

storage_texture_type :

| texture_storage_1d

| texture_storage_2d

| texture_storage_2d_array

| texture_storage_3d

depth_texture_type :

| texture_depth_2d

| texture_depth_2d_array

| texture_depth_cube

| texture_depth_cube_array

| texture_depth_multisampled_2d

texel_format :

| 'rgba8unorm'

| 'rgba8snorm'

| 'rgba8uint'

| 'rgba8sint'

| 'rgba16uint'

| 'rgba16sint'

| 'rgba16float'

| 'r32uint'

| 'r32sint'

| 'r32float'

| 'rg32uint'

| 'rg32sint'

| 'rg32float'

| 'rgba32uint'

| 'rgba32sint'

| 'rgba32float'

4.7. 类型别名

类型别名 为现有类型声明一个新名称。 声明必须出现在module scope,它的scope 就是整个程序。
type_alias :

| type ident equal type_decl

EXAMPLE: Type Alias
type Arr = array<i32, 5>;

type RTArr = array<vec4<f32>>;
const pi_approx: single = 3.1415;
    fn two_pi() -> single {
      return single(2) * pi_approx;
    }

4.8. 类型声明语法

type_decl :

| ident | type_decl_without_ident

type_decl_without_ident :

| bool

| float32

| float16

| int32

| uint32

| vec_prefix less_than type_decl greater_than

| mat_prefix less_than type_decl greater_than

| pointer less_than address_space comma type_decl ( comma access_mode ) ? greater_than

| array_type_decl

| atomic less_than type_decl greater_than

| texture_sampler_types

vec_prefix :

| vec2

| vec3

| vec4

mat_prefix :

| mat2x2

| mat2x3

| mat2x4

| mat3x2

| mat3x3

| mat3x4

| mat4x2

| mat4x3

| mat4x4

当类型声明是标识符(identifier)时,表达式必须在标识符声明(declaration)的范围内,作为类型别名或结构类型。

EXAMPLE: Type Declarations
identifier
  Allows to specify types created by the type command

bool

f32

i32

u32

vec2<f32>

array<f32, 4>

array<f32>

mat2x3<f32>
EXAMPLE: Access modes for buffers
// Storage buffers
@group(0) @binding(0)
var<storage,read> buf1: Buffer;       // Can read, cannot write.
@group(0) @binding(0)
var<storage> buf2: Buffer;            // Can read, cannot write.
@group(0) @binding(1)
var<storage,read_write> buf3: Buffer; // Can both read and write.

// Uniform buffer. Always read-only, and has more restrictive layout rules.
struct ParamsTable {weight: f32}
@group(0) @binding(2)
var<uniform> params: ParamsTable;     // Can read, cannot write.

5. 变量和值声明

5.1. 值声明

WGSL 作者可以使用 value declaration 为不可变值声明名称,这些名称可以是:

值声明没有任何关联的存储。 也就是说,没有与声明关联的 memory locations

5.1.1. let 声明

let 声明(let declaration)指定值的名称。一旦计算出 let 声明的值,它就是不可变的。当标识符(identifier) 使用resolves至一个let-声明,该标识符表示该值。

当声明一个 let 标识符时没有明确指定的类型,例如 let foo = 4,类型是从 equals 标记右侧的表达式自动推断的。 let 声明的类型总是 concrete。 当指定类型时,例如 let foo: i32 = 4,初始化表达式必须计算为该类型。

let-声明只能出现在函数定义中。

EXAMPLE: let-declared constants at function scope
// 'blockSize' 表示 i32 值 1024。
let blockSize: i32 = 1024;

// 'row_size' 表示 u32 值 16u。类型被指定。
let row_size = 16u;

变量(variable)是对内存的命名引用,可以包含特定可存储(storable)类型的值。

5.1.2. override 声明

覆盖声明 指定 pipeline-overridable 常量值的名称。 pipeline-overridable 常量的值在管道创建时是固定的。

该值是由 WebGPU 管道创建方法指定的值(如果指定),否则是其初始化表达式的值。 当 identifierresolves 用于覆盖声明时,标识符表示该值。 override-声明必须满足以下限制:

注意:覆盖表达式是 creation-time expressions 的超集。

EXAMPLE: Module constants, pipeline-overrideable
@id(0)    override has_point_light: bool = true;  // Algorithmic control
@id(1200) override specular_param: f32 = 2.3;     // Numeric control
@id(1300) override gain: f32;                     // Must be overridden
          override width: f32 = 0.0;              // Specified at the API level using
                                                  // the name "width".
          override depth: f32;                    // Specified at the API level using
                                                  // the name "depth".
                                                  // Must be overridden.
          override height = 2 * depth;            // The default value
                                                  // (if not set at the API level),
                                                  // depends on another
                                                  // overridable constant.

5.1.3. 创建时间常数

creation-time constant 指定固定在 shader-creation time 的值的名称。 一旦声明了常量,它的值就是不可变的。 当 identifierresolves 用于创建时间常数时,标识符表示该值。

当声明创建时常量时没有明确指定类型, 例如 const foo = 4,类型是从 equals 标记右侧的表达式自动推断的。 创建时间常量的类型必须是:

当指定类型时,例如 const foo : i32 = 4,初始化表达式必须计算为该类型。

注意:由于 AbstractIntAbstractFloat 在 WGSL 源代码中无法拼写,因此命名值只能通过类型推断来使用它们。

可以在模块范围或函数范围声明创建时间常量。 必须使用初始化程序声明创建时间常量,并且仅由 creation-time expressions 组成。

EXAMPLE: Creation-time constants
const a = 4; // 值为 4 的 AbstractInt。
const b : i32 = 4; // i32 值为 4。
const c : u32 = 4; // u32,值为 4。
const d : f32 = 4; // f32 值为 4。
const e = vec3(a, a, a); // AbstractInt 的 vec3,值为 (4, 4, 4)。
const f = 4.0; // 值为 4 的 AbstractFloat。
const g = mat2x2(a, f, a, f); // 值为 ((2, 4), (2, 4)) 的 AbstractFloat 的 mat2x2。

5.2. var 声明

有两种类型与变量相关联:它的存储类型(store type)(可以放置在被引用内存中的值的类型)和它的引用类型(reference type)(变量本身的类型)。 如果变量具有存储类型 T、存储类(address spaceS 和访问模式 A,则其引用类型为 ref<S,T,A>。 变量的 store type 始终是 concrete

变量声明(variable declaration):

当标识符(identifier)对变量声明使用resolves时,标识符是表示变量内存的引用内存视图(memory view)的表达式,其类型是变量的引用类型。见§ 6.12 变量标识符表达式

有关可以在何处声明特定存储类中的变量以及何时需要、可选或禁止存储类修饰的规则,见§ 5.3 模块作用域变量§ 5.5 函数作用域变量和常量

访问方式总是有默认的,除了storage存储类中的变量,不得写入WGSL源文本。见§ 4.5.1 访问模式默认值

一个变量的生命周期(lifetime)为该变量存在时进行着色器处理的期间。模块范围(module scope)变量的生命周期为着色器阶段的全部执行时间。

对于函数作用域(function scope)变量,每个调用都有其自己的独立变量版本。变量的生存周期取决于其作用域:

两个生命周期重叠的变量不会有重叠的内存(overlapping memory)。当一个变量的生命周期结束时,它的内存可能会被另一个变量使用。

当一个变量被创建时,它的内存包含一个初始值,如下所示:

考虑如下WGSL片段:

EXAMPLE: Variable initial values
var i: i32;         // 初始值为 0。不是推荐的方式。
loop {
  var twice: i32 = 2 * i;   // 重新计算每次迭代。
  i = i++;
  if i == 5 { break; }
}
循环体会执行 5 次,变量 i 会具有值0, 1, 2, 3, 4, 5, 且变量 twice 会具有值0, 2, 4, 6, 8。

考虑如下WGSL片段:

EXAMPLE: Reading a variable multiple times
var x: f32 = 1.0;
let y = x * x + x + 1;
由于 x 为一个变量,所有对齐的访问都变成了加载和存储操作。

然而,期望浏览器或驱动程序优化此中间表示,从而消除冗余负载。

5.3. 模块作用域变量

在整个程序中,变量名都是in scope。 模块作用域(module scope)内的变量被限制如下:

uniform 地址空间中的变量是 uniform buffer 变量。 它的 store type 必须是 host-shareable constructible 类型,并且必须满足 地址空间布局约束

storage 地址空间中的变量是 storage buffer 变量。 它的 store type 必须是 host-shareable 类型并且必须满足 地址空间布局约束。 变量可以用 readread_write 访问模式声明; 默认值为 read

§ 9.3.2 资源接口中所述,统一缓冲区、存储缓冲区、纹理和采样器构成了着色器的资源接口(resource interface of a shader)。 此类变量使用组(group)和绑定(binding)修饰声明。

WGSL 定义了以下可应用于全局变量的属性:

EXAMPLE: Module scope variable declarations
var<private> decibels: f32;
var<workgroup> worklist: array<i32,10>;

struct Params {
  specular: f32,
  count: i32
}
@group(0) @binding(2)
var<uniform> param: Params;    // A uniform buffer

// A storage buffer, for reading and writing
@group(0) @binding(2)
var<storage,read_write> pbuf: array<vec2<f32>>;

// Textures and samplers are always in "handle" space.
@group(0) @binding(2)
var filter_params: sampler;

5.4. 模块常量

出现在所有函数之外的 value declaration 声明了一个 module-scope 常量。

模块范围的常量必须是 override declarationscreation-time constants

整个程序的名称是 in scope

EXAMPLE: Module constants
// The golden ratio.
const golden: f32 = 1.61803398875;

// The second unit vector for three dimensions, with inferred type.
const e2 = vec3<i32>(0,1,0);

当在依赖于常量值的控制流中使用变量或特性时,则认为该变量或特性被程序使用。 无论常量的值如何,无论该值是来自常量声明的值还是来自管线覆盖的值,都是如此。

5.5. 函数作用域变量和常量

在函数体的声明语句中声明的变量或常量在 function scope 中。 该名称可在其声明语句之后立即使用,直到用大括号分隔的语句列表的末尾立即包含该声明。

let-declared 常量必须是 constructible 类型, 或 pointer type

对于在函数作用域中声明的变量:

EXAMPLE: Function scope variables and constants
fn f() {
   var<function> count: u32;  // 函数地址空间中的变量。
   var delta: i32;            // 函数地址空间中的另一个变量。
   var sum: f32 = 0.0;        // 拥有初始化值的函数存储类变量。
   var pi = 3.14159;          // 从初始化值推断出 f32 存储类型。
   let unit: i32 = 1;         // 未使用地址空间的let-声明常量。
}

for 语句的第一个子句中声明的变量或常量可用于第二个和第三个子句以及 for 语句的主体中。

函数作用域变量的实例是动态上下文(dynamic context)。每个一些调用范围内的变量都有一个重叠的生命周期(lifetime),因此,具有不重叠的内存。生命周期不重叠的变量可能会重用之前变量的内存;但是,不能保证同一变量的新实例使用相同的内存。

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

variable_statement :

| variable_decl

| variable_decl equal expression

| let ( ident | variable_ident_decl ) equal expression

| const ( ident | variable_ident_decl ) equal expression

variable_decl :

| var variable_qualifier ? ( ident | variable_ident_decl )

variable_ident_decl :

| ident colon type_decl

variable_qualifier :

| less_than address_space ( comma access_mode ) ? greater_than

global_variable_decl :

| attribute * variable_decl ( equal const_expression ) ?

global_constant_decl :

| const ( ident | variable_ident_decl ) equal expression | attribute * variable_decl ( equal expression ) ?

6. 表达式

表达式指定值如何被计算。

6.1. 创建时间表达式

shader-creation time 计算的表达式称为 creation-time 表达式。 为了在着色器创建时计算表达式,表达式使用的所有 identifiers 必须 resolvecreation-time constantscreation-time functions

创建时表达式的类型可以解析为包含 abstract numeric types 的类型。

示例:(42)分析如下:

示例:-5分析如下:

示例:-2147483648分析如下:

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

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

6.2. 文字表达式

标量文字类型规则
Precondition Conclusion Notes
true: bool true boolean value.
false: bool false boolean value.
e is an integer literal with no suffix e: AbstractInt Abstract integer literal value.
e is a floating point literal with no suffix e: AbstractFloat Abstract float literal value.
e is an integer literal with i suffix e: i32 32-bit signed integer literal value.
e is an integer literal with u suffix e: u32 32-bit unsigned integer literal value.
e is an floating point literal with f suffix e: f32 32-bit floating point literal value.
e is an floating point literal with h suffix e: f16 16-bit floating point literal value.

6.3. 括号表达式

括号表达式类型规则
Precondition Conclusion Description
e : T ( e ) : T 计算为 e
使用括号与表达式周围文本分隔。

6.4. 类型构造函数表达式

类型构造函数表达式显式创建给定 constructible 类型的值。 构造函数表达式共有三种:

6.4.1. 从组件构造

本节中定义的表达式通过以下方式创建 constructible 值:

这里给出的标量形式是多余的,但提供了标量 conversion expressions 的对称性, 并可用于增强可读性。

向量和矩阵形式从具有匹配分量类型的分量和子向量的各种组合构造向量和矩阵值。 有用于构造向量和矩阵的重载overloads ,它们指定目标类型的维度而无需指定组件类型; 组件类型是从构造函数参数推断出来的。

标量构造函数类型规则
Precondition Conclusion Notes
e: bool bool(e): bool Identity.
e: i32 i32(e): i32 Identity.
e: u32 u32(e): u32 Identity.
e: f32 f32(e): f32 Identity.
e: f16 f16(e): f16 Identity.
Vector constructor type rules, where T is a scalar type
Precondition Conclusion Notes
e: T vecN<T>(e): vecN<T> Evaluates e once. Results in the N-element vector where each component has the value of e.
vecN(e): vecN<T>
e1: T
e2: T
vec2<T>(e1,e2): vec2<T>
vec2(e1,e2): vec2<T>
e: vec2<T> vec2<T>(e): vec2<T> Identity. The result is e.
vec2(e): vec2<T>
e1: T
e2: T
e3: T
vec3<T>(e1,e2,e3): vec3<T>
vec3(e1,e2,e3): vec3<T>
e1: T
e2: vec2<T>
vec3<T>(e1,e2): vec3<T>
vec3<T>(e2,e1): vec3<T>
vec3(e1,e2): vec3<T>
vec3(e2,e1): vec3<T>
e: vec3<T> vec3<T>(e): vec3<T> Identity. The result is e.
vec3(e): vec3<T>
e1: T
e2: T
e3: T
e4: T
vec4<T>(e1,e2,e3,e4): vec4<T>
vec4(e1,e2,e3,e4): vec4<T>
e1: T
e2: T
e3: vec2<T>
vec4<T>(e1,e2,e3): vec4<T>
vec4<T>(e1,e3,e2): vec4<T>
vec4<T>(e3,e1,e2): vec4<T>
vec4(e1,e2,e3): vec4<T>
vec4(e1,e3,e2): vec4<T>
vec4(e3,e1,e2): vec4<T>
e1: vec2<T>
e2: vec2<T>
vec4<T>(e1,e2): vec4<T>
vec4(e1,e2): vec4<T>
e1: T
e2: vec3<T>
vec4<T>(e1,e2): vec4<T>
vec4<T>(e2,e1): vec4<T>
vec4(e1,e2): vec4<T>
vec4(e2,e1): vec4<T>
e: vec4<T> vec4<T>(e): vec4<T> Identity. The result is e.
vec4(e): vec4<T>
Matrix constructor type rules
Precondition Conclusion Notes
e: mat2x2<T> mat2x2<T>(e): mat2x2<T>
mat2x2(e): mat2x2<T>
Identity type conversion. The result is e.
e: mat2x3<T> mat2x3<T>(e): mat2x3<T>
mat2x3(e): mat2x3<T>
e: mat2x4<T> mat2x4<T>(e): mat2x4<T>
mat2x4(e): mat2x4<T>
e: mat3x2<T> mat3x2<T>(e): mat3x2<T>
mat3x2(e): mat3x2<T>
e: mat3x3<T> mat3x3<T>(e): mat3x3<T>
mat3x3(e): mat3x3<T>
e: mat3x4<T> mat3x4<T>(e): mat3x4<T>
mat3x4(e): mat3x4<T>
e: mat4x2<T> mat4x2<T>(e): mat4x2<T>
mat4x2(e): mat4x2<T>
e: mat4x3<T> mat4x3<T>(e): mat4x3<T>
mat4x3(e): mat4x3<T>
e: mat4x4<T> mat4x4<T>(e): mat4x4<T>
mat4x4(e): mat4x4<T>
e1: T
...
eN: T
mat2x2<T>(e1,e2,e3,e4): mat2x2<T>
mat3x2<T>(e1,...,e6): mat3x2<T>
mat2x3<T>(e1,...,e6): mat2x3<T>
mat4x2<T>(e1,...,e8): mat4x2<T>
mat2x4<T>(e1,...,e8): mat2x4<T>
mat3x3<T>(e1,...,e9): mat3x3<T>
mat4x3<T>(e1,...,e12): mat4x3<T>
mat3x4<T>(e1,...,e12): mat3x4<T>
mat4x4<T>(e1,...,e16): mat4x4<T>
Column-major construction by elements.
mat2x2(e1,e2,e3,e4): mat2x2<T>
mat3x2(e1,...,e6): mat3x2<T>
mat2x3(e1,...,e6): mat2x3<T>
mat4x2(e1,...,e8): mat4x2<T>
mat2x4(e1,...,e8): mat2x4<T>
mat3x3(e1,...,e9): mat3x3<T>
mat4x3(e1,...,e12): mat4x3<T>
mat3x4(e1,...,e12): mat3x4<T>
mat4x4(e1,...,e16): mat4x4<T>
e1: vec2<T>
e2: vec2<T>
e3: vec2<T>
e4: vec2<T>
mat2x2<T>(e1,e2): mat2x2<T>
mat3x2<T>(e1,e2,e3): mat3x2<T>
mat4x2<T>(e1,e2,e3,e4): mat4x2<T>
Column by column construction.
mat2x2(e1,e2): mat2x2<T>
mat3x2(e1,e2,e3): mat3x2<T>
mat4x2(e1,e2,e3,e4): mat4x2<T>
e1: vec3<T>
e2: vec3<T>
e3: vec3<T>
e4: vec3<T>
mat2x3<T>(e1,e2): mat2x3<T>
mat3x3<T>(e1,e2,e3): mat3x3<T>
mat4x3<T>(e1,e2,e3,e4): mat4x3<T>
Column by column construction.
mat2x3(e1,e2): mat2x3<T>
mat3x3(e1,e2,e3): mat3x3<T>
mat4x3(e1,e2,e3,e4): mat4x3<T>
e1: vec4<T>
e2: vec4<T>
e3: vec4<T>
e4: vec4<T>
mat2x4<T>(e1,e2): mat2x4<T>
mat3x4<T>(e1,e2,e3): mat3x4<T>
mat4x4<T>(e1,e2,e3,e4): mat4x4<T>
Column by column construction.
mat2x4(e1,e2): mat2x4<T>
mat3x4(e1,e2,e3): mat3x4<T>
mat4x4(e1,e2,e3,e4): mat4x4<T>
Array constructor type rules
Precondition Conclusion Notes
e1: T
...
eN: T,
T is constructible
array<T,N>(e1,...,eN) : array<T,N> Construction of an array from elements.

注意:数组<T,N> 是 constructible 因为它的 element count 等于构造函数的参数数量,并且在 shader-creation 时完全确定。

结构构造函数类型规则
Precondition Conclusion Notes
e1: T1
...
eN: TN,
S is a constructible structure type with members having types T1 ... TN.
The expression is in the scope of declaration of S.
S(e1,...,eN): S 从成员进行的结构构造。

6.4.2. 零值表达式

每个可构造的(constructibleT 都有一个唯一的零值(zero value),在 WGSL 中编写为,类型后面跟一对空括号:T ()

零值如下:

注意:WGSL中原子类型(atomic types),runtime-sized数组,以及其他非可构造(constructible)类型没有零值表达式。

标量零值类型规则
Precondition Conclusion Notes
bool(): bool false
Zero value
i32(): i32 0
Zero value
u32(): u32 0u
Zero value
f32(): f32 0.0
Zero value
f16(): f16 0.0
Zero value
向量零值类型规则,其中 T 为标量类型
Precondition Conclusion Notes
vec2<T>(): vec2<T> Zero value
vec3<T>(): vec3<T> Zero value
vec4<T>(): vec4<T> Zero value
EXAMPLE: Zero-valued vectors
vec2<f32>()                 // 两个 f32 元素的零值向量。
vec2<f32>(0.0, 0.0)         // 相同的值,明确写入。

vec3<i32>()                 // 三个 i32 元素的零值向量。
vec3<i32>(0, 0, 0)          // 相同的值,明确写入。
矩阵零值类型规则
Precondition Conclusion Notes
T is f32 or f16 mat2x2<T>(): mat2x2<T>
mat3x2<T>(): mat3x2<T>
mat4x2<T>(): mat4x2<T>
Zero value
mat2x3<T>(): mat2x3<T>
mat3x3<T>(): mat3x3<T>
mat4x3<T>(): mat4x3<T>
Zero value
mat2x4<T>(): mat2x4<T>
mat3x4<T>(): mat3x4<T>
mat4x4<T>(): mat4x4<T>
Zero value
数组零值类型规则
Precondition Conclusion Notes
T is a constructible array<T,N>(): array<T,N> 零值数组 (OpConstantNull)
EXAMPLE: Zero-valued arrays
array<bool, 2>()               // 两个布尔值的零值数组。
array<bool, 2>(false, false)   // 相同的值,明确写入。
结构零值类型规则
Precondition Conclusion Notes
S is a constructible structure type.
The expression is in the scope of declaration of S.
S(): S 零值结构:类型为 S 的结构,每个成员为其成员类型的零值。
(OpConstantNull)
EXAMPLE: Zero-valued structures
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>());
}

6.4.3. 转换表达式

WGSL 不隐式转换或提升一个数值或布尔值为另外的类型。而是使用以下表格中描述的转换表达式conversion expression

有关与浮点类型之间的转换的详细信息,请参阅 § 12.5.2 浮点数转换

标量转换类型规则
Precondition Conclusion Notes
e: u32 bool(e): bool 强制转换为布尔值。
如果 e 则结果为假为 0,否则为真。
e: i32 bool(e): bool 强制转换为布尔值。
如果 e 则结果为假为 0,否则为真。
e: f32 bool(e): bool 强制转换为布尔值。
如果 e 则结果为假为 0.0 或 -0.0,否则为真。 特别是 NaN 和无穷大值映射为真。
e: f16 bool(e): bool 强制转换为布尔值。
如果 e 则结果为假为 0.0 或 -0.0,否则为真。 特别是 NaN 和无穷大值映射为真。
e: bool i32(e): i32 将布尔值转换为有符号整数
如果 e,则结果为 1为真,否则为 0。
e: u32 i32(e): i32 重新解释比特。
结果是 i32 中的唯一值,它与 e 具有相同的位模式。
e: f32 i32(e): i32 值转换,向零舍入。
e: f16 i32(e): i32 值转换,向零舍入。
e: bool u32(e): u32 将布尔值转换为无符号整数。
如果 e,则结果为 1u为真,否则为 0u。
e:AbstractInt 或 i32 u32(e):u32 重新解释比特。
结果是 u32 中的唯一值,它与 e 具有相同的位模式。
e: f32 u32(e): u32 值转换,向零舍入。
e: f16 u32(e): u32 值转换,向零舍入。
e: bool f32(e): f32 将布尔值转换为浮点数。
如果 e,则结果为 1.0为真,否则为 0.0。
e: i32 f32(e): f32 值转换,包括无效情况。
e: u32 f32(e): f32 值转换,包括无效的情况。
e: f16 f32(e): f32 精确值转换。
e: bool f16(e): f16 布尔值到浮点的转换
如果 e,则结果为 1.0为真,否则为 0.0。
e: i32 f16(e): f16 值转换,包括无效情况。
e: u32 f16(e): f16 值转换,包括无效的情况。
e: f32 f16(e): f16 有损值转换。

§ 12.5.2 浮点数转换中解释了与浮点之间的转换细节。

e: vecN ; vecN<bool>(e): vecN<bool> Component-wise 将无符号整数向量强制转换为布尔向量。 e: vecN ; vecN<bool>(e): vecN<bool> Component-wise 将有符号整数向量强制转换为布尔向量。 e: vecN ; vecN<bool>(e): vecN<bool> Component-wise 将 binary32 浮点向量强制转换为布尔向量。 e: vecN ; vecN<bool>(e): vecN<bool> Component-wise 将 binary16 浮点向量强制转换为布尔向量。 e: vecN ; vecN<i32>(e): vecN<i32> Component-wise 将布尔向量转换为有符号向量。
组件 i结果是 i32(e[i]) e: vecN ; vecN<i32>(e): vecN<i32> Component-wise 重新解释位。
组件 i结果是 i32(e[i]) e: vecN ; vecN<i32>(e): vecN<i32> Component-wise 值转换为有符号整数,包括无效情况。 e: vecN ; vecN<i32>(e): vecN<i32> Component-wise 值转换为有符号整数,包括无效情况。 e: vecN ; vecN<u32>(e): vecN<u32> Component-wise 将布尔向量转换为无符号向量。
组件 i结果是 u32(e[i]) e: vecN<AbstractInt>或 vecN<i32> vecN<u32>(e): vecN<u32> Component-wise 重新解释位。 e: vecN ; vecN<u32>(e): vecN<u32> Component-wise 值转换为无符号整数,包括无效的情况。 e: vecN ; vecN<u32>(e): vecN<u32> Component-wise 值转换为无符号整数,包括无效的情况。 e: vecN ; vecN<f32>(e): vecN<f32> Component-wise 将布尔向量转换为浮点数。
组件 i结果是 f32(e[i]) e: vecN ; vecN<f32>(e): vecN<f32> Component-wise 值转换为 binary32 浮点,包括无效的情况。 e: vecN ; vecN<f32>(e): vecN<f32> Component-wise 值转换为 binary32 浮点,包括无效的情况。 e: vecN ; vecN<f32>(e): vecN<f32> Component-wise 将精确值转换为 binary32 浮点数。 e: vecN ; vecN<f16>(e): vecN<f16> Component-wise 将布尔向量转换为 binary16 浮点。
组件 i结果是 f16(e[i]) e: vecN ; vecN<f16>(e): vecN<f16> Component-wise 值转换为 binary16 浮点,包括无效的情况。 e: vecN ; vecN<f16>(e): vecN<f> Component-wise 值转换为 binary16 浮点数,包括无效的情况。 e: vecN ; vecN<f16>(e): vecN<f16> Component-wise 有损值转换为 binary16 浮点。 <表类=数据> 矩阵转换类型规则 <头> 前提条件结论注意事项 e: 垫子CxR ; matCxR<f32>(e): matCxR<f32> Component-wise 将精确值转换为 binary32 浮点。 e: 垫子CxR ; matCxR<f16>(e): matCxR<f16> Component-wise 有损值转换为 binary16 浮点。

6.5. 表示表达式的重新解释

bitcast表达式用于将一种类型中的值的位表示重新解释为另一种类型中的值。

Bitcast类型规则
Precondition Conclusion Notes
e: T
Tnumeric scalarnumeric vector 类型
比特广播<T>(e): T 身份转换。 Component-wiseT是一个向量。
结果是e
e: T1
T1是 i32、u32 还是 f32
T2不是 T1并且是 i32、u32 或 f32
比特广播<T2>(e): T2 将位重新解释为T2
结果是重新解释 e 中的位。作为 T2价值。
e: vecN<T1>
T1是 i32、u32 还是 f32
T2不是 T1并且是 i32、u32 或 f32
bitcast Component-wise 将位重新解释为 T2
结果是重新解释 e 中的位。作为 vecN<T2>;价值。
e: vec2
T是 i32、u32 或 f32
比特广播<T>(e): T 将位重新解释为 T
结果是重新解释 e 中的 32 位。作为 T值,遵循内部布局规则。
e: T
T是 i32、u32 或 f32
bitcast<vec2<f16>>(e): vec2<f16> 将位重新解释为 vec2
结果是重新解释 e 中的 32 位。作为 vec2<f16>值,遵循内部布局规则。
e: vec4
T是 i32、u32 或 f32
bitcast 将位重新解释为 vec2<T>.
结果是重新解释 e 中的 64 位。作为 vec2<T>;值,遵循内部布局规则。
e: vec2<T>
T是 i32、u32 或 f32
bitcast<vec4<f16>>(e): vec4<f16> 将位重新解释为 vec4
结果是重新解释 e 中的 64 位。作为 vec4<f16>值,遵循内部布局规则。
内部布局规则在 § 4.4.7.4 值的内部布局 中描述。

6.6. 复合值分解表达式

6.6.1. 向量访问表达式

访问向量的成员可以使用数组下标(例如 a[2])或使用一系列便名,每个成员都映射到源向量的一个元素。

  • 颜色的便名集合为 r, g, b, a ,分别对应向量元素 0, 1, 2, 和 3。
  • 维度的便名集合为 x, y, z, w ,分别对应向量元素 0, 1, 2, 和 3。

便名访问通过 . 符号。(比如,color.bgra

注意:便名字母不能倍混合使用。(即,你不能使用 rybw

使用便名字母,或数组下标,访问超过向量末尾的元素会出现错误。

便名字母可以按任何顺序应用,包括根据需要复制字母。从向量中提取分量时,你可以提供 1 到 4 个字母。提供超过 4 个字母是错误的。

结果类型取决于提供的字母。假设为一个 vec4<f32>

Accessor Result type
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
6.6.1.1. 向量单分量选择
向量分解:单分量选择
Precondition Conclusion Description
e: vecN<T>
e.x: T
e.r: T
选择 e 的第一个分量
e: vecN<T>
e.y: T
e.g: T
选择 e 的第二个分量
e: vecN<T>
N is 3 or 4
e.z: T
e.b: T
选择 e 的第三个分量
e: vec4<T> e.w: T
e.a: T
选择 e 的第四个分量
e: vecN<T>
i: i32 or u32
e[i]: T 选择 e 的第 i 个分量
第一个分量在 i=0 处。 如果 i 在区间 [0,N-1] 外,那么 T 的任何值都可能被返回。
6.6.1.2. 向量多分量选择
向量分解:多分量选择
Precondition Conclusion Description
e: vecN<T>
I 为字母 x, y, z, 或 w
J 为字母 x, y, z, 或 w
e.IJ: vec2<T>
使用第一个分量 e.I 和第二个分量 e.J 来计算2-元素向量。
仅当 N 为 3 或 4 时,字母 z 有效。
仅当 N 为 4 时,字母 w 有效。
e: vecN<T>
I 为字母 r, g, b, 或 a
J 为字母 r, g, b, 或 a
e.IJ: vec2<T>
使用第一个分量 e.I 和第二个分量 e.J 来计算2-元素向量。
仅当 N 为 3 或 4 时,字母 b 有效。
仅当 N 为 4 时,字母 a 有效。
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 来计算3-元素向量。
仅当 N 为 3 或 4 时,字母 z 有效。
仅当 N 为 4 时,字母 w 有效。
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 来计算3-元素向量。
仅当 N 为 3 或 4 时,字母 b 有效。
仅当 N 为 4 时,字母 a 有效。
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 来计算4-元素向量。
仅当 N 为 3 或 4 时,字母 z 有效。
仅当 N 为 4 时,字母 w 有效。
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 来计算4-元素向量。
仅当 N 为 3 或 4 时,字母 b 有效。
仅当 N 为 4 时,字母 a 有效。
6.6.1.3. 来自向量引用的分量引用

对向量的分量的写访问(write access可能 访问与该向量相关联的所有内存位置(memory locations)。

注意:这意味着如果至少一个访问是写访问,则通过不同调用对向量的不同组件的访问必须被同步。见§ 16.12 同步内置函数

从对向量的引用获取对分量的引用
Precondition Conclusion Description
r: ref<S,vecN<T>>
r.x: ref<S,T>
r.r: ref<S,T>
计算引用 r 所引用的向量的第一个分量的引用。
结果引用的原始变量(originating variable)与 r 的原始变量相同。
r: ref<S,vecN<T>>
r.y: ref<S,T>
r.g: ref<S,T>
计算引用 r 所引用的向量的第二个分量的引用。
结果引用的原始变量(originating variable)与 r 的原始变量相同。
r: ref<S,vecN<T>>
N is 3 or 4
r.z: ref<S,T>
r.b: ref<S,T>
计算引用 r 所引用的向量的第三个分量的引用。
结果引用的原始变量(originating variable)与 r 的原始变量相同。
r: ref<S,vec4<T>>
r.w: ref<S,T>
r.a: ref<S,T>
计算引用 r 所引用的向量的第四个分量的引用。
结果引用的原始变量(originating variable)与 r 的原始变量相同。
r: ref<S,vecN<T>>
i: i32 or u32
r[i] : ref<S,T> 计算引用 r 所引用的向量的第 i 个分量的引用。 如果 i 在区间 [0,N-1] 外,则表达式计算为 invalid memory reference。 结果引用的原始变量(originating variable)与 r 的原始变量相同。

6.6.2. 矩阵访问表达式

列向量提取
Precondition Conclusion Description
e: matCxR<T>
i: i32 or u32
e[i]: vecR<T> 结果为向量 e 的第 i 列。如果 i 在区间 [0,C-1],则可能返回 vecR<T> 的无效值。
从对矩阵的引用获取对列向量的引用
Precondition Conclusion Description
r: ref<S,matCxR<T>>
i: i32 or u32
r[i] : ref<vecR<S,T>> 通过引用 r 计算矩阵第 i 个列向量的引用。 如果 i 在区间 [0,C-1] 外,则表达式计算为无效内存引用(invalid memory reference)。 结果引用的原始变量(originating variable)与 r 的原始变量相同。

6.6.3. 数组访问表达式

数组元素提取
Precondition Conclusion Description
e: array<T,N>
i: i32 or u32
e[i] : T 结果为数组值 e 的第 i 个元素的值。 如果 i 在区间 [0,N-1] 外,则任何 T 的有效值可能被返回。
从对数组的引用获取对数组元素的引用
Precondition Conclusion Description
r: ref<S,array<T,N>>
i: i32 or u32
r[i] : ref<S,T> 计算对由引用 r 引用的数组的第 i 个元素的引用。

如果 i 在区间 [0,N-1] 外,则表达式计算为一个无效内存引用(invalid memory reference)。

结果引用的原始变量(originating variable)与 r 的原始变量相同。

r: ref<S,array<T>>
i: i32 or u32
r[i] : ref<S,T> 计算对由引用 r 引用的runtime-sized数组的第 i 个元素的引用。

如果在运行时数组有 N 个元素,且 i 在区间 [0,N-1] 外,则表达式计算为一个无效内存引用(invalid memory reference)。

结果引用的原始变量(originating variable)与 r 的原始变量相同。

6.6.4. 结构访问表达式

结构成员提取
Precondition Conclusion Description
S 为一个结构类型
M 为类型为 TS 中成员的标识符名称。
e: S
e.M: T 结果是结构值 e 中名称为 M 的成员的值。
从对结构的引用获取对结构成员的引用
Precondition Conclusion Description
S 为一个结构类型
M 为类型为 TS 中的成员名称。
r: ref<SC,S>
r.M: ref<SC,T> 给定对结构的引用,结果是对标识符名称为 M 的结构成员的引用。结果引用的原始变量(originating variable)与 r 的原始变量相同。

6.7. 逻辑表达式

一元逻辑运算
Precondition Conclusion Notes
e: bool
T 为布尔值或 vecN<bool>
!e: T 逻辑否定。

efalse 时结果为 true,当 etrue 是结果为 false

T 为向量时,Component-wise

二元逻辑表达式
Precondition Conclusion Notes
e1: bool
e2: bool
e1 || e2: bool 短路“或”。如果 e1e2 为真,则为真; 仅当 e1 为假时才计算 e2
e1: bool
e2: bool
e1 && e2: bool 短路“与”。 如果 e1e2 都为真,则为真; 仅当 e1 为真时才计算 e2
e1: T
e2: T
T 为布尔值或 vecN<bool>
e1 | e2: T 逻辑“或”。

T 是向量时,Component-wise

计算 e1e2。 Logical "or". Component-wise when T is a vector. Evaluates both e1 and e2.

e1: T
e2: T
T 为布尔值或 vecN<bool>
e1 & e2: T 逻辑“与”。

T 是向量时,Component-wise

计算 e1e2

6.8. 算术表达式

一元算术表达式
Precondition Conclusion Notes
e: T
T 是 AbstractInt, AbstractFloat, i32, f32, f16, vecN , vecN<AbstractFloat>、vecN<i32>、vecN<f32> 或 vecN<f16>
-e: T 否定。 Component-wiseT 是一个向量。 如果 T 是整数类型,e 求最大负值,则结果为e
Binary arithmetic expressions
Precondition Conclusion Notes
e1 : T
e2 : T
S is AbstractInt, AbstractFloat, i32, u32, f32, or f16
T is S, or vecN<S>
e1 + e2 : T 加法。 Component-wiseT 是一个向量。 如果 Tconcrete 整数类型,则结果为模 232
e1 : T
e2 : T
S is AbstractInt, AbstractFloat, i32, u32, f32, or f16
T is S, or vecN<S>
e1 - e2 : T 减法 Component-wiseT 是一个向量。 如果 Tconcrete 整数类型,则结果为模 232
e1 : T
e2 : T
S is AbstractInt, AbstractFloat, i32, u32, f32, or f16
T is S, or vecN<S>
e1 * e2 : T 乘法。 Component-wiseT 是一个向量。 如果 Tconcrete 整数类型,则结果为模 232
e1 : T
e2 : T
S is AbstractInt, AbstractFloat, i32, u32, f32, or f16
T is S, or vecN<S>
e1 / e2 : T 师。 Component-wiseT 是一个向量。

如果 T 是有符号整数类型,标量情况,计算结果为:

  • e1,当 e2 等于零。

  • e1,当 e1T 中的最大负值,e2 是-1。

  • 否则为 truncate(x) ,其中 x 是实值商e1 ÷ e2

笔记: 确保截断行为的需要可能需要实现比计算无符号除法时执行更多的操作。 当已知两个操作数具有相同符号时,使用无符号除法。

如果 T 是无符号整数类型,标量情况,计算结果为:

  • e1,当 e2 为零。

** e1,当 e2 为零。

  • 否则,整数 q 使得 e1 = q × e2 + r,其中 0 ≤ r < e2

e1 : T
e2 : T
S is AbstractInt, AbstractFloat, i32, u32, f32, or f16
T is S, or vecN<S>
e1 % e2 : T Remainder. Component-wise when T is a vector.

如果 T 是有符号整数标量类型,计算 e1e2 一次,并评估为:

  • 0,当 e2 为零。

  • 0,当 e1T 中的最大负值,e2 是-1。

  • 否则,e1 - truncate(e1 ÷ e2) × e2 其中商被计算为实数值。

笔记: 当非零时,结果与e1 具有相同的符号。

笔记: 确保行为一致的需要可能需要实施 比计算无符号余数时执行更多的操作。

如果 T 是无符号整数标量类型,计算结果为:

  • 0,当 e2 为零。

  • 否则,整数 r 这样 e1 = q × e2 + r, q 是整数,0 ≤ r < e2

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

具有混合标量和向量操作数的二元算术表达式
Preconditions Conclusions Semantics
S 为 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
矩阵运算
Preconditions Conclusions Semantics
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
* s: matCxR<T>
组件级缩放:(m * s)[i][j] is m[i][j] * s
s * m: matCxR<T>
组件级缩放: (s * m)[i][j] is m[i][j] * s
m: 垫子CxR<T>
v: vecC<T>
T是 AbstractFloat、f32 或 f16
* v: vecR<T>
线性代数矩阵-列-向量积: 组件 i结果是 dot(m[i],v)
m: 垫子CxR<T>
v: vecR<T>
T是 AbstractFloat、f32 或 f16
v * m: vecC<T>
线性代数行-向量-矩阵乘积:
转置(转置(m) *转置(v))
e1: matKxR<T>
e2: matCxK<T>
T是 AbstractFloat、f32 或 f16
e1 * e2: matCxR<T>
线性代数矩阵乘积。

6.9. 比较表达式

比较
Precondtion Conclusion Notes
e1: T
e2: T
S is AbstractInt, AbstractFloat, bool, i32, u32, f32, or f16
T is S or vecN<S>
TB is bool if T is scalar or
vecN<bool> if T is a vector
e1 == e2: TB Equality. Component-wise when T is a vector.
e1: T
e2: T
S is AbstractInt, AbstractFloat, bool, i32, u32, or f32
T is S or vecN<S>
TB is bool if T is scalar or
vecN<bool> if T is a vector
e1 != e2: TB Inequality. Component-wise when T is a vector.
e1: T
e2: T
S is AbstractInt, AbstractFloat, i32, u32, f32, or f16
T is S, or vecN<S>
TB is bool if T is scalar, or
vecN<bool> if T is a vector
e1 < e2: TB Less than. Component-wise when T is a vector.
e1: T
e2: T
S is AbstractInt, AbstractFloat, i32, u32, f32, or f16
T is S, or vecN<S>
TB is bool if T is scalar, or
vecN<bool> if T is a vector
e1 <= e2: TB Less than or equal. Component-wise when T is a vector.
e1: T
e2: T
S is AbstractInt, AbstractFloat, i32, u32, f32, or f16
T is S, or vecN<S>
TB is bool if T is scalar, or
vecN<bool> if T is a vector
e1 > e2: TB Greater than. Component-wise when T is a vector.
e1: T
e2: T
S is AbstractInt, AbstractFloat, i32, u32, f32, or f16
T is S, or vecN<S>
TB is bool if T is scalar, or
vecN<bool> if T is a vector
e1 >= e2: TB Greater than or equal. Component-wise when T is a vector.

6.10. 位表达式

一元按位运算
Precondition Conclusion Notes
e: T
S is AbstractInt, i32, or u32
T is S or vecN<S>

T 为 i32, u32, vecN<i32>, or vecN<u32>

~e : T e 上的按位补码。

结果中的每一位都与 e 中的相应位相反。

T 是向量时,Component-wise

(OpNot)

二元按位运算
Precondition Conclusion Notes
e1: T
e2: T
S is AbstractInt, i32, or u32
T is S or vecN<S>
e1 | e2: T Bitwise-or. Component-wise when T is a vector.
e1: T
e2: T
S is AbstractInt, i32, or u32
T is S or vecN<S>
e1 & e2: T Bitwise-and. Component-wise when T is a vector.
e1: T
e2: T
S is AbstractInt, i32, or u32
T is S or vecN<S>
e1 ^ e2: T Bitwise-exclusive-or. Component-wise when T is a vector.
位移表达式
Precondition Conclusion Notes
e1: T
e2: TS
T 为 i32, u32, vecN<i32>, or vecN<u32>
如果 e1 为标量,则 TS 为 u32
vecN<u32>.
e1 << e2: T Logical shift left:
e1 左移,在最低有效位置插入零,并丢弃最高有效位。要移位的位数是 e2 的值 modulo e1 的位宽。

当 T 是向量时,Component-wise

(OpShiftLeftLogical)

e1: T
e2: T
T is u32 or vecN<u32>
e1 >> e2: T 逻辑右移:

e1 右移,在最高有效位置插入零,并丢弃最低有效位。要移位的位数是 e2 的值 modulo e1 的位宽。

当 T 是向量时,Component-wise

(OpShiftRightLogical)

e1: T
e2: TS
T is i32 or vecN<i32>
TS is u32 if e1 is a scalar, or
vecN<u32>.
e1 >> e2: T 算术右移:

e1 右移,在最高有效位置插入符号位的副本,并丢弃最低有效位。要移位的位数是 e2 的值 modulo e1 的位宽。

当 T 是向量时,Component-wise

(OpShiftRightArithmetic)

6.11. 函数调用表达式

函数调用表达式执行函数调用(function call),其中被调用函数具有返回类型(return type)。 如果被调用的函数没有返回值,则应使用函数调用语句代替。见§ 7.5 Function Call 语句

6.12. 变量标识符表达式

从变量名获取引用
Precondition Conclusion Description
v 是解析(resolving)为 address space S 中声明的 in-scope 变量的标识符(identifier),存储类型(store type)为 T v: ref<S,T> 结果是对命名变量 v 的内存的引用。

6.13. 形式参数表达式

获取声明为函数形式参数的标识符的值
Precondition Conclusion Description
a 是解析(resolving)为类型 Tin-scope 形式参数声明的标识符(identifier a: T 结果是在调用此函数实例的调用位置(call site)为相应函数调用操作数提供的值。

6.14. 寻址表达式

寻址(address-of)操作符将一个引用转换为其对应的指针。

从引用获取指针
Precondition Conclusion Description
r: ref<S,T,A> &r: ptr<S,T,A> 结果是与引用值 r 相同的内存视图(memory view)对应的指针值。

如果 r 是一个无效内存引用(invalid memory reference),作为结果的指针同样是无效内存引用。

如果 Shandle 存储类,则为一个着色器创建错误(shader-creation error)。 这是一个 shader-creation error,如果 rreference to a vector component

6.15. 间接寻址表达式

间接寻址运算符(indirection)将指针转换为其相应的引用。

从指针获取引用
Precondition Conclusion Description
p: ptr<S,T,A> *p: ref<S,T,A> 结果是与指针值 p 相同的内存视图(memory view)对应的引用值。

如果 p 是一个无效内存引用(invalid memory reference),作为结果的指针同样是无效内存引用。

6.16. 常量标识符表达式

获取 let-声明标识符的值
Precondition Conclusion Description
c 是解析(resolving)为类型 Tin-scope管线可覆盖(override declaration)的标识符(identifier)。 c: T 如果管线创建为常量 ID (constant ID)指定了一个值,那么结果就是该值。对于不同的管线实例,此值可能不同。

否则,结果是为初始化表达式计算的值。管线可覆盖常量出现在模块范围内,因此在着色器开始执行之前进行计算。

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

c 是解析(resolving)为类型 Tin-scope let 声明的标识符(identifier),并且不为管线可覆盖(pipeline-overridable)的。 c: T 结果是为初始化表达式计算出的值。

对于模块范围内的 let 声明,计算发生在着色器开始执行之前。

对于函数内部的 let 声明,每次控制到达声明时都会进行计算。

6.17. 表达式语法总结

当标识符用作 callable 项时,它是以下之一:

Declaration and scope 规则确保这些名称始终是不同的。

paren_expression :

| paren_left expression paren_right

argument_expression_list :

| paren_left ( ( expression comma ) * expression comma ? ) ? paren_right

singular_expression :

| primary_expression postfix_expression ?

lhs_expression :

| ( star | and ) * core_lhs_expression postfix_expression ?

core_lhs_expression :

| ident

| paren_left lhs_expression paren_right

7. Statements

语句是控制其执行的程序片段。 语句一般按顺序执行; 然而, control flow statements 可能会导致程序以非顺序执行。

7.1. 复合语句

compound statement 是用大括号括起来的零个或多个语句序列。 当 declaration 是这些语句之一时,它的 identifier 从下一条语句的开始到复合语句的结束都是 in scope

compound_statement :

| brace_left statement * brace_right

复合语句有两种特殊形式:

7.2. 赋值语句

assignment 计算表达式,并可选择将其存储在内存中(从而更新变量的内容)。

等号左侧的文本是left-hand side, 等号右边的表达式是right-hand side

7.2.1. 简单赋值

left-hand side 是一个表达式并且运算符是 equal 标记时,assignment 是一个 简单赋值。 在这种情况下,right-hand side 的值被写入左侧引用的内存中。

Precondition Statement Description
r: ref<S,T,A>,
A is write or read_write
e: T,
T is a constructible type,
S is a writable address space
r = e Evaluates e, evaluates r, then writes the value computed for e into the memory locations referenced by r.

Note: if the reference is an invalid memory reference, the write may not execute, or may write to a different memory location than expected.

(OpStore)

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

EXAMPLE: Assignments
struct S {
    age: i32,
    weight: f32
}
var<private> person: S;

fn f() {
    var a: i32 = 20;
    a = 30;           // Replace the contents of 'a' with 30.

    person.age = 31;  // Write 31 into the age field of the person variable.

    var uv: vec2<f32>;
    uv.y = 1.25;      // Place 1.25 into the second component of uv.

    let uv_x_ptr: ptr<function,f32> = &uv.x;
    *uv_x_ptr = 2.5;   // Place 2.5 into the first component of uv.

    var friend: S;
    // Copy the contents of the 'person' variable into the 'friend' variable.
    friend = person;
}

7.2.2. 虚假赋值

当赋值的 left-hand side 是下划线标记时, 分配是一个虚假赋值: 评估右侧,然后忽略。

Precondition Statement Description
e: T,
T is constructible, a pointer type, a texture type, or a sampler type
_ = e Evaluates e.

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

虚假赋值对于以下场景有用:

  • 调用返回值的函数,但明确表示不需要结果值。

  • Statically accessing一个变量,从而将其建立为shader’s resource interface的一部分。

    注意:缓冲区变量的存储类型可能无法构造,例如 它包含原子类型或运行时大小的数组。 在这些情况下,请改用指向变量内容的指针。

EXAMPLE: Using phony-assignment to throw away an un-needed function result
var<private> counter: i32;

fn increment_and_yield_previous() -> i32 {
  let previous = counter;
  counter = counter + 1;
  return previous;
}

fn user() {
  // Increment the counter, but don’t use the result.
  _ = increment_and_yield_previous();
}
EXAMPLE: Using phony-assignment to occupy bindings without using them
[[block]] 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> {
  // Declare that buf, t, and s are part of the shader interface, without
  // using them for anything.
  _ = &buf;
  _ = t;
  _ = s;
  return vec4<f32>();
}

7.2.3. 复合赋值

assignment 复合赋值left-hand side 是一个表达式,并且运算符是 compound_assignment_operators 之一。

每个语句的类型要求、语义和行为被定义为好像复合赋值扩展如下表,除了引用表达式 e1 只评估一次。

Statement Expansion
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)

注意:语法不允许 compound assignment 也是 phony assignment

注意:即使引用 e1 被评估一次,它的底层内存被访问两次: 首先 read access 获取旧值,然后 write access 存储更新的值。

EXAMPLE: Compound assignment
var<private> next_item: i32 = 0;

fn advance_item() -> i32 {
   next_item += 1;   // Adds 1 to next_item.
   return next_item - 1;
}

fn bump_item() {
  var data: array<f32,10>;
  next_item = 0;
  // Adds 5.0 to data[0], calling advance_item() only once.
  data[advance_item()] += 5.0;
  // next_item will be 1 here.
}

fn precedence_example() {
  var value = 1;
  // The right-hand side of a compound assignment is its own expression.
  value *= 2 + 3; // Same as value = value * (2 + 3);
  // 'value' now holds 5.
}

注意:复合赋值可以改写为不同的 WGSL 代码,使用 simple assignment 代替。 这个想法是使用一个指针来保存一次评估引用的结果。

例如, 当 e1 是 * 不是 * 对向量内组件的引用,然后 e1+=e2 可以改写为 {let p = &(e1); *p = *p + (e2);}, 其中标识符 p被选择为不同于程序中的所有其他标识符。

e1 是对向量内部组件的引用,需要修改上述技术,因为 WGSL 在这种情况下不允许 taking the address。 例如,如果 ev 是对向量的引用,语句 ev[c] += e2 可以重写为 {let p = &(ev); 让 c0 = c; (*p)[c0] = (*p)[c0] + (e2);}, 其中标识符 c0p 被选择为不同于程序中的所有其他标识符。

7.3. 递增和递减语句

increment statement 给变量的内容加 1。 decrement statemnent 从变量的内容中减去 1。

increment_statement :

| lhs_expression plus_plus

decrement_statement :

| lhs_expression minus_minus

该表达式的计算结果必须为具有 integer scalar store typeread_write access mode 的引用。

Precondition Statement Description
r : ref<SC,T,read_write>,
T is integer scalar
r++ Adds 1 to the contents of memory referenced by r.
Same as r += T(1)
r : ref<SC,T,read_write>,
T is integer scalar
r-- Subtracts 1 from the contents of memory referenced by r.
Same as r -= T(1)
EXAMPLE: Increment and decrement
fn f() {
    var a: i32 = 20;
    a++;
    // Now a contains 21
    a--;
    // Now a contains 20
}

7.4. 控制流

控制流语句可能会导致程序以非顺序执行。

7.4.1. 序列

控制流语句可能会导致程序以非顺序执行。

7.4.2. If 语句

else_statement :

| compound_statement

| if_statement

一个 if 语句根据条件表达式的评估,有条件地执行至多一个 compound statement

Type rule precondition: WGSL 中的 if 语句使用 if/else if/else 结构,其中包含一个必需的 if 子句、零个或多个 elseif 子句和一个可选的 else 子句。 ifelse if 子句条件的每个表达式都必须是标量布尔表达式。

if 语句执行如下:

  • 评估与 if 子句关联的条件。 如果结果为 true,则控制转移到第一个复合语句(紧接在括号中的条件表达式之后)。

  • 否则,按文本顺序(如果存在)评估下一个 else if 子句的条件,如果结果为 true,则控制转移到关联的复合语句。

    • 对所有 else if 子句重复此行为,直到其中一个条件评估为 true

  • 如果没有条件评估为 true,则控制转移到与 else 子句(如果存在)相关联的复合语句。

7.4.3. Switch 语句

case_selectors :

| const_literal ( comma const_literal ) * comma ?

case_compound_statement :

| brace_left statement * fallthrough_statement ? brace_right

fallthrough_statement :

| fallthrough semicolon

switch 语句将控制转移到一组 case 子句中的一个子句或 default 子句,具体取决于选择器表达式的评估。

选择器表达式必须是标量整数类型。 如果选择器值等于案例选择器列表中的值,则控制转移到 该 case 子句的主体。 如果选择器值不等于任何 case 选择器值,则控制是 转移到 default 子句。

每个 switch 语句必须恰好有一个 default 子句。 Type rule precondition: case 选择器值必须与计算选择器表达式的结果具有相同的类型。

一个表达值在 switch 语句的 case 选择器中不能出现多次。

注意:表达式的值才是重要的,而不是拼写。 例如 00x0000都表示零值。

当控制到达 case 主体的末尾时,控制通常转移到 switch 语句之后的第一个语句。 或者,执行 fallthrough 语句 将控制转移到下一个 case 子句或 default 子句的主体,以开关主体中的下一个出现为准。 fallthrough 语句不能作为 switch 的最后一个子句中的最后一个语句出现。 当一个 declaration 出现在 case body 中时,它的 identifierin scope 从下一条语句的开始到 case body 的结束。

注意:在 case body 中声明的标识符不是 case body 的 in scope, 可以通过 fallthrough 语句访问。

EXAMPLE: WGSL Switch
var a : i32;
let x : i32 = generateValue();
switch x {
  case 0: {      // the colon is optional
    a = 1;
  }
  default {      // the default needn’t appear last
    a = 2;
  }
  case 1, 2 {    // multiple selector values can be used
    a = 3;       // a will be overridden in the next case
    fallthrough;
  }
  case 3 {
    a = 4;
  }
}

############################

7.4.4. Loop 语句

loop 语句重复执行loop body; 循环体被指定为 compound statement。 循环体的每次执行称为一次iteration

从下一条语句开始到循环体结束,循环中 declarationidentifierin scope。 每次到达时都会执行声明,因此每次新迭代都会创建变量或常量的新实例,并重新初始化它。

这种重复可以被 breakreturndiscard 声明。

可选地,循环体中的最后一条语句可以是 continuing声明。

注意:loop 语句是与其他着色器语言的最大区别之一。

这种设计直接表达了编译代码中常见的循环习语。 特别是,将循环更新语句放在循环体的末尾允许它们自然地使用循环体中定义的值。

EXAMPLE: GLSL Loop
int a = 2;
for (int i = 0; i < 4; i++) {
  a *= 2;
}
EXAMPLE: WGSL Loop
let a: i32 = 2;
var i: i32 = 0;      // <1>
loop {
  if (i >= 4) { break; }

  a = a * 2;

  i = i++;
}
  • <1> The initialization is listed before the loop.
EXAMPLE: GLSL Loop with continue
int a = 2;
let int step = 1;
for (int i = 0; i < 4; i += step) {
  if (i % 2 == 0) continue;
  a *= 2;
}
EXAMPLE: WGSL Loop with continue
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;
}
EXAMPLE: WGSL Loop with continue and continuing
var a: i32 = 2;
var i: i32 = 0;
loop {
  if (i >= 4) { break; }

  let step: i32 = 1;

  if (i % 2 == 0) { continue; }

  a = a * 2;

  continuing {   // <2>
    i = i + step;
  }
}
  • <2> continue 构造被放置在 loop 的末尾

7.4.5. For 语句

for 语句采用 for(initializer; condition; update_part) { body } 的形式, 是同一个 bodyloop 语句之上的语法糖(在计算机科学中,语法糖是一种编程语言中的语法,旨在使事物更易于阅读或表达。 它使语言更适合人类使用: 可以更清晰、更简洁地表达事物,或者以某些人可能更喜欢的另一种风格表达)。 另外:

  • 如果 initializer 不为空,它会在第一个 iteration 之前的一个额外的 scope 中执行。 初始化程序中声明的范围扩展到循环体的末尾。

  • 如果 condition 不为空,则在循环体的开始处进行检查,如果不满足则执行§ 7.4.7 Break 语句

  • 如果 update_part 不为空,它会在循环体的末尾变成一个 continuing 语句。

Type rule precondition: 条件必须为 bool 类型。

for 循环的 initializer 在执行循环之前执行一次。 当一个 declaration 出现在初始化器中时,它的 identifierin scope 直到 body 的结尾。 与 body 中的声明不同,该声明不会在每次迭代时重新初始化。

conditionbodyupdate_part 依次执行以形成一个循环 iterationbody 是一种特殊形式的 compound statementbody 中声明的标识符是 in scope 从下一条语句的开始到 body 的结尾。

每次到达时都会执行声明,因此每次新迭代都会创建变量或常量的新实例,并重新初始化它。

EXAMPLE: For to Loop transformation
for(var i: i32 = 0; i < 4; i = i++) {
  if (a == 0) {
    continue;
  }
  a = a + 2;
}

转换为:

EXAMPLE: For to Loop transformation
{ // Introduce new scope for loop variable i
  var i: i32 = 0;
  var a: i32 = 0;
  loop {
    if (!(i < 4)) {
      break;
    }

    if (a == 0) {
      continue;
    }
    a = a + 2;

    continuing {
      i = i++;
    }
  }
}

7.4.6. While Statement

while_statement :

| while expression compound_statement

while语句是一种以条件为参数的循环。 在每次循环iteration开始时,一个布尔类型的条件被计算得到。 如果条件为假,while循环结束执行。 否则,剩余迭代部分被执行。

Type rule precondition: 条件必须为 bool 类型。

while循环可以被看作是loopfor语句的语法糖。 以下语句形式是等价的:

  • while condition { body_statements }

  • loop { if ! condition {break;} body_statements }

  • for (; condition ;) { body_statements }

7.4.7. Break 语句

break_statement :

| break

break语句将控制转移至最近封闭循环的主体或switch语句之后,从而结束执行循环或switch语句。

break 语句必须仅在 loopforwhile以及switch语句之中使用。

不要放置 break 语句使得其退出循环的continuing语句。使用break-if代替。

EXAMPLE: WGSL Invalid loop break from a continuing clause
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; } // Invalid.  Use break-if instead.
  }
}

7.4.8. Break-If Statement

break_if_statement :

| break if expression semicolon

break-if语句计算一个布尔条件。 如果条件为真,控制被转移到最近封闭的loop语句主体之后,结束执行该循环。

Type rule precondition: 条件必须为 bool 类型。

注意:break-if语句仅可以出现在continuing语句主体的最后。

EXAMPLE: WGSL Valid loop break-if from a continuing clause
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;
  }
}

7.4.9. Continue 语句

continue_statement :

| continue

continue 语句在最近的封闭 loop 中转移控制:

  • 转发到循环体末尾的 continuing 语句(如果存在)。

  • 否则返回到循环体中的第一条语句,开始下一个 iteration

continue 语句只能用在 loop, forwhile 语句中。 continue 语句的放置不得将控制转移到封闭的 continuing 语句。 (当分支到 continuing 语句时,它是一个 forward 分支。)

不得放置 continue 语句,以便将控制权转移到目标 continuing 语句中使用的声明之后。

注意:如果为了传输嵌套在 continuing 语句中的另一个循环中的控制流,continue 仅能在 continuing 语句中使用。 也就是说,continue 不能被用于传输控制至当前执行的 continuing 语句开始处。

EXAMPLE: Invalid continue bypasses declaration
var i: i32 = 0;
loop {
  if (i >= 4) { break; }
  if (i % 2 == 0) { continue; } // <3>

  let step: i32 = 2;

  continuing {
    i = i + step;
  }
}
  • <3> continue 无效,因为它绕过了 continuing 构造中使用的 step 声明

7.4.10. Continuing 语句

continuing_statement :

| continuing continuing_compound_statement

continuing_compound_statement :

| brace_left statement * break_if_statement ? brace_right

continuing 语句指定要在循环 iteration 结束时执行的 compound statement。 该构造是可选的。

复合语句不得在任何复合语句嵌套级别包含 return

复合语句不得在任何复合语句嵌套级别或通过函数调用包含 discard。 有关此规则的更正式描述,请参阅 § 7.7 语句行为分析

7.4.11. Return 语句

return_statement :

| return expression ?

return 语句结束当前函数的执行。 如果函数是 entry point,则终止当前着色器调用。 否则,在对当前函数调用的 call site 进行评估之后,继续评估下一个表达式或语句。

如果函数没有 return type,则 return 语句是可选的。 如果为这样的函数提供了 return 语句,则它不得 提供一个值。 否则表达式必须存在,称为return value。 在这种情况下,此函数调用的调用点计算为返回值。 返回值的类型必须与函数的返回类型匹配。

7.4.12. Discard 语句

discard 语句立即结束片段着色器调用的执行并丢弃片段。 discard 语句只能用于 fragment 着色器阶段。

更准确地说,执行 discard 语句将:

只有在 discard 语句之前执行的语句才会有可观察到的效果。

注意:discard语句可以被任何着色器阶段中的函数|片段阶段中的函数function in a fragment stage执行, 效果是一样的:立即终止调用。

在执行 discard 语句后,控制流在入口点的持续时间内是不一致([non-uniform])的。

EXAMPLE: Using the discard statement to throw away a fragment
var<private> will_emit_color: bool = false;

fn discard_if_shallow(pos: vec4<f32>) {
  if (pos.z < 0.001) {
    // If this is executed, then the will_emit_color flag will
    // never be set to true.
    discard;
  }
  will_emit_color = true;
}

@fragment
fn main(@builtin(position) coord_in: vec4<f32>)
  -> @location(0) vec4<f32>
{
  discard_if_shallow(coord_in);

  // Set the flag and emit red, but only if the helper function
  // did not execute the discard statement.
  will_emit_color = true;
  return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

7.5. Function Call 语句

func_call_statement :

| ident argument_expression_list

函数调用语句执行 function call

注意:如果函数 returns a value,则忽略该值。

7.6. 语句语法总结

statement 规则匹配可以在函数体内大多数地方使用的语句。

另外,特定语句仅能用于非常特定的情况:

7.7. 语句行为分析

7.7.1. 规则

一些影响控制流的语句仅在某些上下文中有效。

例如,fallthroughswitch外无效,continueloop, for, 或 while外无效。 此外,一致性分析(参见 § 12.2 一致性)需要知道控制流何时可以以多种不同方式退出语句。 这两个目标都是通过一个总结语句和表达式的执行行为的系统来实现的。 行为分析将每个语句和表达式映射到语句或表达式的评估完成后执行的可能方式集。 与值和表达式的类型分析一样,行为分析自下而上进行:首先确定某些基本语句的行为,然后通过应用组合规则确定更高级别结构的行为。 behavior 是一个集合, 它的元素可能包括:

  • Return

  • Discard

  • Break

  • Continue

  • Fallthrough

  • Next

每一个都对应于退出复合语句的一种方式:通过关键字,或通过下一个语句(“Next”)。 我们注意到 "s: B" 表示 s 尊重有关行为的规则,并且具有 behavior B

对于每个函数:

  • 它的主体必须是这些规则的有效声明。

  • 如果函数具有返回类型,则其主体的 behavior 必须是 {Return} 或 {Return, Discard} 之一。

  • 否则,其主体的 behavior 必须是 {Next, Return, Discard} 的子集。

我们为每个函数分配一个 behavior:它是其主体的 behavior(将主体视为常规语句),任何“Return”都被“Next”替换。 由于上述规则,函数行为始终是 {}、{Next}、{Discard} 或 {Next, Discard} 之一。 类似地,我们为每个表达式分配一个 behavior,因为表达式可以包含可以丢弃的函数调用。 与函数一样,表达式行为始终是 {}、{Next}、{Discard} 或 {Next, Discard} 之一。 注意:当前没有有效的程序,其表达式的 behavior 中没有 Next。 原因是只有没有返回类型的函数才能有这样的behavior,而且没有复合表达式可以调用这样的函数。

分析和验证语句行为的规则
语句 先决条件 结果行为
{ } {Next}
{s} s: B B
s1 s2 Note: s1 often ends in a semicolon. s1: B1
Next in B1
s2: B2
(B1∖{Next}) ∪ B2
s1: B1
Next not in B1
s2: B2
B1
var x:T; {Next}
let x = e; e: B B
var x = e; e: B B
x = e; x: B1
e: B2
x is not _
B1B2
_ = e; e: B B
f(e1, ..., en); e1: B1
...
en: Bn
f has behavior B
B ∪ ((B1 ∪ ... ∪ Bn)∖{Next})
return; {Return}
return e; e: B
(B∖{Next}) ∪ {Return}
discard; {Discard}
break; {Break}
break if e; e: B B ∪ {Break}
continue; {Continue}
fallthrough; {Fallthrough}
if (e) s1 else s2 e: B
s1: B1
s2: B2
(B∖{Next}) ∪ B1B2
loop {s1 continuing {s2}} s1: B1
s2: B2
None of {Continue, Return, Discard} are in B2
Break is not in (B1B2)
(B1∖{Continue}) ∪ (B2∖{Next})
s1: B1
s2: B2
None of {Continue, Return, Discard} are in B2
Break is in (B1B2)
(B1B2 ∪ {Next})∖{Break, Continue}
switch(e) {case c1: s1 ... case cn: sn} e: B
s1: B1
...
sn: Bn
Fallthrough is not in Bn
Break is not in (B1 ∪ ... ∪ Bn)
(BB1 ∪ ... ∪ Bn)∖{Fallthrough}
e: B
s1: B1
...
sn: Bn
Fallthrough is not in Bn
Break is in (B1 ∪ ... ∪ Bn)
(BB1 ∪ ... ∪ Bn ∪ {Next})∖{Break, Fallthrough}
出于本分析的目的:
  • for 循环脱糖(见 § 7.4.5 For 语句

  • loop {s} 被视为 loop {s continue {}}

  • 没有 else 分支的 if 语句被视为有一个空的 else 分支(将 Next 添加到它们的 behavior

  • 带有 else if 分支的 if 语句被视为嵌套的简单 if/else 语句

  • default 开头的 switch_body 的行为就像以 case _: 开头的 switch_body

用于分析和验证表达式行为的规则
表达式 先决条件 结果行为
f(e1, ..., en) e1: B1
...
en: Bn
f has behavior B
B ∪ ((B1 ∪ ... ∪ Bn)∖{Next})
Any literal {Next}
Any variable reference {Next}
e1[e2] e1: B1
e2: B2
B1B2
e.field e: B B
e1 || e2 e1: B1
e2: B2
B1B2
e1 && e2 e1: B1
e2: B2
B1B2
每个 built-in function 都有一个 behavior {Next}。 并且每个未在上表中列出的运算符应用程序都具有相同的 behavior,就好像它是一个具有相同操作数的函数调用,并且函数的 behavior 为 {Next}。 如果行为分析失败,则会产生 shader-creation error
  • 行为分析必须能够确定每个语句、表达式和函数的非空的 behavior

  • 函数行为必须满足上面给出的规则。

  • 计算和顶点入口点的行为不得包含丢弃。

7.7.2. 注意

本节是信息性的,非规范性的。 以下是这些规则可能导致程序被拒绝的完整方式列表(这只是重申上面已经列出的信息):
  • 函数体(视为常规语句)具有 {Next, Return, Discard} 中未包含的行为。

  • 具有返回类型的函数体的行为既不是 {Return} 也不是 {Return, Discard}。

  • 连续块的行为包含任何 Continue、Return 或 Discard。

  • 开关的最后一种情况的行为包含 Fallthrough。

  • 计算或顶点入口点函数的行为包含丢弃。

  • 一些明显的无限循环有一个空的行为集,因此是无效的。

该分析可以通过自下而上分析调用图在线性时间内运行(因为函数调用的行为可能取决于函数的代码)。

7.7.3. 实例

以下是一些示例,显示了此分析的实际效果:
EXAMPLE: Trivially dead code is allowed
fn simple() -> i32 {
  var a: i32;
  return 0;  // Behavior: {Return}
  a = 1;     // Valid, statically unreachable code.
             //   Statement behavior: {Next}
             //   Overall behavior (due to sequential statements): {Return}
  return 2;  // Valid, statically unreachable code. Behavior: {Return}
} // Function behaviour: {Return}
EXAMPLE: Compound statements are supported
fn nested() -> i32 {
  var a: i32;
  {             // The start of a compound statement.
    a = 2;      // Behavior: {Next}
    return 1;   // Behavior: {Return}
  }             // The compound statement as a whole has behavior {Return}
  a = 1;        // Valid, statically unreachable code.
                //   Statement behavior: {Next}
                //   Overall behavior (due to sequential statements): {Return}
  return 2;     // Valid, statically unreachable code. Behavior: {Return}
}
EXAMPLE: if/then behaves as if there is an empty else
fn if_example() {
  var a: i32 = 0;
  loop {
    if a == 5 {
      break;      // Behavior: {Break}
    }             // Behavior of the whole if compound statement: {Break, Next},
                  //   as the if has an implicit empty else
    a = a + 1;    // Valid, as the previous statement had "Next" in its behavior
  }
}
EXAMPLE: if/then/else has the behavior of both sides
fn if_example() {
  var a: i32 = 0;
  loop {
    if a == 5 {
      break;      // Behavior: {Break}
    } else {
      continue;   // Behavior: {Continue}
    }             // Behavior of the whole if compound statement: {Break, Continue}
    a = a + 1;    // Valid, statically unreachable code.
                  //   Statement behavior: {Next}
                  //   Overall behavior: {Break, Continue}
  }
}
EXAMPLE: if/else if/else behaves like a nested if/else
fn if_example() {
  var a: i32 = 0;
  loop {
    // if e1 s1 else if e2 s2 else s3
    // is identical to
    // if e1 else { if e2 s2 else s3 }
    if a == 5 {
      break;      // Behavior: {Break}
    } else if a == 42 {
      continue;   // Behavior: {Continue}
    } else {
      return;     // Behavior {Return}
    }             // Behavior of the whole if compound statement:
                  //   {Break, Continue, Return}
  }               // Behavior of the whole loop compound statement {Next, Return}
}                 // Behavior of the whole function {Next}
EXAMPLE: Break in switch becomes Next
fn switch_example() {
  var a: i32 = 0;
  switch a {
    default: {
      break;   // Behavior: {Break}
    }
  }            // Behavior: {Next}, as switch replaces Break by Next
  a = 5;       // Valid, as the previous statement had Next in its behavior
}
EXAMPLE: Obviously infinite loops
fn invalid_infinite_loop() {
  loop { }     // Behavior: { }.  Invalid because it’s empty.
}
EXAMPLE: A conditional continue with continuing statement
fn conditional_continue() {
  var a: i32;
  loop {
    if a == 5 { break; } // Behavior: {Break, Next}
    if a % 2 == 1 {      // Valid, as the previous statement has Next in its behavior
      continue;          // Behavior: {Continue}
    }                    // Behavior: {Continue, Next}
    a = a * 2;           // Valid, as the previous statement has Next in its behavior
    continuing {         // Valid as the continuing statement has behavior {Next}
                         //  which does not include any of:
                         //  {Break, Continue, Discard, Return}
      a = a + 1;
    }
  }                      // The loop as a whole has behavior {Next},
                         //  as it absorbs "Continue" and "Next",
                         //  then replaces "Break" with "Next"
}
EXAMPLE: A redundant continue with continuing statement
fn redundant_continue_with_continuing() {
  var a: i32;
  loop {
    if (a == 5) { break; }
    continue;   // 有效的。 这是多余的,分支到下一个语句。
    continuing {
      a = a + 1;
    }
  }
}
EXAMPLE: A continue at the end of a loop body
fn continue_end_of_loop_body() {
  for (var i: i32 = 0; i < 5; i++ ) {
    continue;   // Valid. This is redundant,
                //   branching to the end of the loop body.
  }             // Behavior: {Next},
                //   as loops absorb "Continue",
                //   and "for" loops always add "Next"
}
for 循环 desugar 到 loop 有条件中断。 如前面的示例所示,条件中断具有 behavior {Break, Next},这导致将“Next”添加到循环的 behavior
EXAMPLE: Effect of a function that discards unconditionally
fn always_discard() {
  discard;
}                   // The whole function has behavior {Discard}
fn code_after_discard() {
  var a: i32;
  always_discard(); // Behavior: {Discard}
  a = a + 1;        // Valid, statically unreachable code.
                    //   Statement behavior: {Next}
                    //   Overall behavior: {Discard}
}
EXAMPLE: Effect of a function that discards conditionally
fn sometimes_discard(a: i32) {
  if (a) {
    discard;        // 行为: {Discard}
  }                 // 行为: {Next, Discard}
}                   // 整个函数有行为 {Next, Discard}
fn code_after_discard() {
  var a: i32;
  a = 42;
  sometimes_discard(a);  // 行为: {Next, Discard}
  a = a + 1;             // 有效
}                        // 整个函数有行为 {Next, Discard}
EXAMPLE: return required in functions that have a return type
fn missing_return () -> i32 {
  var a: i32 = 0;
  if a == 42 {
    return a;       // Behavior: {Return}
  }                 // Behavior: {Next, Return}
}                   // Error: Next is invalid in the body of a
                    //   function with a return type
EXAMPLE: continue must be in a loop
fn continue_out_of_loop () {
  var a: i32 = 0;
  if (a) {
    continue;       // 行为: {Continue}
  }                 // 行为: {Next, Continue}
}                   // Error: 在函数体中Continue无效
如果 continuebreakfallthrough 代替,同样的例子也会因同样的原因而无效。

8. Functions

function 在调用时执行计算工作。

以下列方式之一调用函数:

有两种功能:

8.1. 声明一个用户定义的函数

function declaration 通过指定以下内容来创建用户定义的函数:

  • 一组可选的attributes

  • 函数的名称。

  • 形参列表:一个有序的零序列或更多 formal parameter 声明,用逗号分隔,和被括号包围。

  • 一个可选的,可能是装饰的,返回类型

  • function body.

这是当函数为called时要执行的语句集。

函数声明只能出现在 module scope。 整个程序的函数名称是 in scope

形式参数 declaration 指定一个 identifier 名称和一个值的类型,该值在调用函数时必须提供。 形式参数可能具有属性。 见§ 8.2 Function Calls。 标识符是 in scope 直到函数结束。 给定函数的两个形参不能同名。

注意:一些内置函数可能允许参数为abstract numeric types,但是,该功能尚不被用户定义函数支持。

param_list :

| ( param comma ) * param comma ?

WGSL 定义了以下可应用于函数声明的属性:

WGSL 定义了以下可应用于函数参数和返回类型的属性:

EXAMPLE: Simple functions
// 声明 add_two 函数。
// 它有两个形参,i 和 b。
// 它的返回类型为 i32。
// 它有一个带有 return 语句的主体。
fn add_two(i: i32, b: f32) -> i32 {
  return i + 2;  // A formal parameter is available for use in the body.
}

// 一个计算着色器入口点函数,'main'。
// 它没有指定的返回类型。
// 它调用了ordinary_two 函数,并捕获
// 命名值’six’中的结果值。
@compute fn main() {
   let six: i32 = add_two(4, 5.0);
}

8.2. Function Calls

function call 是调用函数的语句或表达式。

包含函数调用的函数是calling function,或caller。 被调用的函数是called function,或callee

函数调用:

  • 命名 called function,和

  • 提供带括号、逗号分隔的参数值表达式列表。

函数调用必须提供相同数量的参数值 formal parameters在被调用的函数called function中。 每个参数值必须按位置计算为与相应形式参数相同的类型。

总之,在调用函数时:

  1. calling function 的执行被暂停。

  2. called function 执行直到它returns

  3. 继续执行 calling function

被调用的函数returns如下:

详细地说,当执行函数调用时,会发生以下步骤:

  1. 函数调用参数值被评估。 评估的相对顺序是从左到右。

  2. calling function 的执行被暂停。 所有 function scope 变量和常量都保持其当前值。

  3. 如果调用的函数是user-defined, 为被调用函数中的每个函数作用域变量分配内存。

  4. 被调用函数的形参值由函数调用参数值按位置匹配确定。 例如,在被调用函数的主体中,第一个形参将表示 call site 处第一个参数的值。

  5. 如果调用的函数是 user-defined, 控制被转移到其 body 中的第一个语句。

  6. 被调用的函数被执行,直到它returns

  7. 控制权移交给调用函数,被调用函数的执行不暂停。 如果被调用的函数 returns a value,则为函数调用表达式的值提供该值。

注意:如果被调用函数或任何后代被调用函数执行了 discard 语句,当前函数将不会恢复执行。

函数调用的位置称为call site。 调用站点是一个 dynamic context。 因此,相同的文本位置可能代表多个呼叫站点。

8.3. 创建时函数

const声明的函数参数可以在shader-creation time时被计算。 这些函数被称为creation-time functions。 对这些函数的调用可以组成creation-time expressions

如果函数包含任何非creation-time expressions表达式,或任何非creation-time constants声明,则为shader-creation error

注意:const 属性不能被应用于用户声明的函数。

EXAMPLE: Creation-time functions
const first_one = firstLeadingBit(1234 + 4567); // Evaluates to 12
                                                // first_one has the type i32, because
                                                // firstLeadingBit cannot operate on
                                                // AbstractInt

@id(1) override x : i32;
override y = firstLeadingBit(x); // Creation-time expressions can be
                                 // used in override expressions.
                                 // firstLeadingBit(x) is not a
                                 // creation-time expression in this context.

fn foo() {
  var a : array<i32, firstLeadingBit(257)>; // Creation-time functions can be used in
                                            // creation-time expressions if all their
                                            // parameters are creation-time expressions.
}

8.4. 函数限制

注意:不允许递归,因为在任何类型的声明中都不允许循环。

8.4.1. 别名内存视图

Memory locations 可以在函数执行期间使用 memory view 访问。 在一个函数中,每个 memory view 都有一个特定的根标识符。 根标识符可以是 originating variablepointer typeformal parameter

reference 或 [=pointer type|pointer] 类型的本地派生表达式可能会为特定的根标识符引入新名称,但每个表达式都有一个静态可确定的根标识符。 虽然根标识符的 originating variable 是一个动态概念,它取决于函数的 call sites,但可以静态分析 WGSL 程序以确定每个根标识符所有可能的 originating variables 集合。

当两个根标识符 alias 具有相同的 originating variable 时,如果以下情况发生则出现dynamic error

  • 多个别名相同的根标识符访问相同的memory locations, 及

  • 至少一个访问为 write, 及

  • 同一函数调用的执行期间出现的所有访问

注意:此别名限制应用于由函数内制function calls写入的内存地址。

注意:Originating variables 不能有 memory locations 别名。 详见 § 5.2 var 声明 and § 9.3.2 资源接口

EXAMPLE: Aliased memory views
var x : i32 = 0;

fn foo() {
  bar(&x, &x); // Both p and q parameters are aliases of x.
}

// This function produces a dynamic error because of the aliased
// memory accesses.
fn bar(p : ptr<private, i32>, q : ptr<private, i32>) {
  if (x == 0) {
    *p = 1;
  } else {
    *q = 2;
  }
}
EXAMPLE: Aliasing in a helper function
var x : i32 = 0;

fn baz(p : ptr<private, i32>) {
  *p = 2;
}

// This function produces a dynamic error if x == 0, because x is read and
// written through different root identifiers even though the write occurs
// in the scope of baz.
fn bar(p : ptr<private, i32>) {
  if (x == 0) {
    baz(p);
  }
}

fn foo() {
  bar(&x); // p in bar is aliased to x.
}

9. 入口点

entry point 是一个 user-defined function,它为特定的 shader stage 执行工作。

9.1. 着色器阶段

WebGPU 以 drawdispatch commands 的形式向 GPU 发出工作。 这些命令在一组 inputsoutputs 和附加的 resources 的上下文中执行管道。

pipeline 将要在 GPU 上执行的work描述为一系列阶段,其中一些阶段是可编程的。 在 WebGPU 中,在调度绘制或调度命令以执行之前创建管道。 有两种管道:GPUComputePipeline 和 GPURenderPipeline。

dispatch command 使用 GPUComputePipeline 在具有可控并行度的逻辑点网格上运行 计算着色器阶段,同时读取并且可能更新缓冲区和图像资源。

draw command 使用 GPURenderPipeline 运行多阶段进程,其中包含两个可编程阶段以及其他固定功能阶段:

  • vertex shader stage 将单个顶点的输入属性映射到该顶点的输出属性。

  • 固定功能阶段将顶点映射到图形基元(例如三角形)中,然后将其光栅化以生成片段。

  • fragment shader stage 处理每个片段,可能会产生片段输出。

  • 固定功能阶段消耗片段输出,可能会更新外部状态,例如颜色附件以及深度和模板缓冲区。

WebGPU 规范更详细地描述了管道。

WGSL 定义了三个 shader stage,对应流水线的可编程部分:

  • compute

  • vertex

  • fragment

每个着色器阶段都有自己的一组特性和约束,在别处描述。

9.2. 入口点声明

要创建 entry point,请使用 pipeline stage attributes 属性声明 user-defined function

在 WebGPU API 中配置 pipeline 时,入口点的函数名称映射到 WebGPU § GPUProgrammableStage 对象的 entryPoint 属性。

入口点的 formal parameters 形成阶段的 pipeline inputs。 入口点的 return type(如果指定)形成阶段的 pipeline output。 每个输入和输出必须是一个 entry point IO type

注意:[=Compute]计算入口点从来没有返回类型。

EXAMPLE: Entry Point
@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
fn comp_main() { }

着色器阶段中的 函数集 是以下各项的并集:

  • 阶段的入口点函数。

  • 着色器阶段函数体内的函数调用目标,无论该调用是否被执行。

联合被反复应用,直到它稳定。 它将在有限数量的步骤中稳定下来。

9.2.1. 入口点的函数属性

WGSL 定义了以下可应用于入口点声明的属性:

问题:我们可以查询工作组大小维度的上限吗? 是独立于shader,还是创建shader模块后要查询的属性?

EXAMPLE: workgroup_size Attribute
@compute @workgroup_size(8,4,1)
fn sorter() { }
   // OpEntryPoint GLCompute %sorter "sorter"
   // OpExecutionMode %sorter LocalSize 8 4 1

@compute @workgroup_size(8u)
fn reverser() { }
   // OpEntryPoint GLCompute %reverser "reverser"
   // OpExecutionMode %reverser LocalSize 8 1 1

// Using an pipeline-overridable constant.
@override(42) let block_width = 12u;
@compute @workgroup_size(block_width)
fn shuffler() { }
    // SPIR-V 转换使用 WorkgroupSize 修饰的常量,
    // 其中第一个组件是一个装饰有的 OpSpecConstant
    // SpecID 42,默认值为12,第二个和第三个组件
    // 使用默认值 1。

// 错误:必须在计算着色器上指定 workgroup_size
@compute
fn bad_shader() { }

9.3. 着色器接口

着色器接口是一组对象,着色器通过这些对象访问 shader stage 外部的数据,用于读取或写入。 接口包括:

  • 管道输入和输出

  • 缓存资源

  • 纹理资源

  • 采样器资源

这些对象由某些 address spaces 中的模块范围变量表示。

当在 function declaration resolves 中使用 identifiermodule-scope 变量时,我们说该变量是 statically accessed 按功能。let 声明的常量的静态访问定义类似。 请注意,静态访问与着色器的执行是否将实际评估引用变量的表达式,或者甚至执行可能包含表达式的语句无关。

更准确地说,着色器阶段的接口interface of a shader stage包括:

9.3.1. 内置输入和输出

内置输入值提供对系统生成的控制信息的访问。 内置输入集在 § 15 内置值 中列出。

阶段 S 的内置输入,名称为 X,类型为 TX,可通过 a formal parametershader stage Sentry point,采用以下两种方式之一:

  1. 参数具有 builtin(X) 属性,类型为TX

  2. 参数具有结构类型,其中结构成员之一具有属性 builtin(X),类型为TX

相反,当入口点的参数或参数成员具有“内置”属性时, 相应的内置函数必须是入口点着色器阶段的输入。

着色器使用 build-in output value 来传达 控制信息到管道中的后续处理步骤。 内置输出集列在 § 15 内置值 中。

一个名为 Y 和类型 TY 的阶段 S 的内置输出通过 return value 设置为 shader stage Sentry point,采用以下两种方式之一:

  1. 入口点 return type 具有属性 builtin(Y) 并且是 TY 类型。

  2. 入口点 return type 具有结构类型,其中结构成员之一具有属性 builtin(Y)并且是TY类型.

相反,当入口点的返回类型或返回类型的成员具有“内置”属性时, 相应的内置函数必须是入口点着色器阶段的输出。

注意:内置的 position既是顶点着色器的输出,也是片段着色器的输入。

9.3.1.1. 用户定义的输入和输出

用户定义的数据可以作为输入传递到管道的起点、在管道的各个阶段之间传递或从管道末端输出。 不得将用户定义的 IO 传递给 compute 着色器入口点。 用户定义的 IO 必须是 numeric scalarnumeric vector 类型,或者其成员是数字标量或向量的结构类型。 必须为所有用户定义的 IO 分配位置(请参阅 § 9.3.1.3 输入输出位置)。

9.3.1.2. 插值

作者可以通过使用 interpolate 属性来控制如何插入用户定义的 IO 数据。 WGSL 提供了两个方面的插值来控制:插值的类型和插值的采样。

插值类型 interpolation type 必须是以下之一:

  • perspective - 值以透视正确的方式插入。

  • linear - 值以线性、非透视的正确方式进行插值。

  • flat - 值不是内插的。 插值采样不与 flat 插值一起使用。

插值采样 interpolation sampling 必须为以下之一:

  • center - 在像素的中心执行插值。

  • centroid - 插值在位于当前基元内的片段所覆盖的所有样本内的点处执行。该值对于基元中的所有样本都是相同的。

  • sample - 对每个样本执行内插。当应用此属性时,每个样本调用一次 fragment 着色器。

用于标量或向量浮点类型的用户自定义IO:

  • 如果未指定插值属性,则假定为 @interpolate(perspective, center)

  • 如果使用插值类型指定插值属性:

    • 如果插值类型为 flat,则不得指定插值采样。

    • 如果插值类型是 perspectivelinear,则:

      • 任何插值采样都是有效的。

      • 如果未指定插值采样,则假定为 center

标量或向量整数类型的用户定义 IO 必须始终指定为 @interpolate(flat)

插值属性必须在 vertex 输出和 fragment 输入之间匹配,在相同的 pipeline 中具有相同的 location 分配。

9.3.1.3. 输入输出位置

每个位置最多可以存储 16 个字节的值。 类型的字节大小使用 § 4.4.7.1 对齐和大小 中的 SizeOf 列定义。 例如,浮点值的四元素向量占据一个位置。

位置通过 location 属性指定。

每个用户定义的输入和输出都必须有一组完全指定的位置。 入口点 IO 中的每个结构成员必须是内置值之一 (参见 § 9.3.1 内置输入和输出),或分配一个位置。

位置不得在以下每个集合内重叠:

  • 结构类型中的成员。 这适用于任何结构,而不仅仅是管道输入或输出中使用的结构。

  • 入口点的管道输入, 即其形式参数的位置,或其结构类型的形式参数的成员。

注意:位置编号在输入和输出之间是不同的: 入口点管道输入的位置编号与入口点管道输出的位置编号不冲突。

注意:不需要额外的规则来防止入口点输出中的位置重叠。 当输出是一个结构时,上面的第一条规则可以防止重叠。 否则,输出是标量或向量,并且只能分配一个位置。

注意:入口点的可用位置数由 WebGPU API 定义。

EXAMPLE: Applying location attributes
struct A {
  @location(0) x: f32,
  // 尽管位置是 16 字节,但 x 和 y 不能共享位置
  @location(1) y: f32
};

// in1 occupies locations 0 and 1.
// in2 occupies location 2.
// The return value occupies location 0.
@fragment
fn fragShader(in1: A, @location(2) in2: f32) -> @location(0) vec4<f32> {
 // ...
}

用户定义的 IO 可以与同一结构中的内置值混合使用。 例如,

EXAMPLE: Mixing builtins and user-defined IO
// Mixed builtins and user-defined inputs.
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 {
  // ...
}
EXAMPLE: Invalid location assignments
struct A {
  @location(0) x: f32,
  // Invalid, x and y cannot share a location.
  @location(0) y: f32
}

struct B {
 @location(0) x: f32
}

struct C {
  // Invalid, structures with user-defined IO cannot be nested.
  b: B
}

struct D {
  x: vec4<f32>
}

@fragment
// Invalid, location cannot be applied to a structure type.
fn fragShader1(@location(0) in1: D) {
  // ...
}

@fragment
// Invalid, in1 and in2 cannot share a location.
fn fragShader2(@location(0) in1: f32, @location(0) in2: f32) {
  // ...
}

@fragment
// Invalid, location cannot be applied to a structure.
fn fragShader3(@location(0) in1: vec4<f32>) -> @location(0) D {
  // ...
}

9.3.2. 资源接口

resource 是一个对象,除了 pipeline input or output,它提供对 shader stage 外部数据的访问。 资源由着色器的所有调用共享。

有四种资源类型:

着色器的资源接口resource interface of a shader是模块范围的集合 资源变量静态访问 statically accessed 通过着色器阶段中的函数|着色器阶段中的函数 functions in the shader stage

每个资源变量都必须用 groupbinding 属性声明。 与着色器的阶段一起,这些标识了着色器管道上资源的绑定地址。 参考 WebGPU § GPUPipelineLayout

绑定不能在着色器阶段内别名:当被视为一对值时,给定着色器的资源接口中的两个不同变量不能具有相同的组和绑定值。

9.3.3. 资源布局兼容性

WebGPU 要求着色器的资源接口与使用着色器的 layout of the pipeline 匹配。

资源接口中的每个 WGSL 变量都必须绑定到具有兼容 resource typebinding type 的 WebGPU 资源,其中兼容性为 由下表定义。

WebGPU binding type compatibility
WGSL resource WebGPU
Resource type
WebGPU Binding type
uniform buffer GPUBufferBinding GPUBufferBindingType uniform
storage buffer with read_write access storage
storage buffer with read access read-only-storage
sampler GPUSampler GPUSamplerBindingType filtering
non-filtering
sampler_comparison comparison
sampled texture GPUTextureView GPUTextureSampleType float
unfilterable-float
sint
uint
depth
write-only storage texture GPUTextureView GPUStorageTextureAccess write-only

请参阅 WebGPU API 接口验证要求规范。

10. 语言扩展

WGSL 语言预计会随着时间的推移而发展。

extension 是对 WGSL 规范的特定版本的一组连贯修改的命名分组,由以下任意组合组成:

  • 通过新语法添加新概念和行为,包括:

    • 声明、语句、属性和内置函数。

  • 删除当前规范或以前发布的扩展中的限制。

  • 用于减少允许行为集的语法。

  • 用于限制程序的一部分可用的功能的语法。

  • 扩展如何与现有规范交互以及可选地与其他扩展交互的描述。

假设,扩展可用于:

  • 添加数字标量类型,例如不同位宽的整数。

  • 添加语法以限制浮点舍入模式。

  • 添加语法以表示着色器不使用原子类型。

  • 添加新的语句。

  • 添加新的内置函数。

  • 添加对着色器调用执行方式的约束。

  • 添加新的着色器阶段。

10.1. 启用指令

enable directive 表示由特定命名的 extension 可以被使用。 语法规则意味着所有启用指令必须出现在任何 declarations 之前。

该指令使用 identifier, keyword, 或 reserved word 来命名扩展。有效的扩展名列在 § 10.2 扩展列表 中。

指令对标识符的使用与将该标识符用作任何 declaration 中的名称不冲突。

enable_directive :

| enable ident semicolon

注意:语法规则包括终止分号标记,确保附加功能仅在该分号之后可用。 因此,任何 WGSL 实现都可以解析整个 enable 指令。 当实现遇到不受支持的扩展的启用指令时,实现可以发出明确的诊断。

EXAMPLE: Using hypothetical extensions
// Enable a hypothetical extension for arbitrary precision floating point types.
enable aribtrary_precision_float;
enable arbitrary_precision_float; // A redundant enable directive is ok.

// Enable a hypothetical extension to control the rounding mode.
enable rounding_mode;

// Assuming arbitrary_precision_float enables use of:
//    - a type f<E,M>
//    - as a type in function return, formal parameters and let-declarations
//    - as a type constructor from AbstractFloat
//    - operands to division operator: /
// Assuming @rounding_mode attribute is enabled by the rounding_mode enable directive.
@rounding_mode(round_to_even)
fn halve_it(x : f<8, 7>) -> f<8, 7> {
  let two = f<8, 7>(2);
  return x / 2; // uses round to even rounding mode.
}

10.2. 扩展列表

Extension identifier
Identifier WebGPU extension name Description
f16 "shader-f16" Keyword f16 and any floating point literal with a h suffix is valid if and only if this extension is enabled. Otherwise, using f16 keyword or any floating point literal with a h suffix will result in a shader-creation error.

11. WGSL 项目

WGSL 程序是一系列可选的 directives 后跟 module scope declarations

translation_unit :

| global_directive * global_decl *

11.1. 限制条件

程序必须满足以下限制:

可量化的着色器复杂性限制
限制 最大值
structure 类型中的成员数 16383
composite 类型的Nesting depth 255
函数的 parameters 的数量 255
switch 语句中的 case 选择器值的数量 16383

12. Execution

§ 1.1 技术概览 描述了如何调用着色器并将其划分为 invocations。 本节描述了对调用如何单独和集体执行的进一步限制。

12.1. 调用中的程序顺序

WGSL程序中的每个语句在执行过程中可能会被执行零次或多次。 对于给定的调用,给定语句的每次执行都代表一个唯一的动态语句实例

当语句包含表达式时,语句的语义决定:

  • 表达式是否作为语句执行的一部分进行评估。

  • 语句中独立表达式之间求值的相对顺序。

表达式嵌套定义了完成评估必须满足的数据依赖关系。 也就是说,必须先计算嵌套表达式,然后才能计算封闭表达式。 表达式操作数的计算顺序是从左到右 WGSL。 例如,foo() + bar() 必须在 bar() 之前计算 foo()。 参见 § 6 表达式

WGSL 程序中的语句按控制流顺序执行。 参见 § 7 Statements§ 8.2 Function Calls

12.2. 一致性

12.2.1. 术语和概念

以下定义只是提供信息,试图对下一小节中的分析正在计算的内容给出一个直觉。 分析实际上定义了这些概念,以及程序何时有效或违反了统一性规则。 对于给定的一组调用:

  • 如果给定范围内的所有调用都像在程序中的给定点同步执行一样执行,则称该点具有统一控制流

  • 如果一个表达式在统一控制流中执行,并且所有调用计算相同的值,则称它是 统一值.

  • 如果调用在局部变量的每个活动点都保持相同的值,则称其为 统一变量

12.2.2. 均匀度分析概述

一些函数(例如屏障和导数)只有在统一控制流中调用才是安全的。 在本节中,我们指定了一个分析来验证这些函数是否仅在这样的上下文中被调用。

注意:此分析具有以下理想属性:
  • 声音(意味着它拒绝所有会破坏内置一致性要求的程序)

  • 线性时间复杂度(以程序中的令牌数量计)

  • 将一段代码重构为函数,或内联函数,如果着色器在转换之前有效,则不能使着色器无效

  • 如果分析拒绝一个程序,它会提供一个简单的暗示链,用户代理可以使用这些暗示来制作一个好的错误消息

该分析分析每个函数,验证是否存在可以安全调用此函数的上下文。 如果没有这样的上下文,它会拒绝该程序为无效。

同时,它计算有关函数的元数据,以依次帮助分析其调用者。 这意味着必须首先构建调用图,并且必须从叶子向上分析函数,即从不调用标准库之外的函数的函数到入口点。 这样,每当分析一个函数时,它的所有被调用者的元数据都已经被计算出来。 没有陷入循环的风险,因为语言中禁止重复。

注意:同样的事情的另一种说法是,我们对按“是(可能是间接的)被调用者”偏序排序的函数进行拓扑排序,并按该顺序分析它们。

12.2.3. 分析函数的一致性要求

每个功能分两个阶段进行分析。

第一阶段遍历函数的语法,根据以下小节中的规则构建一个有向图。 第二阶段探索该图,导致要么拒绝程序,要么计算调用此函数的约束。

注:除了RequiredToBeUniform和MayBeNonUniform这两个特殊节点外,所有节点都可以理解为具有以下含义之一:

一条边可以理解为从其源节点对应的语句到其目标节点对应的语句的蕴涵。

为了表达某些东西必须始终是统一的(例如,派生类调用站点的控制流),我们将一条从 RequiredToBeUniform 的边添加到相应的节点。 理解这一点的一种方法是,RequiredToBeUniform 对应于命题 True,因此 RequiredToBeUniform -> X 等同于说 X 为真。

反过来,为了表示我们无法确保某些事物的一致性(例如,保存线程 id 的变量),我们将一条从相应节点的边添加到 MayBeNonUniform。 理解这一点的一种方法是,MayBeNonUniform 对应于命题 False,因此 X -> MayBeNonUniform 等同于说 X 为假。

这种解释的一个结果是,从RequiredToBeUniform 可到达的每个节点都对应于程序必须是一致的东西才能有效,并且每个MayBeNonUniform 可以到达的节点都对应于我们无法保证其一致性的东西。如果存在从RequiredToBeUniform 到MayBeNonUniform 的任何路径,那么我们就会违反一致性(因此拒绝该程序)。

对于每个函数,两个标签被计算:

  • 调用站点标签描述函数call sites上的控制流一致性要求, 及

  • 函数标签 描述函数对均匀性的影响。

此外,对于函数的每个 formal parameter参数标签被计算出来,描述了参数值的一致性要求。

Call site tag values
Call Site Tag Description
CallSiteRequiredToBeUniform The function must only be called from uniform control flow.
CallSiteNoRestriction The function may be called from non-uniform control flow.
Function tag values
Function Tag Description
SubsequentControlFlowMayBeNonUniform Calling this function may cause control flow to be non-uniform immediately after the call site.
ReturnValueMayBeNonUniform The return value of the function may be non-uniform.
NoRestriction The function does not introduce non-uniformity.
Parameter tag values
Parameter Tag Description
ParameterRequiredToBeUniform The parameter must be a uniform value.
ParameterRequiredToBeUniformForSubsequentControlFlow The parameter must be a uniform value for control flow after the function call to be uniform.
ParameterRequiredToBeUniformForReturnValue The parameter must be a uniform value in order for the return value to be a uniform value.
ParameterNoRestriction The parameter value has no uniformity requirement.

以下算法描述了如何为给定函数计算这些标签:

注意:此时可以销毁整个图形。上面列出的标签是我们分析这个函数的调用者需要记住的所有标签。

12.2.4. 语句的统一性规则

分析语句的规则将语句本身和对应于它开头的控制流的节点(我们将在下面记为“CF”)作为参数,并返回以下两个:

  • 出口对应控制流的节点

  • 一组要添加到图中的新节点和边

在下表中,(CF1, S) => CF2 表示“从控制流 CF1 开始对 S 进行分析,将所需的更改应用于图形,并将生成的控制流命名为 CF2”。 类似地,(CF1, E) => (CF2, V) 表示“对表达式 E 运行分析,从控制流 CF1 开始,将所需的更改应用于图形,并将生成的控制流节点命名为 CF2 和生成的值节点 V”(表达式分析见下节)。

对于左值位置的表达式,我们有一组类似的规则,我们用 LValue: (CF, E) => (CF, L) 表示。它不是计算与值的一致性相对应的节点,而是计算与我们正在寻址的变量的一致性相对应的节点。

当必须创建多个边时,我们使用 X -> {Y, Z} 作为 X -> Y, X -> Z 的简写。

语句的统一性规则
语句 新节点 递归分析s 产生的控制流节点 新边缘
{s} (CF, s) => CF' CF'
s1 s2,
with Next in behavior of s1

Note: s1 often ends in a semicolon.

(CF, s1) => CF1
(CF1, s2) => CF2
CF2
s1 s2,
without Next in behavior of s1

Note: s1 often ends in a semicolon.

(CF, s1) => CF1

Note: s2 is statically unreachable and not recursively analyzed. s2 does not contribute to the uniformity analysis.

CF1
if e s1 else s2
with behavior {Next}
(CF, e) => (CF', V)
(V, s1) => CF1
(V, s2) => CF2
CF
if e s1 else s2
with another behavior
CFend CFend CFend -> {CF1, CF2}
loop {s1 continuing {s2}}
with behavior {Next}
CF' (CF', s1) => CF1
(CF1, s2) => CF2
CF CF' -> {CF2, CF}
loop {s1 continuing {s2}}
with another behavior
CF'
loop {s1}, with behavior {Next} CF' (CF', s1) => CF1 CF CF' -> {CF1, CF}
loop {s1}
with another behavior
CF'
switch e case _: s_1 .. case _: s_n
with behavior {Next}
(CF, e) => (CF', V)
(V, s_1) => CF_1
...
if s_(n-1) may fallthrough, (CF_(n-1), s_n) => CF_n
else (V, s_n) => CF_n
CF
switch e case _: s_1 .. case _: s_n
with another behavior
CFend CFend CFend -> {CF_1, ..., CF_n}
var x: T; CF
break;
break if e; (CF, e) => (CF', V) CF'
continue; CF
fallthrough;
discard;
return; CF CF_return -> CF
return e; (CF, e) => (CF', V) CF' CF_return -> CF'
Value_return -> V
e2 = e1; (CF, e1) => (CF1, V1)
LValue: (CF1, e2) => (CF2, L2)
CF2 L2 -> V1
_ = e (CF, e) => (CF', V) CF'
let x = e; (CF, e) => (CF', V) CF'
var x = e;

注意:如果 if、switch 或循环语句的行为集(参见 § 7.7 语句行为分析)是 {Next},这意味着我们要么在语句内没有发散,要么重新收敛,所以我们选择 语句开头的控制流对应的节点作为语句出口的控制流对应的节点。

注意:在 switch 语句中,就一致性而言,默认块被视为与 case 块完全相同。

12.2.5. 函数调用的统一性规则

最复杂的规则是函数调用:

注意:请注意,此规则只需要添加以 3 + 函数参数数量为界的边数,而与函数的实现可能有多复杂无关。 这是整个算法线性复杂度的关键。

大多数内置函数都有以下标签:

以下是例外列表:

12.2.6. 表达式的统一性规则

分析表达式的规则将表达式本身和对应于它开头的控制流的节点(我们将在下面记为“CF”)作为参数,并返回以下内容:

  • 出口对应控制流的节点

  • 一个节点对应其值

  • 一组要添加到图中的新节点和边

表达式的一致性规则(在正常的右值位置)
表达式 新节点 递归分析 产生的控制流节点、值节点 新边缘
e1 || e2 (CF, e1) => (CF1, V1)
(V1, e2) => (CF2, V2)
CF2, V2
e1 && e2
Literal CF, CF
reference to function-scope variable, creation-time constant, let-declaration, or non-built-in parameter "x" Result X is the node corresponding to "x" CF, Result Result -> {CF, X}
reference to uniform built-in value "x" CF, CF
reference to non-uniform built-in value "x" CF, MayBeNonUniform
reference to read-only module-scope variable "x" CF, CF
reference to non-read-only module-scope variable "x" CF, MayBeNonUniform
op e,
where op is a unary operator
(CF, e) => (CF', V) CF', V
e.field
e1 op e2,
where op is a non-short-circuiting binary operator
Result (CF, e1) => (CF1, V1)
(CF1, e2) => (CF2, V2)
CF2, Result Result -> {V1, V2}
e1[e2]

以下内置输入变量被认为是统一的:

  • wordgroup_id

  • num_workgroups

所有其他的(参见 § 15 内置值)被认为是不均匀的。

左值位置表达式的一致性规则
表达式 新节点 递归分析 产生的控制流节点、值节点< 新边缘
reference to function-scope variable, creation-time constant, let-declaration, or parameter "x" X is the node corresponding to "x" CF, X
reference to module-scope variable "x" CF, MayBeNonUniform
e.field LValue: (CF, e) => (CF1, L1) CF1, L1
e1[e2] LValue: (CF, e1) => (CF1, L1)
(CF1, e2) => (CF2, V2)
CF2, L1 L1 -> V2

12.2.7. 注释控制流中每个点的一致性

这整个小节是非规范性的。

如果实现者想为开发人员提供一种诊断模式,显示整个着色器控制流中的每个点是否一致(以及因此调用需要一致的函数是否有效),我们建议下列:

  • 运行前面小节中描述的(强制的、规范的)分析,保留每个函数的图表。

  • 反转所有这些图中的所有边

  • 遍历每个函数,从入口点开始,在访问所有调用者之前从不访问函数:

    • 将 MayBeNonUniform 的边添加到至少在一个调用者中不统一的每个参数

    • 如果函数在至少一个调用者的非统一控制流中被调用,则从 MayBeNonUniform 添加一条边到 CF_start

    • 查看 MayBeNonUniform 可以访问哪些节点。访问的每个节点都是控制流中的一个表达式或点,其一致性无法通过分析得到证明

这些可达性分析未访问的任何节点都可以通过分析证明是统一的(因此在那里调用导数或类似函数是安全的)。

注意:仍然需要自下而上的分析,因为它让我们知道在遇到调用时要向图中添加哪些边。

12.3. 计算着色器和工作组

workgroup 是一组调用, 它们同时执行 compute shader stage entry point,并共享对 workgroup 地址空间。

计算着色器的 workgroup grid 是具有整数坐标 (i,j,k) 的点集,其中:

  • 0 ≤ i < workgroup_size_x

  • 0 ≤ j < workgroup_size_y

  • 0 ≤ k < workgroup_size_z

其中 (workgroup_size_x, workgroup_size_y, workgroup_size_z) 是为入口点的 workgroup_size 属性指定的值。

对于工作组网格中的每个点,在工作组中恰好有一个调用。

调用的本地调用ID local invocation ID 是调用对应的工作组网格点的坐标三元组。

当一个调用有local invocation ID (i,j,k),那么它的local invocation index

i + (j * workgroup_size_x) + (k * workgroup_size_x * workgroup_size_y)

注意,如果一个工作组有W 调用, 然后每次调用 I 工作组有一个唯一的本地调用索引 L(I) 使得 0 ≤ L(I) < W,覆盖整个范围。

当 WebGPU 实现从队列中删除调度命令并开始在 GPU 上执行指定工作时,计算着色器开始执行。 dispatch 命令指定dispatch size, 它是一个整数三元组(group_count_x, group_count_y, group_count_z),指示要执行的工作组的数量,如下所述。

特定分派的 compute shader grid 是具有整数坐标 (CSi,CSj,CSk) 的点集,其中:

  • 0 ≤ CSi < workgroup_size_x × group_count_x

  • 0 ≤ CSj < workgroup_size_y × group_count_y

  • 0 ≤ CSk < workgroup_size_z × group_count_z

其中workgroup_size_xworkgroup_size_y,和 workgroup_size_z 与上述计算着色器入口点相同。

计算着色器分派要执行的工作是为计算着色器网格中的每个点恰好执行一次入口点调用。

调用的 global invocation ID 是调用对应的计算着色器网格点的坐标三元组。

调用被组织成工作组,以便每个调用 (CSi, CSj, CSk) 用工作组网格点标识

( CSi mod workgroup_size_x , CSj mod workgroup_size_y , CSk mod workgroup_size_z )

workgroup ID

( ⌊ CSi ÷ workgroup_size_x ⌋, ⌊ CSj ÷ workgroup_size_y ⌋, ⌊ CSk ÷ workgroup_size_z ⌋).

WebGPU 不提供任何保证:

  • 来自不同工作组的调用是否并发执行。 也就是说,您不能假设一次执行多个工作组。

  • 一旦来自一个工作组的调用开始执行,其他工作组是否被阻止执行。 也就是说,您不能假设一次只有一个工作组执行。 当工作组正在执行时,实现可以选择同时执行其他工作组,或其他排队但未阻塞的工作。

  • 来自一个特定工作组的调用是否在另一个工作组的调用之前开始执行。 也就是说,您不能假设工作组是按特定顺序启动的。

12.4. 批量操作

12.4.1. Barrier

屏障是一个synchronization built-in function,它对程序中的内存操作进行排序。 control barrier 由同一个 workgroup 中的所有调用执行,就好像它是并发执行的一样。 因此,控制屏障只能在 compute 着色器中的uniform control flow中执行。

12.4.2. 导数

偏导数partial derivative 是值沿轴的变化率。

对相邻片元(在屏幕空间坐标中)操作的片段着色器调用协作计算近似偏导数。 这些相邻的片段被称为 quad

片元坐标的偏导数是作为以下内置函数运算的一部分隐式计算的:

对于这些,导数有助于确定要采样的纹素的 mip 级别,或者在“textureSampleCompare”的情况下,对参考值进行采样和比较。

调用指定值的偏导数由 § 16.7 倒数内置函数 中描述的内置函数:

  • dpdxdpdxCoarsedpdxFine 计算沿 x 轴的偏导数。

  • dpdydpdyCoarsedpdyFine 计算沿 y 轴的偏导数。

  • fwidthfwidthCoarsefwidthFine 计算相关联的 x 和 y 偏导数的曼哈顿度量。

因为相邻调用必须协作计算导数,所以这些函数只能在片元着色器的uniform control flow中调用。

12.5. 浮点计算

WGSL 遵循 IEEE-754 浮点计算标准 以下例外:

  • 不会产生浮点异常。

  • 可能不会生成信令 NaN。 任何信令 NaN 都可以转换为安静的 NaN。

  • 实现可能假设 NaN 和无穷大不存在

    • 注意:这意味着一些函数(例如 minmax) 由于存在优化,可能不会返回预期的结果 NaN 和无穷大。

  • 实现可能会忽略零的符号。 也就是说,带正号的零可能表现得像带负号的零,反之亦然。

  • 没有指定舍入模式。

  • 实现可能会刷新 § 12.5.1 浮点精度 中列出的任何操作的输入和/或输出的非规范化值。

    • 需要其他操作来保留非规范化数字。

  • 操作的准确性在 § 12.5.1 浮点精度 中给出。

12.5.1. 浮点精度

x 当以无限精度计算时,是操作的精确实值或无限结果。 corrctly rounded浮点类型运算的结果T 是:
  • x,但 xT 的范围内,

  • 否则:

    • T 中的最小值 大于 x,或

    • T 中的最大值 小于 x

也就是说,结果可能会向上或向下舍入: WGSL 未指定舍入模式。

注意:浮点类型包括正无穷大和负无穷大,所以 正确舍入的结果可能是有限的或无限的。

最后一个单位(ULP),对于浮点数 x 是两个不相等的浮点数 ab 之间的最小距离, 使得 a &le ; xb(即 ulp(x) = mina,b|b - a|)。

在下表中,提供了五个操作的准确性 可能性:

  • 正确的结果(对于非浮点返回值)。

  • Correctly rounded

  • 相对误差界表示为 ULP

  • 精度为 inherited from 的函数。 也就是说,准确度等于按照导出函数执行运算。

  • 绝对误差界限。

对于在一个范围内指定的任何精度值,该范围之外的结果的精度是不确定的。

如果任何操作的允许返回值的大小大于最大可表示的有限浮点值,则该操作可能另外返回具有相同符号的无穷大或具有相同符号的最大有限值。

表达式的准确性
Expression f32的准确性 f16的准确性
x + y 正确四舍五入
x - y 正确四舍五入
x * y 正确四舍五入
x / y 2.5 ULP 对于在 [2-126, 2126] 范围内的 |y|
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) 派生自 atan2(sqrt(1.0 - x * x), x)
acosh(x) 派生自 log(x + sqrt(x * x - 1.0))
asin(x) 派生自 atan2(x, sqrt(1.0 - x * x))
asinh(x) 派生自 log(x + sqrt(x * x + 1.0))
atan(x) 4096 ULP 5 ULP
atan2(y, x) 4096 ULP 5 ULP
atanh(x) 派生自 log( (1.0 + x) / (1.0 - x) ) * 0.5
ceil(x) 正确四舍五入
clamp(x,low,high) 正确四舍五入
cos(x) Absolute error ≤ 2-11在范围[-π, π]内 Absolute error ≤ 2-7在范围[-π, π]内
cosh(x) 派生自 (exp(x) - exp(-x)) * 0.5
cross(x, y) 派生自 (x[i] * y[j] - x[j] * y[i])
degrees(x) 派生自 x * 57.295779513082322865
distance(x, y) 派生自 length(x - y)
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) 正确四舍五入
frexp(x) 正确四舍五入
inverseSqrt(x) 2 ULP
ldexp(x, y) 正确四舍五入
length(x) 派生自 sqrt(dot(x, x))
log(x) 3 ULP outside the range [0.5, 2.0].
绝对错误 < 2-21 在范围 [0.5, 2.0] 内
3 ULP 在范围 [0.5, 2.0] 外.
绝对错误 < 2-7 在范围 [0.5, 2.0] 内
log2(x) 3 ULP outside the range [0.5, 2.0].
绝对错误 < 2-21 在范围 [0.5, 2.0] 内
3 ULP 在范围 [0.5, 2.0] 外.
绝对错误 < 2-7 在范围 [0.5, 2.0] 内
max(x, y) 正确四舍五入
min(x, y) 正确四舍五入
mix(x, y, z) 派生自 x * (1.0 - z) + y * z
modf(x) 正确四舍五入
normalize(x) 派生自 x / length(x)
pow(x, y) 派生自 exp2(y * log2(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,
where k = 1.0 - z * z * (1.0 - dot(y, x) * dot(y, x))
If k < 0.0 the result is precisely 0.0
round(x) 正确四舍五入
sign(x) 正确四舍五入
sin(x) Absolute error ≤ 2-11 在范围 [-π, π] 内 绝对错误 ≤ 2-7 在范围 [-π, π] 内
sinh(x) 派生自 (exp(x) - exp(-x)) * 0.5
smoothstep(low, high, x) 派生自 t * t * (3.0 - 2.0 * t),
where t = clamp((x - low) / (high - low), 0.0, 1.0)
sqrt(x) 派生自 1.0 / inverseSqrt(x)
step(edge, x) 正确四舍五入
tan(x) 派生自 sin(x) / cos(x)
tanh(x) 派生自 sinh(x) / cosh(x)
trunc(x) 正确四舍五入

Reassociation 是对表达式中的操作进行重新排序,这样如果精确计算,答案是相同的。 例如:

  • (a + b) + c 重新关联到 a + (b + c)

  • (a - b) + c 重新关联到 (a + c) - b

  • (a * b) / c 重新关联到 (a / c) * b

然而,当以浮点计算时,结果可能不一样。 由于近似,重新关联的结果可能不准确,或者在计算中间结果时可能会触发溢出或 NaN。

一个实现可以重新关联操作。

如果转换后的表达式至少与原始公式一样准确,则实现可以融合操作。 例如,一些融合的乘加实现可能比先乘后加法更准确。

12.5.2. 浮点数转换

在本节中,浮点类型可以是以下任何一种:

  • WGSL 中的 f32f16 类型。

  • 对应于 IEEE-754 浮点标准定义的二进制格式的假设类型。

注意:回想一下 f32 WGSL 类型对应于 IEEE-754 binary32 格式,f16 WGSL 类型对应于 IEEE-754 binary16 格式。

将浮点标量值转换为整数类型时:

  • 如果原始值可以在目标类型中准确表示,则结果就是该值。

  • 否则,原始值朝零四舍五入。

    • 如果舍入的值在目标类型中完全可表示,则结果就是该值。

    • 否则,结果是目标类型中最接近舍入值的值。

注意:换句话说,浮点到整数的转换会向零舍入,然后饱和。

注意:溢出情况下的结果可能不会产生目标类型中幅度最大的值,因为 该值可能无法在原始浮点类型中完全表示。 例如,u32 中的最大值是 4294967295,但是 4294967295.0 在 f32 中不能完全表示。 对于任何实数 x 与 4294967040 ≤ x ≤ 4294967295, 最接近 x 的 f32 值 大于 429467295 或向下舍入为 4294967040。 因此,浮点转换产生的最大 u32 值为 4294967040u。

将值转换为浮点类型时:

  • 如果原始值可以在目标类型中准确表示,则结果就是该值。

    • 如果原始值为零且为整数类型,则结果值具有零符号位。

  • 否则,原始值不能完全表示。

    • 如果原始值不同于但位于目标类型中可表示的两个相邻值之间,则结果是这两个值之一。 WGSL 没有指定是选择较大还是较小的可表示值,并且此类转换的不同实例可能选择不同。

    • 否则,如果原始值位于目标类型的范围之外。

      • 当原始类型为 i32u32 之一且目标类型为 f32 时,不会发生这种情况。

      • 当源类型是指数和尾数位较少的浮点类型时,不会发生这种情况。

      • 如果源类型是尾数位多于目标类型的浮点类型,则:

        • 可以丢弃源值的额外尾数位(将它们视为 0)。

          • 如果结果值是目标类型的最大正常值,那么这就是结果。

        • 否则,结果是与源值具有相同符号的无穷大值。

    • 否则,如果源类型的原始值是 NaN,则结果是目标类型的 NaN。

注意:整数值可能位于两个相邻的可表示浮点值之间。 特别是,f32 类型使用 23 个显式小数位。 此外,当浮点值在正常范围内时(指数既不是极值),尾数就是小数位的集合,以及在位位置 23 的最高有效位置处的额外 1 位。 然后,例如,整数 228 和 1+228 都映射到相同的浮点值: 最低有效 1 位不能用浮点格式表示。 这种碰撞发生在大小至少为 225 的相邻整数对中。

问题:(dneto) 默认舍入模式是一种实现选择。 那是我们想要的吗?

问题:检查 f32 到 f16 转换的行为是否超出了最大正常 f16 值。 我已经写了 NVIDIA GPU 的功能。 有关可执行的测试用例,请参阅 https://github.com/google/amber/pull/918。

13. 内存模型

通常,WGSL 遵循 Vulkan Memory Model。 本节的其余部分描述了 WGSL 程序如何映射到 Vulkan 内存模型。 注意:Vulkan 内存模型是formal Alloy model 的文本版本。

13.1. Memory Operation

在 WGSL 中,read access 相当于 Vulkan 内存模型中的内存读取操作。 在 WGSL 中, write access 相当于 Vulkan Memory Model 中的内存写操作。 read access 当调用执行以下操作之一时发生:

write access 当调用执行以下操作之一时发生:

Atomic read-modify-write 内置函数执行单个内存操作,即 read accesswrite access。 在任何其他情况下都不会发生读和写访问。

读写访问在 Vulkan 中统称为内存操作 内存模型。 内存操作准确访问与操作中使用的特定 memory view 相关联的一组 locations。 例如,从包含多个成员的结构访问 u32 的内存读取,仅读取与该 u32 关联的内存位置 成员。

EXAMPLE: Accessing memory locations
[[block]] struct S {
  a : f32,
  b : u32,
  c : f32
}
@group(0) @binding(0)
var<storage> v : S;
fn foo() {
  let x = v.b; // Does not access memory locations for v.a or v.c.
}

13.2. 内存模型参考

WGSL 中的每个模块范围变量形成一个唯一的 memory model reference 在给定入口点的生命周期内。 WGSL 中的每个函数范围变量形成一个唯一的 memory model reference 对于变量的生命周期。

13.3. 范围操作

当一个调用执行一个作用域操作时,它会影响一个或两个集合的调用。 这些集合是内存范围和执行范围。 memory scope 指定将看到任何 更新受操作影响的内存内容。 对于 同步内置函数,这也是 表示所有受影响的内存操作程序在函数之前排序 对在函数之后排序的受影响的操作程序可见。 execution scope 指定调用集 可能参与一个操作(参见 § 12.4 批量操作)。

Atomic built-in functions 映射到 atomic operations 其内存 scope 为:

  • Workgroup 如果原子指针在 workgroup 地址空间

  • QueueFamily 如果原子指针在 storage 地址空间

同步内置函数 映射到控制 执行和内存 scopes 的障碍 工作组

隐式和显式导数具有隐式 quad 执行范围。

注意:当生成不启用 Vulkan 内存模型的 SPIR-V 时, 应该使用 Device 范围而不是 QueueFamily

13.4. 内存语义

所有 Atomic built-in functions 使用 Relaxed memory semantics 因此,没有内存语义。 workgroupBarrier 使用 AcquireRelease memory semanticsWorkgroupMemory 内存语义。 storageBarrier 使用 AcquireRelease memory semanticsUniformMemory 内存语义。 注意:组合的“workgroupBarrier”和“storageBarrier”使用“AcquireRelease”排序语义以及“WorkgroupMemory”和“UniformMemory”内存语义。

注意:没有原子或同步内置函数使用 MakeAvailableMakeVisible 语义。

13.5. Private vs Non-private

storage 中的所有非原子 read accessesworkgroup 地址空间被视为 non-private 并对应于具有以下范围的 NonPrivatePointer | MakePointerVisible 内存操作数的读取操作:

storage 中的所有非原子 write accessesworkgroup 存储类别被考虑 non-private 并对应于具有以下范围的 NonPrivatePointer | MakePointerAvailable 内存操作数的写操作:

问题: https://github.com/gpuweb/gpuweb/issues/1621

14. 关键字和符号摘要

14.1. 关键字摘要

14.1.1. 类型定义关键字

array :

| 'array'

atomic :

| 'atomic'

bool :

| 'bool'

float32 :

| 'f32'

float16 :

| 'f16'

int32 :

| 'i32'

mat2x2 :

| 'mat2x2'

mat2x3 :

| 'mat2x3'

mat2x4 :

| 'mat2x4'

mat3x2 :

| 'mat3x2'

mat3x3 :

| 'mat3x3'

mat3x4 :

| 'mat3x4'

mat4x2 :

| 'mat4x2'

mat4x3 :

| 'mat4x3'

mat4x4 :

| 'mat4x4'

override :

| 'override'

pointer :

| 'ptr'

sampler :

| 'sampler'

sampler_comparison :

| 'sampler_comparison'

struct :

| 'struct'

texture_1d :

| 'texture_1d'

texture_2d :

| 'texture_2d'

texture_2d_array :

| 'texture_2d_array'

texture_3d :

| 'texture_3d'

texture_cube :

| 'texture_cube'

texture_cube_array :

| 'texture_cube_array'

texture_multisampled_2d :

| 'texture_multisampled_2d'

texture_storage_1d :

| 'texture_storage_1d'

texture_storage_2d :

| 'texture_storage_2d'

texture_storage_2d_array :

| 'texture_storage_2d_array'

texture_storage_3d :

| 'texture_storage_3d'

texture_depth_2d :

| 'texture_depth_2d'

texture_depth_2d_array :

| 'texture_depth_2d_array'

texture_depth_cube :

| 'texture_depth_cube'

texture_depth_cube_array :

| 'texture_depth_cube_array'

texture_depth_multisampled_2d :

| 'texture_depth_multisampled_2d'

uint32 :

| 'u32'

vec2 :

| 'vec2'

vec3 :

| 'vec3'

vec4 :

| 'vec4'

14.1.2. Other Keywords

bitcast :

| 'bitcast'

break :

| 'break'

case :

| 'case'

continue :

| 'continue'

continuing :

| 'continuing'

default :

| 'default'

discard :

| 'discard'

else :

| 'else'

enable :

| 'enable'

fallthrough :

| 'fallthrough'

false :

| 'false'

fn :

| 'fn'

for :

| 'for'

function :

| 'function'

if :

| 'if'

let :

| 'let'

loop :

| 'loop'

private :

| 'private'

return :

| 'return'

storage :

| 'storage'

switch :

| 'switch'

true :

| 'true'

type :

| 'type'

uniform :

| 'uniform'

var :

| 'var'

workgroup :

| 'workgroup'

14.2. 保留字

reserved word 是一个 token 保留供将来使用。 WGSL 程序不得包含保留字。

以下是保留字:

_reserved :

| 'AppendStructuredBuffer'

| 'BlendState'

| 'Buffer'

| 'ByteAddressBuffer'

| 'CompileShader'

| 'ComputeShader'

| 'ConsumeStructuredBuffer'

| 'DepthStencilState'

| 'DepthStencilView'

| 'DomainShader'

| 'GeometryShader'

| 'Hullshader'

| 'InputPatch'

| 'LineStream'

| 'NULL'

| 'OutputPatch'

| 'PixelShader'

| 'PointStream'

| 'RWBuffer'

| 'RWByteAddressBuffer'

| 'RWStructuredBuffer'

| 'RWTexture1D'

| 'RWTexture1DArray'

| 'RWTexture2D'

| 'RWTexture2DArray'

| 'RWTexture3D'

| 'RasterizerState'

| 'RenderTargetView'

| 'SamplerComparisonState'

| 'SamplerState'

| 'Self'

| 'StructuredBuffer'

| 'Texture1D'

| 'Texture1DArray'

| 'Texture2D'

| 'Texture2DArray'

| 'Texture2DMS'

| 'Texture2DMSArray'

| 'Texture3D'

| 'TextureCube'

| 'TextureCubeArray'

| 'TriangleStream'

| 'VertexShader'

| 'abstract'

| 'active'

| 'alignas'

| 'alignof'

| 'as'

| 'asm'

| 'asm_fragment'

| 'async'

| 'atomic_uint'

| 'attribute'

| 'auto'

| 'await'

| 'become'

| 'bf16'

| 'buffer'

| 'cast'

| 'catch'

| 'cbuffer'

| 'centroid'

| 'char'

| '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'

| 'dword'

| 'dynamic_cast'

| 'enum'

| 'explicit'

| 'export'

| 'extends'

| 'extern'

| 'external'

| 'f64'

| 'filter'

| 'final'

| 'finally'

| 'fixed'

| 'flat'

| 'friend'

| 'from'

| 'fvec2'

| 'fvec3'

| 'fvec4'

| 'fxgroup'

| 'get'

| 'goto'

| 'groupshared'

| 'handle'

| 'highp'

| 'hvec2'

| 'hvec3'

| 'hvec4'

| 'i16'

| 'i64'

| 'i8'

| 'iimage1D'

| 'iimage1DArray'

| 'iimage2D'

| 'iimage2DArray'

| 'iimage2DMS'

| 'iimage2DMSArray'

| 'iimage2DRect'

| 'iimage3D'

| 'iimageBuffer'

| 'iimageCube'

| 'iimageCubeArray'

| 'image1D'

| 'image1DArray'

| 'image2D'

| 'image2DArray'

| 'image2DMS'

| 'image2DMSArray'

| 'image2DRect'

| 'image3D'

| 'imageBuffer'

| 'imageCube'

| 'imageCubeArray'

| 'impl'

| 'implements'

| 'import'

| 'in'

| 'inline'

| 'inout'

| 'input'

| 'instanceof'

| 'interface'

| 'invariant'

| 'isampler1D'

| 'isampler1DArray'

| 'isampler2D'

| 'isampler2DArray'

| 'isampler2DMS'

| 'isampler2DMSArray'

| 'isampler2DRect'

| 'isampler3D'

| 'isamplerBuffer'

| 'isamplerCube'

| 'isamplerCubeArray'

| 'isubpassInput'

| 'isubpassInputMS'

| 'itexture1D'

| 'itexture1DArray'

| 'itexture2D'

| 'itexture2DArray'

| 'itexture2DMS'

| 'itexture2DMSArray'

| 'itexture2DRect'

| 'itexture3D'

| 'itextureBuffer'

| 'itextureCube'

| 'itextureCubeArray'

| 'layout'

| 'line'

| 'lineadj'

| 'linear'

| 'lowp'

| 'macro'

| 'macro_rules'

| 'mat'

| 'match'

| 'matrix'

| 'mediump'

| 'meta'

| 'mod'

| 'module'

| 'move'

| 'mut'

| 'mutable'

| 'namespace'

| 'new'

| 'nil'

| 'noexcept'

| 'noinline'

| 'nointerpolation'

| 'noperspective'

| 'null'

| 'nullptr'

| 'of'

| 'operator'

| 'out'

| 'output'

| 'package'

| 'packoffset'

| 'partition'

| 'pass'

| 'patch'

| 'pixelfragment'

| 'point'

| 'precise'

| 'precision'

| 'premerge'

| 'priv'

| 'protected'

| 'pub'

| 'public'

| 'readonly'

| 'ref'

| 'regardless'

| 'register'

| 'reinterpret_cast'

| 'requires'

| 'resource'

| 'restrict'

| 'row_major'

| 'samper'

| 'sample'

| 'sampler1D'

| 'sampler1DArray'

| 'sampler1DArrayShadow'

| 'sampler1DShadow'

| 'sampler2D'

| 'sampler2DArray'

| 'sampler2DArrayShadow'

| 'sampler2DMS'

| 'sampler2DMSArray'

| 'sampler2DRect'

| 'sampler2DRectShadow'

| 'sampler2DShadow'

| 'sampler3D'

| 'sampler3DRect'

| 'samplerBuffer'

| 'samplerCube'

| 'samplerCubeArray'

| 'samplerCubeArrayShadow'

| 'samplerCubeShadow'

| 'samplerShadow'

| 'self'

| 'set'

| 'shared'

| 'signed'

| 'sizeof'

| 'smooth'

| 'snorm'

| 'stateblock'

| 'stateblock_state'

| 'static'

| 'static_assert'

| 'static_cast'

| 'std'

| 'string'

| 'subpassInput'

| 'subpassInputMS'

| 'subroutine'

| 'super'

| 'superp'

| 'target'

| 'tbuffer'

| 'technique'

| 'technique10'

| 'technique11'

| 'template'

| 'texture'

| 'texture1D'

| 'texture1DArray'

| 'texture2D'

| 'texture2DArray'

| 'texture2DMS'

| 'texture2DMSArray'

| 'texture2DRect'

| 'texture3D'

| 'textureBuffer'

| 'textureCube'

| 'textureCubeArray'

| 'this'

| 'thread_local'

| 'throw'

| 'trait'

| 'triangle'

| 'triangleadj'

| 'try'

| 'typedef'

| 'typeid'

| 'typename'

| 'typeof'

| 'u16'

| 'u64'

| 'u8'

| 'uimage1D'

| 'uimage1DArray'

| 'uimage2D'

| 'uimage2DArray'

| 'uimage2DMS'

| 'uimage2DMSArray'

| 'uimage2DRect'

| 'uimage3D'

| 'uimageBuffer'

| 'uimageCube'

| 'uimageCubeArray'

| 'union'

| 'unless'

| 'unorm'

| 'unsafe'

| 'unsigned'

| 'unsized'

| 'usampler1D'

| 'usampler1DArray'

| 'usampler2D'

| 'usampler2DArray'

| 'usampler2DMS'

| 'usampler2DMSArray'

| 'usampler2DRect'

| 'usampler3D'

| 'usamplerBuffer'

| 'usamplerCube'

| 'usamplerCubeArray'

| 'use'

| 'using'

| 'usubpassInput'

| 'usubpassInputMS'

| 'utexture1D'

| 'utexture1DArray'

| 'utexture2D'

| 'utexture2DArray'

| 'utexture2DMS'

| 'utexture2DMSArray'

| 'utexture2DRect'

| 'utexture3D'

| 'utextureBuffer'

| 'utextureCube'

| 'utextureCubeArray'

| 'varying'

| 'vec'

| 'vector'

| 'vertexfragment'

| 'virtual'

| 'void'

| 'volatile'

| 'wchar_t'

| 'wgsl'

| 'where'

| 'with'

| 'writeonly'

| 'yield'

14.3. 句法符号

syntactic token 是一个特殊代码点序列,用于:

  • 拼写表达式运算符,或

  • 作为标点符号:对其他语法元素进行分组、排序或分隔。

and :

| '&' (Code point: U+0026)

and_and :

| '&&' (Code points: U+0026 U+0026)

arrow :

| '->' (Code points: U+002D U+003E)

attr :

| '@' (Code point: U+0040)

forward_slash :

| '/' (Code point: U+002F)

bang :

| '!' (Code point: U+0021)

bracket_left :

| '[' (Code point: U+005B)

bracket_right :

| ']' (Code point: U+005D)

brace_left :

| '{' (Code point: U+007B)

brace_right :

| '}' (Code point: U+007D)

colon :

| ':' (Code point: U+003A)

comma :

| ',' (Code point: U+002C)

equal :

| '=' (Code point: U+003D)

equal_equal :

| '==' (Code points: U+003D U+003D)

not_equal :

| '!=' (Code points: U+0021 U+003D)

greater_than :

| '>' (Code point: U+003E)

greater_than_equal :

| '>=' (Code points: U+003E U+003D)

less_than :

| '<' (Code point: U+003C)

less_than_equal :

| '<=' (Code points: U+003C U+003D)

modulo :

| '%' (Code point: U+0025)

minus :

| '-' (Code point: U+002D)

minus_minus :

| '--' (Code points: U+002D U+002D)

period :

| '.' (Code point: U+002E)

plus :

| '+' (Code point: U+002B)

plus_plus :

| '++' (Code points: U+002B U+002B)

or :

| '|' (Code point: U+007C)

or_or :

| '||' (Code points: U+007C U+007C)

paren_left :

| '(' (Code point: U+0028)

paren_right :

| ')' (Code point: U+0029)

semicolon :

| ';' (Code point: U+003B)

star :

| '*' (Code point: U+002A)

tilde :

| '~' (Code point: U+007E)

underscore :

| '_' (Code point: U+005F)

xor :

| '^' (Code point: U+005E)

plus_equal :

| '+=' (Code points: U+002B U+003D)

minus_equal :

| '-=' (Code points: U+002D U+003D)

times_equal :

| '*=' (Code points: U+002A U+003D)

division_equal :

| '/=' (Code points: U+002F U+003D)

modulo_equal :

| '%=' (Code points: U+0025 U+003D)

and_equal :

| '&=' (Code points: U+0026 U+003D)

or_equal :

| '|=' (Code points: U+007C U+003D)

xor_equal :

| '^=' (Code points: U+005E U+003D)

15. 内置值

下表列出了可用的 built-in input valuesbuilt-in output values

查看 § 9.3.1 内置输入和输出 如何创建内置值。

名称 阶段 输入或输出 类型 描述
vertex_index vertex input u32 当前 API 级绘制命令中当前顶点的索引,与绘制实例无关。

对于非索引绘制,第一个顶点的索引等于绘制的 firstVertex 参数,无论是直接提供还是间接提供。 绘制实例中每增加一个顶点,索引就会增加 1。

对于索引绘制,索引等于顶点的索引缓冲区条目,加上绘制的 baseVertex 参数,无论是直接提供还是间接提供。

instance_index vertex input u32 当前 API 级绘制命令中当前顶点的实例索引。

第一个实例的索引等于绘制的 firstInstance 参数,无论是直接提供还是间接提供。 抽签中每增加一个实例,索引就会增加 1。

position vertex output vec4<f32> 当前顶点的输出位置,使用齐次坐标。 在均匀归一化(其中 xyz 分量中的每一个除以 w 分量)之后,位置在 WebGPU 归一化设备坐标空间中。 查看 WebGPU § Coordinate Systems
position fragment input vec4<f32> 当前片元在framebuffer space中的的帧缓冲区位置。 (xyz 分量已经被缩放,使得 w 现在为 1。) 查看 WebGPU § Coordinate Systems
front_facing fragment input bool 当当前片段位于正面图元上时为True。 否则为False 查看 WebGPU § Front-facing.
frag_depth fragment output f32 在视口深度范围内更新了片段的深度。 查看 WebGPU § Coordinate Systems
local_invocation_id compute input vec3<u32> 当前调用的 local invocation ID,即它在 workgroup grid 中的位置。
local_invocation_index compute input u32 当前调用的 local invocation index,即 workgroup grid 中调用位置的线性化索引。
global_invocation_id compute input vec3<u32> 当前调用的 global invocation ID,即它在 compute shader grid 中的位置。
workgroup_id compute input vec3<u32> 当前调用的 workgroup ID,即工作组在 workgroup grid 中的位置。
num_workgroups compute input vec3<u32> API 计算着色器 dispatcheddispatch sizevec<u32>(group_count_x, group_count_y, group_count_z)
sample_index fragment input u32 当前片段的示例索引。 该值最小为 0,最大为 sampleCount-1, 其中 sampleCount 是为 GPU 渲染管道指定的 MSAA 样本数。
查看 WebGPU § GPURenderPipeline
sample_mask fragment input u32 当前片元的样本覆盖掩码。 它包含一个位掩码,指示正在渲染的图元覆盖此片段中的哪些样本。
查看 WebGPU § Sample Masking
sample_mask fragment output u32 当前片元的示例覆盖掩码控制。 写入此变量的最后一个值成为 shader-output mask。 写入值中的零位将导致颜色附件中的相应样本被丢弃。
查看 WebGPU § Sample Masking
EXAMPLE: Declaring built-in values
 struct VertexOutput {
   @builtin(position) my_pos: vec4<f32>
 }

 @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
 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>,
) {}

16. 内置函数

某些函数是 predeclared,由实现提供,因此始终可用于 WGSL 程序。 这些被称为built-in functions

内建函数是一个函数族,它们都具有相同的名称, 但是通过它们的形式参数的数量、顺序和类型来区分。 这些不同的函数变体中的每一个都是 overload

注意:每个 user-defined function 只有一个 overload

每个 overload 描述如下:

  • 类型参数化,如果有的话。

  • 内置函数名,带括号的 formal parameters 列表,以及可选的 return type

  • 函数重载的行为。

由于内置函数始终在作用域内,因此尝试重新定义一个函数或将内置函数的名称用作任何其他module-scope声明的 identifier 都是错误的。

与 WGSL 程序中定义的普通函数不同,内置函数可以使用相同的函数名和不同的参数集。 换句话说,一个内置函数可能有多个重载,但 WGSL 中的普通函数定义可能没有。

调用内置函数时,函数的所有参数都会在函数求值开始之前求值。 见§ 8.2 Function Calls

16.1. 逻辑内置函数

Parameterization Overload Description
@const fn
all(e: vecN<bool>) -> bool
如果e的每个组件都为真,则返回真。
e: bool @const fn
all(e) -> bool
返回 e.
@const fn
any(e: vecN<bool>) -> bool
如果e的每个组件都为真,则返回真。
e: bool @const fn
any(e) -> bool
返回 e.
Tscalar, abstract numeric type, 或 vector @const fn
select(f: T, t: T, cond: bool) -> T
cond 为真时返回 t,否则返回 f
Tscalar or abstract numeric type @const fn
select(f: vecN<T>, t: vecN<T>, cond: vecN<bool>) -> vecN<T>
Component-wise 选择。结果组件i被计算为 select(f[i],t[i],cond[i]).

16.2. 数组内置函数

Parameterization Overload Description
fn arrayLength(e: ptr<storage,array<T>> ) -> u32 返回runtime-sized数组中的元素数量。

16.3. 浮动内置函数

前提条件 结论 描述
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
abs(e: T ) -> T
返回 e 的绝对值 (例如 e 带有正符号位)。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
acos(e: T ) -> T
返回 e 的反余弦值。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
acosh(e: T ) -> T
Returns the hyperbolic arc cosine of e. The result is 0 when e < 1.
Computes the non-negative functional inverse of cosh.
Component-wise when T is a vector.

Note: The result is not mathematically meaningful when e < 1.

T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
asin(e: T ) -> T
返回 e 的反正弦。 Component-wiseT 是一个向量。
T 为 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
asinh(e: T ) -> T
Returns the hyperbolic arc sine of e.
Computes the functional inverse of sinh.
Component-wise when T is a vector.
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
atan(e: T ) -> T
返回 e 的反正切。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
atanh(e: T ) -> T
返回 e 的双曲反正切。 当 abs(e) ≥ 1,结果为0.
计算 tanh 的反函数。
T 为向量时 Component-wise

注意:当 abs(e) ≥ 1 时结果不是数学上有意义的。

T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
atan2(e1: T ,e2: T ) -> T
返回 e1/e2 的反正切。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
ceil(e: T ) -> T
返回 e 的向上取整 ceilingComponent-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> clamp(e: T , low: T , high: T) -> T Returns either min(max(e,low),high), or the median of the three values e, low, high. Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
cos(e: T ) -> T
返回 e 的余弦。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
cosh(e: T ) -> T
返回 e 的双曲余弦值。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, or f16 @const fn
cross(e1: vec3<T> ,e2: vec3<T>) -> vec3<T>
返回 e1e2 的叉积。 (GLSLstd450Cross)
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
degrees(e1: T ) -> T
将弧度转换为度,近似 e1 × 180 ÷ π. Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
distance(e1: T ,e2: T ) -> f32
返回 e1e2 之间的距离(即 length(e1-e2))。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
exp(e1: T ) -> T
返回 e1 的自然求幂 (即 ee1)。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
exp2(e: T ) -> T
Returns 2 raised to the power e (e.g. 2e). Component-wiseT 是一个向量。
T 是 vecN<AbstractFloat>, vecN<f32>, 或 vecN<f16> @const fn
faceForward(e1: T ,e2: T ,e3: T ) -> T
如果 dot(e2,e3) 是负的返回 e1, 否则返回 -e1
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
floor(e: T ) -> T
返回 e 的向下取整 floorComponent-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
fma(e1: T ,e2: T ,e3: T ) -> T
返回 e1 * e2 + e3. Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
fract(e: T ) -> T
返回e 的小数部分,计算为e - floor(e
Component-wiseT 是一个向量。
T 是 f32 @const fn
frexp(e:T) -> __frexp_result
拆分 e 转换为 significand * 2exponent形式的有效数和指数。 返回 __frexp_result 内置结构, 定义如下:
struct __frexp_result {
  sig : f32, // significand part
  exp : i32  // exponent part
}
有效数的大小在 [0.5, 1.0) 或 0 的范围内。

注意:一个值不能用类型 __frexp_result 显式声明,但一个值可以推断类型。

EXAMPLE: frexp usage
// 推断结果类型
let sig_and_exp = frexp(1.5);
// 设置 fraction_direct 的值为 0.75
let fraction_direct = frexp(1.5).sig;
(GLSLstd450FrexpStruct)
T 是 f16 @const fn frexp(e:T) -> __frexp_result_f16
拆分 e 转换为 significand * 2exponent 形式的有效数和指数。 返回 __frexp_result_f16 内置结构,定义如下:
struct __frexp_result_f16 {
  sig : f16, // significand part
  exp : i32  // exponent part
}
有效数字的大小在 [0.5, 1.0) 或 0 的范围内。

注意:一个值不能用 __freexp_result_f16类型显式声明,但是一个值可以推断出类型。

T 是 vecN<f32> @const fn
frexp(e:T) -> __frexp_result_vecN
拆分 e 的组件 转换为 significand * 2exponent形式的有效数和指数。 返回 __frexp_result_vecN 内置结构,定义为:
struct __frexp_result_vecN {
  sig : vecN<f32>, // significand part
  exp : vecN<i32> // exponent part
}
有效数的每个分量的大小在 [0.5, 1.0) 或 0 的范围内。

注意:不能用 __frexp_result_vecN 类型显式声明值,但值可以推断类型。

T 是 vecN<f16> @const fn frexp(e:T) -> __frexp_result_vecN_f16
拆分 e 的组件 转换为 significand * 2exponent 形式的有效数和指数。 返回 __frexp_result_vecN 内置结构, 定义如下:
struct __frexp_result_vecN_f16 {
  sig : vecN<f16>, // significand part
  exp : vecN<i32>  // exponent part
}
有效数字的每个分量的大小在 [0.5, 1.0) 或 0 的范围内。

注意:一个值不能用 __freexp_result_vecN_f16类型显式声明,但是一个值可以推断出类型。

T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
inverseSqrt(e: T ) -> T
返回 sqrt(e) 的倒数。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16>
I 是 ALLSIGNEDINTEGRAL, 在
如果 T 是标量,那么 I 是标量,或者
T 是向量,那么 I 是向量
@const fn
ldexp(e1: T ,e2: I ) -> T
返回 e1 * 2e2Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
length(e: T ) -> f32
返回 e 的长度 (例如 abs(e) 如果 T 是标量,或者 sqrt(e[0]2 + e[1]2 + ...) 如果 T 是向量)。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
log(e: T ) -> T
返回 e 的自然对数。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
log2(e: T ) -> T
返回 e 的以 2 为底的对数。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
max(e1: T ,e2: T ) -> T
返回 e2 如果 e1 小于 e2,且 e1 除此以外。 如果一个操作数是 NaN,则返回另一个操作数。 如果两个操作数都是 NaN,则返回 NaN。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
min(e1: T ,e2: T ) -> T
返回 e2 如果e2 小于 e1,且 e1 除此以外。 如果一个操作数是 NaN,则返回另一个操作数。 如果两个操作数都是 NaN,则返回 NaN。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
mix(e1: T ,e2: T ,e3: T) -> T
返回 e1e2 的线性混合(例如 e1*(1-e3)+e2*e3)。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, or f16
T2 is vecN<T>
@const fn
mix(e1: T2 ,e2: T2 ,e3: f32 ) -> T
返回 e1e2 的分量线性混合, 使用标量混合因子 e3 对于每个组件。
mix(e1,e2,T2(e3)) 相同
T 是 f32 @const fn
modf(e:T) -> __modf_result
拆分 e 分为小数部分和整数部分。 返回 __modf_result 内置结构, 定义如下:
struct __modf_result {
  fract : f32, // fractional part
  whole : f32  // whole part
}

注意:一个值不能用 __modf_result 类型显式声明,但一个值可以推断类型。

EXAMPLE: modf usage
// 推断结果类型
let fract_and_whole = modf(1.5);
// 设置 fract_direct 的值为 0.5
let fract_direct = modf(1.5).fract;
T 是 f16 @const fn modf(e:T) -> __modf_result_f16
拆分 e 分成小数部分和整数部分。 返回 __modf_result_f16 内置结构, 定义如下:
struct __modf_result_f16 {
  fract : f16, // fractional part
  whole : f16  // whole part
}

注意:一个值不能用 __modf_result_f16 类型显式声明,但一个值可以推断类型.

T 是 vecN<f32> @const fn
modf(e:T) -> __modf_result_vecN
拆分 e 的组件分为小数部分和整数部分。 返回 __modf_result_vecN 内置结构,定义为:
struct __modf_result_vecN {
  fract : vecN<f32>, // fractional part
  whole : vecN<f32>  // whole part
}

注意:不能用类型 __modf_result_vecN 显式声明值,但值可以推断类型。

T 是 vecN<f16> @const fn modf(e:T) -> __modf_result_vecN_f16
拆分 e 的组件 分成小数部分和整数部分. 返回 __modf_result_vecN_f16 内置结构, 定义如下:
struct __modf_result_vecN_f16 {
  fract : vecN<f16>, // fractional part
  whole : vecN<f16>  // whole part
}

注意:一个值不能用 __modf_result_vecN_f16类型显式声明,但是一个值可以推断出类型.

T 是 AbstractFloat, f32, or f16 @const fn
normalize(e: vecN<T> ) -> vecN<T>
返回与 e 方向相同的单位向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
pow(e1: T ,e2: T ) -> T
返回 e1 提升到 e2 的幂。 Component-wiseT 是一个向量。
T 是 f32 or vecN<f32> @const fn quantizeToF16(e: T ) -> T 量化 32 位浮点值 e 好像 e 被转换为 IEEE 754 binary16 值, 然后转换回 IEEE 754 binary32 值。
查看 § 12.5.2 浮点数转换
Component-wiseT 是一个向量。

注意: vec2<f32> 的情况和 unpack2x16float(pack2x16float(e)) 一样。

T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
radians(e1: T ) -> T
将度数转换为弧度,近似于 e1 × π ÷ 180。Component-wiseT 是一个向量
T 是 vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
reflect(e1: T ,e2: T ) -> T
对于事件向量 e1 和表面方向e2,返回反射方向 e1-2*dot(e2,e1)*e2
T 是 vecN<I>
I is AbstractFloat, f32, or f16
@const fn
refract(e1: T ,e2: T ,e3: I ) -> T
对于事件向量 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
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
round(e: T ) -> T
返回值是最接近 e 的整数 k,作为浮点值。
e 位于 kk+1 的中间的整数, 结果是 kk 是偶数,当 k 是奇数时,返回 k+1 。
Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
sign(e: T ) -> T
返回 e 的符号。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
sin(e: T ) -> T
返回 e 的正弦值。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
sinh(e: T ) -> T
返回 e 的双曲正弦值。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
smoothstep(low: T , high: T , x: T ) -> T Component-wiseT 是一个向量。 对于标量 T,结果为: t * t * (3.0 - 2.0 * t), where t = clamp((x - low) / (high - low), 0.0, 1.0).
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
sqrt(e: T ) -> T
返回 e 的平方根。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
step(edge: T ,x: T ) -> T
如果 edgex 返回 1.0 ,否则为 0.0。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
tan(e: T ) -> T
返回 e 的正切。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
tanh(e: T ) -> T
返回 e 的双曲正切值。 Component-wiseT 是一个向量。
T 是 AbstractFloat, f32, f16, vecN<AbstractFloat>, vecN<f32>, or vecN<f16> @const fn
trunc(e: T ) -> T
返回最近的绝对值小于或等于 e 的整数。 Component-wiseT 是一个向量。

16.4. 整数内置函数

Precondition Conclusion Description
S is AbstractInt, i32, or u32
T is S or vecN<S>
@const fn
abs(e: T ) -> T
e 的绝对值。 Component-wiseT 是一个向量。 如果 e 是有符号整数标量类型,求最大负值,则结果为e。 如果 e 是无符号整数类型,则结果为 e
S is AbstractInt, i32, or u32
T is S or vecN<S>
@const fn
clamp(e: T , low: T, high: T) -> T
返回 min(max(e,low),high)Component-wiseT 是一个向量。
T 是 i32 or vecN<i32> @const fn
clamp(e: T ,low: T,high: T) -> T
返回 min(max(e,low),high)Component-wiseT 是一个向量。
T 是 i32, u32, vecN<i32>, or vecN<u32> @const fn
countLeadingZeros(e: T ) -> T
e 的最高位开始的连续0 位的个数,当T 时 是标量类型。
Component-wiseT 是一个向量。
在某些语言中也称为 “clz”。
T 是 i32, u32, vecN<i32>, or vecN<u32> @const fn
countOneBits(e: T ) -> T
e 的表示中 1 的位数。
也叫作 "population count"。
Component-wiseT 是一个向量。 (SPIR-V OpBitCount)
T is i32, u32, vecN<i32>, or vecN<u32> @const fn
countTrailingZeros(e: T ) -> T
e 的最低有效位开始的连续 0 位的数量,当 T 是标量类型。
Component-wiseT 是矢量。
在某些语言中也称为 “ctz”.
T is i32 or vecN<i32> @const fn
firstBitHigh(e: T ) -> T
T 是标量,结果为:
  • -1 如果 e 是 0 或 -1。
  • 否则 e 中最高位的位置 这与e 的符号位不同。

注意:由于有符号整数使用二进制补码表示,符号位出现在最高有效位位置。

Component-wiseT 是标量。

T is u32 or vecN<u32> @const fn
firstBitHigh(e: T ) -> T
对于矢量 T,结果为:
  • T(-1) 如果 e 是 0。
  • 否则 e 中最重要的 1 位的位置。
Component-wiseT 为矢量。
T is i32, u32, vecN<i32>, or vecN<u32> @const fn
firstBitLow(e: T ) -> T
对于矢量 T,结果为:
  • T(-1) 如果 e 是 0。
  • 否则 e 中最低有效 1 位的位置。
Component-wiseT 为矢量
T is i32 or vecN<i32> @const fn
extractBits(
 e: T,
 offset: u32,
 count: u32) -> T
从整数中读取位,带符号扩展。

T 是一个标量,那么:

  • wT 的比特位的宽度
  • o = min(offset,w)
  • c = min(count, w - o)
  • 结果为 0 如果 c 是 0。
  • 否则, 结果的位 0..c-1 从位复制eo..o+c-1。 结果的其他位与结果的位c-1 相同。
Component-wiseT 为矢量。
T is u32 or vecN<u32> @const fn
extractBits(
 e: T,
 offset: u32,
 count: u32) -> T
从整数中读取位,没有符号扩展。

T 是一个标量,那么:

  • wT 的比特位的宽度
  • o = min(offset,w)
  • c = min(count, w - o)
  • 结果为 0 ,如果 c 是 0。
  • 否则, 结果的位 0..c-1 从位复制eo..o+c-1。 其他的位为 0.
Component-wiseT 为矢量。
T is i32, u32, vecN<i32>, or vecN<u32> @const fn
insertBits(
 e: T,
 newbits:T,
 offset: u32,
 count: u32) -> T
以整数设置位。

T 是一个标量,那么:

  • |wT 的比特位的宽度
  • o = min(offset,w)
  • c = min(count, w - o)
  • 结果为 0 ,如果 c 是 0。
  • 否则, 结果的位 o..o+c-1 从 newbits 的位 0..c-1 复制。 结果的其他位从e 复制。
Component-wiseT 为矢量。
S is AbstractInt, i32, or u32
T is S or vecN<S>
@const fn
max(e1: T ,e2: T) -> T
返回 e2 如果 e1 小于 e2, 否则返回 e1Component-wiseT 是一个向量。
S is AbstractInt, i32, or u32
T is S or vecN<S>
@const fn
min(e1: T ,e2: T) -> T
返回 e1 如果 e1 小于 e2, 否则返回 e2Component-wiseT 是一个向量。
T 是 i32, u32, vecN<i32>, or vecN<u32> @const fn
reverseBits(e: T ) -> T
反转 e 中的位:结果的位置 k 处的位 等于 e 的位置 31-k 处的位。
Component-wiseT 是一个向量。
S is AbstractInt, i32, or u32
T is S or vecN<S>
TS 如果 T 是 AbstractInt 或 u32 是标量,或
vecN<AbstractInt>, or vecN<u32> 否则
@const fn shiftLeft(e1: T, e2: TS) -> T 逻辑左移。
换档 e1 左,在最低有效位插入零位,并丢弃最高有效位。

要移位的位数是e2 的值。 如果 e1 具有 concrete 类型,移位值以 e1 的位宽为模。
Component-wiseT 是一个向量。

如果 e2AbstractInt 或 vecN ,它是 shader-creation error 如果任何值小于 0。

S is AbstractInt, i32, or u32
T is S or vecN<S>>br> TS 如果 T 是 AbstractInt 或 u32 是标量,或
vecN<AbstractInt>, 或 vecN<u32> otherwise
@const fn shiftRight(e1: T, e2: TS) -> T 逻辑右移
如果 e1 有符号,移位 e1 对,在最高有效位插入零位,并丢弃最低有效位。 如果 e1 无符号,移位 e1 对,复制e1 的符号位 进入最高有效位,并丢弃最低有效位。

要移位的位数是e2 的值。 如果 e1 具有 concrete 类型,移位值以 e1 的位宽为模。
Component-wiseT 是一个向量。

如果 e2AbstractInt 或 vecN ,它是 shader-creation error 如果任何值小于 0。

16.5. 矩阵内置函数

前提条件 结果 描述
T is AbstractFloat, f32, or f16 determinant(e: matCxC<T> ) -> T 返回 e 的行列式。
T is AbstractFloat, f32, or f16 transpose(e: matRxC<T> ) -> matCxR<T> 返回 e 的转置。

16.6. 向量内置函数

Parameterization Overload Description
T 是 AbstractInt, AbstractFloat, i32, u32, f32, 或 f16 @const fn
dot(e1: vecN<T>,e2: vecN<T>) -> T
返回 e1e2 的点积.

16.7. 倒数内置函数

查看 § 12.4.2 导数.

这些函数:

前提条件 结论 解释
T 是 f32 或 vecN<f32> fn dpdx(e:T) -> T e 的偏导数对应窗口 x 的坐标。 结果与 dpdxFine(e)dpdxCoarse(e) 相同。
fn dpdxCoarse(e:T) ->T 返回 e 的偏导数相对于窗口 x 坐标,使用局部差异。 这可能会导致比 dpdxFine(e) 更少的独特位置。
fn dpdxFine(e:T) ->T 返回 e 的偏导数相对于窗口 x 坐标。
fn dpdy(e:T) ->T 返回 e 的偏导数相对于窗口 y 坐标。 结果与 dpdyFine(e)dpdyCoarse(e) 相同。
fn dpdyCoarse(e:T) ->T 返回 e 的偏导数相对于窗口 y 坐标,使用局部差异。 这可能会导致比 dpdyFine(e) 更少的独特位置。
fn dpdyFine(e:T) ->T 返回 e 的偏导数相对于窗口y坐标。
fn fwidth(e:T) ->T 返回 abs(dpdx(e)) + abs(dpdy(e)).
fn fwidthCoarse(e:T) ->T 返回 abs(dpdxCoarse(e)) + abs(dpdyCoarse(e)).
fn fwidthFine(e:T) ->T 返回 abs(dpdxFine(e)) + abs(dpdyFine(e)).

16.8. 纹理内置函数

在本节中,显示的纹理类型具有以下参数:

参数值必须对相应的纹理类型有效。

16.8.1. textureDimensions

返回纹理的尺寸,或以 texels 为单位的纹理的 mip 级别。

fn textureDimensions(t: texture_1d<T>) -> i32
fn textureDimensions(t: texture_1d<T>, level: i32) -> i32
fn textureDimensions(t: texture_2d<T>) -> vec2<i32>
fn textureDimensions(t: texture_2d<T>, level: i32) -> vec2<i32>
fn textureDimensions(t: texture_2d_array<T>) -> vec2<i32>
fn textureDimensions(t: texture_2d_array<T>, level: i32) -> vec2<i32>
fn textureDimensions(t: texture_3d<T>) -> vec3<i32>
fn textureDimensions(t: texture_3d<T>, level: i32) -> vec3<i32>
fn textureDimensions(t: texture_cube<T>) -> vec2<i32>
fn textureDimensions(t: texture_cube<T>, level: i32) -> vec2<i32>
fn textureDimensions(t: texture_cube_array<T>) -> vec2<i32>
fn textureDimensions(t: texture_cube_array<T>, level: i32) -> vec2<i32>
fn textureDimensions(t: texture_multisampled_2d<T>)-> vec2<i32>
fn textureDimensions(t: texture_depth_2d) -> vec2<i32>
fn textureDimensions(t: texture_depth_2d, level: i32) -> vec2<i32>
fn textureDimensions(t: texture_depth_2d_array) -> vec2<i32>
fn textureDimensions(t: texture_depth_2d_array, level: i32) -> vec2<i32>
fn textureDimensions(t: texture_depth_cube) -> vec2<i32>
fn textureDimensions(t: texture_depth_cube, level: i32) -> vec2<i32>
fn textureDimensions(t: texture_depth_cube_array) -> vec2<i32>
fn textureDimensions(t: texture_depth_cube_array, level: i32) -> vec2<i32>
fn textureDimensions(t: texture_depth_multisampled_2d)-> vec2<i32>
fn textureDimensions(t: texture_storage_1d<F,A>) -> i32
fn textureDimensions(t: texture_storage_2d<F,A>) -> vec2<i32>
fn textureDimensions(t: texture_storage_2d_array<F,A>) -> vec2<i32>
fn textureDimensions(t: texture_storage_3d<F,A>) -> vec3<i32>
fn textureDimensions(t: texture_external) -> vec2<i32>

参数:

t sampled, multisampleddepth, storage,或者 external 纹理。
level mip 级别,级别 0 包含纹理的全尺寸版本。
如果省略,则返回级别 0 的维度。

返回:

以纹素为单位的纹理尺寸。

对于基于立方体的纹理,结果是立方体每个面的尺寸。 立方体面是正方形,因此结果的 x 和 y 分量相等。

如果 level 在范围 [0, textureNumLevels(t)) 之外,则可以返回返回类型的任何有效值。

16.8.2. textureGather

texture gather 操作从二维、二维数组、立方体或立方体数组纹理中读取,计算四元素向量如下:

  • mip level 0 中找出将在带有线性过滤的采样操作中使用的四个纹素:

    • 使用指定的坐标、数组索引(如果存在)和偏移量(如果存在)。

    • 当考虑纹理空间坐标 (u,v) 时,纹素是相邻的,形成一个正方形。

    • 在纹理边缘、立方体面边缘或立方体角处选择的纹素像在普通纹理采样中一样处理。

  • 对于每个纹素,读取一个通道并将其转换为标量值。

    • 对于非深度纹理,从零开始的 component 参数指定要使用的通道。

      • 如果纹理格式支持指定的通道,即有多个 component通道:

        • 当 texel 值为 v 时,产生标量值 v[component]

      • 否则:

        • component 为 1 或 2 时,产量为 0.0。

        • component 为 3(alpha 通道)时,产量为 1.0。

    • 对于深度纹理,产生纹素值。 (深度纹理只有一个通道。)

  • 产生四元素向量,根据纹素的相对坐标将上一步产生的标量排列成分量,如下:

    • Result component Relative texel coordinate
      x (umin,vmax)
      y (umax,vmax)
      z (umax,vmin)
      w (umin,vmin)

TODO:四个纹素是 WebGPU 规范应描述的“样本足迹”。 https://github.com/gpuweb/gpuweb/issues/2343

fn textureGather(component: i32, t: texture_2d<T>, s: sampler, coords: vec2<f32>) -> vec4<T>
fn textureGather(component: i32, t: texture_2d<T>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<T>
fn textureGather(component: i32, t: texture_2d_array<T>, s: sampler, coords: vec2<f32>, array_index: i32) -> vec4<T>
fn textureGather(component: i32, t: texture_2d_array<T>, s: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> vec4<T>
fn textureGather(component: i32, t: texture_cube<T>, s: sampler, coords: vec3<f32>) -> vec4<T>
fn textureGather(component: i32, t: texture_cube_array<T>, s: sampler, coords: vec3<f32>, array_index: i32) -> vec4<T>
fn textureGather(t: texture_depth_2d, s: sampler, coords: vec2<f32>) -> vec4<f32>
fn textureGather(t: texture_depth_2d, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
fn textureGather(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: i32) -> vec4<f32>
fn textureGather(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> vec4<f32>
fn textureGather(t: texture_depth_cube, s: sampler, coords: vec3<f32>) -> vec4<f32>
fn textureGather(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: i32) -> vec4<f32>

参数:

component 仅适用于非深度纹理。
从所选纹素读取的通道索引。
When provided, the component expression must a creation-time expression (e.g. 1).
它的值必须至少为 0,最多为 3。 超出此范围的值将导致 shader-creation error
t The sampled or depth texture to read from.
s The sampler type.
coords 纹理坐标。
array_index 从 0 开始的纹理数组索引。
offset 在对纹理进行采样之前应用于未归一化纹理坐标的可选纹理像素偏移。 在应用任何纹理环绕模式之前应用此偏移量。
offset 表达式必须是:
  • 一个 const_expression 表达式(例如 vec2<i32>(1, 2))。

  • 一个 module-scope的 [=let declaration] 的名称 每个 offset 组件必须至少为 -8,最多为 7。 超出此范围的值将导致 shader-creation error

返回:

一个四元素向量,其分量从选定的纹素的指定通道中提取,如上所述。

EXAMPLE: Gather components from texels in 2D texture
@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);
}

16.8.3. textureGatherCompare

texture gather compare 操作对深度纹理中的四个纹素进行深度比较,并将结果收集到单个向量中,如下所示:

  • 找出将在线性过滤的深度采样操作中使用的四个纹素, 从 mip level 0:

    • 使用指定的坐标、数组索引(如果存在)和偏移量(如果存在)。

    • 当考虑纹理空间坐标 (u,v) 时,纹素是相邻的,形成一个正方形。

    • 在纹理边缘、立方体面边缘或立方体角处选择的纹素像在普通纹理采样中一样处理。

  • 对于每个纹素,执行与深度参考值的比较,产生 0.0 或 1.0 值,由比较采样器参数控制。

  • 产生四元素向量,其中分量是与具有相对纹素坐标的纹素的比较结果,如下所示:

    • Result component Relative texel coordinate
      x (umin,vmax)
      y (umax,vmax)
      z (umax,vmin)
      w (umin,vmin)
fn textureGatherCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> vec4<f32>
fn textureGatherCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> vec4<f32>
fn textureGatherCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32) -> vec4<f32>
fn textureGatherCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32, offset: vec2<i32>) -> vec4<f32>
fn textureGatherCompare(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> vec4<f32>
fn textureGatherCompare(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: i32, depth_ref: f32) -> vec4<f32>
Parameters:
t The depth texture to read from.
s The sampler comparison.
coords The texture coordinates.
array_index The 0-based texture array index.
depth_ref The reference value to compare the sampled depth value against.
offset 在对纹理进行采样之前应用于未归一化纹理坐标的可选纹理像素偏移。 在应用任何纹理环绕模式之前应用此偏移量。
The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
每个 offset 组件必须至少为 -8,最多为 7。 超出此范围的值将导致 shader-creation error

返回:

如上所述,具有所选纹素的比较结果的四元素向量。

EXAMPLE: Gather depth comparison
@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);
}

16.8.4. textureLoad

从纹理中读取单个纹素,无需采样或过滤。

fn textureLoad(t: texture_1d<T>, coords: i32, level: i32) -> vec4<T>
fn textureLoad(t: texture_2d<T>, coords: vec2<i32>, level: i32) -> vec4<T>
fn textureLoad(t: texture_2d_array<T>, coords: vec2<i32>, array_index: i32, level: i32) -> vec4<T>
fn textureLoad(t: texture_3d<T>, coords: vec3<i32>, level: i32) -> vec4<T>
fn textureLoad(t: texture_multisampled_2d<T>, coords: vec2<i32>, sample_index: i32)-> vec4<T>
fn textureLoad(t: texture_depth_2d, coords: vec2<i32>, level: i32) -> f32
fn textureLoad(t: texture_depth_2d_array, coords: vec2<i32>, array_index: i32, level: i32) -> f32
fn textureLoad(t: texture_depth_multisampled_2d, coords: vec2<i32>, sample_index: i32)-> f32
fn textureLoad(t: texture_external, coords: vec2<i32>) -> vec4<f32>

参数:

t sampledmultisampleddepth, 或者 external 纹理。
coords 基于 0 的纹素坐标。
array_index 从 0 开始的纹理数组索引。
level mip 级别,级别 0 包含纹理的全尺寸版本。
sample_index 多重采样纹理的基于 0 的样本索引。

返回:

未过滤的纹素数据。

如果发生越界访问:

  • coords 的任何元素都在相应元素的 [0, textureDimensions(t, level)) 范围之外,或

  • array_index 超出范围 [0, textureNumLayers(t)),或

  • level 超出范围 [0, textureNumLevels(t))

如果发生越界访问,内置函数返回以下之一:

  • 纹理边界内某些纹素的数据

  • 非深度纹理的适当类型的向量 (0,0,0,0) 或 (0,0,0,1)

  • 0.0 深度纹理

16.8.5. textureNumLayers

返回数组纹理的层数(元素数)。

fn textureNumLayers(t: texture_2d_array<T>) -> i32
fn textureNumLayers(t: texture_cube_array<T>) -> i32
fn textureNumLayers(t: texture_depth_2d_array) -> i32
fn textureNumLayers(t: texture_depth_cube_array) -> i32
fn textureNumLayers(t: texture_storage_2d_array<F,A>) -> i32

参数:

t sampledmultisampleddepthstorage 数组纹理。

返回:

数组纹理的层数(元素)。

16.8.6. textureNumLevels

返回纹理的 mip 级别数。

fn textureNumLevels(t: texture_1d<T>) -> i32
fn textureNumLevels(t: texture_2d<T>) -> i32
fn textureNumLevels(t: texture_2d_array<T>) -> i32
fn textureNumLevels(t: texture_3d<T>) -> i32
fn textureNumLevels(t: texture_cube<T>) -> i32
fn textureNumLevels(t: texture_cube_array<T>) -> i32
fn textureNumLevels(t: texture_depth_2d) -> i32
fn textureNumLevels(t: texture_depth_2d_array) -> i32
fn textureNumLevels(t: texture_depth_cube) -> i32
fn textureNumLevels(t: texture_depth_cube_array) -> i32

参数:

t sampleddepth 纹理。

返回:

纹理的 mip 级别数。

16.8.7. textureNumSamples

返回多重采样纹理中每个纹素的样本数。

textureNumSamples(t: texture_multisampled_2d<T>) -> i32
textureNumSamples(t: texture_depth_multisampled_2d) -> i32

参数:

t multisampled 纹理。

返回:

如果多重采样纹理中每个纹素的样本数。

多重采样纹理中每个纹素的采样数。

16.8.8. textureSample

采样纹理。

只能在 fragment 着色器阶段使用。 只能在 uniform control flow 中调用。

fn textureSample(t: texture_1d<f32>, s: sampler, coords: f32) -> vec4<f32>
fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>) -> vec4<f32>
fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
fn textureSample(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: i32) -> vec4<f32>
fn textureSample(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> vec4<f32>
fn textureSample(t: texture_3d<f32>, s: sampler, coords: vec3<f32>) -> vec4<f32>
fn textureSample(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, offset: vec3<i32>) -> vec4<f32>
fn textureSample(t: texture_cube<f32>, s: sampler, coords: vec3<f32>) -> vec4<f32>
fn textureSample(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: i32) -> vec4<f32>
fn textureSample(t: texture_depth_2d, s: sampler, coords: vec2<f32>) -> f32
fn textureSample(t: texture_depth_2d, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> f32
fn textureSample(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: i32) -> f32
fn textureSample(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> f32
fn textureSample(t: texture_depth_cube, s: sampler, coords: vec3<f32>) -> f32
fn textureSample(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: i32) -> f32

参数:

t sampled, depth, 或 external 纹理采样.
s The sampler type.
coords 用于采样的纹理坐标。
array_index 要采样的基于 0 的纹理数组索引。
offset 在对纹理进行采样之前应用于非标准化纹理坐标的可选纹理像素偏移。 在应用任何纹理环绕模式之前应用此偏移量.
The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
每个“偏移”分量必须至少为“-8”,最多为“7”。 超出此范围的值将导致 shader-creation error

返回:

采样值。

16.8.9. textureSampleBias

对具有 mip 级别偏差的纹理进行采样。

只能在 fragment 着色器阶段使用。 只能在 uniform control flow 中调用.

fn textureSampleBias(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, bias: f32) -> vec4<f32>
fn textureSampleBias(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, bias: f32, offset: vec2<i32>) -> vec4<f32>
fn textureSampleBias(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: i32, bias: f32) -> vec4<f32>
fn textureSampleBias(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: i32, bias: f32, offset: vec2<i32>) -> vec4<f32>
fn textureSampleBias(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32>
fn textureSampleBias(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, bias: f32, offset: vec3<i32>) -> vec4<f32>
fn textureSampleBias(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32>
fn textureSampleBias(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: i32, bias: f32) -> vec4<f32>

参数:

t 要采样的 texture
s sampler type.
coords 用于采样的纹理坐标。
array_index 要采样的基于 0 的纹理数组索引。
bias 采样前应用于 mip 级别的偏差。 bias 必须介于 -16.015.99 之间。
offset 在对纹理进行采样之前应用于未归一化纹理坐标的可选纹理像素偏移。 在应用任何纹理环绕模式之前应用此偏移量。
The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
每个 offset 组件必须最少为 -8,最多为 7。 超出此范围的值将导致 shader-creation error

返回:

采样值。

16.8.10. textureSampleCompare

对深度纹理进行采样并将采样的深度值与参考值进行比较。

只能在 fragment 着色器阶段使用。 只能在 uniform control flow 中调用。

fn textureSampleCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32
fn textureSampleCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32
fn textureSampleCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32) -> f32
fn textureSampleCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32, offset: vec2<i32>) -> f32
fn textureSampleCompare(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32
fn textureSampleCompare(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: i32, depth_ref: f32) -> f32

参数:

t 要采样的 depth 纹理。
s sampler comparision 类型。
coords 用于采样的纹理坐标。
array_index 要采样的基于 0 的纹理数组索引。
depth_ref 用于比较采样深度值的参考值。
offset 在对纹理进行采样之前应用于未归一化纹理坐标的可选纹理像素偏移。 在应用任何纹理环绕模式之前应用此偏移量。
The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
每个 offset 组件必须至少为 -8,最多为 7。 超出此范围的值将导致 shader-creation error

返回:

[0.0..1.0] 范围内的一个值。

使用 sampler_comparison,定义的比较运算符将每个采样的纹素与参考值进行比较,从而为每个纹素生成“0”或“1”值。

如果采样器使用双线性过滤,则返回值是这些值的过滤平均值,否则返回单个纹素的比较结果。

16.8.11. textureSampleCompareLevel

对深度纹理进行采样并将采样的深度值与参考值进行比较。

fn textureSampleCompareLevel(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32
fn textureSampleCompareLevel(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32
fn textureSampleCompareLevel(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32) -> f32
fn textureSampleCompareLevel(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32, offset: vec2<i32>) -> f32
fn textureSampleCompareLevel(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32
fn textureSampleCompareLevel(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: i32, depth_ref: f32) -> f32

textureSampleCompareLevel 函数与 textureSampleCompare 相同,除了:

  • textureSampleCompareLevel 总是从 mip 级别 0 采样纹素。

    • 该函数不计算导数。

    • There is no requirement for textureSampleCompareLevel to be invoked in uniform control flow.

  • textureSampleCompareLevel 可以在任何着色器阶段调用。

16.8.12. textureSampleGrad

使用显式渐变对纹理进行采样。

fn textureSampleGrad(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32>
fn textureSampleGrad(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
fn textureSampleGrad(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: i32, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32>
fn textureSampleGrad(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: i32, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
fn textureSampleGrad(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32>
fn textureSampleGrad(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>, offset: vec3<i32>) -> vec4<f32>
fn textureSampleGrad(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32>
fn textureSampleGrad(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: i32, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32>

参数:

t 要采样的 texture
s sampler type.
coords 用于采样的纹理坐标。
array_index 要采样的基于 0 的纹理数组索引。
ddx 用于计算采样位置的 x 方向导数向量。
ddy 用于计算采样位置的 y 方向导数向量。
offset 在对纹理进行采样之前应用于未归一化纹理坐标的可选纹理像素偏移。 在应用任何纹理环绕模式之前应用此偏移量。
The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
每个 offset 组件必须至少为 -8,最多为 7。 超出此范围的值将导致 shader-creation error

返回:

采样值。

16.8.13. textureSampleLevel

使用显式 mip 级别或 mip 级别 0 对纹理进行采样。

fn textureSampleLevel(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, level: f32) -> vec4<f32>
fn textureSampleLevel(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, level: f32, offset: vec2<i32>) -> vec4<f32>
fn textureSampleLevel(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: i32, level: f32) -> vec4<f32>
fn textureSampleLevel(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: i32, level: f32, offset: vec2<i32>) -> vec4<f32>
fn textureSampleLevel(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, level: f32) -> vec4<f32>
fn textureSampleLevel(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, level: f32, offset: vec3<i32>) -> vec4<f32>
fn textureSampleLevel(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, level: f32) -> vec4<f32>
fn textureSampleLevel(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: i32, level: f32) -> vec4<f32>
fn textureSampleLevel(t: texture_depth_2d, s: sampler, coords: vec2<f32>, level: i32) -> f32
fn textureSampleLevel(t: texture_depth_2d, s: sampler, coords: vec2<f32>, level: i32, offset: vec2<i32>) -> f32
fn textureSampleLevel(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: i32, level: i32) -> f32
fn textureSampleLevel(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: i32, level: i32, offset: vec2<i32>) -> f32
fn textureSampleLevel(t: texture_depth_cube, s: sampler, coords: vec3<f32>, level: i32) -> f32
fn textureSampleLevel(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: i32, level: i32) -> f32
fn textureSampleLevel(t: texture_external, s: sampler, coords: vec2<f32>) -> vec4<f32>

参数:

t 要采样的 sampleddepth 纹理。
s sampler type.
coords 用于采样的纹理坐标。
array_index 要采样的基于 0 的纹理数组索引。
level mip 级别,级别 0 包含纹理的全尺寸版本。 对于 levelf32 的函数,如果格式是可过滤的,则可以在两个级别之间插入小数值根据 Texture Format Capabilities
如果未指定,则对 mip 级别 0 进行采样。
offset 在对纹理进行采样之前应用于未归一化纹理坐标的可选纹理像素偏移。 在应用任何纹理环绕模式之前应用此偏移量。
The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
每个 offset 组件必须至少为 -8,最多为 7。 超出此范围的值将导致 shader-creation error

返回:

采样值。

16.8.14. textureStore

将单个纹素写入纹理。

fn textureStore(t: texture_storage_1d<F,write>, coords: i32, value: vec4<T>)
fn textureStore(t: texture_storage_2d<F,write>, coords: vec2<i32>, value: vec4<T>)
fn textureStore(t: texture_storage_2d_array<F,write>, coords: vec2<i32>, array_index: i32, value: vec4<T>)
fn textureStore(t: texture_storage_3d<F,write>, coords: vec3<i32>, value: vec4<T>)

通道格式“T”取决于存储纹素格式“F”。 【查看texel格式表】(#storage-texel-formats) 有关texel格式到通道格式的映射。

参数:

t write-only storage texture.
coords 基于 0 的纹素坐标。
array_index 从 0 开始的纹理数组索引。
value 新的纹素值。

注意:

如果出现以下情况,则会发生越界访问:

  • coords 的任何元素都在相应元素的 [0, textureDimensions(t)) 范围之外,或者

  • array_index 超出了 [0, textureNumLayers(t)) 的范围

如果发生越界访问,内置函数可以执行以下任一操作:

  • 不被执行

  • value 存储到一些边界 texel

16.9. 原子内置函数

原子内置函数可用于 读/写/读-修改-写 原子 对象。 它们是 § 4.3.7 原子类型 上唯一允许的操作。

所有原子内置函数都使用一个 relaxed memory ordering0-SPIR-V 中所有 Memory Semantics 操作数的整数常量)。 这意味着同步和排序保证仅适用于作用于相同 Memory locations 的原子操作。 原子和非原子内存访问之间或作用于不同内存位置的原子访问之间没有同步或排序保证。

原子内置函数“不得”用在 vertex 着色器阶段。

所有原子内置函数 atomic_ptr 参数的存储类 SC 必须是storageworkgroupworkgroup 原子在 SPIR-V 中具有 Workgroup 内存范围,而 storage 原子在 SPIR-V 中具有 Queue Family 内存范围。

所有原子内置函数中的访问模式 A 必须是read_write

16.9.1. 原子负载

fn atomicLoad(atomic_ptr: ptr<SC, atomic<T>, A>) -> T

返回原子加载的 atomic_ptr 指向的值。 它不原子修改|修改 modify 对象。

16.9.2. 原子商店

fn atomicStore(atomic_ptr: ptr<SC, atomic<T>, A>, v: T)

以原子方式将值 v 存储在 atomic_ptr 指向的原子对象中。

16.9.3. 原子 读-修改-写

fn atomicAdd(atomic_ptr: ptr<SC, atomic<T>, A>, v: T) -> T
fn atomicSub(atomic_ptr: ptr<SC, atomic<T>, A>, v: T) -> T
fn atomicMax(atomic_ptr: ptr<SC, atomic<T>, A>, v: T) -> T
fn atomicMin(atomic_ptr: ptr<SC, atomic<T>, A>, v: T) -> T
fn atomicAnd(atomic_ptr: ptr<SC, atomic<T>, A>, v: T) -> T
fn atomicOr(atomic_ptr: ptr<SC, atomic<T>, A>, v: T) -> T
fn atomicXor(atomic_ptr: ptr<SC, atomic<T>, A>, v: T) -> T
每个函数原子地执行以下步骤:

1.加载 atomic_ptr 指向的原始值。

  1. 通过对函数名称执行操作(例如 max)来获得一个新值,其值为 v

  2. 使用 atomic_ptr 存储新值。

每个函数返回存储在原子对象中的原始值。

fn atomicExchange(atomic_ptr: ptr<SC, atomic<T>, A>, v: T) -> T

以原子方式将值 v 存储在指向 atomic_ptr 的原子对象中,并返回存储在原子对象中的原始值。

fn atomicCompareExchangeWeak(atomic_ptr: ptr<SC, atomic<T>, A>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>

struct __atomic_compare_exchange_result<T> {
  old_value : T; // old value stored in the atomic
  exchanged : bool; // true if the exchange was done
}

// Maps to the SPIR-V instruction OpAtomicCompareExchange.

注意:不能使用类型 __compare_exchange_result 显式声明值,但值可以推断类型。

以原子方式执行以下步骤:

  1. 加载 atomic_ptr 指向的原始值。

  2. 使用相等运算将原始值与值 v 进行比较。

  3. 仅当相等比较的结果为 true 时才存储值 v

返回一个双成员结构,其中第一个成员 old_value 是原子对象的原始值,第二个成员 exchanged 是比较是否成功。

注意:在某些实现中,相等比较可能会失败。 也就是说,即使结果向量的第一个元素等于 cmp,结果向量的第二个元素也可能是 false

16.10. 数据打包内置函数

数据打包内置函数可用于使用与 WGSL 中的类型不直接对应的数据格式对值进行编码。 这使程序能够将许多密集打包的值写入内存,从而减少着色器的内存带宽需求。

结论 解释
@const 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 结果。
@const 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 结果。
@const 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 结果。
@const 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 结果。
@const fn pack2x16float(e: vec2<f32>) -> u32 将两个浮点值转换为半精度浮点数,然后将它们合并为一个 u32 值。
输入的分量 e[i] 被转换为 IEEE-754 binary16 值,然后将其放置在位 16 × i 通过 16 × i + 15 结果。 查看 § 12.5.2 浮点数转换.

16.11. 数据解包内置函数

数据解包内置函数可用于解码与 WGSL 中的类型不直接对应的数据格式中的值。 这使程序能够从内存中读取许多密集打包的值,从而减少着色器的内存带宽需求。

结论 解释
@const fn unpack4x8snorm(e: u32) -> vec4<f32> 将 32 位值分解为四个 8 位块,然后将每个块重新解释为有符号的标准化浮点值。
组件 i 结果的最大值是 max(v ÷ 127, -1),其中 v 是位 8×i 的解释 通过 8×i+7 的 e 作为二进制补码的有符号整数。
@const fn unpack4x8unorm(e: u32) -> vec4<f32> 将 32 位值分解为四个 8 位块,然后将每个块重新解释为无符号标准化浮点值。
组件 i 结果是v ÷ 255,其中v 是位 8×i 的解释 通过 8×i+7 的 e 作为无符号整数。
@const fn unpack2x16snorm(e: u32) -> vec2<f32> 将 32 位值分解为两个 16 位块,然后将每个块重新解释为有符号的标准化浮点值。
组件 i 结果的最大值是 max(v ÷ 32767, -1), 其中 v 是位 16×i 的解释 通过 e 的 16×i+15 作为二进制补码的有符号整数。
@const fn unpack2x16unorm(e: u32) -> vec2<f32> 将 32 位值分解为两个 16 位块,然后将每个块重新解释为无符号标准化浮点值。
组件 i 结果是v ÷ 65535,其中v 是位 16×i 的解释 通过 e 的 16×i+15 作为无符号整数。
@const fn unpack2x16float(e: u32) -> vec2<f32> 将 32 位值分解为两个 16 位块,并将每个块重新解释为浮点值。
组件 i 结果的 f32 表示 v,其中 v 是位 16×i 的解释 通过 e 的 16×i+15 作为 IEEE-754 binary16 值。 查看 § 12.5.2 浮点数转换

16.12. 同步内置函数

WGSL 提供以下同步函数:

fn storageBarrier()
fn workgroupBarrier()

所有同步函数都执行一个 control barrier 和 Acquire/Release memory ordering。 也就是说,所有的同步函数,以及受影响的内存和原子操作都按照 program order 相对于同步函数进行排序。 此外,在同步功能由工作组成员执行之后的任何受影响的内存或原子操作程序排序之前,在同步功能之前受影响的内存和原子操作必须对工作组中的所有其他线程可见。 所有同步函数都使用 Workgroup memory scope。 所有同步功能都有一个“工作组”execution scope。 所有同步函数只能在 compute 着色器阶段使用。

storageBarrier 影响 storage 存储类中的内存和原子操作。

workgroupBarrier 影响 workgroup 存储类中的内存和原子操作。

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

Index

Terms defined by this specification