Skip to content

(compute-pass …)

Defines a compute pass that dispatches compute shader workgroups.

(compute-pass :name name
:pipeline pipelineName
:bind-groups [group0 group1]
:bind-groups-pool-offsets [0]
(dispatch :workgroups [64 1 1]))
Key / Sub-form Type Required Default Description
:name symbol Yes - Pass name
:pipeline reference Yes - Compute pipeline
:bind-groups array No [] Bind groups
:bind-groups-pool-offsets array No [] Pool offsets
(dispatch …) sub-form * - Dispatch a workgroup grid stated in the document
(dispatch-indirect …) sub-form * - Dispatch a workgroup grid the GPU wrote into a buffer

*A pass issues at least one dispatch command, of either kind. A pass that dispatches nothing is refused (positional_missing). Several are allowed, and they run in the order they are written.

Reference to a (compute-pipeline …). A bare identifier resolves as a cross-reference.

:pipeline simPipeline

Bind groups in order matching @group(N):

:bind-groups [particleGroup uniformGroup]

Pool offsets for ping-pong bind groups. A :pool N on a (bind-group …) creates N sequential ids and the runtime rotates through them each frame, which is the ping-pong pattern: one frame reads what the last frame wrote. Each offset here corresponds to a bind group in the :bind-groups array.

:bind-groups [simBindGroup]
:bind-groups-pool-offsets [0] ; Alternates each frame

The actual bind group used is:

actual_id = base_id + (frame_counter + offset) % pool_size
Key Type Required Description
:workgroups array Yes Workgroup counts as [x], [x y] or [x y z]

The missing dimensions default to 1, so [64] dispatches (64, 1, 1). The vector is the only spelling: there is no bare :workgroups 64.

(dispatch :workgroups [64])
(dispatch :workgroups [8 8 1])
(dispatch :workgroups [4 4 4])

Each element is a literal, a bare (define …) constant, or a bounded expression over those constants:

(define :name NUM_PARTICLES :value 2048)
(define :name WORKGROUP_SIZE :value 64)
(dispatch :workgroups [(ceil (/ NUM_PARTICLES WORKGROUP_SIZE))])
(dispatch :workgroups [WORKGROUP_SIZE 1 1])

Total threads = workgroups * workgroup_size (from the shader).

Key Type Required Default Description
:buffer reference Yes - Buffer holding the dispatch args (needs indirect usage)
:offset number No 0 Byte offset of the args within that buffer

Reads the three workgroup counts (3 × u32) from a GPU buffer instead of baking them into the PNGB bytecode (the compiled payload); the counts are typically written by an earlier compute pass, so the GPU decides its own dispatch size without a CPU round trip.

The buffer needs indirect usage (plus whatever writes it):

(buffer :name dispatchArgs :size 12 :usage [storage indirect])
; An earlier pass writes [x y z] into dispatchArgs …
(compute-pass :name plan
:pipeline planPipeline
:bind-groups [planGroup]
(dispatch :workgroups [1]))
; … and this one reads them.
(compute-pass :name work
:pipeline workPipeline
:bind-groups [workGroup]
(dispatch-indirect :buffer dispatchArgs :offset 0))
(frame :name main :perform [plan work])
(compute-pass :name process
:pipeline processPipeline
:bind-groups [dataBindGroup]
(dispatch :workgroups [16 1 1]))
(compute-pass :name blur
:pipeline blurPipeline
:bind-groups [imageBindGroup]
(dispatch :workgroups [64 64 1])) ; 512x512 with 8x8 workgroups
(define :name NUM_PARTICLES :value 4096)
(define :name WORKGROUP_SIZE :value 64)
(compute-pass :name simulate
:pipeline simPipeline
:bind-groups [particleGroup]
(dispatch :workgroups [(ceil (/ NUM_PARTICLES WORKGROUP_SIZE))]))
(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 update
:pipeline simPipeline
:bind-groups [simGroup]
:bind-groups-pool-offsets [0]
(dispatch :workgroups [64 1 1]))
(frame :name main :perform [update render])

Each frame:

  • Frame 0: Read from particles_0, write to particles_1
  • Frame 1: Read from particles_1, write to particles_0
  • Frame 2: Read from particles_0, write to particles_1
  • etc.
(compute-pass :name integrate
:pipeline integratePipeline
:bind-groups [physicsGroup]
:bind-groups-pool-offsets [0]
(dispatch :workgroups [64 1 1]))
(compute-pass :name collide
:pipeline collidePipeline
:bind-groups [physicsGroup]
:bind-groups-pool-offsets [0]
(dispatch :workgroups [64 1 1]))
(frame :name physics :perform [integrate collide render])
Rule Error
A name is unique across every form kind duplicate_cross_ref_target
:pipeline key is required missing_required_key
At least one (dispatch …) / (dispatch-indirect …) child positional_missing
A sub-form head the pass does not accept not_head_member
:workgroups takes 1 to 3 elements vector_too_long
Referenced resources must exist not_cross_ref
A misspelt (define …) name in a count not_cross_ref

The empty-pass rule reads:

form `compute-pass` requires at least 1 positional child
from [dispatch | dispatch-indirect], found 0

Maps to compute pass encoding:

const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatchWorkgroups(64, 1, 1);
// or, with (dispatch-indirect …)
// passEncoder.dispatchWorkgroupsIndirect(argsBuffer, 0);
passEncoder.end();