From d2b34a6b2a30f14ce1c40111c9c4b0eb392a08dd Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 6 Nov 2026 20:34:13 -0800 Subject: [PATCH 1/4] chore(quest): claim quest/m0/cef-x11-nvidia.md Quest-Claim: 22fff56f2db47852 From fcad37883bf9a8648846609cc5eda0f87ec85936 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 6 Nov 2026 21:11:07 -0800 Subject: [PATCH 2/4] fix(scene): browser sources under X11 paint only on the X server's GPU Under X11, CEF allocates shared textures on the device the X server hands out over DRI3, whatever --render-node-override says. With the studio on another GPU, that GPU's tiling came out scrambled while the source still read as copied on the GPU. The studio now asks the X server for its DRI3 device at setup and takes the CPU path, saying why, when it is not the studio's GPU. A page painting into memory now carries why, which the Sources panel shows on hover and Health lists while the program shows the source. On NVIDIA under XWayland, the GPU path already paints since --use-angle=gl-egl (#73), which finishes quest/m0/cef-x11-nvidia.md. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 1 + README.md | 8 +- quest/m0/README.md | 1 - quest/m0/cef-x11-nvidia.md | 31 -------- rs/moq-scene/Cargo.toml | 5 +- rs/moq-scene/src/browser/host.rs | 5 +- rs/moq-scene/src/browser/mod.rs | 105 ++++++++++++++++++++++----- rs/moq-scene/src/browser/protocol.rs | 6 +- rs/moq-scene/src/browser/run.rs | 10 ++- rs/moq-studio/src/engine.rs | 14 +++- rs/moq-studio/src/health.rs | 11 +++ rs/moq-studio/src/ui.rs | 7 +- rs/moq-studio/tests/browser.rs | 42 ++++++++++- 13 files changed, 175 insertions(+), 71 deletions(-) delete mode 100644 quest/m0/cef-x11-nvidia.md diff --git a/Cargo.lock b/Cargo.lock index e87b2ec4..a8d3c23e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4313,6 +4313,7 @@ dependencies = [ "url", "vello", "wuff", + "x11rb 0.13.2", ] [[package]] diff --git a/README.md b/README.md index 79fa973e..edc8d52c 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Every panel is a tab you can drag, split, or stack. | Scenes | Pick the scene in preview, and add, rename (double-click), reorder, or remove one. A scene on program, or still leaving it in a transition, cannot be removed, even by an undo, until program cuts away from it. | | History | See every edit, and undo or redo back to any of them. | | Items | Place, show, hide, and order what the preview scene or the top layer draws on the canvas shown, back to front. Select an item to filter or copy it. | -| Sources | Add a source by hand, see whether each and its own track are live and where it is shown, clone one, and remove one. Select a source to filter it. A camera or broadcast without a new picture, or a microphone or system audio without new sound, for a second reads as stalled. A browser source shows whether its page is copied on the GPU or, as a fallback, uploaded from memory (CPU), in place of a track, and **Reload** restarts its page. A media file reads as ended once it plays to its end without looping, **Play** plays it from its start, **Stop** stops it at once, blank and silent, until played again, and **Reload** reads the file again. | +| Sources | Add a source by hand, see whether each and its own track are live and where it is shown, clone one, and remove one. Select a source to filter it. A camera or broadcast without a new picture, or a microphone or system audio without new sound, for a second reads as stalled. A browser source shows whether its page is copied on the GPU or, as a fallback, uploaded from memory (CPU, saying why on hover), in place of a track, and **Reload** restarts its page. A media file reads as ended once it plays to its end without looping, **Play** plays it from its start, **Stop** stops it at once, blank and silent, until played again, and **Reload** reads the file again. | | Devices | List this machine's cameras, displays, windows, and audio inputs, and add one as a source. | | Guests | Invite a guest, show their join link as a QR code, see who joined, and take a guest out of every scene. | | Filters | Remove the selected source's background, crop the selected source or item, add, order, and set its filters, set an item's blend mode, and copy or paste filters. | @@ -870,7 +870,8 @@ driver that imports them, and NV12 capture and VideoToolbox decode surfaces on macOS. A browser source's page is copied on the GPU from the shared texture CEF paints, given the same Vulkan DMA-BUF import and a display to paint through: the Wayland one when `$WAYLAND_DISPLAY` is set, needing no XWayland, or else -the X one in `$DISPLAY`. Every other source, every source on Windows or on a +the X one in `$DISPLAY`, when the X server runs on the studio's GPU, since +CEF paints on the X server's there. Every other source, every source on Windows or on a GPU that cannot import, and every still image delivers its frames from the CPU, which the compositor uploads. A browser source takes that path with no display at all too, and there renders its page in software, WebGL included, so @@ -966,7 +967,8 @@ congested or losing packets, the relay is far away, the encoder is behind or stuck, a source stalled (a camera or broadcast the program draws, or a microphone or system audio the mix on program plays: unmuted and not left out of it), a source the program shows or plays failed or, as a media file -that does not loop, played to its end, or viewers are late, stalling, or cannot +that does not loop, played to its end, a browser source the program shows is +uploaded from memory rather than copied on the GPU, or viewers are late, stalling, or cannot decode a track. A relay lost while the other serves is degraded, since viewers have nothing left to fall back to; with none serving it is unhealthy. Below that it shows: diff --git a/quest/m0/README.md b/quest/m0/README.md index 1e0cc323..6f8cfc80 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -100,7 +100,6 @@ repository. - [Web studio](/quest/m0/web.md) - the full studio in wasm on WebGPU and WebCodecs, served from this repository - [Virtual camera](/quest/m0/vcam/README.md) - the program or a published scene exposed as a system camera on Linux, Windows, and macOS - [Browser sources on the GPU under Mesa](/quest/m0/cef-headless.md) - shared-texture pages paint on Intel and AMD under Wayland, and a page that cannot paint fails loudly -- [Browser sources on the GPU under X11 on NVIDIA](/quest/m0/cef-x11-nvidia.md) - the X11 GPU path paints on NVIDIA, or falls back to the CPU path loudly - [The Apple signing identity is available](/quest/m0/signing.md) - the Apple credentials packaging and the macOS camera need; Windows ships unsigned - [Packaging](/quest/m0/packaging.md) - a signed macOS app, a Windows installer, and a Flatpak for Linux - [The persistable portal restore token is pinned](/quest/m0/pin-portal-token.md) - moq-video's restore token, for Wayland sources diff --git a/quest/m0/cef-x11-nvidia.md b/quest/m0/cef-x11-nvidia.md deleted file mode 100644 index 8a89ed5a..00000000 --- a/quest/m0/cef-x11-nvidia.md +++ /dev/null @@ -1,31 +0,0 @@ -# [S] Browser sources on the GPU under X11 on NVIDIA - -## Goal - -On an NVIDIA GPU in an X11 or XWayland session, browser sources paint on -the GPU path. If the studio cannot give CEF a GBM device, it detects that at -startup and takes the CPU path with a note in Health, rather than leaving -pages stuck in Starting. - -## Plan - -From [moq-dev/studio#59](https://github.com/moq-dev/studio/pull/59), picked -2026-11-05 (maintainer, /quest-plan), m0 by the rule that fixes join m0. - -- On nixos (RTX 3070 Ti, NVIDIA 580), the GPU path under X11 never paints: - CEF's GPU process logs "Can't create buffer -- gbm device is missing". - Wayland sessions avoid it since #59, but X11-only sessions still hit it. -- Since [moq-dev/studio#73](https://github.com/moq-dev/studio/pull/73) the X11 - path passes `--use-angle=gl-egl`, which made it paint on Mesa; rerun nixos - with it first. On nixos2 under GNOME's XWayland, with the studio on the RTX - 2070, CEF still allocated on the Intel GPU that runs the X server (Intel - X-tiled modifier) despite `--render-node-override`. -- First find why. Likely suspects: the NVIDIA GBM backend - (`nvidia-drm_gbm.so`) is not on CEF's search path (`GBM_BACKENDS_PATH`), or - `nvidia-drm` modeset is off. Fix it if the studio can, for example by - passing the backend path to the helper or packaging it. -- Otherwise fail loud: probe for a GBM device when the helper starts, and if - there is none, take the CPU path and say why in Health and the Sources panel. - Fail on an observed probe result, never on a timeout. -- Verify on nixos under X11 (XWayland) and record how in the PR; no CI job - has a GPU. diff --git a/rs/moq-scene/Cargo.toml b/rs/moq-scene/Cargo.toml index 88f941dd..ffcd7a40 100644 --- a/rs/moq-scene/Cargo.toml +++ b/rs/moq-scene/Cargo.toml @@ -88,9 +88,12 @@ wuff = "0.2" # `accelerated_osr` copies the page's shared texture into the studio's device. cef = { version = "156.3", default-features = false, features = ["accelerated_osr"], optional = true } postcard = { version = "1", default-features = false, features = ["use-std"] } -rustix = { version = "1", features = ["net", "process", "stdio"] } +rustix = { version = "1", features = ["fs", "net", "process", "stdio"] } # The helper's CEF profile, removed once the helper has exited. tempfile = { workspace = true } +# Which GPU the X server runs on, which CEF paints on under X11. Pure Rust, and +# 0.13 as the studio's hotkeys use. +x11rb = { version = "0.13", features = ["dri3"] } [dev-dependencies] # An encoder on every machine, for the source track and replay tests. diff --git a/rs/moq-scene/src/browser/host.rs b/rs/moq-scene/src/browser/host.rs index 378812f9..e5e3991c 100644 --- a/rs/moq-scene/src/browser/host.rs +++ b/rs/moq-scene/src/browser/host.rs @@ -496,8 +496,9 @@ wrap_app! { Display::X11 => "x11", }; command_line.append_switch_with_value(Some(&"ozone-platform".into()), Some(&platform.into())); - // On X11, ANGLE otherwise draws through GLX, which on Mesa - // leaves shared textures blank. Wayland is always EGL. + // On X11, ANGLE otherwise draws through GLX, which leaves + // shared textures blank on Mesa, and on NVIDIA finds no GBM + // device to allocate them. Wayland is always EGL. if *display == Display::X11 { command_line.append_switch_with_value(Some(&"use-angle".into()), Some(&"gl-egl".into())); } diff --git a/rs/moq-scene/src/browser/mod.rs b/rs/moq-scene/src/browser/mod.rs index c16dbcfb..17883dda 100644 --- a/rs/moq-scene/src/browser/mod.rs +++ b/rs/moq-scene/src/browser/mod.rs @@ -12,7 +12,7 @@ //! become one; CEF's own subprocesses start the same way. The helper paints //! into a shared GPU texture the studio copies on its device, or into memory //! the studio uploads when either side cannot share textures or there is no -//! display to paint them through ([`Paint`] says which). Its popups, such as a +//! display to paint them through ([`Paint`] says which, and why). Its popups, such as a //! `