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}