1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 23:21:08 +03:00

Set minimum animation frame duration to match max_fps

In #3260, the candidate animation had a frame delay of 1ms.  That is
problematic because there is no hope for wezterm running at 60fps (a
frame delay of ~16ms) to render that without dropping frames.

A consequence of such a short frame delay, coupled with the way that
images are carved up into cells, is that individual cells can easily
become visibled de-synchronized from their neighbors and results in what
looks like mpeg artifacts but are really regions where the cells are a
few frames ahead/behind their neighbors.

The solution is to clamp the frame duration up to match the minimum
displayable frame duration as governed by max_fps.

refs: https://github.com/wez/wezterm/issues/3260
This commit is contained in:
Wez Furlong 2023-03-16 08:33:18 -07:00
parent d84ccf126e
commit 4576f46828
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387

View File

@ -13,7 +13,7 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::{Arc, MutexGuard};
use std::time::Instant;
use std::time::{Duration, Instant};
use termwiz::color::RgbColor;
use termwiz::image::{ImageData, ImageDataType};
use termwiz::surface::CursorShape;
@ -252,6 +252,7 @@ pub struct GlyphCache {
pub block_glyphs: HashMap<SizedBlockKey, Sprite>,
pub cursor_glyphs: HashMap<(Option<CursorShape>, u8), Sprite>,
pub color: HashMap<(RgbColor, NotNan<f32>), Sprite>,
min_frame_duration: Duration,
}
impl GlyphCache {
@ -274,6 +275,7 @@ impl GlyphCache {
block_glyphs: HashMap::new(),
cursor_glyphs: HashMap::new(),
color: HashMap::new(),
min_frame_duration: Duration::from_millis(1000 / fonts.config().max_fps as u64),
})
}
}
@ -302,6 +304,7 @@ impl GlyphCache {
block_glyphs: HashMap::new(),
cursor_glyphs: HashMap::new(),
color: HashMap::new(),
min_frame_duration: Duration::from_millis(1000 / fonts.config().max_fps as u64),
})
}
}
@ -558,6 +561,7 @@ impl GlyphCache {
atlas: &mut Atlas,
decoded: &DecodedImage,
padding: Option<usize>,
min_frame_duration: Duration,
) -> anyhow::Result<(Sprite, Option<Instant>)> {
let mut handle = DecodedImageHandle {
h: decoded.image.data(),
@ -585,7 +589,18 @@ impl GlyphCache {
if frames.len() > 1 {
let now = Instant::now();
let mut next_due = *decoded_frame_start + durations[*decoded_current_frame];
// We round up the frame duration to at least the minimum
// frame duration that wezterm can use when rendering.
// There's no point trying to deal with smaller intervals
// because we simply cannot render them without dropping
// frames.
// In addition, with a 1ms frame delay, there's a good chance
// that any given cell may switch to a different frame from
// its neighbor while we are rendering the entire terminal
// frame, so we want to avoid that.
// <https://github.com/wez/wezterm/issues/3260>
let mut next_due = *decoded_frame_start
+ durations[*decoded_current_frame].max(min_frame_duration);
if now >= next_due {
// Advance to next frame
*decoded_current_frame = *decoded_current_frame + 1;
@ -631,11 +646,22 @@ impl GlyphCache {
let id = image_data.id() as u64;
if let Some(decoded) = self.image_cache.get(&id) {
Self::cached_image_impl(&mut self.frame_cache, &mut self.atlas, decoded, padding)
Self::cached_image_impl(
&mut self.frame_cache,
&mut self.atlas,
decoded,
padding,
self.min_frame_duration,
)
} else {
let decoded = DecodedImage::load(image_data);
let res =
Self::cached_image_impl(&mut self.frame_cache, &mut self.atlas, &decoded, padding)?;
let res = Self::cached_image_impl(
&mut self.frame_cache,
&mut self.atlas,
&decoded,
padding,
self.min_frame_duration,
)?;
self.image_cache.put(id, decoded);
Ok(res)
}