aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/renderer/text/glyph_cache.rs
diff options
context:
space:
mode:
authorKirill Chibisov <contact@kchibisov.com>2022-03-02 13:05:12 +0300
committerGitHub <noreply@github.com>2022-03-02 10:05:12 +0000
commit1880522b64d9a5276acea428705c011cbbf8c04c (patch)
tree2f8a662ee0b65fcf6d6fe831949cd2cf78cabdad /alacritty/src/renderer/text/glyph_cache.rs
parent00383ae967fef6216396c3acaf96a7002b013298 (diff)
downloadr-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.rs322
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, &regular_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);
+ }
+}