1. 引言
前序博客:
WebGPU——Draft 2023.7.17 由苹果、谷歌、Mozilla团队发起,当前处于草稿阶段,旨在成为W3C推荐标准。
WebGPU为 在图形处理单元(GPU)上执行诸如渲染和计算之类的操作 提供了API。
GPU支持丰富的渲染和并行计算应用。WebGPU是通过API,将GPU的硬件能力 供Web使用。WebGPU API为对原生GPU API进行高效映射。WebGPU与WebGL无关,且不明确锚定OpenGL ES (OpenGL for Embedded Systems)。
WebGPU:
- 1)将物理GPU硬件看成是GPUAdapter。
- 2)通过GPUDevice来连接GPUAdapter。
- 3)GPUDevice的GPUQueue用于执行指令。
- 4)GPUDevice可能有自身的内存,可高速访问处理器单元。
- 5)GPUBuffer和GPUTexture是由GPU内存支持的物理资源。
- 6)GPUCommandBuffer和GPURenderBundle是用户录制(user-recorded)指令的容器。
GPU执行编码在GPUCommandBuffer内的指令,通过pipeline来喂入数据。- 6.1)GPUCommandBuffer:由fixed-function和programmable stages混合组成。
- Programmable stages执行shaders,shaders为设计运行于GPU硬件上的特定程序。shaders代码运行在GPU硬件的计算单元内。
- 6.2)pipeline:大多数pipeline状态由GPURenderPipeline或GPUComputePipeline对象定义。
不包含在pipeline对象中的状态则通过指令编码阶段设定,如beginRenderPass()或setBlendConstant()。
- 6.1)GPUCommandBuffer:由fixed-function和programmable stages混合组成。
- 7)GPUShaderModule包含着色器(shader)代码。
- 8)GPUSampler或GPUBindGroup,用于配置GPU使用物理资源的方式。
- 9)未来将通过Web Workers来支持多线程。
2. 坐标系统
渲染(rendering)操作可采用如下坐标系统:【注意,WebGPU的坐标系统 与 某graphics pipeline内的DirectX坐标系统 匹配。】
-
1)归一化设备坐标(Normalized device coordinates(NDC)):具有3个维度:
- − 1.0 ≤ x ≤ 1.0 -1.0\leq x \leq 1.0 −1.0≤x≤1.0。
- − 1.0 ≤ y ≤ 1.0 -1.0\leq y \leq 1.0 −1.0≤y≤1.0。
- 0.0 ≤ z ≤ 1.0 0.0\leq z \leq 1.0 0.0≤z≤1.0。
- 左下角坐标为 ( − 1.0 , − 1.0 , z ) (-1.0, -1.0, z) (−1.0,−1.0,z)。
-
2)Clip space坐标:具有4个维度 ( x , y , z , w ) (x,y,z,w) (x,y,z,w):
- 2.1)Clip space坐标可:
- 用作某vertex的clip position(即某vertex shader的position output)。
- 用作clip volume。
- 2.2)Clip space坐标 与 归一化设备坐标 之间的关系为:
- 若point p = ( p . x , p . y , p . z , p . w ) p=(p.x,p.y,p.z,p.w) p=(p.x,p.y,p.z,p.w)在clip volume内,则其归一化设备坐标为 ( p . x ÷ p . w , p . y ÷ p . w , p . z ÷ p . w ) (p.x\div p.w, p.y \div p.w, p.z \div p.w) (p.x÷p.w,p.y÷p.w,p.z÷p.w)。
- 2.1)Clip space坐标可:
-
3)Framebuffer坐标:用于对framebuffer内的pixels进行寻址:
- 3.1)具有2个维度。
- 3.2)每个pixel在 x x x和 y y y维度的单位为1。
- 3.3)左上角坐标为 ( 0.0 , 0.0 ) (0.0, 0.0) (0.0,0.0)。
- 3.4) x x x向右侧增长。
- 3.5) y y y向下侧增长。
-
4)Viewport坐标:在Framebuffer坐标 x , y x,y x,y维度的基础上,增加了depth z z z。
- 通常 0.0 ≤ z ≤ 1.0 0.0\leq z\leq 1.0 0.0≤z≤1.0,但可通过setViewport()来修改minDepth和maxDepth。
-
5)Fragment坐标:与vIewport坐标匹配。
-
6)UV坐标:用于sample textures,具有2个维度:
- 0 ≤ u ≤ 1.0 0\leq u\leq 1.0 0≤u≤1.0
- 0 ≤ v ≤ 1.0 0\leq v\leq 1.0 0≤v≤1.0
- ( 0.0 , 0.0 ) (0.0, 0.0) (0.0,0.0)为texture内存地址顺序上的首个texel。
- ( 1.0 , 1.0 ) (1.0, 1.0) (1.0,1.0)为texture内存地址顺序上的最后一个texel。
-
7)Window坐标 或 present坐标:与framebuffer坐标匹配,用于与外部显示等接口交互。
3. WebGPU编程模型
3.1 Timeline
WebGPU的行为以“timeline”来表示。算法内的每个操作都发生在某timeline。timeline会明确定义操作顺序以及某操作对应某state。
WebGPU的timeline类型有:【Immutable value可用于任意timeline】
- 1)Content timeline:与Web script执行关联。包含了调用本协议的所有方法。
- 2)Device timeline:与User agent发布的GPU device operations关联。包括:
- 创建adapters、devices、GPU resources以及state objects。从user agent角度来看,这些为经典的同步操作。
- 3)Queue timeline:与GPU计算单元内的操作执行关联。包含实际运行在GPU之上的draw、copy、compute jobs。
如GPUDevice.createBuffer():
- 1)用户填充GPUBufferDescriptor 并为其创建一个GPUBuffer。这发生在Content timeline。
- 2)User agent在Device timeline创建一个底层buffer。
3.2 内存模型
一旦在应用初始化阶段获得了某GPUDevice,则可将WebGPU平台描述为如下层次:
- 1)User agent:用于实现本协议。
- 2)具有该设备底层原生API驱动的操作系统。
- 3)实际的CPU和GPU硬件。
不同层次具有不同的内存类型,user agent在实现本协议时需考虑到:
- 1)script-owned内存:如由script创建的某ArrayBuffer,通常对GPU驱动不可访问。
- 2)user agenet可能有不同的进程来负责运行与GPU驱动的content和communication。此时,使用跨进程共享内存来传输数据。
- 3)特定的GPU有其自身的高带宽内存,这些集成GPU通常与系统共享内存。
为使GPU的渲染或计算高效,大多数物理资源都以内存形式分配。当用户需要为GPU提供新数据时:【以下为最差情况,实际实现时,通常不需要跨越进程边界、或者可将驱动管理内存直接暴露给用户的ArrayBuffer,从而可避免数据拷贝。】
- 1)数据可能首先得跨越进程边界,到达与GPU驱动通信的user agent部分。
- 2)然后,可能需要使其对驱动可见,有时需要将其拷贝到驱动分配的staging memory中。
- 3)最后,可能需要将数据传输到GPU专用内存中,可能会将内部layout转换为对GPU来说更可高效处理的方式。
所有以上数据转换同时通过WebGPU的user agentLai shixian d .
4. 关键内部对象
4.1 adapters
adapter用于标识某WebGPU实现:
- 既为某浏览器的计算或渲染功能实例
- 也为某浏览器的WebGPU实现实例。
adapter不会唯一标识底层实现,多次调用requestAdapter()
,每次会返回不同的adapter对象。
每个adapter对象仅可用于创建一个device:
- 当requestDevice()返回成功之后,该adapter将失效。
- 此外,adapter对象可随时过期。
从而可确保应用使用 所选择的最新adapter系统状态来创建device,同时也增加了更多场景下的鲁棒性。
adapter暴露为GPUAdapter。
4.2 Devices
device为某adapter的逻辑实例化,从而创建了内部对象。可跨多个agents(如专用workers)共享。
device为 基于该device创建的所有内部对象的 外部owner:
- 当device失效(lost或destroyed)时,基于该device创建的所有对象(通过createTexture()直接创建,或,通过createView()简介创建)都将implicitly 不可用。
5. 关键函数
-
1)navigator.gpu:返回可用的GPU对象。
-
2)gpu.requestAdapter():从user agent请求某adapter。
-
3)adapter.requestDevice():向某adapter请求某device。
-
4)adapter.requestAdapterInfo();获取某adapter的GPUAdapterInfo。
-
5)device.destroy():destroy某device,防止未来对该device进行操作。异步操作将失败。可对同一device多次destroy。
-
6)device.createBuffer():创建GPUBuffer。GPUBuffer表示用于GPU运算的一块内存。数据存储在linear layout中,即意味着分配的每个字节都可根据GPUBuffer起始位置的偏移来寻址,取决于具体运算有对齐限制。某些GPUBuffer可映射,使得可通过ArrayBuffer来访问该内存块。
-
7)GPUBuffer.destroy():destroy GPUBuffer。
-
8)GPUBuffer.mapAsync(mode, offset, size):其中mode有2种模式——读或写。映射之后可通过ArrayBuffer来访问GPUBuffer中的内容。
-
9)GPUBuffer.getMappedRange(offset, size):返回GPUBuffer中指定映射范围的内容,为ArrayBuffer。
-
10)GPUBuffer.unmap():解除映射,使得其内容可再次供GPU使用。
-
11)device.createBindGroupLayout():表示将单个shader资源绑定到某GPUBindGroupLayout中。【与wgsl源文件的数量对应】
其中 GPUShaderStage有3种类型:- VERTEX:可供vertex shaders访问。
- FRAGMENT:可供fragment shaders访问。
- COMPUTE:可供compute shaders访问。
-
12)device.createBindGroup():创建GPUBindGroup。
-
13)device.createShaderModule():创建GPUShaderModule。
-
14)device.createPipelineLayout():创建GPUPipelineLayout。
-
15) device.createComputePipeline():使用immediate pipline creation方式来创建GPUComputePipeline。
-
16)device.createCommandEncoder():创建GPUCommandEncoder。
-
17)GPUCommandEncoder.beginComputePass(descriptor):开始对由descriptor描述的某compute pass进行编码。
-
18)dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ):将work分发给当前GPUComputePipeline执行。其中:
- workgroupCountX:为X dimension of the grid of workgroups to dispatch。
- workgroupCountY:为Y dimension of the grid of workgroups to dispatch。
- workgroupCountZ:为Z dimension of the grid of workgroups to dispatch。
即意味着,若某GPUShaderModule定义的entry point具有@workgroup_size(4, 4) 且 该work通过调用
computePass.dispatchWorkgroups(8, 8)
分发,则该entry point将总共触发1024次:- 分发4x4 workgroup 沿X轴8次,沿Y轴8次: 4 ∗ 4 ∗ 8 ∗ 8 = 1024 4*4*8*8=1024 4∗4∗8∗8=1024。
-
19)copyBufferToBuffer(source, sourceOffset, destination, destinationOffset, size):为GPUCommandEncoder中的命令,用于将某GPUBuffer sub-region内的数据 拷贝至 另一GPUBuffer的某sub-region。
-
20)clearBuffer(buffer, offset, size):将某GPUBuffer的某sub-region数据清空,全部设置为0。
-
21)device.queue.writeBuffer(buffer, bufferOffset, data, dataOffset, size):将特定数据写入GPUBuffer特定区域。
-
22)device.queue.submit(commandBuffers):对GPU该queue中的command buffers执行进行调度。已提交的command buffers将不可再次使用。
-
23)device.queue.onSubmittedWorkDone():当该queue完成了当前已提交的所有work时,会返回一个Promise。