Skip to content

Commit

Permalink
EGL: Implement interfaces for EGL_EXT_device_drm
Browse files Browse the repository at this point in the history
This extension (together with the optional
`EGL_EXT_device_drm_render_node` extension) allows querying the DRM
primary and render device node filenames from `EGLDevice`, to help
associating them with DRM devices.

Additionally a platform display _can_ be created with a DRM file
descriptor with master permissions, but this is only useful in
situations where EGL might fail to open a file descriptor itself or will
fail to acquire master permissions (which are only required when the
implementation needs to "modify output attributes": but no behaviour is
defined by this extension that requires this).

This file descriptor will be duplicated by the implementation if it
needs to use it beyond the call to `eglGetPlatformDisplayEXT()`, and
does not need to be kept alive by the caller.

Note that `EGL_EXT_output_drm` is omitted for now, because it relies on
the equally unimplemented `EGL_EXT_output_base` extension (neither of
which are available on my hardware for testing).
  • Loading branch information
MarijnS95 committed Aug 26, 2024
1 parent 90ebeb5 commit 695b060
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 23 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Unreleased

- Fixed EGL's `Device::query_devices()` being too strict about required extensions
- Fixed EGL's `Device::query_devices()` being too strict about required extensions.
- Added `Device::drm_device_node_file()` and `Device::drm_render_device_node_file()` 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

Expand Down
64 changes: 54 additions & 10 deletions glutin/src/api/egl/device.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Everything related to `EGLDevice`.

use std::collections::HashSet;
use std::ffi::{c_void, CStr};
use std::ffi::CStr;
use std::ptr;

use glutin_egl_sys::egl;
Expand All @@ -21,6 +21,11 @@ pub struct Device {
vendor: Option<String>,
}

// 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.
///
Expand Down Expand Up @@ -114,18 +119,57 @@ impl Device {
}

/// 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 file 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 file, or [`None`].
/// Consult [`Self::drm_render_device_node_file()`] to retrieve the render
/// device node file.
///
/// [`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_file(&self) -> Option<String> {
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<String> {
// 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) }
}

/// Get the DRM render device node file for this [`EGLDeviceEXT`].
///
/// Requires the [`EGL_EXT_device_drm_render_node`] extension.
///
/// If the [`EGL_EXT_device_drm_render_node`] is supported, consult
/// [`Self::drm_device_node_file()`] to retrieve the primary device node
/// file.
///
/// [`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_file(&self) -> Option<String> {
if !self.extensions.contains("EGL_EXT_device_drm_render_node") {
return None;
}

// SAFETY: We pass a valid EGLDevice pointer, and validated that the enum name
// is valid because the extension is present.
const EGL_DRM_RENDER_NODE_FILE_EXT: egl::types::EGLenum = 0x3377;
unsafe { Self::query_string(self.raw_device(), EGL_DRM_RENDER_NODE_FILE_EXT) }
}

