Skip to content

Commit f27377a

Browse files
authored
egl: implement interfaces for EGL_EXT_device_drm
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.
1 parent dae89a5 commit f27377a

File tree

6 files changed

+154
-42
lines changed

6 files changed

+154
-42
lines changed

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Unreleased
22

3-
- Fixed EGL's `Device::query_devices()` being too strict about required extensions
4-
- Fixed crash in `EglGetProcAddress` on Win32-x86 platform due to wrong calling convention
5-
- Fixed EGL's `Display::device()` always returning an error due to invalid pointer-argument passing inside
3+
- Fixed EGL's `Device::query_devices()` being too strict about required extensions.
4+
- Fixed crash in `EglGetProcAddress` on Win32-x86 platform due to wrong calling convention.
5+
- Fixed EGL's `Display::device()` always returning an error due to invalid pointer-argument passing inside.
66
- Fixed EGL's `Display::new()` making an `EGLDisplay::Khr` when the EGL version for the display is 1.4 or lower.
7+
- Added `Device::drm_device_node_path()` and `Device::drm_render_device_node_path()` getters to EGL via `EGL_EXT_device_drm`.
8+
- Added support for `DrmDisplayHandle` in EGL's `Display::with_device()` using `EGL_DRM_MASTER_FD_EXT` from `EGL_EXT_device_drm`.
79

810
# Version 0.32.0
911

glutin/src/api/egl/device.rs

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! Everything related to `EGLDevice`.
22
33
use std::collections::HashSet;
4-
use std::ffi::{c_void, CStr};
4+
use std::ffi::CStr;
5+
use std::path::Path;
56
use std::ptr;
67

78
use glutin_egl_sys::egl;
@@ -17,10 +18,15 @@ use super::{Egl, EGL};
1718
pub struct Device {
1819
inner: EGLDeviceEXT,
1920
extensions: HashSet<&'static str>,
20-
name: Option<String>,
21-
vendor: Option<String>,
21+
name: Option<&'static str>,
22+
vendor: Option<&'static str>,
2223
}
2324

