By Shehab Waleed, Founder, Cairo Studio 3 min read
Our homepage is a ring of projects drawn in WebGL. On our own machines it costs about 200ms of total blocking time. On PageSpeed Insights the same page cost 24 seconds and scored 41 on mobile.
We had already worked through the usual list. We trimmed the data the page inlines from 275KB to 82KB, loaded analytics later and lazy loaded images, and each of those was worth doing. None of them could explain 24 seconds.
Why a lab machine disagrees with your laptop
The test machines have no GPU. WebGL still works there, because Chrome falls back to SwiftShader, a software renderer. Two things then go wrong together. Shaders and draw calls all run on the CPU, and Chrome reads the canvas back on every frame, which shows up in a trace as GLES2::ReadPixels. A scene that barely registers on a GPU can block the main thread for the whole test.
Reproducing it locally
The flags are easy to get wrong.
lighthouse https://example.com \
--chrome-flags="--headless=new \
--use-angle=swiftshader \
--enable-unsafe-swiftshader \
--disable-gpu-compositing"
Plain --disable-gpu looks like the same test and is not. It turns WebGL off altogether, so the expensive path never runs and the problem stays hidden.
Detecting it
We ask WebGL which renderer it got.
let cached;
export function softwareGL() {
if (cached !== undefined) return cached;
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const info = gl?.getExtension('WEBGL_debug_renderer_info');
const name = gl
? String(gl.getParameter(
info ? info.UNMASKED_RENDERER_WEBGL : gl.RENDERER
))
: '';
gl?.getExtension('WEBGL_lose_context')?.loseContext();
cached = /swiftshader|llvmpipe|softpipe|software/i.test(name);
} catch {
cached = false;
}
return cached;
}
We check the renderer on purpose, and we do not look for a Lighthouse user agent. Serving test bots a different page is a way of lying to yourself. Anyone whose browser falls back to software rendering gets this path, which includes virtual machines and remote desktops.
What the lighter path changes
- The pixel ratio drops to 0.5.
- Antialiasing is off.
- The entrance spin and post-processing are skipped.
- The scene parks, and redraws only when something changes.
Shaders also compile with compileAsync, so they no longer block on the way in. The home page went from 41 to 80 on mobile, and scores 93 on desktop.
Measuring without fooling yourself
Lighthouse runs from a laptop can swing by 20 points on network jitter and local CPU load alone. Some habits that helped us:
- Use the median of three runs.
- Read environment.benchmarkIndex in the JSON report. On our machine, a value under about 3000 meant something else was hogging the CPU.
- On PageSpeed Insights itself, a value under about 800 means you drew a slow machine. Run it again.
Our mobile home page still has a ceiling, because the hero stays hidden until its entrance animation runs, which delays the largest paint. That one is a design decision, and we have kept it. We wrote up other speed work we do separately.
