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