Skip to content

Latest commit

 

History

History
305 lines (152 loc) · 8.78 KB

04 WGSL基础之普通类型.md

File metadata and controls

305 lines (152 loc) · 8.78 KB

04 WGSL基础之普通类型

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>

  1. N:指向量的维度,例如 2、3、4 分别表示二维向量、三维向量、四维向量
  2. T:指向量每一个分量的类型

例如 vec2(f32) 就是指:

  1. 一个二维向量
  2. 且每一个分量的类型为 f32

照着这个模板套路你自然很容易看明白 vec3<f32>vec4<f32> 是指什么了。


补充一下为什么向量中通常使用 f32,而不是 u32 或 i32 ?

因为我们 99.99% 时间使用的向量都是 归一化后的向量,其每一个分量的取值范围都是 0 ~ 1,或者是 -1 ~ 1


矩阵类型

矩阵的英文单词为 matrix,在 WGSL 中使用缩写 mat 来表示矩阵。

矩阵的书写模板为:matCxR<T>

  1. C:是单词 col 的缩写,指明该矩阵的 列数
  2. R:是单词 rol 的缩写,指明该矩阵的 行数
  3. T:是单词 type 的缩写,指明该矩阵每个分量的类型

例如 mat2x3<f32> 就是指:

  1. 该矩阵为 2 列
  2. 该矩阵为 3 行
  3. 该矩阵每一个分量的类型为 f32

原子类型

原子类型的英文单词为 atomic type,在 WGSL 中使用原子的单词 atomic 来表示原子类型。

在 WGSL 中原子类型是用于封装 整数标量(u32 或 i32) 类型的。

原子类型的书写模板为:atomic<T>,其中 T 只能是 u32 或 i32。


向量类型、矩阵类型都在理解范围内,但是这个 原子类型 是用来做什么的?

“原子类型” 这个词并不是 WGSL 发明的,像 C++ 中就有原子类型。


又到了我胡说八道的时间了,关于原子类型,下面是我查阅相关资料后的理解。

先来看一个名词:原子操作

在上一节我们简单讲解了 并发 和 并行,并发可以简单理解为 “可以在不同任务之间自由切换”。

  1. “不同任务” 准确来说是指 “不同的线程”
  2. “自由切换” 是指 “线程的调度机制”

百度百科中关于 原子操作 的解释为:

原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始就会一直运行到结束,中间不会发生线程之间的切换。


但是在高并发程序中,往往是多个线程要同时访问和操作某一个数据,这就引申出了一个需要解决的事情:如何保护和同步共享数据?

既然是 “保护”,于是就出现了 2 个名词概念:自旋锁、互斥锁

关于 自旋锁和互斥锁 我也不太理解。

总之我们只需要知道:

  1. 原子操作 与 高并发 之间存在 数据同步共享 的问题矛盾
  2. 通过某种策略(自旋锁、互斥锁) 可以解决这种矛盾

而 WGSL 中的 原子类型 就是解决这种矛盾中的一个重要关键点。


至于 原子类型 实际使用方式和示例,等到以后再慢慢学习吧,本文只是有一个模糊印象即可。


数组类型

实际上我更习惯使用 “元祖” 这个词。

数组类型的书写模板为:array<E,N>array<E>

  1. E:是单词 element 的缩写,指明数组元素的类型

  2. N:是单词 number 的缩写,指明数组元素的总个数(也就是数组的长度)

    这里的 N 必须是大于 0 的整数

例如 array<f32,8> 就是指:

  1. 一个数组,该数组中每个元素类型都是 f32
  2. 该数组一共有 8 个元素,数组长度为 8

我们也可以通过常量形式来表达 N :

const arrSize = 8;
...
array<f32,arrSize>

注意必须是使用 const 声明的常量


特别注意:

  1. 平时定义变量时我们只能使用 array<E,N> 这种形式,也就是说在定义一个数组变量时一定设定好该数组的元素总个数。

    一个老生常谈的话题:JS 中的数组 array 实际上并不是真的数组,它只不过是 通过 object 模拟出来的 "数组",JS 中的数组元素之间是可以不连续的。

    但是在 WGSL 中,数组元素必须是连续的。

  2. array<E> 这种形式,缺少元素总个数,它不能应用于具体变量,它只能应用在某些特定上下文中,例如下面即将讲到的 结构类型 中。


结构类型

结构类型实际就相当于 TypeScript 中的 interface 所定义的类型,即 表明某个对象所拥有的属性以及属性值类型。

“结构” 对应的英文单词为 struct,在 WGSL 中就是使用 struct 关键词来定义 结构类型的。

例如:

struct Data {
  a:i32,
  b:vec2<f32>,
  c:array<i32,8>,
  d:array<f32>
}

在上面的示例代码中,最后一项 d 这一行结尾处加不加 英文逗号 "," 都是可以的


对于结构类型中的成员(属性),WGSL 定义了以下几个标识符:

  1. builtin
  2. location
  3. align
  4. size

我们暂时先不去学习这几个标识符的含义。


复合类型

假设某个结构类型中的成员类型为:vector、matrix、array 或 structure(结构类型),那么就把这种结构类型称之为 复合类型。

复合类型的深度定义约定为 1 + N,而 N 的值为:

  1. 若为一维数组,则 N 为 1
  2. 若为向量,则 N 为 1
  3. 若为矩阵,则 N 为 2
  4. 若一个复合类型中的成员存在 多个不同 类型值,则 N 取它们之间 N 为最大的那个值

换句话说,一个复合类型的深度为:1 + max(n1, n2, n3...)


可构造类型

如果某种类型的变量可以被创建、加载、存储、作为函数的参数或返回值,那么这些类型都可以称之为 可构造类型。

平时我们使用的绝大多数类型都是属于可构造类型。

那么问题来了:哪些类型不可构造?

  1. 原子类型

  2. runtime-sized 数组类型

    额~,runtime-sized 数组类型又是什么?我在看 WGSL 官方文档时也没太理解。

以上 2 种类型都不属于可构造类型。


固定占用类型

这里的 “占用” 是指 “内存占用”。

所谓 “固定占用类型” 就是指 占用固定内存大小的那些变量类型。它们在定义(创建) 时所占用的内存大小就已经固定下来,并且不会随着 WGSL 不同生命周期阶段而发生变化。


关于普通类型,我们就讲解到这里。

下一节我们将讲解 内存视图类型。