diff --git a/src-tauri/src/gpu_workarounds.rs b/src-tauri/src/gpu_workarounds.rs index 2c36e97..361fd6d 100644 --- a/src-tauri/src/gpu_workarounds.rs +++ b/src-tauri/src/gpu_workarounds.rs @@ -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 = 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()); }