17. WebGPU WGSL

有关 WGSL 的深入概述,请参阅 WGSL 之旅。还有实际的 WGSL 规范,尽管它可能很难理解,因为它是为语言律师编写的 😂

本文假设您已经知道如何编程。它可能过于简洁,但希望它可以帮助您理解和编写 WGSL 着色器程序。

WGSL 是严格类型化的

与 JavaScript 不同,WGSL 需要了解每个变量、结构字段、函数参数和函数返回类型的类型。如果你使用过 typescript、rust、C++、C#、Java、Swift、Kotlin 等,那么你已经习惯了。

普通类型(plain types)

WGSL 中的普通类型是

  • i32 a 32 bit signed integer
  • u32 a 32 bit unsigned integer
  • f32 a 32 bit floating point number
  • bool a boolean value
  • f16 a 16 bit floating point number (this is an optional feature you
    need to check for and request)

变量声明(variable declaration)

在 JavaScript 中,您可以像这样声明变量和函数

var a = 1;
let c = 3;
function d(e) { return e * 2; }

在 WGSL 中,这些的完整形式是

var a: f32 = 1;
let c: f32 = 3;
fn d(e: f32) -> f32 { return e * 2; }

上面要注意的重要一点是必须为变量声明添加 : <type> ,如 : f32 ,为函数声明添加 -> <type>

自动类型(auto types)

WGSL 有一个声明变量的快捷方式。类似于typescript,如果不声明变量的类型,那么它会自动成为右边表达式的类型

fn foo() -> bool { return false; }
 
var a = 1;     // a is an i32
let b = 2.0;   // b is an f32
var c = 3u;    // c is a u32
var d = foo(); // d is bool

类型转换( type conversion)

此外,严格类型意味着您经常必须转换类型

let a = 1;     // a is an i32
let b = 2.0;   // b is a f32
let c = a + b; // ERROR can't add an i32 to an f32

解决方法是将一个转换为另一个

let a = 1;     // a is an i32
let b = 2.0;   // b is a f32
let c = f32(a) + b; // ok

但是!,WGSL 有所谓的“AbstractInt”和“AbstractFloat”。您可以将它们视为尚未确定其类型的数字。这些只是编译时的特性。

let a = 1;            // a is an i32
let b = 2.0;          // b is a f32
let c = a + b;       // ERROR can't add an i32 to an f32
let d = 1 + 2.0;      // d is a f32

数字的后缀 (numeric suffixes )

2i   // i32
3u   // u32
4f   // f32
4.5f // f32
5h   // f16
5.6h // f16
6    // AbstractInt
7.0  // AbstractFloat

let var 和 const 在 WGSL 和 JavaScript 中的含义不同

在 JavaScript 中, var 是一个具有函数作用域的变量。 let 是块作用域的变量。 const 是一个常量变量(不能改变) [注释1] 具有块作用域。

在 WGSL 中,所有变量都具有块作用域。 var 是一个具有存储空间的变量,因此是可变的。 let 是一个常数值

fn foo() {
  let a = 1;
  a = a + 1;  // ERROR: a is a constant expression
  var b = 2;
  b = b + 1;  // ok
}

const 不是变量,它是编译时常量。不能将 const 用于运行时发生的事情。

const one = 1;              // ok
const two = one * 2;        // ok
const PI = radians(180.0);  // ok
 
fn add(a: f32, b: f32) -> f32 {
  const result = a + b;   // ERROR! const can only be used with compile time expressions
  return result;
}

向量类型(vector types)

WGSL 有 3 种向量类型 vec2 、 vec3 和 vec4 。他们的基本风格是 vec? 所以 vec2 (两个 i32 的向量), vec3 (3 个 f32 的向量), vec4 (4 个 u32 的向量), vec3 3 个布尔值的向量.

例子:

let a = vec2<i32>(1, -2);
let b = vec3<f32>(3.4, 5.6, 7.8);
let c = vec4<u32>(9, 10);

