---
title: Bundle
description: 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.
---

# Bundle



## Import

```ts
import type { Bundle, BundleOptions, BundleRecorder } from "vgpu";
```

## Signature

```ts
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

```ts
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));
});
```

```ts
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;
}
```

```ts
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`.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)