Skip to content

[Bug] Desktop app (macOS) pegs GPU at 70-90% while idle — should be near-zero #2959

Open

Description

@v0l

Buzz Desktop (v0.4.26, xyz.block.buzz.app, Tauri + React) continuously consumes 70-90% GPU on an M1 Pro (MacBookPro18,1) while the window is simply open and idle — no scrolling, no typing, no huddle active. GPU drops to 0% immediately when the window is minimized, confirming it's active rendering/compositing rather than a background task.

For a chat app displaying mostly static text, idle GPU should be in the single digits. 70-90% sustained is a significant resource drain (battery, fan noise, energy) on Apple Silicon.

Likely contributors found in code:

  • src/shared/ui/SpoilerParticles.tsx — a 2D canvas that calls requestAnimationFrame(frame) every frame regardless of whether content changed (lines 135/144), gated only by prefers-reduced-motion.
  • src/shared/theme/ThemeProvider.tsx — macOS window vibrancy (set_window_vibrancy, material sidebar) is force-applied by "Buzz" themes with no user opt-out (line 324, 348). Full-window NSVisualEffectView blur forces continuous GPU compositing. Switching to a non-Buzz theme did not reduce idle GPU in my testing.
  • tauri.conf.json sets "backgroundThrottling": "disabled" — worth revisiting given the above.

Repro:

  1. Open Buzz Desktop on macOS (Apple Silicon), any channel, leave idle.
  2. Observe GPU usage (Activity Monitor ▸ GPU History, or a stats tool): 70-90%.
  3. Minimize window → drops to 0%.

Expected: GPU near-idle (single digits) when no visible content is animating. The particle canvas should pause when its content hasn't changed, and vibrancy should be a user-toggleable setting rather than theme-mandated.

Workaround: enabling macOS Reduce Motion helps (disables the particle loop) but shouldn't be required to use the app without excessive GPU draw.

Activity

  1. Seb0 commented on Jul 26, 2026

    @Seb0

    same for me, iGPU goes up to 85%+ on M3 👍

  2. GeneralJah215 commented on Aug 13, 2026

    @GeneralJah215

    Same symptom on Windows, and I think I can narrow the cause. Two of the three
    suspects in the original report look clear, and there is a fourth that has not
    come up yet.

    A CSS animation is producing a frame on every vsync.

    .buzz-shimmer in desktop/src/shared/styles/globals/animations.css animates
    background-position on a background-clip: text element, 2600ms linear infinite. background-position is not compositor-animatable, so each frame is
    real paint work rather than a composite, and background-clip: text means the
    glyph mask is re-rasterised with it.

    desktop/src/shared/ui/Shimmer.tsx mounts it whenever an agent is working, via
    BotActivityBar, TypingIndicatorRow and CardMintComposerChip.

    The measurement. The stylesheet already ships a
    prefers-reduced-motion: reduce block that sets animation: none on this exact
    class, so it can be switched off without patching anything. That makes a clean
    A/B rather than an inference. Same window, same session, same working agents,
    one variable changed. MainFrame.Draw counted from a CDP tracing profile:

    shimmer running (as shipped)   1924 MainFrame.Draw in 12s  = 160.3 fps
    shimmer off (reduced motion)     12 MainFrame.Draw in 12s  =   1.0 fps
    

    160.3 fps is exactly this display's refresh rate. Per 10 seconds, paint went
    from 1173 ms to 11 ms, and summed renderer + compositor + GPU thread time fell
    61%.

    document.getAnimations() on the live page returned exactly two running
    animations: this one, and a Tailwind pulse. pulse animates opacity, which
    the compositor handles without repainting.

    Why reading the code has not found it. A 30-second V8 CPU profile taken
    during the exact hot moment showed the main thread 72% idle, with no JS
    frame above 0.1% of a core. A CSS animation executes no JavaScript, so a
    JS-only profile is structurally blind to this. It took a tracing profile — one
    that records paint, raster and composite — to see it at all. Worth flagging
    because it is an easy wrong turn for anyone investigating this issue.

    On the original report's suspects. SpoilerParticles.tsx is a genuine
    unconditional requestAnimationFrame loop and is worth fixing on its own
    merits, but it only mounts where a spoiler is rendered. Enabling Reduce Motion
    disables the particle loop and the shimmer, so that workaround cannot
    distinguish between them — which may be why the shimmer has not surfaced.

    Fix options, measured. Same harness, one variable per row:

    approach paint renderer+compositor+GPU CPU
    own compositing layer (will-change + translateZ(0)) -3% +2%
    steps(52, end) on the existing keyframes -85% -42%
    steps(26, end) -92% -47%
    steps(13, end) -95% -51%
    animation removed (reference floor only, not proposed) -99% -61%

    The compositing-layer approach is worth calling out as a dead end. It is the
    reflexive fix and it does essentially nothing here — background-position still
    has to be painted, and promoting the element just adds a layer to manage. I
    tried it first and it cost slightly more than it saved.

    Quantising the timing function is the cheap win. At steps(52, end) the
    highlight advances every 50 ms instead of every vsync and still reads as
    continuous motion. A transform-animated overlay should reach the floor while
    preserving the look exactly, but that is a larger rewrite and I have not
    measured it, so I am not claiming a number for it.

    Not proposing removal. The shimmer communicates real state and should stay.

    Why this is probably quiet in the tracker. The cost scales with refresh rate
    and pixel count, because it is per-frame paint of a rasterised text mask. A 4K
    160 Hz user pays roughly 2.7x what a 1080p 60 Hz user pays for the identical UI
    state. On a 60 Hz laptop it reads as "the fans spin up while agents are busy"
    and may never get filed at all.

    Caveats, stated plainly. These numbers are one machine — Windows 11,
    3840x2160 @ 160 Hz — from a build carrying unrelated local changes elsewhere.
    The .buzz-shimmer rule is byte-identical to current main, so the mechanism
    transfers, but I have not re-run the numbers against a clean main build. The
    2.7x scaling figure is arithmetic from refresh rate and pixel count, not a
    second measured configuration. And I measured frame production and paint totals,
    not invalidated area — I am not claiming the whole window repaints, only that a
    frame is produced every vsync and that paint dominates it.

    Happy to open a PR for the steps() change if that is the direction you want,
    or to leave it to you if you would rather do the transform rewrite. The
    measurement seemed like the part worth handing over.

  3. ktecho commented on Sep 11, 2026

    @ktecho

    Yeah, it's the animation. For me in Linux (Kubuntu) it takes between 25 and 30% of CPU, but only when the "Agent is typing..." appears on the screen, because it has that stupid animation like a glowing effect or something. Please remove the animation or make it so we can configure it in Settings.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions