From db6554f6b1f1cbb7cbda3bd25eab389200483451 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 16 Jul 2024 18:23:09 +0200 Subject: [PATCH 1/4] wayland: Don't crash if libwayland isn't available --- wgpu/src/window/wayland.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wgpu/src/window/wayland.rs b/wgpu/src/window/wayland.rs index a77b1150cd..024dbe2242 100644 --- a/wgpu/src/window/wayland.rs +++ b/wgpu/src/window/wayland.rs @@ -70,6 +70,10 @@ impl ProvidesRegistryState for AppData { } pub fn get_wayland_device_ids(window: &W) -> Option<(u16, u16)> { + if !wayland_sys::client::is_lib_available() { + return None; + } + let conn = match window.display_handle().map(|handle| handle.as_raw()) { #[allow(unsafe_code)] Ok(RawDisplayHandle::Wayland(WaylandDisplayHandle { From fdb4e9afce21e06c3d7a6f72963be24d14484827 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 16 Jul 2024 18:23:48 +0200 Subject: [PATCH 2/4] refactor: Extract ids_from_dev from wayland specific code --- wgpu/src/window.rs | 27 +++++++++++++++++++++++++++ wgpu/src/window/wayland.rs | 30 +----------------------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index baf8a05458..8af4797bc2 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -5,3 +5,30 @@ mod wayland; pub use compositor::Compositor; pub use wgpu::Surface; + +#[cfg(all(unix, not(target_os = "macos")))] +fn ids_from_dev(dev: u64) -> Option((u16, u16)) { + let path = PathBuf::from(format!( + "/sys/dev/char/{}:{}/device", + major(dev), + minor(dev) + )); + let vendor = { + let path = path.join("vendor"); + let mut file = File::open(&path).ok()?; + let mut contents = String::new(); + let _ = file.read_to_string(&mut contents).ok()?; + u16::from_str_radix(contents.trim().trim_start_matches("0x"), 16) + .ok()? + }; + let device = { + let path = path.join("device"); + let mut file = File::open(&path).ok()?; + let mut contents = String::new(); + let _ = file.read_to_string(&mut contents).ok()?; + u16::from_str_radix(contents.trim().trim_start_matches("0x"), 16) + .ok()? + }; + + Some((vendor, device)) +} diff --git a/wgpu/src/window/wayland.rs b/wgpu/src/window/wayland.rs index 024dbe2242..01d7aeac6f 100644 --- a/wgpu/src/window/wayland.rs +++ b/wgpu/src/window/wayland.rs @@ -107,35 +107,7 @@ pub fn get_wayland_device_ids(window: &W) -> Option<(u16, u16)> { }; let dev = feedback.main_device(); - let path = PathBuf::from(format!( - "/sys/dev/char/{}:{}/device", - major(dev), - minor(dev) - )); - let vendor = { - let path = path.join("vendor"); - let mut file = File::open(&path).ok()?; - let mut contents = String::new(); - let _ = file.read_to_string(&mut contents).ok()?; - u16::from_str_radix( - contents.trim().trim_start_matches("0x"), - 16, - ) - .ok()? - }; - let device = { - let path = path.join("device"); - let mut file = File::open(&path).ok()?; - let mut contents = String::new(); - let _ = file.read_to_string(&mut contents).ok()?; - u16::from_str_radix( - contents.trim().trim_start_matches("0x"), - 16, - ) - .ok()? - }; - - Some((vendor, device)) + super::ids_from_dev(dev) } _ => None, } From 4989895da7d478981ee60b47822d268e20ccc399 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 16 Jul 2024 18:24:11 +0200 Subject: [PATCH 3/4] compositor: Add code to extract adapter from x11 --- wgpu/Cargo.toml | 3 ++ wgpu/src/window.rs | 9 +++- wgpu/src/window/compositor.rs | 7 +++- wgpu/src/window/wayland.rs | 2 - wgpu/src/window/x11.rs | 77 +++++++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 wgpu/src/window/x11.rs diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 5b5e7c6e69..8360548acd 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -51,3 +51,6 @@ wayland-protocols.workspace = true wayland-backend = { version = "0.3.3", features = ["client_system"] } wayland-client = { version = "0.31.2" } wayland-sys = { version = "0.31.1", features = ["dlopen"] } +as-raw-xcb-connection = "1.0.1" +tiny-xlib = "0.2.3" +x11rb = { version = "0.13.1", features = ["allow-unsafe-code", "dl-libxcb", "dri3"] } \ No newline at end of file diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index 8af4797bc2..92f1687372 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -2,12 +2,19 @@ pub mod compositor; #[cfg(all(unix, not(target_os = "macos")))] mod wayland; +#[cfg(all(unix, not(target_os = "macos")))] +mod x11; pub use compositor::Compositor; pub use wgpu::Surface; #[cfg(all(unix, not(target_os = "macos")))] -fn ids_from_dev(dev: u64) -> Option((u16, u16)) { +use rustix::fs::{major, minor}; +#[cfg(all(unix, not(target_os = "macos")))] +use std::{fs::File, io::Read, path::PathBuf}; + +#[cfg(all(unix, not(target_os = "macos")))] +fn ids_from_dev(dev: u64) -> Option<(u16, u16)> { let path = PathBuf::from(format!( "/sys/dev/char/{}:{}/device", major(dev), diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 15919db5e7..c0839451ac 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -8,6 +8,8 @@ use crate::{Backend, Primitive, Renderer, Settings}; #[cfg(all(unix, not(target_os = "macos")))] use super::wayland::get_wayland_device_ids; +#[cfg(all(unix, not(target_os = "macos")))] +use super::x11::get_x11_device_ids; /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] @@ -29,7 +31,10 @@ impl Compositor { compatible_window: Option, ) -> Option { #[cfg(all(unix, not(target_os = "macos")))] - let ids = compatible_window.as_ref().and_then(get_wayland_device_ids); + let ids = compatible_window.as_ref().and_then(|window| { + get_wayland_device_ids(window) + .or_else(|| get_x11_device_ids(window)) + }); // HACK: // 1. If we specifically didn't select an nvidia gpu diff --git a/wgpu/src/window/wayland.rs b/wgpu/src/window/wayland.rs index 01d7aeac6f..b50f5d6909 100644 --- a/wgpu/src/window/wayland.rs +++ b/wgpu/src/window/wayland.rs @@ -1,12 +1,10 @@ use crate::graphics::compositor::Window; use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle}; -use rustix::fs::{major, minor}; use sctk::{ dmabuf::{DmabufFeedback, DmabufHandler, DmabufState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, }; -use std::{fs::File, io::Read, path::PathBuf}; use wayland_client::{ backend::Backend, globals::registry_queue_init, protocol::wl_buffer, Connection, QueueHandle, diff --git a/wgpu/src/window/x11.rs b/wgpu/src/window/x11.rs new file mode 100644 index 0000000000..23c772ea2a --- /dev/null +++ b/wgpu/src/window/x11.rs @@ -0,0 +1,77 @@ +use crate::graphics::compositor::Window; + +use as_raw_xcb_connection::AsRawXcbConnection; +use raw_window_handle::{ + RawDisplayHandle, XcbDisplayHandle, XlibDisplayHandle, +}; +use rustix::fs::fstat; +use tiny_xlib::Display; +use x11rb::{ + connection::{Connection, RequestConnection}, + protocol::dri3::{ConnectionExt as _, X11_EXTENSION_NAME as DRI3_NAME}, + xcb_ffi::XCBConnection, +}; + +pub fn get_x11_device_ids(window: &W) -> Option<(u16, u16)> { + x11rb::xcb_ffi::load_libxcb().ok()?; + + #[allow(unsafe_code)] + let (conn, screen) = match window + .display_handle() + .map(|handle| handle.as_raw()) + { + #[allow(unsafe_code)] + Ok(RawDisplayHandle::Xlib(XlibDisplayHandle { + display, + screen, + .. + })) => match display { + Some(ptr) => unsafe { + let xlib_display = Display::from_ptr(ptr.as_ptr()); + let conn = XCBConnection::from_raw_xcb_connection( + xlib_display.as_raw_xcb_connection() as *mut _, + false, + ) + .ok(); + // intentially leak the display, we don't want to close the connection + + (conn?, screen) + }, + None => (XCBConnection::connect(None).ok()?.0, screen), + }, + Ok(RawDisplayHandle::Xcb(XcbDisplayHandle { + connection, + screen, + .. + })) => match connection { + Some(ptr) => ( + unsafe { + XCBConnection::from_raw_xcb_connection(ptr.as_ptr(), false) + .ok()? + }, + screen, + ), + None => (XCBConnection::connect(None).ok()?.0, screen), + }, + _ => { + return None; + } + }; + + // check for DRI3 + let _ = conn.extension_information(DRI3_NAME).ok()??; + // we have dri3, dri3_open exists on any version, so lets skip version checks. + + // provider being NONE tells the X server to use the RandR provider. + let screen = &conn.setup().roots[screen as usize]; + let dri3 = conn + .dri3_open(screen.root, x11rb::NONE) + .ok()? + .reply() + .ok()?; + let device_fd = dri3.device_fd; + let stat = fstat(device_fd).ok()?; + let dev = stat.st_rdev; + + super::ids_from_dev(dev) +} From fe7ef703a88ebdf014bd19a138eb11c420ae7e3b Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 16 Jul 2024 22:33:30 +0200 Subject: [PATCH 4/4] x11: Workaround nvidia driver lacking DRI --- wgpu/Cargo.toml | 2 +- wgpu/src/window/x11.rs | 126 +++++++++++++++++++++++++++++++++++------ 2 files changed, 111 insertions(+), 17 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 8360548acd..15499d38f9 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -53,4 +53,4 @@ wayland-client = { version = "0.31.2" } wayland-sys = { version = "0.31.1", features = ["dlopen"] } as-raw-xcb-connection = "1.0.1" tiny-xlib = "0.2.3" -x11rb = { version = "0.13.1", features = ["allow-unsafe-code", "dl-libxcb", "dri3"] } \ No newline at end of file +x11rb = { version = "0.13.1", features = ["allow-unsafe-code", "dl-libxcb", "dri3", "randr"] } \ No newline at end of file diff --git a/wgpu/src/window/x11.rs b/wgpu/src/window/x11.rs index 23c772ea2a..58da401a2e 100644 --- a/wgpu/src/window/x11.rs +++ b/wgpu/src/window/x11.rs @@ -1,14 +1,26 @@ +use std::{ + fs, + io::{BufRead, BufReader}, + path::Path, +}; + use crate::graphics::compositor::Window; use as_raw_xcb_connection::AsRawXcbConnection; use raw_window_handle::{ RawDisplayHandle, XcbDisplayHandle, XlibDisplayHandle, }; -use rustix::fs::fstat; +use rustix::fs::{fstat, stat}; use tiny_xlib::Display; use x11rb::{ connection::{Connection, RequestConnection}, - protocol::dri3::{ConnectionExt as _, X11_EXTENSION_NAME as DRI3_NAME}, + protocol::{ + dri3::{ConnectionExt as _, X11_EXTENSION_NAME as DRI3_NAME}, + randr::{ + ConnectionExt as _, ProviderCapability, + X11_EXTENSION_NAME as RANDR_NAME, + }, + }, xcb_ffi::XCBConnection, }; @@ -57,21 +69,103 @@ pub fn get_x11_device_ids(window: &W) -> Option<(u16, u16)> { return None; } }; + let root = conn.setup().roots[screen as usize].root; - // check for DRI3 - let _ = conn.extension_information(DRI3_NAME).ok()??; - // we have dri3, dri3_open exists on any version, so lets skip version checks. + // The nvidia xorg driver advertises DRI2 and DRI3, + // but doesn't really return any useful data for either of them. + // We also can't query EGL, as a display created from an X11 display + // running on the properietary driver won't return an EGLDevice. + // + // So we have to resort to hacks. + + // check for randr + let _ = conn.extension_information(RANDR_NAME).ok()??; + // check version, because we need providers to exist + let version = conn.randr_query_version(1, 4).ok()?.reply().ok()?; + if version.major_version < 1 + || (version.major_version == 1 && version.minor_version < 4) + { + return None; + } + + // get the name of the first Source Output provider, that will be our main device + let randr = conn.randr_get_providers(root).ok()?.reply().ok()?; + let mut name = None; + for provider in randr.providers { + let info = conn + .randr_get_provider_info(provider, randr.timestamp) + .ok()? + .reply() + .ok()?; + if info + .capabilities + .contains(ProviderCapability::SOURCE_OUTPUT) + || name.is_none() + { + name = std::str::from_utf8(&info.name) + .ok() + .map(ToString::to_string); + } + } + + // if that name is formatted `NVIDIA-x`, then x represents the /dev/nvidiaX number, which we can relate to /dev/dri + if let Some(number) = name.and_then(|name| { + name.trim().strip_prefix("NVIDIA-")?.parse::().ok() + }) { + // let it be known, that I hate this "interface"... + for busid in fs::read_dir("/proc/driver/nvidia/gpus") + .ok()? + .map(Result::ok) + .flatten() + { + for line in BufReader::new( + fs::File::open(busid.path().join("information")).ok()?, + ) + .lines() + { + if let Ok(line) = line { + if line.starts_with("Device Minor") { + if let Some((_, num)) = line.split_once(":") { + let minor = num.trim().parse::().ok()?; + if minor == number { + // we found the device + for device in fs::read_dir( + Path::new("/sys/module/nvidia/drivers/pci:nvidia/") + .join(busid.file_name()) + .join("drm"), + ) + .ok()? + .map(Result::ok) + .flatten() + { + let device = device.file_name(); + if device.to_string_lossy().starts_with("card") + || device.to_string_lossy().starts_with("render") + { + let stat = + stat(Path::new("/dev/dri").join(device)).ok()?; + let dev = stat.st_rdev; + return super::ids_from_dev(dev); + } + } + } + } + } + } + } + } - // provider being NONE tells the X server to use the RandR provider. - let screen = &conn.setup().roots[screen as usize]; - let dri3 = conn - .dri3_open(screen.root, x11rb::NONE) - .ok()? - .reply() - .ok()?; - let device_fd = dri3.device_fd; - let stat = fstat(device_fd).ok()?; - let dev = stat.st_rdev; + None + } else { + // check via DRI3 + let _ = conn.extension_information(DRI3_NAME).ok()??; + // we have dri3, dri3_open exists on any version, so skip version checks. - super::ids_from_dev(dev) + // provider being NONE tells the X server to use the RandR provider. + let dri3 = conn.dri3_open(root, x11rb::NONE).ok()?.reply().ok()?; + let device_fd = dri3.device_fd; + let stat = fstat(device_fd).ok()?; + let dev = stat.st_rdev; + super::ids_from_dev(dev) + } }