mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
Revert "perf(fs-player): cut CPU under WebKitGTK software compositing"
This reverts commit77edfaa867. 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 from25537f27for the lyrics menu stays in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -434,40 +434,20 @@ const FsSeekbar = memo(function FsSeekbar({ duration }: { duration: number }) {
|
|||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Cache the last applied values so we can skip DOM writes when nothing
|
const s = usePlayerStore.getState();
|
||||||
// changed. The store subscription fires on every state change (queue,
|
const pct = s.progress * 100;
|
||||||
// volume, currentTrack…), not just progress, so without this guard we
|
if (timeRef.current) timeRef.current.textContent = formatTime(s.currentTime);
|
||||||
// were writing identical width/textContent strings dozens of times per
|
if (playedRef.current) playedRef.current.style.width = `${pct}%`;
|
||||||
// second — each one triggers a style invalidation in WebKitGTK.
|
if (bufRef.current) bufRef.current.style.width = `${Math.max(pct, s.buffered * 100)}%`;
|
||||||
let lastTime = -1;
|
if (inputRef.current) inputRef.current.value = String(s.progress);
|
||||||
let lastPct = -1;
|
|
||||||
let lastBufW = -1;
|
|
||||||
let lastProg = -1;
|
|
||||||
|
|
||||||
const apply = (s: ReturnType<typeof usePlayerStore.getState>) => {
|
return usePlayerStore.subscribe(state => {
|
||||||
const pct = s.progress * 100;
|
const p = state.progress * 100;
|
||||||
const bufW = Math.max(pct, s.buffered * 100);
|
if (timeRef.current) timeRef.current.textContent = formatTime(state.currentTime);
|
||||||
const sec = s.currentTime | 0;
|
if (playedRef.current) playedRef.current.style.width = `${p}%`;
|
||||||
if (sec !== lastTime) {
|
if (bufRef.current) bufRef.current.style.width = `${Math.max(p, state.buffered * 100)}%`;
|
||||||
lastTime = sec;
|
if (inputRef.current) inputRef.current.value = String(state.progress);
|
||||||
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);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSeek = useCallback(
|
const handleSeek = useCallback(
|
||||||
|
|||||||
@@ -3854,22 +3854,6 @@
|
|||||||
html.no-compositing .fsr-lyrics-overlay { -webkit-mask-image: none; mask-image: none; }
|
html.no-compositing .fsr-lyrics-overlay { -webkit-mask-image: none; mask-image: none; }
|
||||||
html.no-compositing .fsr-lyrics-rail { will-change: auto; }
|
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 ─────────────────────────────────────────────────
|
/* ── no-compositing overrides ─────────────────────────────────────────────────
|
||||||
Applied only when WEBKIT_DISABLE_COMPOSITING_MODE=1 is set (Arch Linux etc).
|
Applied only when WEBKIT_DISABLE_COMPOSITING_MODE=1 is set (Arch Linux etc).
|
||||||
Replaces GPU-only effects (backdrop-filter, CSS filter, mask-image) with
|
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.
|
/* Portrait wrap: replace mask-image fade with a pseudo-element overlay gradient.
|
||||||
pointer-events: none on wrap means the overlay never blocks clicks.
|
pointer-events: none on wrap means the overlay never blocks clicks. */
|
||||||
Also drop translateZ(0) — useless GPU layer hint without a compositor. */
|
|
||||||
html.no-compositing .fs-portrait-wrap {
|
html.no-compositing .fs-portrait-wrap {
|
||||||
-webkit-mask-image: none;
|
-webkit-mask-image: none;
|
||||||
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 {
|
html.no-compositing .fs-portrait-wrap::before {
|
||||||
content: '';
|
content: '';
|
||||||
@@ -3924,16 +3899,12 @@ html.no-compositing .fs-portrait-wrap::before {
|
|||||||
|
|
||||||
/* Lyrics fade overlays: gradient-only, no backdrop-filter — fine in no-compositing */
|
/* Lyrics fade overlays: gradient-only, no backdrop-filter — fine in no-compositing */
|
||||||
|
|
||||||
/* Mesh blobs: stop animations + flatten to a solid dark fill.
|
/* Mesh blobs: stop animations — in software mode each frame composites surfaces
|
||||||
Even static, the 130 × 130 % radial-gradient with color-mix() is recomputed
|
larger than the screen (blob-a is 130 × 130 % of the viewport); on high-DPI
|
||||||
on every paint, and the `transition: background 400ms` on accent change
|
displays this saturates the CPU and drops the player to ~10 FPS.
|
||||||
re-rasterises the giant surface for nearly half a second per track switch.
|
Static gradients are preserved; only the slow pan animation is removed. */
|
||||||
Atmosphere is sacrificed; the dynamic accent is preserved on title /
|
|
||||||
seekbar / controls via their own var(--dynamic-fs-accent) bindings. */
|
|
||||||
html.no-compositing .fs-mesh-blob {
|
html.no-compositing .fs-mesh-blob {
|
||||||
animation: none;
|
animation: none;
|
||||||
background: #06060e;
|
|
||||||
transition: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Seekbar played bar: remove box-shadow — its width changes on every playback
|
/* 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;
|
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 */
|
||||||
.chat-popup {
|
.chat-popup {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -40,15 +40,6 @@ export class SpringScroller {
|
|||||||
|
|
||||||
scrollTo(targetY: number) {
|
scrollTo(targetY: number) {
|
||||||
this.target = Math.max(0, targetY);
|
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();
|
if (this.rafId === null) this.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user