Repository navigation
Linux browser: WPE WebKit at the display's rate, frames shared with GPUI as dma-bufs - #1039
Conversation
1a8a732 to
bb8fb59
Compare
…PUI as dma-bufs The sidebar browser on Linux ran WebKitGTK in an offscreen window: every frame went through the CPU, over a pipe, into a new GPUI image, so pages topped out near 60 fps whatever the display, and large ones fell well below. Zeron now carries its own WPE WebKit platform (browser/linux/wpe.c) and runs it in the helper when the build and the system have WPE WebKit 2.50+: - WebKit renders on the GPU GPUI draws with, into dma-bufs in a DRM format modifier GPUI's Vulkan device imports. GPUI copies only each frame's damaged regions into the page's atlas tile, on the GPU (zeronsh/zui#18). - Where the driver renders no layout Vulkan imports, the helper reads frames back with OpenGL ES into a sealed memfd. Without a GPU, WebKit renders on the CPU into shared memory. Both update the tile in place. - GPUI's frame callbacks are the pages' vblank: WebKit hears a frame is on screen at most once per display refresh, so pages run at 60, 144 or 240 Hz and never render frames the display won't show. Hidden pages, and pages replaced by their error, get none. - WebKit runs copy, cut and paste itself, sets the pointer's cursor, and sees click counts and the pointer leaving the page. If the WPE helper can't start, the session uses WebKitGTK. Its helper now renders at the output's scale (it rendered 4x the pixels at 1.5x on Wayland and downscaled them) and sends Cairo's surface as-is instead of converting it through GdkPixbuf, which took most of its CPU. A helper that crashes, sends a message Zeron can't read or is still being written by a forked child (ETXTBSY) is stopped, replaced or retried; the tests run the real helper on every render node, in every layout its driver renders, read back and in shared memory, at 60, 144 and 240 Hz.
`ZERON_BROWSER_BENCH` measured one page's frame rate and CPU. It now loads a fresh document for each of a 2D canvas, WebGL 2, scrolling, a CSS spinner and, with `ZERON_BROWSER_BENCH_VIDEO`, a 60 fps video, and records for each: the page's frame rate, when GPUI painted every new frame, the bytes copied into the page's tile from CPU memory and between GPU buffers, CPU, PSS and NVIDIA GPU memory for Zeron and for WebKit's processes, and a capture. It then times 40 presses from GPUI's input to the frame showing them, and measures an idle page. `ZERON_BROWSER_BENCH_WIDTH` sizes the browser. The counts are kept only in fixture builds (`browser-fixture`), for both engines, so WebKitGTK and every WPE path are measured the same way. `ZERON_BROWSER_DMABUF=readback` keeps WebKit on the GPU but reads its frames back, the path GPUs that render no importable layout take.
The WebGL page's benchInfo() clears a WebGL 1 context and reads a pixel back, so a bench run records that both WebGL versions render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: zeronsh[bot] <346184242+zeronsh[bot]@users.noreply.github.com>
The WPE helper kept the last clipboard text Zeron shared, and Zeron shared only text. Pasting with an image or nothing on the system clipboard then inserted that earlier text, possibly into another page. Zeron now shares the clipboard on every paste, and empty text clears the helper's copy. build.rs embeds an empty WPE helper, as when WPE is missing, if wpe.c does not compile against the installed development files (an older GBM), and detects them again when PKG_CONFIG_PATH changes. probe_gpu releases the GBM device and render node it opened when the GPU is unusable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: zeronsh[bot] <346184242+zeronsh[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: zeronsh[bot] <346184242+zeronsh[bot]@users.noreply.github.com>
…ers idle frame callbacks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: zeronsh[bot] <346184242+zeronsh[bot]@users.noreply.github.com>
cc6fcd6 to
266c0c6
Compare
There was a problem hiding this comment.
Security audit: no malicious code, network calls, telemetry, binary blobs or CI changes. The WPE helper's navigation/permission/download policy matches the WebKitGTK helper's line for line; developer extras off, all permissions denied. The zui (#18) and gpui-component (#6) re-pins add only the existing crates.io ash. Fixed: a paste without text no longer pastes the helper's earlier clipboard copy (with a test); build.rs falls back to WebKitGTK if wpe.c fails to compile; probe_gpu leak. Verified on the rebased head: zeron-ui lib tests, the WebKitGTK linux-browser regressions (X11/Wayland × Vulkan/GL), and on WPE WebKit 2.54 (Arch rootfs, shared-memory path, no GPU here) all browser::linux tests plus the fixture on X11 and cage. dma-buf zero-copy was not exercised (no GPU). Follow-up: land zui#18 / gpui-component#6 and re-pin, since b564a34/59f8924 exist only in the contributor's forks.
Refs #540
On a 240 Hz display, pages in the Linux browser now show a new frame on every refresh, at every viewport size measured. That holds for a 2D canvas, WebGL 2, scrolling and CSS animation: one refresh missed in 69,086.
mainshows 5–60 fps and never starts a video. With dma-bufs no frame pixel goes through the CPU, and a frame costs 1–2.4 ms of CPU against up to 230 ms onmain.mainThe problem
On Linux the sidebar browser ran WebKitGTK in a
GtkOffscreenWindow. Each frame was read back to the CPU, converted pixel by pixel throughGdkPixbuf, written whole through the helper's stdout, and uploaded by GPUI as a new image. WebKitGTK also read its scale factor before the view had a window, so on a 1.5× Wayland output it rendered at 2× (1.78× the pixels) and GTK scaled that down. Animated pages never got past 60 fps on any display, and large ones fell far below it (#540).The change
Zeron implements a WPE WebKit platform (
crates/ui/src/browser/linux/wpe.c) and runs it in the helper when the build and the system have WPE WebKit 2.50+:ZERON_BROWSER_DMABUF=readbackZERON_BROWSER_DMABUF=0ETXTBSY) is stopped, replaced or retried.GdkPixbuf.build.rsembeds the WPE helper when pkg-config findswpe-webkit-2.0 >= 2.50and its dependencies, and warns otherwise. The release workflows install only WebKitGTK, so release builds and CI keep using WebKitGTK until they add the WPE packages.Dependencies: zeronsh/zui#18 (in-place atlas updates from pixels or imported dma-bufs, display-rate frame callbacks in inactive windows, a Wayland frame-loop fix) and zeronsh/gpui-component#6 (the same zui pin, so the lockfile has one gpui).
Cargo.tomlpins both PR heads; re-pin to their merge commits once they land.Numbers
Release builds of
main(c04e374) and this branch, measured by the same bench code. This PR'sZERON_BROWSER_BENCHand its fixture-only frame counters were ported ontomainfor the baseline; the port is not part of the PR.smaps_rollup, GPU memory fromnvidia-smi.Frames shown per second
mainDisplay refreshes without a new frame · p99 interval between frames
mainCPU, Zeron + WebKit (% of one core) · CPU time per frame shown
mainMB copied into the page's atlas tile per frame shown
mainPeak memory over a run, MiB: PSS · NVIDIA GPU memory (Zeron + WebKit)
mainPress → frame painted, ms: p50 · p95 · max (120 presses per cell)
mainIdle page: CPU · frames per second
mainOther configurations (WPE dma-buf unless stated):
main(2 runs)ZED_DEVICE_ID=0x13c0), 1649×1386 px, modifier0x0200000000401b03(3 runs)The iGPU has 2 compute units and the display hangs off the RTX 3080, so the compositor copies every frame across GPUs. It still renders without readback.
Correctness
End to end,
browser-fixture(debug build) on DP-1. It checks input, menus, resizes, sidebar transitions, frosted overlays, captures, the page's frame rate against the display's, and WebKit's buffer count.[64,127,191,255].0x0300000000606010–15,0x0300000000e08010–15) and 4 AMD ones (0x0200000000401b03,0x0200000000401603,0x0200000000000901, linear). It also reads frames back, and renders in shared memory.mainand here:networkStatestaysNETWORK_NO_SOURCEwith no error, althoughcanPlayTypeanswersprobably. Not investigated.Tests
browser::linux::wpe(24): packet framing and fd pairing, oversized packets, buffer and mirror validation, damage clamping and merging, vblank rate detection, scroll scaling, cursors. Against the real helper:ETXTBSY);cargo test -p zeron-ui --lib browser::linux -- --include-ignored --test-threads=1: 31 passed. Before the rebase, 16 back-to-back runs of the 42browsertests all passed.ui-testscommand (cargo nextest … -p zeron-ui --lib --test voice_orb_geometry), locally: 1950 passed, 12 skipped.linux-browserran the WebKitGTK fallback on Xvfb and weston, Vulkan and OpenGL.Review
Three rounds of adversarial review (Claude Fable 5.1, read-only):
presentnow wakes the surface. The fix is mutation-checked by a test.releaseis now always carried by an atlas completion, errors included.ETXTBSYwhen a forked child still held a freshly extracted helper open: the spawn now retries with backoff.closeevicts the image.#540 acceptance criteria
main.mainat three viewport sizes.Not verified
Intel GPUs, a real GPU device loss, a release package with WPE WebKit (the workflows don't install it yet), and macOS or Windows at runtime (CI only).