Use EGL_MESA_device_software as fallback for screen capture

Previously, screenshots and screencopy panicked if no DRM render nodes
are present.

Instead, create a `GlowRenderer` using llvmpipe, if available. This
should work as a fallback until pixman is integrated.
This commit is contained in:
Ian Douglas Scott 2024-08-05 11:32:42 -07:00 committed by Ian Douglas Scott
parent 87020c79ba
commit 5537fa4822
2 changed files with 48 additions and 12 deletions

View file

@ -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<DrmNode, Device>,
pub input_devices: HashMap<String, input::Device>,
pub primary_node: Option<DrmNode>,
// Mesa llvmpipe renderer, if supported and there are no render nodes
pub software_renderer: Option<GlowRenderer>,
pub api: GpuManager<GbmGlowBackend<DrmDeviceFd>>,
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<DrmNode> {
}
}
/// Create `GlowRenderer` for `EGL_MESA_device_software` device, if present
fn software_renderer() -> anyhow::Result<GlowRenderer> {
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>,

View file

@ -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<N: Into<KmsNodes>, F: FnOnce(&mut KmsState) -> Option<N>>(
&mut self,
kms_node_cb: F,
) -> Result<RendererRef, GlMultiError> {
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)),