diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 6bc72613..279cacb7 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -12,7 +12,7 @@ use smithay::{ gbm::{GbmAllocator, GbmBufferFlags}, }, drm::{DrmDeviceFd, DrmNode, NodeType}, - egl::{context::ContextPriority, EGLContext}, + egl::{context::ContextPriority, EGLContext, EGLDevice, EGLDisplay}, input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{glow::GlowRenderer, multigpu::GpuManager}, @@ -54,6 +54,8 @@ pub struct KmsState { pub drm_devices: HashMap, pub input_devices: HashMap, pub primary_node: Option, + // Mesa llvmpipe renderer, if supported and there are no render nodes + pub software_renderer: Option, pub api: GpuManager>, session: LibSeatSession, @@ -78,6 +80,18 @@ pub fn init_backend( info!("Using {} as primary gpu for rendering.", primary); } + let software_renderer = if primary.is_none() { + match software_renderer() { + Ok(renderer) => Some(renderer), + Err(err) => { + error!(?err, "Failed to initialize software EGL renderer."); + None + } + } + } else { + None + }; + // watch for gpu events let udev_dispatcher = init_udev(session.seat(), &event_loop.handle()) .context("Failed to initialize udev connection")?; @@ -107,6 +121,7 @@ pub fn init_backend( drm_devices: HashMap::new(), input_devices: HashMap::new(), primary_node: primary, + software_renderer, api: GpuManager::new(GbmGlowBackend::new()).context("Failed to initialize gpu backend")?, session, @@ -193,6 +208,22 @@ fn determine_primary_gpu(seat: String) -> Option { } } +/// Create `GlowRenderer` for `EGL_MESA_device_software` device, if present +fn software_renderer() -> anyhow::Result { + let mut devices = EGLDevice::enumerate()?; + let device = devices + .find(|device| { + device + .extensions() + .iter() + .any(|ext| ext == "EGL_MESA_device_software") + }) + .ok_or_else(|| anyhow::anyhow!("no EGL device found with `EGL_MESA_device_software`"))?; + let display = unsafe { EGLDisplay::new(device)? }; + let context = EGLContext::new(&display)?; + unsafe { Ok(GlowRenderer::new(context)?) } +} + fn init_udev( seat: String, evlh: &LoopHandle<'static, State>, diff --git a/src/state.rs b/src/state.rs index 5fb0070c..58a5a886 100644 --- a/src/state.rs +++ b/src/state.rs @@ -389,23 +389,28 @@ impl BackendData { /// Get an offscreen renderer for screen capture / screenshot rendering /// /// `kms_node_cb` callback use used to determine nodes to render with when using kms backend. - /// Panics if this returns `None`. + /// If this returns `None`, it will attempt to use llvmpipe, then panic if no renderer is + /// found. pub fn offscreen_renderer, F: FnOnce(&mut KmsState) -> Option>( &mut self, kms_node_cb: F, ) -> Result { match self { BackendData::Kms(kms) => { - let KmsNodes { - render_node, - target_node, - copy_format, - } = kms_node_cb(kms).expect("No Software Rendering").into(); - Ok(RendererRef::GlMulti(kms.api.renderer( - &render_node, - &target_node, - copy_format, - )?)) + if let Some(nodes) = kms_node_cb(kms) { + let nodes = nodes.into(); + Ok(RendererRef::GlMulti(kms.api.renderer( + &nodes.render_node, + &nodes.target_node, + nodes.copy_format, + )?)) + } else { + Ok(RendererRef::Glow( + kms.software_renderer + .as_mut() + .expect("No Software Rendering"), + )) + } } BackendData::Winit(winit) => Ok(RendererRef::Glow(winit.backend.renderer())), BackendData::X11(x11) => Ok(RendererRef::Glow(&mut x11.renderer)),