diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 78d2003d0e..0aae2172fa 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -202,6 +202,48 @@ impl Instance { } } + pub unsafe fn create_surface_from_drm( + &self, + fd: i32, + plane: u32, + connector_id: u32, + width: u32, + height: u32, + refresh_rate: u32, + ) -> Result { + profiling::scope!("Instance::create_surface_from_drm"); + + let mut errors = HashMap::default(); + let mut surface_per_backend: HashMap> = + HashMap::default(); + + let instance = unsafe { self.as_hal::() } + .ok_or(CreateSurfaceError::BackendNotEnabled(Backend::Vulkan))?; + + match instance.create_surface_from_drm(fd, plane, connector_id, width, height, refresh_rate) + { + Ok(surface) => { + surface_per_backend.insert(Backend::Vulkan, Box::new(surface)); + } + Err(err) => { + errors.insert(Backend::Vulkan, err); + } + } + + if surface_per_backend.is_empty() { + Err(CreateSurfaceError::FailedToCreateSurfaceForAnyBackend( + errors, + )) + } else { + let surface = Surface { + presentation: Mutex::new(rank::SURFACE_PRESENTATION, None), + surface_per_backend, + }; + + Ok(surface) + } + } + /// # Safety /// /// `layer` must be a valid pointer. @@ -772,6 +814,31 @@ impl Global { Ok(id) } + pub unsafe fn instance_create_surface_from_drm( + &self, + fd: i32, + plane: u32, + connector_id: u32, + width: u32, + height: u32, + refresh_rate: u32, + id_in: Option, + ) -> Result { + let surface = unsafe { + self.instance.create_surface_from_drm( + fd, + plane, + connector_id, + width, + height, + refresh_rate, + ) + }?; + let id = self.surfaces.prepare(id_in).assign(Arc::new(surface)); + + Ok(id) + } + /// # Safety /// /// `layer` must be a valid pointer. diff --git a/wgpu-hal/src/vulkan/instance.rs b/wgpu-hal/src/vulkan/instance.rs index a67781dba7..e566f736a9 100644 --- a/wgpu-hal/src/vulkan/instance.rs +++ b/wgpu-hal/src/vulkan/instance.rs @@ -2,6 +2,7 @@ use std::{ borrow::ToOwned as _, boxed::Box, ffi::{c_void, CStr, CString}, + mem::MaybeUninit, slice, str::FromStr, string::{String, ToString as _}, @@ -259,6 +260,12 @@ impl super::Instance { // VK_KHR_surface extensions.push(khr::surface::NAME); + // Extensions needed for drm support + extensions.push(khr::display::NAME); + extensions.push(ext::physical_device_drm::NAME); + extensions.push(khr::get_display_properties2::NAME); + extensions.push(ext::acquire_drm_display::NAME); + // Platform-specific WSI extensions if cfg!(all( unix, @@ -523,6 +530,133 @@ impl super::Instance { Ok(self.create_surface_from_vk_surface_khr(surface)) } + pub fn create_surface_from_drm( + &self, + fd: i32, + plane: u32, + connector_id: u32, + width: u32, + height: u32, + refresh_rate: u32, + ) -> Result { + if !self.shared.extensions.contains(&khr::display::NAME) { + return Err(crate::InstanceError::new(String::from( + "Vulkan driver does not support VK_KHR_display", + ))); + } + + let drm_stat = { + let mut stat = MaybeUninit::::uninit(); + + if unsafe { libc::fstat(fd, stat.as_mut_ptr()) } != 0 { + return Err(crate::InstanceError::new( + "Unable to fstat drm device".to_string(), + )); + } + + unsafe { stat.assume_init() } + }; + + let raw_devices = match unsafe { self.shared.raw.enumerate_physical_devices() } { + Ok(devices) => devices, + Err(err) => { + log::error!("enumerate_adapters: {}", err); + Vec::new() + } + }; + + let mut physical_device = None; + + for device in raw_devices { + let properties2 = vk::PhysicalDeviceProperties2KHR::default(); + + let mut drm_props = vk::PhysicalDeviceDrmPropertiesEXT::default(); + let mut properties2 = properties2.push_next(&mut drm_props); + + unsafe { + self.shared + .raw + .get_physical_device_properties2(device, &mut properties2) + }; + + let primary_devid = + libc::makedev(drm_props.primary_major as _, drm_props.primary_minor as _); + let render_devid = + libc::makedev(drm_props.render_major as _, drm_props.render_minor as _); + + if primary_devid == drm_stat.st_rdev || render_devid == drm_stat.st_rdev { + physical_device = Some(device) + } + } + + let physical_device = physical_device.ok_or(crate::InstanceError::new( + "Failed to find suitable drm device".to_string(), + ))?; + + // FIXME: consider implementing this strategy on working vulkan drivers + // let displays = unsafe { + // display_instance + // .get_physical_device_display_properties(physical_device) + // .expect("Failed to get displays") + // }; + + let acquire_drm_display_instance = + ext::acquire_drm_display::Instance::new(&self.shared.entry, &self.shared.raw); + + let display = unsafe { + acquire_drm_display_instance + .get_drm_display(physical_device, fd, connector_id) + .expect("Failed to get drm display") + }; + + unsafe { + acquire_drm_display_instance + .acquire_drm_display(physical_device, fd, display) + .expect("Failed to acquire drm display") + } + + let display_instance = khr::display::Instance::new(&self.shared.entry, &self.shared.raw); + + let modes = unsafe { + display_instance + .get_display_mode_properties(physical_device, display) + .expect("Failed to get display modes") + }; + + let mut mode = None; + + for current_mode in modes { + log::trace!( + "Comparing mode {}x{}@{} with {width}x{height}@{refresh_rate}", + current_mode.parameters.visible_region.width, + current_mode.parameters.visible_region.height, + current_mode.parameters.refresh_rate + ); + if current_mode.parameters.refresh_rate == refresh_rate + && current_mode.parameters.visible_region.width == width + && current_mode.parameters.visible_region.height == height + { + mode = Some(current_mode) + } + } + + let mode = mode.ok_or(crate::InstanceError::new( + "Failed to find suitable display mode".to_string(), + ))?; + + let create_info = vk::DisplaySurfaceCreateInfoKHR::default() + .display_mode(mode.display_mode) + .image_extent(mode.parameters.visible_region) + .transform(vk::SurfaceTransformFlagsKHR::IDENTITY) + .alpha_mode(vk::DisplayPlaneAlphaFlagsKHR::OPAQUE) + .plane_index(plane); + + let surface = unsafe { display_instance.create_display_plane_surface(&create_info, None) } + .expect("Failed to create DRM surface"); + + Ok(self.create_surface_from_vk_surface_khr(surface)) + } + #[cfg(metal)] fn create_surface_from_view( &self, diff --git a/wgpu/src/api/surface.rs b/wgpu/src/api/surface.rs index dddfd13ecf..9a470feb51 100644 --- a/wgpu/src/api/surface.rs +++ b/wgpu/src/api/surface.rs @@ -290,6 +290,30 @@ pub enum SurfaceTargetUnsafe { raw_window_handle: raw_window_handle::RawWindowHandle, }, + /// Surface from a DRM device. + /// + /// If the specified DRM configuration is not supported by any of the backends, then the surface + /// will not be supported by any adapters. + /// + /// # Safety + /// + /// - All parameters must point to valid DRM values and remain valid for as long as the resulting [`Surface`] exists. + /// - The file descriptor (`fd`), plane, connector, and mode configuration must be valid and compatible. + Drm { + /// The file descriptor of the DRM device. + fd: i32, + /// The plane index on which to create the surface. + plane: u32, + /// The ID of the connector associated with the selected mode. + connector_id: u32, + /// The display width of the selected mode. + width: u32, + /// The display height of the selected mode. + height: u32, + /// The display refresh rate of the selected mode multiplied by 1000 (e.g., 60Hz → 60000). + refresh_rate: u32, + }, + /// Surface from `CoreAnimationLayer`. /// /// # Safety diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index 561d393229..07428a1f9a 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -794,6 +794,25 @@ impl dispatch::InstanceInterface for ContextWgpuCore { .instance_create_surface(raw_display_handle, raw_window_handle, None) }, + SurfaceTargetUnsafe::Drm { + fd, + plane, + connector_id, + width, + height, + refresh_rate, + } => unsafe { + self.0.instance_create_surface_from_drm( + fd, + plane, + connector_id, + width, + height, + refresh_rate, + None, + ) + }, + #[cfg(metal)] SurfaceTargetUnsafe::CoreAnimationLayer(layer) => unsafe { self.0.instance_create_surface_metal(layer, None)