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    allocator::{
10        dmabuf::{AnyError, Dmabuf, DmabufAllocator},
11        gbm::{GbmAllocator, GbmBufferFlags, GbmDevice},
12        Allocator,
13    },
14    drm::{CreateDrmNodeError, DrmNode},
15    egl::{context::ContextPriority, EGLContext, EGLDisplay, Error as EGLError},
16    renderer::{
17        gles::{GlesError, GlesRenderer},
18        multigpu::{ApiDevice, Error as MultiError, GraphicsApi},
19        Renderer, RendererSuper,
20    },
21    SwapBuffersError,
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            multigpu::{Error as MultigpuError, MultiRenderer, MultiTexture},
30            Bind, ExportMem, ImportDma, ImportEgl, ImportMem,
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<
157        R: From<GlesRenderer> + Renderer<Error = GlesError> + Borrow<GlesRenderer>,
158        A: AsFd + Clone + 'static,
159    > GraphicsApi for GbmGlesBackend<R, A>
160{
161    type Device = GbmGlesDevice<R>;
162    type Error = Error;
163
164    fn enumerate(&self, list: &mut Vec<Self::Device>) -> Result<(), Self::Error> {
165        self.needs_enumeration.store(false, Ordering::SeqCst);
166
167        // remove old stuff
168        list.retain(|renderer| {
169            self.devices
170                .keys()
171                .any(|node| renderer.node.dev_id() == node.dev_id())
172        });
173
174        // add new stuff
175        let new_renderers = self
176            .devices
177            .iter()
178            .filter(|(node, _)| {
179                !list
180                    .iter()
181                    .any(|renderer| renderer.node.dev_id() == node.dev_id())
182            })
183            .map(|(node, (display, gbm))| {
184                let renderer = if let Some(factory) = self.factory.as_ref() {
185                    factory(display)?.into()
186                } else {
187                    let context =
188                        EGLContext::new_with_priority(display, self.context_priority.unwrap_or_default())
189                            .map_err(Error::Egl)?;
190                    unsafe { GlesRenderer::new(context).map_err(Error::Gl)? }.into()
191                };
192
193                Ok(GbmGlesDevice {
194                    node: *node,
195                    _display: display.clone(),
196                    renderer,
197                    allocator: Box::new(DmabufAllocator(gbm.clone())),
198                })
199            })
200            .flat_map(|x: Result<GbmGlesDevice<R>, Error>| match x {
201                Ok(x) => Some(x),
202                Err(x) => {
203                    warn!("Skipping GbmDevice: {}", x);
204                    None
205                }
206            })
207            .collect::<Vec<GbmGlesDevice<R>>>();
208        list.extend(new_renderers);
209
210        // but don't replace already initialized renderers
211
212        Ok(())
213    }
214
215    fn needs_enumeration(&self) -> bool {
216        self.needs_enumeration.load(Ordering::Acquire)
217    }
218
219    fn identifier() -> &'static str {
220        "gbm_gles"
221    }
222}
223
224// TODO: Replace with specialization impl in multigpu/mod once possible
225impl<
226        T: GraphicsApi,
227        R: From<GlesRenderer> + Renderer<Error = GlesError> + Borrow<GlesRenderer>,
228        A: AsFd + Clone + 'static,
229    > std::convert::From<GlesError> for MultiError<GbmGlesBackend<R, A>, T>
230where
231    T::Error: 'static,
232    <<T::Device as ApiDevice>::Renderer as RendererSuper>::Error: 'static,
233{
234    #[inline]
235    fn from(err: GlesError) -> MultiError<GbmGlesBackend<R, A>, T> {
236        MultiError::Render(err)
237    }
238}
239
240/// [`ApiDevice`] of the [`GbmGlesBackend`]
241pub struct GbmGlesDevice<R> {
242    node: DrmNode,
243    renderer: R,
244    allocator: Box<dyn Allocator<Buffer = Dmabuf, Error = AnyError>>,
245    _display: EGLDisplay,
246}
247
248impl<R: Renderer> fmt::Debug for GbmGlesDevice<R> {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        f.debug_struct("GbmGlesDevice")
251            .field("node", &self.node)
252            .field("renderer", &self.renderer)
253            .finish_non_exhaustive()
254    }
255}
256
257impl<R: Renderer + Borrow<GlesRenderer>> ApiDevice for GbmGlesDevice<R> {
258    type Renderer = R;
259
260    fn renderer(&self) -> &Self::Renderer {
261        &self.renderer
262    }
263    fn renderer_mut(&mut self) -> &mut Self::Renderer {
264        &mut self.renderer
265    }
266    fn allocator(&mut self) -> &mut dyn Allocator<Buffer = Dmabuf, Error = AnyError> {
267        self.allocator.as_mut()
268    }
269    fn node(&self) -> &DrmNode {
270        &self.node
271    }
272    fn can_do_cross_device_imports(&self) -> bool {
273        !self.renderer.borrow().is_software()
274    }
275}
276
277#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
278impl<R, A> ImportEgl for MultiRenderer<'_, '_, GbmGlesBackend<R, A>, GbmGlesBackend<R, A>>
279where
280    A: AsFd + Clone + 'static,
281    R: From<GlesRenderer>
282        + BorrowMut<GlesRenderer>
283        + Renderer<Error = GlesError>
284        + Bind<Dmabuf>
285        + ImportDma
286        + ImportMem
287        + ImportEgl
288        + ExportMem
289        + 'static,
290    R::TextureId: Clone + Send,
291{
292    fn bind_wl_display(&mut self, display: &wayland_server::DisplayHandle) -> Result<(), EGLError> {
293        self.render.renderer_mut().bind_wl_display(display)
294    }
295    fn unbind_wl_display(&mut self) {
296        self.render.renderer_mut().unbind_wl_display()
297    }
298    fn egl_reader(&self) -> Option<&EGLBufferReader> {
299        self.render.renderer().egl_reader()
300    }
301
302    #[profiling::function]
303    fn import_egl_buffer(
304        &mut self,
305        buffer: &wl_buffer::WlBuffer,
306        surface: Option<&crate::wayland::compositor::SurfaceData>,
307        damage: &[Rectangle<i32, BufferCoords>],
308    ) -> Result<Self::TextureId, Self::Error> {
309        if let Some(dmabuf) = Self::try_import_egl(self.render.renderer_mut(), buffer)
310            .ok()
311            .or_else(|| {
312                self.target
313                    .as_mut()
314                    .and_then(|renderer| Self::try_import_egl(renderer.device.renderer_mut(), buffer).ok())
315            })
316            .or_else(|| {
317                self.other_renderers
318                    .iter_mut()
319                    .find_map(|renderer| Self::try_import_egl(renderer.renderer_mut(), buffer).ok())
320            })
321        {
322            let texture = MultiTexture::from_surface(surface, dmabuf.size(), dmabuf.format());
323            let texture_ref = texture.0.clone();
324            let res = self.import_dmabuf_internal(&dmabuf, texture, Some(damage));
325            if res.is_ok() {
326                if let Some(surface) = surface {
327                    surface.data_map.insert_if_missing_threadsafe(|| texture_ref);
328                }
329            }
330            return res;
331        }
332
333        Err(MultigpuError::DeviceMissing)
334    }
335}
336
337#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
338impl<R, A> MultiRenderer<'_, '_, GbmGlesBackend<R, A>, GbmGlesBackend<R, A>>
339where
340    A: AsFd + Clone + 'static,
341    R: From<GlesRenderer>
342        + BorrowMut<GlesRenderer>
343        + Renderer<Error = GlesError>
344        + ImportDma
345        + ImportMem
346        + ImportEgl
347        + ExportMem
348        + 'static,
349{
350    #[profiling::function]
351    fn try_import_egl(
352        renderer: &mut R,
353        buffer: &wl_buffer::WlBuffer,
354    ) -> Result<Dmabuf, MultigpuError<GbmGlesBackend<R, A>, GbmGlesBackend<R, A>>> {
355        if !renderer
356            .borrow_mut()
357            .extensions
358            .iter()
359            .any(|ext| ext == "GL_OES_EGL_image")
360        {
361            return Err(MultigpuError::Render(GlesError::GLExtensionNotSupported(&[
362                "GL_OES_EGL_image",
363            ])));
364        }
365
366        if renderer.egl_reader().is_none() {
367            return Err(MultigpuError::Render(GlesError::EGLBufferAccessError(
368                crate::backend::egl::BufferAccessError::NotManaged(crate::backend::egl::EGLError::BadDisplay),
369            )));
370        }
371
372        unsafe {
373            renderer
374                .borrow_mut()
375                .egl_context()
376                .make_current()
377                .map_err(GlesError::from)
378                .map_err(MultigpuError::Render)?
379        };
380
381        let egl = renderer
382            .egl_reader()
383            .as_ref()
384            .unwrap()
385            .egl_buffer_contents(buffer)
386            .map_err(GlesError::EGLBufferAccessError)
387            .map_err(MultigpuError::Render)?;
388        renderer
389            .borrow_mut()
390            .egl_context()
391            .display()
392            .create_dmabuf_from_image(egl.image(0).unwrap(), egl.size, egl.y_inverted)
393            .map_err(GlesError::BindBufferEGLError)
394            .map_err(MultigpuError::Render)
395    }
396}