Skip to content

perf: optimize CPU and WindowServer overhead during music playback - #40

Open
SnackForever wants to merge 5 commits into
Yoddikko:mainfrom
SnackForever:perf/reduce-cpu-equalizer-playerbar
Open

SnackForever wants to merge 5 commits into
Yoddikko:mainfrom
SnackForever:perf/reduce-cpu-equalizer-playerbar

Conversation

@SnackForever

@SnackForever SnackForever commented Sep 23, 2026 •

Copy link
Copy Markdown

User description

Description

This PR optimizes CPU, GPU, and WindowServer utilization during music playback on macOS (especially on Apple Silicon with multi-monitor / 120Hz ProMotion displays).

Profiling identified that:

  1. Continuous dynamic layout recalculations in EqualizerView triggered repetitive AppKit view layout passes (NSView layoutSubtreeIfNeeded).
  2. Whole-bar state invalidations in PlayerBar re-evaluated the artwork glow, Liquid Glass styling, and controls on every progress tick.
  3. Uncomposited scrolling text in PlayerBarMarqueeText caused repeated CoreText glyph rasterization on each frame.
  4. Frequent DOM queries in SingletonPlayerWebView caused unnecessary WebKit IPC overhead.
  5. Continuous animations continued to render at 120Hz even when the window was located on an inactive desktop Space / virtual desktop, occluded behind other windows, or paused.

Key Highlights

  • -95% background CPU on inactive desktop Spaces (dropping from ~28% continuous down to 1.3%–1.7%) via native NSWindow.occlusionState tracking.
  • -88% CPU when paused (dropping from ~27.4% down to 3.3%).
  • -50% WebKit WebContent overhead via DOM query throttling and hasVideo result caching.
  • -44% reduction in layout passes (layoutIfNeeded), offloading bar scaling and text scrolling to CoreAnimation / Metal while keeping 100% of 120Hz ProMotion fluidity intact.
  • Addresses both CodeAnt-AI review items (mix segment seek hold and authoritative hasVideo DOM fallback protection).

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)
  • 📚 Documentation update
  • 🎨 UI/UX improvement
  • ♻️ Refactoring (no functional changes)
  • 🧪 Test update
  • 🔧 Build/CI configuration

Changes Made

1. Window Occlusion & Background Lifecycle (WindowOcclusionMonitor.swift, AppDelegate.swift)

  • Introduced WindowOcclusionMonitor tracking NSWindow.occlusionState via windowDidChangeOcclusionState.
  • When KasetPlus is on an inactive virtual desktop / Space, completely covered by other windows, or hidden via Cmd + W, continuous GPU/CPU animation loops immediately suspend.
  • When brought back into view, animations resume seamlessly at full 120 FPS.

2. Equalizer Bar GPU Scale Transforms (EqualizerView.swift)

  • Replaced dynamic .frame(height:) height recalculations with .scaleEffect(y: self.heightFraction, anchor: .bottom).
  • Avoids triggering CPU AppKit layout passes (NSView layoutSubtreeIfNeeded), offloading bar scaling to GPU CoreAnimation / Metal.
  • Suspends animation when occluded, miniaturized, or when playback is paused.

3. State Invalidation Isolation & Seek Hold Fix (PlayerBar.swift)

  • Extracted seek lane and mix tracks into isolated subviews (PlayerBarProgressLaneContainer and PlayerBarMixTracksMenu).
  • Playback time updates now only invalidate the progress slider bar rather than re-evaluating the artwork glow, Liquid Glass styling, marquee text, and transport controls.
  • Fix (CodeAnt-AI): Wired PlayerBarSeekHold to PlayerBarMixTracksMenu segment seeks to eliminate stale observer progress bounce-back.

4. GPU Texture Compositing (PlayerBarMarqueeText.swift)

  • Added .compositingGroup() modifier to the scrolling text HStack so Core Animation renders glyphs into a single GPU texture layer rather than re-rasterizing text glyphs via CPU CoreText (TRun::DrawGlyphs) on each display frame.
  • Automatically suspends scrolling when the window is occluded or closed.

5. WebKit DOM Query Throttling & Caching (SingletonPlayerWebView+ObserverScript.swift)

  • Throttled MutationObserver debounce and cached hasVideo element query results.
  • Fix (CodeAnt-AI): Protected authoritative video values against periodic false-negative DOM query overwrites.

Empirical Benchmark

Measured on macOS Tahoe (Apple Silicon, 120Hz ProMotion display) using the track Enemy (Arcane) across different real-world usage scenarios:

