Merge pull request 'fix(linux): skip the NVIDIA explicit-sync workaround on driver 620+ with WebKitGTK 2.54+ (#48)' (#49) from fix/nvidia-explicit-sync-gate into main
Build Lotus Chat Desktop / prepare (push) Successful in 33s
Build Lotus Chat Desktop / build-linux (push) Successful in 17m7s
Build Lotus Chat Desktop / build-arch (push) Successful in 1m5s
Build Lotus Chat Desktop / checksums (push) Successful in 49s
Build Lotus Chat Desktop / build-windows (push) Successful in 42m30s
Build Lotus Chat Desktop / update-manifest (push) Successful in 39s

This commit was merged in pull request #49.
This commit is contained in:
2026-11-09 17:00:34 -05:00
+158 -5
View File
@@ -7,7 +7,16 @@
//! 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.
//! renderer, so that is the default on native Wayland, but only on the
//! versions that need it (cinny-desktop #48, below).
//! - #48 (2026-11-08, same RTX 3070, native Wayland, Arch): with driver
//! 620.70.06 and webkit2gtk 2.54.2 the app launches and runs smoothly
//! WITHOUT the flag, while the default (flag set) was reported as
//! "extremely laggy" (implicit sync on NVIDIA can stall frames). So the
//! flag is skipped when the driver is at least 620 AND WebKitGTK is at
//! least 2.54, the combination verified. Anything older, or a version we
//! can't read, keeps the flag: getting this wrong means the app doesn't
//! open at all.
//! - 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`,
@@ -17,10 +26,47 @@
//! - `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.
//! - `__NV_DISABLE_EXPLICIT_SYNC=1` set by hand: forces the old behaviour on
//! versions where we'd skip it (the user's value always wins).
/// First NVIDIA driver major version verified to work on native Wayland
/// without `__NV_DISABLE_EXPLICIT_SYNC` (620.70.06, #48).
const EXPLICIT_SYNC_OK_DRIVER: u32 = 620;
/// First WebKitGTK (major, minor) verified with it (2.54.2, #48).
const EXPLICIT_SYNC_OK_WEBKIT: (u32, u32) = (2, 54);
/// Whether this driver + WebKitGTK combination no longer needs explicit sync
/// disabled. Unknown versions count as "still needs it". Pure, for tests.
pub(crate) fn explicit_sync_fixed(driver_major: Option<u32>, webkit: Option<(u32, u32)>) -> bool {
matches!(
(driver_major, webkit),
(Some(d), Some(w)) if d >= EXPLICIT_SYNC_OK_DRIVER && w >= EXPLICIT_SYNC_OK_WEBKIT
)
}
/// The driver's major version from `/sys/module/nvidia/version` ("620.70.06")
/// or a `/proc/driver/nvidia/version` line ("NVRM version: NVIDIA UNIX Open
/// Kernel Module for x86_64 620.70.06 Release Build …"): the first
/// `N.N` token whose major part is a plausible driver number.
pub(crate) fn parse_driver_major(text: &str) -> Option<u32> {
text.split_whitespace().find_map(|tok| {
let (major, rest) = tok.split_once('.')?;
let minor = rest.split('.').next()?;
if major.is_empty() || minor.is_empty() {
return None;
}
if !major.bytes().all(|b| b.is_ascii_digit()) || !minor.bytes().all(|b| b.is_ascii_digit())
{
return None;
}
major.parse::<u32>().ok().filter(|m| *m >= 100)
})
}
/// Environment variables to set: pure, for tests.
pub(crate) fn decide(
nvidia: bool,
explicit_sync_fixed: 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());
@@ -45,7 +91,9 @@ pub(crate) fn decide(
}
};
if native_wayland {
want("__NV_DISABLE_EXPLICIT_SYNC", "1");
if !explicit_sync_fixed {
want("__NV_DISABLE_EXPLICIT_SYNC", "1");
}
if set("LOTUS_GPU_SAFE_MODE") {
want("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
}
@@ -61,11 +109,51 @@ fn nvidia_driver_loaded() -> bool {
|| std::path::Path::new("/sys/module/nvidia").exists()
}
#[cfg(target_os = "linux")]
fn nvidia_driver_major() -> Option<u32> {
["/sys/module/nvidia/version", "/proc/driver/nvidia/version"]
.iter()
.find_map(|p| {
std::fs::read_to_string(p)
.ok()
.and_then(|t| parse_driver_major(&t))
})
}
/// The WebKitGTK library actually loaded (the system one for the pacman
/// package, the bundled one for the AppImage). Only reads constants from the
/// library, so it is safe before GTK is initialised.
#[cfg(target_os = "linux")]
fn webkit_version() -> (u32, u32) {
// SAFETY: plain getters with no arguments and no preconditions.
unsafe {
(
webkit2gtk::ffi::webkit_get_major_version(),
webkit2gtk::ffi::webkit_get_minor_version(),
)
}
}
/// Call first thing in `main`, before anything starts GTK or spawns threads.
pub fn apply() {
#[cfg(target_os = "linux")]
{
let changes = decide(nvidia_driver_loaded(), |k| std::env::var(k).ok());
let nvidia = nvidia_driver_loaded();
let (driver, webkit) = if nvidia {
(nvidia_driver_major(), Some(webkit_version()))
} else {
(None, None)
};
let fixed = explicit_sync_fixed(driver, webkit);
let changes = decide(nvidia, fixed, |k| std::env::var(k).ok());
if nvidia && fixed && changes.is_empty() {
let (wm, wn) = webkit.unwrap_or_default();
eprintln!(
"gpu-workarounds: NVIDIA driver {} with WebKitGTK {wm}.{wn}: no workaround needed \
(set __NV_DISABLE_EXPLICIT_SYNC=1 if the window fails with \"Error 71\")",
driver.unwrap_or_default()
);
}
if changes.is_empty() {
return;
}
@@ -83,7 +171,7 @@ pub fn apply() {
#[cfg(test)]
mod tests {
use super::decide;
use super::{decide, explicit_sync_fixed, parse_driver_major};
use std::collections::HashMap;
fn run(nvidia: bool, env: &[(&str, &str)]) -> Vec<(&'static str, &'static str)> {
@@ -91,7 +179,15 @@ mod tests {
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
decide(nvidia, |k| map.get(k).cloned())
decide(nvidia, false, |k| map.get(k).cloned())
}
fn run_fixed(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(true, true, |k| map.get(k).cloned())
}
const WAYLAND: &[(&str, &str)] = &[("WAYLAND_DISPLAY", "wayland-0"), ("DISPLAY", ":0")];
@@ -175,4 +271,61 @@ mod tests {
env.push(("LOTUS_NO_GPU_WORKAROUNDS", "1"));
assert!(run(true, &env).is_empty());
}
// ── #48: skip the explicit-sync workaround where it is no longer needed ──
#[test]
fn explicit_sync_fixed_needs_both_new_driver_and_new_webkit() {
assert!(explicit_sync_fixed(Some(620), Some((2, 54))));
assert!(explicit_sync_fixed(Some(625), Some((2, 56))));
assert!(explicit_sync_fixed(Some(620), Some((3, 0))));
// The September crash: driver 615 + WebKitGTK 2.52.
assert!(!explicit_sync_fixed(Some(615), Some((2, 52))));
// Only one side new (e.g. the AppImage's older bundled WebKitGTK).
assert!(!explicit_sync_fixed(Some(620), Some((2, 52))));
assert!(!explicit_sync_fixed(Some(615), Some((2, 54))));
// Unknown versions keep the workaround.
assert!(!explicit_sync_fixed(None, Some((2, 54))));
assert!(!explicit_sync_fixed(Some(620), None));
}
#[test]
fn fixed_versions_set_nothing_on_wayland() {
assert!(run_fixed(WAYLAND).is_empty());
}
#[test]
fn fixed_versions_still_honour_safe_mode_and_x11() {
let mut env = WAYLAND.to_vec();
env.push(("LOTUS_GPU_SAFE_MODE", "1"));
assert_eq!(
run_fixed(&env),
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
);
assert_eq!(
run_fixed(&[("DISPLAY", ":0"), ("XDG_SESSION_TYPE", "x11")]),
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
);
}
#[test]
fn parses_driver_versions() {
assert_eq!(parse_driver_major("620.70.06\n"), Some(620));
assert_eq!(
parse_driver_major(
"NVRM version: NVIDIA UNIX Open Kernel Module for x86_64 620.70.06 Release Build (notroot@) Sat Nov 7 14:23:54 UTC 2026\nGCC version: Selected multilib: .;@m64\n"
),
Some(620)
);
assert_eq!(
parse_driver_major(
"NVRM version: NVIDIA UNIX x86_64 Kernel Module 615.71.09 Thu Sep 3 2026"
),
Some(615)
);
assert_eq!(parse_driver_major("NVRM version: nothing here"), None);
assert_eq!(parse_driver_major(""), None);
// A GCC version must not be mistaken for the driver.
assert_eq!(parse_driver_major("GCC version: gcc 14.2.1"), None);
}
}