← 输入框|github.com|刷新
Skip to content

VertexBuffers and Bind Group namespacing #167

Closed

Description

@litherum

Consider a Render Pipeline that includes two vertex buffers and three bind groups.

In WebGPU, vertex buffers are attached to the pass by calling void setVertexBuffers(u32 startSlot ...);. This startSlot parameter refers to the inputSlot parameter inside WebGPUVertexInputDescriptor.

In Metal, sequence<WebGPUVertexInputDescriptor> corresponds to MTLVertexBufferLayoutDescriptorArray, which is parallel to the buffers in the vertex shader's argument table. Therefore, a call to WebGPURenderPassEncoder.setVertexBuffers(x, {buf}, {0}) will result in a call to -[MTLRenderCommandEncoder setVertexBuffer:buf offset:0 atIndex:x]. So far so good.

In WebGPU, bind groups are attached to the pass by calling void setBindGroup(u32 index, WebGPUBindGroup bindGroup, optional sequence<u32> dynamicOffsets);. This index parameter refers to the index into the sequence<WebGPUBindGroupLayout> bindGroupLayouts inside the WebGPUPipelineLayoutDescriptor.

In Metal, WebGPUBindGroup corresponds to a MTLBuffer that has been populated by using a MTLArgumentEncoder. These get attached to the MTLRenderCommandEncoder by setting values in that same vertex shader's argument table. Therefore, a call to WebGPUProgrammablePassEncoder.setBindGroup(y, bindGroup) will result in a call to -[MTLRenderCommandEncoder setVertexBuffer:argumentBuffer offset:0 atIndex:y]

So, as you can see above, we have a collision. If a WebGPU programmer tries to use a vertex buffer at slot x and a bind group at index x, a naive Metal implementation would clobber one in favor of the other.

I think there are three possible solutions that we should choose among:

  • Forbid WebGPU programmers from using the same vertex buffer slot IDs as bind group indices.
  • Metal implementations can internally separate and tightly pack the vertex and argument buffers. Doing this is only possible if the runtime knows how many argument buffers there will be at the time WebGPUProgrammablePassEncoder.setBindGroup() is called (or vice versa), which requires WebGPU programmers to call WebGPUProgrammablePassEncoder.setPipeline() before calling WebGPUProgrammablePassEncoder.setBindGroup().
  • Instead of tightly packing the buffers, we can add a hardcoded constant to the bind group indices. This causes the vertex argument table to be sparse, and requires WebGPU to place hard limits on the number of vertex buffers and bind groups that may ever occur together. If the limits are high, this sparseness could hurt performance-wise, but if they're small, it shouldn't be a problem.

