diff options
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/wayland/src/udev/gpu.rs | 235 | ||||
| -rw-r--r-- | crates/wayland/src/udev/mod.rs | 7 | ||||
| -rw-r--r-- | crates/wayland/src/udev/platform.rs | 34 | ||||
| -rw-r--r-- | crates/wayland/src/udev/render.rs | 29 | ||||
| -rw-r--r-- | crates/wayland/src/udev/session.rs | 45 |
5 files changed, 295 insertions, 55 deletions
diff --git a/crates/wayland/src/udev/gpu.rs b/crates/wayland/src/udev/gpu.rs index 5b21171..27a73f4 100644 --- a/crates/wayland/src/udev/gpu.rs +++ b/crates/wayland/src/udev/gpu.rs @@ -1,76 +1,139 @@ -//! Opt-in (`SRDWM_GPU=1`) capability probe for a real GBM+EGL GPU render -//! path on the udev backend - see [`probe`]'s own doc comment for exactly -//! what this does and does not do yet. +//! Opt-in (`SRDWM_GPU=1`) GBM+EGL+`DrmCompositor` GPU render path for the +//! udev backend - see [`probe`]'s own doc comment for exactly what this +//! does and does not do yet (Phase 2: one output, clear-color only, no +//! window content/decorations, no VT-switch support - see the plan this +//! was built from, `snappy-percolating-boole.md`, for the full scoping). use std::os::fd::{AsFd, OwnedFd}; -use smithay::backend::egl::{EGLDevice, EGLDisplay}; -use smithay::reexports::gbm::Device as GbmDevice; +use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}; +use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter; +use smithay::backend::drm::output::{DrmOutput, DrmOutputManager, DrmOutputRenderElements}; +use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmDeviceNotifier}; +use smithay::backend::egl::{EGLContext, EGLDevice, EGLDisplay}; +use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement; +use smithay::backend::renderer::gles::GlesRenderer; +use smithay::output::Output; +use smithay::reexports::drm::buffer::DrmFourcc; +use smithay::reexports::drm::control::{connector, crtc, Mode as DrmMode}; use smithay::reexports::rustix; +use smithay::utils::DeviceFd; use super::Card; -/// Marker returned by a successful probe. Carries no data yet - see -/// [`probe`]'s own doc comment for why - this exists purely to answer, -/// definitively and safely, "does GBM+EGL actually initialize on this exact -/// machine's DRM device" (logged from inside `probe` itself) as a plain -/// `bool`-shaped `Option` a future caller can match on once there's -/// something to actually do with a `Some`. -pub(crate) struct GpuProbe; +/// The concrete render-element type this phase's (empty) element list uses +/// - see [`GpuContext::initialize_output`]'s own doc comment for why the +/// actual choice of `E` doesn't matter yet, since no real elements are +/// passed through it. +type GpuElement = MemoryRenderBufferRenderElement<GlesRenderer>; -/// Attempts GBM device creation plus an EGL display/device query against -/// `card`'s DRM fd, gated behind `SRDWM_GPU=1` (unset - the default -- -/// skips this entirely: zero cost, zero risk, identical to every session -/// before this one). Returns `None` on *any* failure at any step, each -/// logged with which step failed and why, so a machine without working -/// KMS+3D driver support (or a VM with only dumb-buffer scanout - this -/// backend's own long-standing default target, see `udev/mod.rs`'s module -/// doc comment) gets one clear, harmless log line instead of anything -/// touching the actual rendering/modesetting this backend already does. +/// One head successfully driven through `DrmOutputManager::initialize_output` +/// - see [`GpuContext::initialize_output`]'s own doc comment. +pub(crate) type GpuOutput = DrmOutput< + GbmAllocator<DrmDeviceFd>, + GbmFramebufferExporter<DrmDeviceFd>, + Option<smithay::desktop::utils::OutputPresentationFeedback>, + DrmDeviceFd, +>; + +/// A `DrmOutputManager` instantiated with this backend's concrete +/// allocator/exporter/fd types - spelled out once here so every later +/// field/signature that needs it doesn't have to repeat all four type +/// parameters. +pub(crate) type GpuOutputManager = DrmOutputManager< + GbmAllocator<DrmDeviceFd>, + GbmFramebufferExporter<DrmDeviceFd>, + Option<smithay::desktop::utils::OutputPresentationFeedback>, + DrmDeviceFd, +>; + +/// Everything a successful [`probe`] built: a real GLES renderer bound to +/// this hardware's GPU, and a `DrmOutputManager` ready for +/// `initialize_output` calls. Nothing here is wired into `render_udev_frame` +/// yet - that is the next step in the plan this was built from, done +/// separately once this compiles and the log confirms it actually +/// initializes on this hardware. +pub(crate) struct GpuContext { + pub(crate) renderer: GlesRenderer, + pub(crate) output_manager: GpuOutputManager, + /// The one head [`GpuContext::initialize_output`] was successfully + /// called for, if any - Phase 2 of the plan this was built from only + /// ever targets a single head, see that call site (`udev/platform.rs`) + /// for which one and why. `render_udev_frame` (`udev/render.rs`) + /// checks this to decide whether a given head renders through here or + /// through the existing legacy Pixman path. + pub(crate) output: Option<(crtc::Handle, GpuOutput)>, +} + +/// Reasonable, widely-supported scanout formats to try, most-preferred +/// first - the same two `anvil` tries before falling back further, +/// without that example's own optional 10-bit path (`ANVIL_DISABLE_10BIT` +/// is anvil-specific and this backend has no equivalent theme/config +/// concept for it yet). +const COLOR_FORMATS: [DrmFourcc; 2] = [DrmFourcc::Argb8888, DrmFourcc::Xrgb8888]; + +/// Attempts the full GBM+EGL+GLES+`DrmDevice`+`DrmOutputManager` chain +/// against `card`'s DRM fd, gated behind `SRDWM_GPU=1` (unset - the +/// default - skips this entirely: zero cost, zero risk, identical to +/// every session before this one). Returns `None` on *any* failure at any +/// step, each logged with which step failed and why, so a machine without +/// working KMS+3D driver support (or a VM with only dumb-buffer scanout -- +/// this backend's own long-standing default target, see `udev/mod.rs`'s +/// module doc comment) gets one clear, harmless log line instead of +/// anything touching the actual rendering/modesetting this backend already +/// does. +/// +/// `DrmDevice::new`'s own `disable_connectors` parameter (passed `false` +/// here, matching the existing legacy path's own connector handling) is +/// *not* an atomic-vs-legacy switch, despite that being an easy assumption +/// - reading smithay's own source (`backend/drm/device/mod.rs`) directly +/// showed `DrmDevice::create_internal` tries atomic capability first and +/// falls back to a `Legacy` internal variant automatically if the driver +/// doesn't support it, both exposed through the one `DrmDevice` type via +/// its own `is_atomic()` query - logged here, not assumed, since Phase 1's +/// probe never got far enough to find this out empirically on this +/// specific machine's `i915` driver. /// -/// Deliberately does **not** yet create an `EGLContext`, a `GlesRenderer`, -/// or touch scanout at all. Reading smithay's own reference compositor -/// (`anvil/src/udev.rs`) confirmed it wires GBM+EGL rendering together with -/// *atomic*-KMS scanout as one unit via `DrmCompositor`, not as a renderer -/// swapped into the existing legacy `set_crtc`/`page_flip` flip loop this -/// backend uses today. Adopting `DrmCompositor` is real, separate, -/// larger-scoped work than "swap the renderer" - it replaces the same -/// `UdevHead` mode-set/flip machinery this session's own VT-switch fixes -/// (`register_session_notifier`'s `ActivateSession` arm, `copy_and_flip`'s -/// retry backoff) live in, and needs its own plan. This probe answers the -/// *first* question - does the hardware even support it at all - safely, -/// before that larger integration is scoped and attempted. -pub(crate) fn probe(card: &Card) -> Option<GpuProbe> { +/// Deliberately does **not** yet call `initialize_output` for any specific +/// head, or touch `render_udev_frame` at all - this function's whole job +/// is building a ready-to-use `GpuContext`; which head (if any) actually +/// gets driven through it is a per-session, per-head decision the caller +/// (`udev/platform.rs`'s startup path) makes once this succeeds. +pub(crate) fn probe(card: &Card) -> Option<(GpuContext, DrmDeviceNotifier)> { if std::env::var("SRDWM_GPU").as_deref() != Ok("1") { return None; } - // GBM wants its own fd, not the one this backend's KMS control-plane - // ioctls (`set_crtc`, `page_flip`, property gets/sets) already share - // via `Rc<Card>` - duplicated here rather than reusing `card`'s own - // fd directly, both because `gbm::Device`'s `EGLNativeDisplay` impl - // needs `T: Send + 'static` (an `Rc`-shared `Card` is neither) and - // because giving GBM a fd genuinely separate from the control-plane - // one matches real compositor practice, not just this one's - // convenience. + // One `DrmDeviceFd` used for everything below - GBM, EGL, and the + // `DrmDevice` itself all share this single duped fd (cheaply cloned, + // `DrmDeviceFd` is `Arc`-backed), matching `anvil`'s own construction + // rather than Phase 1's separate raw-fd dup for GBM alone. Duped from + // `card`'s own fd (not reused directly) both because `gbm::Device`'s + // `EGLNativeDisplay` impl and `DrmDeviceFd` itself need `Send + + // 'static` (an `Rc`-shared `Card` is neither) and because keeping this + // whole GPU path on a fd genuinely separate from the existing legacy + // control-plane one (`Card`, still driving every non-GPU head) + // guarantees this experimental path can never contend with or disrupt + // it at the fd level. let dup_fd: OwnedFd = match rustix::io::dup(card.as_fd()) { Ok(fd) => fd, Err(e) => { - log::warn!("udev: SRDWM_GPU=1 but failed to dup the DRM fd for GBM: {e} - staying on the software (Pixman) render path"); + log::warn!("udev: SRDWM_GPU=1 but failed to dup the DRM fd: {e} - staying on the software (Pixman) render path"); return None; } }; - let gbm = match GbmDevice::new(dup_fd) { + let fd = DrmDeviceFd::new(DeviceFd::from(dup_fd)); + let gbm = match GbmDevice::new(fd.clone()) { Ok(gbm) => gbm, Err(e) => { log::warn!("udev: SRDWM_GPU=1 but GBM device creation failed: {e} - staying on the software (Pixman) render path"); return None; } }; - // Safety: `gbm` owns its own fd (the dup above), stays alive for as - // long as this function needs it, and is a real GBM device - exactly - // what `EGLDisplay::new`'s own safety contract (a native display handle - // that outlives the `EGLDisplay`) requires. - let display = match unsafe { EGLDisplay::new(gbm) } { + // Safety: `gbm` owns its own fd (via the shared `DrmDeviceFd` above), + // stays alive for as long as this function needs it, and is a real GBM + // device - exactly what `EGLDisplay::new`'s own safety contract (a + // native display handle that outlives the `EGLDisplay`) requires. + let display = match unsafe { EGLDisplay::new(gbm.clone()) } { Ok(d) => d, Err(e) => { log::warn!("udev: SRDWM_GPU=1 but EGL display creation failed: {e} - staying on the software (Pixman) render path"); @@ -89,9 +152,77 @@ pub(crate) fn probe(card: &Card) -> Option<GpuProbe> { return None; } let render_node = egl_device.try_get_render_node().ok().flatten(); + let context = match EGLContext::new(&display) { + Ok(c) => c, + Err(e) => { + log::warn!("udev: SRDWM_GPU=1 but EGL context creation failed: {e} - staying on the software (Pixman) render path"); + return None; + } + }; + // Safety: `context` was just created above, is not shared with + // anything else, and this function does not touch it again after this + // call - exactly what `GlesRenderer::new`'s own safety contract (sole, + // current ownership of the context being wrapped) requires. + let renderer = match unsafe { GlesRenderer::new(context) } { + Ok(r) => r, + Err(e) => { + log::warn!("udev: SRDWM_GPU=1 but GLES renderer creation failed: {e} - staying on the software (Pixman) render path"); + return None; + } + }; + let (drm_device, notifier) = match DrmDevice::new(fd, false) { + Ok(pair) => pair, + Err(e) => { + log::warn!("udev: SRDWM_GPU=1 but DrmDevice creation failed: {e} - staying on the software (Pixman) render path"); + return None; + } + }; log::info!( - "udev: SRDWM_GPU=1 - GBM+EGL capability confirmed on this hardware (render node: {render_node:?}). \ - Full GPU rendering isn't wired up yet (see gpu::probe's own doc comment); still rendering via Pixman for now." + "udev: SRDWM_GPU=1 - DrmDevice created (render node: {render_node:?}, atomic modesetting: {}).", + drm_device.is_atomic() ); - Some(GpuProbe) + let allocator = GbmAllocator::new(gbm.clone(), GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT); + let exporter = GbmFramebufferExporter::new(gbm.clone(), render_node); + let render_formats = renderer.egl_context().dmabuf_render_formats().iter().copied(); + let output_manager = DrmOutputManager::new(drm_device, allocator, exporter, Some(gbm), COLOR_FORMATS, render_formats); + log::info!( + "udev: SRDWM_GPU=1 - GBM+EGL+GLES+DrmOutputManager all initialized successfully on this hardware. \ + No output is being driven through it yet (see gpu::probe's own doc comment); every head still renders via Pixman for now." + ); + Some((GpuContext { renderer, output_manager, output: None }, notifier)) +} + +impl GpuContext { + /// Drives exactly one head (`crtc`/`mode`/`connector`/`output`, the + /// same values `udev/drm.rs`'s `bring_up_head` already resolved for + /// this head's *existing* legacy `UdevHead`) through this context's + /// `DrmOutputManager`, returning the resulting `GpuOutput` on success. + /// + /// `elements` is always empty for this phase (Phase 2 of the plan this + /// was built from renders a plain clear color only, no window content/ + /// decorations/cursor yet) - so the concrete choice of `E` here + /// ([`GpuElement`], a plain `MemoryRenderBufferRenderElement`) is + /// arbitrary; nothing about it is load-bearing until a later phase + /// actually pushes real elements through this same call shape. + /// + /// Returns `false` and leaves `self.output` untouched on failure + /// (logged) - same fallback contract as [`probe`] itself: a head this + /// fails for simply never gets an entry in `self.output_manager`'s + /// internal map, so it can still be driven through the existing + /// legacy Pixman path exactly as if `SRDWM_GPU` had never been set for + /// that particular head. + pub(crate) fn initialize_output(&mut self, crtc: crtc::Handle, mode: DrmMode, connector: connector::Handle, output: &Output) -> bool { + let elements: DrmOutputRenderElements<GlesRenderer, GpuElement> = DrmOutputRenderElements::default(); + match self.output_manager.initialize_output::<GlesRenderer, GpuElement>(crtc, mode, &[connector], output, None, &mut self.renderer, &elements) { + Ok(gpu_output) => { + log::info!("udev: SRDWM_GPU=1 - output initialized through DrmOutputManager for crtc {crtc:?}"); + self.output = Some((crtc, gpu_output)); + true + } + Err(e) => { + log::warn!("udev: SRDWM_GPU=1 but initialize_output failed for crtc {crtc:?}: {e:?} - this head stays on the software (Pixman) render path"); + false + } + } + } } diff --git a/crates/wayland/src/udev/mod.rs b/crates/wayland/src/udev/mod.rs index b5529c4..d22666d 100644 --- a/crates/wayland/src/udev/mod.rs +++ b/crates/wayland/src/udev/mod.rs @@ -238,6 +238,13 @@ pub(crate) struct UdevState { /// `None` before the first frame, same reasoning as `last_rendered_ /// workspace` above (renders fully regardless). pub(crate) last_rendered_layout: Option<u64>, + /// Set only when `SRDWM_GPU=1` and `gpu::probe` succeeds on this + /// hardware - see that function's own doc comment for exactly what + /// it does and does not do yet. `None` (the default, every session + /// that doesn't set the env var, and every one where the probe fails) + /// means every head renders through `renderer`/`PixmanRenderer` above, + /// completely unaffected by this field's existence. + pub(crate) gpu: Option<gpu::GpuContext>, } impl UdevState { diff --git a/crates/wayland/src/udev/platform.rs b/crates/wayland/src/udev/platform.rs index 3f25c2e..6826cde 100644 --- a/crates/wayland/src/udev/platform.rs +++ b/crates/wayland/src/udev/platform.rs @@ -1,6 +1,6 @@ use super::*; use super::drm::{bring_up_head, pick_crtc, probe_connected}; -use super::session::{register_drm_fd, register_libinput, register_session_notifier, register_udev_monitor}; +use super::session::{register_drm_fd, register_gpu_drm_notifier, register_libinput, register_session_notifier, register_udev_monitor}; pub struct UdevPlatform { event_loop: EventLoop<'static, CompState>, @@ -39,8 +39,15 @@ impl UdevPlatform { // Opt-in only (`SRDWM_GPU=1`, unset by default) - see `gpu::probe`'s // own doc comment for exactly what this does and does not do yet. // A no-op unless that variable is set, so this line changes nothing - // about any session that doesn't set it. - let _gpu_probe = super::gpu::probe(&card); + // about any session that doesn't set it. `gpu_notifier` is + // registered as its own calloop event source further down + // (alongside `register_drm_fd`'s own registration for the + // existing legacy heads); `gpu_context` is stored on `UdevState` + // below and consulted by `render_udev_frame`. + let (mut gpu_context, gpu_notifier) = match super::gpu::probe(&card) { + Some((ctx, notifier)) => (Some(ctx), Some(notifier)), + None => (None, None), + }; // Every connected connector becomes a head, laid out left-to-right. let connected = probe_connected(&card)?; @@ -94,6 +101,15 @@ impl UdevPlatform { // `WaylandPlatform::connect` for why this isn't optional. smithay::wayland::output::OutputManagerState::new_with_xdg_output::<CompState>(&display_handle); + // Phase 2 of the GPU-rendering plan (`gpu.rs`'s own module doc + // comment) only ever targets one head - the first, same as the + // pointer-centring choice just above - not every head at once. + // A no-op whenever `gpu_context` is `None` (every session that + // doesn't set `SRDWM_GPU=1`, or where `gpu::probe` itself failed). + if let Some(ctx) = gpu_context.as_mut() { + ctx.initialize_output(first.crtc, first.mode, first.connector, &first.output); + } + let compositor_state = CompositorState::new::<CompState>(&display_handle); let xdg_shell_state = XdgShellState::new::<CompState>(&display_handle); let xdg_decoration_state = XdgDecorationState::new::<CompState>(&display_handle); @@ -136,6 +152,7 @@ impl UdevPlatform { disabled_connectors: std::collections::HashSet::new(), last_rendered_workspace: None, last_rendered_layout: None, + gpu: gpu_context, }; let mut state = CompState { @@ -275,6 +292,17 @@ impl UdevPlatform { let handle = event_loop.handle(); register_drm_fd(&handle, &card)?; + // Only when `SRDWM_GPU=1` and `gpu::probe` succeeded - see + // `register_gpu_drm_notifier`'s own doc comment. A failure here + // (this specific registration, not the probe itself) is logged, + // not fatal: the GPU head just never gets a `frame_submitted()` + // call and its swapchain eventually stalls, no worse than the + // probe never having succeeded at all. + if let Some(gpu_notifier) = gpu_notifier { + if let Err(e) = register_gpu_drm_notifier(&handle, gpu_notifier) { + log::warn!("udev: SRDWM_GPU=1 but failed to register the GPU DRM notifier: {e}"); + } + } let libinput_handle = register_libinput(&handle, &session, &seat_name)?; register_session_notifier(&handle, notifier, libinput_handle)?; if let Err(e) = register_udev_monitor(&handle, &seat_name) { diff --git a/crates/wayland/src/udev/render.rs b/crates/wayland/src/udev/render.rs index d8f0003..f9dc739 100644 --- a/crates/wayland/src/udev/render.rs +++ b/crates/wayland/src/udev/render.rs @@ -212,6 +212,35 @@ impl CompState { let origin = self.udev.as_ref().map(|u| u.heads[index].location).unwrap_or_default(); let Some(udev) = self.udev.as_mut() else { return }; + let head_crtc = udev.heads[index].crtc; + // Phase 2 of the GPU-rendering plan (`gpu.rs`'s own module doc + // comment): the one head `initialize_output` was called for + // (if `SRDWM_GPU=1` and everything up to that point succeeded) + // renders a plain clear color through the real GBM+EGL+ + // DrmCompositor pipeline instead of anything below - no + // window content, decorations, or cursor yet, deliberately + // (see that module's own doc comment for why). Every other + // head, and this same head whenever `udev.gpu`/its `output` + // is `None`, falls straight through to the existing, + // untouched Pixman path unchanged. + if let Some(gpu) = udev.gpu.as_mut() { + if let Some((gpu_crtc, gpu_output)) = gpu.output.as_mut() { + if *gpu_crtc == head_crtc { + let elements: [smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement<smithay::backend::renderer::gles::GlesRenderer>; 0] = []; + let clear_color = [0.05, 0.05, 0.08, 1.0]; + match gpu_output.render_frame(&mut gpu.renderer, &elements, clear_color, smithay::backend::drm::compositor::FrameFlags::DEFAULT) { + Ok(res) if !res.is_empty => match gpu_output.queue_frame(None) { + Ok(()) => {} + Err(e) => log::warn!("udev: SRDWM_GPU=1 queue_frame failed for crtc {head_crtc:?}: {e:?}"), + }, + Ok(_) => {} + Err(e) => log::warn!("udev: SRDWM_GPU=1 render_frame failed for crtc {head_crtc:?}: {e:?}"), + } + continue; + } + } + } + let head = &mut udev.heads[index]; let back = 1 - head.front; diff --git a/crates/wayland/src/udev/session.rs b/crates/wayland/src/udev/session.rs index 196740b..a45cccd 100644 --- a/crates/wayland/src/udev/session.rs +++ b/crates/wayland/src/udev/session.rs @@ -38,6 +38,51 @@ pub(crate) fn register_drm_fd(handle: &LoopHandle<'static, CompState>, card: &Rc Ok(()) } +/// Registers the `DrmDeviceNotifier` [`gpu::probe`](super::gpu::probe) +/// returns on success as its own calloop event source - a real, +/// independent `DrmDevice`/fd from the legacy one [`register_drm_fd`] +/// above already watches, so this is a second, parallel registration, not +/// a replacement. Only called from `udev/platform.rs`'s startup path when +/// `SRDWM_GPU=1` and the probe actually succeeded; a no-op (never called +/// at all) otherwise. +/// +/// `DrmEvent::VBlank` marks the matching `GpuOutput` (if any - `udev.gpu` +/// might still be mid-construction, or the crtc might not be this +/// backend's GPU-driven one) as having had its frame actually scanned +/// out, via `frame_submitted()` - required by `DrmOutput::queue_frame`'s +/// own doc comment (see `render_udev_frame`'s GPU branch), or the +/// underlying swapchain eventually runs out of buffers. `DrmEvent::Error` +/// is logged, not treated as fatal - matching every other error-handling +/// choice in this GPU path, which always prefers "log and keep going" over +/// tearing down the whole session for an experimental, opt-in feature. +pub(crate) fn register_gpu_drm_notifier(handle: &LoopHandle<'static, CompState>, notifier: smithay::backend::drm::DrmDeviceNotifier) -> PlatformResult<()> { + // Fully qualified, not the bare `DrmEvent` this module's own `use + // super::*` already brings in - that name is `drm::control::Event` + // (the legacy raw drm-rs event type `register_drm_fd` above reads via + // `card.receive_events()`, with a `PageFlip` variant), a different + // type from smithay's own higher-level `backend::drm::DrmEvent` (with + // `Vblank`/`Error` variants) this notifier actually produces. + handle + .insert_source(notifier, move |event, _metadata, data: &mut CompState| match event { + smithay::backend::drm::DrmEvent::VBlank(crtc) => { + if let Some(udev) = data.udev.as_mut() { + if let Some(gpu) = udev.gpu.as_ref() { + if let Some((gpu_crtc, gpu_output)) = gpu.output.as_ref() { + if *gpu_crtc == crtc { + if let Err(e) = gpu_output.frame_submitted() { + log::warn!("udev: SRDWM_GPU=1 frame_submitted failed for crtc {crtc:?}: {e:?}"); + } + } + } + } + } + } + smithay::backend::drm::DrmEvent::Error(e) => log::warn!("udev: SRDWM_GPU=1 DrmDevice error: {e:?}"), + }) + .map_err(|e| PlatformError::Other(format!("failed to register GPU DRM notifier: {e}")))?; + Ok(()) +} + /// Registers the real libinput event source and hands back a second, /// reference-counted handle onto the exact same underlying context (`Libinput` /// wraps a `libinput_ref`/`libinput_unref`-counted C pointer - see its own |