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

background: cache computed gradient images

Avoid recomputing on each config change/reload.
This commit is contained in:
Wez Furlong 2022-06-01 07:29:24 -07:00
parent 167127c14b
commit 2b083cc131
2 changed files with 161 additions and 108 deletions

View File

@ -290,7 +290,7 @@ impl Default for BackgroundOrigin {
}
}
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic, PartialEq)]
pub enum Interpolation {
Linear,
Basis,
@ -303,7 +303,7 @@ impl Default for Interpolation {
}
}
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic, PartialEq)]
pub enum BlendMode {
Rgb,
LinearRgb,
@ -317,7 +317,7 @@ impl Default for BlendMode {
}
}
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic, PartialEq)]
pub enum GradientOrientation {
Horizontal,
Vertical,
@ -337,7 +337,7 @@ impl Default for GradientOrientation {
}
}
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic, PartialEq)]
pub enum GradientPreset {
Blues,
BrBg,
@ -424,7 +424,7 @@ impl GradientPreset {
}
}
#[derive(Debug, Clone, FromDynamic, ToDynamic)]
#[derive(Debug, Clone, FromDynamic, ToDynamic, PartialEq)]
pub struct Gradient {
#[dynamic(default)]
pub orientation: GradientOrientation,

View File

@ -5,7 +5,7 @@ use crate::Dimensions;
use anyhow::Context;
use config::{
BackgroundHorizontalAlignment, BackgroundLayer, BackgroundRepeat, BackgroundSize,
BackgroundSource, BackgroundVerticalAlignment, ConfigHandle, DimensionContext,
BackgroundSource, BackgroundVerticalAlignment, ConfigHandle, DimensionContext, Gradient,
GradientOrientation,
};
use std::collections::HashMap;
@ -16,110 +16,23 @@ use wezterm_term::StableRowIndex;
lazy_static::lazy_static! {
static ref IMAGE_CACHE: Mutex<HashMap<String, CachedImage>> = Mutex::new(HashMap::new());
static ref GRADIENT_CACHE: Mutex<Vec<CachedGradient>> = Mutex::new(vec![]);
}
struct CachedImage {
modified: SystemTime,
struct CachedGradient {
g: Gradient,
width: u32,
height: u32,
image: Arc<ImageData>,
marked: bool,
speed: f32,
}
impl CachedImage {
fn load(path: &str, speed: f32) -> anyhow::Result<Arc<ImageData>> {
let modified = std::fs::metadata(path)
.and_then(|m| m.modified())
.with_context(|| format!("getting metadata for {}", path))?;
let mut cache = IMAGE_CACHE.lock().unwrap();
if let Some(cached) = cache.get_mut(path) {
if cached.modified == modified && cached.speed == speed {
cached.marked = false;
return Ok(Arc::clone(&cached.image));
}
}
let data = std::fs::read(path)
.with_context(|| format!("Failed to load window_background_image {}", path))?;
log::trace!("loaded {}", path);
let mut data = ImageDataType::EncodedFile(data).decode();
data.adjust_speed(speed);
let image = Arc::new(ImageData::with_data(data));
cache.insert(
path.to_string(),
Self {
modified,
image: Arc::clone(&image),
marked: false,
speed,
},
);
Ok(image)
}
fn mark() {
let mut cache = IMAGE_CACHE.lock().unwrap();
for entry in cache.values_mut() {
entry.marked = true;
}
}
fn sweep() {
let mut cache = IMAGE_CACHE.lock().unwrap();
cache.retain(|k, entry| {
if entry.marked {
log::trace!("Unloading {} from cache", k);
}
!entry.marked
});
}
}
pub struct LoadedBackgroundLayer {
pub source: Arc<ImageData>,
pub def: BackgroundLayer,
}
fn load_background_layer(
layer: &BackgroundLayer,
dimensions: &Dimensions,
render_metrics: &RenderMetrics,
) -> anyhow::Result<LoadedBackgroundLayer> {
let h_context = DimensionContext {
dpi: dimensions.dpi as f32,
pixel_max: dimensions.pixel_width as f32,
pixel_cell: render_metrics.cell_size.width as f32,
};
let v_context = DimensionContext {
dpi: dimensions.dpi as f32,
pixel_max: dimensions.pixel_height as f32,
pixel_cell: render_metrics.cell_size.height as f32,
};
let data = match &layer.source {
BackgroundSource::Gradient(g) => {
impl CachedGradient {
fn compute(g: &Gradient, width: u32, height: u32) -> anyhow::Result<Arc<ImageData>> {
let grad = g
.build()
.with_context(|| format!("building gradient {:?}", g))?;
let mut width = match layer.width {
BackgroundSize::Dimension(d) => d.evaluate_as_pixels(h_context),
unsup => anyhow::bail!("{:?} not yet implemented", unsup),
} as u32;
let mut height = match layer.height {
BackgroundSize::Dimension(d) => d.evaluate_as_pixels(v_context),
unsup => anyhow::bail!("{:?} not yet implemented", unsup),
} as u32;
if matches!(g.orientation, GradientOrientation::Radial { .. }) {
// To simplify the math, we compute a perfect circle
// for the radial gradient, and let the texture sampler
// perturb it to fill the window
width = width.min(height);
height = height.min(width);
}
let mut imgbuf = image::RgbaImage::new(width, height);
let fw = width as f64;
let fh = height as f64;
@ -206,8 +119,7 @@ fn load_background_layer(
let nx = noise(&rng, noise_amount);
let ny = noise(&rng, noise_amount);
let t = (nx + (x as f64 - cx).powi(2) + (ny + y as f64 - cy).powi(2))
.sqrt()
let t = (nx + (x as f64 - cx).powi(2) + (ny + y as f64 - cy).powi(2)).sqrt()
/ radius;
*pixel = to_pixel(grad.at(t));
}
@ -215,9 +127,148 @@ fn load_background_layer(
}
let data = imgbuf.into_vec();
Arc::new(ImageData::with_data(ImageDataType::new_single_frame(
let image = Arc::new(ImageData::with_data(ImageDataType::new_single_frame(
width, height, data,
)))
)));
Ok(image)
}
fn load(g: &Gradient, width: u32, height: u32) -> anyhow::Result<Arc<ImageData>> {
let mut cache = GRADIENT_CACHE.lock().unwrap();
if let Some(entry) = cache
.iter_mut()
.find(|entry| entry.g == *g && entry.width == width && entry.height == height)
{
entry.marked = false;
return Ok(Arc::clone(&entry.image));
}
let image = Self::compute(g, width, height)?;
cache.push(Self {
g: g.clone(),
width,
height,
image: Arc::clone(&image),
marked: false,
});
Ok(image)
}
fn mark() {
let mut cache = GRADIENT_CACHE.lock().unwrap();
for entry in cache.iter_mut() {
entry.marked = true;
}
}
fn sweep() {
let mut cache = GRADIENT_CACHE.lock().unwrap();
cache.retain(|entry| !entry.marked);
}
}
struct CachedImage {
modified: SystemTime,
image: Arc<ImageData>,
marked: bool,
speed: f32,
}
impl CachedImage {
fn load(path: &str, speed: f32) -> anyhow::Result<Arc<ImageData>> {
let modified = std::fs::metadata(path)
.and_then(|m| m.modified())
.with_context(|| format!("getting metadata for {}", path))?;
let mut cache = IMAGE_CACHE.lock().unwrap();
if let Some(cached) = cache.get_mut(path) {
if cached.modified == modified && cached.speed == speed {
cached.marked = false;
return Ok(Arc::clone(&cached.image));
}
}
let data = std::fs::read(path)
.with_context(|| format!("Failed to load window_background_image {}", path))?;
log::trace!("loaded {}", path);
let mut data = ImageDataType::EncodedFile(data).decode();
data.adjust_speed(speed);
let image = Arc::new(ImageData::with_data(data));
cache.insert(
path.to_string(),
Self {
modified,
image: Arc::clone(&image),
marked: false,
speed,
},
);
Ok(image)
}
fn mark() {
let mut cache = IMAGE_CACHE.lock().unwrap();
for entry in cache.values_mut() {
entry.marked = true;
}
}
fn sweep() {
let mut cache = IMAGE_CACHE.lock().unwrap();
cache.retain(|k, entry| {
if entry.marked {
log::trace!("Unloading {} from cache", k);
}
!entry.marked
});
}
}
pub struct LoadedBackgroundLayer {
pub source: Arc<ImageData>,
pub def: BackgroundLayer,
}
fn load_background_layer(
layer: &BackgroundLayer,
dimensions: &Dimensions,
render_metrics: &RenderMetrics,
) -> anyhow::Result<LoadedBackgroundLayer> {
let h_context = DimensionContext {
dpi: dimensions.dpi as f32,
pixel_max: dimensions.pixel_width as f32,
pixel_cell: render_metrics.cell_size.width as f32,
};
let v_context = DimensionContext {
dpi: dimensions.dpi as f32,
pixel_max: dimensions.pixel_height as f32,
pixel_cell: render_metrics.cell_size.height as f32,
};
let data = match &layer.source {
BackgroundSource::Gradient(g) => {
let mut width = match layer.width {
BackgroundSize::Dimension(d) => d.evaluate_as_pixels(h_context),
unsup => anyhow::bail!("{:?} not yet implemented", unsup),
} as u32;
let mut height = match layer.height {
BackgroundSize::Dimension(d) => d.evaluate_as_pixels(v_context),
unsup => anyhow::bail!("{:?} not yet implemented", unsup),
} as u32;
if matches!(g.orientation, GradientOrientation::Radial { .. }) {
// To simplify the math, we compute a perfect circle
// for the radial gradient, and let the texture sampler
// perturb it to fill the window
width = width.min(height);
height = height.min(width);
}
CachedGradient::load(g, width, height)?
}
BackgroundSource::Color(color) => {
// In theory we could just make a 1x1 texture and allow
@ -295,6 +346,7 @@ pub fn reload_background_image(
.collect();
CachedImage::mark();
CachedGradient::mark();
let result = load_background_image(config, dimensions, render_metrics)
.into_iter()
@ -310,6 +362,7 @@ pub fn reload_background_image(
.collect();
CachedImage::sweep();
CachedGradient::sweep();
result
}