Track displayed surface visibility for idle inhibition
This commit is contained in:
1 parent
aa54ab1b2b
commit
6b2974d0fa
11 files changed
+206
-3
No files matched your search
@@ -289,6 +289,9 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.display_presentation.clear();
|
||||||
|
state.refresh_idle_inhibition();
|
||||||
|
|
||||||
libinput_context.suspend();
|
libinput_context.suspend();
|
||||||
tracing::info!("direct session paused");
|
tracing::info!("direct session paused");
|
||||||
}
|
}
|
||||||
@@ -594,6 +597,18 @@ fn open_primary_device(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if retired {
|
||||||
|
if let Some(output) = state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|backend| backend.devices.get(&node))
|
||||||
|
.and_then(|device| device.outputs.get(&crtc))
|
||||||
|
{
|
||||||
|
state.display_presentation.presented(&output.output);
|
||||||
|
}
|
||||||
|
state.refresh_idle_inhibition();
|
||||||
|
}
|
||||||
|
|
||||||
state.record_drm_presentation(node, crtc, metadata.time, metadata.sequence);
|
state.record_drm_presentation(node, crtc, metadata.time, metadata.sequence);
|
||||||
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
|
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
|
||||||
let active = state.direct_backend.as_ref().is_some_and(|backend| backend.active);
|
let active = state.direct_backend.as_ref().is_some_and(|backend| backend.active);
|
||||||
@@ -661,6 +676,7 @@ pub(crate) fn sleep_locked_outputs(state: &mut Ferese) {
|
|||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
cancel_output_timers(&state.loop_handle, output);
|
cancel_output_timers(&state.loop_handle, output);
|
||||||
output.power_off = true;
|
output.power_off = true;
|
||||||
|
state.display_presentation.remove_output(&output.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
@@ -669,6 +685,7 @@ pub(crate) fn sleep_locked_outputs(state: &mut Ferese) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
state.refresh_idle_inhibition();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn wake_locked_outputs(state: &mut Ferese) {
|
pub(crate) fn wake_locked_outputs(state: &mut Ferese) {
|
||||||
@@ -983,8 +1000,10 @@ fn render_output(
|
|||||||
&result.states,
|
&result.states,
|
||||||
Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
||||||
);
|
);
|
||||||
|
let visibility = result.states.clone();
|
||||||
drop(result);
|
drop(result);
|
||||||
output.surface.queue_frame(presentation)?;
|
output.surface.queue_frame(presentation)?;
|
||||||
|
state.display_presentation.queued(&output.output, &visibility);
|
||||||
output.primary_commit = primary_commit;
|
output.primary_commit = primary_commit;
|
||||||
output
|
output
|
||||||
.render_metrics
|
.render_metrics
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
//! Visibility of client content on displayed outputs, independent of capture.
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use smithay::backend::renderer::element::{Id, RenderElementPresentationState, RenderElementStates};
|
||||||
|
use smithay::output::Output;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct OutputPresentation {
|
||||||
|
displayed: HashSet<Id>,
|
||||||
|
queued: Option<HashSet<Id>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct DisplayPresentation {
|
||||||
|
outputs: HashMap<Output, OutputPresentation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visible(states: &RenderElementStates) -> HashSet<Id> {
|
||||||
|
states
|
||||||
|
.states
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(id, state)| {
|
||||||
|
(state.visible_area > 0 && state.presentation_state != RenderElementPresentationState::Skipped)
|
||||||
|
.then(|| id.clone())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayPresentation {
|
||||||
|
// Call only after a successful submission, never from an offscreen capture.
|
||||||
|
pub fn queued(&mut self, output: &Output, states: &RenderElementStates) {
|
||||||
|
self.outputs.entry(output.clone()).or_default().queued = Some(visible(states));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn presented(&mut self, output: &Output) {
|
||||||
|
if let Some(record) = self.outputs.get_mut(output)
|
||||||
|
&& let Some(queued) = record.queued.take()
|
||||||
|
{
|
||||||
|
record.displayed = queued;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visible(&self, surface: &Id) -> bool {
|
||||||
|
self.outputs.values().any(|record| record.displayed.contains(surface))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_surface(&mut self, surface: &Id) {
|
||||||
|
for record in self.outputs.values_mut() {
|
||||||
|
record.displayed.remove(surface);
|
||||||
|
if let Some(queued) = &mut record.queued {
|
||||||
|
queued.remove(surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_output(&mut self, output: &Output) {
|
||||||
|
self.outputs.remove(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.outputs.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use smithay::backend::renderer::element::RenderElementState;
|
||||||
|
use smithay::output::{PhysicalProperties, Subpixel};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn output(name: &str) -> Output {
|
||||||
|
Output::new(
|
||||||
|
name.into(),
|
||||||
|
PhysicalProperties {
|
||||||
|
size: (0, 0).into(),
|
||||||
|
subpixel: Subpixel::Unknown,
|
||||||
|
make: "test".into(),
|
||||||
|
model: "test".into(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scene(id: &Id) -> RenderElementStates {
|
||||||
|
let mut states = RenderElementStates::default();
|
||||||
|
states.states.insert(
|
||||||
|
id.clone(),
|
||||||
|
RenderElementState {
|
||||||
|
visible_area: 20,
|
||||||
|
presentation_state: RenderElementPresentationState::ZeroCopy,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
states
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn visibility_changes_only_when_a_submission_is_presented() {
|
||||||
|
let mut record = DisplayPresentation::default();
|
||||||
|
let output = output("a");
|
||||||
|
let id = Id::new();
|
||||||
|
record.queued(&output, &scene(&id));
|
||||||
|
assert!(!record.visible(&id));
|
||||||
|
record.presented(&output);
|
||||||
|
assert!(record.visible(&id));
|
||||||
|
|
||||||
|
record.queued(&output, &RenderElementStates::default());
|
||||||
|
assert!(record.visible(&id));
|
||||||
|
record.presented(&output);
|
||||||
|
assert!(!record.visible(&id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn outputs_are_independent_and_removal_cannot_resurrect_a_surface() {
|
||||||
|
let mut record = DisplayPresentation::default();
|
||||||
|
let a = output("a");
|
||||||
|
let b = output("b");
|
||||||
|
let id = Id::new();
|
||||||
|
for output in [&a, &b] {
|
||||||
|
record.queued(output, &scene(&id));
|
||||||
|
record.presented(output);
|
||||||
|
}
|
||||||
|
record.remove_output(&a);
|
||||||
|
assert!(record.visible(&id));
|
||||||
|
record.queued(&b, &scene(&id));
|
||||||
|
record.remove_surface(&id);
|
||||||
|
record.presented(&b);
|
||||||
|
assert!(!record.visible(&id));
|
||||||
|
record.clear();
|
||||||
|
assert!(!record.visible(&id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn occluded_and_skipped_content_do_not_count_as_visible() {
|
||||||
|
let id = Id::new();
|
||||||
|
let mut states = scene(&id);
|
||||||
|
states.states.get_mut(&id).unwrap().visible_area = 0;
|
||||||
|
assert!(visible(&states).is_empty());
|
||||||
|
states.states.get_mut(&id).unwrap().visible_area = 20;
|
||||||
|
states.states.get_mut(&id).unwrap().presentation_state = RenderElementPresentationState::Skipped;
|
||||||
|
assert!(visible(&states).is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,12 +71,31 @@ impl CompositorHandler for Ferese {
|
|||||||
self.record_client_commit(&window);
|
self.record_client_commit(&window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !is_sync_subsurface(surface) {
|
||||||
|
let mut surfaces = Vec::new();
|
||||||
|
smithay::desktop::utils::with_surfaces_surface_tree(surface, |surface, _| surfaces.push(surface.clone()));
|
||||||
|
for surface in surfaces {
|
||||||
|
let mapped = smithay::backend::renderer::utils::with_renderer_surface_state(&surface, |state| {
|
||||||
|
state.buffer().is_some()
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !mapped {
|
||||||
|
smithay::desktop::utils::with_surfaces_surface_tree(&surface, |child, _| {
|
||||||
|
self.display_presentation.remove_surface(&child.into());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.refresh_idle_inhibition();
|
||||||
|
}
|
||||||
|
|
||||||
layer_shell::handle_commit(self, surface);
|
layer_shell::handle_commit(self, surface);
|
||||||
xdg_shell::handle_commit(&mut self.popups, &mut self.space, surface);
|
xdg_shell::handle_commit(&mut self.popups, &mut self.space, surface);
|
||||||
crate::backends::direct::render_surface(self, surface);
|
crate::backends::direct::render_surface(self, surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn destroyed(&mut self, surface: &WlSurface) {
|
fn destroyed(&mut self, surface: &WlSurface) {
|
||||||
|
self.display_presentation.remove_surface(&surface.into());
|
||||||
|
|
||||||
if self.session_lock.active {
|
if self.session_lock.active {
|
||||||
self.session_lock
|
self.session_lock
|
||||||
.surfaces
|
.surfaces
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ impl IdleInhibitHandler for Ferese {
|
|||||||
fn inhibit(&mut self, surface: WlSurface) {
|
fn inhibit(&mut self, surface: WlSurface) {
|
||||||
let count = self.idle_inhibitors.entry(surface).or_default();
|
let count = self.idle_inhibitors.entry(surface).or_default();
|
||||||
*count = count.saturating_add(1);
|
*count = count.saturating_add(1);
|
||||||
self.idle_notifier_state.set_is_inhibited(true);
|
self.refresh_idle_inhibition();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uninhibit(&mut self, surface: WlSurface) {
|
fn uninhibit(&mut self, surface: WlSurface) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod config;
|
|||||||
mod cursor;
|
mod cursor;
|
||||||
mod daemon;
|
mod daemon;
|
||||||
mod dimming;
|
mod dimming;
|
||||||
|
mod display_presentation;
|
||||||
mod effects;
|
mod effects;
|
||||||
mod floating;
|
mod floating;
|
||||||
mod frame_scheduler;
|
mod frame_scheduler;
|
||||||
|
|||||||
@@ -235,8 +235,12 @@ impl PortalSession {
|
|||||||
|
|
||||||
impl crate::Ferese {
|
impl crate::Ferese {
|
||||||
pub(crate) fn refresh_idle_inhibition(&mut self) {
|
pub(crate) fn refresh_idle_inhibition(&mut self) {
|
||||||
self.idle_notifier_state
|
self.idle_notifier_state.set_is_inhibited(
|
||||||
.set_is_inhibited(!self.idle_inhibitors.is_empty() || self.portal_session.idle_inhibited());
|
self.idle_inhibitors
|
||||||
|
.keys()
|
||||||
|
.any(|surface| self.display_presentation.visible(&surface.into()))
|
||||||
|
|| self.portal_session.idle_inhibited(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn end_portal_session(&mut self) {
|
pub(crate) fn end_portal_session(&mut self) {
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ impl SessionLockHandler for Ferese {
|
|||||||
}
|
}
|
||||||
self.cancel_logout_confirmation();
|
self.cancel_logout_confirmation();
|
||||||
self.session_lock.active = true;
|
self.session_lock.active = true;
|
||||||
|
self.display_presentation.clear();
|
||||||
|
self.refresh_idle_inhibition();
|
||||||
self.session_lock.idle_since = Some(Instant::now());
|
self.session_lock.idle_since = Some(Instant::now());
|
||||||
match self
|
match self
|
||||||
.loop_handle
|
.loop_handle
|
||||||
@@ -302,6 +304,8 @@ impl SessionLockHandler for Ferese {
|
|||||||
}
|
}
|
||||||
let sleeping = self.session_lock.sleeping;
|
let sleeping = self.session_lock.sleeping;
|
||||||
self.session_lock = Lock::default();
|
self.session_lock = Lock::default();
|
||||||
|
self.display_presentation.clear();
|
||||||
|
self.refresh_idle_inhibition();
|
||||||
if sleeping {
|
if sleeping {
|
||||||
crate::backends::direct::wake_locked_outputs(self);
|
crate::backends::direct::wake_locked_outputs(self);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ pub struct Ferese {
|
|||||||
output_identity_ids: HashMap<String, OutputId>,
|
output_identity_ids: HashMap<String, OutputId>,
|
||||||
pub(crate) windows: WindowRegistry<Window>,
|
pub(crate) windows: WindowRegistry<Window>,
|
||||||
pub(crate) render: crate::render::RenderResources,
|
pub(crate) render: crate::render::RenderResources,
|
||||||
|
pub(crate) display_presentation: crate::display_presentation::DisplayPresentation,
|
||||||
pub(crate) nested_backend: Option<NestedBackend>,
|
pub(crate) nested_backend: Option<NestedBackend>,
|
||||||
pub(crate) wallpaper: WallpaperState,
|
pub(crate) wallpaper: WallpaperState,
|
||||||
window_stack: WindowStack,
|
window_stack: WindowStack,
|
||||||
@@ -561,6 +562,7 @@ impl Ferese {
|
|||||||
output_identity_ids: HashMap::new(),
|
output_identity_ids: HashMap::new(),
|
||||||
windows: WindowRegistry::default(),
|
windows: WindowRegistry::default(),
|
||||||
render: Default::default(),
|
render: Default::default(),
|
||||||
|
display_presentation: Default::default(),
|
||||||
nested_backend: None,
|
nested_backend: None,
|
||||||
wallpaper: WallpaperState::with_wakeup(config.wallpaper, Some(event_loop.get_signal())),
|
wallpaper: WallpaperState::with_wakeup(config.wallpaper, Some(event_loop.get_signal())),
|
||||||
window_stack: WindowStack::default(),
|
window_stack: WindowStack::default(),
|
||||||
|
|||||||
@@ -44,8 +44,13 @@ impl Ferese {
|
|||||||
geometry.settle_presentation();
|
geometry.settle_presentation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.with_surfaces(|surface, _| {
|
||||||
|
self.display_presentation.remove_surface(&surface.into());
|
||||||
|
});
|
||||||
self.space.unmap_elem(&window);
|
self.space.unmap_elem(&window);
|
||||||
}
|
}
|
||||||
|
self.refresh_idle_inhibition();
|
||||||
|
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn unregister_output(&mut self, output: &Output) {
|
pub fn unregister_output(&mut self, output: &Output) {
|
||||||
|
self.display_presentation.remove_output(output);
|
||||||
|
self.refresh_idle_inhibition();
|
||||||
|
|
||||||
let Some(output_id) = self.output_ids.remove(output) else {
|
let Some(output_id) = self.output_ids.remove(output) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -183,6 +183,10 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
state.loop_signal.stop();
|
state.loop_signal.stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
state.display_presentation.queued(&output, &rendered_states);
|
||||||
|
state.display_presentation.presented(&output);
|
||||||
|
state.refresh_idle_inhibition();
|
||||||
|
|
||||||
if state.session_lock.active {
|
if state.session_lock.active {
|
||||||
state.lock_frame_presented(&output);
|
state.lock_frame_presented(&output);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user