alacritty/alacritty/src/renderer/text/atlas.rs

274 lines
8.5 KiB
Rust

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);
}
}
}