smithay/backend/egl/device.rs
1use std::{ffi::CStr, os::raw::c_void, path::PathBuf, ptr};
2
3use super::{
4 ffi::{self, egl::types::EGLDeviceEXT},
5 wrap_egl_call_bool, wrap_egl_call_ptr, EGLDisplay, EGLError, Error,
6};
7
8#[cfg(feature = "backend_drm")]
9use crate::backend::drm::{DrmNode, NodeType};
10
11/// safe EGLDevice wrapper
12#[derive(Debug, Clone)]
13pub struct EGLDevice {
14 pub(super) inner: EGLDeviceEXT,
15 device_extensions: Vec<String>,
16}
17
18unsafe impl Send for EGLDevice {}
19
20impl EGLDevice {
21 /// Returns an iterator which enumerates over the available [`EGLDevices`](EGLDevice) on the system.
22 ///
23 /// This function will return an error if the following extensions are not available:
24 /// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt)
25 /// - [`EGL_EXT_device_enumeration`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_enumeration.txt)
26 /// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt)
27 pub fn enumerate() -> Result<impl Iterator<Item = EGLDevice>, Error> {
28 // Check the required extensions are present:
29 let extensions = ffi::make_sure_egl_is_loaded()?;
30
31 if !extensions.iter().any(|s| s == "EGL_EXT_device_base") {
32 return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_base"]));
33 }
34
35 if !extensions.iter().any(|s| s == "EGL_EXT_device_enumeration") {
36 return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_enumeration"]));
37 }
38
39 if !extensions.iter().any(|s| s == "EGL_EXT_device_query") {
40 return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_query"]));
41 }
42
43 // Yes, this is marked as `mut` even though the value is never mutated. EGL expects a mutable pointer
44 // for num_devices and will not modify the value if we are asking for pointers to some EGLDeviceEXT.
45 let mut device_amount = 0;
46 wrap_egl_call_bool(|| unsafe {
47 // Passing 0 for max devices and a null-pointer for devices is safe because we indicate we only
48 // want the number of devices.
49 ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), &mut device_amount)
50 })
51 .map_err(Error::QueryDevices)?;
52
53 let mut devices = Vec::with_capacity(device_amount as usize);
54
55 wrap_egl_call_bool(|| unsafe {
56 // SAFETY:
57 // - Vector used as pointer is correct size.
58 // - Device amount will accommodate all available devices because we have checked the size earlier.
59 ffi::egl::QueryDevicesEXT(device_amount, devices.as_mut_ptr(), &mut device_amount)
60 })
61 .map_err(Error::QueryDevices)?;
62
63 // Set the length of the vec so that rust does not think it is still empty.
64
65 // SAFETY:
66 // 1) the vector is pre-allocated to the same size as the amount of returned devices.
67 // 2) EGL has initialized every value in the vector.
68 unsafe { devices.set_len(device_amount as usize) };
69
70 Ok(devices
71 .into_iter()
72 .map(|device| {
73 // SAFETY: We have queried that the extensions are valid and the device pointer is valid.
74 let device_extensions = unsafe { device_extensions(device) }?;
75 Ok(EGLDevice {
76 inner: device,
77 device_extensions,
78 })
79 })
80 .collect::<Result<Vec<_>, EGLError>>()
81 .map_err(Error::QueryDevices)?
82 .into_iter())
83 }
84
85 /// Returns the [`EGLDevices`](EGLDevice) related to the given `EGLDisplay`.
86 ///
87 /// This function will return an error if the following extensions are not available:
88 /// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt)
89 /// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt)
90 pub fn device_for_display(display: &EGLDisplay) -> Result<EGLDevice, Error> {
91 // Check the required extensions are present:
92 let extensions = ffi::make_sure_egl_is_loaded()?;
93
94 if !extensions.iter().any(|s| s == "EGL_EXT_device_base") {
95 return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_base"]));
96 }
97
98 if !extensions.iter().any(|s| s == "EGL_EXT_device_query") {
99 return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_query"]));
100 }
101
102 let mut device: ffi::egl::types::EGLAttrib = 0;
103 if unsafe {
104 ffi::egl::QueryDisplayAttribEXT(
105 display.get_display_handle().handle,
106 ffi::egl::DEVICE_EXT as i32,
107 &mut device as *mut _,
108 )
109 } != ffi::egl::TRUE
110 {
111 return Err(Error::DisplayNotSupported);
112 }
113
114 let device = device as EGLDeviceEXT;
115
116 // Per the EGL specification:
117 //
118 // > Functions with a return type of EGLDeviceEXT will return this value on failure: EGL_NO_DEVICE_EXT
119 if device == ffi::egl::NO_DEVICE_EXT {
120 return Err(Error::DisplayNotSupported);
121 }
122
123 // SAFETY: We have queried that the extensions are valid and the device pointer is valid.
124 let device_extensions = unsafe { device_extensions(device) }.map_err(Error::QueryDevices)?;
125 Ok(EGLDevice {
126 inner: device,
127 device_extensions,
128 })
129 }
130
131 /// Returns a list of extensions the device supports.
132 pub fn extensions(&self) -> Vec<String> {
133 self.device_extensions.clone()
134 }
135
136 /// Returns the path to the drm node of this EGLDevice.
137 ///
138 /// This function will return an error if the following extensions are not available:
139 /// - [`EGL_EXT_device_drm`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm.txt)
140 pub fn drm_device_path(&self) -> Result<PathBuf, Error> {
141 if !self.extensions().contains(&"EGL_EXT_device_drm".to_owned()) {
142 Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_drm"]))
143 } else {
144 let raw_path = wrap_egl_call_ptr(|| unsafe {
145 ffi::egl::QueryDeviceStringEXT(
146 self.inner,
147 ffi::egl::DRM_DEVICE_FILE_EXT as ffi::egl::types::EGLint,
148 )
149 })
150 .map_err(Error::QueryDeviceProperty)?;
151 if raw_path.is_null() {
152 return Err(Error::EmptyDeviceProperty);
153 }
154
155 // This is safe because of the following:
156 // 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long
157 // as the EGLDisplay is valid. Since the pointer is only used in this function, the
158 // lifetime of the pointer will fulfil Rust's CStr requirements on lifetime.
159 // 2) The string returned by EGL is null terminated.
160 let device_path = unsafe { CStr::from_ptr(raw_path) }
161 .to_str()
162 // EGL ensures the string is valid UTF-8
163 .expect("Non-UTF8 device path name");
164
165 Ok(PathBuf::from(device_path))
166 }
167 }
168
169 /// Returns the path to the render node of this EGLDevice.
170 ///
171 /// This function will return an error if the following extensions are not available:
172 /// - [`EGL_EXT_device_drm_render_node`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt)
173 pub fn render_device_path(&self) -> Result<PathBuf, Error> {
174 if !self
175 .extensions()
176 .contains(&"EGL_EXT_device_drm_render_node".to_owned())
177 {
178 Err(Error::EglExtensionNotSupported(&[
179 "EGL_EXT_device_drm_render_node",
180 ]))
181 } else {
182 let raw_path = wrap_egl_call_ptr(|| unsafe {
183 ffi::egl::QueryDeviceStringEXT(
184 self.inner,
185 ffi::egl::DRM_RENDER_NODE_FILE_EXT as ffi::egl::types::EGLint,
186 )
187 })
188 .map_err(Error::QueryDeviceProperty)?;
189 if raw_path.is_null() {
190 return Err(Error::EmptyDeviceProperty);
191 }
192
193 // This is safe because of the following:
194 // 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long
195 // as the EGLDisplay is valid. Since the pointer is only used in this function, the
196 // lifetime of the pointer will fulfil Rust's CStr requirements on lifetime.
197 // 2) The string returned by EGL is null terminated.
198 let device_path = unsafe { CStr::from_ptr(raw_path) }
199 .to_str()
200 // EGL ensures the string is valid UTF-8
201 .expect("Non-UTF8 device path name");
202
203 Ok(PathBuf::from(device_path))
204 }
205 }
206
207 /// Returns the drm node beloging to this device.
208 /// Tries to optain a render_node first through `EGL_EXT_device_drm_render_node`
209 /// (see also [`EGLDevice::render_device_path`]) and then falls back to
210 /// get a render_node from `EGL_EXT_device_drm` (see also [`EGLDevice::drm_device_path`]).
211 /// If both fail to produce a render node, whichever device returned by
212 /// `EGL_EXT_device_drm` is returned.
213 #[cfg(feature = "backend_drm")]
214 pub fn try_get_render_node(&self) -> Result<Option<DrmNode>, Error> {
215 // first lets try to get a render_node directly
216 match self
217 .render_device_path()
218 .ok()
219 .and_then(|path| DrmNode::from_path(path).ok())
220 {
221 Some(node) => Ok(Some(node)),
222 // else we take a drm_path
223 None => {
224 let path = self.drm_device_path()?;
225 let node = DrmNode::from_path(path).ok();
226 // and try to convert it to a render_node
227 Ok(node.map(|node| {
228 node.node_with_type(NodeType::Render)
229 .and_then(Result::ok)
230 // and otherwise go with whatever we got initially
231 .unwrap_or(node)
232 }))
233 }
234 }
235 }
236
237 /// Queries if device is not backed by any actual device node and simply renders into client memory.
238 ///
239 /// Note: This simply tests presence of the [`EGL_MESA_device_software`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/docs/_extra/specs/EGL_MESA_device_software.txt) extension.
240 pub fn is_software(&self) -> bool {
241 // Note: EGL_MESA_device_software requires EGL_EXT_device_query which we already test for initializing the device,
242 // so there is no need to re-test it here again.
243 self.extensions().contains(&"EGL_MESA_device_software".to_owned())
244 }
245
246 /// Returns the pointer to the raw [`EGLDevice`].
247 ///
248 /// The pointer will become invalid, when this struct is destroyed.
249 pub fn get_device_handle(&self) -> *const c_void {
250 self.inner
251 }
252}
253
254/// Returns all device extensions a device supports.
255///
256/// # Safety
257///
258/// - The `device` must be a valid pointer to an `EGLDeviceEXT`.
259/// - The following extensions must be supported by the display which provides the device:
260/// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt)
261/// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt)
262unsafe fn device_extensions(device: EGLDeviceEXT) -> Result<Vec<String>, EGLError> {
263 let raw_extensions = wrap_egl_call_ptr(|| {
264 ffi::egl::QueryDeviceStringEXT(device, ffi::egl::EXTENSIONS as ffi::egl::types::EGLint)
265 })?;
266
267 // SAFETY:
268 // 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long
269 // as the EGLDisplay is valid. Safety requirements for the function ensure this.
270 // 2) The string returned by EGL is null terminated.
271 let c_extensions = CStr::from_ptr(raw_extensions);
272
273 Ok(c_extensions
274 .to_str()
275 // EGL ensures the string is valid UTF-8
276 .expect("Non-UTF8 device extension name")
277 // Each extension is space separated (0x20) in the pointer, so strlen cannot return an improper length.
278 .split_whitespace()
279 // Take an owned copy so we do not point to garbage if EGL somehow vanishes.
280 .map(ToOwned::to_owned)
281 .collect::<Vec<_>>())
282}