| 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 | |