WebGPU 中消失的 VAO
1 VAO 是 OpenGL 技術中提出來的
參考:
其中有一段文字記錄了 VAO 是什麼:
A Vertex Array Object (VAO) is an object which contains one or more Vertex Buffer Objects and is designed to store the information for a complete rendered object. In our example this is a diamond consisting of four vertices as well as a color for each vertex.
VAO 記錄的是多個(一組) VBO 的 gl.bindBuffer 和 gl.vertexAttribPointer (WebGL API)的狀態,省去切換另一組 VBO 時再次設置綁定關係和讀取規則的成本。
裏面也描述了 VBO 是什麼:一個 VBO 可以是 position,也可以是 uv,甚至可以是 indices;當然,在 WebGL 中,你可以用 1 個 VBO 來存儲 position + uv + normal,但是不能和 indices 混用(和 type 有關)。
2 WebGPU 天生就能先保存狀態
WebGPU 不需要 VAO 了,源於 WebGPU 的機制,並不是過程式,所以不需要藉助 VAO 保存綁定一組 VBO 並讀取它的狀態。
2.1 WebGL 與 WebGPU 相關 API 對比
當 device.createShaderModule 時,就有 buffers 屬性描述着色器需要什麼類型的數據,類似 gl.vertexAttribPointer 的作用;
而 gl.bindBuffer 的操作則由 renderPass.setVertexBuffer 完成;
關於數據的傳遞,gl.bufferData 的任務就由 device.createBuffer 時通過映射、解映射的機制將 TypedArray 傳遞進 GPUBuffer 來替代
2.2 誰替代了 VAO?
那麼誰能在渲染時告訴着色器,我有多組 VBO 要切換呢?
準確的說,VBO 這個概念已經被 GPUBuffer + GPUShaderModule 替代了,由後者兩個對象共同分擔,GPUBuffer 專註於 cpu~gpu 的數據傳遞,GPUShaderModule 不僅僅是着色器代碼本身,還承擔著 GPUBuffer[type=vertex] 的數據如何讀取的職能(替代了 gl.vertexAttribPointer 的職能)。
VAO 的職能則轉至 GPURenderPipeline 完成,其 GPURenderPipelineDescriptor.GPUVertexState.buffers 屬性是 GPUVertexBufferLayout[] 類型的,這每一個 GPUVertexBufferLayout 對象就類似於 VAO 的職能。
2.3 代碼舉例
下列只有一個 GPUVertexBufferLayout:
const renderPipeline = device.createRenderPipeline({
/* ... */
vertex: {
module: device.createShaderModule({
code: ` /* wgsl vertex shader code */ `,
}),
entryPoint: 'vertex_main',
buffers: [
{
arrayStride: 4 * 5, // 一個頂點數據占 20 bytes
attributes: [
{
// for Position VertexAttribute
shaderLocation: 0,
offset: 0,
format: "float32x3" // 其中頂點的坐標屬性占 12 位元組,三個 float32 數字
},
{
// for UV0 VertexAttribute
shaderLocation: 1,
offset: 3 * 4,
format: "float32x2" // 頂點的紋理坐標占 8 位元組,兩個 float32 數字
}
]
}
]
}
})
下面有兩個 GPUVertexBufferLayout:
const renderPipeline = device.createRenderPipeline({
vertex: {
module: spriteShaderModule,
entryPoint: 'vert_main',
buffers: [
{
arrayStride: 4 * 4,
stepMode: 'instance',
attributes: [
{
// instance position
shaderLocation: 0,
offset: 0,
format: 'float32x2',
},
{
// instance velocity
shaderLocation: 1,
offset: 2 * 4,
format: 'float32x2',
},
],
},
{
arrayStride: 2 * 4,
stepMode: 'vertex',
attributes: [
{
// vertex positions
shaderLocation: 2,
offset: 0,
format: 'float32x2',
},
],
},
],
},
/* ... */
});
通過 renderPassEncoder.setVertexBuffer 就能切換 VBO 了:
renderPassEncoder.setVertexBuffer(0, bf0);
renderPassEncoder.setVertexBuffer(1, bf1);