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/atlas.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/atlas.rs')
-rw-r--r-- | alacritty/src/renderer/text/atlas.rs | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/alacritty/src/renderer/text/atlas.rs b/alacritty/src/renderer/text/atlas.rs new file mode 100644 index 00000000..97c8b0b4 --- /dev/null +++ b/alacritty/src/renderer/text/atlas.rs @@ -0,0 +1,273 @@ +use std::ptr; + +use crossfont::{BitmapBuffer, RasterizedGlyph}; + +use crate::gl; +use crate::gl::types::*; + +use super::Glyph; + +/// Size of the Atlas. +pub const ATLAS_SIZE: i32 = 1024; + +/// Manages a single texture atlas. +/// +/// The strategy for filling an atlas looks roughly like this: +/// +/// ```text +/// (width, height) +/// ┌─────┬─────┬─────┬─────┬─────┐ +/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while +/// │ │ │ │ │ │ glyph_height < height - row_baseline +/// ├─────┼─────┼─────┼─────┼─────┤ +/// │ 5 │ 6 │ 7 │ 8 │ 9 │ +/// │ │ │ │ │ │ +/// ├─────┼─────┼─────┼─────┴─────┤ <- Row height is tallest glyph in row; this is +/// │ 1 │ 2 │ 3 │ 4 │ used as the baseline for the following row. +/// │ │ │ │ │ <- Row considered full when next glyph doesn't +/// └─────┴─────┴─────┴───────────┘ fit in the row. +/// (0, 0) x-> +/// ``` +#[derive(Debug)] +pub struct Atlas { + /// Texture id for this atlas. + id: GLuint, + + /// Width of atlas. + width: i32, + + /// Height of atlas. + height: i32, + + /// Left-most free pixel in a row. + /// + /// This is called the extent because it is the upper bound of used pixels + /// in a row. + row_extent: i32, + + /// Baseline for glyphs in the current row. + row_baseline: i32, + + /// Tallest glyph in current row. + /// + /// This is used as the advance when end of row is reached. + row_tallest: i32, +} + +/// Error that can happen when inserting a texture to the Atlas. +pub enum AtlasInsertError { + /// Texture atlas is full. + Full, + + /// The glyph cannot fit within a single texture. + GlyphTooLarge, +} + +impl Atlas { + pub fn new(size: i32) -> Self { + let mut id: GLuint = 0; + unsafe { + gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); + gl::GenTextures(1, &mut id); + gl::BindTexture(gl::TEXTURE_2D, id); + // Use RGBA texture for both normal and emoji glyphs, since it has no performance + // impact. + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA as i32, + size, + size, + 0, + gl::RGBA, + gl::UNSIGNED_BYTE, + ptr::null(), + ); + + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); + + gl::BindTexture(gl::TEXTURE_2D, 0); + } + + Self { id, width: size, height: size, row_extent: 0, row_baseline: 0, row_tallest: 0 } + } + + pub fn clear(&mut self) { + self.row_extent = 0; + self.row_baseline = 0; + self.row_tallest = 0; + } + + /// Insert a RasterizedGlyph into the texture atlas. + pub fn insert( + &mut self, + glyph: &RasterizedGlyph, + active_tex: &mut u32, + ) -> Result<Glyph, AtlasInsertError> { + if glyph.width > self.width || glyph.height > self.height { + return Err(AtlasInsertError::GlyphTooLarge); + } + + // If there's not enough room in current row, go onto next one. + if !self.room_in_row(glyph) { + self.advance_row()?; + } + + // If there's still not room, there's nothing that can be done here.. + if !self.room_in_row(glyph) { + return Err(AtlasInsertError::Full); + } + + // There appears to be room; load the glyph. + Ok(self.insert_inner(glyph, active_tex)) + } + + /// Insert the glyph without checking for room. + /// + /// Internal function for use once atlas has been checked for space. GL + /// errors could still occur at this point if we were checking for them; + /// hence, the Result. + fn insert_inner(&mut self, glyph: &RasterizedGlyph, active_tex: &mut u32) -> Glyph { + let offset_y = self.row_baseline; + let offset_x = self.row_extent; + let height = glyph.height as i32; + let width = glyph.width as i32; + let multicolor; + + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.id); + + // Load data into OpenGL. + let (format, buffer) = match &glyph.buffer { + BitmapBuffer::Rgb(buffer) => { + multicolor = false; + (gl::RGB, buffer) + }, + BitmapBuffer::Rgba(buffer) => { + multicolor = true; + (gl::RGBA, buffer) + }, + }; + + gl::TexSubImage2D( + gl::TEXTURE_2D, + 0, + offset_x, + offset_y, + width, + height, + format, + gl::UNSIGNED_BYTE, + buffer.as_ptr() as *const _, + ); + + gl::BindTexture(gl::TEXTURE_2D, 0); + *active_tex = 0; + } + + // Update Atlas state. + self.row_extent = offset_x + width; + if height > self.row_tallest { + self.row_tallest = height; + } + + // Generate UV coordinates. + let uv_bot = offset_y as f32 / self.height as f32; + let uv_left = offset_x as f32 / self.width as f32; + let uv_height = height as f32 / self.height as f32; + let uv_width = width as f32 / self.width as f32; + + Glyph { + tex_id: self.id, + multicolor, + top: glyph.top as i16, + left: glyph.left as i16, + width: width as i16, + height: height as i16, + uv_bot, + uv_left, + uv_width, + uv_height, + } + } + + /// Check if there's room in the current row for given glyph. + pub fn room_in_row(&self, raw: &RasterizedGlyph) -> bool { + let next_extent = self.row_extent + raw.width as i32; + let enough_width = next_extent <= self.width; + let enough_height = (raw.height as i32) < (self.height - self.row_baseline); + + enough_width && enough_height + } + + /// Mark current row as finished and prepare to insert into the next row. + pub fn advance_row(&mut self) -> Result<(), AtlasInsertError> { + let advance_to = self.row_baseline + self.row_tallest; + if self.height - advance_to <= 0 { + return Err(AtlasInsertError::Full); + } + + self.row_baseline = advance_to; + self.row_extent = 0; + self.row_tallest = 0; + + Ok(()) + } + + /// Load a glyph into a texture atlas. + /// + /// If the current atlas is full, a new one will be created. + #[inline] + pub fn load_glyph( + active_tex: &mut GLuint, + atlas: &mut Vec<Atlas>, + current_atlas: &mut usize, + rasterized: &RasterizedGlyph, + ) -> Glyph { + // At least one atlas is guaranteed to be in the `self.atlas` list; thus + // the unwrap. + match atlas[*current_atlas].insert(rasterized, active_tex) { + Ok(glyph) => glyph, + Err(AtlasInsertError::Full) => { + *current_atlas += 1; + if *current_atlas == atlas.len() { + let new = Atlas::new(ATLAS_SIZE); + *active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy. + atlas.push(new); + } + Atlas::load_glyph(active_tex, atlas, current_atlas, rasterized) + }, + Err(AtlasInsertError::GlyphTooLarge) => Glyph { + tex_id: atlas[*current_atlas].id, + multicolor: false, + top: 0, + left: 0, + width: 0, + height: 0, + uv_bot: 0., + uv_left: 0., + uv_width: 0., + uv_height: 0., + }, + } + } + + #[inline] + pub fn clear_atlas(atlas: &mut Vec<Atlas>, current_atlas: &mut usize) { + for atlas in atlas.iter_mut() { + atlas.clear(); + } + *current_atlas = 0; + } +} + +impl Drop for Atlas { + fn drop(&mut self) { + unsafe { + gl::DeleteTextures(1, &self.id); + } + } +} |