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.
  • Loading branch information
MarijnS95 authored and kchibisov committed Sep 10, 2024
1 parent 23b5f85 commit d8742b6
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 42 deletions.
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
90 changes: 72 additions & 18 deletions glutin/src/api/egl/device.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,10 +18,15 @@ use super::{Egl, EGL};
pub struct Device {
inner: EGLDeviceEXT,
extensions: HashSet<&'static str>,
name: Option<String>,
vendor: Option<String>,
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.
///
Expand Down Expand Up @@ -93,39 +99,87 @@ 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
}

/// Get the name of the 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<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) }.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.
Expand All @@ -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<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
60 changes: 39 additions & 21 deletions glutin/src/api/egl/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<RawDisplayHandle>,
Expand All @@ -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());
}
Expand All @@ -129,9 +122,22 @@ 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 {
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 @@ -254,13 +260,11 @@ impl Display {

let mut attrs = Vec::<EGLAttrib>::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);
Expand All @@ -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 _)
},
Expand Down Expand Up @@ -328,13 +338,11 @@ impl Display {
let mut attrs = Vec::<EGLint>::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);
Expand All @@ -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") =>
Expand All @@ -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;
Expand Down Expand Up @@ -419,7 +432,12 @@ impl Display {
fn get_display(egl: &Egl, display: RawDisplayHandle) -> Result<EglDisplay> {
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())
},
Expand Down
3 changes: 3 additions & 0 deletions glutin/src/api/egl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<Egl>> = Lazy::new(|| {
#[cfg(windows)]
let paths = ["libEGL.dll", "atioglxx.dll"];
Expand Down
5 changes: 5 additions & 0 deletions glutin_examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand All @@ -39,3 +40,7 @@ crate-type = ["cdylib"]
[[example]]
name = "egl_device"
required-features = ["egl"]

[[example]]
name = "drm"
required-features = ["egl", "drm"]
30 changes: 30 additions & 0 deletions glutin_examples/examples/drm.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit d8742b6

Please sign in to comment.