fix(linux): NVIDIA + Wayland keeps the GPU renderer (explicit-sync fix)
The 4.12.343 workaround (WEBKIT_DISABLE_DMABUF_RENDERER=1 + GDK_BACKEND=x11)
made the app launch but pushed every frame through shared memory under
XWayland: noticeably laggy at 3840x2058. The reporter's WAYLAND_DEBUG trace
showed the real cause on native Wayland:
wl_display#1.error(wp_linux_drm_syncobj_surface_v1#51, 4,
"explicit sync is used, but no acquire point is set")
Gdk-Message: Error 71 (Protocol error) dispatching to Wayland display.
An explicit-sync bug, not a GBM format/modifier one. Their test matrix
(RTX 3070, driver 615.71.09, webkit2gtk 2.52.6, KDE Wayland):
- __NV_DISABLE_EXPLICIT_SYNC=1 alone: clean, GPU (DMA-BUF) renderer, smooth;
- WEBKIT_DISABLE_DMABUF_RENDERER=1 alone on Wayland: clean (no X11 needed);
- WEBKIT_DMABUF_RENDERER_DISABLE_GBM=1: same explicit-sync error on Wayland,
"Failed to import DMABuf" under XWayland;
- X11/XWayland with the DMA-BUF renderer: "Failed to create GBM buffer".
New defaults when the NVIDIA driver is loaded:
- native Wayland: __NV_DISABLE_EXPLICIT_SYNC=1, GPU renderer kept;
- X11 session or user-forced GDK_BACKEND=x11: WEBKIT_DISABLE_DMABUF_RENDERER=1;
- never force X11 any more;
- LOTUS_GPU_SAFE_MODE=1: opt-in shared-memory rendering on Wayland too;
- LOTUS_NO_GPU_WORKAROUNDS=1 / user-set values: untouched.
One stderr line says what was set. 9 unit tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5b4528e4e5
commit
bb5364b53c
@@ -1,17 +1,22 @@
|
||||
//! WebKitGTK workarounds for NVIDIA's proprietary driver on Linux.
|
||||
//!
|
||||
//! On NVIDIA + Wayland (reported on CachyOS/KDE, driver 615, webkit2gtk 2.52)
|
||||
//! the app never shows a window: GDK dies with "Error 71 (Protocol error)
|
||||
//! dispatching to Wayland display", and under XWayland WebKit's DMA-BUF
|
||||
//! renderer then fails with "Failed to create GBM buffer … Invalid argument".
|
||||
//! `WEBKIT_DISABLE_DMABUF_RENDERER=1 GDK_BACKEND=x11` makes it run normally.
|
||||
//! Diagnosed on CachyOS/KDE Wayland, RTX 3070, driver 615.71.09, webkit2gtk
|
||||
//! 2.52.6 (reported 2026-09-27, WAYLAND_DEBUG trace in the PR):
|
||||
//! - Native Wayland: the compositor kills the connection with
|
||||
//! `wp_linux_drm_syncobj_surface_v1 … "explicit sync is used, but no acquire
|
||||
//! point is set"` (GDK: "Error 71 (Protocol error)"), i.e. an explicit-sync
|
||||
//! bug between WebKit and the driver, not a buffer-format problem.
|
||||
//! `__NV_DISABLE_EXPLICIT_SYNC=1` fixes it and keeps WebKit's GPU (DMA-BUF)
|
||||
//! renderer, so that is the default on native Wayland.
|
||||
//! - X11 / XWayland: the DMA-BUF renderer can't allocate or import buffers
|
||||
//! ("Failed to create GBM buffer … Invalid argument", "Failed to import
|
||||
//! DMABuf"), so there it is disabled (`WEBKIT_DISABLE_DMABUF_RENDERER=1`,
|
||||
//! shared-memory frames). We never force X11 any more.
|
||||
//!
|
||||
//! So, before GTK/WebKit initialise, and only when the NVIDIA driver is loaded:
|
||||
//! - `WEBKIT_DISABLE_DMABUF_RENDERER=1`;
|
||||
//! - on a Wayland session with XWayland available, `GDK_BACKEND=x11`.
|
||||
//!
|
||||
//! Anything the user already set wins, and `LOTUS_NO_GPU_WORKAROUNDS=1`
|
||||
//! disables all of it (e.g. once a newer driver/WebKit fixes this).
|
||||
//! Opt-ins / opt-outs (anything the user already set always wins):
|
||||
//! - `LOTUS_GPU_SAFE_MODE=1`: last resort, shared-memory rendering on Wayland
|
||||
//! too (slower, especially at high resolutions).
|
||||
//! - `LOTUS_NO_GPU_WORKAROUNDS=1`: change nothing.
|
||||
|
||||
/// Environment variables to set: pure, for tests.
|
||||
pub(crate) fn decide(
|
||||
@@ -22,15 +27,30 @@ pub(crate) fn decide(
|
||||
if !nvidia || set("LOTUS_NO_GPU_WORKAROUNDS") {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
if !set("WEBKIT_DISABLE_DMABUF_RENDERER") {
|
||||
out.push(("WEBKIT_DISABLE_DMABUF_RENDERER", "1"));
|
||||
}
|
||||
let wayland = set("WAYLAND_DISPLAY")
|
||||
let wayland_session = set("WAYLAND_DISPLAY")
|
||||
|| get("XDG_SESSION_TYPE").is_some_and(|v| v.eq_ignore_ascii_case("wayland"));
|
||||
// Only fall back to X11 when there is an X server (XWayland) to talk to.
|
||||
if wayland && set("DISPLAY") && !set("GDK_BACKEND") {
|
||||
out.push(("GDK_BACKEND", "x11"));
|
||||
// GTK uses Wayland unless GDK_BACKEND says otherwise (it may list
|
||||
// several, e.g. "wayland,x11": the first one wins).
|
||||
let native_wayland = wayland_session
|
||||
&& get("GDK_BACKEND").map_or(true, |v| {
|
||||
v.is_empty()
|
||||
|| v.trim_start().to_ascii_lowercase().starts_with("wayland")
|
||||
|| v.trim() == "*"
|
||||
});
|
||||
|
||||
let mut out = Vec::new();
|
||||
let mut want = |k: &'static str, v: &'static str| {
|
||||
if !set(k) {
|
||||
out.push((k, v));
|
||||
}
|
||||
};
|
||||
if native_wayland {
|
||||
want("__NV_DISABLE_EXPLICIT_SYNC", "1");
|
||||
if set("LOTUS_GPU_SAFE_MODE") {
|
||||
want("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
}
|
||||
} else {
|
||||
want("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -44,9 +64,20 @@ fn nvidia_driver_loaded() -> bool {
|
||||
/// Call first thing in `main`, before anything starts GTK or spawns threads.
|
||||
pub fn apply() {
|
||||
#[cfg(target_os = "linux")]
|
||||
for (key, value) in decide(nvidia_driver_loaded(), |k| std::env::var(k).ok()) {
|
||||
eprintln!("gpu-workarounds: NVIDIA driver detected, setting {key}={value} (LOTUS_NO_GPU_WORKAROUNDS=1 to disable)");
|
||||
std::env::set_var(key, value);
|
||||
{
|
||||
let changes = decide(nvidia_driver_loaded(), |k| std::env::var(k).ok());
|
||||
if changes.is_empty() {
|
||||
return;
|
||||
}
|
||||
let list: Vec<String> = changes.iter().map(|(k, v)| format!("{k}={v}")).collect();
|
||||
eprintln!(
|
||||
"gpu-workarounds: NVIDIA driver detected, setting {} \
|
||||
(LOTUS_GPU_SAFE_MODE=1 for shared-memory rendering, LOTUS_NO_GPU_WORKAROUNDS=1 to disable)",
|
||||
list.join(" ")
|
||||
);
|
||||
for (key, value) in changes {
|
||||
std::env::set_var(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,51 +102,70 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nvidia_wayland_gets_both() {
|
||||
fn nvidia_wayland_keeps_gpu_renderer_and_disables_explicit_sync() {
|
||||
assert_eq!(
|
||||
run(true, WAYLAND),
|
||||
vec![
|
||||
("WEBKIT_DISABLE_DMABUF_RENDERER", "1"),
|
||||
("GDK_BACKEND", "x11")
|
||||
]
|
||||
vec![("__NV_DISABLE_EXPLICIT_SYNC", "1")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nvidia_x11_session_only_disables_dmabuf() {
|
||||
assert_eq!(
|
||||
run(true, &[("DISPLAY", ":0")]),
|
||||
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wayland_without_xwayland_keeps_wayland() {
|
||||
assert_eq!(
|
||||
run(true, &[("WAYLAND_DISPLAY", "wayland-0")]),
|
||||
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
||||
);
|
||||
fn never_forces_x11() {
|
||||
for env in [
|
||||
WAYLAND,
|
||||
&[("DISPLAY", ":0")][..],
|
||||
&[("WAYLAND_DISPLAY", "w")][..],
|
||||
] {
|
||||
assert!(run(true, env).iter().all(|(k, _)| *k != "GDK_BACKEND"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xdg_session_type_counts_as_wayland() {
|
||||
assert_eq!(
|
||||
run(true, &[("XDG_SESSION_TYPE", "wayland"), ("DISPLAY", ":1")]),
|
||||
run(true, &[("XDG_SESSION_TYPE", "wayland")]),
|
||||
vec![("__NV_DISABLE_EXPLICIT_SYNC", "1")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nvidia_x11_session_disables_dmabuf_renderer() {
|
||||
assert_eq!(
|
||||
run(true, &[("DISPLAY", ":0"), ("XDG_SESSION_TYPE", "x11")]),
|
||||
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_forced_x11_on_wayland_counts_as_x11() {
|
||||
let mut env = WAYLAND.to_vec();
|
||||
env.push(("GDK_BACKEND", "x11"));
|
||||
assert_eq!(
|
||||
run(true, &env),
|
||||
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
||||
);
|
||||
let mut env = WAYLAND.to_vec();
|
||||
env.push(("GDK_BACKEND", "wayland,x11"));
|
||||
assert_eq!(run(true, &env), vec![("__NV_DISABLE_EXPLICIT_SYNC", "1")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn safe_mode_adds_shared_memory_rendering_on_wayland() {
|
||||
let mut env = WAYLAND.to_vec();
|
||||
env.push(("LOTUS_GPU_SAFE_MODE", "1"));
|
||||
assert_eq!(
|
||||
run(true, &env),
|
||||
vec![
|
||||
("WEBKIT_DISABLE_DMABUF_RENDERER", "1"),
|
||||
("GDK_BACKEND", "x11")
|
||||
("__NV_DISABLE_EXPLICIT_SYNC", "1"),
|
||||
("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_settings_win() {
|
||||
let env = [
|
||||
("WAYLAND_DISPLAY", "wayland-0"),
|
||||
("DISPLAY", ":0"),
|
||||
("WEBKIT_DISABLE_DMABUF_RENDERER", "0"),
|
||||
("GDK_BACKEND", "wayland"),
|
||||
];
|
||||
let mut env = WAYLAND.to_vec();
|
||||
env.push(("__NV_DISABLE_EXPLICIT_SYNC", "0"));
|
||||
assert!(run(true, &env).is_empty());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user