WGSL属于静态类型语言,换言之在 WGSL 中每一个数据(变量/表达式)都需要明确指定其类型。WGSL 中类型大致可以划分为 3类:普通类型、内存视图类型、纹理和采样器类型
本文我们只学习 普通类型。
我们先来看一下在 WGSL 中 数字 1 究竟可以是哪种类型,以及他们不同的表达形式。
表现形式 | 对应类型 |
---|---|
1i | 32 位有符号整数 |
1u | 32 位无符号整数 |
1.0f | 32 位浮点数 |
1.0h | 16 进制浮点数 |
假设你会某一种面对对象编程语言,或者你会 TypeScript,那么对于数据类型并不会感到陌生,接受起来会非常容易。
因为我最早从事 Flash AS3 开发,as3 就属于面对对象语言,后来 flash 没落了,我转行做过销售、UI 设计,3 年前转行做前端开发(原定的是全栈开发),刚开始学习 JS 发现这怎么跟 as3 那么像,后来发现 TypeScript 更像。
如果你是只用 JS 的前端人员,且不会 TS,那么第一次接触 静态类型语言,你会非常不适应,觉得处处受限,但是这种不适感会很快消失的,因为你会发现所谓 “静态类型” 无非就是定义变量之前提前告诉编译器这个变量是什么类型,而编译器会根据你定义的类型来自动做代码检查,并且会根据类型申请创建更加精准的连续内存而已。
补充:上面提到的 “变量” 不仅仅是指变量本身,还包括 函数、表达式 等。
接下来我们开始逐个学习 WGSL 中都有哪些类型概念。
特别说明:这些类型并不是孤立存在的,一些简单基础的类型可以构建出复杂、复合的类型。
类型名 | 类型形式 | 对应值说明 |
---|---|---|
布尔类型 | bool | true 或 false |
无符号整数类型 | u32 | 0 和 正整数 |
有符号整数类型 | i32 | 负整数、0、正整数 |
32位浮点数类型 | f32 | -- |
16位浮点数类型 | f16 | -- |
下面的类型,前提是假设你掌握 图形学基础 知识。如果你连 向量、矩阵 都不明白它们是什么,那么你一定要先去学图形学基础,再来学习 WGSL。
向量的英文单词为 vector,在 WGSL 中使用缩写 vec
来表示向量。
向量的书写模板为:vecN<T>
- N:指向量的维度,例如 2、3、4 分别表示二维向量、三维向量、四维向量
- T:指向量每一个分量的类型
例如 vec2(f32)
就是指:
- 一个二维向量
- 且每一个分量的类型为 f32
照着这个模板套路你自然很容易看明白 vec3<f32>
、vec4<f32>
是指什么了。
补充一下为什么向量中通常使用 f32,而不是 u32 或 i32 ?
因为我们 99.99% 时间使用的向量都是
归一化后的向量
,其每一个分量的取值范围都是0 ~ 1
,或者是-1 ~ 1
矩阵的英文单词为 matrix,在 WGSL 中使用缩写 mat
来表示矩阵。
矩阵的书写模板为:matCxR<T>
- C:是单词 col 的缩写,指明该矩阵的 列数
- R:是单词 rol 的缩写,指明该矩阵的 行数
- T:是单词 type 的缩写,指明该矩阵每个分量的类型
例如 mat2x3<f32>
就是指:
- 该矩阵为 2 列
- 该矩阵为 3 行
- 该矩阵每一个分量的类型为 f32
原子类型的英文单词为 atomic type,在 WGSL 中使用原子的单词 atomic
来表示原子类型。
在 WGSL 中原子类型是用于封装 整数标量(u32 或 i32) 类型的。
原子类型的书写模板为:atomic<T>
,其中 T 只能是 u32 或 i32。
向量类型、矩阵类型都在理解范围内,但是这个 原子类型 是用来做什么的?
“原子类型” 这个词并不是 WGSL 发明的,像 C++ 中就有原子类型。
又到了我胡说八道的时间了,关于原子类型,下面是我查阅相关资料后的理解。
先来看一个名词:原子操作
在上一节我们简单讲解了 并发 和 并行,并发可以简单理解为 “可以在不同任务之间自由切换”。
- “不同任务” 准确来说是指 “不同的线程”
- “自由切换” 是指 “线程的调度机制”
百度百科中关于 原子操作 的解释为:
原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始就会一直运行到结束,中间不会发生线程之间的切换。
但是在高并发程序中,往往是多个线程要同时访问和操作某一个数据,这就引申出了一个需要解决的事情:如何保护和同步共享数据?
既然是 “保护”,于是就出现了 2 个名词概念:自旋锁、互斥锁
关于 自旋锁和互斥锁 我也不太理解。
总之我们只需要知道:
- 原子操作 与 高并发 之间存在 数据同步共享 的问题矛盾
- 通过某种策略(自旋锁、互斥锁) 可以解决这种矛盾
而 WGSL 中的 原子类型 就是解决这种矛盾中的一个重要关键点。
至于 原子类型 实际使用方式和示例,等到以后再慢慢学习吧,本文只是有一个模糊印象即可。
实际上我更习惯使用 “元祖” 这个词。
数组类型的书写模板为:array<E,N>
或 array<E>
-
E:是单词 element 的缩写,指明数组元素的类型
-
N:是单词 number 的缩写,指明数组元素的总个数(也就是数组的长度)
这里的 N 必须是大于 0 的整数
例如 array<f32,8>
就是指:
- 一个数组,该数组中每个元素类型都是 f32
- 该数组一共有 8 个元素,数组长度为 8
我们也可以通过常量形式来表达 N :
const arrSize = 8;
...
array<f32,arrSize>
注意必须是使用 const 声明的常量
特别注意:
-
平时定义变量时我们只能使用
array<E,N>
这种形式,也就是说在定义一个数组变量时一定设定好该数组的元素总个数。一个老生常谈的话题:JS 中的数组 array 实际上并不是真的数组,它只不过是 通过 object 模拟出来的 "数组",JS 中的数组元素之间是可以不连续的。
但是在 WGSL 中,数组元素必须是连续的。
-
而
array<E>
这种形式,缺少元素总个数,它不能应用于具体变量,它只能应用在某些特定上下文中,例如下面即将讲到的 结构类型 中。
结构类型实际就相当于 TypeScript 中的 interface 所定义的类型,即 表明某个对象所拥有的属性以及属性值类型。
“结构” 对应的英文单词为 struct,在 WGSL 中就是使用 struct 关键词来定义 结构类型的。
例如:
struct Data {
a:i32,
b:vec2<f32>,
c:array<i32,8>,
d:array<f32>
}
在上面的示例代码中,最后一项 d 这一行结尾处加不加 英文逗号 "," 都是可以的
对于结构类型中的成员(属性),WGSL 定义了以下几个标识符:
- builtin
- location
- align
- size
我们暂时先不去学习这几个标识符的含义。
假设某个结构类型中的成员类型为:vector、matrix、array 或 structure(结构类型),那么就把这种结构类型称之为 复合类型。
复合类型的深度定义约定为 1 + N,而 N 的值为:
- 若为一维数组,则 N 为 1
- 若为向量,则 N 为 1
- 若为矩阵,则 N 为 2
- 若一个复合类型中的成员存在 多个不同 类型值,则 N 取它们之间 N 为最大的那个值
换句话说,一个复合类型的深度为:1 + max(n1, n2, n3...)
如果某种类型的变量可以被创建、加载、存储、作为函数的参数或返回值,那么这些类型都可以称之为 可构造类型。
平时我们使用的绝大多数类型都是属于可构造类型。
那么问题来了:哪些类型不可构造?
-
原子类型
-
runtime-sized 数组类型
额~,runtime-sized 数组类型又是什么?我在看 WGSL 官方文档时也没太理解。
以上 2 种类型都不属于可构造类型。
这里的 “占用” 是指 “内存占用”。
所谓 “固定占用类型” 就是指 占用固定内存大小的那些变量类型。它们在定义(创建) 时所占用的内存大小就已经固定下来,并且不会随着 WGSL 不同生命周期阶段而发生变化。
关于普通类型,我们就讲解到这里。
下一节我们将讲解 内存视图类型。