smithay/backend/renderer/multigpu/
gbm.rs

1//! Implementation of the multi-gpu [`GraphicsApi`] using
2//! user provided GBM devices and OpenGL ES for rendering.
3
4use tracing::warn;
5#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
6use wayland_server::protocol::wl_buffer;
7
8use crate::backend::{
9    SwapBuffersError,
10    allocator::{
11        Allocator,
12        dmabuf::{AnyError, Dmabuf, DmabufAllocator},
13        gbm::{GbmAllocator, GbmBufferFlags, GbmDevice},
14    },
15    drm::{CreateDrmNodeError, DrmNode},
16    egl::{EGLContext, EGLDisplay, Error as EGLError, context::ContextPriority},
17    renderer::{
18        Renderer, RendererSuper,
19        gles::{GlesError, GlesRenderer},
20        multigpu::{ApiDevice, Error as MultiError, GraphicsApi},
21    },
22};
23#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
24use crate::{
25    backend::{
26        allocator::Buffer as BufferTrait,
27        egl::display::EGLBufferReader,
28        renderer::{
29            Bind, ExportMem, ImportDma, ImportEgl, ImportMem,
30            multigpu::{Error as MultigpuError, MultiRenderer, MultiTexture, import_dmabuf_internal},
31        },
32    },
33    utils::{Buffer as BufferCoords, Rectangle},
34};
35#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
36use std::borrow::BorrowMut;
37use std::{
38    borrow::Borrow,
39    collections::HashMap,
40    fmt,
41    os::unix::prelude::AsFd,
42    sync::atomic::{AtomicBool, Ordering},
43};
44
45/// Errors raised by the [`GbmGlesBackend`]
46#[derive(Debug, thiserror::Error)]
47pub enum Error {
48    /// EGL api error
49    #[error(transparent)]
50    Egl(#[from] EGLError),
51    /// OpenGL error
52    #[error(transparent)]
53    Gl(#[from] GlesError),
54    /// Error creating a drm node
55    #[error(transparent)]
56    DrmNode(#[from] CreateDrmNodeError),
57}
58
59impl From<Error> for SwapBuffersError {
60    #[inline]
61    fn from(err: Error) -> SwapBuffersError {
62        match err {
63            x @ Error::DrmNode(_) | x @ Error::Egl(_) => SwapBuffersError::ContextLost(Box::new(x)),
64            Error::Gl(x) => x.into(),
65        }
66    }
67}
68
69type Factory = Box<dyn Fn(&EGLDisplay) -> Result<GlesRenderer, Error>>;
70
71/// A [`GraphicsApi`] utilizing user-provided GBM Devices and OpenGL ES for rendering.
72pub struct GbmGlesBackend<R, A: AsFd + 'static> {
73    devices: HashMap<DrmNode, (EGLDisplay, GbmAllocator<A>)>,
74    factory: Option<Factory>,
75    context_priority: Option<ContextPriority>,
76    allocator_flags: GbmBufferFlags,
77    needs_enumeration: AtomicBool,
78    _renderer: std::marker::PhantomData<R>,
79}
80
81impl<R, A: AsFd + fmt::Debug + 'static> fmt::Debug for GbmGlesBackend<R, A> {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        f.debug_struct("GbmGlesBackend")
84            .field("devices", &self.devices)
85            .finish()
86    }
87}
88
89impl<R, A: AsFd + 'static> Default for GbmGlesBackend<R, A> {
90    #[inline]
91    fn default() -> Self {
92        GbmGlesBackend {
93            devices: HashMap::new(),
94            factory: None,
95            context_priority: None,
96            allocator_flags: GbmBufferFlags::RENDERING,
97            needs_enumeration: AtomicBool::new(true),
98            _renderer: std::marker::PhantomData,
99        }
100    }
101}
102
103impl<R, A: AsFd + Clone + Send + 'static> GbmGlesBackend<R, A> {
104    /// Initialize a new [`GbmGlesBackend`] with a factory for instantiating [`GlesRenderer`]s
105    pub fn with_factory<F>(factory: F) -> Self
106    where
107        F: Fn(&EGLDisplay) -> Result<GlesRenderer, Error> + 'static,
108    {
109        Self {
110            factory: Some(Box::new(factory)),
111            ..Default::default()
112        }
113    }
114
115    /// Initialize a new [`GbmGlesBackend`] with a [`ContextPriority`] for instantiating [`GlesRenderer`]s
116    ///
117    /// Note: This is mutually exclusive with [`GbmGlesBackend::with_factory`](GbmGlesBackend::with_factory),
118    /// but you can create an [`EGLContext`] with a specific [`ContextPriority`] within your factory.
119    /// See [`EGLContext::new_with_priority`] for more information.
120    pub fn with_context_priority(priority: ContextPriority) -> Self {
121        Self {
122            context_priority: Some(priority),
123            ..Default::default()
124        }
125    }
126
127    /// Sets the default flags to use for allocating buffers via the [`GbmAllocator`]
128    /// provided by these backends devices.
129    ///
130    /// Only affects nodes added via [`add_node`][Self::add_node] *after* calling this method.
131    pub fn set_allocator_flags(&mut self, flags: GbmBufferFlags) {
132        self.allocator_flags = flags;
133    }
134
135    /// Add a new GBM device for a given node to the api
136    pub fn add_node(&mut self, node: DrmNode, gbm: GbmDevice<A>) -> Result<(), EGLError> {
137        if self.devices.contains_key(&node) {
138            return Ok(());
139        }
140
141        let allocator = GbmAllocator::new(gbm.clone(), self.allocator_flags);
142        self.devices
143            .insert(node, (unsafe { EGLDisplay::new(gbm)? }, allocator));
144        self.needs_enumeration.store(true, Ordering::SeqCst);
145        Ok(())
146    }
147
148    /// Remove a given node from the api
149    pub fn remove_node(&mut self, node: &DrmNode) {
150        if self.devices.remove(node).is_some() {
151            self.needs_enumeration.store(true, Ordering::SeqCst);
152        }
153    }
154}
155
156impl<R: From<GlesRenderer> + Renderer<Error = GlesError> + Borrow<GlesRenderer>, A: AsFd + Clone + 'static>
157    GraphicsApi for GbmGlesBackend<R, A>
158{
159    type Device = GbmGlesDevice<R>;
160    type Error = Error;
161
162    fn enumerate(&self, list: &mut Vec<Self::Device>) -> Result<(), Self::Error> {
163        self.needs_enumeration.store(false, Ordering::SeqCst);
164
165        // remove old stuff
166        list.retain(|renderer| {
167            self.devices
168                .keys()
169                .any(|node| renderer.node.dev_id() == node.dev_id())
170        });
171
172        // add new stuff
173        let new_renderers = self
174            .devices
175            .iter()
176            .filter(|(node, _)| {
177                !list
178                    .iter()
179                    .any(|renderer| renderer.node.dev_id() == node.dev_id())
180            })
181            .map(|(node, (display, gbm))| {
182                let renderer = if let Some(factory) = self.factory.as_ref() {
183                    factory(display)?.into()
184                } else {
185                    let context =
186                        EGLContext::new_with_priority(display, self.context_priority.unwrap_or_default())
187                            .map_err(Error::Egl)?;
188                    unsafe { GlesRenderer::new(context).map_err(Error::Gl)? }.into()
189                };
190
191                Ok(GbmGlesDevice {
192                    node: *node,
193                    _display: display.clone(),
194                    renderer,
195                    allocator: Box::new(DmabufAllocator(gbm.clone())),
196                })
197            })
198            .flat_map(|x: Result<GbmGlesDevice<R>, Error>| match x {
199                Ok(x) => Some(x),
200                Err(x) => {
201                    warn!("Skipping GbmDevice: {}", x);
202                    None
203                }
204            })
205            .collect::<Vec<GbmGlesDevice<R>>>();
206        list.extend(new_renderers);
207
208        // but don't replace already initialized renderers
209
210        Ok(())
211    }
212
213    fn needs_enumeration(&self) -> bool {
214        self.needs_enumeration.load(Ordering::Acquire)
215    }
216
217    fn identifier() -> &'static str {
218        "gbm_gles"
219    }
220}
221
222// TODO: Replace with specialization impl in multigpu/mod once possible
223impl<
224    T: GraphicsApi,
225    R: From<GlesRenderer> + Renderer<Error = GlesError> + Borrow<GlesRenderer>,
226    A: AsFd + Clone + 'static,
227> std::convert::From<GlesError> for MultiError<GbmGlesBackend<R, A>, T>
228where
229    T::Error: 'static,
230    <<T::Device as ApiDevice>::Renderer as RendererSuper>::Error: 'static,
231{
232    #[inline]
233    fn from(err: GlesError) -> MultiError<GbmGlesBackend<R, A>, T> {
234        MultiError::Render(err)
235    }
236}
237
238/// [`ApiDevice`] of the [`GbmGlesBackend`]
239pub struct GbmGlesDevice<R> {
240    node: DrmNode,
241    renderer: R,
242    allocator: Box<dyn Allocator<Buffer = Dmabuf, Error = AnyError>>,
243    _display: EGLDisplay,
244}
245
246impl<R: Renderer> fmt::Debug for GbmGlesDevice<R> {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        f.debug_struct("GbmGlesDevice")
249            .field("node", &self.node)
250            .field("renderer", &self.renderer)
251            .finish_non_exhaustive()
252    }
253}
254
255impl<R: Renderer + Borrow<GlesRenderer>> ApiDevice for GbmGlesDevice<R> {
256    type Renderer = R;
257
258    fn renderer(&self) -> &Self::Renderer {
259        &self.renderer
260    }
261    fn renderer_mut(&mut self) -> &mut Self::Renderer {
262        &mut self.renderer
263    }
264    fn allocator(&mut self) -> &mut dyn Allocator<Buffer = Dmabuf, Error = AnyError> {
265        self.allocator.as_mut()
266    }
267    fn node(&self) -> &DrmNode {
268        &self.node
269    }
270    fn can_do_cross_device_imports(&self) -> bool {
271        !self.renderer.borrow().is_software()
272    }
273}
274
275#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
276impl<R, A> ImportEgl for MultiRenderer<'_, '_, GbmGlesBackend<R, A>, GbmGlesBackend<R, A>>
277where
278    A: AsFd + Clone + 'static,
279    R: From<GlesRenderer>
280        + BorrowMut<GlesRenderer>
281        + Renderer<Error = GlesError>
282        + Bind<Dmabuf>
283        + ImportDma
284        + ImportMem
285        + ImportEgl
286        + ExportMem
287        + 'static,
288    R::TextureId: Clone + Send,
289{
290    fn bind_wl_display(&mut self, display: &wayland_server::DisplayHandle) -> Result<(), EGLError> {
291        self.render.renderer_mut().bind_wl_display(display)
292    }
293    fn unbind_wl_display(&mut self) {
294        self.render.renderer_mut().unbind_wl_display()
295    }
296    fn egl_reader(&self) -> Option<&EGLBufferReader> {
297        self.render.renderer().egl_reader()
298    }
299
300    #[profiling::function]
301    fn import_egl_buffer(
302        &mut self,
303        buffer: &wl_buffer::WlBuffer,
304        surface: Option<&crate::wayland::compositor::SurfaceData>,
305        damage: &[Rectangle<i32, BufferCoords>],
306    ) -> Result<Self::TextureId, Self::Error> {
307        if let Some(dmabuf) = Self::try_import_egl(self.render.renderer_mut(), buffer)
308            .ok()
309            .or_else(|| {
310                self.target
311                    .as_mut()
312                    .and_then(|renderer| Self::try_import_egl(renderer.device.renderer_mut(), buffer).ok())
313            })
314            .or_else(|| {
315                self.other_renderers
316                    .iter_mut()
317                    .find_map(|renderer| Self::try_import_egl(renderer.renderer_mut(), buffer).ok())
318            })
319        {
320            let texture = MultiTexture::from_surface(surface, dmabuf.size(), dmabuf.format());
321            let texture_ref = texture.0.clone();
322            let res = import_dmabuf_internal(
323                self.render,
324                self.target.as_mut().map(|target| &mut *target.device),
325                &mut self.other_renderers,
326                &dmabuf,
327                texture,
328                Some(damage),
329            );
330            if res.is_ok() {
331                if let Some(surface) = surface {
332                    surface.data_map.insert_if_missing_threadsafe(|| texture_ref);
333                }
334            }
335            return res;
336        }
337
338        Err(MultigpuError::DeviceMissing)
339    }
340}
341
342#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
343impl<R, A> MultiRenderer<'_, '_, GbmGlesBackend<R, A>, GbmGlesBackend<R, A>>
344where
345    A: AsFd + Clone + 'static,
346    R: From<GlesRenderer>
347        + BorrowMut<GlesRenderer>
348        + Renderer<Error = GlesError>
349        + ImportDma
350        + ImportMem
351        + ImportEgl
352        + ExportMem
353        + 'static,
354{
355    #[profiling::function]
356    fn try_import_egl(
357        renderer: &mut R,
358        buffer: &wl_buffer::WlBuffer,
359    ) -> Result<Dmabuf, MultigpuError<GbmGlesBackend<R, A>, GbmGlesBackend<R, A>>> {
360        if !renderer
361            .borrow_mut()
362            .extensions
363            .iter()
364            .any(|ext| ext == "GL_OES_EGL_image")
365        {
366            return Err(MultigpuError::Render(GlesError::GLExtensionNotSupported(&[
367                "GL_OES_EGL_image",
368            ])));
369        }
370
371        if renderer.egl_reader().is_none() {
372            return Err(MultigpuError::Render(GlesError::EGLBufferAccessError(
373                crate::backend::egl::BufferAccessError::NotManaged(crate::backend::egl::EGLError::BadDisplay),
374            )));
375        }
376
377        unsafe {
378            renderer
379                .borrow_mut()
380                .egl_context()
381                .make_current()
382                .map_err(GlesError::from)
383                .map_err(MultigpuError::Render)?
384        };
385
386        let egl = renderer
387            .egl_reader()
388            .as_ref()
389            .unwrap()
390            .egl_buffer_contents(buffer)
391            .map_err(GlesError::EGLBufferAccessError)
392            .map_err(MultigpuError::Render)?;
393        renderer
394            .borrow_mut()
395            .egl_context()
396            .display()
397            .create_dmabuf_from_image(egl.image(0).unwrap(), egl.size, egl.y_inverted)
398            .map_err(GlesError::BindBufferEGLError)
399            .map_err(MultigpuError::Render)
400    }
401}