{"record":{"id":"e2de642be7588192","repo":"remotion-dev/remotion","slug":"failed-to-acquire-2d-context-for-output-canvas","errorCode":null,"errorMessage":"Failed to acquire 2D context for output canvas","messagePattern":"Failed to acquire 2D context for output canvas","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/effects/run-effect-chain.ts","lineNumber":108,"sourceCode":"\t// on `params` so it flows through code/drag override merging.\n\tconst enabledEffects = effects.filter(\n\t\t(e) => !(e.params as {disabled?: boolean}).disabled,\n\t);\n\tconst runs = groupByBackend(enabledEffects);\n\n\tlet currentImage: CanvasImageSource = source;\n\tlet lastTarget: HTMLCanvasElement | null = null;\n\n\tif (runs.length === 0) {\n\t\t// In-place pipeline (e.g. <HtmlInCanvas>: drawElementImage into the same\n\t\t// surface, no further effects) — the bitmap is already on `output`.\n\t\tif (source === output) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst ctx = output.getContext('2d');\n\t\tif (!ctx) {\n\t\t\tthrow new Error('Failed to acquire 2D context for output canvas');\n\t\t}\n\n\t\tctx.clearRect(0, 0, width, height);\n\t\tctx.drawImage(currentImage, 0, 0, width, height);\n\t\treturn true;\n\t}\n\n\tlet needsGpuDevice = false;\n\tfor (const run of runs) {\n\t\tif (run.backend === 'webgpu') {\n\t\t\tneedsGpuDevice = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tconst gpuDevice = needsGpuDevice ? await getGpuDevice() : null;\n\tif (isCancelled()) {\n\t\treturn false;","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/core/src/effects/run-effect-chain.ts#L90-L126","documentation":"Thrown by the imperative effect-chain runner when output.getContext('2d') returns null on the final compositing canvas (HTMLCanvasElement or OffscreenCanvas) during the no-effects / in-place branch. A canvas can only host one context type; once a 'webgl' or 'webgpu' context has been acquired on it, getContext('2d') permanently returns null, and a lost-context state can also produce null.","triggerScenarios":"Calling runEffectChain with an `output` canvas that already obtained a WebGL/WebGPU context; or passing an OffscreenCanvas that was transferred from a worker where its context was already detached; or running in an environment where the GPU process crashed and the 2D context cannot be re-acquired.","commonSituations":"Reusing the same canvas element for both a WebGL/WebGPU effect and the final 2D composite; browser GPU-process crash mid-render; headless render where the canvas pool hands back a canvas that previously held a non-2D context.","solutions":["Ensure the canvas passed as `output` has never acquired a 'webgl' or 'webgpu' context — the effect chain allocates intermediate WebGL canvases itself via CanvasPool, so the output canvas must stay 2D-only.","If using OffscreenCanvas, do not transfer it from a worker with an attached context; create a fresh OffscreenCanvas on the side that calls runEffectChain.","On intermittent GPU-process crashes, recreate the output canvas element (the old one is poisoned) before retrying the chain.","If you authored a custom EffectDefinition whose setup() calls target.getContext('webgl'), make sure it operates on a pooled intermediate canvas, not on `output`."],"exampleFix":"// before: reusing a canvas that already has webgl\nconst canvas = document.createElement('canvas');\nconst gl = canvas.getContext('webgl');\nrunEffectChain({output: canvas, /* ... */}); // throws\n\n// after: keep a dedicated 2D-only output canvas\nconst output = document.createElement('canvas');\nrunEffectChain({output, /* ... */});","handlingStrategy":"validation","validationCode":"// Before calling runEffectChain, confirm `output` can still yield a 2D context.\nfunction canAcquire2D(output: HTMLCanvasElement | OffscreenCanvas): boolean {\n  // Probe without poisoning the canvas: getContext on a canvas that already\n  // has a different context type returns null without changing state only\n  // for the *first* acquisition — so only call this on a fresh canvas.\n  return output.getContext('2d') !== null;\n}\nif (!canAcquire2D(output)) {\n  output = document.createElement('canvas'); // allocate a fresh 2D canvas\n}","typeGuard":"function isFresh2DCanvas(c: HTMLCanvasElement | OffscreenCanvas): boolean {\n  // Heuristic: a canvas that has never acquired another context will return a 2D context.\n  // Do NOT call this on a canvas you intend to keep WebGL on.\n  return c.getContext('2d') !== null;\n}","tryCatchPattern":null,"preventionTips":["Never reuse a WebGL/WebGPU canvas as the effect-chain output; keep a dedicated 2D composite canvas.","Let Remotion's CanvasPool own intermediate WebGL surfaces.","On GPU-process crash, discard the poisoned canvas and allocate a new one before retrying."],"tags":["canvas","effects","webgl","gpu","context"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}