(render-bundle …)
Pre-records a pipeline plus its vertex buffers, bind groups and one draw. A
(render-pass …) then replays it via
:execute-bundles instead of declaring an inline pipeline and draw.
A bundle is recorded once and replayed every frame, which cuts the per-frame encoding cost of a draw sequence that never changes; it is the WebGPU equivalent of a display list.
Syntax
Section titled “Syntax”(render-bundle :name bundle :pipeline pipe :color-formats [preferred-canvas-format] :depth-stencil-format depth24plus :sample-count 1 :vertex-buffers [verts] :bind-groups [g] (draw :vertex-count 3 :instance-count 1))
(render-pass :name draw (color-attachment :view context-current-texture :load-op clear :store-op store) (depth-stencil-attachment :view depthTex :depth-load-op clear :depth-store-op store) :execute-bundles [bundle])Keys & Sub-forms
Section titled “Keys & Sub-forms”| Key / Sub-form | Type | Required | Default | Description |
|---|---|---|---|---|
:name |
symbol | Yes | - | Unique bundle name |
:pipeline |
reference | Yes | - | Render pipeline recorded into the bundle (a bundle’s descriptor is its own layout) |
:color-formats |
array | Yes | - | Colour attachment formats the bundle is recorded against (at least one, all colour formats) |
:depth-stencil-format |
symbol | No | - | Depth/stencil format the bundle is recorded against |
:sample-count |
number | No | 1 |
Sample count the bundle is recorded against |
:vertex-buffers |
array | No | - | Buffers bound to vertex slots 0..N in order |
:bind-groups |
array | No | - | Bind groups set at @group 0..N in order |
:index-buffer |
reference | No | - | Index buffer when the bundle draws indexed |
(draw …) |
sub-form | * | - | The recorded non-indexed draw |
(draw-indexed …) |
sub-form | * | - | The recorded indexed draw |
*A bundle records exactly one draw, and it is a (draw …) or
(draw-indexed …) child: the same two forms a render pass issues, with the
same keys, so a recorded draw can state :instance-count, :first-vertex,
:base-vertex and :first-instance like any other. Zero is
positional_missing, two is positional_too_many, and (draw-indirect …) is
not among the heads a bundle accepts.
:color-formats is required and needs at least one entry, matching
GPURenderPassLayout.colorFormats. Write [preferred-canvas-format] for a
bundle replayed into a pass that draws to the canvas. The list holds colour
formats only: a depth format there is rejected on the spot, rather than taken
as a way to record a depth-only bundle.
Attachment compatibility
Section titled “Attachment compatibility”:color-formats, :depth-stencil-format and :sample-count record the
attachment shape the bundle is compatible with. Together they are WebGPU’s
GPURenderPassLayout, and three things have to agree on it: the bundle
states it, the :pipeline it records derives one from its colour targets,
depth-stencil state and multisample count, and the replaying pass derives one
from its attachments. Any disagreement between the three is a compile error,
and each is reported where it is written:
render-bundle 'bundle' records :color-formats slot 0 as 'rgba8unorm'but pipeline 'pipe' declares color target 0 as :format 'bgra8unorm'
render-bundle 'bundle' records 4 sample(s) but pipeline 'pipe' hassample count 1
color attachment 0 of render-pass 'draw' writes texture 'offscreen' with:format 'bgra8unorm' but replayed render-bundle 'bundle' was recordedagainst 'rgba8unorm'Two of those are easy to miss, because the value is not in the form you are
reading. A pass never states its formats or its sample count: WebGPU takes them
from the attachments’ textures, so a bundle can lose to a (texture …) three
forms away. And an omitted :sample-count is 1, so a bundle that says nothing
disagrees with a 4-sample pipeline.
An absent :depth-stencil-format is a value too, meaning “no depth
attachment”, not a wildcard: omitting it under a pipeline that declares
(depth-stencil …) is an error, and so is stating one under a pipeline that
does not.
The pass omits its own pipeline
Section titled “The pass omits its own pipeline”A pass that replays bundles does not declare :pipeline or a draw sub-form; the
bundle carries both. That is why :pipeline is optional on a render pass.
Examples
Section titled “Examples”Instanced draw recorded once
Section titled “Instanced draw recorded once”(define :name NUM_ASTEROIDS :value 500)
(render-bundle :name asteroids :pipeline meshPipeline :color-formats [preferred-canvas-format] :depth-stencil-format depth24plus :sample-count 1 :vertex-buffers [vertices] :bind-groups [sceneGroup] (draw :vertex-count 36 :instance-count NUM_ASTEROIDS))
(render-pass :name draw (color-attachment :view context-current-texture :clear-value [0 0 0 1] :load-op clear :store-op store) (depth-stencil-attachment :view depthTex :depth-clear-value 1.0 :depth-load-op clear :depth-store-op store) :execute-bundles [asteroids])
(frame :name main :perform [draw])Indexed bundle
Section titled “Indexed bundle”(render-bundle :name teapot :pipeline meshPipeline :color-formats [preferred-canvas-format] :depth-stencil-format depth24plus :vertex-buffers [vertexBuffer] :index-buffer indexBuffer :bind-groups [sceneGroup] (draw-indexed :index-count 2976))Several bundles in one pass
Section titled “Several bundles in one pass”:execute-bundles takes a list, replayed in order:
(render-pass :name draw (color-attachment :view context-current-texture :clear-value [0 0 0 1] :load-op clear :store-op store) :execute-bundles [terrain props foliage])Validation Rules
Section titled “Validation Rules”| Rule | Error |
|---|---|
| A name is unique across every form kind | duplicate_cross_ref_target |
| Referenced pipeline, buffers and bind groups must exist | not_cross_ref |
A pass’s :execute-bundles entries must name declared bundles |
not_cross_ref |
:color-formats is required |
missing_required_key |
:color-formats needs at least one entry |
vector_too_short |
Exactly one (draw …) / (draw-indexed …) child |
positional_missing / positional_too_many |
| Any other sub-form head | not_head_member |
The layout-equality rules are the compiler’s, since they compare values in three different forms. They carry a located message and no code; the shapes are in Attachment compatibility above, plus:
render-bundle 'bundle' lists 'depth24plus' at :color-formats slot 0; thatlist holds COLOR attachment formats
pipeline 'pipe' declares a depth-stencil state but render-bundle 'bundle'records no :depth-stencil-formatWebGPU Mapping
Section titled “WebGPU Mapping”const encoder = device.createRenderBundleEncoder({ colorFormats: ['bgra8unorm'], depthStencilFormat: 'depth24plus', sampleCount: 1,});encoder.setPipeline(pipeline);encoder.setVertexBuffer(0, verts);encoder.setBindGroup(0, group);encoder.draw(3, 1, 0, 0);const bundle = encoder.finish();
// each frame:pass.executeBundles([bundle]);Related
Section titled “Related”(render-pass …)- Replays bundles via:execute-bundles(render-pipeline …)- The recorded pipeline(bind-group …)- Resources recorded into the bundle