{"record":{"id":"f1b0ea502755c4a5","repo":"remotion-dev/remotion","slug":"failed-to-create-webgl-vertex-array-f1b0ea","errorCode":null,"errorMessage":"Failed to create WebGL vertex array","messagePattern":"Failed to create WebGL vertex array","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/effects/src/pixel-dissolve.ts","lineNumber":247,"sourceCode":"\tgl.linkProgram(program);\n\tif (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n\t\tconst log = gl.getProgramInfoLog(program);\n\t\tgl.deleteProgram(program);\n\t\tthrow new Error(`Pixel Dissolve program link failed: ${log ?? '(no log)'}`);\n\t}\n\n\treturn program;\n};\n\nconst createFullscreenQuad = (\n\tgl: WebGL2RenderingContext,\n): {\n\treadonly vao: WebGLVertexArrayObject;\n\treadonly vbo: WebGLBuffer;\n} => {\n\tconst vao = gl.createVertexArray();\n\tif (!vao) {\n\t\tthrow new Error('Failed to create WebGL vertex array');\n\t}\n\n\tgl.bindVertexArray(vao);\n\n\tconst vbo = gl.createBuffer();\n\tif (!vbo) {\n\t\tthrow new Error('Failed to create WebGL buffer');\n\t}\n\n\tgl.bindBuffer(gl.ARRAY_BUFFER, vbo);\n\tgl.bufferData(\n\t\tgl.ARRAY_BUFFER,\n\t\tnew Float32Array([-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1]),\n\t\tgl.STATIC_DRAW,\n\t);\n\n\treturn {vao, vbo};\n};","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/effects/src/pixel-dissolve.ts#L229-L265","documentation":"Thrown by pixelDissolve's createFullscreenQuad helper when gl.createVertexArray() returns null during effect setup. WebGL2 returns null instead of throwing when the GL context is lost, when the implementation runs out of VAO slots, or when the underlying GPU/SwiftShader cannot service the request. The guard converts that silent null into an explicit failure so the rest of setup does not dereference null.","triggerScenarios":"Calling pixelDissolve() and mounting it on a composition whose setup runs in an environment where the WebGL2 context is already lost or has exhausted vertex-array-object capacity. The exact line is the first allocation inside createFullscreenQuad, before any buffer or texture work.","commonSituations":"Headless rendering on Remotion Lambda/CI where Chrome falls back to SwiftShader; rendering many concurrent effect instances so VAO pool is drained; a previous frame triggered WEBGL_lose_context; running in a VM or RDP session with no real GPU; browser tab backgrounded long enough for the driver to reclaim the context.","solutions":["Confirm WebGL2 actually initializes first: in a browser console run `const c=document.createElement('canvas'); const gl=c.getContext('webgl2'); console.log(gl && gl.createVertexArray());` — null/undefined means the environment, not your code.","On Remotion Lambda ensure the function uses the published Chrome for Testing layer with GPU/SwiftShader enabled; do not strip the bundled Chromium.","Reduce the number of simultaneously mounted WebGL2 effects in one composition — each pixelDissolve/pixelate/blur allocates its own context and VAO.","If the context was lost mid-render, catch the WEBGL_lose_context event and remount the effect (re-run its setup) instead of reusing the stale state.","Reproduce locally with `chrome --disable-gpu --use-gl=swiftshader` to confirm the failure is environment-driven."],"exampleFix":"// before — assumes context always has capacity\nimport {pixelDissolve} from '@remotion/effects';\nconst effect = pixelDissolve(); // throws 'Failed to create WebGL vertex array' on starved GPU\n\n// after — feature-detect before mounting the effect\nfunction supportsWebGL2VAO(): boolean {\n  try {\n    const c = document.createElement('canvas');\n    const gl = c.getContext('webgl2');\n    return !!gl && !!gl.createVertexArray();\n  } catch {\n    return false;\n  }\n}\n// branch on supportsWebGL2VAO() before applying pixelDissolve","handlingStrategy":"try-catch","validationCode":"function webgl2Ready(): boolean {\n  try {\n    const c = document.createElement('canvas');\n    const gl = c.getContext('webgl2');\n    if (!gl || gl.isContextLost()) return false;\n    const vao = gl.createVertexArray();\n    if (vao) gl.deleteVertexArray(vao);\n    return vao !== null;\n  } catch {\n    return false;\n  }\n}\n// call webgl2Ready() before mounting pixelDissolve","typeGuard":"function isWebGL2(gl: WebGLRenderingContext | null): gl is WebGL2RenderingContext {\n  return !!gl && typeof (gl as WebGL2RenderingContext).createVertexArray === 'function';\n}","tryCatchPattern":"try {\n  const effect = pixelDissolve();\n} catch (err) {\n  if ((err as Error).message === 'Failed to create WebGL vertex array') {\n    // surface a friendly 'WebGL2 unavailable in this environment' message\n  }\n  throw err;\n}","preventionTips":["Feature-detect WebGL2 + VAO allocation in the render environment before applying GPU effects.","On Remotion Lambda, always use the published Chrome for Testing layer; never a stripped Chromium.","Listen for 'webglcontextlost' on the canvas and remount effects on 'webglcontextrestored'.","Limit the number of simultaneously mounted WebGL2 effects in one composition."],"tags":["webgl","gpu","environment","pixel-dissolve","rendering","headless"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}