Repository navigation
[Bug] WebView rendering pins GPU at 5-8W continuously (44x vs native app, same streaming workload) #3585
Description
Environment
OpenBitFun 1.0.7 / macOS 27.0.1 (26A434) / MacBook Pro Mac17,2, Apple M5 (4P+6E), 16 GB / Liquid Retina XDR 3024x1964 ProMotion / renderer WKWebView (Tauri). Uptime 66 h.
Summary
With the window visible, the WebKit render process pins the GPU at 5.3-7.9 W; CPU+GPU sits at 9.1-10.0 W for ordinary chat use. Minimizing drops GPU to 0.06 W. Control: Zed (native Rust+Metal) doing the same foreground AI streaming draws 0.12 W GPU and 3.4-5.3 W total.
The render process uses LESS CPU than Zed (22.7% vs 33-37% of a core) yet draws 44x the GPU power. This is compositing cost, not compute.
Data
OpenBitFun foreground, streaming (powermetrics tasks, 1 s):
- WindowServer: 298.8 CPU ms/s, energy 1393.6, 698 wakeups/s
- WebKit.WebContent: 160.2 ms/s, energy 649.9
- WebKit.GPU: 39.3 ms/s, energy 141.7
- openbitfun-desktop: 32.5 ms/s, energy 103.2, 335 wakeups/s
- Control wpsoffice: 191.1 ms/s but energy only 51.9 (1/12)
Power in mW: CPU 4115/4343/3795/4722/3734, GPU 5335/5356/5340/5329/5313 (pinned), Combined 9450/9704/9124/10035/9079. Peak under heavy streaming: GPU 7922, CPU 13689, Combined 21433.
Window minimized vs visible (n=126/29 samples, 2 s cadence):
- GPU 0.06 W vs 5.30 W
- CPU 1.13 W vs 4.34 W
- CPU+GPU ~1.2 W vs ~9.6 W
- WindowServer 12.0% vs 43.0% of one core
- WebContent 8.5% vs 22.7%
- SystemLoad 8.14 W vs 12.17 W
Zed control (foreground streaming): GPU 0.12-0.13 W, CPU 3.31-5.16 W, combined 3.4-5.3 W, WindowServer 11.6-14.7%, RSS 242 MB. Note macOS Energy Impact ranks Zed HIGHER (1215-1724 vs 650-1005) because that score is CPU-driven; it is not a usable proxy for GPU-bound WebView cost.
Root cause evidence
2 s sample of WebContent while visible and streaming: 457/1210 samples inside __CFRunLoopDoTimers (38%), of which 318 in WebCore::timerFired -> 202 in the RemoteLayerTreeDrawingArea lambda -> 149 RemoteLayerTreeDrawingArea::updateRendering() -> updateCompositingLayers -> computeCompositingRequirements recursing 8+ levels.
So a timer-driven frame cycle runs even when only a few characters change, and every small DOM mutation re-walks a very large layer tree, re-encodes it over IPC, and re-composites 3024x1964 px (~700M px/s at ProMotion), which matches the sustained 5-8 W.
Secondary issues in the same session
- Main process wakes 335-352 times/s with no active tool call (energy per CPU-ms/s 3.2, vs 0.11-0.27 for native apps).
- GitStateManager polls without backoff: 3777 refreshes in 4h23m (~4.2 s); 19 zombie children accumulated under openbitfun-desktop.
- manage_dialog_queue fails every 10 s forever: 1613 ERROR lines "Session is not loaded on the execution host".
- 73 uncaught frontend errors (28 Canceled, 26 TypeError, 19 JSON Parse error).
- 2098 KaTeX strict-mode warnings crossing the JS->native bridge.
- 68.7 MB of logs written today; app.log rotates 10 MB every ~4 h.
- Render RSS 1.17 GB (peak 2.3 GB), main footprint 699 MB (peak 1.7 GB) after 66 h. A bigger DOM means a bigger layer tree.
Impact
A visible window costs 5-8 W more than minimized: 30-40% of total system load, roughly 42 Wh per 8 h working day (about half a battery).
Reproduction
- Apple Silicon, OpenBitFun 1.0.7, ProMotion display, window maximized.
- Open a long conversation until render RSS exceeds ~1 GB.
- Send a message and let the agent stream.
- sudo powermetrics --samplers cpu_power,gpu_power -i 1000 -n 10 -> GPU pinned at 5-8 W.
- Minimize -> GPU below 0.1 W within one sample; restore -> returns to the pinned level.
Suggested directions
- Batch streamed text into ~10 Hz DOM commits instead of a drawing-area frame cycle per token.
- Audit promoted compositing layers (backdrop-filter, will-change, transform, shadow) in message bubbles and highlighting.
- Check for a persistent infinite animation keeping the compositor awake.
- Rate-limit the GitStateManager refresh and manage_dialog_queue retry, and reap git children.
- Virtualize the message list so DOM and layer count do not scale with conversation length.
- Offer a reduce-effects / low-power setting.
Happy to run an instrumented build or provide further sample/powermetrics captures.
Activity