srdusr
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock25
-rw-r--r--crates/wayland/src/udev/gpu.rs235
-rw-r--r--crates/wayland/src/udev/mod.rs7
-rw-r--r--crates/wayland/src/udev/platform.rs34
-rw-r--r--crates/wayland/src/udev/render.rs29
-rw-r--r--crates/wayland/src/udev/session.rs45
6 files changed, 320 insertions, 55 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ce2155d..41db1b5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -826,6 +826,28 @@ dependencies = [
]
[[package]]
+name = "gbm"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a"
+dependencies = [
+ "bitflags 2.13.0",
+ "drm",
+ "drm-fourcc",
+ "gbm-sys",
+ "libc",
+]
+
+[[package]]
+name = "gbm-sys"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2040,6 +2062,7 @@ dependencies = [
"atomic_float",
"bitflags 2.13.0",
"calloop 0.14.4",
+ "cc",
"cgmath",
"cursor-icon",
"downcast-rs",
@@ -2048,6 +2071,7 @@ dependencies = [
"drm-fourcc",
"encoding_rs",
"errno",
+ "gbm",
"gl_generator",
"indexmap",
"input",
@@ -2055,6 +2079,7 @@ dependencies = [
"libloading",
"libseat",
"pixman",
+ "pkg-config",
"profiling",
"rand",
"rustix 1.1.4",
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