fix(linux): disable DMABuf for Strix Halo on Wayland

Closes #6709
This commit is contained in:
t8y2
2026-08-23 11:09:14 +00:00
parent 2927e01591
commit e15ade130d
+250 -24
View File
@@ -280,8 +280,24 @@ struct LinuxDrmRenderDevice {
device_file: std::path::PathBuf,
driver: Option<String>,
boot_vga: bool,
pci_id: Option<(u16, u16)>,
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct LinuxDmabufRendererPciQuirk {
vendor_id: u16,
device_id: u16,
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
const LINUX_DMABUF_RENDERER_PCI_QUIRKS: &[LinuxDmabufRendererPciQuirk] = &[LinuxDmabufRendererPciQuirk {
// Strix Halo can stop presenting new WebKitGTK DMABuf frames while the
// WebView remains interactive on native Wayland.
vendor_id: 0x1002,
device_id: 0x1586,
}];
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn linux_nvidia_driver_from_state(
proprietary_control_exists: bool,
@@ -311,6 +327,13 @@ fn linux_selected_drm_render_device<'a>(
devices.iter().find(|device| device.boot_vga).or_else(|| devices.first())
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn linux_pci_id_from_sysfs_value(value: &str) -> Option<u16> {
let value = value.trim();
let value = value.strip_prefix("0x").or_else(|| value.strip_prefix("0X")).unwrap_or(value);
(!value.is_empty()).then(|| u16::from_str_radix(value, 16).ok()).flatten()
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn linux_drm_render_devices_from_paths(
sys_class_drm: &std::path::Path,
@@ -337,7 +360,13 @@ fn linux_drm_render_devices_from_paths(
.ok()
.and_then(|path| path.file_name().and_then(std::ffi::OsStr::to_str).map(str::to_ascii_lowercase));
let boot_vga = std::fs::read_to_string(device_path.join("boot_vga")).is_ok_and(|value| value.trim() == "1");
Some(LinuxDrmRenderDevice { device_file, driver, boot_vga })
let vendor_id = std::fs::read_to_string(device_path.join("vendor"))
.ok()
.and_then(|value| linux_pci_id_from_sysfs_value(&value));
let device_id = std::fs::read_to_string(device_path.join("device"))
.ok()
.and_then(|value| linux_pci_id_from_sysfs_value(&value));
Some(LinuxDrmRenderDevice { device_file, driver, boot_vga, pci_id: vendor_id.zip(device_id) })
})
.collect::<Vec<_>>();
devices.sort_by(|left, right| left.device_file.cmp(&right.device_file));
@@ -373,27 +402,28 @@ fn linux_drm_driver_is_software_only(driver: Option<&str>) -> bool {
driver.is_none_or(|driver| LINUX_SOFTWARE_ONLY_DRM_DRIVERS.contains(&driver))
}
#[cfg(target_os = "linux")]
fn linux_nvidia_driver() -> LinuxNvidiaDriver {
let devices = linux_drm_render_devices();
let explicit_device_file = std::env::var_os("WEBKIT_WEB_RENDER_DEVICE_FILE")
.filter(|path| !path.is_empty())
.map(std::path::PathBuf::from)
// Resolve stable /dev/dri/by-path links to the renderD* node used by sysfs.
.map(|path| std::fs::canonicalize(&path).unwrap_or(path));
let render_driver = linux_selected_drm_render_device(explicit_device_file.as_deref(), &devices)
.and_then(|device| device.driver.as_deref());
linux_nvidia_driver_from_state(
std::path::Path::new("/dev/nvidiactl").exists(),
std::path::Path::new("/proc/driver/nvidia/version").exists(),
render_driver,
)
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn linux_selected_device_has_dmabuf_quirk(
selected_device: Option<&LinuxDrmRenderDevice>,
uses_native_wayland: bool,
) -> bool {
uses_native_wayland
&& selected_device.is_some_and(|device| {
let Some((vendor_id, device_id)) = device.pci_id else {
return false;
};
LINUX_DMABUF_RENDERER_PCI_QUIRKS
.iter()
.any(|quirk| vendor_id == quirk.vendor_id && device_id == quirk.device_id)
})
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn linux_webkit_rendering_workarounds(
driver: LinuxNvidiaDriver,
has_hardware_render_device: bool,
selected_device: Option<&LinuxDrmRenderDevice>,
uses_native_wayland: bool,
) -> &'static [(&'static str, &'static str)] {
match driver {
LinuxNvidiaDriver::Proprietary => {
@@ -414,6 +444,9 @@ fn linux_webkit_rendering_workarounds(
// disable the DMABuf renderer there as well.
&[("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
}
LinuxNvidiaDriver::None if linux_selected_device_has_dmabuf_quirk(selected_device, uses_native_wayland) => {
&[("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
}
LinuxNvidiaDriver::None => {
// AMD / Intel and other Mesa drivers keep DMABuf enabled to avoid
// unnecessary CPU usage and UI lag on Wayland.
@@ -422,6 +455,14 @@ fn linux_webkit_rendering_workarounds(
}
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn linux_webkit_environment_override<'a>(
existing_value: Option<&std::ffi::OsStr>,
workaround_value: &'a str,
) -> Option<&'a str> {
existing_value.is_none().then_some(workaround_value)
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn linux_system_gtk3_immodules_cache_path() -> Option<&'static str> {
[
@@ -451,6 +492,30 @@ fn linux_appimage_wayland_backend_override(
}
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn linux_uses_native_wayland(
appimage: Option<&std::ffi::OsStr>,
wayland_display: Option<&std::ffi::OsStr>,
session_type: Option<&std::ffi::OsStr>,
gdk_backend: Option<&std::ffi::OsStr>,
) -> bool {
let has_wayland_display = wayland_display.is_some_and(|value| !value.is_empty());
let is_wayland_session =
session_type.and_then(std::ffi::OsStr::to_str).is_some_and(|value| value.eq_ignore_ascii_case("wayland"));
if !has_wayland_display || !is_wayland_session {
return false;
}
let automatic_backend = linux_appimage_wayland_backend_override(appimage, wayland_display, gdk_backend);
gdk_backend.or_else(|| automatic_backend.map(std::ffi::OsStr::new)).map_or(true, |backends| {
backends
.to_string_lossy()
.split(',')
.next()
.is_some_and(|backend| backend.trim().eq_ignore_ascii_case("wayland"))
})
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn linux_appimage_system_gtk_immodules_cache(
appimage: Option<&std::ffi::OsStr>,
@@ -479,10 +544,32 @@ fn linux_appimage_system_gtk_immodules_cache(
#[cfg(target_os = "linux")]
fn apply_linux_webkit_rendering_workarounds() {
let render_devices = linux_drm_render_devices();
let explicit_device_file = std::env::var_os("WEBKIT_WEB_RENDER_DEVICE_FILE")
.filter(|path| !path.is_empty())
.map(std::path::PathBuf::from)
// Resolve stable /dev/dri/by-path links to the renderD* node used by sysfs.
.map(|path| std::fs::canonicalize(&path).unwrap_or(path));
let selected_device = linux_selected_drm_render_device(explicit_device_file.as_deref(), &render_devices);
let nvidia_driver = linux_nvidia_driver_from_state(
std::path::Path::new("/dev/nvidiactl").exists(),
std::path::Path::new("/proc/driver/nvidia/version").exists(),
selected_device.and_then(|device| device.driver.as_deref()),
);
let has_hardware_render_device =
render_devices.iter().any(|device| !linux_drm_driver_is_software_only(device.driver.as_deref()));
for (key, value) in linux_webkit_rendering_workarounds(linux_nvidia_driver(), has_hardware_render_device) {
if std::env::var_os(key).is_none() {
let uses_native_wayland = linux_uses_native_wayland(
std::env::var_os("APPIMAGE").as_deref(),
std::env::var_os("WAYLAND_DISPLAY").as_deref(),
std::env::var_os("XDG_SESSION_TYPE").as_deref(),
std::env::var_os("GDK_BACKEND").as_deref(),
);
for (key, value) in linux_webkit_rendering_workarounds(
nvidia_driver,
has_hardware_render_device,
selected_device,
uses_native_wayland,
) {
if let Some(value) = linux_webkit_environment_override(std::env::var_os(key).as_deref(), value) {
std::env::set_var(key, value);
}
}
@@ -907,7 +994,8 @@ mod tests {
use super::{
app_menu_copy_support_info_label, app_menu_quit_label, linux_appimage_system_gtk_immodules_cache,
linux_appimage_wayland_backend_override, linux_drm_driver_is_software_only,
linux_drm_render_devices_from_paths, linux_nvidia_driver_from_state, linux_selected_drm_render_device,
linux_drm_render_devices_from_paths, linux_nvidia_driver_from_state, linux_pci_id_from_sysfs_value,
linux_selected_drm_render_device, linux_uses_native_wayland, linux_webkit_environment_override,
linux_webkit_rendering_workarounds, native_window_decorations_override, should_confirm_app_exit_request,
should_enable_single_instance, should_fallback_to_native_quit, should_hide_window_on_close,
should_setup_desktop_tray, should_show_main_window_after_setup, should_show_main_window_before_setup_tasks,
@@ -1075,7 +1163,27 @@ mod tests {
}
fn drm_render_device(path: &str, driver: &str, boot_vga: bool) -> LinuxDrmRenderDevice {
LinuxDrmRenderDevice { device_file: PathBuf::from(path), driver: Some(driver.to_string()), boot_vga }
LinuxDrmRenderDevice {
device_file: PathBuf::from(path),
driver: Some(driver.to_string()),
boot_vga,
pci_id: None,
}
}
fn drm_pci_render_device(
path: &str,
driver: &str,
boot_vga: bool,
vendor_id: u16,
device_id: u16,
) -> LinuxDrmRenderDevice {
LinuxDrmRenderDevice {
device_file: PathBuf::from(path),
driver: Some(driver.to_string()),
boot_vga,
pci_id: Some((vendor_id, device_id)),
}
}
#[test]
@@ -1090,9 +1198,42 @@ mod tests {
assert!(linux_drm_render_devices_from_paths(&sys_class_drm, &dev_dri).is_empty());
std::fs::write(dev_dri.join("renderD129"), []).unwrap();
std::fs::write(sys_class_drm.join("renderD129/device/vendor"), "0x1002\n").unwrap();
std::fs::write(sys_class_drm.join("renderD129/device/device"), "0x1586\n").unwrap();
let devices = linux_drm_render_devices_from_paths(&sys_class_drm, &dev_dri);
assert_eq!(devices.len(), 1);
assert_eq!(devices[0].device_file, dev_dri.join("renderD129"));
assert_eq!(devices[0].pci_id, Some((0x1002, 0x1586)));
std::fs::remove_dir_all(root).unwrap();
}
#[test]
fn parses_linux_drm_pci_ids_without_accepting_malformed_values() {
assert_eq!(linux_pci_id_from_sysfs_value("0x1002\n"), Some(0x1002));
assert_eq!(linux_pci_id_from_sysfs_value("0X1586"), Some(0x1586));
assert_eq!(linux_pci_id_from_sysfs_value("8086"), Some(0x8086));
assert_eq!(linux_pci_id_from_sysfs_value(""), None);
assert_eq!(linux_pci_id_from_sysfs_value("0x"), None);
assert_eq!(linux_pci_id_from_sysfs_value("0x10000"), None);
assert_eq!(linux_pci_id_from_sysfs_value("not-a-device"), None);
let root = std::env::temp_dir().join(format!("dbx-drm-pci-ids-{}", uuid::Uuid::new_v4()));
let sys_class_drm = root.join("sys/class/drm");
let dev_dri = root.join("dev/dri");
for node in ["renderD128", "renderD129"] {
std::fs::create_dir_all(sys_class_drm.join(node).join("device")).unwrap();
std::fs::create_dir_all(&dev_dri).unwrap();
std::fs::write(dev_dri.join(node), []).unwrap();
}
std::fs::write(sys_class_drm.join("renderD128/device/vendor"), "malformed").unwrap();
std::fs::write(sys_class_drm.join("renderD128/device/device"), "0x1586").unwrap();
std::fs::write(sys_class_drm.join("renderD129/device/vendor"), "0x1002").unwrap();
let devices = linux_drm_render_devices_from_paths(&sys_class_drm, &dev_dri);
assert_eq!(devices.len(), 2);
assert_eq!(devices[0].pci_id, None);
assert_eq!(devices[1].pci_id, None);
std::fs::remove_dir_all(root).unwrap();
}
@@ -1150,23 +1291,108 @@ mod tests {
#[test]
fn applies_driver_specific_linux_webkit_rendering_workarounds() {
assert_eq!(
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::Proprietary, true),
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::Proprietary, true, None, false),
&[("WEBKIT_DISABLE_DMABUF_RENDERER", "1"), ("__NV_DISABLE_EXPLICIT_SYNC", "1")]
);
assert_eq!(
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::Nouveau, true),
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::Nouveau, true, None, false),
&[("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
);
assert_eq!(linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, true), &[]);
assert_eq!(linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, true, None, false), &[]);
// Without any hardware render device (GPU-less VM / server) Mesa falls
// back to llvmpipe, whose DMABuf compositing path can crash the WebKit
// process.
assert_eq!(
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, false),
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, false, None, false),
&[("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
);
}
#[test]
fn disables_linux_webkit_dmabuf_only_for_strix_halo_on_native_wayland() {
let strix_halo = drm_pci_render_device("/dev/dri/renderD128", "amdgpu", true, 0x1002, 0x1586);
let mut strix_halo_without_driver = strix_halo.clone();
strix_halo_without_driver.driver = None;
let adjacent_amd = drm_pci_render_device("/dev/dri/renderD128", "amdgpu", true, 0x1002, 0x1587);
let native_wayland =
linux_uses_native_wayland(None, Some(OsStr::new("wayland-0")), Some(OsStr::new("wayland")), None);
assert!(native_wayland);
assert_eq!(
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, true, Some(&strix_halo), native_wayland),
&[("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
);
assert_eq!(
linux_webkit_rendering_workarounds(
LinuxNvidiaDriver::None,
true,
Some(&strix_halo_without_driver),
native_wayland,
),
&[("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
);
assert_eq!(
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, true, Some(&adjacent_amd), native_wayland),
&[]
);
for native_wayland in [
linux_uses_native_wayland(
None,
Some(OsStr::new("wayland-0")),
Some(OsStr::new("wayland")),
Some(OsStr::new("x11")),
),
linux_uses_native_wayland(None, None, Some(OsStr::new("wayland")), None),
linux_uses_native_wayland(None, Some(OsStr::new("wayland-0")), None, None),
linux_uses_native_wayland(None, Some(OsStr::new("wayland-0")), Some(OsStr::new("x11")), None),
] {
assert!(!native_wayland);
assert_eq!(
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, true, Some(&strix_halo), native_wayland),
&[]
);
}
}
#[test]
fn linux_webkit_strix_quirk_follows_the_selected_hybrid_render_node() {
let devices = [
drm_pci_render_device("/dev/dri/renderD128", "i915", true, 0x8086, 0x46a6),
drm_pci_render_device("/dev/dri/renderD129", "amdgpu", false, 0x1002, 0x1586),
];
let default_device = linux_selected_drm_render_device(None, &devices).unwrap();
assert_eq!(linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, true, Some(default_device), true), &[]);
let explicit_device = linux_selected_drm_render_device(Some(Path::new("/dev/dri/renderD129")), &devices);
assert_eq!(
linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, true, explicit_device, true),
&[("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
);
let unmatched_device = linux_selected_drm_render_device(Some(Path::new("/dev/dri/renderD130")), &devices);
assert!(unmatched_device.is_none());
assert_eq!(linux_webkit_rendering_workarounds(LinuxNvidiaDriver::None, true, unmatched_device, true), &[]);
}
#[test]
fn appimage_linux_webkit_quirk_respects_x11_first_and_explicit_wayland() {
let appimage = Some(OsStr::new("/opt/DBX.AppImage"));
let display = Some(OsStr::new("wayland-0"));
let session = Some(OsStr::new("wayland"));
assert!(!linux_uses_native_wayland(appimage, display, session, None));
assert!(linux_uses_native_wayland(appimage, display, session, Some(OsStr::new("wayland"))));
assert!(!linux_uses_native_wayland(appimage, display, session, Some(OsStr::new("x11,wayland,*"))));
}
#[test]
fn linux_webkit_workarounds_preserve_user_environment_values() {
assert_eq!(linux_webkit_environment_override(None, "1"), Some("1"));
for value in [OsStr::new(""), OsStr::new("0"), OsStr::new("1")] {
assert_eq!(linux_webkit_environment_override(Some(value), "1"), None);
}
}
#[test]
fn treats_virtual_and_2d_drm_drivers_as_software_rendering() {
assert!(linux_drm_driver_is_software_only(Some("virtio-pci")));