访问向量的分量(accessors )

可以使用各种访问器访问向量中的分量

let a = vec4<f32>(1, 2, 3, 4);
let b = a.z;   // via x,y,z,w
let c = a.b;   // via r,g,b,a
let d = a[2];  // via array element accessors

以上, b 、 c 、 d 都是一样的。他们都在访问 a 的第三个元素。

访问向量的组合分量(swizzles)

您还可以访问 1 个以上的元素。

let a = vec4<f32>(1, 2, 3, 4);
let b = a.zx;   // via x,y,z,w
let c = a.br;   // via r,g,b,a
let d = vec2<f32>(a[2], a[0]);

以上, b 、 c 、 d 都是一样的。

你也可以重复访问元素

let a = vec4<f32>(1, 2, 3, 4);
let b = vec3<f32>(a.z, a.z, a.y);
let c = a.zzy;

以上 b 和 c 是一样的。他们都是 vec3 ,内容是 3, 3, 2

矢量简写(vector shortcuts)

基本类型有快捷方式。将 => i 、 => f 、 更改为 u 并将 更改为 h 所以

let a = vec4<f32>(1, 2, 3, 4);
let b = vec4f(1, 2, 3, 4);

a 和 b 是同一类型

构造向量(vector construction )

向量可以用更小的类型构造

let a = vec4f(1, 2, 3, 4);
let b = vec2f(2, 3);
let c = vec4f(1, b, 4);
let d = vec2f(1, a.yz, 4);
let e = vec2f(a.xyz, 4);
let f = vec2f(1, a.yzw);

a 、 c 、 d 、 e 和 f 相同。

向量数学 (vector math )

可以对向量进行数学运算

let a = vec4f(1, 2, 3, 4);
let b = vec4f(5, 6, 7, 8);
let c = a + b;  // c is vec4f(6, 8, 10, 12)
let d = a * b;  // d is vec4f(5, 12, 21, 32)
let e = a - b;  // e is vec4f(-4, -4, -4, -4)

许多函数也适用于向量

let a = vec4f(1, 2, 3, 4);
let b = vec4f(5, 6, 7, 8);
let c = mix(a, b, 0.5);                   // c is vec4f(3, 4, 5, 6)
let d = mix(a, b, vec4f(0, 0.5, 0.5, 1)); // d is vec4f(1, 4, 5, 8)

矩阵 (matrices )

WGSL 有一堆矩阵类型。矩阵是向量数组。格式为 mat<numVectors>x<vectorSize><<type>> ,例如 mat3x4 是一个包含 3 个 vec4<32> 的数组。和向量一样,矩阵也有相同的简写

let a: mat4x4<f32> = ...
let b: mat4x4f = ...

a 和 b 是同一类型

矩阵向量访问 (matrix vector access )

您可以使用数组语法引用矩阵的向量

let a = mat4x4f(...);
let b = a[2];   // b is a vec4f of the 3rd vector of a

最常见的 3D 计算矩阵类型是 mat4x4f ,可以直接与 vec4f 相乘产生另一个 vec4f

let a = mat4x4f(....);
let b = vec4f(1, 2, 3, 4);
let c = a * b;  // c is a vec4f and the result of a * b

数组(arrays )

WGSL 中的数组使用 array<type, numElements> 语法声明

let a = array<f32, 5>;   // an array of five f32s
let b = array<vec4f, 6>; // an array of six vec4fs

运行时确定大小的数组 (runtime sized arrays )

位于根作用域存储声明中的数组是唯一可以指定没有大小的数组

@group(0) @binding(0) var<storage> foo: array<mat4x4f>;

foo 中的元素数量由运行时使用的绑定组的设置定义

函数(functions )

WGSL 中的函数遵循fn name(parameters) -> returnType { ..body... }模式。

fn add(a: f32, b: f32) -> f32 {
  return a + b;
}

入口点(entry points )

WGSL 程序需要一个入口点。入口点由 @vertex @fragment @compute指定

@vertex fn myFunc(a: f32, b: f32) -> @builtin(position): vec4f {
  return vec4f(0, 0, 0, 0);
}

着色器仅使用其入口点访问的内容

@group(0) @binding(0) var<uniforms> uni: vec4f;
 
vec4f fn foo() {
  return uni;
}
 
@vertex fn vs1(): @builtin(position) vec4f {
  return vec4f(0);
}
 
@vertex fn vs2(): @builtin(position) vec4f {
  return foo();
}

Above uni is not accessed by vs1 and so will not show up as a required binding if you use vs1 in a pipeline. vs2 does reference uni indirectly through calling foo so it will show up as a required binding when using vs2 in a pipeline.

vs1 无法访问以上 uni ,因此如果您在管道中使用 vs1 ,则不会显示为必需的绑定。 vs2 确实通过调用 foo 间接引用了 uni ,因此在管道中使用 vs2 时它将显示为必需的绑定。

属性 (attributes)

The word attributes has 2 means in WebGPU. One is vertex attributes which is covered in the article on vertex buffers. The other is in WGSL where an attribute starts with @.

属性在 WebGPU 中有 2 种含义。一个是顶点属性,这在顶点缓冲区一文中有所介绍。另一个是在 WGSL 中,其中属性以 @ 开头。

@location(number) 用于定义着色器的输入和输出。

顶点着色器输入 (vertex shader inputs)

对于顶点着色器,输入由顶点着色器的入口点函数的 @location 属性定义

@vertex vs1(@location(0) foo: f32, @location(1) bar: vec4f) ...
 
struct Stuff {
  @location(0) foo: f32,
  @location(1) bar: vec4f,
};
@vertex vs2(s: Stuff) ...

vs1 和 vs2 都在需要由顶点缓冲区提供的位置 0 和 1 上声明顶点着色器的输入。

阶段间变量(inter stage variables)

对于阶段间变量, @location 属性定义变量在着色器之间传递的位置

struct VSOut {
  @builtin(position) pos: vec4f,
  @location(0) color: vec4f,
  @location(1) texcoords: vec2f,
};
 
struct FSIn {
  @location(1) uv: vec2f,
  @location(0) diffuse: vec4f,
};
 
@vertex fn foo(...) -> VSOut { ... }
@fragment fn bar(moo: FSIn) ... 

Above, the vertex shader foo passes color as vec4f on location(0) and texcoords as a vec2f on location(1). The fragment shader bar receives them as uv and diffuse because their locations match.
上面,顶点着色器 foo 在 location(0) 上将 color 作为 vec4f 传递,将 texcoords 作为 location(1) 上的 vec2f 传递。片段着色器 bar 将它们接收为 uv 和 diffuse ,因为它们的位置匹配。

片段着色器输出 (fragment shader outputs )

对于片段着色器, @location 指定将结果存储在哪个 GPURenderPassDescriptor.colorAttachment 中。

struct FSOut {
  @location(0) albedo: vec4f;
  @location(1) normal: vec4f;
}
@fragment fn bar(...) -> FSOut { ... }

@builtin(name)

@builtin 属性用于指定特定变量的值来自 WebGPU 的内置功能

@vertex fn vs1(@builtin(vertex_index) foo: u32, @builtin(instance_index) bar: u32) ... {
  ...
}

上面的 foo 从内置的 vertex_index 获取它的值, bar 从 instance_index 获取它的值

struct Foo {
  @builtin(vertex_index) vNdx: u32,
  @builtin(instance_index) iNdx: u32,
}
@vertex fn vs1(blap: Foo) ... {
  ...
}

上面的 blap.vNdx 从内置的 vertex_index 获取它的值, blap.iNdxinstance_index 获取它的值

