diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 62ba7c1f..fa196b87 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -8,7 +8,7 @@ use crate::{ init_shaders, output_elements, }, config::ScreenFilter, - shell::Shell, + shell::{Shell, element::surface::ScanoutTargetNodeGuard}, state::SurfaceDmabufFeedback, utils::prelude::*, wayland::handlers::{ @@ -1057,21 +1057,24 @@ impl SurfaceThreadState { vrr = has_active_fullscreen; } - let mut elements = output_elements( - Some(&render_node), - &mut renderer, - &self.shell, - self.clock.now(), - self.mirroring.as_ref().unwrap_or(&self.output), - CursorMode::All, - #[cfg(not(feature = "debug"))] - None, - #[cfg(feature = "debug")] - Some((&self.egui, &self.timings)), - ) - .map_err(|err| { - anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) - })?; + let mut elements = { + let _scanout_node = ScanoutTargetNodeGuard::new(Some(self.target_node)); + output_elements( + Some(&render_node), + &mut renderer, + &self.shell, + self.clock.now(), + self.mirroring.as_ref().unwrap_or(&self.output), + CursorMode::All, + #[cfg(not(feature = "debug"))] + None, + #[cfg(feature = "debug")] + Some((&self.egui, &self.timings)), + ) + .map_err(|err| { + anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) + })? + }; if vrr && fullscreen_drives_refresh_rate && !self.timings.past_min_render_time(&self.clock) { diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 8495fe30..8b0a606e 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -6,6 +6,7 @@ use crate::{ }; use std::{ borrow::Cow, + cell::Cell, sync::{ Mutex, atomic::{AtomicBool, Ordering}, @@ -14,11 +15,17 @@ use std::{ }; use smithay::{ - backend::renderer::{ - ImportAll, Renderer, - element::{ - AsRenderElements, Kind, RenderElementStates, - surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree}, + backend::{ + drm::DrmNode, + renderer::{ + ImportAll, Renderer, + element::{ + AsRenderElements, Kind, RenderElementStates, + surface::{ + KindEvaluation, WaylandSurfaceRenderElement, render_elements_from_surface_tree, + }, + }, + utils::RendererSurfaceStateUserData, }, }, desktop::{ @@ -49,6 +56,7 @@ use smithay::{ SubsurfaceCachedState, SurfaceData, TraversalAction, get_parent, with_states, with_surface_tree_downward, }, + dmabuf::get_dmabuf, seat::WaylandFocus, shell::xdg::{ SurfaceCachedState, ToplevelCachedState, ToplevelSurface, XdgToplevelSurfaceData, @@ -67,6 +75,66 @@ use crate::{ }, }; +thread_local! { + /// The scan-out target [`DrmNode`] of the surface thread that is currently accumulating + /// render elements, if any. + static SCANOUT_TARGET_NODE: Cell> = const { Cell::new(None) }; +} + +/// RAII guard that sets the current thread's scan-out target node and restores the previous value +/// on drop. Hold it only across render-element accumulation so the gate can't outlive a render pass. +/// See [`SCANOUT_TARGET_NODE`]. +pub struct ScanoutTargetNodeGuard(Option); + +impl ScanoutTargetNodeGuard { + pub fn new(node: Option) -> Self { + ScanoutTargetNodeGuard(SCANOUT_TARGET_NODE.with(|n| n.replace(node))) + } +} + +impl Drop for ScanoutTargetNodeGuard { + fn drop(&mut self) { + SCANOUT_TARGET_NODE.with(|n| n.set(self.0)); + } +} + +/// The [`DrmNode`] the surface's currently committed buffer was allocated on, if it is a dmabuf. +fn buffer_node(data: &SurfaceData) -> Option { + let surface_state = data.data_map.get::()?; + let surface_state = surface_state.lock().unwrap(); + surface_state + .buffer() + .and_then(|buffer| get_dmabuf(buffer).ok()) + .and_then(|dmabuf| dmabuf.node()) +} + +/// Build the [`KindEvaluation`] for a window's surface tree. +fn scanout_kind_eval(scanout_override: Option) -> KindEvaluation { + match (scanout_override, SCANOUT_TARGET_NODE.with(|n| n.get())) { + // Forced off. + (Some(false), _) => Kind::Unspecified.into(), + // No node restriction: preserve the previous behaviour exactly. + (Some(true), None) => Kind::ScanoutCandidate.into(), + (None, None) => FRAME_TIME_FILTER, + // Node restriction in effect: only buffers on the scan-out node may be candidates. + (Some(true), Some(node)) => KindEvaluation::Closure(Box::new(move |data| { + if buffer_node(data) == Some(node) { + Kind::ScanoutCandidate + } else { + Kind::Unspecified + } + })), + (None, Some(node)) => KindEvaluation::Closure(Box::new(move |data| { + if buffer_node(data) == Some(node) + { + frame_time_filter_fn(data) + } else { + Kind::Unspecified + } + })), + } +} + #[derive(Debug, Clone, PartialEq, Hash, Eq)] pub struct CosmicSurface(pub Window); @@ -836,7 +904,7 @@ impl CosmicSurface { location + offset, scale, alpha, - FRAME_TIME_FILTER, + scanout_kind_eval(None), ) }) .collect() @@ -868,16 +936,7 @@ impl CosmicSurface { location, scale, alpha, - scanout_override - .map(|val| { - if val { - Kind::ScanoutCandidate - } else { - Kind::Unspecified - } - .into() - }) - .unwrap_or(FRAME_TIME_FILTER), + scanout_kind_eval(scanout_override), ) } WindowSurface::X11(surface) => { @@ -891,16 +950,7 @@ impl CosmicSurface { location, scale, alpha, - scanout_override - .map(|val| { - if val { - Kind::ScanoutCandidate - } else { - Kind::Unspecified - } - .into() - }) - .unwrap_or(FRAME_TIME_FILTER), + scanout_kind_eval(scanout_override), ) } }