~/guides/test-webgl-on-the-real-gpu

Test WebGL on the real GPU with Playwright

A headed Chromium on the real driver, a warm-up step and a frame counter. This catches context loss and slow frames that headless runs never show.

· 1 min read · written in English

Headless runs use a software renderer, so they cannot see GPU problems (why). This script measures a page on the real GPU.

js
import { chromium } from 'playwright';

const url = process.argv[2];
const browser = await chromium.launch({
  headless: false,
  args: ['--use-angle=d3d11', '--ignore-gpu-blocklist', '--window-position=-2400,0'],
});
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });

// 1. warm-up: is WebGL available at all in this run?
await page.goto('about:blank');
const ok = await page.evaluate(() => !!document.createElement('canvas').getContext('webgl2'));
if (!ok) { console.log('GPU not ready, rerun'); process.exit(2); }

// 2. log every context and every loss
await page.addInitScript(() => {
  const orig = HTMLCanvasElement.prototype.getContext;
  HTMLCanvasElement.prototype.getContext = function (type, ...rest) {
    const ctx = orig.call(this, type, ...rest);
    if (/webgl/.test(type)) {
      console.log(`[gl] ${type} ${ctx ? 'ok' : 'NULL'}`);
      ctx && this.addEventListener('webglcontextlost', () => console.log('[gl] LOST'));
    }
    return ctx;
  };
});
page.on('console', (m) => m.text().startsWith('[gl]') && console.log(m.text()));

// 3. measure frames
await page.goto(url);
await page.waitForTimeout(5000);
const fps = await page.evaluate(() => new Promise((r) => {
  let n = 0; const t0 = performance.now();
  const f = () => (++n, performance.now() - t0 < 2000 ? requestAnimationFrame(f) : r(n / 2));
  requestAnimationFrame(f);
}));
console.log({ url, fps });
await browser.close();

--window-position=-2400,0 keeps the window off-screen while still using the real GPU. Run each page a few times: the first launch after a driver reset can fail on its own.