fix(kms): gate overlay scanout per-element by buffer GPU node

This commit is contained in:
Ericky Dos Santos 2026-06-29 10:25:28 -04:00 committed by Jacob Kauffmann
parent 9d52653d5c
commit 012c77ec81
2 changed files with 95 additions and 42 deletions

View file

@ -8,7 +8,7 @@ use crate::{
init_shaders, output_elements, init_shaders, output_elements,
}, },
config::ScreenFilter, config::ScreenFilter,
shell::Shell, shell::{Shell, element::surface::ScanoutTargetNodeGuard},
state::SurfaceDmabufFeedback, state::SurfaceDmabufFeedback,
utils::prelude::*, utils::prelude::*,
wayland::handlers::{ wayland::handlers::{
@ -1057,21 +1057,24 @@ impl SurfaceThreadState {
vrr = has_active_fullscreen; vrr = has_active_fullscreen;
} }
let mut elements = output_elements( let mut elements = {
Some(&render_node), let _scanout_node = ScanoutTargetNodeGuard::new(Some(self.target_node));
&mut renderer, output_elements(
&self.shell, Some(&render_node),
self.clock.now(), &mut renderer,
self.mirroring.as_ref().unwrap_or(&self.output), &self.shell,
CursorMode::All, self.clock.now(),
#[cfg(not(feature = "debug"))] self.mirroring.as_ref().unwrap_or(&self.output),
None, CursorMode::All,
#[cfg(feature = "debug")] #[cfg(not(feature = "debug"))]
Some((&self.egui, &self.timings)), None,
) #[cfg(feature = "debug")]
.map_err(|err| { Some((&self.egui, &self.timings)),
anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) )
})?; .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) if vrr && fullscreen_drives_refresh_rate && !self.timings.past_min_render_time(&self.clock)
{ {

View file

@ -6,6 +6,7 @@ use crate::{
}; };
use std::{ use std::{
borrow::Cow, borrow::Cow,
cell::Cell,
sync::{ sync::{
Mutex, Mutex,
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
@ -14,11 +15,17 @@ use std::{
}; };
use smithay::{ use smithay::{
backend::renderer::{ backend::{
ImportAll, Renderer, drm::DrmNode,
element::{ renderer::{
AsRenderElements, Kind, RenderElementStates, ImportAll, Renderer,
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree}, element::{
AsRenderElements, Kind, RenderElementStates,
surface::{
KindEvaluation, WaylandSurfaceRenderElement, render_elements_from_surface_tree,
},
},
utils::RendererSurfaceStateUserData,
}, },
}, },
desktop::{ desktop::{
@ -49,6 +56,7 @@ use smithay::{
SubsurfaceCachedState, SurfaceData, TraversalAction, get_parent, with_states, SubsurfaceCachedState, SurfaceData, TraversalAction, get_parent, with_states,
with_surface_tree_downward, with_surface_tree_downward,
}, },
dmabuf::get_dmabuf,
seat::WaylandFocus, seat::WaylandFocus,
shell::xdg::{ shell::xdg::{
SurfaceCachedState, ToplevelCachedState, ToplevelSurface, XdgToplevelSurfaceData, 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<Option<DrmNode>> = 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<DrmNode>);
impl ScanoutTargetNodeGuard {
pub fn new(node: Option<DrmNode>) -> 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<DrmNode> {
let surface_state = data.data_map.get::<RendererSurfaceStateUserData>()?;
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<bool>) -> 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)] #[derive(Debug, Clone, PartialEq, Hash, Eq)]
pub struct CosmicSurface(pub Window); pub struct CosmicSurface(pub Window);
@ -836,7 +904,7 @@ impl CosmicSurface {
location + offset, location + offset,
scale, scale,
alpha, alpha,
FRAME_TIME_FILTER, scanout_kind_eval(None),
) )
}) })
.collect() .collect()
@ -868,16 +936,7 @@ impl CosmicSurface {
location, location,
scale, scale,
alpha, alpha,
scanout_override scanout_kind_eval(scanout_override),
.map(|val| {
if val {
Kind::ScanoutCandidate
} else {
Kind::Unspecified
}
.into()
})
.unwrap_or(FRAME_TIME_FILTER),
) )
} }
WindowSurface::X11(surface) => { WindowSurface::X11(surface) => {
@ -891,16 +950,7 @@ impl CosmicSurface {
location, location,
scale, scale,
alpha, alpha,
scanout_override scanout_kind_eval(scanout_override),
.map(|val| {
if val {
Kind::ScanoutCandidate
} else {
Kind::Unspecified
}
.into()
})
.unwrap_or(FRAME_TIME_FILTER),
) )
} }
} }