Skip to content

(bind-group …)

Creates a bind group that binds GPU resources (buffers, textures, samplers) to shader binding slots.

(bind-group :name groupName :layout pipelineName :group 0 :pool 2
(entry :binding 0 :buffer bufferName)
(entry :binding 1 :texture textureName)
(entry :binding 2 :sampler samplerName))
Key / Sub-form Type Required Default Description
:name symbol Yes - Bind group name
:layout reference Yes - The layout this group is built against
:group number No 0 Which @group index of a pipeline’s layout it binds
:pool number No - Ping-pong pool size (1 to 255); absent means a single bind group
(entry …) sub-form No - Resource bindings, one per slot

:layout is one key, mirroring GPUBindGroupDescriptor.layout, which is one member. Its value names either a pipeline, whose auto-derived layout the group is built against, or an explicit (bind-group-layout …).

Derived from a pipeline’s layout:

:layout renderPipeline :group 0

Explicit bind group layout reference:

:layout myLayout

:group is @group(N) as the WGSL spells it, and it pairs with the entries’ :binding. It is meaningful for the pipeline arm only: that arm resolves the layout as pipeline.getBindGroupLayout(:group), while an explicit (bind-group-layout …) is the layout and an index selects nothing there. Stating :group 0 beside one changes nothing: the document compiles to the same bytes, with no diagnostic. Any other index warns (bind-group 'g' names an explicit (bind-group-layout …) as its :layout, so :group 1 selects nothing), and because the index is still applied to the WGSL match, a binding the shader uses at @group(0) is then reported as never bound and the compile fails.

A render pipeline, a compute pipeline and a bind-group layout are one namespace, so the name resolves to exactly one form: a collision between two of those kinds is a duplicate_cross_ref_target at the second declaration rather than a guess here.

Each (entry …) sub-form binds one resource to a binding index:

Key Type Required Description
:binding number Yes Binding index
:buffer reference * Buffer reference
:sampler reference * Sampler reference
:texture reference * Texture reference (bound via its default 2d view)
:texture-view reference * Explicit (texture-view …) reference (for 1d/3d/array/cube sampling)
:offset number No Byte offset of the bound slice into :buffer (default 0)
:size number No Byte size of the bound slice (default: the rest of :buffer)
:ping-pong number No Pool offset for ping-pong (see :pool below)

*Exactly one of :buffer, :sampler, :texture, or :texture-view per entry. None is required_one_of_missing, two is mutually_exclusive_keys_present.

(bind-group :name materials :layout renderPipeline :group 1
(entry :binding 0 :buffer uniformBuffer)
(entry :binding 1 :texture colorTexture)
(entry :binding 2 :sampler linearSampler))

:offset and :size bind a sub-range of a buffer instead of the whole thing: the GPUBufferBinding offset/size pair. Both require :buffer (stating either on a sampler or texture entry is a located dependent_key_missing), and :offset + :size must fit inside the declared buffer size, which the compiler checks and reports as (entry …) :offset/:size slice ends at byte N, past the end of buffer 'b'.

(buffer :name scene :size 512 :usage [uniform copy-dst])
(bind-group :name perObject :layout pipe :group 0
(entry :binding 0 :buffer scene :offset 256 :size 16))

WebGPU’s dynamic-offset alignment rules apply: 256 bytes for uniform bindings, 256 for storage bindings by default.

Creates multiple bind group instances for ping-pong patterns, where a resource is read from one slot and written to another and the two swap each frame. Used with pooled buffers or textures; each (entry …) selects its read/write slot via :ping-pong, and the runtime resolves the actual id as base + (frame + ping-pong) % pool.

(bind-group :name particleBindGroup :layout simPipeline :group 0 :pool 2
(entry :binding 0 :buffer particles :ping-pong 0)
(entry :binding 1 :buffer particles :ping-pong 1))
(buffer :name uniforms :size 64 :usage [uniform copy-dst])
(bind-group :name mainBindGroup :layout mainPipeline :group 0
(entry :binding 0 :buffer uniforms))
(texture :name diffuse :size [512 512] :format rgba8unorm :usage [texture-binding])
(sampler :name linearSampler :mag-filter linear :min-filter linear)
(bind-group :name materials :layout renderPipeline :group 1
(entry :binding 0 :texture diffuse)
(entry :binding 1 :sampler linearSampler))
(bind-group-layout :name layout0
(entry :binding 0 :visibility [vertex fragment] (buffer :type uniform)))
(bind-group :name group0 :layout layout0
(entry :binding 0 :buffer uniforms))
(buffer :name particles :size 65536 :usage [storage] :pool 2)
(bind-group :name simGroup :layout simPipeline :group 0 :pool 2
(entry :binding 0 :buffer particles :ping-pong 0)
(entry :binding 1 :buffer particles :ping-pong 1))
(compute-pass :name simulate
:pipeline simPipeline
:bind-groups [simGroup]
:bind-groups-pool-offsets [0]
(dispatch :workgroups [64 1 1]))
(bind-group :name perFrame :layout renderPipeline :group 0
(entry :binding 0 :buffer frameUniforms))
(bind-group :name perMaterial :layout renderPipeline :group 1
(entry :binding 0 :texture albedo)
(entry :binding 1 :sampler linearSampler))
(render-pass :name draw
(color-attachment :view context-current-texture
:clear-value [0 0 0 1] :load-op clear :store-op store)
:pipeline renderPipeline
:bind-groups [perFrame perMaterial]
(draw :vertex-count 1000))
Rule Error
Name must be unique across every form kind duplicate_cross_ref_target
:layout is required missing_required_key
:layout must name a pipeline or a bind-group layout not_cross_ref
(entry …) requires :binding missing_required_key
No resource in an (entry …) required_one_of_missing
Two resources in one (entry …) mutually_exclusive_keys_present
:offset / :size without :buffer dependent_key_missing
Referenced resources must exist not_cross_ref

Two rules are the compiler’s, so they carry a located message and no code: a slice that runs past the end of its buffer, and a non-zero :group beside an explicit (bind-group-layout …), which warns and then fails as an unbound binding (:group 0 there is silent and harmless). Reusing another form’s name is a compiler diagnostic too, because names are one namespace across every form kind: duplicate name 'x': already declared as a (buffer …) at line N.

Maps to GPUBindGroup via:

device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: uniformBuffer } }
]
});