pixijs/pixijs · error · Error
endBundle called without an active render bundle.
Error message
endBundle called without an active render bundle.
What it means
Thrown by GpuEncoderSystem.endBundle() when no render bundle is currently being recorded. The check looks for a non-null renderPassEncoder that has a 'finish' method (only bundle encoders have finish). A null renderPassEncoder or a real pass encoder (no finish) means there is nothing to end, so the call is rejected.
Source
Thrown at src/rendering/renderers/gpu/GpuEncoderSystem.ts:156
// A bundle encoder exposes the same render/bind command API as the pass, so it stands in as
// the write target while recording. The real pass stays in _passEncoder and is restored by
// endBundle.
this.renderPassEncoder = this._gpu.device.createRenderBundleEncoder(descriptor);
}
/**
* Finishes recording the current render bundle and restores the previous render pass encoder.
* @returns The recorded {@link GPURenderBundle} ready to be executed via {@link executeBundle}.
*/
public endBundle(): GPURenderBundle
{
const encoder = this.renderPassEncoder;
// `finish` only exists on a bundle encoder, so it both narrows the type for the call below
// and guards against endBundle being called without an active bundle.
if (!encoder || !('finish' in encoder))
{
throw new Error('endBundle called without an active render bundle.');
}
const bundle = encoder.finish();
this.renderPassEncoder = this._passEncoder;
this._clearCache();
return bundle;
}
/**
* Replays a previously recorded render bundle on the current render pass.
* The bound state cache is cleared since the bundle may set its own pipeline, bind groups, and buffers.
* @param bundle - The render bundle to execute.
*/
public executeBundle(bundle: GPURenderBundle): void
{
this._clearCache();View on GitHub (pinned to 4b141e3ced)
Solutions
- Track your own recording flag and only call endBundle when it is true.
- Ensure every endBundle corresponds to exactly one preceding successful beginBundle.
- Reset your flag in a finally block so a second cleanup pass does not call endBundle again.
Example fix
// before
let bundle = encoder.endBundle(); // throws if no bundle active
// after
let recording = false;
encoder.beginBundle();
recording = true;
try {
recordDraws();
} finally {
if (recording) { bundle = encoder.endBundle(); recording = false; }
} Defensive patterns
Strategy: validation
Validate before calling
let recording = false;
encoder.beginBundle(); recording = true;
// ... record ...
if (recording) { encoder.endBundle(); recording = false; } Type guard
function hasActiveBundle(encoder: GpuEncoderSystem): boolean {
return !!encoder.renderPassEncoder && 'finish' in encoder.renderPassEncoder;
} Prevention
- Pair every beginBundle with exactly one endBundle via a flag.
- Reset the flag in finally so cleanup paths don't double-end.
- Never call endBundle from a generic finalize routine without checking state.
When it happens
Trigger: Calling endBundle() without a prior successful beginBundle(), or after endBundle() has already been called once (double-end). Also when the render pass itself has ended, leaving renderPassEncoder null.
Common situations: Unbalanced begin/end calls. Calling endBundle in a cleanup/finalize path that runs even when recording never started. A previous endBundle already restored the pass encoder.
Related errors
- Cannot begin a new render bundle while one is already being
- [HTMLSource] GPUQueue.copyElementImageToTexture is not avail
- [BindGroup] the resource bound as '${j}' was destroyed while
- WebGPU not supported. No GPU adapter was returned by navigat
- UniformBufferBatch: array is too large: ${size * 4}
AI-assisted analysis of pixijs/pixijs@4b141e3ced (2026-08-12).
Data as JSON: /api/errors/56280710c847bd0a.
Report an issue: GitHub.