From 93576691fe371c572502190a56025f4d395674fc Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Tue, 5 Jan 2021 14:16:21 -0800 Subject: [PATCH] compute most line-glyphs on-the-fly We now have too many permutations to pre-render in the initial texture size, so do this on the fly instead. refs: https://github.com/wez/wezterm/issues/415 --- termwiz/src/cell.rs | 2 +- wezterm-gui/src/gui/glyphcache.rs | 153 ++++++++++++++++++++++ wezterm-gui/src/gui/renderstate.rs | 5 +- wezterm-gui/src/gui/termwindow.rs | 7 +- wezterm-gui/src/gui/utilsprites.rs | 203 ----------------------------- 5 files changed, 161 insertions(+), 209 deletions(-) diff --git a/termwiz/src/cell.rs b/termwiz/src/cell.rs index 2eb1255443f..6d219a625fc 100644 --- a/termwiz/src/cell.rs +++ b/termwiz/src/cell.rs @@ -156,7 +156,7 @@ impl Default for Intensity { /// Specify just how underlined you want your `Cell` to be #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u16)] pub enum Underline { /// The cell is not underlined diff --git a/wezterm-gui/src/gui/glyphcache.rs b/wezterm-gui/src/gui/glyphcache.rs index d9e95db9c4c..4e73b5508a3 100644 --- a/wezterm-gui/src/gui/glyphcache.rs +++ b/wezterm-gui/src/gui/glyphcache.rs @@ -1,3 +1,4 @@ +use super::utilsprites::RenderMetrics; use ::window::bitmaps::atlas::{Atlas, Sprite}; use ::window::bitmaps::{Image, Texture2d}; use ::window::glium::backend::Context as GliumContext; @@ -12,6 +13,7 @@ use std::sync::Arc; use termwiz::image::ImageData; use wezterm_font::units::*; use wezterm_font::{FontConfiguration, GlyphInfo}; +use wezterm_term::Underline; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GlyphKey { @@ -112,11 +114,20 @@ impl std::fmt::Debug for CachedGlyph { } } +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct LineKey { + strike_through: bool, + underline: Underline, + overline: bool, +} + pub struct GlyphCache { glyph_cache: HashMap>>, pub atlas: Atlas, fonts: Rc, image_cache: HashMap>, + line_glyphs: HashMap>, + metrics: RenderMetrics, } impl GlyphCache { @@ -124,6 +135,7 @@ impl GlyphCache { backend: &Rc, fonts: &Rc, size: usize, + metrics: &RenderMetrics, ) -> anyhow::Result { let surface = Rc::new(SrgbTexture2d::empty_with_format( backend, @@ -139,6 +151,8 @@ impl GlyphCache { glyph_cache: HashMap::new(), image_cache: HashMap::new(), atlas, + metrics: metrics.clone(), + line_glyphs: HashMap::new(), }) } @@ -146,6 +160,7 @@ impl GlyphCache { self.atlas.clear(); self.image_cache.clear(); self.glyph_cache.clear(); + self.line_glyphs.clear(); } } @@ -310,4 +325,142 @@ impl GlyphCache { Ok(sprite) } + + fn line_sprite(&mut self, key: LineKey) -> anyhow::Result> { + let mut buffer = Image::new( + self.metrics.cell_size.width as usize, + self.metrics.cell_size.height as usize, + ); + let black = ::window::color::Color::rgba(0, 0, 0, 0); + let white = ::window::color::Color::rgb(0xff, 0xff, 0xff); + + let cell_rect = Rect::new(Point::new(0, 0), self.metrics.cell_size); + + let draw_single = |buffer: &mut Image| { + for row in 0..self.metrics.underline_height { + buffer.draw_line( + Point::new( + cell_rect.origin.x, + cell_rect.origin.y + self.metrics.descender_row + row, + ), + Point::new( + cell_rect.origin.x + self.metrics.cell_size.width, + cell_rect.origin.y + self.metrics.descender_row + row, + ), + white, + Operator::Source, + ); + } + }; + + let draw_double = |buffer: &mut Image| { + for row in 0..self.metrics.underline_height { + buffer.draw_line( + Point::new( + cell_rect.origin.x, + cell_rect.origin.y + self.metrics.descender_row + row, + ), + Point::new( + cell_rect.origin.x + self.metrics.cell_size.width, + cell_rect.origin.y + self.metrics.descender_row + row, + ), + white, + Operator::Source, + ); + buffer.draw_line( + Point::new( + cell_rect.origin.x, + cell_rect.origin.y + self.metrics.descender_plus_two + row, + ), + Point::new( + cell_rect.origin.x + self.metrics.cell_size.width, + cell_rect.origin.y + self.metrics.descender_plus_two + row, + ), + white, + Operator::Source, + ); + } + }; + + let draw_strike = |buffer: &mut Image| { + for row in 0..self.metrics.underline_height { + buffer.draw_line( + Point::new( + cell_rect.origin.x, + cell_rect.origin.y + self.metrics.strike_row + row, + ), + Point::new( + cell_rect.origin.x + self.metrics.cell_size.width, + cell_rect.origin.y + self.metrics.strike_row + row, + ), + white, + Operator::Source, + ); + } + }; + + let draw_overline = |buffer: &mut Image| { + for row in 0..self.metrics.underline_height { + buffer.draw_line( + Point::new(cell_rect.origin.x, cell_rect.origin.y + row), + Point::new( + cell_rect.origin.x + self.metrics.cell_size.width, + cell_rect.origin.y + row, + ), + white, + Operator::Source, + ); + } + }; + + buffer.clear_rect(cell_rect, black); + if key.overline { + draw_overline(&mut buffer); + } + match key.underline { + Underline::None => {} + Underline::Single | + // FIXME: these extra styles need to be rendered separately! + Underline::Curly | Underline::Dotted | Underline::Dashed => { + draw_single(&mut buffer) + } + Underline::Double => draw_double(&mut buffer), + } + if key.strike_through { + draw_strike(&mut buffer); + } + let sprite = self.atlas.allocate(&buffer)?; + self.line_glyphs.insert(key, sprite.clone()); + Ok(sprite) + } + + /// Figure out what we're going to draw for the underline. + /// If the current cell is part of the current URL highlight + /// then we want to show the underline. + pub fn cached_line_sprite( + &mut self, + is_highlited_hyperlink: bool, + is_strike_through: bool, + underline: Underline, + overline: bool, + ) -> anyhow::Result> { + let effective_underline = match (is_highlited_hyperlink, underline) { + (true, Underline::None) => Underline::Single, + (true, Underline::Single) => Underline::Double, + (true, _) => Underline::Single, + (false, u) => u, + }; + + let key = LineKey { + strike_through: is_strike_through, + overline, + underline: effective_underline, + }; + + if let Some(s) = self.line_glyphs.get(&key) { + return Ok(s.clone()); + } + + self.line_sprite(key) + } } diff --git a/wezterm-gui/src/gui/renderstate.rs b/wezterm-gui/src/gui/renderstate.rs index e25a450d4fa..03e1bb20c48 100644 --- a/wezterm-gui/src/gui/renderstate.rs +++ b/wezterm-gui/src/gui/renderstate.rs @@ -40,7 +40,8 @@ impl OpenGLRenderState { pixel_height: usize, ) -> anyhow::Result { loop { - let glyph_cache = RefCell::new(GlyphCache::new_gl(&context, fonts, atlas_size)?); + let glyph_cache = + RefCell::new(GlyphCache::new_gl(&context, fonts, atlas_size, metrics)?); let result = UtilSprites::new(&mut *glyph_cache.borrow_mut(), metrics); match result { Ok(util_sprites) => { @@ -267,7 +268,7 @@ impl RenderState { RenderState::Software(_) => {} RenderState::GL(gl) => { let size = size.unwrap_or_else(|| gl.glyph_cache.borrow().atlas.size()); - let mut glyph_cache = GlyphCache::new_gl(&gl.context, fonts, size)?; + let mut glyph_cache = GlyphCache::new_gl(&gl.context, fonts, size, metrics)?; gl.util_sprites = UtilSprites::new(&mut glyph_cache, metrics)?; *gl.glyph_cache.borrow_mut() = glyph_cache; } diff --git a/wezterm-gui/src/gui/termwindow.rs b/wezterm-gui/src/gui/termwindow.rs index d568e291137..40362c84117 100644 --- a/wezterm-gui/src/gui/termwindow.rs +++ b/wezterm-gui/src/gui/termwindow.rs @@ -2937,13 +2937,14 @@ impl TermWindow { // underline and strikethrough let underline_tex_rect = gl_state - .util_sprites - .select_sprite( + .glyph_cache + .borrow_mut() + .cached_line_sprite( is_highlited_hyperlink, attrs.strikethrough(), attrs.underline(), attrs.overline(), - ) + )? .texture_coords(); // Iterate each cell that comprises this glyph. There is usually diff --git a/wezterm-gui/src/gui/utilsprites.rs b/wezterm-gui/src/gui/utilsprites.rs index 61c9101c7e0..32cca8fbeec 100644 --- a/wezterm-gui/src/gui/utilsprites.rs +++ b/wezterm-gui/src/gui/utilsprites.rs @@ -7,7 +7,6 @@ use std::rc::Rc; use termwiz::surface::CursorShape; use wezterm_font::units::*; use wezterm_font::FontConfiguration; -use wezterm_term::Underline; #[derive(Copy, Clone, Debug)] pub struct RenderMetrics { @@ -53,20 +52,9 @@ impl RenderMetrics { pub struct UtilSprites { pub white_space: Sprite, - pub single_underline: Sprite, - pub double_underline: Sprite, - pub strike_through: Sprite, - pub single_and_strike: Sprite, - pub double_and_strike: Sprite, pub cursor_box: Sprite, pub cursor_i_beam: Sprite, pub cursor_underline: Sprite, - pub overline: Sprite, - pub single_under_over: Sprite, - pub double_under_over: Sprite, - pub strike_over: Sprite, - pub single_strike_over: Sprite, - pub double_strike_over: Sprite, } impl UtilSprites { @@ -87,136 +75,6 @@ impl UtilSprites { buffer.clear_rect(cell_rect, black); let white_space = glyph_cache.atlas.allocate(&buffer)?; - let draw_single = |buffer: &mut Image| { - for row in 0..metrics.underline_height { - buffer.draw_line( - Point::new( - cell_rect.origin.x, - cell_rect.origin.y + metrics.descender_row + row, - ), - Point::new( - cell_rect.origin.x + metrics.cell_size.width, - cell_rect.origin.y + metrics.descender_row + row, - ), - white, - Operator::Source, - ); - } - }; - - let draw_double = |buffer: &mut Image| { - for row in 0..metrics.underline_height { - buffer.draw_line( - Point::new( - cell_rect.origin.x, - cell_rect.origin.y + metrics.descender_row + row, - ), - Point::new( - cell_rect.origin.x + metrics.cell_size.width, - cell_rect.origin.y + metrics.descender_row + row, - ), - white, - Operator::Source, - ); - buffer.draw_line( - Point::new( - cell_rect.origin.x, - cell_rect.origin.y + metrics.descender_plus_two + row, - ), - Point::new( - cell_rect.origin.x + metrics.cell_size.width, - cell_rect.origin.y + metrics.descender_plus_two + row, - ), - white, - Operator::Source, - ); - } - }; - - let draw_strike = |buffer: &mut Image| { - for row in 0..metrics.underline_height { - buffer.draw_line( - Point::new( - cell_rect.origin.x, - cell_rect.origin.y + metrics.strike_row + row, - ), - Point::new( - cell_rect.origin.x + metrics.cell_size.width, - cell_rect.origin.y + metrics.strike_row + row, - ), - white, - Operator::Source, - ); - } - }; - - let draw_overline = |buffer: &mut Image| { - for row in 0..metrics.underline_height { - buffer.draw_line( - Point::new(cell_rect.origin.x, cell_rect.origin.y + row), - Point::new( - cell_rect.origin.x + metrics.cell_size.width, - cell_rect.origin.y + row, - ), - white, - Operator::Source, - ); - } - }; - - buffer.clear_rect(cell_rect, black); - draw_overline(&mut buffer); - let overline = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_single(&mut buffer); - let single_underline = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_overline(&mut buffer); - draw_single(&mut buffer); - let single_under_over = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_double(&mut buffer); - let double_underline = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_overline(&mut buffer); - draw_double(&mut buffer); - let double_under_over = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_strike(&mut buffer); - let strike_through = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_overline(&mut buffer); - draw_strike(&mut buffer); - let strike_over = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_single(&mut buffer); - draw_strike(&mut buffer); - let single_and_strike = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_overline(&mut buffer); - draw_single(&mut buffer); - draw_strike(&mut buffer); - let single_strike_over = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_double(&mut buffer); - draw_strike(&mut buffer); - let double_and_strike = glyph_cache.atlas.allocate(&buffer)?; - - buffer.clear_rect(cell_rect, black); - draw_overline(&mut buffer); - draw_double(&mut buffer); - draw_strike(&mut buffer); - let double_strike_over = glyph_cache.atlas.allocate(&buffer)?; - // Derive a width for the border box from the underline height, // but aspect ratio adjusted for width. let border_width = (metrics.underline_height as f64 * metrics.cell_size.width as f64 @@ -311,73 +169,12 @@ impl UtilSprites { Ok(Self { white_space, - single_underline, - double_underline, - strike_through, - single_and_strike, - double_and_strike, cursor_box, cursor_i_beam, cursor_underline, - overline, - single_under_over, - double_under_over, - strike_over, - single_strike_over, - double_strike_over, }) } - /// Figure out what we're going to draw for the underline. - /// If the current cell is part of the current URL highlight - /// then we want to show the underline. - pub fn select_sprite( - &self, - is_highlited_hyperlink: bool, - is_strike_through: bool, - underline: Underline, - overline: bool, - ) -> &Sprite { - match ( - is_highlited_hyperlink, - is_strike_through, - underline, - overline, - ) { - (true, false, Underline::None, false) => &self.single_underline, - (true, false, Underline::Single, false) => &self.double_underline, - (true, false, Underline::Double, false) => &self.single_underline, - (true, true, Underline::None, false) => &self.strike_through, - (true, true, Underline::Single, false) => &self.single_and_strike, - (true, true, Underline::Double, false) => &self.double_and_strike, - (false, false, Underline::None, false) => &self.white_space, - (false, false, Underline::Single, false) => &self.single_underline, - (false, false, Underline::Double, false) => &self.double_underline, - (false, true, Underline::None, false) => &self.strike_through, - (false, true, Underline::Single, false) => &self.single_and_strike, - (false, true, Underline::Double, false) => &self.double_and_strike, - - (true, false, Underline::None, true) => &self.single_under_over, - (true, false, Underline::Single, true) => &self.double_under_over, - (true, false, Underline::Double, true) => &self.single_under_over, - (true, true, Underline::None, true) => &self.strike_over, - (true, true, Underline::Single, true) => &self.single_strike_over, - (true, true, Underline::Double, true) => &self.double_strike_over, - (false, false, Underline::None, true) => &self.overline, - (false, false, Underline::Single, true) => &self.single_under_over, - (false, false, Underline::Double, true) => &self.double_under_over, - (false, true, Underline::None, true) => &self.strike_over, - (false, true, Underline::Single, true) => &self.single_strike_over, - (false, true, Underline::Double, true) => &self.double_strike_over, - - // FIXME: these are just placeholders under we render - // these things properly - (_, _, Underline::Curly, _) => &self.double_underline, - (_, _, Underline::Dotted, _) => &self.double_underline, - (_, _, Underline::Dashed, _) => &self.double_underline, - } - } - pub fn cursor_sprite(&self, shape: Option) -> &Sprite { match shape { None => &self.white_space,