Repository navigation
Description
Revised 2026-09-06. The original body blamed the bundled WebKitGTK build alone. That was wrong: it is an interaction with the NVIDIA driver. A contributor pushed back that this was a graphics issue and was right. Rewritten; the correction is explained in the comments.
Summary
The Linux AppImage pegs a full CPU core while idle on the home screen — on NVIDIA. It is an interaction between the WebKitGTK build bundled into the AppImage and the NVIDIA proprietary driver. Neither factor alone explains it. On the AMD integrated GPU of the same machine, the same AppImage costs ~23% and is fine.
This is not caused by #8348. That PR fixed the boot hang from #6744 and made a pre-existing cost reachable for the first time — before it, the app never got past the splash screen.
Environment
Phase-Desktop-Linux-x86_64.appimage, shell-v1.0.12; web app v0.75.0–v0.76.0- Nobara / Fedora 44, KWin Wayland (the app runs under XWayland —
AppRunforcesGDK_BACKEND=x11) - Two GPUs, which is what made the test possible: NVIDIA RTX 3090 (driver 595.91.07) and an AMD Raphael iGPU (Mesa)
- Bundled WebKitGTK 2.52.6; host WebKitGTK 2.52.5
The measurement that matters
Four cells, same machine, same app build, same AppDir harness, same 3840×2049 window, same web-app version, run back to back, each screenshot-verified rendering the same home screen:
| NVIDIA 595.91.07 | AMD iGPU (Mesa) | |
|---|---|---|
| Bundled Ubuntu WebKit 2.52.6 | 98.7% | 22.6% |
| Host Fedora WebKit 2.52.5 | 35.5% | 23.8% |
Reading it:
- On AMD the WebKit build is irrelevant — 22.6% vs 23.8%.
- On NVIDIA it is decisive — 98.7% vs 35.5%.
- NVIDIA costs ~12 points more than the iGPU even with the good build, which is a separate and much smaller effect.
So the defect lives in the diagonal: Ubuntu's WebKitGTK build hits a pathological path on the NVIDIA driver.
Symptom profile (all on NVIDIA)
WebKitWebProcesssits at 92–100% of one core indefinitely, across 11+ runs- Flat from 700×500 to 3840×2049 — cost does not respond to viewport area, which is not how paint cost behaves
- Unchanged when the window is minimized
- Main-thread stack samples: 100% in WebCore, 0% in
libc(the thread never reaches the event loop), 0% in JavaScriptCore. Four addresses account for ~88% of samples. - By contrast, in the fast configurations the main thread idles ~58% of the time and most of its work is JS.
Ruled out
Everything below was measured on NVIDIA, which held the GPU constant — that is precisely why the real cause stayed invisible until a second GPU was brought in. These eliminations still stand.
| Hypothesis | Evidence against |
|---|---|
| WebKit version | Transplanting v1.0.11's 2.52.3 into the current AppDir: 99.3%, identical. Not an upstream regression. |
| Painting / rasterization | Flat 700×500 → 3840×2049; the particle canvas has a working resize handler, so the page genuinely reflowed |
| Window visibility | 98.5% minimized vs 98.6% visible |
| Audio / GStreamer | Reproducing the identical 14 AAC decode failures on the fast build: 17.9%, vs 18.2% healthy |
| JIT | 1 GB JIT reservation present; negative control JSC_useJIT=0 → 0 KB validates the check. FTL/B3/DFG in both. |
Missing WebKitGPUProcess |
A logging wrapper at the exact helper path recorded zero spawn attempts |
| Different app build | Same BuildID 4bd8285653d0bd1956d30bec1538128fd25bd9c3 |
| Bundled text/graphics libs | Removed so host versions load: 98.8% |
| Bundled GLib/GTK layer | Host GLib 2.88.3 + GTK 3.24.32 loaded: 99.0% |
AppRun hook environment |
Full AppRun env on host libs: 48.3%, with a positive control confirming the env took effect |
| Skia vs Cairo, Skia GPU, allocator | Statically identical between the two builds |
| Frontend animation work | Shrinking the particle canvas to 1×1 saves nothing; neutering all requestAnimationFrame saves 7 of 38 points; CSS animations 2 points |
WEBKIT_DISABLE_DMABUF_RENDERER=1 |
98.6% — no help |
WEBKIT_DISABLE_COMPOSITING_MODE=1 |
98.8% — no help |
__NV_DISABLE_EXPLICIT_SYNC=1 |
99.2% — no help |
| All three of the above together | 99.0% — no help |
| XWayland vs native Wayland | 98.6% native Wayland vs 98.7% forced X11. Verified the backend actually changed (GDK_BACKEND=wayland in the web process env, 2 Wayland fds), since AppRun forces X11 unconditionally and silently overrides the variable. |
Cross-checked against Tauri's Linux graphics guidance
Tauri's Linux graphics debugging page documents a known NVIDIA class of problems. This is not that class.
Its symptoms are blank/white windows, flicker on resize, death on resize, AcceleratedSurfaceDMABuf was unable to construct a complete framebuffer, and Gdk-Message: Error 71 (Protocol error). None of those signatures appear in any of 48 captured run logs, and the app renders correctly throughout — the symptom here is purely idle CPU.
The one documented fault that is real on this hardware — Failed to create GBM buffer ... Invalid argument — appeared only in a bare WebView run without WEBKIT_DMABUF_RENDERER_FORCE_SHM. The app sets that unconditionally on Linux (#6293), so it is already mitigated and is not the symptom being reported here.
All four documented workarounds are accounted for:
| Tauri's suggestion | Result here |
|---|---|
nvidia_drm.modeset=1 |
Already active — nvidia_drm loaded with live DRM connectors, and this is a Wayland session on NVIDIA. The doc scopes this to drivers older than 545; this host runs 595.91.07. |
__NV_DISABLE_EXPLICIT_SYNC=1 |
99.2% — no help |
WEBKIT_DISABLE_DMABUF_RENDERER=1 |
98.6% — no help |
WEBKIT_DISABLE_COMPOSITING_MODE=1 |
98.8% — no help |
So there is no known environment-variable mitigation for this one. It is a distinct NVIDIA fault from the documented dmabuf class.
A second, smaller defect found along the way
The stock AppImage cannot run on Mesa at all. Forcing the Mesa EGL vendor aborts before the web process starts:
Could not create default EGL display: EGL_BAD_PARAMETER. Aborting...
The bundled libwayland-* and libxcb-* shadow the host's and break Mesa's EGL initialisation. I had to unbundle them to run the AMD cells at all. This blocks "just render on the iGPU" as a user-facing workaround, and is worth fixing independently.
Workarounds today
- NVIDIA users: the distro package /
.debpath costs ~35% versus the AppImage's ~99%. - Non-NVIDIA users are likely unaffected — inferred from one AMD iGPU, not broadly measured.
Open questions
- Why Ubuntu's build specifically. Every static dimension I can check is equivalent — version, Skia, Skia GPU, JIT tiers, libpas, assertions. Naming the hot function needs debug symbols on the Ubuntu build under NVIDIA; the stack profile has four addresses to start from.
- Intel GPUs are untested. So are other NVIDIA driver versions and other distros' WebKitGTK builds beyond Fedora's.
- Note this codebase already carries an NVIDIA workaround in exactly this area —
WEBKIT_DMABUF_RENDERER_FORCE_SHM=1, added in fix(tauri): Set WEBKIT_DMABUF_RENDERER_FORCE_SHM=1 to avoid nvidia graphics bug at no performance cost #6293. Same neighbourhood, and it is already active in these runs. - Whether a Flatpak (which would use the runtime's WebKit) avoids this is an assumption, not a measurement.
Related
- AppImage desktop app does not work #6744 — the original AppImage failure, fixed by Fix the Linux AppImage hanging at "Loading audio" #8348
- Linux AppImage ships no AAC decoder — all audio is silent (gstreamer1.0-libav missing from the release runner) #8615 — companion defect from the same investigation: the AppImage ships no AAC decoder, so all audio is silent
Diagnosed with Claude Code.
Activity
alicewonderland-dev commentedon Sep 6, 2026
Ran the upstream-regression test: it is not a regression. Correcting the suggested fix above.
The issue body proposed transplanting an older AppImage's
libwebkit2gtkinto the current AppDir, on the theory that Ubuntu's 2.52.6 might carry a regression. That theory is now dead.Versions bundled by past releases
Ubuntu did bump 2.52.3 → 2.52.6 inside the window, so the test was worth running.
Result
Transplanting v1.0.11's 2.52.3 (library,
WebKitWebProcess,WebKitNetworkProcessand injected bundle, all version-matched) into the current v1.0.12 AppDir, everything else untouched:Verified: the process loaded the transplanted library from the test AppDir, that file reports 2.52.3, the pristine AppDir was not modified, and the run was screenshot-confirmed rendering the same home screen at 3840×2049.
What this means
The split is Ubuntu's build vs Fedora's build, not old vs new. Ubuntu's WebKitGTK is ~2.2× the CPU of Fedora's for this app across two different upstream versions six weeks apart.
So the AppImage has always been this slow. It was invisible until #8348, because before that the app never got past the splash screen. Nothing regressed — a pre-existing cost became reachable.
Pinning or downgrading the runner's WebKit will not help. Please disregard that suggestion in the issue body.
Remaining options, roughly in order of tractability
.debidles only 58% of the time, versus 18–20% total CPU for the bare page in a plain WebView. Something renders continuously at rest — therequestAnimationFrameparticle canvas inSceneParticles(client/src/components/menu/MenuParticles.tsx) is the obvious first suspect, alongside any always-running CSS animations. If the home screen were genuinely idle at rest, both WebKit builds would be fine. Not yet quantified — I have not measured what share of the cost is the canvas.Diagnosed with Claude Code.
alicewonderland-dev commentedon Sep 6, 2026
Correction: the frontend lever is small, and my earlier bare-page comparison was confounded
I suggested above that reducing the app's continuous main-thread work — naming the
SceneParticlesrequestAnimationFramecanvas — was the most tractable lever. I measured it, and it isn't. Retracting that.Measurements
Live page in a plain WebKitGTK
WebViewon the host (fast) build, window 3840×2049, JS injected 25s after load:requestAnimationFrameneuteredPositive control, so the null result is trustworthy — probing the live page returned:
{"canvases":["2560x1366(css 2560x1366)"], "animatedEls":1, "dom":309, "raf":"function"}One canvas, full size, so the selector matched. One CSS-animated element. A 309-element DOM.
Removing every animation on the page recovers under 20% of its cost. The canvas rasterization specifically is worth nothing.
Also correcting: the shell adds ~nothing
I earlier contrasted "18–20% for the bare page" against the shell's 36–51%, implying the desktop shell roughly doubles the cost. That comparison was confounded by window size — the bare-page figure was measured at 1600×1000 and the shell at 3840×2049. At a matched 3840×2049 the bare page is 38.2%, versus 36–51% for the
.debshell. The shell adds essentially nothing; it is the page, at that size.What the residual actually is, and why it sharpens the diagnosis
With every animation off, ~31% remains, and on the host build it scales with window size (~19% at 1600×1000 → ~38% at 3840×2049). That is ordinary paint cost for a 4K window.
The AppImage build behaves qualitatively differently on exactly this axis: it is flat at 92–100% from 700×500 all the way to 3840×2049. A build whose cost does not respond to viewport area is not "slower painting" — it is doing something else, which is consistent with the stack profile showing the main thread never reaching the event loop.
So option 1 in my earlier comment is withdrawn. The remaining options are 2 (ship a Flatpak, using the runtime's WebKit) and 3 (chase the Ubuntu-vs-Fedora WebKitGTK build-config difference), and 3 now looks like the one that actually matters.
Diagnosed with Claude Code.
[-]Linux AppImage burns a full CPU core at idle — the bundled WebKitGTK build costs 2.2x the distro's[/-][+]Linux AppImage burns a full CPU core at idle on NVIDIA — bundled WebKitGTK × NVIDIA driver interaction[/+]alicewonderland-dev commentedon Sep 6, 2026
Correction: "this is a graphics card issue" was right. Body rewritten.
A contributor pushed back that this is a graphics issue. They were right, and my headline conclusion — "the bundled WebKitGTK build is the cause" — was wrong in a way worth being explicit about. I have rewritten the issue body; this comment records what changed and why I missed it.
Why I got it wrong
Every measurement in the original investigation ran on this machine with the NVIDIA GPU active. That held the GPU constant across the decisive A/B. A constant cannot explain a difference, so when swapping WebKit moved the number from 99% to 45%, WebKit looked like the whole answer. It was half of one — and the missing half was invisible precisely because I never varied it.
This box happens to have a second GPU, so the claim was directly testable.
The test
Same machine, same app build, same AppDir harness, same 3840×2049 window, same web-app version, run back to back, all four screenshot-verified on the same home screen.
On AMD the WebKit build makes no difference at all (22.6 vs 23.8). On NVIDIA it makes all the difference (98.7 vs 35.5). It is an interaction, not either factor alone.
Also tried, and they do not help
WEBKIT_DISABLE_DMABUF_RENDERER=1→ 98.6%WEBKIT_DISABLE_COMPOSITING_MODE=1→ 98.8%So there is no cheap environment-variable fix, and
WEBKIT_DMABUF_RENDERER_FORCE_SHM=1(already set for NVIDIA since #6293) is active throughout and does not save it.Found while testing this
The stock AppImage cannot run on Mesa at all — it aborts with
Could not create default EGL display: EGL_BAD_PARAMETERbefore the web process starts, because its bundledlibwayland-*/libxcb-*shadow the host's and break Mesa's EGL init. I had to unbundle those to run the AMD cells. That blocks "render on the iGPU" as a user workaround and is probably worth its own fix.What still stands, and what is still open
The eliminations in the body all hold — they were just all measured on NVIDIA. Notably it is still not an upstream WebKit regression: 2.52.3 transplanted into the current AppDir gives 99.3%.
Still unknown: why Ubuntu's build specifically (every static dimension is equivalent; naming the hot function needs debug symbols under NVIDIA), whether Intel is affected, and whether other NVIDIA driver versions behave differently. And the Flatpak suggestion remains an assumption I have not measured.
Diagnosed with Claude Code.
alicewonderland-dev commentedon Sep 6, 2026
Checked against Tauri's Linux graphics guidance — this is a different NVIDIA fault, and none of its workarounds help
Cross-referenced Tauri's Linux graphics debugging page. Folded into the body; summarising here.
The documented symptoms do not match. That page describes blank/white windows, flicker on resize, death on resize,
AcceleratedSurfaceDMABuf was unable to construct a complete framebuffer, andGdk-Message: Error 71 (Protocol error). None of those strings appear in any of 48 captured run logs, and the app renders correctly the whole time. Our symptom is idle CPU only.The one documented fault that is real on this hardware is already mitigated. I did see
Failed to create GBM buffer ... Invalid argument— but only in a bareWebViewrun withoutWEBKIT_DMABUF_RENDERER_FORCE_SHM. The app sets that unconditionally on Linux (#6293), so the documented dmabuf breakage never reaches it. Worth knowing that #6293 is doing real work.All four workarounds tested or accounted for, stock AppImage on NVIDIA:
nvidia_drm.modeset=1nvidia_drmloaded, live DRM connectors, Wayland session on NVIDIA). Doc scopes it to drivers < 545; this host is 595.91.07.__NV_DISABLE_EXPLICIT_SYNC=1WEBKIT_DISABLE_DMABUF_RENDERER=1WEBKIT_DISABLE_COMPOSITING_MODE=1Bonus, since the page raises Wayland:
AppRunforcesGDK_BACKEND=x11unconditionally, so passingGDK_BACKEND=waylandis silently overridden — my first attempt at this was an invalid test. Editing the hook to actually run natively on Wayland gives 98.6%, versus 98.7% forced X11, with the backend change verified (GDK_BACKEND=waylandin the web process environment, 2 Wayland fds open). XWayland is not implicated.Conclusion: there is no known environment-variable mitigation for this. It sits outside the documented dmabuf class, which is consistent with the stack profile — the main thread is wedged in WebCore and never reaches the event loop, rather than failing in the rendering backend.
Diagnosed with Claude Code.
feat(packaging): ship a Flatpak for the Linux desktop shell (phase-rs…
feat(packaging): ship a Flatpak for the Linux desktop shell (phase-rs…