Skip to content

perf: demand-driven rendering + mobile-aware auto (1.5.0) - #6

Open
amnn-in wants to merge 1 commit into
feat/lensing-zoomfrom
perf/mobile-idle
Open

perf: demand-driven rendering + mobile-aware auto (1.5.0)#6
amnn-in wants to merge 1 commit into
feat/lensing-zoomfrom
perf/mobile-idle

Conversation

@amnn-in

@amnn-in amnn-in commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Field report: an app using glasskit lagged on mobile in every mode. Root cause for the refraction modes: svg-clone and webgl ran an unconditional requestAnimationFrame loop forever, and svg-clone's per-frame SVG attribute writes forced the browser to re-run the whole filter 60×/second even when nothing on screen moved. On top of that, auto served mobile Chromium the svg mode — the slowest possible path on a phone GPU.

Stacked on #5 (feat/lensing-zoom) — merge #5 first; this PR then retargets main automatically.

What changed

Demand-driven frame loop. The loop now spins down after ~1s with no observed motion and wakes on scroll / resize / pointer / wheel / CSS transitions & animations (window-level passive listeners), style/class mutations on the element, ResizeObserver (now also watching the background element), and update() / refresh().

No-op guards so "awake" ≠ "working".

  • syncClone keys on relative geometry — glass scrolling together with its background writes nothing, so the SVG filter never re-runs (this was the big mobile jank source).
  • renderGL keys on its uniform inputs — a static image background skips the entire GL pass when nothing moved.

Idle cost per instance (measured, old → new):

rAF ticks/s SVG attr writes/s GL draws/s forced rect reads/s
svg-clone 60 → 0 360 → 0 120 → 0
webgl 60 → 0 60 → 0 120 → 0

New live option (engine + <glass-kit live> + React + types): null = auto (video/canvas backgrounds keep per-frame texture upload, since their pixels can change without any layout signal — the generator's WebGL preview is unaffected), false = on-demand even for a static canvas (call refresh() after redrawing), true = permanent loop.

Mobile-aware auto. auto now resolves to css on phones (navigator.userAgentData.mobile, falling back to (any-pointer:fine)); refraction modes remain available explicitly. README gains a Performance section documenting all of this.

Verification

New bench/verify-perf.mjs (14 checks, all ✓): idle counters via instrumented requestAnimationFrame / SVGElement.setAttribute / drawArrays; scroll wakes and re-syncs the clone; a scrolled instance renders identically to a freshly-applied one (max channel delta 3/255 — Chromium filter noise, not misalignment); update() wakes exactly one re-render then idles; canvas live-by-default and live:false semantics; Pixel 7 emulation → auto = css, desktop → svg.

Regressions: verify-buildmap (300 configs byte-identical, zoom probes ✓), verify-zoom (svg + webgl lensing, byte-identical update({zoom:0}) round-trips — proving the wake path renders exact frames), bench-smoke — all pass.

Version: 1.5.0 (behavior change in auto + new option). After merge: git tag v1.5.0 && git push origin v1.5.0 publishes to npm.

🤖 Generated with Claude Code

svg-clone and webgl no longer run an unconditional rAF loop. The loop
spins down after ~1s with no observed motion and wakes on scroll /
resize / pointer / CSS transitions & animations / style mutations /
ResizeObserver / update()/refresh(). Idle cost per instance drops from
60 rAF ticks + 360 SVG attr writes + 120 forced rect reads per second
(svg-clone) and 60 GL draws/s (webgl) to zero.

- syncClone: relative-geometry no-op guard — scrolling glass and
  background together no longer touches the filter (attr writes forced
  a full re-filter every frame, the main mobile jank source)
- renderGL: uniform-input key early-out; static image backgrounds skip
  the whole GL pass when nothing moved
- new `live` option: null (auto — video/canvas backgrounds stay
  per-frame), false forces on-demand for static canvases, true forces
  a permanent loop. Wired through web component, React and types.
- auto mode resolves to css on phones (userAgentData.mobile, falling
  back to any-pointer:fine) — the svg filter chain re-runs on every
  scrolled frame and janks mobile GPUs
- ResizeObserver now also watches the background element
- bench/verify-perf.mjs: 14 checks — idle counters, wake-on-scroll,
  scrolled-vs-fresh alignment, live/canvas semantics, mobile auto

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant