Skip to content

Stop 20 Hz playback ticks from re-rendering the full-screen backdrop - #88

Merged
sanylax0 merged 1 commit into
mainfrom
claude/repo-review-improvements-yvk3t7-ui-render-fix
Jul 18, 2026
Merged

sanylax0 merged 1 commit into
mainfrom
claude/repo-review-improvements-yvk3t7-ui-render-fix

Conversation

@sanylax2

Copy link
Copy Markdown
Collaborator

What

PR 4 of the jetsam RCA plan — the UI-render branch of the RCA. While playing, Player.tick() writes position 20×/s; NowPlayingView.body (and MiniPlayerView.body in the always-mounted library page) read it, so entire pages re-evaluated at 20 Hz — including AlbumBackdrop's full-screen .blur(radius: 60, opaque: true), a ~14 MB offscreen rasterization carrying an implicit 0.6 s crossfade animation. Constant invalidation of an offscreen-rendered layer with in-flight animation snapshots is a memory ramp.

How

  • Leaf-view isolation: every player.position read moves into tiny dedicated views — TrackProgressRing, ScrubberBar (NowPlayingView), MiniProgressLine (MiniPlayerView). @Observable tracks dependencies per view body, so the 20 Hz ticks now invalidate only those leaves; the backdrop and page chrome never re-evaluate on tick. Visuals unchanged.
  • Static cached backdrop: AlbumBackdrop no longer live-blurs. Artwork is downscaled once to a ~40 px bitmap (BackdropRenderer, NSCache per URL, rendered off-main) and upscaled by the compositor with high interpolation — visually equivalent to a radius-60 gaussian, with zero per-frame offscreen rendering and no 14 MB backing store. Track changes crossfade between two cached bitmaps via an opacity transition; the scrim/vignette overlay is unchanged.

Testing

On device: Now Playing should look the same (soft blurred art, crossfade on track change; tweak the 40 px constant in BackdropRenderer if you want it softer/sharper). With Instruments → Core Animation "Color Offscreen-Rendered Yellow", the backdrop should no longer glow yellow, and Allocations should show flat memory during plain playback.

🤖 Generated with Claude Code

https://claude.ai/code/session_013TJoWkqg8bzkGdzxWhWjWP


Generated by Claude Code

The jetsam RCA's second finding: Player.tick() writes position at 20 Hz
and NowPlayingView's body read it (progress ring, scrubber), so the
whole page — including AlbumBackdrop's full-screen
.blur(radius:60, opaque:true), a ~14 MB offscreen rasterization with an
implicit 0.6 s crossfade — re-evaluated twenty times a second while
playing. Two changes:

- All player.position reads move into tiny leaf views
  (TrackProgressRing, ScrubberBar, MiniProgressLine); @observable
  tracks per-body, so the ticks now invalidate only those leaves.
- AlbumBackdrop no longer live-blurs: artwork is downscaled once to a
  ~40 px bitmap (cached per URL) and upscaled by the compositor —
  visually identical to the radius-60 blur, zero per-frame offscreen
  rendering; track changes crossfade between two cached bitmaps.

@sanylax0 sanylax0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ts better work

@sanylax0
sanylax0 merged commit f0506d4 into main Jul 18, 2026
4 checks passed
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.

3 participants