2026-09-27 16:56:36 -04:00
|
|
|
//! WebKitGTK workarounds for NVIDIA's proprietary driver on Linux.
|
|
|
|
|
//!
|
2026-09-27 22:22:00 -04:00
|
|
|
//! 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.
|
2026-09-27 16:56:36 -04:00
|
|
|
//!
|
2026-09-27 22:22:00 -04:00
|
|
|
//! 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.
|
2026-09-27 16:56:36 -04:00
|
|
|
|
|
|
|
|
/// Environment variables to set: pure, for tests.
|
|
|
|
|
pub(crate) fn decide(
|
|
|
|
|
nvidia: bool,
|
|
|
|
|
get: impl Fn(&str) -> Option<String>,
|
|
|
|
|
) -> Vec<(&'static str, &'static str)> {
|
|
|
|
|
let set = |k: &str| get(k).is_some_and(|v| !v.is_empty());
|
|
|
|
|
if !nvidia || set("LOTUS_NO_GPU_WORKAROUNDS") {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
2026-09-27 22:22:00 -04:00
|
|
|
let wayland_session = set("WAYLAND_DISPLAY")
|
2026-09-27 16:56:36 -04:00
|
|
|
|| get("XDG_SESSION_TYPE").is_some_and(|v| v.eq_ignore_ascii_case("wayland"));
|
2026-09-27 22:22:00 -04:00
|
|
|
// 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");
|
2026-09-27 16:56:36 -04:00
|
|
|
}
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
fn nvidia_driver_loaded() -> bool {
|
|
|
|
|
std::path::Path::new("/proc/driver/nvidia/version").exists()
|
|
|
|
|
|| std::path::Path::new("/sys/module/nvidia").exists()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Call first thing in `main`, before anything starts GTK or spawns threads.
|
|
|
|
|
pub fn apply() {
|
|
|
|
|
#[cfg(target_os = "linux")]
|
2026-09-27 22:22:00 -04:00
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
2026-09-27 16:56:36 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::decide;
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
fn run(nvidia: bool, env: &[(&str, &str)]) -> Vec<(&'static str, &'static str)> {
|
|
|
|
|
let map: HashMap<String, String> = env
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
|
|
|
.collect();
|
|
|
|
|
decide(nvidia, |k| map.get(k).cloned())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const WAYLAND: &[(&str, &str)] = &[("WAYLAND_DISPLAY", "wayland-0"), ("DISPLAY", ":0")];
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn nothing_without_nvidia() {
|
|
|
|
|
assert!(run(false, WAYLAND).is_empty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-09-27 22:22:00 -04:00
|
|
|
fn nvidia_wayland_keeps_gpu_renderer_and_disables_explicit_sync() {
|
2026-09-27 16:56:36 -04:00
|
|
|
assert_eq!(
|
|
|
|
|
run(true, WAYLAND),
|
2026-09-27 22:22:00 -04:00
|
|
|
vec![("__NV_DISABLE_EXPLICIT_SYNC", "1")]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
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")]),
|
|
|
|
|
vec![("__NV_DISABLE_EXPLICIT_SYNC", "1")]
|
2026-09-27 16:56:36 -04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-09-27 22:22:00 -04:00
|
|
|
fn nvidia_x11_session_disables_dmabuf_renderer() {
|
2026-09-27 16:56:36 -04:00
|
|
|
assert_eq!(
|
2026-09-27 22:22:00 -04:00
|
|
|
run(true, &[("DISPLAY", ":0"), ("XDG_SESSION_TYPE", "x11")]),
|
2026-09-27 16:56:36 -04:00
|
|
|
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-09-27 22:22:00 -04:00
|
|
|
fn user_forced_x11_on_wayland_counts_as_x11() {
|
|
|
|
|
let mut env = WAYLAND.to_vec();
|
|
|
|
|
env.push(("GDK_BACKEND", "x11"));
|
2026-09-27 16:56:36 -04:00
|
|
|
assert_eq!(
|
2026-09-27 22:22:00 -04:00
|
|
|
run(true, &env),
|
2026-09-27 16:56:36 -04:00
|
|
|
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
|
|
|
|
);
|
2026-09-27 22:22:00 -04:00
|
|
|
let mut env = WAYLAND.to_vec();
|
|
|
|
|
env.push(("GDK_BACKEND", "wayland,x11"));
|
|
|
|
|
assert_eq!(run(true, &env), vec![("__NV_DISABLE_EXPLICIT_SYNC", "1")]);
|
2026-09-27 16:56:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-09-27 22:22:00 -04:00
|
|
|
fn safe_mode_adds_shared_memory_rendering_on_wayland() {
|
|
|
|
|
let mut env = WAYLAND.to_vec();
|
|
|
|
|
env.push(("LOTUS_GPU_SAFE_MODE", "1"));
|
2026-09-27 16:56:36 -04:00
|
|
|
assert_eq!(
|
2026-09-27 22:22:00 -04:00
|
|
|
run(true, &env),
|
2026-09-27 16:56:36 -04:00
|
|
|
vec![
|
2026-09-27 22:22:00 -04:00
|
|
|
("__NV_DISABLE_EXPLICIT_SYNC", "1"),
|
|
|
|
|
("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
|
2026-09-27 16:56:36 -04:00
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn user_settings_win() {
|
2026-09-27 22:22:00 -04:00
|
|
|
let mut env = WAYLAND.to_vec();
|
|
|
|
|
env.push(("__NV_DISABLE_EXPLICIT_SYNC", "0"));
|
2026-09-27 16:56:36 -04:00
|
|
|
assert!(run(true, &env).is_empty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn opt_out_disables_everything() {
|
|
|
|
|
let mut env = WAYLAND.to_vec();
|
|
|
|
|
env.push(("LOTUS_NO_GPU_WORKAROUNDS", "1"));
|
|
|
|
|
assert!(run(true, &env).is_empty());
|
|
|
|
|
}
|
|
|
|
|
}
|