Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions desktop/src-tauri/src/webkit_rendering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
//!
//! WebKitGTK's dmabuf renderer aborts the web process during startup on some
//! GPU/driver/compositor combinations, so Buzz comes up with no window at all
//! and the user has no way to fix it (#2338, upstream tauri#9394). Setting
//! `WEBKIT_DISABLE_DMABUF_RENDERER=1` avoids the abort by falling back to the
//! shared-memory buffer path.
//! and the user has no way to fix it (#2338, #3656, upstream tauri#9394).
//! Setting `WEBKIT_DISABLE_DMABUF_RENDERER=1` avoids the abort by falling back
//! to the shared-memory buffer path.
//!
//! WebKit reads each of these variables exactly once per process, so the choice
//! has to be made before anything initializes there is no runtime toggle and
//! has to be made before anything initializes -- there is no runtime toggle and
//! no second chance later in the same process. This module therefore decides
//! from cheap preflight signals instead of reacting to a crash:
//!
//! * an NVIDIA GPU, the driver family behind most upstream reports; and
//! * an NVIDIA or AMD GPU -- both driver families have triggered the blank
//! screen on various compositor/kernel combinations (#2338, #3656); and
//! * AppImage packaging, where linuxdeploy's AppRun hook pins `GDK_BACKEND=x11`
//! and the dmabuf renderer buys nothing on that XWayland path (#2338).
//!
Expand All @@ -31,6 +32,9 @@ const SAFE_RENDERING: &str = "--safe-rendering";
/// PCI vendor ID reported by NVIDIA devices under `/sys/class/drm`.
const NVIDIA_PCI_VENDOR: &str = "0x10de";

/// PCI vendor ID reported by AMD/ATI devices under `/sys/class/drm`.
const AMD_PCI_VENDOR: &str = "0x1002";

/// Where DRM devices advertise their PCI vendor.
const DRM_ROOT: &str = "/sys/class/drm";

Expand Down Expand Up @@ -138,7 +142,8 @@ fn plan(
}

let signals = [
(nvidia_gpu(drm_root), "NVIDIA GPU"),
(gpu_vendor(drm_root, NVIDIA_PCI_VENDOR), "NVIDIA GPU"),
(gpu_vendor(drm_root, AMD_PCI_VENDOR), "AMD GPU"),
(env("APPIMAGE").is_some(), "AppImage"),
];
let hits: Vec<&str> = signals
Expand All @@ -148,7 +153,7 @@ fn plan(

match hits.is_empty() {
true => Plan::Leave {
why: "no NVIDIA GPU and not an AppImage".to_string(),
why: "no NVIDIA/AMD GPU and not an AppImage".to_string(),
},
false => Plan::Apply {
vars: &HEURISTIC,
Expand Down Expand Up @@ -177,15 +182,15 @@ fn describe(user_set: &[(&str, OsString)]) -> String {
shown.join(", ")
}

/// Whether any DRM device reports NVIDIA's PCI vendor ID. An unreadable device
/// tree is not a hit the workaround has a real cost, so it needs evidence.
fn nvidia_gpu(drm_root: &Path) -> bool {
/// Whether any DRM device reports the given PCI vendor ID. An unreadable device
/// tree is not a hit -- the workaround has a real cost, so it needs evidence.
fn gpu_vendor(drm_root: &Path, vendor_id: &str) -> bool {
let Ok(entries) = std::fs::read_dir(drm_root) else {
return false;
};
entries.flatten().any(|entry| {
std::fs::read_to_string(entry.path().join("device/vendor"))
.is_ok_and(|vendor| vendor.trim().eq_ignore_ascii_case(NVIDIA_PCI_VENDOR))
.is_ok_and(|vendor| vendor.trim().eq_ignore_ascii_case(vendor_id))
})
}

Expand Down
31 changes: 30 additions & 1 deletion desktop/src-tauri/src/webkit_rendering/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,38 @@ fn test_an_appimage_launch_disables_the_dmabuf_renderer() {
}

#[test]
fn test_a_plain_non_nvidia_launch_changes_nothing() {
fn test_amd_gpu_disables_the_dmabuf_renderer() {
// Radeon (0x1002) triggers the same blank-screen as NVIDIA on some
// compositors (reported in #3656 with Hyprland on CachyOS).
let drm = drm(&["0x1002"]);
let plan = plan(NO_ARGS, &env_from(&[]), drm.path());

assert_eq!(
applied(&plan),
Some(&["WEBKIT_DISABLE_DMABUF_RENDERER"][..])
);
let Plan::Apply { why, .. } = &plan else {
unreachable!()
};
assert!(why.contains("AMD"), "{why}");
}

#[test]
fn test_an_amd_gpu_alongside_another_vendor_still_counts() {
// AMD integrated + discrete or AMD integrated only, both should match.
let drm = drm(&["0x8086", "0x1002"]);

assert_eq!(
applied(&plan(NO_ARGS, &env_from(&[]), drm.path())),
Some(&["WEBKIT_DISABLE_DMABUF_RENDERER"][..])
);
}

#[test]
fn test_a_plain_non_nvidia_non_amd_launch_changes_nothing() {
// Intel-only machine with no NVIDIA and no AMD card leaves rendering alone.
let drm = drm(&["0x8086"]);

assert!(matches!(
plan(NO_ARGS, &env_from(&[]), drm.path()),
Plan::Leave { .. }
Expand Down