Description
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 callWebGPUProgrammablePassEncoder.setPipeline()before callingWebGPUProgrammablePassEncoder.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
litherum commentedon Jan 16, 2019
And a related question: Suppose an application does:
litherum commentedon Jan 16, 2019
I guess the crux of this question is: Should it be possible for an application to do
or
?
litherum commentedon Jan 16, 2019
https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf says there can be at most 31 entries in each argument table.
JusSn commentedon Jan 17, 2019
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.
kvark commentedon Jan 17, 2019
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:
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
devshgraphicsprogramming commentedon Jan 18, 2019
That seems rather painful.
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
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.
devshgraphicsprogramming commentedon Jan 18, 2019
But again, on the other hand.
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
JusSn commentedon Jan 25, 2019
@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 stagesince each has to be backed by an argument buffer, so 4 as limited by Vk is no issue.kvark commentedon Jan 25, 2019
@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.
JusSn commentedon Jan 25, 2019
@kvark you're right; I meant that Metal's argument buffers are bound per stage. Updated my comment!
litherum commentedon Feb 1, 2019
We resolved this during the San Diego F2F. There can be at most 16 bound vertex buffers and at most 4 bound bindgroups.
Clean up capability info (gpuweb#167)