Builtin NameStageIOTypeDescription
vertex_indexvertexinputu32Index of the current vertex within the current API-level draw command, independent of draw instancing.For a non-indexed draw, the first vertex has an index equal to the firstVertex argument of the draw, whether provided directly or indirectly. The index is incremented by one for each additional vertex in the draw instance.For an indexed draw, the index is equal to the index buffer entry for the vertex, plus the baseVertex argument of the draw, whether provided directly or indirectly.
instance_indexvertexinputu32Instance index of the current vertex within the current API-level draw command.The first instance has an index equal to the firstInstance argument of the draw, whether provided directly or indirectly. The index is incremented by one for each additional instance in the draw.
positionvertexoutputvec4Output position of the current vertex, using homogeneous coordinates. After homogeneous normalization (where each of the x, y, and z components are divided by the w component), the position is in the WebGPU normalized device coordinate space. See WebGPU § 3.3 Coordinate Systems.
fragmentinputvec4Framebuffer position of the current fragment in framebuffer space. (The x, y, and z components have already been scaled such that w is now 1.) See WebGPU § 3.3 Coordinate Systems.
front_facingfragmentinputboolTrue when the current fragment is on a front-facing primitive. False otherwise.
frag_depthfragmentoutputf32Updated depth of the fragment, in the viewport depth range. See WebGPU § 3.3 Coordinate Systems.
local_invocation_idcomputeinputvec3The current invocation’s local invocation ID, i.e. its position in the workgroup grid.
local_invocation_indexcomputeinputu32The current invocation’s local invocation index, a linearized index of the invocation’s position within the workgroup grid.
global_invocation_idcomputeinputvec3The current invocation’s global invocation ID, i.e. its position in the compute shader grid.
workgroup_idcomputeinputvec3The current invocation’s workgroup ID, i.e. the position of the workgroup in the workgroup grid.
num_workgroupscomputeinputvec3The dispatch size, vec<u32>(group_count_x, group_count_y, group_count_z), of the compute shader dispatched by the API.
sample_indexfragmentinputu32Sample index for the current fragment. The value is least 0 and at most sampleCount-1, where sampleCount is the MSAA sample [count](https://www.w3.org/TR/webgpu/#dom-gpumultisamplestate-count) specified for the GPU render pipeline.
See WebGPU § 10.3 GPURenderPipeline.
sample_maskfragmentinputu32Sample coverage mask for the current fragment. It contains a bitmask indicating which samples in this fragment are covered by the primitive being rendered.
See WebGPU § 23.3.11 Sample Masking.
fragmentoutputu32Sample coverage mask control for the current fragment. The last value written to this variable becomes the shader-output mask. Zero bits in the written value will cause corresponding samples in the color attachments to be discarded.
See WebGPU § 23.3.11 Sample Masking.

flow control 流量控制

for

  for (var i = 0; i < 10; i++) { ... }

if

    if (i < 5) {
      ...
    } else if (i > 7) {
      ..
    } else {
      ...
    }

while

  var j = 0;
  while (j < 5) {
    ...
    j++;
  }

loop

  var k = 0;
  loop {
    k++;
    if (k >= 5) {
      break;
    }
  }

break

  var k = 0;
  loop {
    k++;
    if (k >= 5) {
      break;
    }
  }

break if

  var k = 0;
  loop {
    k++;
    break if (k >= 5);
  }

continue

  for (var i = 0; i < 10; ++i) {
    if (i % 2 == 1) {
      continue;
    }
    ...
  }

continuing

  for (var i = 0; i < 10; ++i) {
    if (i % 2 == 1) {
      continue;
    }
    ...
 
    continuing {
      // continue goes here
      ...
    }
  }

discard

   if (v < 0.5) {
     discard;
   }

discard 退出着色器。它只能在片段着色器中使用

switch

var a : i32;
let x : i32 = generateValue();
switch x {
  case 0: {      // The colon is optional
    a = 1;
  }
  default {      // The default need not appear last
    a = 2;
  }
  case 1, 2, {   // Multiple selector values can be used
    a = 3;
  }
  case 3, {      // The trailing comma is optional
    a = 4;
  }
  case 4 {
    a = 5;
  }
}

switch only works with u32 or i32 and cases must be constants.
switch 仅适用于 u32 或 i32 ,并且大小写必须是常量。

操作符( Operators )

NameOperatorsAssociativityBinding
Parenthesized(...)
Primarya(), a[], a.bLeft-to-right
Unary-a, !a, ~a, *a, &aRight-to-leftAll above
Multiplicativea * b, a / b, a % bLeft-to-rightAll above
Additivea + b, a - bLeft-to-rightAll above
Shifta << b, a >> bRequires parenthesesUnary
Relationala < b, a > b, a <= b, a >= b, a == b, a != bRequires parenthesesAll above
Binary ANDa & bLeft-to-rightUnary
Binary XORa ^ bLeft-to-rightUnary
Binary ORa | bLeft-to-rightUnary
Short-circuit ANDa && bLeft-to-rightRelational
Short-circuit ORa | bLeft-to-rightRelational

内置函数(Builtin functions )

See the WGSL Function reference.
请参阅 WGSL 函数参考。

与其他语言的差异

if 、 while 、 switch 、 break-if 表达式不需要括号。

if a < 5 {
  doTheThing();
}

没有三元运算符

许多语言都有三元运算符 condition ? trueExpression : falseExpression WGSL 没有。 WGSL确实有 select

  let a = select(falseExpression, trueExpression, condition);

++ 和 – 是语句,不是表达式。

Many languages have pre-increment and post-increment operators
许多语言都有前置和后置运算符

let a = 5;
let b = a++;  // b = 5, a = 6  (post-increment)
let c = ++a;  // c = 7, a = 7  (pre-increment)

WGSL 两者都没有。它只有增量和减量语句

var a = 5;
a++;          // is now 6
++a;          // ERROR: no such thing has pre-increment
let b = a++;  // ERROR: a++ is not an expression, it's a statement

+= , -= 不是表达式,它们是赋值语句

let a = 5;
a += 2;          // a = 7
let b = a += 2;  // a = 9, b = 9
let a = 5;
a += 2;           // a is 7
let b = a += 2;  // ERROR: a += 2 is not an expression

Swizzles 不能出现在左边

在某些语言中可以,但 WGSL不能

var color = vec4f(0.25, 0.5, 0.75, 1);
color.rgb = color.bgr; // ERROR
color = vec4(color.bgr, 1);  // Ok

Phony assignment to _

_ 是一个特殊的变量,您可以分配给它来使某些东西看起来已被使用但实际上并未使用它。

@group(0) @binding(0) var<uniforms> uni1: vec4f;
@group(0) @binding(0) var<uniforms> uni2: mat4x4f;
 
@vertex fn vs1(): @builtin(position) vec4f {
  return vec4f(0);
}
 
@vertex fn vs2(): @builtin(position) vec4f {
  _ = uni1;
  _ = uni2;
  return vec4f(0);
}

以上 uni1 和 uni2 都不会被 vs1 访问,因此如果您在管道中使用 vs1 ,它们将不会显示为必需的绑定。 vs2 确实同时引用了 uni1 和 uni2 ,因此在管道中使用 vs2 时,它们都将显示为必需的绑定。


注释1

JavaScript 中的变量包含 undefined 、 null 、 boolean 、 number 、 string 、 reference-to-object 的基本类型。对于刚接触编程的人来说,const o = {name: 'foo'}; o.name = 'bar'; 起作用可能会造成混淆,因为 o 被声明为 const 。问题是, o 是常量。它是对对象的常量引用。您不能更改 o 引用的对象。您可以更改对象本身。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值