vgpu3 symbolsView source ↗

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

TypeScript
1
import type { Bundle, BundleOptions, BundleRecorder } from "vgpu";

Signature

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

ParamTypeRequiredDefaultNotes
bundle.optsBundleOptionsRecording options.
opts.targetTarget | TargetSignatureFormats, depth format, and sample count are recorded. Signature form is { colors: [...], depth?, sampleCount? }; colors is required.
opts.labelstring`bundle${n}`Bundle id and GPU label. Auto id increments from bundle1.
bundle.cb(recorder: BundleRecorder) => voidCalled immediately to encode commands.
recorder.draw.drawableDraw | EffectDraw or fullscreen effect to encode into the bundle.
recorder.draw.optsDrawCallOptions{}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.bundlesreadonly 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

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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));
});
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
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 blendConstant cannot be recorded: render bundle encoders have no way to set the pass blend constant. Recording throws VGPU-BUNDLE-BLEND-CONSTANT; use FramePass.draw for those draws.
  • Draws whose stencil has ref cannot be recorded either: render bundle encoders have no way to set the pass stencil reference. Recording throws VGPU-BUNDLE-STENCIL-REF; stencil pipeline state without ref records fine.
  • See also: FramePass.bundles, Draw, Effect, Surface, Target, createRenderBundle.