Activity

  1. litherum commented on Jan 16, 2019

    @litherum
    ContributorAuthor

    And a related question: Suppose an application does:

    WebGPUProgrammablePassEncoder.setPipeline(pipelineA);
    WebGPUProgrammablePassEncoder.setBindGroup(x, bindGroup);
    WebGPUProgrammablePassEncoder.setPipeline(pipelineB);
    // What is bound to bind group "x" here?
    // What if both pipelines agree on the layout for bind group layout x?
    
  2. litherum commented on Jan 16, 2019

    @litherum
    ContributorAuthor

    I guess the crux of this question is: Should it be possible for an application to do

    let pass = commandBuffer.beginRenderPass(descriptor);
    pass.setBindGroup(x, bindGroup);
    pass.setPipeline(pipelineA);
    

    or

    let pass = commandBuffer.beginRenderPass(descriptor);
    pass.setVertexBuffers(x, {buf}, {0});
    pass.setPipeline(pipelineA);
    

    ?

  3. litherum commented on Jan 16, 2019

    @litherum
    ContributorAuthor

    https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf says there can be at most 31 entries in each argument table.

  4. JusSn commented on Jan 17, 2019

    @JusSn
    Contributor

    Each function can have up to 8 argument buffer entries regardless of hardware. This counts towards the 31 buffer entry limit.

    Argument buffers are analogous to descriptor sets/bind groups, but we need to split each bind group into one argument buffer per shader function on the Metal side of things.

  5. kvark commented on Jan 17, 2019

    @kvark
    Contributor

    Thanks for bringing this up, @litherum ! Interesting how it's similar to #145 in a way :)

    We faced this early in gfx-rs and had to work around with roughly the following logic:

    • vertex input buffers are bound after the slots occupied by the pipeline layout
    • when the layout is changed, we invalidate the current bindings and re-bind them (we have to keep the vertex buffers around in the meantime)

    If you know that argument buffers are supported, and you are willing to use them in the implementation, your life becomes much easier. Given that we can have only 4 bind groups (minimum required by Vulkan), you can just always bind the vertex buffers starting from index 4 (earlier vertex bindings would be the argument buffers corresponding to the bind groups), regardless of whether you want vertex buffers to be enclosed in an argument buffer or not.

    Your follow-up question about bind group setting versus pipeline was touched on a few times, e.g. here: #47 (comment), plus search for "Inheritance" in #19

  6. devshgraphicsprogramming commented on Jan 18, 2019

    @devshgraphicsprogramming

    Forbid WebGPU programmers from using the same vertex buffer slot IDs as bind group indices.

    That seems rather painful.

    Metal implementations can internally separate and tightly pack the vertex and argument buffers. Doing this is only possible if the runtime knows how many argument buffers there will be at the time WebGPUProgrammablePassEncoder.setBindGroup() is called (or vice versa), which requires WebGPU programmers to call WebGPUProgrammablePassEncoder.setPipeline() before calling WebGPUProgrammablePassEncoder.setBindGroup().

    Much more pleasant, you'll have the Graphics Pipeline layouts ready so cumulative offsets (webgpu tuples to Metal entry mapping) can be precomputed at pipeline creation time.

    But for this to work

    WebGPUProgrammablePassEncoder.setPipeline(pipelineA);
    WebGPUProgrammablePassEncoder.setBindGroup(x, bindGroup);
    WebGPUProgrammablePassEncoder.setPipeline(pipelineB);
    // What is bound to bind group "x" here?
    // What if both pipelines agree on the layout for bind group layout x?
    

    WebGPU would have to agree that once you switch a pipeline, all the bindings are unbound ( a little bit how when you switch pipelines on Vulkan and the descriptor set layout doesn't match it will get unbound), unless the layouts for everything (vertex input, ssbos, ubos, textures and images) are identical.

  7. devshgraphicsprogramming commented on Jan 18, 2019

    @devshgraphicsprogramming

    But again, on the other hand.

    Instead of tightly packing the buffers, we can add a hardcoded constant to the bind group indices. This causes the vertex argument table to be sparse, and requires WebGPU to place hard limits on the number of vertex buffers and bind groups that may ever occur together. If the limits are high, this sparseness could hurt performance-wise, but if they're small, it shouldn't be a problem.

    Since Metal allows for 31 offsets, then it means we can have 16 Vertex Inputs + 4 BindGroups (descriptor sets) with absolutely no issue.

    Mapping could be (per @kvark 's implementation):
    Metal 0-3 = Bind Groups
    Metal 4-20 = Vertex Input Slots

    Unless what @JusSn said means you can only have 8 of these per shader stage

  8. JusSn commented on Jan 25, 2019

    @JusSn
    Contributor

    @devshgraphicsprogramming Yes, we also decided on this during the F2F. What my earlier comment means is that Metal could support up to 8 BindGroups per stage since each has to be backed by an argument buffer, so 4 as limited by Vk is no issue.

  9. kvark commented on Jan 25, 2019

    @kvark
    Contributor

    @JusSn to be completely pedantic, you don't bind resource groups per stage, so you could have 4 argument buffers and just bind some of them to multiple stages, if needed.

  10. JusSn commented on Jan 25, 2019

    @JusSn
    Contributor

    @kvark you're right; I meant that Metal's argument buffers are bound per stage. Updated my comment!

  11. litherum commented on Feb 1, 2019

    @litherum
    ContributorAuthor

    We resolved this during the San Diego F2F. There can be at most 16 bound vertex buffers and at most 4 bound bindgroups.

  12. added a commit that references this issue on Sep 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions