1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-24 13:52:55 +03:00

show terminal background while waiting for background image to load

Avoid a "flash" of a single black but likely overly stretched and
awkwardly interpolated frame while we wait for big/animated/complex
images to load and decode.

For corrupt images, or images with an incorrect or typo'd filename in
the config, this prevents us from punting and just showing a transparent
background instead of something reasonable.
This commit is contained in:
Wez Furlong 2023-05-07 09:16:44 -07:00
parent d0e9a03440
commit 27fbff4ae1
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
5 changed files with 79 additions and 34 deletions

View File

@ -32,6 +32,11 @@ As features stabilize some brief notes about them will accumulate here.
* Don't hide mouse cursor when pressing only modifier keys. #3570
* [PaneSelect](config/lua/keyassignment/PaneSelect.md) will now un-zoom to show
all panes, then re-zoom after performing its action. #3573
* Images, especially animated images, are now decoded in the background. When
used as a background layer for the terminal, we now use the normal
terminal background color as a placeholder until the first frame has been
decoded. In other circumstances, you may observe a brief black frame while
waiting for the image to decode.
#### New

View File

@ -31,6 +31,12 @@ use wezterm_font::units::*;
use wezterm_font::{FontConfiguration, GlyphInfo, LoadedFont, LoadedFontId};
use wezterm_term::Underline;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadState {
Loading,
Loaded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CellMetricKey {
pub pixel_width: u16,
@ -383,6 +389,7 @@ struct FrameState {
source: FrameSource,
current_frame: DecodedFrame,
frames: Vec<DecodedFrame>,
load_state: LoadState,
}
impl FrameState {
@ -405,6 +412,7 @@ impl FrameState {
height: BLACK_SIZE,
duration: Duration::from_millis(0),
},
load_state: LoadState::Loading,
}
}
@ -414,6 +422,7 @@ impl FrameState {
Ok(frame) => {
self.frames.push(frame.clone());
self.current_frame = frame;
self.load_state = LoadState::Loaded;
true
}
Err(TryRecvError::Empty) => false,
@ -480,6 +489,14 @@ impl DecodedImage {
}
}
fn load_state(&self) -> LoadState {
let frames = self.frames.borrow();
match frames.as_ref() {
Some(state) => state.load_state,
None => LoadState::Loading,
}
}
fn start_frame_decoder(lease: BlobLease, image_data: &Arc<ImageData>) -> Self {
match FrameDecoder::start(lease.clone()) {
Ok(rx) => Self {
@ -854,7 +871,7 @@ impl GlyphCache {
decoded: &DecodedImage,
padding: Option<usize>,
min_frame_duration: Duration,
) -> anyhow::Result<(Sprite, Option<Instant>)> {
) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> {
let mut handle = DecodedImageHandle {
h: decoded.image.data(),
current_frame: *decoded.current_frame.borrow(),
@ -862,12 +879,12 @@ impl GlyphCache {
match &*handle.h {
ImageDataType::Rgba8 { hash, .. } => {
if let Some(sprite) = frame_cache.get(hash) {
return Ok((sprite.clone(), None));
return Ok((sprite.clone(), None, decoded.load_state()));
}
let sprite = atlas.allocate_with_padding(&handle, padding)?;
frame_cache.insert(*hash, sprite.clone());
return Ok((sprite, None));
return Ok((sprite, None, decoded.load_state()));
}
ImageDataType::AnimRgba8 {
hashes,
@ -915,7 +932,7 @@ impl GlyphCache {
let hash = hashes[*decoded_current_frame];
if let Some(sprite) = frame_cache.get(&hash) {
return Ok((sprite.clone(), next));
return Ok((sprite.clone(), next, decoded.load_state()));
}
let sprite = atlas.allocate_with_padding(&handle, padding)?;
@ -928,6 +945,7 @@ impl GlyphCache {
*decoded_frame_start
+ durations[*decoded_current_frame].max(min_frame_duration),
),
decoded.load_state(),
));
}
ImageDataType::EncodedLease(_) | ImageDataType::EncodedFile(_) => {
@ -967,7 +985,7 @@ impl GlyphCache {
let hash = frames.frame_hash();
if let Some(sprite) = frame_cache.get(&hash) {
return Ok((sprite.clone(), next));
return Ok((sprite.clone(), next, frames.load_state));
}
let frame = Image::from_raw(
@ -982,6 +1000,7 @@ impl GlyphCache {
Ok((
sprite,
Some(*decoded_frame_start + frames.frame_duration().max(min_frame_duration)),
frames.load_state,
))
}
}
@ -991,7 +1010,7 @@ impl GlyphCache {
&mut self,
image_data: &Arc<ImageData>,
padding: Option<usize>,
) -> anyhow::Result<(Sprite, Option<Instant>)> {
) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> {
let hash = image_data.hash();
if let Some(decoded) = self.image_cache.get(&hash) {

View File

@ -1,4 +1,5 @@
use crate::color::LinearRgba;
use crate::glyphcache::LoadState;
use crate::quad::{QuadAllocator, QuadTrait};
use crate::termwindow::RenderState;
use crate::utilsprites::RenderMetrics;
@ -397,15 +398,17 @@ impl crate::TermWindow {
&self,
bg_color: LinearRgba,
top: StableRowIndex,
) -> anyhow::Result<()> {
) -> anyhow::Result<bool> {
let gl_state = self.render_state.as_ref().unwrap();
let mut layer_idx = -127;
let mut loaded_any = false;
for layer in self.window_background.iter() {
if self.render_background(gl_state, bg_color, layer, layer_idx, top)? {
loaded_any = true;
layer_idx = layer_idx.saturating_add(1);
}
}
Ok(())
Ok(loaded_any)
}
fn render_background(
@ -422,12 +425,16 @@ impl crate::TermWindow {
let color = bg_color.mul_alpha(layer.def.opacity);
let (sprite, next_due) = gl_state
let (sprite, next_due, load_state) = gl_state
.glyph_cache
.borrow_mut()
.cached_image(&layer.source, None)?;
self.update_next_frame_time(next_due);
if load_state == LoadState::Loading {
return Ok(false);
}
let pixel_width = self.dimensions.pixel_width as f32;
let pixel_height = self.dimensions.pixel_height as f32;
let pixel_aspect = pixel_width / pixel_height;

View File

@ -432,7 +432,7 @@ impl crate::TermWindow {
padding.next_power_of_two()
};
let (sprite, next_due) = gl_state
let (sprite, next_due, _load_state) = gl_state
.glyph_cache
.borrow_mut()
.cached_image(image.image_data(), Some(padding))?;

View File

@ -171,6 +171,8 @@ impl crate::TermWindow {
log::trace!("quad map elapsed {:?}", start.elapsed());
metrics::histogram!("quad.map", start.elapsed());
let mut paint_terminal_background = false;
// Render the full window background
match (self.window_background.is_empty(), self.allow_images) {
(false, true) => {
@ -185,8 +187,16 @@ impl crate::TermWindow {
})
.unwrap_or(0);
self.render_backgrounds(bg_color, top)
let loaded_any = self
.render_backgrounds(bg_color, top)
.context("render_backgrounds")?;
if !loaded_any {
// Either there was a problem loading the background(s)
// or they haven't finished loading yet.
// Use the regular terminal background until that changes.
paint_terminal_background = true;
}
}
_ if window_is_transparent => {
// Avoid doubling up the background color: the panes
@ -194,32 +204,36 @@ impl crate::TermWindow {
// should be no gaps that need filling in
}
_ => {
// Regular window background color
let background = if panes.len() == 1 {
// If we're the only pane, use the pane's palette
// to draw the padding background
panes[0].pane.palette().background
} else {
self.palette().background
}
.to_linear()
.mul_alpha(self.config.window_background_opacity);
self.filled_rectangle(
&mut layers,
0,
euclid::rect(
0.,
0.,
self.dimensions.pixel_width as f32,
self.dimensions.pixel_height as f32,
),
background,
)
.context("filled_rectangle for window background")?;
paint_terminal_background = true;
}
}
if paint_terminal_background {
// Regular window background color
let background = if panes.len() == 1 {
// If we're the only pane, use the pane's palette
// to draw the padding background
panes[0].pane.palette().background
} else {
self.palette().background
}
.to_linear()
.mul_alpha(self.config.window_background_opacity);
self.filled_rectangle(
&mut layers,
0,
euclid::rect(
0.,
0.,
self.dimensions.pixel_width as f32,
self.dimensions.pixel_height as f32,
),
background,
)
.context("filled_rectangle for window background")?;
}
for pos in panes {
if pos.is_active {
self.update_text_cursor(&pos);