Budget the GPU for WebGL
SHOULD: WebGL cost scales with pixels and object count, not DOM size. Cap pixel ratio at 2 (setPixelRatio(Math.min(devicePixelRatio, 2)) — fragment cost is quadratic in DPR), keep draw calls low by merging/instancing static geometry (~<100/frame), and dispose() geometries, materials, and textures on unmount because GPU memory is not garbage-collected. Keep mobile textures ≤1024².
Cap pixel ratio at ~2, keep draw calls low, and dispose GPU resources — WebGL cost scales with pixels and object count, not DOM size
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
useEffect(() => () => { geometry.dispose(); material.dispose(); }, []);Bad
Good
Why it matters
Distilled from the Core Web Vitals / mobile / draw-call budgets in emalorenzo's three-agent-skills and web.dev's WebGL guidance, and it matters because WebGL performance is governed by metrics that have nothing to do with the rest of the page. Two costs dominate. Fragment shading scales with the pixels drawn, which is `cssPixels × devicePixelRatio²`, so `renderer.setPixelRatio(window.devicePixelRatio)` on a 3× phone quietly asks the GPU to shade NINE times the area of a 1× render — the single most common cause of a 3D hero that melts a mid-range phone.
Cap it hard: `setPixelRatio(Math.min(devicePixelRatio, 2))`, above which the extra density is imperceptible and only expensive. Second, every draw call carries fixed CPU overhead, so a scene is bounded by object COUNT more than triangle count; hundreds of separate meshes stall the main thread, and the fix is to merge static geometry or use instancing to draw many copies in one call (budget: well under ~100 draw calls/frame).
A third, silent failure: GPU memory is not garbage-collected — geometries, materials and textures must be explicitly `.dispose()`d when a scene unmounts, or they leak until the browser drops the context. This is the runtime companion to content-canvas-accessible-fallback (the same 3D feature needs a static fallback where WebGL is weak or absent) and to performance-raf-stop-condition / animations-ibelick-pause-offscreen (do not run the render loop when nothing is on screen).