Symbols in this topic
Bundle
Main API (vgpu) render bundle recorded by bundle(gpu, { target }, cb). Bundles freeze commands, attachment formats, sample count, and bind-group identities for static work; FramePass.bundles() checks signature and resource staleness (VGPU-R3-BUNDLE-STALE) when replaying.
Import
import type { Bundle, BundleOptions, BundleRecorder } from "vgpu";Signature
import type { Draw, DrawCallOptions, Effect, Target, TargetSignature } from "vgpu";
interface BundleOptions {
readonly target: Target | TargetSignature;
readonly label?: string;
}
interface BundleRecorder {
draw(drawable: Draw | Effect, opts?: DrawCallOptions): void;
}
interface Bundle {
readonly id: string;
readonly gpu: GPURenderBundle;
}Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| bundle.opts | BundleOptions | ✔ | — | Recording options. |
| opts.target | Target | TargetSignature | ✔ | — | Formats, depth format, and sample count are recorded. Signature form is { colors: [...], depth?, sampleCount? }; colors is required. |
| opts.label | string | ✖ | `bundle${n}` | Bundle id and GPU label. Auto id increments from bundle1. |
| bundle.cb | (recorder: BundleRecorder) => void | ✔ | — | Called immediately to encode commands. |
| recorder.draw.drawable | Draw | Effect | ✔ | — | Draw or fullscreen effect to encode into the bundle. |
| recorder.draw.opts | DrawCallOptions | ✖ | {} | Counts and offsets captured in the recorded commands. indirect records fine — render bundle encoders support drawIndirect/drawIndexedIndirect — and the GPU re-reads the argument buffer on every replay. |
| framePass.bundles.bundles | readonly Bundle[] | ✔ | — | Replayed bundles; must be created by bundle(). |
Returns: bundle(gpu) returns Bundle with id and native gpu render bundle; BundleRecorder.draw() returns void; FramePass.bundles() returns void.
Throws: VGPU-R3-BUNDLE-STALE when replay target formats/depth/sample count differ from the recorded signature or when a recorded draw's bound resource identity / claimed group changed after recording; VGPU-R3-BUNDLE-INVALID when replay receives an object not created by bundle(); VGPU-BUNDLE-BLEND-CONSTANT when recording a draw with blendConstant (the blend constant is render-pass state that render bundle encoders cannot set; encode such draws in a frame pass instead); VGPU-BUNDLE-STENCIL-REF when recording a draw whose stencil has ref (the stencil reference is likewise render-pass state; stencil state without ref records fine); VGPU-SURFACE-DISPOSED when replaying against a disposed surface; draw binding errors such as VGPU-R1-BINDING-NEVER-SET can throw during recording. Signature mismatch messages print both recorded and actual signature keys.
Examples
import { init, bundle, draw, frame, target } from "vgpu/mock";
const gpu = await init();
const colorTarget = target(gpu, { size: [64, 64] });
const drawable = draw(gpu, { shader: `
@vertex fn vs_main(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
var p = array<vec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));
return vec4f(p[vi], 0, 1);
}
@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1, 1, 0, 1); }
` });
const statics = bundle(gpu, { target: colorTarget, label: "static" }, (recorded) => {
recorded.draw(drawable);
});
frame(gpu, (currentFrame) => {
currentFrame.pass({ target: colorTarget }, (pass) => pass.bundles(statics));
});import { init, bundle, effect, frame, surface } from "vgpu/mock";
const gpu = await init();
const canvasSurface = surface(gpu, mockCanvas());
const shader = effect(gpu, `@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1); }`);
let statics = bundle(gpu, { target: canvasSurface, label: "surfaceStatics" }, (recorded) => recorded.draw(shader));
canvasSurface.onResize(() => {
statics = bundle(gpu, { target: canvasSurface, label: "surfaceStatics" }, (recorded) => recorded.draw(shader));
});
frame(gpu, (currentFrame) => {
currentFrame.pass({ target: canvasSurface }, (p) => p.bundles(statics));
});
function mockCanvas(): HTMLCanvasElement {
return {
width: 10,
height: 10,
clientWidth: 10,
clientHeight: 10,
getContext() { return { configure() {}, unconfigure() {}, getCurrentTexture() { return { createView: () => ({}) }; } }; },
} as unknown as HTMLCanvasElement;
}import { init, bundle, clock, effect, frame, pingPong } from "vgpu/mock";
const gpu = await init();
const ping = pingPong(gpu, 32, 32);
const shader = effect(gpu, `@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1); }`);
const even = bundle(gpu, { target: ping.write }, (b) => b.draw(shader));
ping.swap();
const odd = bundle(gpu, { target: ping.write }, (b) => b.draw(shader));
ping.swap();
frame(gpu, (currentFrame) => {
currentFrame.pass({ target: ping.write }, (p) => p.bundles(clock(gpu).frameCount % 2 ? odd : even));
});Signature-arm recording
bundle(gpu, { target: { colors: ["bgra8unorm"], depth: "depth24plus", sampleCount: 4 } }, cb) records before a target exists. This relaxes only the replay target: any resources sampled by draws still need to be set before recording. Cold signature recording creates missing pipelines synchronously, which can jank; pre-warm first with await draw.compile(signature) or await effect.compile(signature).
For future canvas surfaces, use navigator.gpu.getPreferredCanvasFormat() when building the signature. A bundle recorded for bgra8unorm will not replay on an rgba8unorm surface, and the stale error prints both keys.
Notes
- Bundles match replay targets by render signature, not size. They survive resizing the target they draw onto.
- Re-record when the bundle samples a resized target; vgpu detects the changed texture identity and reports
VGPU-R3-BUNDLE-STALE. surface.onResize(...)fires immediately, so the same re-recording callback can initialize and refresh bundles that sample resized resources.- Bundles freeze bind group identities, not buffer contents. Updating JS-owned packed values in-place is safe; rebinding a different texture/buffer/sampler stales the bundle.
- Draws with
blendConstantcannot be recorded: render bundle encoders have no way to set the pass blend constant. Recording throwsVGPU-BUNDLE-BLEND-CONSTANT; useFramePass.drawfor those draws. - Draws whose
stencilhasrefcannot be recorded either: render bundle encoders have no way to set the pass stencil reference. Recording throwsVGPU-BUNDLE-STENCIL-REF; stencil pipeline state withoutrefrecords fine. - See also:
FramePass.bundles,Draw,Effect,Surface,Target,createRenderBundle.