1//! Everything related to `EGLDevice`.
2
3use std::collections::HashSet;
4use std::ffi::{c_void, CStr};
5use std::ptr;
6
7use glutin_egl_sys::egl;
8use glutin_egl_sys::egl::types::EGLDeviceEXT;
9
10use crate::error::{ErrorKind, Result};
11
12use super::display::{extensions_from_ptr, get_extensions, CLIENT_EXTENSIONS};
13use super::{Egl, EGL};
14
15/// Wrapper for `EGLDevice`.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Device {
18 inner: EGLDeviceEXT,
19 extensions: HashSet<&'static str>,
20 name: Option<String>,
21 vendor: Option<String>,
22}
23
24impl 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.
118unsafe impl Send for Device {}
119unsafe impl Sync for Device {}
120
121impl 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