aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/renderer/text/atlas.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/atlas.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/atlas.rs')
-rw-r--r--alacritty/src/renderer/text/atlas.rs273
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);
+ }
+ }
+}