Skip to content

(render-pass …)

Defines a render pass that configures render targets and issues draw commands.

(render-pass :name name
(color-attachment :view context-current-texture
:clear-value [0 0 0 1] :load-op clear :store-op store)
(depth-stencil-attachment :view depthTexture
:depth-clear-value 1.0 :depth-load-op clear :depth-store-op store)
:pipeline pipelineName
:bind-groups [group0 group1]
:vertex-buffers [vertices]
(draw :vertex-count 3))
Key / Sub-form Type Required Default Description
:name symbol Yes - Pass name
(color-attachment …) sub-form - Color render targets (repeat for multiple render targets)
(depth-stencil-attachment …) sub-form - Depth/stencil target (at most one)
:pipeline reference * - Render pipeline
:bind-groups array No [] Bind groups
:bind-groups-pool-offsets array No [] Pool offsets for bind groups
:vertex-buffers array No [] Vertex buffers
:vertex-buffers-pool-offsets array No [] Pool offsets for vertex buffers
:index-buffer reference No - Index buffer
:stencil-reference number No - Stencil reference value
:blend-constant array No [0 0 0 0] [r g b a] value the constant blend factors multiply by
:viewport array No full target [x y w h] or [x y w h minDepth maxDepth]
:scissor-rect array No full target [x y w h] in integer pixels
:occlusion-query-set reference No - (query-set :type occlusion) the pass writes
:execute-bundles array * - (render-bundle …) forms to replay
(draw …) sub-form * - Draw call parameters
(draw-indexed …) sub-form * - Indexed draw parameters
(draw-indirect …) sub-form * - Non-indexed draw with GPU-supplied args
(draw-indexed-indirect …) sub-form * - Indexed draw with GPU-supplied args
(occlusion-query …) sub-form No - Brackets draws in an occlusion query
(timestamp-writes …) sub-form No - Records GPU timestamps at pass start/end (at most one)

† A pass needs at least one attachment, of either kind. WebGPU cannot begin a render pass that writes nowhere, so a pass with none is refused by the compiler with the pass named.

*None of the starred keys is required on its own. The usual pass declares a :pipeline and at least one draw sub-form, or replays pre-recorded work via :execute-bundles (the bundle carries its own pipeline and draw). A pass with attachments and nothing else is legal too: it is a clear-only pass, and it compiles. A pass may issue several draws; they run in the order they are written.

Repeat the sub-form once per color target for multiple render targets (MRT). Each color attachment:

Key Type Required Description
:view reference Yes A (texture …), or context-current-texture (the canvas’s current texture)
:resolve-target reference No MSAA resolve target
:clear-value array No Clear color [r g b a], exactly four components
:load-op symbol Yes clear or load
:store-op symbol Yes store or discard

:load-op and :store-op are required. WebGPU defaults neither, so a pass that does not say whether it clears or loads is a pass whose contents depend on whatever was left in the texture.

:clear-value is optional because WebGPU does default it (to transparent black), but it belongs with a clear: stating one beside :load-op load is an error, since WebGPU loads the existing contents and never reads the value.

(color-attachment :view context-current-texture
:clear-value [0.1 0.1 0.1 1.0] :load-op clear :store-op store)

Depth and stencil attachment. At most one per pass.

Key Type Required Description
:view reference Yes The depth/stencil (texture …) this pass tests and writes
:depth-clear-value number Conditional Depth cleared to, in [0,1]
:depth-load-op symbol Conditional clear or load
:depth-store-op symbol Conditional store or discard
:stencil-clear-value number Conditional Stencil cleared to
:stencil-load-op symbol Conditional clear or load
:stencil-store-op symbol Conditional store or discard

Each aspect’s op pair is required exactly when the view’s format carries that aspect, and rejected when it does not. A depth24plus view takes :depth-load-op and :depth-store-op and no stencil ops; a stencil8 view takes the stencil pair and no depth ops; a depth24plus-stencil8 view takes both pairs. The rule reads the (texture …)’s :format, so the compiler reports it on the attachment and names the view:

the depth/stencil attachment of render-pass 'draw' states :stencil-load-op,
but its view 'depthTex' is :format 'depth24plus', which has no stencil aspect

A clear value belongs with a clear. :depth-clear-value is required when :depth-load-op is clear, because WebGPU has no default for it, and stating one beside :depth-load-op load is an error rather than inert input. The stencil pair works the same way.

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

:pipeline myPipeline

Bind groups in order matching @group(N):

:bind-groups [frameBindGroup materialBindGroup objectBindGroup]

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

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

Vertex buffers in order matching vertex stage buffer indices:

:vertex-buffers [positionBuffer normalBuffer uvBuffer]

Pool offsets for ping-pong vertex buffers:

:vertex-buffers [particleBuffer]
:vertex-buffers-pool-offsets [1] ; Use the "other" buffer

Non-indexed draw call:

Key Type Default Description
:vertex-count number required Vertices to draw
:instance-count number 1 Instances
:first-vertex number 0 Offset into the vertex buffers, in vertices
:first-instance number 0 First instance index
(draw :vertex-count 3)
(draw :vertex-count 36 :instance-count 100)

Indexed draw call. It reads the pass’s :index-buffer. The index format (uint16 / uint32) comes from that (buffer …): from its :index-of source for a generated shape’s index companion, or from its own :index-format key when nothing sources it.

Key Type Default Description
:index-count number required Indices to draw
:instance-count number 1 Instances
:first-index number 0 Offset into the index buffer, in indices
:base-vertex number 0 Added to each index before vertex lookup
:first-instance number 0 First instance index
:index-buffer indices
(draw-indexed :index-count 36)

(draw-indirect …) / (draw-indexed-indirect …)

