image/loading: Adjust frame timing by accrued delay

This should fix animations systematically slowing down.
This commit is contained in:
Sophie Herold 2026-06-23 22:45:16 +02:00
parent 5505f5d876
commit eee2b415d2
4 changed files with 26 additions and 14 deletions

View file

@ -0,0 +1 @@
Don't accumulate delays during animations. !596

View file

@ -649,22 +649,24 @@ impl SharedFrameBuffer {
tiling.unwrap()
}
/// Return true if the next frame should be shown and removes the outdated
/// frame
pub fn frame_timeout(&self, elapsed: Duration) -> bool {
/// Remove current frame if frame delay elapsed
///
/// Returns the amount of time the frame is shown 'too late'
pub fn frame_timeout(&self, elapsed: Duration) -> Option<Duration> {
let images = &self.buffer.load().images;
// Only move to next image if there is one buffered
if images.len() > 1
&& images
.front()
.is_some_and(|next_frame| elapsed >= next_frame.delay)
&& let Some(next_frame) = images.front()
&& elapsed >= next_frame.delay
{
let delay = elapsed - next_frame.delay;
self.next_frame();
return true;
let delay = delay.min(next_frame.delay);
return Some(delay);
}
false
None
}
pub fn next_frame(&self) {

View file

@ -253,8 +253,8 @@ mod imp {
/// Ticks callback for animated image formats
pub(super) tick_callback: RefCell<Option<gtk::TickCallbackId>>,
/// Frame block time for currently shown frame
pub(super) last_animated_frame: Cell<i64>,
/// Adjusted frame clock time for when the current frame was shown
pub(super) last_animated_frame: Cell<Option<i64>>,
/// Gesture, zoom or rotate, used for the duration of the gesture
pub(super) locked_gestured: Cell<Option<Gesture>>,

View file

@ -160,17 +160,26 @@ impl imp::LpImage {
// Do not animate if not visible
if !obj.is_mapped() {
self.last_animated_frame.set(None);
return glib::ControlFlow::Continue;
}
let elapsed = clock.frame_time() - self.last_animated_frame.get();
let duration = std::time::Duration::from_micros(elapsed as u64);
let duration = if let Some(last_animated_frame) = self.last_animated_frame.get() {
let elapsed = clock.frame_time() - last_animated_frame;
std::time::Duration::from_micros(elapsed as u64)
} else {
self.last_animated_frame.set(Some(clock.frame_time()));
std::time::Duration::ZERO
};
// Check if it's time to show the next frame
if self.frame_buffer.frame_timeout(duration) {
if let Some(delay) = self.frame_buffer.frame_timeout(duration) {
// Just draw since frame_timeout updated to new frame
obj.queue_draw();
self.last_animated_frame.set(clock.frame_time());
// Adjust the time when the frame was shown by how much anwanted delay we
// accumulated
self.last_animated_frame
.set(Some(clock.frame_time() - delay.as_micros() as i64));
if let Some(decoder) = self.decoder.borrow().as_ref() {
// Decode new frame and load it into buffer
decoder.fill_frame_buffer();