The VertexLayout shipped in #186310 requires VertexBufferLayout.binding indices to be densely packed starting from 0 (i.e. {0, 1, 2, ...}). Sparse bindings (e.g. {0, 3} with no buffer at slot 1 or 2) currently throw at createRenderPipeline time.
Why this matters
A common renderer pattern is to keep a stable shared binding (e.g. position=binding-0 reused across many pipelines) while reserving higher binding indices for per-pipeline or per-pass attributes. With dense-only bindings, every pipeline that wants to use binding=3 must also supply something at bindings 0, 1, 2, even when those slots are unused by the active vertex shader. WebGPU, Vulkan, Metal, and D3D12 all allow sparse bindings.
Proposal
Drop the dense-binding restriction in the createRenderPipeline validator and update RenderPass.bindVertexBuffer so that calls can address any slot in [0, maxVertexBuffers) regardless of whether lower slots have been bound.
The Dart API surface doesn't change; only the validation does.
HAL dependency
Impeller's RenderPass::SetVertexBuffer(BufferView vertex_buffers[], size_t vertex_buffer_count) rejects empty BufferViews in any of the supplied slots, so it can't currently express "binding 3 only". Two ways to lift this:
- Add a
firstBinding parameter to RenderPass::SetVertexBuffer, mirroring vkCmdBindVertexBuffers(firstBinding, ...). Each backend would forward firstBinding to its native call (Metal [[buffer(N)]] indexing, Vulkan firstBinding, OpenGLES per-slot binding loop).
- Allow empty
BufferView slots in the existing array and have each backend skip them.
Option 1 is closer to what the Vulkan backend wants natively and avoids the "sparse iteration" question for backends that don't have a sparse-binding native call.
References
The
VertexLayoutshipped in #186310 requiresVertexBufferLayout.bindingindices to be densely packed starting from 0 (i.e.{0, 1, 2, ...}). Sparse bindings (e.g.{0, 3}with no buffer at slot 1 or 2) currently throw atcreateRenderPipelinetime.Why this matters
A common renderer pattern is to keep a stable shared binding (e.g.
position=binding-0reused across many pipelines) while reserving higher binding indices for per-pipeline or per-pass attributes. With dense-only bindings, every pipeline that wants to use binding=3 must also supply something at bindings 0, 1, 2, even when those slots are unused by the active vertex shader. WebGPU, Vulkan, Metal, and D3D12 all allow sparse bindings.Proposal
Drop the dense-binding restriction in the
createRenderPipelinevalidator and updateRenderPass.bindVertexBufferso that calls can address anyslotin[0, maxVertexBuffers)regardless of whether lower slots have been bound.The Dart API surface doesn't change; only the validation does.
HAL dependency
Impeller's
RenderPass::SetVertexBuffer(BufferView vertex_buffers[], size_t vertex_buffer_count)rejects emptyBufferViews in any of the supplied slots, so it can't currently express "binding 3 only". Two ways to lift this:firstBindingparameter toRenderPass::SetVertexBuffer, mirroringvkCmdBindVertexBuffers(firstBinding, ...). Each backend would forwardfirstBindingto its native call (Metal[[buffer(N)]]indexing, VulkanfirstBinding, OpenGLES per-slot binding loop).BufferViewslots in the existing array and have each backend skip them.Option 1 is closer to what the Vulkan backend wants natively and avoids the "sparse iteration" question for backends that don't have a sparse-binding native call.
References