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
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user