1 | //! Everything related to `EGLDevice`. |
2 | |
3 | use std::collections::HashSet; |
4 | use std::ffi::{c_void, CStr}; |
5 | use std::ptr; |
6 | |
7 | use glutin_egl_sys::egl; |
8 | use glutin_egl_sys::egl::types::EGLDeviceEXT; |
9 | |
10 | use crate::error::{ErrorKind, Result}; |
11 | |
12 | use super::display::{extensions_from_ptr, get_extensions, CLIENT_EXTENSIONS}; |
13 | use super::{Egl, EGL}; |
14 | |
15 | /// Wrapper for `EGLDevice`. |
16 | #[derive (Debug, Clone, PartialEq, Eq)] |
17 | pub struct Device { |
18 | inner: EGLDeviceEXT, |
19 | extensions: HashSet<&'static str>, |
20 | name: Option<String>, |
21 | vendor: Option<String>, |
22 | } |
23 | |
24 | impl Device { |
25 | /// Query the available devices. |
26 | /// |
27 | /// This function returns [`Err`] if the `EGL_EXT_device_query` and |
28 | /// `EGL_EXT_device_enumeration` or `EGL_EXT_device_base` extensions are |
29 | /// not available. |
30 | pub fn query_devices() -> Result<impl Iterator<Item = Device>> { |
31 | let egl = match EGL.as_ref() { |
32 | Some(egl) => egl, |
33 | None => return Err(ErrorKind::NotFound.into()), |
34 | }; |
35 | |
36 | let no_display_extensions = |
37 | CLIENT_EXTENSIONS.get_or_init(|| get_extensions(egl, egl::NO_DISPLAY)); |
38 | |
39 | // Querying devices requires EGL_EXT_device_enumeration and |
40 | // EGL_EXT_device_query. |
41 | // |
42 | // Or we can check for the EGL_EXT_device_base extension since it contains both |
43 | // extensions. |
44 | if (!no_display_extensions.contains("EGL_EXT_device_enumeration" ) |
45 | && !no_display_extensions.contains("EGL_EXT_device_query" )) |
46 | || !no_display_extensions.contains("EGL_EXT_device_base" ) |
47 | { |
48 | return Err(ErrorKind::NotSupported("EGL does not support EGL_EXT_device_base" ).into()); |
49 | } |
50 | |
51 | let mut device_count = 0; |
52 | |
53 | if unsafe { |
54 | // The specification states: |
55 | // > An EGL_BAD_PARAMETER error is generated if <max_devices> is |
56 | // > less than or equal to zero unless <devices> is NULL, or if |
57 | // > <num_devices> is NULL. |
58 | // |
59 | // The error will never be generated since num_devices is a pointer |
60 | // to the count being queried. Therefore there is no need to check |
61 | // the error. |
62 | egl.QueryDevicesEXT(0, ptr::null_mut(), &mut device_count) == egl::FALSE |
63 | } { |
64 | super::check_error()?; |
65 | // On failure, EGL_FALSE is returned. |
66 | return Err(ErrorKind::NotSupported("Querying device count failed" ).into()); |
67 | } |
68 | |
69 | let mut devices = Vec::with_capacity(device_count as usize); |
70 | |
71 | unsafe { |
72 | let mut count = device_count; |
73 | if egl.QueryDevicesEXT(device_count, devices.as_mut_ptr(), &mut count) == egl::FALSE { |
74 | super::check_error()?; |
75 | // On failure, EGL_FALSE is returned. |
76 | return Err(ErrorKind::NotSupported("Querying devices failed" ).into()); |
77 | } |
78 | |
79 | // SAFETY: EGL has initialized the vector for the number of devices. |
80 | devices.set_len(device_count as usize); |
81 | } |
82 | |
83 | Ok(devices.into_iter().flat_map(|ptr| Device::from_ptr(egl, ptr))) |
84 | } |
85 | |
86 | /// Get the device extensions supported by this device. |
87 | /// |
88 | /// These extensions are distinct from the display extensions and should not |
89 | /// be used interchangeably. |
90 | pub fn extensions(&self) -> &HashSet<&str> { |
91 | &self.extensions |
92 | } |
93 | |
94 | /// Get the name of the device. |
95 | /// |
96 | /// This function will return [`None`] if the `EGL_EXT_device_query_name` |
97 | /// device extension is not available. |
98 | pub fn name(&self) -> Option<&str> { |
99 | self.name.as_deref() |
100 | } |
101 | |
102 | /// Get the vendor of the device. |
103 | /// |
104 | /// This function will return [`None`] if the `EGL_EXT_device_query_name` |
105 | /// device extension is not available. |
106 | pub fn vendor(&self) -> Option<&str> { |
107 | self.vendor.as_deref() |
108 | } |
109 | |
110 | /// Get a raw handle to the `EGLDevice`. |
111 | pub fn raw_device(&self) -> *const c_void { |
112 | self.inner |
113 | } |
114 | } |
115 | |
116 | // SAFETY: An EGLDevice is immutable and valid for the lifetime of the EGL |
117 | // library. |
118 | unsafe impl Send for Device {} |
119 | unsafe impl Sync for Device {} |
120 | |
121 | impl Device { |
122 | unsafe fn query_string(egl_device: *const c_void, name: egl::types::EGLenum) -> Option<String> { |
123 | let egl = super::EGL.as_ref().unwrap(); |
124 | |
125 | // SAFETY: The caller has ensured the name is valid. |
126 | let ptr = unsafe { egl.QueryDeviceStringEXT(egl_device, name as _) }; |
127 | |
128 | if ptr.is_null() { |
129 | return None; |
130 | } |
131 | |
132 | unsafe { CStr::from_ptr(ptr) }.to_str().ok().map(String::from) |
133 | } |
134 | |
135 | pub(crate) fn from_ptr(egl: &Egl, ptr: *const c_void) -> Result<Self> { |
136 | // SAFETY: The EGL specification guarantees the returned string is |
137 | // static and null terminated: |
138 | // |
139 | // > eglQueryDeviceStringEXT returns a pointer to a static, |
140 | // > zero-terminated string describing some aspect of the specified |
141 | // > EGLDeviceEXT. <name> must be EGL_EXTENSIONS. |
142 | let extensions = |
143 | unsafe { extensions_from_ptr(egl.QueryDeviceStringEXT(ptr, egl::EXTENSIONS as _)) }; |
144 | |
145 | let (name, vendor) = if extensions.contains("EGL_EXT_device_query_name" ) { |
146 | // SAFETY: RENDERER_EXT and VENDOR are valid strings for device string queries |
147 | // if EGL_EXT_device_query_name. |
148 | unsafe { |
149 | (Self::query_string(ptr, egl::RENDERER_EXT), Self::query_string(ptr, egl::VENDOR)) |
150 | } |
151 | } else { |
152 | (None, None) |
153 | }; |
154 | |
155 | Ok(Self { inner: ptr, extensions, name, vendor }) |
156 | } |
157 | } |
158 | |