~/posts/work-went-black-on-intel-graphics

Why my 3D page went black on Intel graphics

A fly-through page ran at 23 fps and then lost its WebGL context on an ordinary office PC. The cause was a tier check that asked the CPU about the GPU.

· 2 min read · written in English

The /work page on this site is a 3D fly-through: a particle nebula, glass cards for each project, bloom on top. On my development machine it looked fine. Then colleagues opened it and reported two things: every section was black, and scrolling felt frozen.

The symptom

On the machine that reproduced it (Intel UHD Graphics 730, Direct3D 11 through ANGLE) the page did one of two things:

Measuring on the real GPU

My usual headless test runs WebGL on SwiftShader, a software renderer. It is great for screenshots and useless here: a software rasterizer never runs out of GPU memory and never resets. I switched to a headed Chromium with the real driver:

bash
chromium --use-angle=d3d11 --ignore-gpu-blocklist

One more trap showed up immediately. When a page makes Chrome's GPU process reset, Chrome remembers it and refuses new WebGL contexts for that page with "Web page caused context loss and was blocked". Some test runs started in that state before any of my code ran, so I added a warm-up step: create a context on about:blank first, and throw away runs where even that fails.

The cause

The page picked its quality tier from navigator.hardwareConcurrency. A 12-thread CPU said "high", so an integrated GPU got the full path: 52,000 particles, MSAA on the post-processing target and bloom at a 1.75 device-pixel ratio.

CPU cores say nothing about the GPU.

The fix

  1. Ask the GPU. WEBGL_debug_renderer_info exposes the renderer string. Intel UHD/Iris, mobile GPUs and software renderers get the low tier: fewer particles, lower DPR, no MSAA on the composer.
  2. Degrade at runtime. If frames stay slower than ~34 ms for a while, drop bloom first, then DPR, then half of the particles, one step at a time.
  3. Survive context loss. Listen for webglcontextlost, call preventDefault(), stop the loop and show the plain project list instead of a black screen.
  4. Escape hatch. ?q=low and ?q=high override the guess.
before after
Intel UHD 730, idle 23 fps ~60 fps
context loss black screen project list

What I keep from this