From d191404f2d16b7e2e2225ed23f28aaebb8717af1 Mon Sep 17 00:00:00 2001 From: Psychotoxical Date: Sun, 19 Apr 2026 18:07:13 +0200 Subject: [PATCH] Revert "perf(fs-player): cut CPU under WebKitGTK software compositing" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 77edfaa8672ae44fa38b4f2e922657c6fee3c080. Restores the full Apple Music-style lyrics visuals from PR #205 on Linux: rAF spring scroller, per-line scale transforms, text-shadows, title glow, album-art/play-button outer glows, mesh-blob radial gradients, and the FS-art opacity crossfade. PR #205's author noticed the Linux codepath lost the intended feel — the CPU tradeoff is accepted. The trigger-ref fix from 25537f27 for the lyrics menu stays in place. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/FullscreenPlayer.tsx | 46 +++++------------ src/styles/components.css | 78 ++--------------------------- src/utils/springScroll.ts | 9 ---- 3 files changed, 18 insertions(+), 115 deletions(-) diff --git a/src/components/FullscreenPlayer.tsx b/src/components/FullscreenPlayer.tsx index 950e70ad..bc12cc86 100644 --- a/src/components/FullscreenPlayer.tsx +++ b/src/components/FullscreenPlayer.tsx @@ -434,40 +434,20 @@ const FsSeekbar = memo(function FsSeekbar({ duration }: { duration: number }) { const inputRef = useRef(null); useEffect(() => { - // Cache the last applied values so we can skip DOM writes when nothing - // changed. The store subscription fires on every state change (queue, - // volume, currentTrack…), not just progress, so without this guard we - // were writing identical width/textContent strings dozens of times per - // second — each one triggers a style invalidation in WebKitGTK. - let lastTime = -1; - let lastPct = -1; - let lastBufW = -1; - let lastProg = -1; + const s = usePlayerStore.getState(); + const pct = s.progress * 100; + if (timeRef.current) timeRef.current.textContent = formatTime(s.currentTime); + if (playedRef.current) playedRef.current.style.width = `${pct}%`; + if (bufRef.current) bufRef.current.style.width = `${Math.max(pct, s.buffered * 100)}%`; + if (inputRef.current) inputRef.current.value = String(s.progress); - const apply = (s: ReturnType) => { - const pct = s.progress * 100; - const bufW = Math.max(pct, s.buffered * 100); - const sec = s.currentTime | 0; - if (sec !== lastTime) { - lastTime = sec; - if (timeRef.current) timeRef.current.textContent = formatTime(s.currentTime); - } - if (pct !== lastPct) { - lastPct = pct; - if (playedRef.current) playedRef.current.style.width = `${pct}%`; - } - if (bufW !== lastBufW) { - lastBufW = bufW; - if (bufRef.current) bufRef.current.style.width = `${bufW}%`; - } - if (s.progress !== lastProg) { - lastProg = s.progress; - if (inputRef.current) inputRef.current.value = String(s.progress); - } - }; - - apply(usePlayerStore.getState()); - return usePlayerStore.subscribe(apply); + return usePlayerStore.subscribe(state => { + const p = state.progress * 100; + if (timeRef.current) timeRef.current.textContent = formatTime(state.currentTime); + if (playedRef.current) playedRef.current.style.width = `${p}%`; + if (bufRef.current) bufRef.current.style.width = `${Math.max(p, state.buffered * 100)}%`; + if (inputRef.current) inputRef.current.value = String(state.progress); + }); }, []); const handleSeek = useCallback( diff --git a/src/styles/components.css b/src/styles/components.css index 966b9c03..2b8539d8 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -3854,22 +3854,6 @@ html.no-compositing .fsr-lyrics-overlay { -webkit-mask-image: none; mask-image: none; } html.no-compositing .fsr-lyrics-rail { will-change: auto; } -/* Apple-style FS lyrics — strip GPU-only effects. - Without compositing, transform: scale() on every line forces a per-line - software repaint on each active-line change, and the 48 px / 32 px blur - text-shadows repaint on every word tick. Replace with opacity + colour, - which are cheap text-only repaints. */ -html.no-compositing .fsa-lyric-line { - transform: none; - transition: color 380ms cubic-bezier(0.25, 0.46, 0.45, 0.94), - opacity 380ms cubic-bezier(0.25, 0.46, 0.45, 0.94); -} -html.no-compositing .fsa-lyric-line.fsal-past { transform: none; } -html.no-compositing .fsa-lyric-line.fsal-active { transform: none; text-shadow: none; } -html.no-compositing .fsa-lyric-line.fsal-active .fsa-lyric-word.active { - text-shadow: none; -} - /* ── no-compositing overrides ───────────────────────────────────────────────── Applied only when WEBKIT_DISABLE_COMPOSITING_MODE=1 is set (Arch Linux etc). Replaces GPU-only effects (backdrop-filter, CSS filter, mask-image) with @@ -3894,19 +3878,10 @@ html.no-compositing .fs-portrait { } /* Portrait wrap: replace mask-image fade with a pseudo-element overlay gradient. - pointer-events: none on wrap means the overlay never blocks clicks. - Also drop translateZ(0) — useless GPU layer hint without a compositor. */ + pointer-events: none on wrap means the overlay never blocks clicks. */ html.no-compositing .fs-portrait-wrap { -webkit-mask-image: none; mask-image: none; - transform: none; -} - -/* FS art crossfade: opacity transition compositing is GPU-cheap, software-expensive. - Snap instantly between covers; visually a hard cut, but the previous layer is - replaced before the user can really notice. */ -html.no-compositing .fs-art { - transition: none; } html.no-compositing .fs-portrait-wrap::before { content: ''; @@ -3924,16 +3899,12 @@ html.no-compositing .fs-portrait-wrap::before { /* Lyrics fade overlays: gradient-only, no backdrop-filter — fine in no-compositing */ -/* Mesh blobs: stop animations + flatten to a solid dark fill. - Even static, the 130 × 130 % radial-gradient with color-mix() is recomputed - on every paint, and the `transition: background 400ms` on accent change - re-rasterises the giant surface for nearly half a second per track switch. - Atmosphere is sacrificed; the dynamic accent is preserved on title / - seekbar / controls via their own var(--dynamic-fs-accent) bindings. */ +/* Mesh blobs: stop animations — in software mode each frame composites surfaces + larger than the screen (blob-a is 130 × 130 % of the viewport); on high-DPI + displays this saturates the CPU and drops the player to ~10 FPS. + Static gradients are preserved; only the slow pan animation is removed. */ html.no-compositing .fs-mesh-blob { animation: none; - background: #06060e; - transition: none; } /* Seekbar played bar: remove box-shadow — its width changes on every playback @@ -3942,45 +3913,6 @@ html.no-compositing .fs-seekbar-played { box-shadow: none; } -/* Track title: drop the 40 px text-shadow glow — paints a giant blur on a - clamp(28–68 px) font and re-rasterises whenever anything around it - redraws (cluster updates, seekbar tick, dynamic accent change). */ -html.no-compositing .fs-track-title { - text-shadow: 0 2px 6px rgba(0, 0, 0, 0.65); - transition: color 200ms ease-in-out; -} - -/* Album art wrap: replace the accent-coloured outer glow with a flat - shadow. The 28 px blur was repainting every accent change. */ -html.no-compositing .fs-art-wrap { - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.07); - transition: none; -} - -/* Play button: drop accent glow + hover filter. Keep flat dark shadow only. */ -html.no-compositing .fs-btn-play { - box-shadow: 0 4px 14px rgba(0, 0, 0, 0.55); -} -html.no-compositing .fs-btn-play:hover { - filter: none; - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.65); -} - -/* Rail-style FS lyrics: same problems as Apple style — 28 px text-shadow - blur on every active line + every active word, plus per-line transform - scaleX. Strip them; rail still scrolls fine via the parent translateY. */ -html.no-compositing .fsr-lyric-line { - transition: color 280ms ease; - transform: none; -} -html.no-compositing .fsr-lyric-line.fsrl-active { - text-shadow: none; - transform: none; -} -html.no-compositing .fsr-lyric-line.fsrl-active .fsr-lyric-word.active { - text-shadow: none; -} - /* Chat */ .chat-popup { position: absolute; diff --git a/src/utils/springScroll.ts b/src/utils/springScroll.ts index a2981067..ce18f221 100644 --- a/src/utils/springScroll.ts +++ b/src/utils/springScroll.ts @@ -40,15 +40,6 @@ export class SpringScroller { scrollTo(targetY: number) { this.target = Math.max(0, targetY); - // Software-rendered Linux: replace our 60 fps rAF spring with the - // browser's native smooth scroll. The browser handles easing internally - // (one animation, not 60 JS callbacks per second) and we still get a - // smooth visual instead of a hard snap. - if (document.documentElement.classList.contains('no-compositing')) { - this.stop(); - this.container.scrollTo({ top: this.target, behavior: 'smooth' }); - return; - } if (this.rafId === null) this.tick(); }