WebGPU
Take screenshots and record video of WebGPU pages — three.js WebGPURenderer, Babylon.js, or raw WebGPU — with headless Chrome.
Headless Chrome does not expose WebGPU by default. Without setup this fails silently: the page loads, the screenshot command succeeds, and the canvas is black. The --webgpu flag enables a launch preset that makes WebGPU work, including in GPU-less containers and CI.
Quick start
Also available as the AGENT_BROWSER_WEBGPU environment variable or "webgpu": true in agent-browser.json.
What the preset does
| Platform | Flags applied |
|---|---|
| All | --enable-unsafe-webgpu (WebGPU is hidden in headless and GPU-blocklisted environments by default) |
| Linux | --enable-features=Vulkan --use-angle=vulkan --use-vulkan=swiftshader --use-webgpu-adapter=swiftshader --disable-vulkan-surface — WebGPU through SwiftShader's software Vulkan; no GPU required |
macOS uses the hardware Metal backend and Windows uses D3D — nothing extra to install on either.
Platform support
| Platform | WebGPU rendering (headless) | Screenshots of WebGPU canvases |
|---|---|---|
| macOS | works | works headless |
| Windows | works (hardware D3D) | headless captures black — use --headed on a logged-in desktop |
| Linux | works (SwiftShader Vulkan) | headless capture not supported upstream — add --headed (virtual display starts automatically) |
The Windows/Linux screenshot gap is an upstream headless-Chrome limitation: WebGPU canvas presentation never reaches the headless compositor even though rendering works (verifiable by pixel readback). No flag combination is known to fix it.
On Linux, --headed is all you need even on displayless servers and containers: when no DISPLAY is set and Xvfb is installed (apt-get install -y xvfb), agent-browser starts a private virtual display for the browser and tears it down with it (AGENT_BROWSER_NO_XVFB=1 opts out):
On Windows, the headed session must run in a logged-in desktop session — launching from an ssh/service context is not enough (schedule the launch on the interactive desktop with schtasks /IT, then drive the session from anywhere).
Verify with doctor
The probe launches a scratch session with the preset and pixel-checks two stages separately — WebGPU failures are silent black, so only pixel assertions prove anything:
- render — requests an adapter (with retries for cold-start nulls), clears an offscreen texture red through a real render pass, and reads the buffer back. Reports the adapter in use.
- screenshot — decodes an actual screenshot of a presenting canvas, proving the capture path end to end.
On macOS both checks pass headless; on Windows/Linux the second check tells you when the platform needs --headed for capture. Run agent-browser doctor --webgpu --headed to validate that path itself — on displayless Linux the probe starts its own Xvfb, so both checks should pass.
Linux, containers, and CI
The SwiftShader Vulkan path needs the system Vulkan loader and Mesa ICD. Without them requestAdapter() returns null:
Container recipe (xvfb only needed for the screenshot path):
No real GPU or /dev/dri is required. To prefer hardware Vulkan on a Linux machine that has working drivers, override both the Vulkan driver and the adapter — the preset pins --use-vulkan=swiftshader, so overriding only the adapter still enumerates SwiftShader (user args win over the preset):
Gotchas
Secure contexts only. navigator.gpu exists only in secure contexts: https://, http://localhost, and file:// qualify; a plain http:// LAN address does not, regardless of flags.
Don't screenshot too early. WebGPU apps initialize asynchronously — three.js WebGPURenderer awaits an async init() before the first frame. A screenshot at page load captures a blank canvas with no error. Wait for an app-specific ready signal, or give the render loop a couple of frames:
three.js falls back silently. When no WebGPU adapter is available, WebGPURenderer switches to its WebGL2 backend without an error. To check what a page actually got:
Custom pixel readbacks: avoid canvas snapshots. drawImage(webgpuCanvas, ...) depends on presentation timing and reads transparent black on some platforms even when rendering works. Render to an offscreen texture and read it back with copyTextureToBuffer + mapAsync — deterministic on every platform (this is how the doctor probe proves rendering).
SwiftShader is a CPU rasterizer. Simple scenes render fine; heavy scenes run at single-digit FPS. That rarely matters for screenshots, but for smooth video of complex scenes use hardware (macOS/Windows, or Linux with real Vulkan drivers and --use-vulkan=native,--use-webgpu-adapter=default).