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:
parent
167127c14b
commit
2b083cc131
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user