//! Implementation of the multi-gpu [`GraphicsApi`] using //! user provided GBM devices and OpenGL ES for rendering. use tracing::warn; #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] use wayland_server::protocol::wl_buffer; use crate::backend::{ allocator::{ dmabuf::{AnyError, Dmabuf, DmabufAllocator}, gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}, Allocator, }, drm::{CreateDrmNodeError, DrmNode}, egl::{context::ContextPriority, EGLContext, EGLDisplay, Error as EGLError}, renderer::{ gles::{GlesError, GlesRenderer}, multigpu::{ApiDevice, Error as MultiError, GraphicsApi}, Renderer, RendererSuper, }, SwapBuffersError, }; #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] use crate::{ backend::{ allocator::Buffer as BufferTrait, egl::display::EGLBufferReader, renderer::{ multigpu::{Error as MultigpuError, MultiRenderer, MultiTexture}, Bind, ExportMem, ImportDma, ImportEgl, ImportMem, }, }, utils::{Buffer as BufferCoords, Rectangle}, }; #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] use std::borrow::BorrowMut; use std::{ collections::HashMap, fmt, os::unix::prelude::AsFd, sync::atomic::{AtomicBool, Ordering}, }; /// Errors raised by the [`GbmGlesBackend`] #[derive(Debug, thiserror::Error)] pub enum Error { /// EGL api error #[error(transparent)] Egl(#[from] EGLError), /// OpenGL error #[error(transparent)] Gl(#[from] GlesError), /// Error creating a drm node #[error(transparent)] DrmNode(#[from] CreateDrmNodeError), } impl From for SwapBuffersError { #[inline] fn from(err: Error) -> SwapBuffersError { match err { x @ Error::DrmNode(_) | x @ Error::Egl(_) => SwapBuffersError::ContextLost(Box::new(x)), Error::Gl(x) => x.into(), } } } type Factory = Box Result>; /// A [`GraphicsApi`] utilizing user-provided GBM Devices and OpenGL ES for rendering. pub struct GbmGlesBackend { devices: HashMap)>, factory: Option, context_priority: Option, allocator_flags: GbmBufferFlags, needs_enumeration: AtomicBool, _renderer: std::marker::PhantomData, } impl fmt::Debug for GbmGlesBackend { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("GbmGlesBackend") .field("devices", &self.devices) .finish() } } impl Default for GbmGlesBackend { #[inline] fn default() -> Self { GbmGlesBackend { devices: HashMap::new(), factory: None, context_priority: None, allocator_flags: GbmBufferFlags::RENDERING, needs_enumeration: AtomicBool::new(true), _renderer: std::marker::PhantomData, } } } impl GbmGlesBackend { /// Initialize a new [`GbmGlesBackend`] with a factory for instantiating [`GlesRenderer`]s pub fn with_factory(factory: F) -> Self where F: Fn(&EGLDisplay) -> Result + 'static, { Self { factory: Some(Box::new(factory)), ..Default::default() } } /// Initialize a new [`GbmGlesBackend`] with a [`ContextPriority`] for instantiating [`GlesRenderer`]s /// /// Note: This is mutually exclusive with [`GbmGlesBackend::with_factory`](GbmGlesBackend::with_factory), /// but you can create an [`EGLContext`] with a specific [`ContextPriority`] within your factory. /// See [`EGLContext::new_with_priority`] for more information. pub fn with_context_priority(priority: ContextPriority) -> Self { Self { context_priority: Some(priority), ..Default::default() } } /// Sets the default flags to use for allocating buffers via the [`GbmAllocator`] /// provided by these backends devices. /// /// Only affects nodes added via [`add_node`][Self::add_node] *after* calling this method. pub fn set_allocator_flags(&mut self, flags: GbmBufferFlags) { self.allocator_flags = flags; } /// Add a new GBM device for a given node to the api pub fn add_node(&mut self, node: DrmNode, gbm: GbmDevice) -> Result<(), EGLError> { if self.devices.contains_key(&node) { return Ok(()); } let allocator = GbmAllocator::new(gbm.clone(), self.allocator_flags); self.devices .insert(node, (unsafe { EGLDisplay::new(gbm)? }, allocator)); self.needs_enumeration.store(true, Ordering::SeqCst); Ok(()) } /// Remove a given node from the api pub fn remove_node(&mut self, node: &DrmNode) { if self.devices.remove(node).is_some() { self.needs_enumeration.store(true, Ordering::SeqCst); } } } impl + Renderer, A: AsFd + Clone + 'static> GraphicsApi for GbmGlesBackend { type Device = GbmGlesDevice; type Error = Error; fn enumerate(&self, list: &mut Vec) -> Result<(), Self::Error> { self.needs_enumeration.store(false, Ordering::SeqCst); // remove old stuff list.retain(|renderer| { self.devices .keys() .any(|node| renderer.node.dev_id() == node.dev_id()) }); // add new stuff let new_renderers = self .devices .iter() .filter(|(node, _)| { !list .iter() .any(|renderer| renderer.node.dev_id() == node.dev_id()) }) .map(|(node, (display, gbm))| { let renderer = if let Some(factory) = self.factory.as_ref() { factory(display)?.into() } else { let context = EGLContext::new_with_priority(display, self.context_priority.unwrap_or_default()) .map_err(Error::Egl)?; unsafe { GlesRenderer::new(context).map_err(Error::Gl)? }.into() }; Ok(GbmGlesDevice { node: *node, _display: display.clone(), renderer, allocator: Box::new(DmabufAllocator(gbm.clone())), }) }) .flat_map(|x: Result, Error>| match x { Ok(x) => Some(x), Err(x) => { warn!("Skipping GbmDevice: {}", x); None } }) .collect::>>(); list.extend(new_renderers); // but don't replace already initialized renderers Ok(()) } fn needs_enumeration(&self) -> bool { self.needs_enumeration.load(Ordering::Acquire) } fn identifier() -> &'static str { "gbm_gles" } } // TODO: Replace with specialization impl in multigpu/mod once possible impl + Renderer, A: AsFd + Clone + 'static> std::convert::From for MultiError, T> where T::Error: 'static, <::Renderer as RendererSuper>::Error: 'static, { #[inline] fn from(err: GlesError) -> MultiError, T> { MultiError::Render(err) } } /// [`ApiDevice`] of the [`GbmGlesBackend`] pub struct GbmGlesDevice { node: DrmNode, renderer: R, allocator: Box>, _display: EGLDisplay, } impl fmt::Debug for GbmGlesDevice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("GbmGlesDevice") .field("node", &self.node) .field("renderer", &self.renderer) .finish_non_exhaustive() } } impl ApiDevice for GbmGlesDevice { type Renderer = R; fn renderer(&self) -> &Self::Renderer { &self.renderer } fn renderer_mut(&mut self) -> &mut Self::Renderer { &mut self.renderer } fn allocator(&mut self) -> &mut dyn Allocator { self.allocator.as_mut() } fn node(&self) -> &DrmNode { &self.node } } #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] impl ImportEgl for MultiRenderer<'_, '_, GbmGlesBackend, GbmGlesBackend> where A: AsFd + Clone + 'static, R: From + BorrowMut + Renderer + Bind + ImportDma + ImportMem + ImportEgl + ExportMem + 'static, R::TextureId: Clone + Send, { fn bind_wl_display(&mut self, display: &wayland_server::DisplayHandle) -> Result<(), EGLError> { self.render.renderer_mut().bind_wl_display(display) } fn unbind_wl_display(&mut self) { self.render.renderer_mut().unbind_wl_display() } fn egl_reader(&self) -> Option<&EGLBufferReader> { self.render.renderer().egl_reader() } #[profiling::function] fn import_egl_buffer( &mut self, buffer: &wl_buffer::WlBuffer, surface: Option<&crate::wayland::compositor::SurfaceData>, damage: &[Rectangle], ) -> Result { if let Some(dmabuf) = Self::try_import_egl(self.render.renderer_mut(), buffer) .ok() .or_else(|| { self.target .as_mut() .and_then(|renderer| Self::try_import_egl(renderer.device.renderer_mut(), buffer).ok()) }) .or_else(|| { self.other_renderers .iter_mut() .find_map(|renderer| Self::try_import_egl(renderer.renderer_mut(), buffer).ok()) }) { let texture = MultiTexture::from_surface(surface, dmabuf.size(), dmabuf.format()); let texture_ref = texture.0.clone(); let res = self.import_dmabuf_internal(&dmabuf, texture, Some(damage)); if res.is_ok() { if let Some(surface) = surface { surface.data_map.insert_if_missing_threadsafe(|| texture_ref); } } return res; } Err(MultigpuError::DeviceMissing) } } #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] impl MultiRenderer<'_, '_, GbmGlesBackend, GbmGlesBackend> where A: AsFd + Clone + 'static, R: From + BorrowMut + Renderer + ImportDma + ImportMem + ImportEgl + ExportMem + 'static, { #[profiling::function] fn try_import_egl( renderer: &mut R, buffer: &wl_buffer::WlBuffer, ) -> Result, GbmGlesBackend>> { if !renderer .borrow_mut() .extensions .iter() .any(|ext| ext == "GL_OES_EGL_image") { return Err(MultigpuError::Render(GlesError::GLExtensionNotSupported(&[ "GL_OES_EGL_image", ]))); } if renderer.egl_reader().is_none() { return Err(MultigpuError::Render(GlesError::EGLBufferAccessError( crate::backend::egl::BufferAccessError::NotManaged(crate::backend::egl::EGLError::BadDisplay), ))); } unsafe { renderer .borrow_mut() .egl_context() .make_current() .map_err(GlesError::from) .map_err(MultigpuError::Render)? }; let egl = renderer .egl_reader() .as_ref() .unwrap() .egl_buffer_contents(buffer) .map_err(GlesError::EGLBufferAccessError) .map_err(MultigpuError::Render)?; renderer .borrow_mut() .egl_context() .display() .create_dmabuf_from_image(egl.image(0).unwrap(), egl.size, egl.y_inverted) .map_err(GlesError::BindBufferEGLError) .map_err(MultigpuError::Render) } }