本文学习一下在 WGSL 中内存相关的知识点。
提前说一下,本文所说的 “内存” 你可以把它理解为:
- 供 CPU 使用的内存
- 供 GPU 使用的显存
无论是以上哪种 “存”,这些知识点都是相同的。
内存地址:
当然你也可以称呼它为 “内存位置”
- 每一个内存地址可以存 8 个字节的内容
- 多个内存地址组成一组内存
- 每一个变量都对应一组内存地址
- 每个变量所占的内存地址都不会与其他变量所占用的内存地址重叠
内存的3种访问模式:
- 只读(read):读取内存地址的内容
- 只写(write):设置内存地址的内容
- 读写(read_write):既可以读取,也可以设置 内存地址的内容
可存储类型:
WGSL 中可存储的类型有:
- 标量(scalar):bool、u32、i32、f32、f16
- 向量(vector):vector2(f32)、vector3(f32)、vector4(f32)...
- 矩阵(matrix):mat2x2(f32)、mat3x3(f32)...
- 原子(atomic):
atomic<u32>
、atomic<i32>
- 数组:
array<f32,8>
、array<i32,9>
- 结构(structure):struct Data { ... }
- 纹理(texture):texture
- 采样器(sampler):sampler
**IO可共享类型:**
管线输入和输出的值必须为 IO 可共享类型。
所谓 “IO”,实际是 输入(Input) 和 输出(Out) 首字母的缩写
当然你粗浅的把 输入 理解为 写,把 输出 理解为 读,IO 也就是 读写
在 WGSL 中只有以下 3 种类型属于 IO 可共享类型:
- 标量类型
- 数值向量类型
- 成员全部为 标量 或 数值向量 的结构类型
只有内置管线输入可能为 布尔类型,而用户输入或输出的数据属性不能 或 包含布尔类型。
主机可共享类型:
这里的 “主机” 在 WGSL 英文文档中使用的单词是 host
所谓 主机可共享类型 包含 2 种含义:
- 主机与 GPU 之间共享的缓冲区内容
- 主机与 GPU 之间可以复制 且 无需格式转换的内容
名词解释:内存布局(memory layout)
假定我们现在得到了一大块连续的内存,为了更好的管理这块内存,我们需要对这块内存进行不同功能的划分(分割),通常把这种对内存的划分称之为 “内存布局”。
那究竟都怎么划分、布局了呢?
答:通常情况下,我们将系统内存划分成 2 + 5 段。
2 + 5 段?
假设系统内存是一个竖直的长条形,那么这 2 + 5 段 他们的分布顺序为:
-
最上面的内核区:用于系统本身运行所需的内存
-
最下面的保留区:用于系统预留出的一些内存
最上面 和 最下面 这 2 段 就是
2 + 5 段
中的 2
除了最上面和最下面,中间部分会被划分为 5 段,从上往下,他们依次是:
-
栈区(stack):存放函数、方法、指针、局部变量、函数中参数等
-
堆区(heap):存放那些我们通过 new 创建的变量
-
未初始化数据(uninitialized data):也被称为 BSS 段,就是在代码中还未被初始化的变量
-
已初始化数据(initialized data):通常简称为 数据段,就是在代码中那些已经被我们初始化过的变量、全局变量、全局静态变量
-
文本段(text):也被称为 代码段,就是我们程序的代码文本
代码段的内容发生变化意味着上面几个分段的内容也会相应发生变化(重新计算)
在有些计算机文章中,会将上面提到的 “段” 按照上下关系,划分为 高地址 和 低地址。
例如 文本段 属于 低地址,而 栈区 位于 高地址。
思考一下:假设我们通过 WebGPU+WGSL 更改绘制三角形的颜色,实现方式是?
-
第1种方式:每次通过修改 WGSL 中的代码,然后重新执行一遍 .wgsl 内容
对应的是修改 “文本段(代码段)” 中的内容
-
第2种方式:WGSL 代码内容只执行一次,但是我们通过某个方法动态修改内存中的变量(内存)值 来实现颜色更换
对应的是修改 “栈区/堆区” 中的内容
想象一下,上面哪种方式性能最佳?
上面讲解的 内存布局 是指 通常意义上程序运行的机制。
但是对于 WGSL 而言,它的内存布局有着更加细致、严格 的相关知识点,例如。
- 对齐和大小
- 结构成员布局
- 数组布局
- 值的内部布局
- 地址空间布局约束
我暂时也没有完全理解这些概念,不过,有 2 个词一定要提一下。
统一缓冲区(Uniform buffer) 与 存储缓冲区(storage buffer)
在 WSGL 官方文档中很多地方都使用单词 uniform 和 storage 来指上述 2 个缓冲区。
先翻篇,接着看下一个概念。
内存视图类型
如同读写一个纹理数据需要使用到 纹理视图 一样,对于内存的读写 也需要通过 内存视图 才可以。
内存视图 的英文为 memory view
在 WGSL 中有 2 种内存视图类型:
-
引用类型:reference types,简写为 ref
引用类型 并不会出现在 WGSL 源码中,它是用来分析 WGSL 代码的
-
指针类型:pointer types,简写为 ptr
指针类型 可能会出现在 WGSL 源码中
特别提醒:上面说的是 引用类型和指针类型,并不是 引用和指针。
无论哪种视图类型,他们的格式规范都相似,分别是:
- ref<S,T,A>
- ptr<S,T,A>
S:存储类
T:可存储类型
A:访问模式
某种程度上来讲:
- 引用:暗含 全局变量 的意思
- 指针:暗含 局部变量 的意思
比如:
- 用 var 声明的是 引用类型
- 用 let 声明的是 指针类型
有点像 JS 中的 var 和 let
再比如:函数中的参数就是 指针类型,而不是 引用类型
在WGSL中可以像TypeScript那样定义某种类型。
这种形式被称为 “类型别名”。
举个例子:
type Arr = array<i32,5>
type RTArray = array<vec4<f32>>
在WGSL中,另外一个非常重要的知识点是:纹理与采样器类型
在 WGSL 定义了和它们 2 个相关的很多字面类型。
例如 纹素(颜色)相关的有 rgba8unorm、rgba8uint、rgba16uint、r32float 等等
纹理深度相关的有 texture_depth_2d、texture_depth_cube 等等
我们先不去深究这些。
本文我们只是最粗略、初级得了解了一下 WGSL 中 内存的一些概念。
水平有限,目前能够讲解的也就这些了。
WGSL 最核心的事情就是和 内存 打交道,只有慢慢完全了解 内存 相关知识点,才能够清晰理解 WGSL 的核心运行原理。
本文到此结束,接下来要学习一下在 WGSL 中如何声明变量。