2 Commits

Author SHA1 Message Date
kilyabin 0c5a6d565c perf: fix WaitUntil being bypassed by request_redraw in AboutToWait
Calling request_redraw() inside AboutToWait immediately woke the event
loop on every iteration, making WaitUntil a no-op and producing a
busy-loop (98% CPU). Move request_redraw to NewEvents(ResumeTimeReached)
so the thread actually sleeps between frames.
2026-06-18 02:33:23 +04:00
kilyabin 4b30a1d260 perf: replace busy-loop Poll with WaitUntil frame limiter
Screensaver was consuming ~8% CPU due to ControlFlow::Poll rendering
as fast as possible. Switch to WaitUntil with configurable FPS cap.
Blank and image screensavers auto-cap at 2fps since they have no
animation, reducing CPU usage to near zero.
2026-06-18 02:29:20 +04:00
3 changed files with 26 additions and 7 deletions
+1
View File
@@ -33,6 +33,7 @@ type = "blank" # pure black (default, OLED-optimal)
monitor = 0 # monitor index (0 = primary). See: scr33ny monitors monitor = 0 # monitor index (0 = primary). See: scr33ny monitors
burn_shift = 2 # pixel shift for OLED burn-in prevention burn_shift = 2 # pixel shift for OLED burn-in prevention
shift_interval = 120 # seconds between shifts shift_interval = 120 # seconds between shifts
fps = 30 # max frames per second (blank/image auto-cap at 2)
# ── Idle daemon ─────────────────────────────────────────────────────────────── # ── Idle daemon ───────────────────────────────────────────────────────────────
+20 -4
View File
@@ -1,10 +1,10 @@
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::rc::Rc; use std::rc::Rc;
use std::time::Instant; use std::time::{Duration, Instant};
use anyhow::Result; use anyhow::Result;
use winit::{ use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent}, event::{ElementState, Event, KeyEvent, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
keyboard::{KeyCode, PhysicalKey}, keyboard::{KeyCode, PhysicalKey},
window::{Fullscreen, WindowBuilder}, window::{Fullscreen, WindowBuilder},
@@ -55,8 +55,16 @@ pub fn run(config: Config, monitor_idx: usize) -> Result<()> {
let mut screensaver: Box<dyn Screensaver> = build_screensaver(&config, font.clone())?; let mut screensaver: Box<dyn Screensaver> = build_screensaver(&config, font.clone())?;
let mut widgets: Vec<Box<dyn Widget>> = build_widgets(&config, font)?; let mut widgets: Vec<Box<dyn Widget>> = build_widgets(&config, font)?;
// Adaptive FPS: blank/image need only 12 fps, animations up to config.display.fps
let target_fps = match &config.screensaver {
ScreensaverConfig::Blank | ScreensaverConfig::Image { .. } => {
config.display.fps.min(2)
}
_ => config.display.fps,
}.max(1);
let frame_time = Duration::from_millis(1000 / target_fps as u64);
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Poll);
let monitors: Vec<_> = event_loop.available_monitors().collect(); let monitors: Vec<_> = event_loop.available_monitors().collect();
let monitor = monitors.get(monitor_idx).or_else(|| monitors.first()).cloned(); let monitor = monitors.get(monitor_idx).or_else(|| monitors.first()).cloned();
@@ -131,7 +139,15 @@ pub fn run(config: Config, monitor_idx: usize) -> Result<()> {
} }
} }
Event::AboutToWait => window.request_redraw(), // Timer expired or first run — schedule a redraw
Event::NewEvents(StartCause::ResumeTimeReached { .. } | StartCause::Init) => {
window.request_redraw();
}
// All events processed — sleep until next frame
Event::AboutToWait => {
elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + frame_time));
}
_ => {} _ => {}
} }
+3 -1
View File
@@ -147,11 +147,13 @@ pub struct DisplayConfig {
/// Anti-burn-in pixel shift in pixels, applied every shift_interval seconds /// Anti-burn-in pixel shift in pixels, applied every shift_interval seconds
pub burn_shift: u32, pub burn_shift: u32,
pub shift_interval: u64, pub shift_interval: u64,
/// Target frames per second. Lower = less CPU. Blank/image need only 12.
pub fps: u32,
} }
impl Default for DisplayConfig { impl Default for DisplayConfig {
fn default() -> Self { fn default() -> Self {
Self { monitor: 0, burn_shift: 2, shift_interval: 120 } Self { monitor: 0, burn_shift: 2, shift_interval: 120, fps: 30 }
} }
} }