improv: clear image_cache on refresh

This commit is contained in:
Hojjat 2026-08-24 10:57:29 -06:00 • committed by Victoria Brekenfeld
parent 80b8eb47d7
commit 31827ed240
2 changed files with 80 additions and 29 deletions

View file

@ -460,9 +460,7 @@ pub struct CursorStateInner {
cursors: HashMap<CursorIcon, Cursor>,
current_image: Option<Image>,
/// Rasterized cursor frames keyed by `(shape, pixel size, frame index)` so
/// each SVG frame is rasterized (and uploaded) at most once per size.
image_cache: Vec<((CursorIcon, u32, usize), Image, MemoryRenderBuffer)>,
image_cache: Vec<CachedFrame>,
hidden: bool,
idle_timer: Option<RegistrationToken>,
@ -476,6 +474,15 @@ pub struct CursorStateInner {
magnification: f32,
anim_from: f32,
anim_start: Option<Instant>,
rest_started: Option<Instant>,
}
/// A rasterized cursor frame, keyed by `(shape, pixel size, frame index)`.
struct CachedFrame {
key: (CursorIcon, u32, usize),
image: Image,
buffer: MemoryRenderBuffer,
unmagnified: bool,
}
/// One sampled pointer position on the recent motion path.
@ -485,6 +492,9 @@ struct PathSample {
time: Instant,
}
/// How long everything must stay unmagnified before the enlarged frames go.
const MAGNIFIED_FRAME_GRACE: Duration = Duration::from_secs(10);
/// How far back the motion path is considered when looking for a shake.
const SHAKE_INTERVAL: Duration = Duration::from_millis(1000);
/// Path-length / bounding-box-diagonal ratio required to count as a shake.
@ -529,6 +539,21 @@ impl CursorStateInner {
self.cursor_size
}
/// Drop the rasterizations only a magnified cursor needed, once nothing has
/// magnified it for [`MAGNIFIED_FRAME_GRACE`].
pub fn drop_magnified_frames(&mut self, now: Instant, zoomed: bool) {
if zoomed || self.is_magnifying() {
self.rest_started = None;
return;
}
let rest_started = *self.rest_started.get_or_insert(now);
if now.duration_since(rest_started) < MAGNIFIED_FRAME_GRACE {
return;
}
self.image_cache.retain(|frame| frame.unmagnified);
}
/// Feed one relative-motion event into the shake detector.
pub fn detect_shake(&mut self, delta: Point<f64, Logical>, now: Instant) {
// Drop samples that have aged out of the time window.
@ -680,6 +705,7 @@ impl Default for CursorStateInner {
magnification: 1.0,
anim_from: 1.0,
anim_start: None,
rest_started: None,
}
}
}
@ -748,34 +774,36 @@ pub fn draw_cursor<R>(
let key = (current_cursor, size_px, frame_idx);
// Rasterize and upload this (shape, size, frame) only if not cached.
if !state
.image_cache
.iter()
.any(|(cached, _, _)| *cached == key)
{
let frame = {
let cursor = state.get_named_cursor(current_cursor);
cursor.render_frame(size_px, frame_idx)
};
let actual_scale = (frame.size / state.size()).max(1);
let buffer = MemoryRenderBuffer::from_slice(
&frame.pixels_rgba,
Fourcc::Argb8888,
(frame.width as i32, frame.height as i32),
actual_scale as i32,
Transform::Normal,
None,
);
state.image_cache.push((key, frame, buffer));
}
let index = match state.image_cache.iter().position(|frame| frame.key == key) {
Some(index) => index,
None => {
let image = {
let cursor = state.get_named_cursor(current_cursor);
cursor.render_frame(size_px, frame_idx)
};
let actual_scale = (image.size / state.size()).max(1);
let buffer = MemoryRenderBuffer::from_slice(
&image.pixels_rgba,
Fourcc::Argb8888,
(image.width as i32, image.height as i32),
actual_scale as i32,
Transform::Normal,
None,
);
state.image_cache.push(CachedFrame {
key,
image,
buffer,
unmagnified: size_px == unmagnified_px,
});
state.image_cache.len() - 1
}
};
let (frame, pointer_image) = {
let (_, frame, buffer) = state
.image_cache
.iter()
.find(|(cached, _, _)| *cached == key)
.unwrap();
(frame.clone(), buffer.clone())
let entry = &mut state.image_cache[index];
entry.unmagnified |= size_px == unmagnified_px;
(entry.image.clone(), entry.buffer.clone())
};
let actual_scale = (frame.size / state.size()).max(1);

View file

@ -1614,6 +1614,29 @@ impl Common {
a11y_keyboard_monitor.refresh();
}
self.image_copy_capture_state.cleanup();
self.cleanup_cursor_images();
}
/// Release the enlarged cursor frames a finished shake or zoom left behind.
fn cleanup_cursor_images(&mut self) {
let shell = self.shell.read();
let zoomed = shell.zoom_state.as_ref().is_some_and(|zoom_state| {
shell
.outputs()
.any(|output| zoom_state.animating_level(output) > 1.0)
});
let now = Instant::now();
for seat in shell.seats.iter() {
if let Some(cursor_state) = seat
.user_data()
.get::<crate::backend::render::cursor::CursorState>()
{
cursor_state
.lock()
.unwrap()
.drop_magnified_frames(now, zoomed);
}
}
}
pub fn refresh_idle_inhibit(&mut self) {