/// # 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.
unsafe fn query_string(egl_device: EGLDeviceEXT, name: egl::types::EGLenum) -> Option<String> {
let egl = super::EGL.as_ref().unwrap();

// SAFETY: The caller has ensured the name is valid.
Expand All @@ -138,7 +182,7 @@ impl Device {
unsafe { CStr::from_ptr(ptr) }.to_str().ok().map(String::from)
}

pub(crate) fn from_ptr(egl: &Egl, ptr: *const c_void) -> Result<Self> {
pub(crate) fn from_ptr(egl: &Egl, ptr: EGLDeviceEXT) -> Result<Self> {
// SAFETY: The EGL specification guarantees the returned string is
// static and null terminated:
//
Expand Down
61 changes: 49 additions & 12 deletions glutin/src/api/egl/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,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<RawDisplayHandle>,
Expand All @@ -102,13 +104,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());
}
Expand All @@ -129,9 +124,23 @@ impl Display {

let mut attrs = Vec::<EGLint>::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 {
#[cfg(free_unix)]
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");
Expand Down Expand Up @@ -262,9 +271,18 @@ impl Display {
handle.display.map_or(egl::DEFAULT_DISPLAY as *mut _, |d| d.as_ptr()),
)
},
#[cfg(free_unix)]
RawDisplayHandle::Gbm(handle) if extensions.contains("EGL_KHR_platform_gbm") => {
(egl::PLATFORM_GBM_KHR, handle.gbm_device.as_ptr())
},
#[cfg(free_unix)]
RawDisplayHandle::Drm(_) => {
return Err(ErrorKind::NotSupported(
"`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`",
)
.into())
},
#[cfg(android_platform)]
RawDisplayHandle::Android(_) if extensions.contains("EGL_KHR_platform_android") => {
(egl::PLATFORM_ANDROID_KHR, egl::DEFAULT_DISPLAY as *mut _)
},
Expand Down Expand Up @@ -319,6 +337,7 @@ impl Display {
let extensions = CLIENT_EXTENSIONS.get().unwrap();

let mut attrs = Vec::<EGLint>::with_capacity(5);
#[allow(unused_mut)]
let mut legacy = false;
let (platform, display) = match display {
#[cfg(wayland_platform)]
Expand Down Expand Up @@ -348,9 +367,18 @@ impl Display {
handle.connection.map_or(egl::DEFAULT_DISPLAY as *mut _, |c| c.as_ptr()),
)
},
#[cfg(free_unix)]
RawDisplayHandle::Gbm(handle) if extensions.contains("EGL_MESA_platform_gbm") => {
(egl::PLATFORM_GBM_MESA, handle.gbm_device.as_ptr())
},
#[cfg(free_unix)]
RawDisplayHandle::Drm(_) => {
return Err(ErrorKind::NotSupported(
"`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`",
)
.into())
},
#[cfg(windows)]
RawDisplayHandle::Windows(..) if extensions.contains("EGL_ANGLE_platform_angle") => {
// Only CreateWindowSurface appears to work with Angle.
legacy = true;
Expand Down Expand Up @@ -411,11 +439,20 @@ impl Display {

fn get_display(egl: &Egl, display: RawDisplayHandle) -> Result<EglDisplay> {
let display = match display {
#[cfg(free_unix)]
RawDisplayHandle::Gbm(handle) => handle.gbm_device.as_ptr(),
#[cfg(free_unix)]
RawDisplayHandle::Drm(_) => {
return Err(ErrorKind::NotSupported(
"`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`",
)
.into())
},
#[cfg(x11_platform)]
RawDisplayHandle::Xlib(XlibDisplayHandle { display, .. }) => {
display.map_or(egl::DEFAULT_DISPLAY as *mut _, |d| d.as_ptr())
},
#[cfg(android_platform)]
RawDisplayHandle::Android(_) => egl::DEFAULT_DISPLAY as *mut _,
_ => {
return Err(
Expand Down
6 changes: 6 additions & 0 deletions glutin_examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ x11 = ["glutin-winit/x11"]
wayland = ["glutin-winit/wayland", "winit/wayland-dlopen", "winit/wayland-csd-adwaita"]

[dependencies]
anyhow = "1"
glutin = { path = "../glutin", default-features = false }
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"] }
Expand All @@ -39,3 +41,7 @@ crate-type = ["cdylib"]
[[example]]
name = "egl_device"
required-features = ["egl"]

[[example]]
name = "drm"
required-features = ["egl", "drm"]
28 changes: 28 additions & 0 deletions glutin_examples/examples/drm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::fs::OpenOptions;
use std::os::fd::AsRawFd;

use anyhow::{Context, Result};
use glutin::api::egl;
use raw_window_handle::{DrmDisplayHandle, RawDisplayHandle};

fn main() -> Result<()> {
let devices = egl::device::Device::query_devices().context("Query EGL devices")?;
for egl_device in devices {
let Some(drm) = dbg!(egl_device.drm_device_node_file()) else {
continue;
};
dbg!(egl_device.drm_render_device_node_file());
let fd = OpenOptions::new().read(true).write(true).open(drm)?;

// 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)) }
.context("Create EGL Display")?;
dbg!(&egl_display);
}

Ok(())
}

0 comments on commit 695b060

Please sign in to comment.