Section titled “(draw-indirect …) / (draw-indexed-indirect …)”

Reads the draw arguments from a GPU buffer rather than from the PNGB bytecode (the compiled payload). The buffer is typically written by a compute pass, so the GPU decides how much to draw. draw-indirect reads GPUDrawIndirectArgs (4 × u32: vertex-count, instance-count, first-vertex, first-instance); draw-indexed-indirect reads GPUDrawIndexedIndirectArgs (5 × u32) and requires the pass’s :index-buffer.

Key Type Default Description
:buffer reference required Buffer holding the args (needs indirect usage)
:offset number 0 Byte offset of the args within the buffer
(buffer :name drawArgs :size 16 :usage [storage indirect])
(render-pass :name draw
(color-attachment :view context-current-texture
:clear-value [0 0 0 1] :load-op clear :store-op store)
:pipeline pipe
(draw-indirect :buffer drawArgs :offset 0))

:viewport takes four numbers (minDepth and maxDepth default to 0 and 1) or all six. :scissor-rect discards fragments outside its rectangle.

(render-pass :name halfScreen
(color-attachment :view context-current-texture
:clear-value [0 0 0 1] :load-op clear :store-op store)
:pipeline pipe
:viewport [0 0 256 512]
:scissor-rect [0 0 256 512]
(draw :vertex-count 3))

:blend-constant is the RGBA value that a target’s constant / one-minus-constant blend factors multiply by (setBlendConstant); it defaults to [0 0 0 0]. :stencil-reference is the value the pipeline’s stencil :compare tests against and that the replace stencil operation writes.

:blend-constant [0.5 0.5 0.5 1]
:stencil-reference 1

:occlusion-query-set plus (occlusion-query …) brackets count the samples a draw passes; (timestamp-writes …) records GPU timestamps at pass start and end. Both are covered on the (query-set …) page.

(render-pass :name draw
(color-attachment :view context-current-texture
:clear-value [0 0 0 1] :load-op clear :store-op store)
(timestamp-writes :query-set ts :beginning-of-pass-write-index 0 :end-of-pass-write-index 1)
:pipeline pipe
:occlusion-query-set occ
(occlusion-query :query-index 0 (draw :vertex-count 36)))
(render-pass :name drawTriangle
(color-attachment :view context-current-texture
:clear-value [0 0 0 1] :load-op clear :store-op store)
:pipeline trianglePipeline
(draw :vertex-count 3))
(texture :name depthTexture :size canvas :format depth24plus :usage [render-attachment])
(render-pass :name render3D
(color-attachment :view context-current-texture
:clear-value [0.1 0.1 0.2 1] :load-op clear :store-op store)
(depth-stencil-attachment :view depthTexture
:depth-clear-value 1.0 :depth-load-op clear :depth-store-op store)
:pipeline meshPipeline
:bind-groups [uniforms materials]
:vertex-buffers [vertices]
:index-buffer indices
(draw-indexed :index-count 1000))
(define :name SAMPLE_COUNT :value 4)
(texture :name msaaTarget :size canvas :format preferred-canvas-format
:usage [render-attachment] :sample-count (* SAMPLE_COUNT 1))
(render-pass :name msaaPass
(color-attachment :view msaaTarget :resolve-target context-current-texture
:clear-value [0 0 0 1] :load-op clear :store-op discard)
:pipeline msaaPipeline
(draw :vertex-count 3))
(render-pass :name drawParticles
(color-attachment :view context-current-texture :load-op load :store-op store)
:pipeline particlePipeline
:bind-groups [uniforms]
:vertex-buffers [quadVerts particleData]
:vertex-buffers-pool-offsets [0 1]
(draw :vertex-count 6 :instance-count 2048))
(buffer :name particles :size 131072 :usage [vertex storage] :pool 2)
(render-pass :name drawParticles
(color-attachment :view context-current-texture
:clear-value [0 0 0 1] :load-op clear :store-op store)
:pipeline renderPipeline
:vertex-buffers [particles]
:vertex-buffers-pool-offsets [0] ; Read from current frame's buffer
(draw :vertex-count 6 :instance-count 2048))
(texture :name offscreen :size [1024 1024] :format rgba8unorm
:usage [render-attachment texture-binding])
(render-pass :name offscreenPass
(color-attachment :view offscreen
:clear-value [0 0 0 0] :load-op clear :store-op store)
:pipeline scenePipeline
(draw :vertex-count 1000))
Rule Error
A name is unique across every form kind duplicate_cross_ref_target
Referenced resources must exist not_cross_ref
(color-attachment …) states :load-op and :store-op missing_required_key
:clear-value must have exactly 4 components vector_length_mismatch
:scissor-rect takes exactly 4 numbers vector_length_mismatch
:viewport takes 4 numbers or 6 union_no_branch_matched
A sub-form head the pass does not accept not_head_member
At most one (depth-stencil-attachment …) or (timestamp-writes …) positional_too_many

Three more are the compiler’s, reading values no schema can see. They carry a located message and no code:

Rule Message shape
A pass needs at least one attachment render-pass 'draw' has no (color-attachment …) and no (depth-stencil-attachment …)
A clear value belongs with a clear color attachment 0 of render-pass 'draw' states a :clear-value beside ':load-op load'
(occlusion-query …) needs :occlusion-query-set render-pass 'draw' brackets draws in an (occlusion-query …) but states no :occlusion-query-set

Maps to render pass encoding:

const passEncoder = commandEncoder.beginRenderPass({
colorAttachments: [{
view: texture.createView(),
clearValue: { r: 0, g: 0, b: 0, a: 1 },
loadOp: 'clear',
storeOp: 'store'
}]
});
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.setVertexBuffer(0, vertexBuffer);
passEncoder.draw(3);
passEncoder.end();