Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wgpu: Add x11 device detection #162

Merged
merged 4 commits into from
Jul 16, 2024
Merged
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
3 changes: 3 additions & 0 deletions wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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", "randr"] }
34 changes: 34 additions & 0 deletions wgpu/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@
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")))]
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),
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))
}
7 changes: 6 additions & 1 deletion wgpu/src/window/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -29,7 +31,10 @@ impl Compositor {
compatible_window: Option<W>,
) -> Option<Self> {
#[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
Expand Down
36 changes: 5 additions & 31 deletions wgpu/src/window/wayland.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -70,6 +68,10 @@ impl ProvidesRegistryState for AppData {
}

pub fn get_wayland_device_ids<W: Window>(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 {
Expand Down Expand Up @@ -103,35 +105,7 @@ pub fn get_wayland_device_ids<W: Window>(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,
}
Expand Down
171 changes: 171 additions & 0 deletions wgpu/src/window/x11.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
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, stat};
use tiny_xlib::Display;
use x11rb::{
connection::{Connection, RequestConnection},
protocol::{
dri3::{ConnectionExt as _, X11_EXTENSION_NAME as DRI3_NAME},
randr::{
ConnectionExt as _, ProviderCapability,
X11_EXTENSION_NAME as RANDR_NAME,
},
},
xcb_ffi::XCBConnection,
};

pub fn get_x11_device_ids<W: Window>(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;
}
};
let root = conn.setup().roots[screen as usize].root;

// 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::<u32>().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::<u32>().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);
}
}
}
}
}
}
}
}

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.

// 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)
}
}
Loading