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);