srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-03-26 00:26:00 +0200
committersrdusr <[email protected]>2025-03-26 00:26:00 +0200
commit0d3c7ab727b321a36ffea66c461221b14bbc48d8 (patch)
treebdbafccf64086d732af38ca37a005c4c4911ac32 /crates/wayland/src
parentef4bc112b8507880ddfa53ffce45505762384612 (diff)
downloadsrdwm-0d3c7ab727b321a36ffea66c461221b14bbc48d8.tar.gz
srdwm-0d3c7ab727b321a36ffea66c461221b14bbc48d8.zip
Wire real GBM+EGL+DrmCompositor rendering for one head (GPU Phase 2)
Extends the SRDWM_GPU=1 opt-in path (Phase 1, fa0c7f1) from a capability probe into an actual, working GPU render pipeline for exactly one head, per the plan this was built from the plan file. gpu.rs: probe now goes all the way through EGLContext, GlesRenderer, DrmDevice (real DRM device, separate duped fd from the existing legacy Card), and DrmOutputManager construction, returning both a GpuContext and its DrmDeviceNotifier on success. GpuContext::initialize_output drives one crtc/mode/connector through DrmOutputManager, storing the resulting GpuOutput for the render loop to find. Confirmed while reading smithay's own source directly (not assumed): DrmDevice::new's disable_connectors parameter is not an atomic-vs- legacy switch - DrmDevice::create_internal tries atomic capability first and falls back to a Legacy internal variant automatically, exposed via DrmDevice::is_atomic(), logged here rather than assumed. platform.rs: probes at startup, calls initialize_output for the first head only (Phase 2's deliberate scope - see the plan), registers the DrmDeviceNotifier as its own calloop event source alongside (not replacing) the existing legacy DRM-fd registration. render.rs: render_udev_frame's per-head loop checks, before any of the existing Pixman-specific element-building logic runs, whether this head's crtc matches the GPU context's initialized output; if so, renders a plain clear color through render_frame/queue_frame and continues to the next head, completely bypassing the Pixman path for that head. Every other head, and this same head whenever the GPU context or its output is absent, is entirely unaffected. session.rs: register_gpu_drm_notifier handles DrmEvent::VBlank by calling frame_submitted() on the matching GpuOutput (required per queue_frame's own doc comment, or the swapchain runs out of buffers) and logs DrmEvent::Error without treating it as fatal. Deliberately out of scope for this phase (documented in the plan): window content/decorations/cursor on the GPU head (clear color only), multi-monitor GPU rendering (one head only), and VT-switch pause/ resume for the GPU head specifically (DrmOutputManager's own pause()/ activate() calls are a different API surface from the existing manual set_crtc+DPMS reassertion, and porting that pairing correctly needs its own isolated verification pass). SRDWM_GPU unset (the default) is unaffected: every step above only runs when it's set to "1", and every failure at any step falls back to the existing, untouched Pixman path with a logged reason, same fallback contract Phase 1 already established.
Diffstat (limited to 'crates/wayland/src')
-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
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