WebGL ยท idle simulation ยท cpuPerformance

Idle hands

Move your pointer across the water. Then stop, and watch the meter fall to zero. The loop is not throttled and it is not running a cheaper shader. It is cancelled, and it restarts on the next thing you do. The frames-never-drawn count is the part your battery notices.

waiting device tier: detecting awake 0.0s
This drop needs WebGL, and this browser did not give us a context. Nothing else on the page depends on it, so the numbers below simply stay at zero rather than inventing a simulation that is not running.
move the pointer ยท then stop and watch it go quiet
Simulation steps / sec0

Measured over the last second of wall time. Zero when idle, because nothing is running.

Frames drawn0

Total since the page loaded.

Frames never drawn0

What an always-on loop would have drawn in the same wall time, at this display's measured refresh.

Live ripples0

Three floats each: x, y, and the moment it was struck. No other per-ripple state exists.

What the saving is and is not. Cancelling the loop removes the GPU and main-thread cost of drawing. It does not make the tab free, and a browser already throttles a backgrounded one hard. The case this covers is the common one: a visible tab, a hero on screen, and nobody touching it. That is most of the life of any hero animation.
01 ยท the shape

Three floats per ripple, and the maths lives on the GPU

The usual way to animate a struck object is a tween per object, ticked on the CPU every frame. Store the strike time instead and the whole envelope becomes a function of now - struck, evaluated in the fragment shader. The CPU keeps an array of twelve vec3s and touches it only when you move.

That is what makes stopping cheap. There is no tween mid-flight to unwind and no per-object state to keep coherent. When the last ripple's amplitude falls under the visible threshold, the loop has nothing left to say and exits.

// CPU: this is the entire per-ripple state
ripples[i] = [x, y, nowSeconds];

// GPU: damped oscillator, evaluated fresh every frame from the timestamp
float age  = u_time - r.z;
float env  = exp(-age * 2.4);                // dies out in about a second
float wave = sin(dist * 46.0 - age * 9.0) * env;
height += wave * smoothstep(0.55, 0.0, dist);

// the loop is cancelled, not slowed, when nothing is left to draw
if (idleFor > 2000 && peakAmplitude < 0.004) {
  cancelAnimationFrame(raf); raf = 0;   // zero work until the next pointer event
}
02 ยท the seed

Ask the device before the first frame, not after

Chrome 152 ships navigator.cpuPerformance, a read-only integer from 1 to 4. Drop 041 uses it to seed an adaptive quality manager. Here it does one smaller job: it picks the starting resolution scale so a weak device never has to render one expensive frame before the measurement catches up.

It returns 0 when the browser cannot classify the device, and that case is handled on its own line. Reading 0 as a tier would quietly seed every unclassified visitor to the worst path, which is the opposite of the point. On anything without the API at all, the scale starts at a safe middle and the frame timer takes over from there.

01

Seed, do not cap

The tier picks where quality starts. It never sets a ceiling. A fast machine misread as slow still climbs on measured frame time.

02

Zero is not a tier

Unclassified means unknown, so it falls to the same middle default as a browser with no API at all.

03

It is a hint

Users can override it in Chrome settings and enterprise policy can set it. Anything that treats it as a gate is broken by design.

03 ยท receipt

Verify it yourself

> source verification
[RENDER] hand-written WebGL ยท one fragment shader ยท detecting
[STATE] 12 ripples max ยท 3 floats each ยท no per-object CPU tick, no allocation per frame
[IDLE] rAF cancelled after 2s without input once amplitude falls below threshold
[TIER] detecting
[REFRESH] measuring ยท the frames-never-drawn figure is derived from this, not assumed
[NETWORK] 0 outbound requests since load, measured by PerformanceObserver
[ASSETS] 0 CDNs ยท 0 webfonts ยท 0 analytics ยท single HTML file ยท works offline
[LIMIT] Stopping the loop saves drawing work. A backgrounded tab is already throttled by the browser.
[LIMIT] cpuPerformance is Chrome 152, stable 25 Aug 2026. Elsewhere the scale starts at a middle default.
[LIMIT] Frames-never-drawn is an estimate against a hypothetical always-on loop, not a power measurement.
Back to the Labs โ†’