diff --git a/CHANGELOG.md b/CHANGELOG.md index fda224e295..55e675ec97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # Unreleased -- Fixed EGL's `Device::query_devices()` being too strict about required extensions -- Fixed crash in `EglGetProcAddress` on Win32-x86 platform due to wrong calling convention -- Fixed EGL's `Display::device()` always returning an error due to invalid pointer-argument passing inside +- Fixed EGL's `Device::query_devices()` being too strict about required extensions. +- Fixed crash in `EglGetProcAddress` on Win32-x86 platform due to wrong calling convention. +- Fixed EGL's `Display::device()` always returning an error due to invalid pointer-argument passing inside. - Fixed EGL's `Display::new()` making an `EGLDisplay::Khr` when the EGL version for the display is 1.4 or lower. +- Added `Device::drm_device_node_path()` and `Device::drm_render_device_node_path()` getters to EGL via `EGL_EXT_device_drm`. +- Added support for `DrmDisplayHandle` in EGL's `Display::with_device()` using `EGL_DRM_MASTER_FD_EXT` from `EGL_EXT_device_drm`. # Version 0.32.0 diff --git a/glutin/src/api/egl/device.rs b/glutin/src/api/egl/device.rs index 30b48baab8..71d9476ee3 100644 --- a/glutin/src/api/egl/device.rs +++ b/glutin/src/api/egl/device.rs @@ -1,7 +1,8 @@ //! Everything related to `EGLDevice`. use std::collections::HashSet; -use std::ffi::{c_void, CStr}; +use std::ffi::CStr; +use std::path::Path; use std::ptr; use glutin_egl_sys::egl; @@ -17,10 +18,15 @@ use super::{Egl, EGL}; pub struct Device { inner: EGLDeviceEXT, extensions: HashSet<&'static str>, - name: Option, - vendor: Option, + name: Option<&'static str>, + vendor: Option<&'static str>, } +// SAFETY: An EGLDevice is immutable and valid for the lifetime of the EGL +// library. +unsafe impl Send for Device {} +unsafe impl Sync for Device {} + impl Device { /// Query the available devices. /// @@ -93,7 +99,7 @@ impl Device { /// /// These extensions are distinct from the display extensions and should not /// be used interchangeably. - pub fn extensions(&self) -> &HashSet<&str> { + pub fn extensions(&self) -> &HashSet<&'static str> { &self.extensions } @@ -101,31 +107,79 @@ impl Device { /// /// This function will return [`None`] if the `EGL_EXT_device_query_name` /// device extension is not available. - pub fn name(&self) -> Option<&str> { - self.name.as_deref() + pub fn name(&self) -> Option<&'static str> { + self.name } /// Get the vendor of the device. /// /// This function will return [`None`] if the `EGL_EXT_device_query_name` /// device extension is not available. - pub fn vendor(&self) -> Option<&str> { - self.vendor.as_deref() + pub fn vendor(&self) -> Option<&'static str> { + self.vendor } /// Get a raw handle to the `EGLDevice`. - pub fn raw_device(&self) -> *const c_void { + pub fn raw_device(&self) -> EGLDeviceEXT { self.inner } -} -// SAFETY: An EGLDevice is immutable and valid for the lifetime of the EGL -// library. -unsafe impl Send for Device {} -unsafe impl Sync for Device {} + /// Get the DRM primary or render device node path for this + /// [`EGLDeviceEXT`]. + /// + /// Requires the [`EGL_EXT_device_drm`] extension. + /// + /// If the [`EGL_EXT_device_drm_render_node`] extension is supported, this + /// is guaranteed to return the **primary** device node path, or [`None`]. + /// Consult [`Self::drm_render_device_node_path()`] to retrieve the + /// **render** device node path. + /// + /// [`EGL_EXT_device_drm`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm.txt + /// [`EGL_EXT_device_drm_render_node`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt + pub fn drm_device_node_path(&self) -> Option<&'static Path> { + if !self.extensions.contains("EGL_EXT_device_drm") { + return None; + } -impl Device { - unsafe fn query_string(egl_device: *const c_void, name: egl::types::EGLenum) -> Option { + // SAFETY: We pass a valid EGLDevice pointer, and validated that the enum name + // is valid because the extension is present. + unsafe { Self::query_string(self.raw_device(), egl::DRM_DEVICE_FILE_EXT) }.map(Path::new) + } + + /// Get the DRM render device node path for this [`EGLDeviceEXT`]. + /// + /// Requires the [`EGL_EXT_device_drm_render_node`] extension. + /// + /// If the [`EGL_EXT_device_drm`] extension is supported in addition to + /// [`EGL_EXT_device_drm_render_node`], + /// consult [`Self::drm_device_node_path()`] to retrieve the **primary** + /// device node path. + /// + /// [`EGL_EXT_device_drm`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm.txt + /// [`EGL_EXT_device_drm_render_node`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt + pub fn drm_render_device_node_path(&self) -> Option<&'static Path> { + if !self.extensions.contains("EGL_EXT_device_drm_render_node") { + return None; + } + + const EGL_DRM_RENDER_NODE_PATH_EXT: egl::types::EGLenum = 0x3377; + // SAFETY: We pass a valid EGLDevice pointer, and validated that the enum name + // is valid because the extension is present. + unsafe { Self::query_string(self.raw_device(), EGL_DRM_RENDER_NODE_PATH_EXT) } + .map(Path::new) + } + + /// # Safety + /// The caller must pass a valid `egl_device` pointer and must ensure that + /// `name` is valid for this device, i.e. by guaranteeing that the + /// extension that introduces it is present. + /// + /// The returned string is `'static` for the lifetime of the globally loaded + /// EGL library in [`EGL`]. + unsafe fn query_string( + egl_device: EGLDeviceEXT, + name: egl::types::EGLenum, + ) -> Option<&'static str> { let egl = super::EGL.as_ref().unwrap(); // SAFETY: The caller has ensured the name is valid. @@ -135,10 +189,10 @@ impl Device { return None; } - unsafe { CStr::from_ptr(ptr) }.to_str().ok().map(String::from) + unsafe { CStr::from_ptr(ptr) }.to_str().ok() } - pub(crate) fn from_ptr(egl: &Egl, ptr: *const c_void) -> Result { + pub(crate) fn from_ptr(egl: &Egl, ptr: EGLDeviceEXT) -> Result { // SAFETY: The EGL specification guarantees the returned string is // static and null terminated: // diff --git a/glutin/src/api/egl/display.rs b/glutin/src/api/egl/display.rs index e2295ac54b..3cc47653bb 100644 --- a/glutin/src/api/egl/display.rs +++ b/glutin/src/api/egl/display.rs @@ -13,9 +13,7 @@ use glutin_egl_sys::egl::types::{EGLAttrib, EGLDisplay, EGLint}; use once_cell::sync::OnceCell; -use raw_window_handle::RawDisplayHandle; -#[cfg(x11_platform)] -use raw_window_handle::XlibDisplayHandle; +use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; use crate::config::ConfigTemplate; use crate::context::Version; @@ -91,8 +89,10 @@ impl Display { /// /// # Safety /// - /// If `raw_display` is [`Some`], `raw_display` must point to a valid system - /// display. + /// If `raw_display` is [`Some`], `raw_display` must point to a valid + /// [`RawDisplayHandle::Drm`]. The provided + /// [`raw_display_handle::DrmDisplayHandle.fd`] may be closed after calling + /// this function. pub unsafe fn with_device( device: &Device, raw_display: Option, @@ -102,13 +102,6 @@ impl Display { None => return Err(ErrorKind::NotFound.into()), }; - if raw_display.is_some() { - return Err(ErrorKind::NotSupported( - "Display::with_device does not support a `raw_display` argument yet", - ) - .into()); - } - if !egl.GetPlatformDisplayEXT.is_loaded() { return Err(ErrorKind::NotSupported("eglGetPlatformDisplayEXT is not supported").into()); } @@ -129,9 +122,22 @@ impl Display { let mut attrs = Vec::::with_capacity(3); - // TODO: Some extensions exist like EGL_EXT_device_drm which allow specifying - // which DRM master fd to use under the hood by the implementation. This would - // mean there would need to be an unsafe equivalent to this function. + match raw_display { + Some(RawDisplayHandle::Drm(handle)) + if device.extensions().contains("EGL_EXT_device_drm") => + { + attrs.push(egl::DRM_MASTER_FD_EXT as EGLint); + attrs.push(handle.fd as EGLint); + }, + Some(_) => { + return Err(ErrorKind::NotSupported( + "`egl::display::Display::with_device()` does not support \ + non-`DrmDisplayHandle` `RawDisplayHandle`s", + ) + .into()) + }, + None => {}, + }; // Push at the end so we can pop it on failure let mut has_display_reference = extensions.contains("EGL_KHR_display_reference"); @@ -254,13 +260,11 @@ impl Display { let mut attrs = Vec::::with_capacity(5); let (platform, display) = match display { - #[cfg(wayland_platform)] RawDisplayHandle::Wayland(handle) if extensions.contains("EGL_KHR_platform_wayland") => { (egl::PLATFORM_WAYLAND_KHR, handle.display.as_ptr()) }, - #[cfg(x11_platform)] RawDisplayHandle::Xlib(handle) if extensions.contains("EGL_KHR_platform_x11") => { attrs.push(egl::PLATFORM_X11_SCREEN_KHR as EGLAttrib); attrs.push(handle.screen as EGLAttrib); @@ -272,6 +276,12 @@ impl Display { RawDisplayHandle::Gbm(handle) if extensions.contains("EGL_KHR_platform_gbm") => { (egl::PLATFORM_GBM_KHR, handle.gbm_device.as_ptr()) }, + RawDisplayHandle::Drm(_) => { + return Err(ErrorKind::NotSupported( + "`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`", + ) + .into()) + }, RawDisplayHandle::Android(_) if extensions.contains("EGL_KHR_platform_android") => { (egl::PLATFORM_ANDROID_KHR, egl::DEFAULT_DISPLAY as *mut _) }, @@ -328,13 +338,11 @@ impl Display { let mut attrs = Vec::::with_capacity(5); let mut legacy = false; let (platform, display) = match display { - #[cfg(wayland_platform)] RawDisplayHandle::Wayland(handle) if extensions.contains("EGL_EXT_platform_wayland") => { (egl::PLATFORM_WAYLAND_EXT, handle.display.as_ptr()) }, - #[cfg(x11_platform)] RawDisplayHandle::Xlib(handle) if extensions.contains("EGL_EXT_platform_x11") => { attrs.push(egl::PLATFORM_X11_SCREEN_EXT as EGLint); attrs.push(handle.screen as EGLint); @@ -343,7 +351,6 @@ impl Display { handle.display.map_or(egl::DEFAULT_DISPLAY as *mut _, |d| d.as_ptr()), ) }, - #[cfg(x11_platform)] RawDisplayHandle::Xcb(handle) if extensions.contains("EGL_MESA_platform_xcb") || extensions.contains("EGL_EXT_platform_xcb") => @@ -358,6 +365,12 @@ impl Display { RawDisplayHandle::Gbm(handle) if extensions.contains("EGL_MESA_platform_gbm") => { (egl::PLATFORM_GBM_MESA, handle.gbm_device.as_ptr()) }, + RawDisplayHandle::Drm(_) => { + return Err(ErrorKind::NotSupported( + "`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`", + ) + .into()) + }, RawDisplayHandle::Windows(..) if extensions.contains("EGL_ANGLE_platform_angle") => { // Only CreateWindowSurface appears to work with Angle. legacy = true; @@ -419,7 +432,12 @@ impl Display { fn get_display(egl: &Egl, display: RawDisplayHandle) -> Result { let display = match display { RawDisplayHandle::Gbm(handle) => handle.gbm_device.as_ptr(), - #[cfg(x11_platform)] + RawDisplayHandle::Drm(_) => { + return Err(ErrorKind::NotSupported( + "`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`", + ) + .into()) + }, RawDisplayHandle::Xlib(XlibDisplayHandle { display, .. }) => { display.map_or(egl::DEFAULT_DISPLAY as *mut _, |d| d.as_ptr()) }, diff --git a/glutin/src/api/egl/mod.rs b/glutin/src/api/egl/mod.rs index e3ecbdef46..356a1a40f5 100644 --- a/glutin/src/api/egl/mod.rs +++ b/glutin/src/api/egl/mod.rs @@ -28,6 +28,9 @@ pub mod device; pub mod display; pub mod surface; +// WARNING: If this implementation is ever changed to unload or replace the +// library, note that public API functions currently retirm `&'static str`ings +// out of it, which would become invalid. pub(crate) static EGL: Lazy> = Lazy::new(|| { #[cfg(windows)] let paths = ["libEGL.dll", "atioglxx.dll"]; diff --git a/glutin_examples/Cargo.toml b/glutin_examples/Cargo.toml index 281e1bdbaa..4ff9927a18 100644 --- a/glutin_examples/Cargo.toml +++ b/glutin_examples/Cargo.toml @@ -24,6 +24,7 @@ glutin-winit = { path = "../glutin-winit", default-features = false } png = { version = "0.17.6", optional = true } raw-window-handle = "0.6" winit = { version = "0.30.0", default-features = false, features = ["rwh_06"] } +drm = { version = "0.12", optional = true } [target.'cfg(target_os = "android")'.dependencies] winit = { version = "0.30.0", default-features = false, features = ["android-native-activity", "rwh_06"] } @@ -39,3 +40,7 @@ crate-type = ["cdylib"] [[example]] name = "egl_device" required-features = ["egl"] + +[[example]] +name = "drm" +required-features = ["egl", "drm"] diff --git a/glutin_examples/examples/drm.rs b/glutin_examples/examples/drm.rs new file mode 100644 index 0000000000..ce1bc9bdc8 --- /dev/null +++ b/glutin_examples/examples/drm.rs @@ -0,0 +1,30 @@ +use std::fs::OpenOptions; +use std::os::fd::AsRawFd; + +use glutin::api::egl; +use raw_window_handle::{DrmDisplayHandle, RawDisplayHandle}; + +fn main() { + let devices = egl::device::Device::query_devices().expect("Query EGL devices"); + for egl_device in devices { + dbg!(&egl_device); + dbg!(egl_device.drm_render_device_node_path()); + let Some(drm) = dbg!(egl_device.drm_device_node_path()) else { + continue; + }; + let fd = OpenOptions::new() + .read(true) + .write(true) + .open(drm) + .expect("Open DRM device with Read/Write permissions"); + + // https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm.txt: + // Providing DRM_MASTER_FD is only to cover cases where EGL might fail to open + // it itself. + let rdh = RawDisplayHandle::Drm(DrmDisplayHandle::new(fd.as_raw_fd())); + + let egl_display = unsafe { egl::display::Display::with_device(&egl_device, Some(rdh)) } + .expect("Create EGL Display"); + dbg!(&egl_display); + } +}