BabylonJS/Babylon.js · error

_readPixelsAsync only work on WebGL2+

Error message

_readPixelsAsync only work on WebGL2+

What it means

_readPixelsAsync relies on WebGL2's PIXEL_PACK_BUFFER and fence-sync readback, which do not exist in WebGL1. The engine guards on _webGLVersion < 2 and throws immediately rather than silently falling back.

Source

Thrown at packages/dev/core/src/Engines/engine.pure.ts:1027

                        return false;
                    }
                    return true;
                },
                resolve,
                reject,
                intervalms
            );
        });
    }

    /**
     * This function might return null synchronously, so it is technically not async.
     * @internal
     */
    // eslint-disable-next-line no-restricted-syntax
    public _readPixelsAsync(x: number, y: number, w: number, h: number, format: number, type: number, outputBuffer: ArrayBufferView): Nullable<Promise<ArrayBufferView>> {
        if (this._webGLVersion < 2) {
            throw new Error("_readPixelsAsync only work on WebGL2+");
        }

        const gl = <WebGL2RenderingContext>(this._gl as any);
        const buf = gl.createBuffer();
        gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
        gl.bufferData(gl.PIXEL_PACK_BUFFER, outputBuffer.byteLength, gl.STREAM_READ);
        gl.readPixels(x, y, w, h, format, type, 0);
        gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);

        const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
        if (!sync) {
            return null;
        }

        gl.flush();

        // eslint-disable-next-line github/no-then
        return this._clientWaitAsync(sync, 0, 10).then(() => {

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Use the synchronous engine.readPixels() on WebGL1 instead of _readPixelsAsync.
  2. Ensure the runtime supports WebGL2 (update browser/headless flags: --enable-unsafe-swiftshader or use a GPU-enabled CI runner).
  3. Check engine.webGLVersion (or getCaps().textureFloatRender etc.) at startup and branch your code accordingly.

Example fix

// before
const data = await engine._readPixelsAsync(x, y, w, h, fmt, type, buf);
// after
if (engine.webGLVersion >= 2) {
  const data = await engine._readPixelsAsync(x, y, w, h, fmt, type, buf);
} else {
  engine.readPixels(x, y, w, h, buf);
}
Defensive patterns

Strategy: validation

Validate before calling

if (engine.webGLVersion < 2) {
  // use sync readPixels instead of _readPixelsAsync
}

Type guard

function supportsAsyncReadback(e) {
  return (e as { webGLVersion: number }).webGLVersion >= 2;
}

Try / catch

try {
  await engine._readPixelsAsync(x, y, w, h, fmt, type, buf);
} catch (e) {
  if (/only work on WebGL2/.test(e.message)) {
    engine.readPixels(x, y, w, h, buf);
  }
}

Prevention

When it happens

Trigger: Calling engine._readPixelsAsync(...) on an engine running WebGL1 (old browser, forced WebGL1 fallback, or a context created without WebGL2 support).

Common situations: Legacy Safari/iOS versions before WebGL2 support, running on a machine/driver where WebGL2 is unavailable so the engine silently falls back, headless environments (old headless Chrome) lacking WebGL2.

Related errors


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/f916eab81c8f9660. Report an issue: GitHub.