diff options
author | Kirill Chibisov <contact@kchibisov.com> | 2022-03-02 13:05:12 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-02 10:05:12 +0000 |
commit | 1880522b64d9a5276acea428705c011cbbf8c04c (patch) | |
tree | 2f8a662ee0b65fcf6d6fe831949cd2cf78cabdad /alacritty/src/renderer/text/glyph_cache.rs | |
parent | 00383ae967fef6216396c3acaf96a7002b013298 (diff) | |
download | r-alacritty-1880522b64d9a5276acea428705c011cbbf8c04c.tar.gz r-alacritty-1880522b64d9a5276acea428705c011cbbf8c04c.tar.bz2 r-alacritty-1880522b64d9a5276acea428705c011cbbf8c04c.zip |
Add fallback GLES2 renderer
Currently Alacritty only works on hardware which supports OpenGL 3.3 or
more, which can become problematic with older devices. This patch adds a
new GLES2 renderer, since it is much more widely supported, especially
on weaker hardware like phones or a Raspberry Pi.
While the GLES2 renderer is slower than the OpenGL 3.3+ version, it is
still significantly faster than software rendering. However because of
this performance difference it is only used when necessary and there
should be no difference for machines supporting OpenGL 3.3+.
The two renderers are largely independent and separated in the
`renderer/text/glsl3` and `renderer/text/gles2` modules. Separate
shaders are also required for text rendering. The rectangle rendering
for underlines and the visual bell works identically for both versions,
but does have some version-specific shader code.
Fixes #128.
Co-authored-by: Christian Duerr <contact@christianduerr.com>
Diffstat (limited to 'alacritty/src/renderer/text/glyph_cache.rs')
-rw-r--r-- | alacritty/src/renderer/text/glyph_cache.rs | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/alacritty/src/renderer/text/glyph_cache.rs b/alacritty/src/renderer/text/glyph_cache.rs new file mode 100644 index 00000000..c75cad7a --- /dev/null +++ b/alacritty/src/renderer/text/glyph_cache.rs @@ -0,0 +1,322 @@ +use std::collections::HashMap; +use std::hash::BuildHasherDefault; + +use crossfont::{ + Error as RasterizerError, FontDesc, FontKey, GlyphKey, Metrics, Rasterize, RasterizedGlyph, + Rasterizer, Size, Slant, Style, Weight, +}; +use fnv::FnvHasher; +use log::{error, info}; +use unicode_width::UnicodeWidthChar; + +use crate::config::font::{Font, FontDescription}; +use crate::config::ui_config::Delta; +use crate::gl::types::*; + +use super::builtin_font; + +/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory. +pub trait LoadGlyph { + /// Load the rasterized glyph into GPU memory. + fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph; + + /// Clear any state accumulated from previous loaded glyphs. + /// + /// This can, for instance, be used to reset the texture Atlas. + fn clear(&mut self); +} + +#[derive(Copy, Clone, Debug)] +pub struct Glyph { + pub tex_id: GLuint, + pub multicolor: bool, + pub top: i16, + pub left: i16, + pub width: i16, + pub height: i16, + pub uv_bot: f32, + pub uv_left: f32, + pub uv_width: f32, + pub uv_height: f32, +} + +/// Naïve glyph cache. +/// +/// Currently only keyed by `char`, and thus not possible to hold different +/// representations of the same code point. +pub struct GlyphCache { + /// Cache of buffered glyphs. + cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>, + + /// Rasterizer for loading new glyphs. + rasterizer: Rasterizer, + + /// Regular font. + pub font_key: FontKey, + + /// Bold font. + pub bold_key: FontKey, + + /// Italic font. + pub italic_key: FontKey, + + /// Bold italic font. + pub bold_italic_key: FontKey, + + /// Font size. + pub font_size: crossfont::Size, + + /// Font offset. + font_offset: Delta<i8>, + + /// Glyph offset. + glyph_offset: Delta<i8>, + + /// Font metrics. + metrics: Metrics, + + /// Whether to use the built-in font for box drawing characters. + builtin_box_drawing: bool, +} + +impl GlyphCache { + pub fn new(mut rasterizer: Rasterizer, font: &Font) -> Result<GlyphCache, crossfont::Error> { + let (regular, bold, italic, bold_italic) = Self::compute_font_keys(font, &mut rasterizer)?; + + // Need to load at least one glyph for the face before calling metrics. + // The glyph requested here ('m' at the time of writing) has no special + // meaning. + rasterizer.get_glyph(GlyphKey { font_key: regular, character: 'm', size: font.size() })?; + + let metrics = rasterizer.metrics(regular, font.size())?; + + Ok(Self { + cache: HashMap::default(), + rasterizer, + font_size: font.size(), + font_key: regular, + bold_key: bold, + italic_key: italic, + bold_italic_key: bold_italic, + font_offset: font.offset, + glyph_offset: font.glyph_offset, + metrics, + builtin_box_drawing: font.builtin_box_drawing, + }) + } + + fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) { + let size = self.font_size; + + // Cache all ascii characters. + for i in 32u8..=126u8 { + self.get(GlyphKey { font_key: font, character: i as char, size }, loader, true); + } + } + + /// Computes font keys for (Regular, Bold, Italic, Bold Italic). + fn compute_font_keys( + font: &Font, + rasterizer: &mut Rasterizer, + ) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> { + let size = font.size(); + + // Load regular font. + let regular_desc = Self::make_desc(font.normal(), Slant::Normal, Weight::Normal); + + let regular = Self::load_regular_font(rasterizer, ®ular_desc, size)?; + + // Helper to load a description if it is not the `regular_desc`. + let mut load_or_regular = |desc: FontDesc| { + if desc == regular_desc { + regular + } else { + rasterizer.load_font(&desc, size).unwrap_or(regular) + } + }; + + // Load bold font. + let bold_desc = Self::make_desc(&font.bold(), Slant::Normal, Weight::Bold); + + let bold = load_or_regular(bold_desc); + + // Load italic font. + let italic_desc = Self::make_desc(&font.italic(), Slant::Italic, Weight::Normal); + + let italic = load_or_regular(italic_desc); + + // Load bold italic font. + let bold_italic_desc = Self::make_desc(&font.bold_italic(), Slant::Italic, Weight::Bold); + + let bold_italic = load_or_regular(bold_italic_desc); + + Ok((regular, bold, italic, bold_italic)) + } + + fn load_regular_font( + rasterizer: &mut Rasterizer, + description: &FontDesc, + size: Size, + ) -> Result<FontKey, crossfont::Error> { + match rasterizer.load_font(description, size) { + Ok(font) => Ok(font), + Err(err) => { + error!("{}", err); + + let fallback_desc = + Self::make_desc(Font::default().normal(), Slant::Normal, Weight::Normal); + rasterizer.load_font(&fallback_desc, size) + }, + } + } + + fn make_desc(desc: &FontDescription, slant: Slant, weight: Weight) -> FontDesc { + let style = if let Some(ref spec) = desc.style { + Style::Specific(spec.to_owned()) + } else { + Style::Description { slant, weight } + }; + FontDesc::new(desc.family.clone(), style) + } + + /// Get a glyph from the font. + /// + /// If the glyph has never been loaded before, it will be rasterized and inserted into the + /// cache. + /// + /// # Errors + /// + /// This will fail when the glyph could not be rasterized. Usually this is due to the glyph + /// not being present in any font. + pub fn get<L: ?Sized>( + &mut self, + glyph_key: GlyphKey, + loader: &mut L, + show_missing: bool, + ) -> Glyph + where + L: LoadGlyph, + { + // Try to load glyph from cache. + if let Some(glyph) = self.cache.get(&glyph_key) { + return *glyph; + }; + + // Rasterize the glyph using the built-in font for special characters or the user's font + // for everything else. + let rasterized = self + .builtin_box_drawing + .then(|| { + builtin_font::builtin_glyph( + glyph_key.character, + &self.metrics, + &self.font_offset, + &self.glyph_offset, + ) + }) + .flatten() + .map_or_else(|| self.rasterizer.get_glyph(glyph_key), Ok); + + let glyph = match rasterized { + Ok(rasterized) => self.load_glyph(loader, rasterized), + // Load fallback glyph. + Err(RasterizerError::MissingGlyph(rasterized)) if show_missing => { + // Use `\0` as "missing" glyph to cache it only once. + let missing_key = GlyphKey { character: '\0', ..glyph_key }; + if let Some(glyph) = self.cache.get(&missing_key) { + *glyph + } else { + // If no missing glyph was loaded yet, insert it as `\0`. + let glyph = self.load_glyph(loader, rasterized); + self.cache.insert(missing_key, glyph); + + glyph + } + }, + Err(_) => self.load_glyph(loader, Default::default()), + }; + + // Cache rasterized glyph. + *self.cache.entry(glyph_key).or_insert(glyph) + } + + /// Load glyph into the atlas. + /// + /// This will apply all transforms defined for the glyph cache to the rasterized glyph before + pub fn load_glyph<L: ?Sized>(&self, loader: &mut L, mut glyph: RasterizedGlyph) -> Glyph + where + L: LoadGlyph, + { + glyph.left += i32::from(self.glyph_offset.x); + glyph.top += i32::from(self.glyph_offset.y); + glyph.top -= self.metrics.descent as i32; + + // The metrics of zero-width characters are based on rendering + // the character after the current cell, with the anchor at the + // right side of the preceding character. Since we render the + // zero-width characters inside the preceding character, the + // anchor has been moved to the right by one cell. + if glyph.character.width() == Some(0) { + glyph.left += self.metrics.average_advance as i32; + } + + // Add glyph to cache. + loader.load_glyph(&glyph) + } + + /// Clear currently cached data in both GL and the registry. + pub fn clear_glyph_cache<L: LoadGlyph>(&mut self, loader: &mut L) { + loader.clear(); + self.cache = HashMap::default(); + + self.load_common_glyphs(loader); + } + + pub fn update_font_size<L: LoadGlyph>( + &mut self, + font: &Font, + scale_factor: f64, + loader: &mut L, + ) -> Result<(), crossfont::Error> { + // Update dpi scaling. + self.rasterizer.update_dpr(scale_factor as f32); + self.font_offset = font.offset; + + // Recompute font keys. + let (regular, bold, italic, bold_italic) = + Self::compute_font_keys(font, &mut self.rasterizer)?; + + self.rasterizer.get_glyph(GlyphKey { + font_key: regular, + character: 'm', + size: font.size(), + })?; + let metrics = self.rasterizer.metrics(regular, font.size())?; + + info!("Font size changed to {:?} with scale factor of {}", font.size(), scale_factor); + + self.font_size = font.size(); + self.font_key = regular; + self.bold_key = bold; + self.italic_key = italic; + self.bold_italic_key = bold_italic; + self.metrics = metrics; + self.builtin_box_drawing = font.builtin_box_drawing; + + self.clear_glyph_cache(loader); + + Ok(()) + } + + pub fn font_metrics(&self) -> crossfont::Metrics { + self.metrics + } + + /// Prefetch glyphs that are almost guaranteed to be loaded anyways. + pub fn load_common_glyphs<L: LoadGlyph>(&mut self, loader: &mut L) { + self.load_glyphs_for_font(self.font_key, loader); + self.load_glyphs_for_font(self.bold_key, loader); + self.load_glyphs_for_font(self.italic_key, loader); + self.load_glyphs_for_font(self.bold_italic_key, loader); + } +} |