1 | //! Everything related to `EGLDevice`. |
2 | |
3 | use std::collections::HashSet; |
4 | use std::ffi::CStr; |
5 | use std::path::Path; |
6 | use std::ptr; |
7 | |
8 | use glutin_egl_sys::egl; |
9 | use glutin_egl_sys::egl::types::EGLDeviceEXT; |
10 | |
11 | use crate::error::{ErrorKind, Result}; |
12 | |
13 | use super::display::{extensions_from_ptr, get_extensions, CLIENT_EXTENSIONS}; |
14 | use super::{Egl, EGL}; |
15 | |
16 | /// Wrapper for `EGLDevice`. |
17 | #[derive (Debug, Clone, PartialEq, Eq)] |
18 | pub 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. |
27 | unsafe impl Send for Device {} |
28 | unsafe impl Sync for Device {} |
29 | |
30 | impl 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 | } |
218 | |