25+
// SAFETY: An EGLDevice is immutable and valid for the lifetime of the EGL
26+
// library.
27+
unsafe impl Send for Device {}
28+
unsafe impl Sync for Device {}
29+
2430
impl Device {
2531
/// Query the available devices.
2632
///
@@ -93,39 +99,87 @@ impl Device {
9399
///
94100
/// These extensions are distinct from the display extensions and should not
95101
/// be used interchangeably.
96-
pub fn extensions(&self) -> &HashSet<&str> {
102+
pub fn extensions(&self) -> &HashSet<&'static str> {
97103
&self.extensions
98104
}
99105

100106
/// Get the name of the device.
101107
///
102108
/// This function will return [`None`] if the `EGL_EXT_device_query_name`
103109
/// device extension is not available.
104-
pub fn name(&self) -> Option<&str> {
105-
self.name.as_deref()
110+
pub fn name(&self) -> Option<&'static str> {
111+
self.name
106112
}
107113

108114
/// Get the vendor of the device.
109115
///
110116
/// This function will return [`None`] if the `EGL_EXT_device_query_name`
111117
/// device extension is not available.
112-
pub fn vendor(&self) -> Option<&str> {
113-
self.vendor.as_deref()
118+
pub fn vendor(&self) -> Option<&'static str> {
119+
self.vendor
114120
}
115121

116122
/// Get a raw handle to the `EGLDevice`.
117-
pub fn raw_device(&self) -> *const c_void {
123+
pub fn raw_device(&self) -> EGLDeviceEXT {
118124
self.inner
119125
}
120-
}
121126

122-
// SAFETY: An EGLDevice is immutable and valid for the lifetime of the EGL
123-
// library.
124-
unsafe impl Send for Device {}
125-
unsafe impl Sync for Device {}
127+
/// Get the DRM primary or render device node path for this
128+
/// [`EGLDeviceEXT`].
129+
///
130+
/// Requires the [`EGL_EXT_device_drm`] extension.
131+
///
132+
/// If the [`EGL_EXT_device_drm_render_node`] extension is supported, this
133+
/// is guaranteed to return the **primary** device node path, or [`None`].
134+
/// Consult [`Self::drm_render_device_node_path()`] to retrieve the
135+
/// **render** device node path.
136+
///
137+
/// [`EGL_EXT_device_drm`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm.txt
138+
/// [`EGL_EXT_device_drm_render_node`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt
139+
pub fn drm_device_node_path(&self) -> Option<&'static Path> {
140+
if !self.extensions.contains("EGL_EXT_device_drm") {
141+
return None;
142+
}
126143

127-
impl Device {
128-
unsafe fn query_string(egl_device: *const c_void, name: egl::types::EGLenum) -> Option<String> {
144+
// SAFETY: We pass a valid EGLDevice pointer, and validated that the enum name
145+
// is valid because the extension is present.
146+
unsafe { Self::query_string(self.raw_device(), egl::DRM_DEVICE_FILE_EXT) }.map(Path::new)
147+
}
148+
149+
/// Get the DRM render device node path for this [`EGLDeviceEXT`].
150+
///
151+
/// Requires the [`EGL_EXT_device_drm_render_node`] extension.
152+
///
153+
/// If the [`EGL_EXT_device_drm`] extension is supported in addition to
154+
/// [`EGL_EXT_device_drm_render_node`],
155+
/// consult [`Self::drm_device_node_path()`] to retrieve the **primary**
156+
/// device node path.
157+
///
158+
/// [`EGL_EXT_device_drm`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm.txt
159+
/// [`EGL_EXT_device_drm_render_node`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt
160+
pub fn drm_render_device_node_path(&self) -> Option<&'static Path> {
161+
if !self.extensions.contains("EGL_EXT_device_drm_render_node") {
162+
return None;
163+
}
164+
165+
const EGL_DRM_RENDER_NODE_PATH_EXT: egl::types::EGLenum = 0x3377;
166+
// SAFETY: We pass a valid EGLDevice pointer, and validated that the enum name
167+
// is valid because the extension is present.
168+
unsafe { Self::query_string(self.raw_device(), EGL_DRM_RENDER_NODE_PATH_EXT) }
169+
.map(Path::new)
170+
}
171+
172+
/// # Safety
173+
/// The caller must pass a valid `egl_device` pointer and must ensure that
174+
/// `name` is valid for this device, i.e. by guaranteeing that the
175+
/// extension that introduces it is present.
176+
///
177+
/// The returned string is `'static` for the lifetime of the globally loaded
178+
/// EGL library in [`EGL`].
179+
unsafe fn query_string(
180+
egl_device: EGLDeviceEXT,
181+
name: egl::types::EGLenum,
182+
) -> Option<&'static str> {
129183
let egl = super::EGL.as_ref().unwrap();
130184

131185
// SAFETY: The caller has ensured the name is valid.
@@ -135,10 +189,10 @@ impl Device {
135189
return None;
136190
}
137191

138-
unsafe { CStr::from_ptr(ptr) }.to_str().ok().map(String::from)
192+
unsafe { CStr::from_ptr(ptr) }.to_str().ok()
139193
}
140194

141-
pub(crate) fn from_ptr(egl: &Egl, ptr: *const c_void) -> Result<Self> {
195+
pub(crate) fn from_ptr(egl: &Egl, ptr: EGLDeviceEXT) -> Result<Self> {
142196
// SAFETY: The EGL specification guarantees the returned string is
143197
// static and null terminated:
144198
//

glutin/src/api/egl/display.rs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ use glutin_egl_sys::egl::types::{EGLAttrib, EGLDisplay, EGLint};
1313

1414
use once_cell::sync::OnceCell;
1515

16-
use raw_window_handle::RawDisplayHandle;
17-
#[cfg(x11_platform)]
18-
use raw_window_handle::XlibDisplayHandle;
16+
use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle};
1917

2018
use crate::config::ConfigTemplate;
2119
use crate::context::Version;
@@ -91,8 +89,10 @@ impl Display {
9189
///
9290
/// # Safety
9391
///
94-
/// If `raw_display` is [`Some`], `raw_display` must point to a valid system
95-
/// display.
92+
/// If `raw_display` is [`Some`], `raw_display` must point to a valid
93+
/// [`RawDisplayHandle::Drm`]. The provided
94+
/// [`raw_display_handle::DrmDisplayHandle.fd`] may be closed after calling
95+
/// this function.
9696
pub unsafe fn with_device(
9797
device: &Device,
9898
raw_display: Option<RawDisplayHandle>,
@@ -102,13 +102,6 @@ impl Display {
102102
None => return Err(ErrorKind::NotFound.into()),
103103
};
104104

105-
if raw_display.is_some() {
106-
return Err(ErrorKind::NotSupported(
107-
"Display::with_device does not support a `raw_display` argument yet",
108-
)
109-
.into());
110-
}
111-
112105
if !egl.GetPlatformDisplayEXT.is_loaded() {
113106
return Err(ErrorKind::NotSupported("eglGetPlatformDisplayEXT is not supported").into());
114107
}
@@ -129,9 +122,22 @@ impl Display {
129122

130123
let mut attrs = Vec::<EGLint>::with_capacity(3);
131124

132-
// TODO: Some extensions exist like EGL_EXT_device_drm which allow specifying
133-
// which DRM master fd to use under the hood by the implementation. This would
134-
// mean there would need to be an unsafe equivalent to this function.
125+
match raw_display {
126+
Some(RawDisplayHandle::Drm(handle))
127+
if device.extensions().contains("EGL_EXT_device_drm") =>
128+
{
129+
attrs.push(egl::DRM_MASTER_FD_EXT as EGLint);
130+
attrs.push(handle.fd as EGLint);
131+
},
132+
Some(_) => {
133+
return Err(ErrorKind::NotSupported(
134+
"`egl::display::Display::with_device()` does not support \
135+
non-`DrmDisplayHandle` `RawDisplayHandle`s",
136+
)
137+
.into())
138+
},
139+
None => {},
140+
};
135141

136142
// Push at the end so we can pop it on failure
137143
let mut has_display_reference = extensions.contains("EGL_KHR_display_reference");
@@ -254,13 +260,11 @@ impl Display {
254260

255261
let mut attrs = Vec::<EGLAttrib>::with_capacity(5);
256262
let (platform, display) = match display {
257-
#[cfg(wayland_platform)]
258263
RawDisplayHandle::Wayland(handle)
259264
if extensions.contains("EGL_KHR_platform_wayland") =>
260265
{
261266
(egl::PLATFORM_WAYLAND_KHR, handle.display.as_ptr())
262267
},
263-
#[cfg(x11_platform)]
264268
RawDisplayHandle::Xlib(handle) if extensions.contains("EGL_KHR_platform_x11") => {
265269
attrs.push(egl::PLATFORM_X11_SCREEN_KHR as EGLAttrib);
266270
attrs.push(handle.screen as EGLAttrib);
@@ -272,6 +276,12 @@ impl Display {
272276
RawDisplayHandle::Gbm(handle) if extensions.contains("EGL_KHR_platform_gbm") => {
273277
(egl::PLATFORM_GBM_KHR, handle.gbm_device.as_ptr())
274278
},
279+
RawDisplayHandle::Drm(_) => {
280+
return Err(ErrorKind::NotSupported(
281+
"`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`",
282+
)
283+
.into())
284+
},
275285
RawDisplayHandle::Android(_) if extensions.contains("EGL_KHR_platform_android") => {
276286
(egl::PLATFORM_ANDROID_KHR, egl::DEFAULT_DISPLAY as *mut _)
277287
},
@@ -328,13 +338,11 @@ impl Display {
328338
let mut attrs = Vec::<EGLint>::with_capacity(5);
329339
let mut legacy = false;
330340
let (platform, display) = match display {
331-
#[cfg(wayland_platform)]
332341
RawDisplayHandle::Wayland(handle)
333342
if extensions.contains("EGL_EXT_platform_wayland") =>
334343
{
335344
(egl::PLATFORM_WAYLAND_EXT, handle.display.as_ptr())
336345
},
337-
#[cfg(x11_platform)]
338346
RawDisplayHandle::Xlib(handle) if extensions.contains("EGL_EXT_platform_x11") => {
339347
attrs.push(egl::PLATFORM_X11_SCREEN_EXT as EGLint);
340348
attrs.push(handle.screen as EGLint);
@@ -343,7 +351,6 @@ impl Display {
343351
handle.display.map_or(egl::DEFAULT_DISPLAY as *mut _, |d| d.as_ptr()),
344352
)
345353
},
346-
#[cfg(x11_platform)]
347354
RawDisplayHandle::Xcb(handle)
348355
if extensions.contains("EGL_MESA_platform_xcb")
349356
|| extensions.contains("EGL_EXT_platform_xcb") =>
@@ -358,6 +365,12 @@ impl Display {
358365
RawDisplayHandle::Gbm(handle) if extensions.contains("EGL_MESA_platform_gbm") => {
359366
(egl::PLATFORM_GBM_MESA, handle.gbm_device.as_ptr())
360367
},
368+
RawDisplayHandle::Drm(_) => {
369+
return Err(ErrorKind::NotSupported(
370+
"`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`",
371+
)
372+
.into())
373+
},
361374
RawDisplayHandle::Windows(..) if extensions.contains("EGL_ANGLE_platform_angle") => {
362375
// Only CreateWindowSurface appears to work with Angle.
363376
legacy = true;
@@ -419,7 +432,12 @@ impl Display {
419432
fn get_display(egl: &Egl, display: RawDisplayHandle) -> Result<EglDisplay> {
420433
let display = match display {
421434
RawDisplayHandle::Gbm(handle) => handle.gbm_device.as_ptr(),
422-
#[cfg(x11_platform)]
435+
RawDisplayHandle::Drm(_) => {
436+
return Err(ErrorKind::NotSupported(
437+
"`DrmDisplayHandle` must be used with `egl::display::Display::with_device()`",
438+
)
439+
.into())
440+
},
423441
RawDisplayHandle::Xlib(XlibDisplayHandle { display, .. }) => {
424442
display.map_or(egl::DEFAULT_DISPLAY as *mut _, |d| d.as_ptr())
425443
},

glutin/src/api/egl/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ pub mod device;
2828
pub mod display;
2929
pub mod surface;
3030

31+
// WARNING: If this implementation is ever changed to unload or replace the
32+
// library, note that public API functions currently retirm `&'static str`ings
33+
// out of it, which would become invalid.
3134
pub(crate) static EGL: Lazy<Option<Egl>> = Lazy::new(|| {
3235
#[cfg(windows)]
3336
let paths = ["libEGL.dll", "atioglxx.dll"];

glutin_examples/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ glutin-winit = { path = "../glutin-winit", default-features = false }
2424
png = { version = "0.17.6", optional = true }
2525
raw-window-handle = "0.6"
2626
winit = { version = "0.30.0", default-features = false, features = ["rwh_06"] }
27+
drm = { version = "0.12", optional = true }
2728

2829
[target.'cfg(target_os = "android")'.dependencies]
2930
winit = { version = "0.30.0", default-features = false, features = ["android-native-activity", "rwh_06"] }
@@ -39,3 +40,7 @@ crate-type = ["cdylib"]
3940
[[example]]
4041
name = "egl_device"
4142
required-features = ["egl"]
43+
44+
[[example]]
45+
name = "drm"
46+
required-features = ["egl", "drm"]

glutin_examples/examples/drm.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use std::fs::OpenOptions;
2+
use std::os::fd::AsRawFd;
3+
4+
use glutin::api::egl;
5+
use raw_window_handle::{DrmDisplayHandle, RawDisplayHandle};
6+
7+
fn main() {
8+
let devices = egl::device::Device::query_devices().expect("Query EGL devices");
9+
for egl_device in devices {
10+
dbg!(&egl_device);
11+
dbg!(egl_device.drm_render_device_node_path());
12+
let Some(drm) = dbg!(egl_device.drm_device_node_path()) else {
13+
continue;
14+
};
15+
let fd = OpenOptions::new()
16+
.read(true)
17+
.write(true)
18+
.open(drm)
19+
.expect("Open DRM device with Read/Write permissions");
20+
21+
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_drm.txt:
22+
// Providing DRM_MASTER_FD is only to cover cases where EGL might fail to open
23+
// it itself.
24+
let rdh = RawDisplayHandle::Drm(DrmDisplayHandle::new(fd.as_raw_fd()));
25+
26+
let egl_display = unsafe { egl::display::Display::with_device(&egl_device, Some(rdh)) }
27+
.expect("Create EGL Display");
28+
dbg!(&egl_display);
29+
}
30+
}

0 commit comments

Comments
 (0)