Repository navigation
Linux: import dma-bufs into atlas tiles, and display-rate frame callbacks - #18
Open
alexandre-vl wants to merge 3 commits into
Open
alexandre-vl wants to merge 3 commits into
alexandre-vl wants to merge 3 commits into
Conversation
A producer that tracks damage, like an embedded browser, uploads only what changed into an image it already painted instead of a whole new image per frame: `Window::update_image` overwrites a region of the image's tile, and `RenderImage::blank` with `Window::insert_blank_image` gives a frame-sized tile without uploading blank pixels. On Linux, a dma-buf another process renders into is imported into the Vulkan device once (`Window::import_dmabuf`) and its damaged regions are copied on the GPU, so frames never touch the CPU. The device opens with VK_EXT_image_drm_format_modifier when the GPU has it, so tiled layouts import too: GPUs whose EGL renders only to tiled layouts (NVIDIA) share frames as well. `Window::dmabuf_target` names the render node and the layouts the atlas imports: uncompressed ones only, since wgpu moves an imported image out of an undefined layout without acquiring it from the producer's queue, which may reinitialize compression metadata. `on_complete` runs exactly once, outside the atlas lock, after the GPU has finished all work queued before it, errors included, so a producer can recycle a buffer as soon as the GPU stops reading it. Pixel uploads land before the frame's copies and drop the copies they cover. A lost device runs the pending completions and drops the imports; producers import again.
An inactive window slows its frame callbacks to ~30 fps to save energy. Content that follows the display, such as video or a web page, asks for the next frame with `Window::on_next_display_frame` instead: the window keeps the display's rate for that callback, and slows down again once such requests stop.
A frame callback requests the next one, which only a commit sends. A draw forced outside the frame loop left `renderer_presented` set, so the next frame that drew nothing skipped that commit: the compositor never answered and frames stopped. Reset it with each frame callback.
zeronsh Bot
pushed a commit
to alexandre-vl/zeron
that referenced
this pull request
Oct 26, 2026
…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.
zeronsh Bot
pushed a commit
to alexandre-vl/zeron
that referenced
this pull request
Oct 27, 2026
…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.
zeronsh Bot
pushed a commit
to alexandre-vl/zeron
that referenced
this pull request
Oct 27, 2026
…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.
zeronsh Bot
pushed a commit
to alexandre-vl/zeron
that referenced
this pull request
Oct 27, 2026
…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.
zeronsh Bot
added a commit
to zeronsh/zeron
that referenced
this pull request
Oct 27, 2026
…PUI as dma-bufs (#1039) * Linux browser: WPE WebKit at the display's rate, frames shared with GPUI 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. * Linux browser bench: shown frames, pacing, copies, press latency, memory `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. * Linux browser bench: WebGL page checks a WebGL 1 context too 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. * Linux browser: rustfmt the WPE engine and its tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: zeronsh[bot] <346184242+zeronsh[bot]@users.noreply.github.com> * Linux browser: a paste without text clears WPE's clipboard copy 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> * Linux browser fixture: rustfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: zeronsh[bot] <346184242+zeronsh[bot]@users.noreply.github.com> * Linux browser docs: the WPE vblank check needs a compositor that answers 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> --------- Co-authored-by: Wing <44917547+wingleeio@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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs zeronsh/zeron#540. Zeron's Linux browser uses this branch: zeronsh/zeron#1039 (it pins b564a34).
Zeron's Linux browser now renders pages with WPE WebKit in a helper process, into dma-bufs. To show those frames at the display's rate without CPU copies, GPUI needs three things:
The branch is stacked on 0dc69aa (
zeron/drag-needs-held-button), the rev Zeron pins today, so this diff shows only the three commits below.Window::on_next_display_frameAPI
RenderImage::blank(size)update_imageWindow::insert_blank_image(image, frame)Window::update_image(image, frame, region, update, on_complete)gpui_wgpuupdates in place, the others returnOk(false)regionfrom CPU pixels (AtlasTileUpdate::Pixels) or from an imported dma-buf (AtlasTileUpdate::Dmabuf)Window::dmabuf_target()Window::import_dmabuf(&Dmabuf)→DmabufImportWindow::on_next_display_frame(cb)on_next_framefor content that follows the display: an inactive window, otherwise capped at ~30 fps, keeps the display's ratePlatformAtlas::{insert_blank, update_tile, dmabuf_target, import_dmabuf}The
update_tilecontracton_completeruns exactly once, errors included, never with the atlas locked, and only after the GPU has finished all work queued before it. A producer can recycle a buffer from that callback.What is imported
Format:
AR24(B8G8R8A8_UNORM), single plane.The device opens with
VK_EXT_image_drm_format_modifierwhen the GPU has it, so tiled layouts import too. NVIDIA's EGL renders only to its tiled layouts.Only uncompressed modifiers are imported:
wgpu moves an imported image out of
UNDEFINEDwithout acquiring it from the producer's queue, which lets a driver reinitialize compression metadata and lose the pixels.Before creating the image, the import checks each layout's importability and maximum extent (
vkGetPhysicalDeviceImageFormatProperties2with the modifier). It also checks the dma-buf is at least as large as the image's memory requirement, because an unsupported layout or size is undefined behavior, not an error.The image binds the buffer with an explicit plane layout (offset, stride) in dedicated memory. The duplicated fd is closed if allocation fails; on success, Vulkan owns it.
Measured in Zeron
zeronsh/zeron#1039 measured Zeron's browser with this branch: release builds, a 240 Hz display, a 2849×1956 px viewport, 3 runs per cell.
main(WebKitGTK, CPU frames)The same path runs on the Radeon 610M iGPU (RADV, modifier
0x0200000000401b03) and through Xwayland. The PR has the full tables.Tests
New:
update_tile_overwrites_only_the_region,update_tile_needs_a_cached_tile_inside_its_bounds,dmabuf_regions_copy_into_the_tile,dmabuf_imports_refuse_unsupported_layouts_and_sizes,only_uncompressed_layouts_are_imported,inactive_windows_slow_frame_callbacks_but_not_display_frames.cargo test --locked -p gpui --libcargo test --locked -p gpui_wgpu --lib -- --include-ignoredcargo test --locked -p gpui_linux --libcargo check --locked -p gpui -p gpui_wgpu -p gpui_linux --testson 5526f55 aloneNot covered by tests:
clear()with copies pending.Need help on this PR? Tag
@codesmith-botwith what you need.