Scenario Stock Baseline This PR (Optimized) Delta
Inactive Desktop Space / Background 28.3% CPU
42.4% WindowServer
1.3% – 1.7% CPU
25.9% – 26.7% WindowServer
-95% CPU 🔥
~50% WS reduction
Paused (Window in foreground) 27.4% CPU
47.5% WindowServer
3.3% CPU
33.0% – 44.0% WindowServer
-88% CPU ⚡
Closed window (Cmd + W) ~23.0% CPU
~36.5% WindowServer
~1.5% CPU
~26.0% WindowServer
-93% CPU 🔋
Active Playback (Foreground, 120Hz) ~26.2% CPU
258 layout passes
~28.0% CPU (120 FPS)
144 layout passes
-44% layout passes
100% fluid ProMotion
WebKit WebContent Process 6.5% – 8.1% CPU 2.8% – 3.6% CPU -50% WebContent ✅

Testing

  • Unit tests pass 100% (swift test --filter PlayerBar and swift test --filter Equalizer pass cleanly)
  • Tested on macOS 26+ (Apple Silicon)
  • Empirically benchmarked under identical audio and display conditions
  • Zero visual regressions; 100% animation fidelity intact

Checklist

  • My code follows the project's style guidelines
  • I have run swiftlint --strict && swiftformat .
  • I have verified all unit tests pass locally
  • I have addressed CodeAnt-AI bot review feedback
  • My changes generate no new warnings

CodeAnt-AI Description

Reduce CPU and rendering work during music playback while keeping progress and segment seeking accurate

What Changed

  • Equalizer bars animate through GPU transforms instead of repeated layout updates, preserving smooth playback visuals
  • Equalizer and scrolling track text pause when the main window is hidden, covered, minimized, or on another desktop Space, then resume when visible
  • Player bar progress updates are isolated from artwork, controls, and styling so regular playback ticks do not refresh the entire bar
  • Scrolling track text uses composited rendering, and WebView video detection reuses results while limiting expensive page-wide checks
  • Segment seeks retain the selected position until playback confirms the change, preventing progress from jumping back during a seek
  • Video detection no longer replaces an authoritative positive result with a temporary negative DOM check

Impact

✅ Lower CPU while music plays in hidden or covered windows
✅ Smoother equalizer and marquee playback visuals
✅ Fewer progress-related player bar refreshes
✅ Reliable segment seeking without progress jumps

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Copilot AI lite review requested due to automatic review settings September 23, 2026 11:49
@codeant-ai

codeant-ai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed f9ad896 Sep 23, 2026 · 14:50 14:51
✅ Reviewed your PR 6d285c6 Sep 23, 2026 · 11:49 11:54

@codeant-ai

codeant-ai Bot commented Sep 23, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Sep 23, 2026
Comment thread Sources/Kaset/Views/PlayerBar.swift
Comment thread Sources/Kaset/Views/SingletonPlayerWebView+ObserverScript.swift Outdated
…progress invalidation

- EqualizerBar: replace GeometryReader dynamic frame height with native scaleEffect transform to eliminate 60/120Hz CPU layout recalculations in NSHostingView / WindowServer.
- PlayerBar: extract PlayerBarProgressLaneContainer and PlayerBarMixTracksMenu to isolate playerService.progress observation; prevents re-evaluating Liquid Glass container, artwork glow shaders, and controls on every 500ms playback tick.
- ObserverScript: cache hasVideo and throttle full-DOM querySelectorAll calls to eliminate recurring WebKit content process CPU load.
@SnackForever
SnackForever force-pushed the perf/reduce-cpu-equalizer-playerbar branch from 6d285c6 to c30c366 Compare September 23, 2026 11:57
…edback

- fix(playerbar): wire PlayerBarSeekHold to mix segment seeks preventing stale observer progress jumps
- fix(observer): prevent false-negative DOM query from overwriting authoritative hasVideo state
- perf(lifecycle): introduce WindowOcclusionMonitor to suspend continuous GPU/CPU animations when window is on inactive Spaces or occluded (-95% background CPU)
- perf(equalizer): keep full 120Hz ProMotion fluidity while eliminating background render load
@SnackForever

Copy link
Copy Markdown
Author

Note on CI Checks Status

For visibility during review, the failing checks reported by CI are pre-existing issues present on main and not regressions caused by this PR:

  1. Dev Build (build):

  2. Lint & Format (SwiftLint & SwiftFormat):

    • The CI runs repo-wide linting (swiftformat --lint .), which flags ~44 pre-existing formatting issues in legacy test files (ListenBrainzServiceTests.swift, DiscordRichPresenceTests.swift, etc.).
    • All files modified in this PR were strictly checked locally with swiftlint --strict and swiftformat . and have 0 violations.
  3. Test Suite (Unit & UI Tests):

    • The failing test suites on main correspond to pre-existing missing localization strings in .lproj files (AppLocalizationTests, currently addressed in PR New Crowdin updates #29) and unmocked web-service tests.
    • All test suites related to the touched components (PlayerBar and Equalizer) pass 100%.

@Yoddikko

Copy link
Copy Markdown
Owner

Hi @SnackForever thanks for the PR. I will take a look in these weeks, I'm quite busy

@SnackForever

Copy link
Copy Markdown
Author

Hi @SnackForever thanks for the PR. I will take a look in these weeks, I'm quite busy

No problem, have a nice day

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants