glutin/api/egl/device.rs
1//! Everything related to `EGLDevice`.
2
3use std::collections::HashSet;
4use std::ffi::CStr;
5use std::path::Path;
6use std::ptr;
7
8use glutin_egl_sys::egl;
9use glutin_egl_sys::egl::types::EGLDeviceEXT;
10
11use crate::error::{ErrorKind, Result};
12
13use super::display::{extensions_from_ptr, get_extensions, CLIENT_EXTENSIONS};
14use super::{Egl, EGL};
15
16/// Wrapper for `EGLDevice`.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Device {
19 inner: EGLDeviceEXT,
20 extensions: HashSet<&'static str>,
21 name: Option<&'static str>,
22 vendor: Option<&'static str>,
23}
24
25// SAFETY: An EGLDevice is immutable and valid for the lifetime of the EGL
26// library.
27unsafe impl Send for Device {}
28unsafe impl Sync for Device {}
29
30impl Device {
31 /// Query the available devices.
32 ///
33 /// This function returns [`Err`] if the `EGL_EXT_device_query` and
34 /// `EGL_EXT_device_enumeration` or `EGL_EXT_device_base` extensions are
35 /// not available.
36 pub fn query_devices() -> Result<impl Iterator<Item = Device>> {
37 let egl = match EGL.as_ref() {
38 Some(egl) => egl,
39 None => return Err(ErrorKind::NotFound.into()),
40 };
41
42 let client_extensions =
43 CLIENT_EXTENSIONS.get_or_init(|| get_extensions(egl, egl::NO_DISPLAY));
44
45 // Querying devices requires EGL_EXT_device_enumeration or EGL_EXT_device_base.
46 if !client_extensions.contains("EGL_EXT_device_base") {
47 if !client_extensions.contains("EGL_EXT_device_enumeration") {
48 return Err(ErrorKind::NotSupported(
49 "Enumerating devices is not supported by the EGL instance",
50 )
51 .into());
52 }
53 // EGL_EXT_device_enumeration depends on EGL_EXT_device_query,
54 // so also check that just in case.
55 if !client_extensions.contains("EGL_EXT_device_query") {
56 return Err(ErrorKind::NotSupported(
57 "EGL_EXT_device_enumeration without EGL_EXT_device_query, buggy driver?",
58 )
59 .into());
60 }
61 }
62
63 let mut device_count = 0;
64
65 if unsafe {
66 // The specification states:
67 // > An EGL_BAD_PARAMETER error is generated if <max_devices> is
68 // > less than or equal to zero unless <devices> is NULL, or if
69 // > <num_devices> is NULL.
70 //
71 // The error will never be generated since num_devices is a pointer
72 // to the count being queried. Therefore there is no need to check
73 // the error.
74 egl.QueryDevicesEXT(0, ptr::null_mut(), &mut device_count) == egl::FALSE
75 } {
76 super::check_error()?;
77 // On failure, EGL_FALSE is returned.
78 return Err(ErrorKind::NotSupported("Querying device count failed").into());
79 }
80
81 let mut devices = Vec::with_capacity(device_count as usize);
82
83 unsafe {
84 let mut count = device_count;
85 if egl.QueryDevicesEXT(device_count, devices.as_mut_ptr(), &mut count) == egl::FALSE {
86 super::check_error()?;
87 // On failure, EGL_FALSE is returned.
88 return Err(ErrorKind::NotSupported("Querying devices failed").into());
89 }
90
91 // SAFETY: EGL has initialized the vector for the number of devices.
92 devices.set_len(device_count as usize);
93 }
94
95 Ok(devices.into_iter().flat_map(|ptr| Device::from_ptr(egl, ptr)))
96 }
97
98 /// Get the device extensions supported by this device.
99 ///
100 /// These extensions are distinct from the display extensions and should not
101 /// be used interchangeably.
102 pub fn extensions(&self) -> &HashSet<&'static str> {
103 &self.extensions
104 }
105
106 /// Get the name of the device.
107 ///
108 /// This function will return [`None`] if the `EGL_EXT_device_query_name`
109 /// device extension is not available.
110 pub fn name(&self) -> Option<&'static str> {
111 self.name
112 }
113
114 /// Get the vendor of the device.
115 ///
116 /// This function will return [`None`] if the `EGL_EXT_device_query_name`
117 /// device extension is not available.
118 pub fn vendor(&self) -> Option<&'static str> {
119 self.vendor
120 }
121
122 /// Get a raw handle to the `EGLDevice`.
123 pub fn raw_device(&self) -> EGLDeviceEXT {
124 self.inner
125 }
126
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 }
143
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> {
183 let egl = super::EGL.as_ref().unwrap();
184
185 // SAFETY: The caller has ensured the name is valid.
186 let ptr = unsafe { egl.QueryDeviceStringEXT(egl_device, name as _) };
187
188 if ptr.is_null() {
189 return None;
190 }
191
192 unsafe { CStr::from_ptr(ptr) }.to_str().ok()
193 }
194
195 pub(crate) fn from_ptr(egl: &Egl, ptr: EGLDeviceEXT) -> Result<Self> {
196 // SAFETY: The EGL specification guarantees the returned string is
197 // static and null terminated:
198 //
199 // > eglQueryDeviceStringEXT returns a pointer to a static,
200 // > zero-terminated string describing some aspect of the specified
201 // > EGLDeviceEXT. <name> must be EGL_EXTENSIONS.
202 let extensions =
203 unsafe { extensions_from_ptr(egl.QueryDeviceStringEXT(ptr, egl::EXTENSIONS as _)) };
204
205 let (name, vendor) = if extensions.contains("EGL_EXT_device_query_name") {
206 // SAFETY: RENDERER_EXT and VENDOR are valid strings for device string queries
207 // if EGL_EXT_device_query_name.
208 unsafe {
209 (Self::query_string(ptr, egl::RENDERER_EXT), Self::query_string(ptr, egl::VENDOR))
210 }
211 } else {
212 (None, None)
213 };
214
215 Ok(Self { inner: ptr, extensions, name, vendor })
216 }
217}