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 - Colour 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 colour target for multiple render targets (MRT). Each colour 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 colour [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();

Every key and value on this page traced to the WebGPU name it stands for, with a link to the definition. How the tracing is made, and what keeps it from rotting, is the subject of Where the Words Come From.

(render-pass …) is GPURenderPassDescriptor (MDN) handed to GPUCommandEncoder.beginRenderPass(). The form is both the descriptor and the commands recorded on the encoder the call returns, so its keys come from two places in the spec.

Key WebGPU Note
:name GPUObjectDescriptorBase.label
:occlusion-query-set GPURenderPassDescriptor.occlusionQuerySet
:pipeline GPURenderCommandsMixin.setPipeline()
:vertex-buffers GPURenderCommandsMixin.setVertexBuffer()
:vertex-buffers-pool-offsets PNGine’s own which instance of a pooled buffer each slot binds this frame
:index-buffer GPURenderCommandsMixin.setIndexBuffer()
:bind-groups GPUBindingCommandsMixin.setBindGroup()
:bind-groups-pool-offsets PNGine’s own which instance of a pooled bind group each slot binds this frame
:stencil-reference GPURenderPassEncoder.setStencilReference()
:blend-constant GPURenderPassEncoder.setBlendConstant()
:viewport GPURenderPassEncoder.setViewport()
:scissor-rect GPURenderPassEncoder.setScissorRect()
:execute-bundles GPURenderPassEncoder.executeBundles()
(color-attachment …) GPURenderPassDescriptor.colorAttachments a positional child, not a key
(depth-stencil-attachment …) GPURenderPassDescriptor.depthStencilAttachment a positional child, not a key
(timestamp-writes …) GPURenderPassDescriptor.timestampWrites a positional child, not a key

(color-attachment …) mirrors GPURenderPassColorAttachment (MDN).

Key WebGPU Note
:view GPURenderPassColorAttachment.view
:resolve-target GPURenderPassColorAttachment.resolveTarget
:clear-value GPURenderPassColorAttachment.clearValue
:load-op GPURenderPassColorAttachment.loadOp
:store-op GPURenderPassColorAttachment.storeOp

Not expressible in PNGine yet: GPURenderPassColorAttachment.depthSlice.

(depth-stencil-attachment …) mirrors GPURenderPassDepthStencilAttachment (MDN).

Key WebGPU Note
:view GPURenderPassDepthStencilAttachment.view
:depth-clear-value GPURenderPassDepthStencilAttachment.depthClearValue
:depth-load-op GPURenderPassDepthStencilAttachment.depthLoadOp
:depth-store-op GPURenderPassDepthStencilAttachment.depthStoreOp
:stencil-clear-value GPURenderPassDepthStencilAttachment.stencilClearValue
:stencil-load-op GPURenderPassDepthStencilAttachment.stencilLoadOp
:stencil-store-op GPURenderPassDepthStencilAttachment.stencilStoreOp

Not expressible in PNGine yet: GPURenderPassDepthStencilAttachment.depthReadOnly, GPURenderPassDepthStencilAttachment.stencilReadOnly.

(timestamp-writes …) mirrors GPURenderPassTimestampWrites (MDN).

Key WebGPU Note
:query-set GPURenderPassTimestampWrites.querySet
:beginning-of-pass-write-index GPURenderPassTimestampWrites.beginningOfPassWriteIndex
:end-of-pass-write-index GPURenderPassTimestampWrites.endOfPassWriteIndex

(occlusion-query …) is a call to GPURenderPassEncoder.beginOcclusionQuery() (MDN); each key is one argument.

Key Argument Note
:query-index queryIndex

The draws inside the form run between beginOcclusionQuery() and endOcclusionQuery().

(draw …) is a call to GPURenderCommandsMixin.draw() (MDN); each key is one argument.

Key Argument Note
:vertex-count vertexCount
:instance-count instanceCount
:first-vertex firstVertex
:first-instance firstInstance

(draw-indexed …) is a call to GPURenderCommandsMixin.drawIndexed() (MDN); each key is one argument.

Key Argument Note
:index-count indexCount
:instance-count instanceCount
:first-index firstIndex
:base-vertex baseVertex
:first-instance firstInstance

(draw-indirect …) is a call to GPURenderCommandsMixin.drawIndirect() (MDN); each key is one argument.

Key Argument Note
:buffer indirectBuffer
:offset indirectOffset

(draw-indexed-indirect …) is a call to GPURenderCommandsMixin.drawIndexedIndirect() (MDN); each key is one argument.

Key Argument Note
:buffer indirectBuffer
:offset indirectOffset

load-op (:load-op on (color-attachment …), :depth-load-op on (depth-stencil-attachment …), :stencil-load-op on (depth-stencil-attachment …)) spells the GPULoadOp enum.

Value WebGPU Note
clear "clear"
load "load"

store-op (:store-op on (color-attachment …), :depth-store-op on (depth-stencil-attachment …), :stencil-store-op on (depth-stencil-attachment …)) spells the GPUStoreOp enum.

Value WebGPU Note
store "store"
discard "discard"

Checked against the WebGPU specification at revision b8c0fa9; the links go to the current draft.