在读规范之前,我们要先有个整体概念:WGSL有变量和值的概念, 变量 和 值 都有名称,不像GLSL(类c/c++语法)中变量是一个具名的、可供程序操作的储存空间, WGSL中对值和变量做了区分。 WGSL值是在运行时就确定下来,固定不变的,而变量是在根据上下文动态改变的,它指向存储位置,具有访问空间、存储类型和访问模式。let a:int32 = 45, a 是值, 而 var b:int32 = 45 中的 b 是变量,可以修改其值。建议想要了解WGSL语法的,最好读WGSL最新规范,老的规范很多地方解释的含糊其辞,跟天书似的,最新规范讲的清晰,简单明了了。
变量 Variable和值 Value声明为数据值提供名称。
值声明(value declaration)为值创建了一个名称,这个值一经声明就不可变。四种类型的值声明是const、override、let和正式参数声明。
变量声明(variable declaration)为存储值的内存位置创建了一个名称;如果变量具有read_write访问模式,则存储在其中的值可能会更新。有一种变量声明,var,但它有地址空间和各种组合访问模式的选项。
注意:
规范里的意思是:WGSL 不同于GLSL 类c/c++ 语法, GLSL中变量声明只是值的名字, 但在WGSL 中 值有名字,这个名字是否关联存储位置可以分为不可修改的值 或 可修改的值(也就是变量),不可修改的变量叫 value, 可修改的变量是 variable。
值声明没有关联的内存位置。例如,任何WGSL表达式都不能构成指向值的指针。
出现在任何函数定义之外的声明都属于模块作用域。它的名字在整个程序(entire program)的作用域中。
变量和值声明的语法大体类似:
// Specific value declarations.
const name [: type] = initializer ;
[attribute] override name [: type] [= initializer];
let name [: type] = initializer ;
// General variable form.
[attribute]* var[<address_space[, access_mode]>] name [: type] [= initializer];
// Specific variable declarations.
// Function scope.
var[<function>] name [: type] [= initializer];
// Module scope.
var<private> name [: type] [= initializer];
var<workgroup> name : type;
[attribute]+ var<uniform> name : type;
[attribute]+ var name : texture_type;
[attribute]+ var name : sampler_type;
[attribute]+ var<storage[, access_mode]> name : type;
变量声明是WGSL程序中唯一的可变数据。值声明总是不可变的。变量可以是引用值和指针值的基础,因为变量有关联的内存位置,而值声明不能是指针或引用值的基础。
使用变量通常比使用值声明的开销更大,因为使用变量需要额外的操作来读写与变量相关的内存位置。
一般来说,作者应该按照以下顺序使用声明,最喜欢的选项列在前面:
const-declaration
override-declaration
let-declaration
variable-declaration
这通常会导致着色器的最佳整体性能。
const 声明
const值名字在创建着色器时被确定,每个const声明都需要一个初始化方法。const声明可以在模块作用域中声明,也可以在函数作用域中声明。初始化表达式必须是const表达式。const声明的类型必须是具体或抽象的可构造类型。只有const声明的有效值类型可以是抽象类型。
EXAMPLE: CONST-DECLARATIONS AT MODULE SCOPE
const a = 4; // AbstractInt with a value of 4.
const b : i32 = 4; // i32 with a value of 4.
const c : u32 = 4; // u32 with a value of 4.
const d : f32 = 4; // f32 with a value of 4.
const e = vec3(a, a, a); // vec3 of AbstractInt with a value of (4, 4, 4).
const f = 2.0; // AbstractFloat with a value of 2.
const g = mat2x2(a, f, a, f); // mat2x2 of AbstractFloat with a value of:
// ((4.0, 2.0), (4.0, 2.0)).
// The AbstractInt a converts to AbstractFloat.
// An AbstractFloat cannot convert to AbstractInt.
const h = array(a, f, a, f); // array of AbstractFloat with 4 components:
// (4.0, 2.0, 4.0, 2.0).
override 声明
// 用的比较少,暂且搁置
let 声明
let声明为值指定一个名称,该名称在每次语句运行执行时被确定。let声明只能在函数作用域中声明,因此是动态环境(dynamic context)。let声明必须有一个初始化表达式。这个值是初始化方法的具体值。let声明的有效值类型必须是具体的可构造类型或指针类型。
// 'blockSize' denotes the i32 value 1024.
let blockSize: i32 = 1024;
// 'row_size' denotes the u32 value 16u. The type is inferred.
let row_size = 16u;
var 声明 (这部分是重点,概念较多)
变量是指向内存的命名引用,可以包含某种特定存储类型的值。
变量关联着两种类型:存储类型(可以放置在引用内存中的值类型)和引用类型(变量本身的类型)。如果一个变量的存储类型是T,地址空间是AS,访问模式是AM,那么它的引用类型是ref<AS,T,AM>。变量的存储类型总是具体的。
变量的声明:
指定变量的名字;
确定变量的地址空间(address space)、存储类型(store type)和访问模式(access mode),它们共同构成了变量的引用类型, 即 ref<AS, T, AM>;
确保执行环境在变量的生存期内在指定的地址空间中为存储类型的值分配内存,支持给定的访问模式。
如果变量位于 private或 function地址空间中,则可选性地具有初始值设定项表达式。如果存在,初始值设定项必须计算为变量的存储类型。如果存在,私有变量的初始值设定项必须是 const-expression或 override-expression。地址空间( 非 function或 privatet)中的变量不能有初始值设定项。(也就是说位于private 和 function 地址空间中的变量 可以有初始值,其它地址空间的没有初始值)
private地址空间、storage地址空间、uniform地址空间、workgroup地址空间和 handle·地址空间中的变量只能在模块作用域中声明;而function地址空间中的变量只能在函数作用域中声明。除 handle和function外,必须指定所有地址空间。handle地址空间不能指定。指定function地址空间是可选的。
访问模式总是有一个默认值,除了storage地址空间中的变量外,不能在WGSL源中指定。
uniform地址空间中的变量称为uniform buffer变量。它的存储类型必须是可共享、可构造的类型,并且必须满足地址空间布局约束。
storage地址空间中的变量就是storage buffer变量。它的存储类型必须是可共享的主机类型,并且必须满足地址空间布局约束。该变量可以声明为read或read_write访问模式。默认值是read。
纹理资源 texture resource是一个有效值类型是纹理类型的变量,它是在模块作用域中声明的。它持有一个不透明的手柄,用于访问纹理中底层纹素(texels)网格。handle本身在handle地址空间中,始终是只读的。在很多情况下,底层的纹素(texel)是只读的,我们说texture变量是不可变的。对于一个只写的存储纹理,底层的纹素(texel)是write-only,我们说texture变量是可变的(mutable)。
sampler resource是一个其有效值类型是采样器类型的变量,它在模块作用域中声明,存在于handle地址空间中,并且是不可变的。(可变,不可变即是能不能修改的意思)。
uniform buffer、storage buffer、textures和 samplers构成了着色器的资源接口。
变量的生命周期(lifetime)是着色器执行期间与变量关联的内存位置的一段时间,模块作用的变量生命周期是整个着色器执行阶段,在private地址空间和function地址空间中,每次调用都有一个独立的变量版本。
private, function , wrokgroup 地址空间中的变量被创建时,会有个一个初始值:
function地址空间中的变量:
(1)如果声明的变量没有指定初始化方法,类型值为 0。
(2)否则,它是在程序执行时对具体化初始化表达式求值的结果。
private地址空间中的变量:
(1) 如果声明的变量没有指定初始化方法,store类型的0值。
(2) 否则,它是具体化初始化表达式的计算结果。初始化方法必须是重载表达式,因此其值的确定不迟于管道创建时间。
workgroup地址空间中的变量:
(1) 当存储类型(store type)可构造时,类型值为0。
(2)如果存储类型是原子类型,则零值(底层类型是整型)。
(3)否则,如果存储类型不可构造的,则通过将这些规则递归地应用于组合的每个组件来确定0值,直到遇到可构造类型为止。
也就是说 var 在 private , function , workgroup 地址空间时,都会有初始值,如果没有给定初始值,一般都是 0 值 。
其他地址空间中的变量是在draw命令或dispatch命令中绑定设置的资源。
var i: i32; // Initial value is 0. Not recommended style.
loop {
var twice: i32 = 2 * i; // Re-evaluated each iteration.
i++;
if i == 5 { break; }
}
The loop body will execute five times. Variable i will take on values 0, 1, 2, 3, 4, 5, and variable twice will take on values 0, 2, 4, 6, 8.
循环体被执行了5次, 变量 i 取值 为 0,1,2,3,4,5, 变量 twice 的结果值为 0,2,4,6,8。
var x: f32 = 1.0;
let y = x * x + x + 1;
因为 x 是变量,对其访问都会转变成 对内存的load 和 store 操作。我们希望浏览器或驱动程序能够优化这种中间表示,以消除冗余负载。(很明显 , let 声明的 y 是值,确定下来是不变的)
模块作用域 的变量声明:
var<private> decibels: f32;
var<workgroup> worklist: array<i32,10>;
struct Params {
specular: f32,
count: i32
}
// Uniform buffer. Always read-only, and has more restrictive layout rules.
@group(0) @binding(2)
var<uniform> param: Params; // A uniform buffer
// A storage buffer, for reading and writing
@group(0) @binding(0)
var<storage,read_write> pbuf: array<vec2<f32>>;
// Textures and samplers are always in "handle" space.
@group(0) @binding(1)
var filter_params: sampler;
缓冲区的访问模式:
// 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.
struct ParamsTable {weight: f32}
// Uniform buffer. Always read-only, and has more restrictive layout rules.
@group(0) @binding(2)
var<uniform> params: ParamsTable; // Can read, cannot write.
函数作用域的变量和常量声明:
fn f() {
var<function> count: u32; // A variable in function address space.
var delta: i32; // Another variable in the function address space.
var sum: f32 = 0.0; // A function address space variable with initializer.
var pi = 3.14159; // Infer the f32 store type from the initializer.
}