remotion-dev/remotion · error · Error

Failed to create WebGL program

Error message

Failed to create WebGL program

What it means

Thrown inside linkProgram() in fisheye-runtime.ts when gl.createProgram() returns null (fisheye-runtime.ts:49-52). A null program object means the GL driver would not allocate a program handle — context lost or program-object pool exhausted. Both fisheye shaders have compiled, but there is no program to link them into.

Source

Thrown at packages/effects/src/fisheye/fisheye-runtime.ts:51

	gl.shaderSource(shader, source);
	gl.compileShader(shader);
	if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
		const log = gl.getShaderInfoLog(shader);
		gl.deleteShader(shader);
		throw new Error(`Shader compile failed: ${log ?? '(no log)'}`);
	}

	return shader;
};

const linkProgram = (
	gl: WebGL2RenderingContext,
	vs: WebGLShader,
	fs: WebGLShader,
): WebGLProgram => {
	const program = gl.createProgram();
	if (!program) {
		throw new Error('Failed to create WebGL program');
	}

	gl.attachShader(program, vs);
	gl.attachShader(program, fs);
	gl.linkProgram(program);
	if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
		const log = gl.getProgramInfoLog(program);
		gl.deleteProgram(program);
		throw new Error(`Program link failed: ${log ?? '(no log)'}`);
	}

	return program;
};

const createProgram = (
	gl: WebGL2RenderingContext,
	vertexSource: string,
	fragmentSource: string,

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Render with the Angle backend (--gl=angle / chromiumOptions.gl='angle' / Angle in Studio).
  2. If using fisheye-runtime directly, always pair setupFisheye() with cleanupFisheye() so gl.deleteProgram frees the handle.
  3. Reduce simultaneous WebGL2 effects and reuse identical fisheye() params via calculateKey.
  4. Verify gl.isContextLost() is false and restore the context otherwise.
  5. Update GPU drivers or move to a host with adequate GL resources.

Example fix

// before
const program = gl.createProgram();
if (!program) {
  throw new Error('Failed to create WebGL program');
}

// after (ensure cleanup when using the runtime directly)
const state = setupFisheye(canvas);
try { /* applyFisheye(...) */ } finally { cleanupFisheye(state); }
Defensive patterns

Strategy: try-catch

Validate before calling

const canAllocateProgram = (gl: WebGL2RenderingContext): boolean => {
  if (gl.isContextLost()) return false;
  const probe = gl.createProgram();
  if (!probe) return false;
  gl.deleteProgram(probe);
  return true;
};

Try / catch

import {setupFisheye, cleanupFisheye} from '@remotion/effects/fisheye-runtime';

let state;
try {
  state = setupFisheye(canvas);
} catch (err) {
  if (err instanceof Error && err.message === 'Failed to create WebGL program') {
    // free other fisheye states, retry on Angle
    throw err;
  }
  throw err;
}
try { /* applyFisheye(state, ...) */ } finally { cleanupFisheye(state); }

Prevention

When it happens

Trigger: setupFisheye() -> createProgram() -> linkProgram() reaches gl.createProgram() and gets null. This occurs when the WebGL2 context is lost or when leaked programs (from setupFisheye() without cleanupFisheye(), or many effect() instances) have exhausted the driver's program-object budget.

Common situations: Apps using fisheye-runtime directly without cleanupFisheye(); long-running Studio leaking programs; compositions with many simultaneous fisheye()/WebGL2 effects; CI on hosts whose GPU process crashed.

Related errors


AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12). Data as JSON: /api/errors/0f6fbe37205a2d72. Report an issue: GitHub.