Merge pull request #7 from josephdunn/perf/lock-idle-redraw-dirty-tracking
perf: only redraw the lock screen when something changes
This commit is contained in:
+65
-12
@@ -24,6 +24,12 @@ pub struct LockedSurface {
|
|||||||
wayland_surface: Option<wl_surface::WlSurface>,
|
wayland_surface: Option<wl_surface::WlSurface>,
|
||||||
output: wl_output::WlOutput,
|
output: wl_output::WlOutput,
|
||||||
configured: bool,
|
configured: bool,
|
||||||
|
/// Set whenever rendered state changes (keystroke, status, clock minute,
|
||||||
|
/// animation step). update() renders only when this is set or an animation
|
||||||
|
/// is in flight, so an idle lock screen does no per-frame cairo work.
|
||||||
|
dirty: bool,
|
||||||
|
/// Last clock minute (unix-minute) we rendered, to detect %H:%M rollover.
|
||||||
|
last_minute: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LockedSurface {
|
impl LockedSurface {
|
||||||
@@ -54,6 +60,8 @@ impl LockedSurface {
|
|||||||
wayland_surface: None,
|
wayland_surface: None,
|
||||||
output,
|
output,
|
||||||
configured: false,
|
configured: false,
|
||||||
|
dirty: true,
|
||||||
|
last_minute: i64::MIN,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +70,7 @@ impl LockedSurface {
|
|||||||
log::debug!("LockedSurface: Configured, starting animation");
|
log::debug!("LockedSurface: Configured, starting animation");
|
||||||
self.configured = true;
|
self.configured = true;
|
||||||
self.start_time = Instant::now();
|
self.start_time = Instant::now();
|
||||||
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this surface matches the given Wayland surface
|
/// Check if this surface matches the given Wayland surface
|
||||||
@@ -72,12 +81,15 @@ impl LockedSurface {
|
|||||||
.is_some_and(|ws| ws.id() == surface.id())
|
.is_some_and(|ws| ws.id() == surface.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the surface state (called on each frame)
|
/// Update the surface state (called on each frame). Returns `true` if the
|
||||||
pub fn update(&mut self) {
|
/// surface was re-rendered and therefore needs to be committed. An idle
|
||||||
|
/// surface (no input, no animation, same clock minute) returns `false` and
|
||||||
|
/// does no cairo work, which keeps a locked session near-zero CPU.
|
||||||
|
pub fn update(&mut self) -> bool {
|
||||||
self.input_handler.update();
|
self.input_handler.update();
|
||||||
|
|
||||||
if !self.configured {
|
if !self.configured {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update fade animation
|
// Update fade animation
|
||||||
@@ -92,9 +104,22 @@ impl LockedSurface {
|
|||||||
1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
|
1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
|
||||||
};
|
};
|
||||||
let new_alpha = eased_t.min(1.0);
|
let new_alpha = eased_t.min(1.0);
|
||||||
if (new_alpha - self.fade_alpha).abs() > 0.001 {
|
if t >= 1.0 {
|
||||||
|
// The eased curve only approaches 1.0 asymptotically, and the
|
||||||
|
// 0.001 throttle below suppresses the tiny final steps — which
|
||||||
|
// would leave fade_alpha stuck just under 1.0 forever. Since
|
||||||
|
// `fade_alpha < 1.0` is our "still animating" signal, that would
|
||||||
|
// force a full render every frame. Snap to exactly 1.0 once the
|
||||||
|
// fade duration has elapsed so the animation cleanly completes.
|
||||||
|
if self.fade_alpha != 1.0 {
|
||||||
|
self.fade_alpha = 1.0;
|
||||||
|
self.renderer.set_fade_alpha(1.0);
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
} else if (new_alpha - self.fade_alpha).abs() > 0.001 {
|
||||||
self.fade_alpha = new_alpha;
|
self.fade_alpha = new_alpha;
|
||||||
self.renderer.set_fade_alpha(self.fade_alpha);
|
self.renderer.set_fade_alpha(self.fade_alpha);
|
||||||
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +127,7 @@ impl LockedSurface {
|
|||||||
if self.input_handler.should_show_wrong_password() && !self.wrong_password_shown {
|
if self.input_handler.should_show_wrong_password() && !self.wrong_password_shown {
|
||||||
self.renderer.show_wrong_password();
|
self.renderer.show_wrong_password();
|
||||||
self.wrong_password_shown = true;
|
self.wrong_password_shown = true;
|
||||||
|
self.dirty = true;
|
||||||
} else if !self.input_handler.should_show_wrong_password() && self.wrong_password_shown {
|
} else if !self.input_handler.should_show_wrong_password() && self.wrong_password_shown {
|
||||||
self.wrong_password_shown = false;
|
self.wrong_password_shown = false;
|
||||||
}
|
}
|
||||||
@@ -110,12 +136,16 @@ impl LockedSurface {
|
|||||||
if self.input_handler.should_show_key_highlight() && !self.key_highlight_shown {
|
if self.input_handler.should_show_key_highlight() && !self.key_highlight_shown {
|
||||||
self.renderer.show_key_highlight();
|
self.renderer.show_key_highlight();
|
||||||
self.key_highlight_shown = true;
|
self.key_highlight_shown = true;
|
||||||
|
self.dirty = true;
|
||||||
} else if !self.input_handler.should_show_key_highlight() && self.key_highlight_shown {
|
} else if !self.input_handler.should_show_key_highlight() && self.key_highlight_shown {
|
||||||
self.key_highlight_shown = false;
|
self.key_highlight_shown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update caps lock state in renderer
|
// Update caps lock state in renderer
|
||||||
self.renderer.caps_lock = self.input_handler.caps_lock();
|
if self.renderer.caps_lock != self.input_handler.caps_lock() {
|
||||||
|
self.renderer.caps_lock = self.input_handler.caps_lock();
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Set background if available and not already applied
|
// Set background if available and not already applied
|
||||||
if !self.background_applied {
|
if !self.background_applied {
|
||||||
@@ -123,14 +153,34 @@ impl LockedSurface {
|
|||||||
log::info!("Applying background image to renderer");
|
log::info!("Applying background image to renderer");
|
||||||
self.renderer.set_background(background.clone());
|
self.renderer.set_background(background.clone());
|
||||||
self.background_applied = true;
|
self.background_applied = true;
|
||||||
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The clock displays %H:%M, so it only needs a redraw once per minute.
|
||||||
|
if self.config.clock {
|
||||||
|
let minute = chrono::Local::now().timestamp().div_euclid(60);
|
||||||
|
if self.last_minute != minute {
|
||||||
|
self.last_minute = minute;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep emitting frames while an animation is in flight so it can run to
|
||||||
|
// completion even though no new event arrives.
|
||||||
|
let animating = self.fade_alpha < 1.0 || self.renderer.is_animating();
|
||||||
|
|
||||||
|
if !self.dirty && !animating {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
self.renderer
|
self.renderer
|
||||||
.set_password_display(self.input_handler.password_length());
|
.set_password_display(self.input_handler.password_length());
|
||||||
self.renderer
|
self.renderer
|
||||||
.set_cursor_position(self.input_handler.cursor_position());
|
.set_cursor_position(self.input_handler.cursor_position());
|
||||||
self.renderer.render();
|
self.renderer.render();
|
||||||
|
self.dirty = false;
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit the rendered frame to the Wayland surface
|
/// Commit the rendered frame to the Wayland surface
|
||||||
@@ -163,6 +213,7 @@ impl LockedSurface {
|
|||||||
}
|
}
|
||||||
self.renderer.resize(width, height);
|
self.renderer.resize(width, height);
|
||||||
self.background_applied = false;
|
self.background_applied = false;
|
||||||
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_wrong_password(&mut self) {
|
pub fn show_wrong_password(&mut self) {
|
||||||
@@ -178,6 +229,10 @@ impl LockedSurface {
|
|||||||
.input_handler
|
.input_handler
|
||||||
.handle_key_event(event.keysym, event.utf8, modifiers);
|
.handle_key_event(event.keysym, event.utf8, modifiers);
|
||||||
|
|
||||||
|
// Any key event may change the password display, cursor or caps state,
|
||||||
|
// so request a redraw on the next update().
|
||||||
|
self.dirty = true;
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
InputAction::PasswordChanged => {
|
InputAction::PasswordChanged => {
|
||||||
self.input_handler.set_key_highlight();
|
self.input_handler.set_key_highlight();
|
||||||
@@ -207,10 +262,14 @@ impl LockedSurface {
|
|||||||
pub fn set_background(&mut self, surface: ImageSurface) {
|
pub fn set_background(&mut self, surface: ImageSurface) {
|
||||||
self.background = Some(surface);
|
self.background = Some(surface);
|
||||||
self.background_applied = false;
|
self.background_applied = false;
|
||||||
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_system_status(&mut self, status: SystemStatus) {
|
pub fn set_system_status(&mut self, status: SystemStatus) {
|
||||||
self.renderer.system_status = status;
|
if self.renderer.system_status != status {
|
||||||
|
self.renderer.system_status = status;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,12 +296,6 @@ impl LockManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
|
||||||
for surface in &mut self.surfaces {
|
|
||||||
surface.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn surface_count(&self) -> usize {
|
pub fn surface_count(&self) -> usize {
|
||||||
self.surfaces.len()
|
self.surfaces.len()
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -830,9 +830,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
if let Ok(mut lm) = state.lock_manager.lock() {
|
if let Ok(mut lm) = state.lock_manager.lock() {
|
||||||
lm.set_system_status(status);
|
lm.set_system_status(status);
|
||||||
lm.update();
|
// Only commit surfaces that actually re-rendered this tick. An idle
|
||||||
|
// lock screen renders nothing and commits nothing.
|
||||||
for surface in &mut lm.surfaces {
|
for surface in &mut lm.surfaces {
|
||||||
let _ = surface.commit(&mut state.pool);
|
if surface.update() {
|
||||||
|
let _ = surface.commit(&mut state.pool);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if state.exit {
|
if state.exit {
|
||||||
|
|||||||
@@ -121,6 +121,14 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether any feedback animation is currently in flight and therefore
|
||||||
|
/// requires continued per-frame redraws until it finishes.
|
||||||
|
pub(crate) fn is_animating(&self) -> bool {
|
||||||
|
self.wrong_password_start.is_some()
|
||||||
|
|| self.key_highlight_start.is_some()
|
||||||
|
|| self.cleared_feedback_start.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn update_feedback_timers(&mut self) {
|
pub(crate) fn update_feedback_timers(&mut self) {
|
||||||
self.update_uptime();
|
self.update_uptime();
|
||||||
if let Some(start) = self.wrong_password_start {
|
if let Some(start) = self.wrong_password_start {
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default, PartialEq)]
|
||||||
pub struct SystemStatus {
|
pub struct SystemStatus {
|
||||||
pub battery_percent: Option<f64>,
|
pub battery_percent: Option<f64>,
|
||||||
pub is_charging: bool,
|
pub is_charging: bool,
|
||||||
|
|||||||
Reference in New Issue
Block a user