diff options
Diffstat (limited to 'alacritty/src/renderer/text')
-rw-r--r-- | alacritty/src/renderer/text/atlas.rs | 273 | ||||
-rw-r--r-- | alacritty/src/renderer/text/builtin_font.rs | 823 | ||||
-rw-r--r-- | alacritty/src/renderer/text/gles2.rs | 478 | ||||
-rw-r--r-- | alacritty/src/renderer/text/glsl3.rs | 460 | ||||
-rw-r--r-- | alacritty/src/renderer/text/glyph_cache.rs | 322 | ||||
-rw-r--r-- | alacritty/src/renderer/text/mod.rs | 202 |
6 files changed, 2558 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); + } + } +} diff --git a/alacritty/src/renderer/text/builtin_font.rs b/alacritty/src/renderer/text/builtin_font.rs new file mode 100644 index 00000000..0922d3ef --- /dev/null +++ b/alacritty/src/renderer/text/builtin_font.rs @@ -0,0 +1,823 @@ +//! Hand-rolled drawing of unicode [box drawing](http://www.unicode.org/charts/PDF/U2500.pdf) +//! and [block elements](https://www.unicode.org/charts/PDF/U2580.pdf). + +use std::{cmp, mem, ops}; + +use crossfont::{BitmapBuffer, Metrics, RasterizedGlyph}; + +use crate::config::ui_config::Delta; + +// Colors which are used for filling shade variants. +const COLOR_FILL_ALPHA_STEP_1: Pixel = Pixel { _r: 192, _g: 192, _b: 192 }; +const COLOR_FILL_ALPHA_STEP_2: Pixel = Pixel { _r: 128, _g: 128, _b: 128 }; +const COLOR_FILL_ALPHA_STEP_3: Pixel = Pixel { _r: 64, _g: 64, _b: 64 }; + +/// Default color used for filling. +const COLOR_FILL: Pixel = Pixel { _r: 255, _g: 255, _b: 255 }; + +/// Returns the rasterized glyph if the character is part of the built-in font. +pub fn builtin_glyph( + character: char, + metrics: &Metrics, + offset: &Delta<i8>, + glyph_offset: &Delta<i8>, +) -> Option<RasterizedGlyph> { + let mut glyph = match character { + // Box drawing characters and block elements. + '\u{2500}'..='\u{259f}' => box_drawing(character, metrics, offset), + _ => return None, + }; + + // Since we want to ignore `glyph_offset` for the built-in font, subtract it to compensate its + // addition when loading glyphs in the renderer. + glyph.left -= glyph_offset.x as i32; + glyph.top -= glyph_offset.y as i32; + + Some(glyph) +} + +fn box_drawing(character: char, metrics: &Metrics, offset: &Delta<i8>) -> RasterizedGlyph { + let height = (metrics.line_height as i32 + offset.y as i32) as usize; + let width = (metrics.average_advance as i32 + offset.x as i32) as usize; + // Use one eight of the cell width, since this is used as a step size for block elemenets. + let stroke_size = cmp::max((width as f32 / 8.).round() as usize, 1); + let heavy_stroke_size = stroke_size * 2; + + // Certain symbols require larger canvas than the cell itself, since for proper contiguous + // lines they require drawing on neighbour cells. So treat them specially early on and handle + // 'normal' characters later. + let mut canvas = match character { + // Diagonals: '╱', '╲', '╳'. + '\u{2571}'..='\u{2573}' => { + // Last coordinates. + let x_end = width as f32; + let mut y_end = height as f32; + + let top = height as i32 + metrics.descent as i32 + stroke_size as i32; + let height = height + 2 * stroke_size; + let mut canvas = Canvas::new(width, height + 2 * stroke_size); + + // The offset that we should take into account when drawing, since we've enlarged + // buffer vertically by twice of that amount. + let y_offset = stroke_size as f32; + y_end += y_offset; + + let k = y_end / x_end; + let f_x = |x: f32, h: f32| -> f32 { -1. * k * x + h + y_offset }; + let g_x = |x: f32, h: f32| -> f32 { k * x + h + y_offset }; + + let from_x = 0.; + let to_x = x_end + 1.; + for stroke_size in 0..2 * stroke_size { + let stroke_size = stroke_size as f32 / 2.; + if character == '\u{2571}' || character == '\u{2573}' { + let h = y_end - stroke_size as f32; + let from_y = f_x(from_x, h); + let to_y = f_x(to_x, h); + canvas.draw_line(from_x, from_y, to_x, to_y); + } + if character == '\u{2572}' || character == '\u{2573}' { + let from_y = g_x(from_x, stroke_size as f32); + let to_y = g_x(to_x, stroke_size as f32); + canvas.draw_line(from_x, from_y, to_x, to_y); + } + } + + let buffer = BitmapBuffer::Rgb(canvas.into_raw()); + return RasterizedGlyph { + character, + top, + left: 0, + height: height as i32, + width: width as i32, + buffer, + }; + }, + _ => Canvas::new(width, height), + }; + + match character { + // Horizontal dashes: '┄', '┅', '┈', '┉', '╌', '╍'. + '\u{2504}' | '\u{2505}' | '\u{2508}' | '\u{2509}' | '\u{254c}' | '\u{254d}' => { + let (num_gaps, stroke_size) = match character { + '\u{2504}' => (2, stroke_size), + '\u{2505}' => (2, heavy_stroke_size), + '\u{2508}' => (3, stroke_size), + '\u{2509}' => (3, heavy_stroke_size), + '\u{254c}' => (1, stroke_size), + '\u{254d}' => (1, heavy_stroke_size), + _ => unreachable!(), + }; + + let dash_gap_len = cmp::max(width / 8, 1); + let dash_len = + cmp::max(width.saturating_sub(dash_gap_len * num_gaps) / (num_gaps + 1), 1); + let y = canvas.y_center(); + for gap in 0..=num_gaps { + let x = cmp::min(gap * (dash_len + dash_gap_len), width); + canvas.draw_h_line(x as f32, y, dash_len as f32, stroke_size); + } + }, + // Vertical dashes: '┆', '┇', '┊', '┋', '╎', '╏'. + '\u{2506}' | '\u{2507}' | '\u{250a}' | '\u{250b}' | '\u{254e}' | '\u{254f}' => { + let (num_gaps, stroke_size) = match character { + '\u{2506}' => (2, stroke_size), + '\u{2507}' => (2, heavy_stroke_size), + '\u{250a}' => (3, stroke_size), + '\u{250b}' => (3, heavy_stroke_size), + '\u{254e}' => (1, stroke_size), + '\u{254f}' => (1, heavy_stroke_size), + _ => unreachable!(), + }; + + let dash_gap_len = cmp::max(height / 8, 1); + let dash_len = + cmp::max(height.saturating_sub(dash_gap_len * num_gaps) / (num_gaps + 1), 1); + let x = canvas.x_center(); + for gap in 0..=num_gaps { + let y = cmp::min(gap * (dash_len + dash_gap_len), height); + canvas.draw_v_line(x, y as f32, dash_len as f32, stroke_size); + } + }, + // Horizontal lines: '─', '━', '╴', '╶', '╸', '╺'. + // Vertical lines: '│', '┃', '╵', '╷', '╹', '╻'. + // Light and heavy line box components: + // '┌','┍','┎','┏','┐','┑','┒','┓','└','┕','┖','┗','┘','┙','┚','┛',├','┝','┞','┟','┠','┡', + // '┢','┣','┤','┥','┦','┧','┨','┩','┪','┫','┬','┭','┮','┯','┰','┱','┲','┳','┴','┵','┶','┷', + // '┸','┹','┺','┻','┼','┽','┾','┿','╀','╁','╂','╃','╄','╅','╆','╇','╈','╉','╊','╋'. + // Mixed light and heavy lines: '╼', '╽', '╾', '╿'. + '\u{2500}'..='\u{2503}' | '\u{250c}'..='\u{254b}' | '\u{2574}'..='\u{257f}' => { + // Left horizontal line. + let stroke_size_h1 = match character { + '\u{2500}' | '\u{2510}' | '\u{2512}' | '\u{2518}' | '\u{251a}' | '\u{2524}' + | '\u{2526}' | '\u{2527}' | '\u{2528}' | '\u{252c}' | '\u{252e}' | '\u{2530}' + | '\u{2532}' | '\u{2534}' | '\u{2536}' | '\u{2538}' | '\u{253a}' | '\u{253c}' + | '\u{253e}' | '\u{2540}' | '\u{2541}' | '\u{2542}' | '\u{2544}' | '\u{2546}' + | '\u{254a}' | '\u{2574}' | '\u{257c}' => stroke_size, + '\u{2501}' | '\u{2511}' | '\u{2513}' | '\u{2519}' | '\u{251b}' | '\u{2525}' + | '\u{2529}' | '\u{252a}' | '\u{252b}' | '\u{252d}' | '\u{252f}' | '\u{2531}' + | '\u{2533}' | '\u{2535}' | '\u{2537}' | '\u{2539}' | '\u{253b}' | '\u{253d}' + | '\u{253f}' | '\u{2543}' | '\u{2545}' | '\u{2547}' | '\u{2548}' | '\u{2549}' + | '\u{254b}' | '\u{2578}' | '\u{257e}' => heavy_stroke_size, + _ => 0, + }; + // Right horizontal line. + let stroke_size_h2 = match character { + '\u{2500}' | '\u{250c}' | '\u{250e}' | '\u{2514}' | '\u{2516}' | '\u{251c}' + | '\u{251e}' | '\u{251f}' | '\u{2520}' | '\u{252c}' | '\u{252d}' | '\u{2530}' + | '\u{2531}' | '\u{2534}' | '\u{2535}' | '\u{2538}' | '\u{2539}' | '\u{253c}' + | '\u{253d}' | '\u{2540}' | '\u{2541}' | '\u{2542}' | '\u{2543}' | '\u{2545}' + | '\u{2549}' | '\u{2576}' | '\u{257e}' => stroke_size, + '\u{2501}' | '\u{250d}' | '\u{250f}' | '\u{2515}' | '\u{2517}' | '\u{251d}' + | '\u{2521}' | '\u{2522}' | '\u{2523}' | '\u{252e}' | '\u{252f}' | '\u{2532}' + | '\u{2533}' | '\u{2536}' | '\u{2537}' | '\u{253a}' | '\u{253b}' | '\u{253e}' + | '\u{253f}' | '\u{2544}' | '\u{2546}' | '\u{2547}' | '\u{2548}' | '\u{254a}' + | '\u{254b}' | '\u{257a}' | '\u{257c}' => heavy_stroke_size, + _ => 0, + }; + // Top vertical line. + let stroke_size_v1 = match character { + '\u{2502}' | '\u{2514}' | '\u{2515}' | '\u{2518}' | '\u{2519}' | '\u{251c}' + | '\u{251d}' | '\u{251f}' | '\u{2522}' | '\u{2524}' | '\u{2525}' | '\u{2527}' + | '\u{252a}' | '\u{2534}' | '\u{2535}' | '\u{2536}' | '\u{2537}' | '\u{253c}' + | '\u{253d}' | '\u{253e}' | '\u{253f}' | '\u{2541}' | '\u{2545}' | '\u{2546}' + | '\u{2548}' | '\u{2575}' | '\u{257d}' => stroke_size, + '\u{2503}' | '\u{2516}' | '\u{2517}' | '\u{251a}' | '\u{251b}' | '\u{251e}' + | '\u{2520}' | '\u{2521}' | '\u{2523}' | '\u{2526}' | '\u{2528}' | '\u{2529}' + | '\u{252b}' | '\u{2538}' | '\u{2539}' | '\u{253a}' | '\u{253b}' | '\u{2540}' + | '\u{2542}' | '\u{2543}' | '\u{2544}' | '\u{2547}' | '\u{2549}' | '\u{254a}' + | '\u{254b}' | '\u{2579}' | '\u{257f}' => heavy_stroke_size, + _ => 0, + }; + // Bottom vertical line. + let stroke_size_v2 = match character { + '\u{2502}' | '\u{250c}' | '\u{250d}' | '\u{2510}' | '\u{2511}' | '\u{251c}' + | '\u{251d}' | '\u{251e}' | '\u{2521}' | '\u{2524}' | '\u{2525}' | '\u{2526}' + | '\u{2529}' | '\u{252c}' | '\u{252d}' | '\u{252e}' | '\u{252f}' | '\u{253c}' + | '\u{253d}' | '\u{253e}' | '\u{253f}' | '\u{2540}' | '\u{2543}' | '\u{2544}' + | '\u{2547}' | '\u{2577}' | '\u{257f}' => stroke_size, + '\u{2503}' | '\u{250e}' | '\u{250f}' | '\u{2512}' | '\u{2513}' | '\u{251f}' + | '\u{2520}' | '\u{2522}' | '\u{2523}' | '\u{2527}' | '\u{2528}' | '\u{252a}' + | '\u{252b}' | '\u{2530}' | '\u{2531}' | '\u{2532}' | '\u{2533}' | '\u{2541}' + | '\u{2542}' | '\u{2545}' | '\u{2546}' | '\u{2548}' | '\u{2549}' | '\u{254a}' + | '\u{254b}' | '\u{257b}' | '\u{257d}' => heavy_stroke_size, + _ => 0, + }; + + let x_v = canvas.x_center(); + let y_h = canvas.y_center(); + + let v_line_bounds_top = canvas.v_line_bounds(x_v, stroke_size_v1); + let v_line_bounds_bot = canvas.v_line_bounds(x_v, stroke_size_v2); + let h_line_bounds_left = canvas.h_line_bounds(y_h, stroke_size_h1); + let h_line_bounds_right = canvas.h_line_bounds(y_h, stroke_size_h2); + + let size_h1 = cmp::max(v_line_bounds_top.1 as i32, v_line_bounds_bot.1 as i32) as f32; + let x_h = cmp::min(v_line_bounds_top.0 as i32, v_line_bounds_bot.0 as i32) as f32; + let size_h2 = width as f32 - x_h; + + let size_v1 = + cmp::max(h_line_bounds_left.1 as i32, h_line_bounds_right.1 as i32) as f32; + let y_v = cmp::min(h_line_bounds_left.0 as i32, h_line_bounds_right.0 as i32) as f32; + let size_v2 = height as f32 - y_v; + + // Left horizontal line. + canvas.draw_h_line(0., y_h, size_h1, stroke_size_h1); + // Right horizontal line. + canvas.draw_h_line(x_h, y_h, size_h2, stroke_size_h2); + // Top vertical line. + canvas.draw_v_line(x_v, 0., size_v1, stroke_size_v1); + // Bottom vertical line. + canvas.draw_v_line(x_v, y_v, size_v2, stroke_size_v2); + }, + // Light and double line box components: + // '═','║','╒','╓','╔','╕','╖','╗','╘','╙','╚','╛','╜','╝','╞','╟','╠','╡','╢','╣','╤','╥', + // '╦','╧','╨','╩','╪','╫','╬'. + '\u{2550}'..='\u{256c}' => { + let v_lines = match character { + '\u{2552}' | '\u{2555}' | '\u{2558}' | '\u{255b}' | '\u{255e}' | '\u{2561}' + | '\u{2564}' | '\u{2567}' | '\u{256a}' => (canvas.x_center(), canvas.x_center()), + _ => { + let v_line_bounds = canvas.v_line_bounds(canvas.x_center(), stroke_size); + let left_line = cmp::max(v_line_bounds.0 as i32 - 1, 0) as f32; + let right_line = cmp::min(v_line_bounds.1 as i32 + 1, width as i32) as f32; + + (left_line, right_line) + }, + }; + let h_lines = match character { + '\u{2553}' | '\u{2556}' | '\u{2559}' | '\u{255c}' | '\u{255f}' | '\u{2562}' + | '\u{2565}' | '\u{2568}' | '\u{256b}' => (canvas.y_center(), canvas.y_center()), + _ => { + let h_line_bounds = canvas.h_line_bounds(canvas.y_center(), stroke_size); + let top_line = cmp::max(h_line_bounds.0 as i32 - 1, 0) as f32; + let bottom_line = cmp::min(h_line_bounds.1 as i32 + 1, height as i32) as f32; + + (top_line, bottom_line) + }, + }; + + // Get bounds for each double line we could have. + let v_left_bounds = canvas.v_line_bounds(v_lines.0, stroke_size); + let v_right_bounds = canvas.v_line_bounds(v_lines.1, stroke_size); + let h_top_bounds = canvas.h_line_bounds(h_lines.0, stroke_size); + let h_bot_bounds = canvas.h_line_bounds(h_lines.1, stroke_size); + + let height = height as f32; + let width = width as f32; + + // Left horizontal part. + let (top_left_size, bot_left_size) = match character { + '\u{2550}' | '\u{256b}' => (canvas.x_center(), canvas.x_center()), + '\u{2555}'..='\u{2557}' => (v_right_bounds.1, v_left_bounds.1), + '\u{255b}'..='\u{255d}' => (v_left_bounds.1, v_right_bounds.1), + '\u{2561}'..='\u{2563}' | '\u{256a}' | '\u{256c}' => { + (v_left_bounds.1, v_left_bounds.1) + }, + '\u{2564}'..='\u{2566}' => (canvas.x_center(), v_left_bounds.1), + '\u{2569}'..='\u{2569}' => (v_left_bounds.1, canvas.x_center()), + _ => (0., 0.), + }; + + // Right horizontal part. + let (top_right_x, bot_right_x, right_size) = match character { + '\u{2550}' | '\u{2565}' | '\u{256b}' => { + (canvas.x_center(), canvas.x_center(), width) + }, + '\u{2552}'..='\u{2554}' | '\u{2568}' => (v_left_bounds.0, v_right_bounds.0, width), + '\u{2558}'..='\u{255a}' => (v_right_bounds.0, v_left_bounds.0, width), + '\u{255e}'..='\u{2560}' | '\u{256a}' | '\u{256c}' => { + (v_right_bounds.0, v_right_bounds.0, width) + }, + '\u{2564}' | '\u{2566}' => (canvas.x_center(), v_right_bounds.0, width), + '\u{2567}' | '\u{2569}' => (v_right_bounds.0, canvas.x_center(), width), + _ => (0., 0., 0.), + }; + + // Top vertical part. + let (left_top_size, right_top_size) = match character { + '\u{2551}' | '\u{256a}' => (canvas.y_center(), canvas.y_center()), + '\u{2558}'..='\u{255c}' | '\u{2567}' | '\u{2568}' => { + (h_bot_bounds.1, h_top_bounds.1) + }, + '\u{255d}' => (h_top_bounds.1, h_bot_bounds.1), + '\u{255e}'..='\u{2560}' => (canvas.y_center(), h_top_bounds.1), + '\u{2561}'..='\u{2563}' => (h_top_bounds.1, canvas.y_center()), + '\u{2569}' | '\u{256b}' | '\u{256c}' => (h_top_bounds.1, h_top_bounds.1), + _ => (0., 0.), + }; + + // Bottom vertical part. + let (left_bot_y, right_bot_y, bottom_size) = match character { + '\u{2551}' | '\u{256a}' => (canvas.y_center(), canvas.y_center(), height), + '\u{2552}'..='\u{2554}' => (h_top_bounds.0, h_bot_bounds.0, height), + '\u{2555}'..='\u{2557}' => (h_bot_bounds.0, h_top_bounds.0, height), + '\u{255e}'..='\u{2560}' => (canvas.y_center(), h_bot_bounds.0, height), + '\u{2561}'..='\u{2563}' => (h_bot_bounds.0, canvas.y_center(), height), + '\u{2564}'..='\u{2566}' | '\u{256b}' | '\u{256c}' => { + (h_bot_bounds.0, h_bot_bounds.0, height) + }, + _ => (0., 0., 0.), + }; + + // Left horizontal line. + canvas.draw_h_line(0., h_lines.0, top_left_size, stroke_size); + canvas.draw_h_line(0., h_lines.1, bot_left_size, stroke_size); + + // Right horizontal line. + canvas.draw_h_line(top_right_x, h_lines.0, right_size, stroke_size); + canvas.draw_h_line(bot_right_x, h_lines.1, right_size, stroke_size); + + // Top vertical line. + canvas.draw_v_line(v_lines.0, 0., left_top_size, stroke_size); + canvas.draw_v_line(v_lines.1, 0., right_top_size, stroke_size); + + // Bottom vertical line. + canvas.draw_v_line(v_lines.0, left_bot_y, bottom_size, stroke_size); + canvas.draw_v_line(v_lines.1, right_bot_y, bottom_size, stroke_size); + }, + // Arcs: '╭', '╮', '╯', '╰'. + '\u{256d}' | '\u{256e}' | '\u{256f}' | '\u{2570}' => { + canvas.draw_ellipse_arc(stroke_size); + + // Mirror `X` axis. + if character == '\u{256d}' || character == '\u{2570}' { + let center = canvas.x_center() as usize; + + let extra_offset = if stroke_size % 2 == width % 2 { 0 } else { 1 }; + + let buffer = canvas.buffer_mut(); + for y in 1..height { + let left = (y - 1) * width; + let right = y * width - 1; + if extra_offset != 0 { + buffer[right] = buffer[left]; + } + for offset in 0..center { + buffer.swap(left + offset, right - offset - extra_offset); + } + } + } + // Mirror `Y` axis. + if character == '\u{256d}' || character == '\u{256e}' { + let center = canvas.y_center() as usize; + + let extra_offset = if stroke_size % 2 == height % 2 { 0 } else { 1 }; + + let buffer = canvas.buffer_mut(); + if extra_offset != 0 { + let bottom_row = (height - 1) * width; + for index in 0..width { + buffer[bottom_row + index] = buffer[index]; + } + } + for offset in 1..=center { + let top_row = (offset - 1) * width; + let bottom_row = (height - offset - extra_offset) * width; + for index in 0..width { + buffer.swap(top_row + index, bottom_row + index); + } + } + } + }, + // Parts of full block: '▀', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '▔', '▉', '▊', '▋', '▌', + // '▍', '▎', '▏', '▐', '▕'. + '\u{2580}'..='\u{2587}' | '\u{2589}'..='\u{2590}' | '\u{2594}' | '\u{2595}' => { + let width = width as f32; + let height = height as f32; + let mut rect_width = match character { + '\u{2589}' => width * 7. / 8., + '\u{258a}' => width * 6. / 8., + '\u{258b}' => width * 5. / 8., + '\u{258c}' => width * 4. / 8., + '\u{258d}' => width * 3. / 8., + '\u{258e}' => width * 2. / 8., + '\u{258f}' => width * 1. / 8., + '\u{2590}' => width * 4. / 8., + '\u{2595}' => width * 1. / 8., + _ => width, + }; + + let (mut rect_height, mut y) = match character { + '\u{2580}' => (height * 4. / 8., height * 8. / 8.), + '\u{2581}' => (height * 1. / 8., height * 1. / 8.), + '\u{2582}' => (height * 2. / 8., height * 2. / 8.), + '\u{2583}' => (height * 3. / 8., height * 3. / 8.), + '\u{2584}' => (height * 4. / 8., height * 4. / 8.), + '\u{2585}' => (height * 5. / 8., height * 5. / 8.), + '\u{2586}' => (height * 6. / 8., height * 6. / 8.), + '\u{2587}' => (height * 7. / 8., height * 7. / 8.), + '\u{2594}' => (height * 1. / 8., height * 8. / 8.), + _ => (height, height), + }; + + // Fix `y` coordinates. + y = height - y; + + // Ensure that resulted glyph will be visible and also round sizes instead of straight + // flooring them. + rect_width = cmp::max(rect_width.round() as i32, 1) as f32; + rect_height = cmp::max(rect_height.round() as i32, 1) as f32; + + let x = match character { + '\u{2590}' => canvas.x_center(), + '\u{2595}' => width as f32 - rect_width, + _ => 0., + }; + + canvas.draw_rect(x, y, rect_width, rect_height, COLOR_FILL); + }, + // Shades: '░', '▒', '▓', '█'. + '\u{2588}' | '\u{2591}' | '\u{2592}' | '\u{2593}' => { + let color = match character { + '\u{2588}' => COLOR_FILL, + '\u{2591}' => COLOR_FILL_ALPHA_STEP_3, + '\u{2592}' => COLOR_FILL_ALPHA_STEP_2, + '\u{2593}' => COLOR_FILL_ALPHA_STEP_1, + _ => unreachable!(), + }; + canvas.fill(color); + }, + // Quadrants: '▖', '▗', '▘', '▙', '▚', '▛', '▜', '▝', '▞', '▟'. + '\u{2596}'..='\u{259F}' => { + let (w_second, h_second) = match character { + '\u{2598}' | '\u{2599}' | '\u{259a}' | '\u{259b}' | '\u{259c}' => { + (canvas.x_center(), canvas.y_center()) + }, + _ => (0., 0.), + }; + let (w_first, h_first) = match character { + '\u{259b}' | '\u{259c}' | '\u{259d}' | '\u{259e}' | '\u{259f}' => { + (canvas.x_center(), canvas.y_center()) + }, + _ => (0., 0.), + }; + let (w_third, h_third) = match character { + '\u{2596}' | '\u{2599}' | '\u{259b}' | '\u{259e}' | '\u{259f}' => { + (canvas.x_center(), canvas.y_center()) + }, + _ => (0., 0.), + }; + let (w_fourth, h_fourth) = match character { + '\u{2597}' | '\u{2599}' | '\u{259a}' | '\u{259c}' | '\u{259f}' => { + (canvas.x_center(), canvas.y_center()) + }, + _ => (0., 0.), + }; + + // Second quadrant. + canvas.draw_rect(0., 0., w_second, h_second, COLOR_FILL); + // First quadrant. + canvas.draw_rect(canvas.x_center(), 0., w_first, h_first, COLOR_FILL); + // Third quadrant. + canvas.draw_rect(0., canvas.y_center(), w_third, h_third, COLOR_FILL); + // Fourth quadrant. + canvas.draw_rect(canvas.x_center(), canvas.y_center(), w_fourth, h_fourth, COLOR_FILL); + }, + _ => unreachable!(), + } + + let top = height as i32 + metrics.descent as i32; + let buffer = BitmapBuffer::Rgb(canvas.into_raw()); + RasterizedGlyph { character, top, left: 0, height: height as i32, width: width as i32, buffer } +} + +#[repr(packed)] +#[derive(Clone, Copy, Debug, Default)] +struct Pixel { + _r: u8, + _g: u8, + _b: u8, +} + +impl Pixel { + fn gray(color: u8) -> Self { + Self { _r: color, _g: color, _b: color } + } +} + +impl ops::Add for Pixel { + type Output = Pixel; + + fn add(self, rhs: Pixel) -> Self::Output { + let _r = self._r.saturating_add(rhs._r); + let _g = self._g.saturating_add(rhs._g); + let _b = self._b.saturating_add(rhs._b); + Pixel { _r, _g, _b } + } +} + +impl ops::Div<u8> for Pixel { + type Output = Pixel; + + fn div(self, rhs: u8) -> Self::Output { + let _r = self._r / rhs; + let _g = self._g / rhs; + let _b = self._b / rhs; + Pixel { _r, _g, _b } + } +} + +/// Canvas which is used for simple line drawing operations. +/// +/// The coordinate system is the following: +/// +/// 0 x +/// --------------→ +/// | +/// | +/// | +/// | +/// | +/// | +/// y↓ +struct Canvas { + /// Canvas width. + width: usize, + + /// Canvas height. + height: usize, + + /// Canvas buffer we draw on. + buffer: Vec<Pixel>, +} + +impl Canvas { + /// Builds new `Canvas` for line drawing with the given `width` and `height` with default color. + fn new(width: usize, height: usize) -> Self { + let buffer = vec![Pixel::default(); width * height]; + Self { width, height, buffer } + } + + /// Vertical center of the `Canvas`. + fn y_center(&self) -> f32 { + self.height as f32 / 2. + } + + /// Horizontal center of the `Canvas`. + fn x_center(&self) -> f32 { + self.width as f32 / 2. + } + + /// Canvas underlying buffer for direct manipulation + fn buffer_mut(&mut self) -> &mut [Pixel] { + &mut self.buffer + } + + /// Gives bounds for horizontal straight line on `y` with `stroke_size`. + fn h_line_bounds(&self, y: f32, stroke_size: usize) -> (f32, f32) { + let start_y = cmp::max((y - stroke_size as f32 / 2.) as i32, 0) as f32; + let end_y = cmp::min((y + stroke_size as f32 / 2.) as i32, self.height as i32) as f32; + + (start_y, end_y) + } + + /// Gives bounds for vertical straight line on `y` with `stroke_size`. + fn v_line_bounds(&self, x: f32, stroke_size: usize) -> (f32, f32) { + let start_x = cmp::max((x - stroke_size as f32 / 2.) as i32, 0) as f32; + let end_x = cmp::min((x + stroke_size as f32 / 2.) as i32, self.width as i32) as f32; + + (start_x, end_x) + } + + /// Draws a horizontal straight line from (`x`, `y`) of `size` with the given `stroke_size`. + fn draw_h_line(&mut self, x: f32, y: f32, size: f32, stroke_size: usize) { + let (start_y, end_y) = self.h_line_bounds(y, stroke_size); + self.draw_rect(x, start_y as f32, size, (end_y - start_y) as f32, COLOR_FILL); + } + + /// Draws a vertical straight line from (`x`, `y`) of `size` with the given `stroke_size`. + fn draw_v_line(&mut self, x: f32, y: f32, size: f32, stroke_size: usize) { + let (start_x, end_x) = self.v_line_bounds(x, stroke_size); + self.draw_rect(start_x as f32, y, (end_x - start_x) as f32, size, COLOR_FILL); + } + + /// Draws a rect from the (`x`, `y`) of the given `width` and `height` using `color`. + fn draw_rect(&mut self, x: f32, y: f32, width: f32, height: f32, color: Pixel) { + let start_x = x as usize; + let end_x = cmp::min((x + width) as usize, self.width); + let start_y = y as usize; + let end_y = cmp::min((y + height) as usize, self.height); + for y in start_y..end_y { + let y = y * self.width; + self.buffer[start_x + y..end_x + y].fill(color); + } + } + + /// Put pixel into buffer with the given color if the color is brighter than the one buffer + /// already has in place. + #[inline] + fn put_pixel(&mut self, x: f32, y: f32, color: Pixel) { + if x < 0. || y < 0. || x > self.width as f32 - 1. || y > self.height as f32 - 1. { + return; + } + let index = x as usize + y as usize * self.width; + if color._r > self.buffer[index]._r { + self.buffer[index] = color; + } + } + + /// Xiaolin Wu's line drawing from (`from_x`, `from_y`) to (`to_x`, `to_y`). + fn draw_line(&mut self, mut from_x: f32, mut from_y: f32, mut to_x: f32, mut to_y: f32) { + let steep = (to_y - from_y).abs() > (to_x - from_x).abs(); + if steep { + mem::swap(&mut from_x, &mut from_y); + mem::swap(&mut to_x, &mut to_y); + } + if from_x > to_x { + mem::swap(&mut from_x, &mut to_x); + mem::swap(&mut from_y, &mut to_y); + } + + let delta_x = to_x - from_x; + let delta_y = to_y - from_y; + let gradient = if delta_x.abs() <= f32::EPSILON { 1. } else { delta_y / delta_x }; + + let x_end = f32::round(from_x); + let y_end = from_y + gradient * (x_end - from_x); + let x_gap = 1. - (from_x + 0.5).fract(); + + let xpxl1 = x_end; + let ypxl1 = y_end.trunc(); + + let color_1 = Pixel::gray(((1. - y_end.fract()) * x_gap * COLOR_FILL._r as f32) as u8); + let color_2 = Pixel::gray((y_end.fract() * x_gap * COLOR_FILL._r as f32) as u8); + if steep { + self.put_pixel(ypxl1, xpxl1, color_1); + self.put_pixel(ypxl1 + 1., xpxl1, color_2); + } else { + self.put_pixel(xpxl1, ypxl1, color_1); + self.put_pixel(xpxl1 + 1., ypxl1, color_2); + } + + let mut intery = y_end + gradient; + + let x_end = f32::round(to_x); + let y_end = to_y + gradient * (x_end - to_x); + let x_gap = (to_x + 0.5).fract(); + let xpxl2 = x_end; + let ypxl2 = y_end.trunc(); + + let color_1 = Pixel::gray(((1. - y_end.fract()) * x_gap * COLOR_FILL._r as f32) as u8); + let color_2 = Pixel::gray((y_end.fract() * x_gap * COLOR_FILL._r as f32) as u8); + if steep { + self.put_pixel(ypxl2, xpxl2, color_1); + self.put_pixel(ypxl2 + 1., xpxl2, color_2); + } else { + self.put_pixel(xpxl2, ypxl2, color_1); + self.put_pixel(xpxl2, ypxl2 + 1., color_2); + } + + if steep { + for x in xpxl1 as i32 + 1..xpxl2 as i32 { + let color_1 = Pixel::gray(((1. - intery.fract()) * COLOR_FILL._r as f32) as u8); + let color_2 = Pixel::gray((intery.fract() * COLOR_FILL._r as f32) as u8); + self.put_pixel(intery.trunc(), x as f32, color_1); + self.put_pixel(intery.trunc() + 1., x as f32, color_2); + intery += gradient; + } + } else { + for x in xpxl1 as i32 + 1..xpxl2 as i32 { + let color_1 = Pixel::gray(((1. - intery.fract()) * COLOR_FILL._r as f32) as u8); + let color_2 = Pixel::gray((intery.fract() * COLOR_FILL._r as f32) as u8); + self.put_pixel(x as f32, intery.trunc(), color_1); + self.put_pixel(x as f32, intery.trunc() + 1., color_2); + intery += gradient; + } + } + } + + /// Draws a part of an ellipse centered in `(0., 0.)` with `self.x_center()` and `self.y_center` + /// vertex and co-vertex respectively using a given `stroke` in the bottom-right quadrant of the + /// `Canvas` coordinate system. + fn draw_ellipse_arc(&mut self, stroke_size: usize) { + fn colors_with_error(error: f32, max_transparancy: f32) -> (Pixel, Pixel) { + let transparancy = error * max_transparancy; + let alpha_1 = 1. - transparancy; + let alpha_2 = 1. - (max_transparancy - transparancy); + let color_1 = Pixel::gray((COLOR_FILL._r as f32 * alpha_1) as u8); + let color_2 = Pixel::gray((COLOR_FILL._r as f32 * alpha_2) as u8); + (color_1, color_2) + } + + let h_line_bounds = self.h_line_bounds(self.y_center(), stroke_size); + let v_line_bounds = self.v_line_bounds(self.x_center(), stroke_size); + let h_line_bounds = (h_line_bounds.0 as usize, h_line_bounds.1 as usize); + let v_line_bounds = (v_line_bounds.0 as usize, v_line_bounds.1 as usize); + let max_transparancy = 0.5; + + for (radius_y, radius_x) in (h_line_bounds.0..h_line_bounds.1) + .into_iter() + .zip((v_line_bounds.0..v_line_bounds.1).into_iter()) + { + let radius_x = radius_x as f32; + let radius_y = radius_y as f32; + let radius_x2 = radius_x * radius_x; + let radius_y2 = radius_y * radius_y; + let quarter = f32::round(radius_x2 / f32::sqrt(radius_x2 + radius_y2)) as usize; + + for x in 0..=quarter { + let x = x as f32; + let y = radius_y * f32::sqrt(1. - x * x / radius_x2); + let error = y.fract(); + + let (color_1, color_2) = colors_with_error(error, max_transparancy); + + let x = x.clamp(0., radius_x); + let y_next = (y + 1.).clamp(0., h_line_bounds.1 as f32 - 1.); + let y = y.clamp(0., h_line_bounds.1 as f32 - 1.); + + self.put_pixel(x, y, color_1); + self.put_pixel(x, y_next, color_2); + } + + let quarter = f32::round(radius_y2 / f32::sqrt(radius_x2 + radius_y2)) as usize; + for y in 0..=quarter { + let y = y as f32; + let x = radius_x * f32::sqrt(1. - y * y / radius_y2); + let error = x - x.fract(); + + let (color_1, color_2) = colors_with_error(error, max_transparancy); + + let x_next = (x + 1.).clamp(0., v_line_bounds.1 as f32 - 1.); + let x = x.clamp(0., v_line_bounds.1 as f32 - 1.); + let y = y.clamp(0., radius_y as f32); + + self.put_pixel(x, y, color_1); + self.put_pixel(x_next, y, color_2); + } + } + + // Ensure the part closer to edges is properly filled. + self.draw_h_line(0., self.y_center(), stroke_size as f32, stroke_size); + self.draw_v_line(self.x_center(), 0., stroke_size as f32, stroke_size); + + // Fill the resulted arc, since it could have gaps in-between. + for y in 0..self.height { + let row = y * self.width; + let left = match self.buffer[row..row + self.width].iter().position(|p| p._r != 0) { + Some(left) => row + left, + _ => continue, + }; + let right = match self.buffer[row..row + self.width].iter().rposition(|p| p._r != 0) { + Some(right) => row + right, + _ => continue, + }; + + for index in left + 1..right { + self.buffer[index] = + self.buffer[index] + self.buffer[index - 1] / 2 + self.buffer[index + 1] / 2; + } + } + } + + /// Fills the `Canvas` with the given `Color`. + fn fill(&mut self, color: Pixel) { + self.buffer.fill(color); + } + + /// Consumes `Canvas` and returns its underlying storage as raw byte vector. + fn into_raw(self) -> Vec<u8> { + // SAFETY This is safe since we use `repr(packed)` on `Pixel` struct for underlying storage + // of the `Canvas` buffer which consists of three u8 values. + unsafe { + let capacity = self.buffer.capacity() * mem::size_of::<Pixel>(); + let len = self.buffer.len() * mem::size_of::<Pixel>(); + let buf = self.buffer.as_ptr() as *mut u8; + mem::forget(self.buffer); + Vec::from_raw_parts(buf, len, capacity) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crossfont::Metrics; + + #[test] + fn builtin_line_drawing_glyphs_coverage() { + // Dummy metrics values to test built-in glyphs coverage. + let metrics = Metrics { + average_advance: 6., + line_height: 16., + descent: 4., + underline_position: 2., + underline_thickness: 2., + strikeout_position: 2., + strikeout_thickness: 2., + }; + + let offset = Default::default(); + let glyph_offset = Default::default(); + + // Test coverage of box drawing characters. + for character in '\u{2500}'..='\u{259f}' { + assert!(builtin_glyph(character, &metrics, &offset, &glyph_offset).is_some()); + } + + for character in ('\u{2450}'..'\u{2500}').chain('\u{25a0}'..'\u{2600}') { + assert!(builtin_glyph(character, &metrics, &offset, &glyph_offset).is_none()); + } + } +} diff --git a/alacritty/src/renderer/text/gles2.rs b/alacritty/src/renderer/text/gles2.rs new file mode 100644 index 00000000..9b161081 --- /dev/null +++ b/alacritty/src/renderer/text/gles2.rs @@ -0,0 +1,478 @@ +use std::mem::size_of; +use std::ptr; + +use crossfont::RasterizedGlyph; +use log::info; + +use alacritty_terminal::term::cell::Flags; +use alacritty_terminal::term::SizeInfo; + +use crate::display::content::RenderableCell; +use crate::gl; +use crate::gl::types::*; +use crate::renderer::shader::{ShaderProgram, ShaderVersion}; +use crate::renderer::{cstr, Error}; + +use super::atlas::{Atlas, ATLAS_SIZE}; +use super::{ + Glyph, LoadGlyph, LoaderApi, TextRenderApi, TextRenderBatch, TextRenderer, TextShader, +}; + +// Shader source. +static TEXT_SHADER_F: &str = include_str!("../../../res/gles2/text.f.glsl"); +static TEXT_SHADER_V: &str = include_str!("../../../res/gles2/text.v.glsl"); + +#[derive(Debug)] +pub struct Gles2Renderer { + program: TextShaderProgram, + vao: GLuint, + vbo: GLuint, + ebo: GLuint, + atlas: Vec<Atlas>, + batch: Batch, + current_atlas: usize, + active_tex: GLuint, +} + +impl Gles2Renderer { + pub fn new() -> Result<Self, Error> { + info!("Using OpenGL ES 2.0 renderer"); + + let program = TextShaderProgram::new(ShaderVersion::Gles2)?; + let mut vao: GLuint = 0; + let mut vbo: GLuint = 0; + let mut ebo: GLuint = 0; + + let mut vertex_indices = Vec::with_capacity(BATCH_MAX / 4 * 6); + for index in 0..(BATCH_MAX / 4) as u16 { + let index = index * 4; + vertex_indices.push(index); + vertex_indices.push(index + 1); + vertex_indices.push(index + 3); + + vertex_indices.push(index + 1); + vertex_indices.push(index + 2); + vertex_indices.push(index + 3); + } + + unsafe { + gl::Enable(gl::BLEND); + + gl::DepthMask(gl::FALSE); + + gl::GenVertexArrays(1, &mut vao); + gl::GenBuffers(1, &mut ebo); + gl::GenBuffers(1, &mut vbo); + gl::BindVertexArray(vao); + + // Elements buffer. + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); + gl::BufferData( + gl::ELEMENT_ARRAY_BUFFER, + (vertex_indices.capacity() * size_of::<u16>()) as isize, + vertex_indices.as_ptr() as *const _, + gl::STATIC_DRAW, + ); + + // Vertex buffer. + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + gl::BufferData( + gl::ARRAY_BUFFER, + (BATCH_MAX * size_of::<TextVertex>()) as isize, + ptr::null(), + gl::STREAM_DRAW, + ); + + let mut index = 0; + let mut size = 0; + + macro_rules! add_attr { + ($count:expr, $gl_type:expr, $type:ty) => { + gl::VertexAttribPointer( + index, + $count, + $gl_type, + gl::FALSE, + size_of::<TextVertex>() as i32, + size as *const _, + ); + gl::EnableVertexAttribArray(index); + + #[allow(unused_assignments)] + { + size += $count * size_of::<$type>(); + index += 1; + } + }; + } + + // Cell coords. + add_attr!(2, gl::SHORT, i16); + + // Glyph coords. + add_attr!(2, gl::SHORT, i16); + + // UV. + add_attr!(2, gl::FLOAT, u32); + + // Color and bitmap color. + // + // These are packed together because of an OpenGL driver issue on macOS, which caused a + // `vec3(u8)` text color and a `u8` for glyph color to cause performance regressions. + add_attr!(4, gl::UNSIGNED_BYTE, u8); + + // Background color. + add_attr!(4, gl::UNSIGNED_BYTE, u8); + + // Cleanup. + gl::BindVertexArray(0); + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + } + + Ok(Self { + program, + vao, + vbo, + ebo, + atlas: vec![Atlas::new(ATLAS_SIZE)], + batch: Batch::new(), + current_atlas: 0, + active_tex: 0, + }) + } +} + +impl Drop for Gles2Renderer { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &self.vbo); + gl::DeleteBuffers(1, &self.ebo); + gl::DeleteVertexArrays(1, &self.vao); + } + } +} + +impl<'a> TextRenderer<'a> for Gles2Renderer { + type RenderApi = RenderApi<'a>; + type RenderBatch = Batch; + type Shader = TextShaderProgram; + + fn program(&self) -> &Self::Shader { + &self.program + } + + fn with_api<'b: 'a, F, T>(&'b mut self, _: &'b SizeInfo, func: F) -> T + where + F: FnOnce(Self::RenderApi) -> T, + { + unsafe { + gl::UseProgram(self.program.id()); + gl::BindVertexArray(self.vao); + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); + gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo); + gl::ActiveTexture(gl::TEXTURE0); + } + + let res = func(RenderApi { + active_tex: &mut self.active_tex, + batch: &mut self.batch, + atlas: &mut self.atlas, + current_atlas: &mut self.current_atlas, + program: &mut self.program, + }); + + unsafe { + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindVertexArray(0); + + gl::UseProgram(0); + } + + res + } + + fn loader_api(&mut self) -> LoaderApi<'_> { + LoaderApi { + active_tex: &mut self.active_tex, + atlas: &mut self.atlas, + current_atlas: &mut self.current_atlas, + } + } +} + +/// Maximum items to be drawn in a batch. +/// +/// We use the closest number to `u16::MAX` dividable by 4 (amount of vertices we push for a glyph), +/// since it's the maximum possible index in `glDrawElements` in gles2. +const BATCH_MAX: usize = (u16::MAX - u16::MAX % 4) as usize; + +#[derive(Debug)] +pub struct Batch { + tex: GLuint, + vertices: Vec<TextVertex>, +} + +impl Batch { + fn new() -> Self { + Self { tex: 0, vertices: Vec::with_capacity(BATCH_MAX) } + } + + #[inline] + fn len(&self) -> usize { + self.vertices.len() + } + + #[inline] + fn capacity(&self) -> usize { + BATCH_MAX + } + + #[inline] + fn size(&self) -> usize { + self.len() * size_of::<TextVertex>() + } + + #[inline] + fn clear(&mut self) { + self.vertices.clear(); + } +} + +impl TextRenderBatch for Batch { + #[inline] + fn tex(&self) -> GLuint { + self.tex + } + + #[inline] + fn full(&self) -> bool { + self.capacity() == self.len() + } + + #[inline] + fn is_empty(&self) -> bool { + self.len() == 0 + } + + fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo) { + if self.is_empty() { + self.tex = glyph.tex_id; + } + + // Calculate the cell position. + let x = cell.point.column.0 as i16 * size_info.cell_width() as i16; + let y = cell.point.line as i16 * size_info.cell_height() as i16; + + // Calculate the glyph position. + let glyph_x = cell.point.column.0 as i16 * size_info.cell_width() as i16 + glyph.left; + let glyph_y = (cell.point.line + 1) as i16 * size_info.cell_height() as i16 - glyph.top; + + let colored = if glyph.multicolor { 1 } else { 0 }; + let is_wide = if cell.flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 }; + + let mut vertex = TextVertex { + x, + y: y + size_info.cell_height() as i16, + + glyph_x, + glyph_y: glyph_y + glyph.height, + + u: glyph.uv_left, + v: glyph.uv_bot + glyph.uv_height, + r: cell.fg.r, + g: cell.fg.g, + b: cell.fg.b, + colored, + bg_r: cell.bg.r, + bg_g: cell.bg.g, + bg_b: cell.bg.b, + bg_a: (cell.bg_alpha * 255.0) as u8, + }; + + self.vertices.push(vertex); + + vertex.y = y; + vertex.glyph_y = glyph_y; + vertex.u = glyph.uv_left; + vertex.v = glyph.uv_bot; + self.vertices.push(vertex); + + vertex.x = x + is_wide * size_info.cell_width() as i16; + vertex.glyph_x = glyph_x + glyph.width; + vertex.u = glyph.uv_left + glyph.uv_width; + vertex.v = glyph.uv_bot; + self.vertices.push(vertex); + + vertex.x = x + is_wide * size_info.cell_width() as i16; + vertex.y = y + size_info.cell_height() as i16; + vertex.glyph_x = glyph_x + glyph.width; + vertex.glyph_y = glyph_y + glyph.height; + vertex.u = glyph.uv_left + glyph.uv_width; + vertex.v = glyph.uv_bot + glyph.uv_height; + self.vertices.push(vertex); + } +} + +#[derive(Debug)] +pub struct RenderApi<'a> { + active_tex: &'a mut GLuint, + batch: &'a mut Batch, + atlas: &'a mut Vec<Atlas>, + current_atlas: &'a mut usize, + program: &'a mut TextShaderProgram, +} + +impl<'a> Drop for RenderApi<'a> { + fn drop(&mut self) { + if !self.batch.is_empty() { + self.render_batch(); + } + } +} + +impl<'a> LoadGlyph for RenderApi<'a> { + fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { + Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) + } + + fn clear(&mut self) { + Atlas::clear_atlas(self.atlas, self.current_atlas) + } +} + +impl<'a> TextRenderApi<Batch> for RenderApi<'a> { + fn batch(&mut self) -> &mut Batch { + self.batch + } + + fn render_batch(&mut self) { + unsafe { + gl::BufferSubData( + gl::ARRAY_BUFFER, + 0, + self.batch.size() as isize, + self.batch.vertices.as_ptr() as *const _, + ); + } + + if *self.active_tex != self.batch.tex() { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.batch.tex()); + } + *self.active_tex = self.batch.tex(); + } + + unsafe { + let num_indices = (self.batch.len() / 4 * 6) as i32; + + // The rendering is inspired by + // https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md. + + // Draw background. + self.program.set_rendering_pass(RenderingPass::Background); + gl::BlendFunc(gl::ONE, gl::ZERO); + gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); + + // First text rendering pass. + self.program.set_rendering_pass(RenderingPass::SubpixelPass1); + gl::BlendFuncSeparate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE); + gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); + + // Second text rendering pass. + self.program.set_rendering_pass(RenderingPass::SubpixelPass2); + gl::BlendFuncSeparate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE); + gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); + + // Third pass. + self.program.set_rendering_pass(RenderingPass::SubpixelPass3); + gl::BlendFuncSeparate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA); + gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); + } + + self.batch.clear(); + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct TextVertex { + // Cell coordinates. + x: i16, + y: i16, + + // Glyph coordinates. + glyph_x: i16, + glyph_y: i16, + + // Offsets into Atlas. + u: f32, + v: f32, + + // Color. + r: u8, + g: u8, + b: u8, + + // Whether the glyph is colored. + colored: u8, + + // Background color. + bg_r: u8, + bg_g: u8, + bg_b: u8, + bg_a: u8, +} + +// NOTE: These flags must be in sync with their usage in the gles2/text.*.glsl shaders. +#[repr(u8)] +enum RenderingPass { + Background = 0, + SubpixelPass1 = 1, + SubpixelPass2 = 2, + SubpixelPass3 = 3, +} + +#[derive(Debug)] +pub struct TextShaderProgram { + /// Shader program. + program: ShaderProgram, + + /// Projection scale and offset uniform. + u_projection: GLint, + + /// Rendering pass. + /// + /// The rendering is split into 4 passes. One is used for the background and the rest to + /// perform subpixel text rendering according to + /// https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md. + /// + /// Rendering is split into three passes. + u_rendering_pass: GLint, +} + +impl TextShaderProgram { + pub fn new(shader_version: ShaderVersion) -> Result<Self, Error> { + let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, TEXT_SHADER_F)?; + Ok(Self { + u_projection: program.get_uniform_location(cstr!("projection"))?, + u_rendering_pass: program.get_uniform_location(cstr!("renderingPass"))?, + program, + }) + } + + fn set_rendering_pass(&self, rendering_pass: RenderingPass) { + unsafe { gl::Uniform1i(self.u_rendering_pass, rendering_pass as i32) } + } +} + +impl TextShader for TextShaderProgram { + fn id(&self) -> GLuint { + self.program.id() + } + + fn projection_uniform(&self) -> GLint { + self.u_projection + } +} diff --git a/alacritty/src/renderer/text/glsl3.rs b/alacritty/src/renderer/text/glsl3.rs new file mode 100644 index 00000000..6701cf3d --- /dev/null +++ b/alacritty/src/renderer/text/glsl3.rs @@ -0,0 +1,460 @@ +use std::mem::size_of; +use std::ptr; + +use crossfont::RasterizedGlyph; +use log::info; + +use alacritty_terminal::term::cell::Flags; +use alacritty_terminal::term::SizeInfo; + +use crate::display::content::RenderableCell; +use crate::gl; +use crate::gl::types::*; +use crate::renderer::shader::{ShaderProgram, ShaderVersion}; +use crate::renderer::{cstr, Error}; + +use super::atlas::{Atlas, ATLAS_SIZE}; +use super::{ + Glyph, LoadGlyph, LoaderApi, RenderingGlyphFlags, TextRenderApi, TextRenderBatch, TextRenderer, + TextShader, +}; + +// Shader source. +static TEXT_SHADER_F: &str = include_str!("../../../res/glsl3/text.f.glsl"); +static TEXT_SHADER_V: &str = include_str!("../../../res/glsl3/text.v.glsl"); + +/// Maximum items to be drawn in a batch. +const BATCH_MAX: usize = 0x1_0000; + +#[derive(Debug)] +pub struct Glsl3Renderer { + program: TextShaderProgram, + vao: GLuint, + ebo: GLuint, + vbo_instance: GLuint, + atlas: Vec<Atlas>, + current_atlas: usize, + active_tex: GLuint, + batch: Batch, +} + +impl Glsl3Renderer { + pub fn new() -> Result<Self, Error> { + info!("Using OpenGL 3.3 renderer"); + + let program = TextShaderProgram::new(ShaderVersion::Glsl3)?; + let mut vao: GLuint = 0; + let mut ebo: GLuint = 0; + let mut vbo_instance: GLuint = 0; + + unsafe { + gl::Enable(gl::BLEND); + gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); + gl::Enable(gl::MULTISAMPLE); + + // Disable depth mask, as the renderer never uses depth tests. + gl::DepthMask(gl::FALSE); + + gl::GenVertexArrays(1, &mut vao); + gl::GenBuffers(1, &mut ebo); + gl::GenBuffers(1, &mut vbo_instance); + gl::BindVertexArray(vao); + + // --------------------- + // Set up element buffer + // --------------------- + let indices: [u32; 6] = [0, 1, 3, 1, 2, 3]; + + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); + gl::BufferData( + gl::ELEMENT_ARRAY_BUFFER, + (6 * size_of::<u32>()) as isize, + indices.as_ptr() as *const _, + gl::STATIC_DRAW, + ); + + // ---------------------------- + // Setup vertex instance buffer + // ---------------------------- + gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance); + gl::BufferData( + gl::ARRAY_BUFFER, + (BATCH_MAX * size_of::<InstanceData>()) as isize, + ptr::null(), + gl::STREAM_DRAW, + ); + + let mut index = 0; + let mut size = 0; + + macro_rules! add_attr { + ($count:expr, $gl_type:expr, $type:ty) => { + gl::VertexAttribPointer( + index, + $count, + $gl_type, + gl::FALSE, + size_of::<InstanceData>() as i32, + size as *const _, + ); + gl::EnableVertexAttribArray(index); + gl::VertexAttribDivisor(index, 1); + + #[allow(unused_assignments)] + { + size += $count * size_of::<$type>(); + index += 1; + } + }; + } + + // Coords. + add_attr!(2, gl::UNSIGNED_SHORT, u16); + + // Glyph offset and size. + add_attr!(4, gl::SHORT, i16); + + // UV offset. + add_attr!(4, gl::FLOAT, f32); + + // Color and cell flags. + // + // These are packed together because of an OpenGL driver issue on macOS, which caused a + // `vec3(u8)` text color and a `u8` cell flags to increase the rendering time by a + // huge margin. + add_attr!(4, gl::UNSIGNED_BYTE, u8); + + // Background color. + add_attr!(4, gl::UNSIGNED_BYTE, u8); + + // Cleanup. + gl::BindVertexArray(0); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); + } + + Ok(Self { + program, + vao, + ebo, + vbo_instance, + atlas: vec![Atlas::new(ATLAS_SIZE)], + current_atlas: 0, + active_tex: 0, + batch: Batch::new(), + }) + } +} + +impl<'a> TextRenderer<'a> for Glsl3Renderer { + type RenderApi = RenderApi<'a>; + type RenderBatch = Batch; + type Shader = TextShaderProgram; + + fn with_api<'b: 'a, F, T>(&'b mut self, size_info: &'b SizeInfo, func: F) -> T + where + F: FnOnce(Self::RenderApi) -> T, + { + unsafe { + gl::UseProgram(self.program.id()); + self.program.set_term_uniforms(size_info); + + gl::BindVertexArray(self.vao); + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); + gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance); + gl::ActiveTexture(gl::TEXTURE0); + } + + let res = func(RenderApi { + active_tex: &mut self.active_tex, + batch: &mut self.batch, + atlas: &mut self.atlas, + current_atlas: &mut self.current_atlas, + program: &mut self.program, + }); + + unsafe { + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindVertexArray(0); + + gl::UseProgram(0); + } + + res + } + + fn program(&self) -> &Self::Shader { + &self.program + } + + fn loader_api(&mut self) -> LoaderApi<'_> { + LoaderApi { + active_tex: &mut self.active_tex, + atlas: &mut self.atlas, + current_atlas: &mut self.current_atlas, + } + } +} + +impl Drop for Glsl3Renderer { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &self.vbo_instance); + gl::DeleteBuffers(1, &self.ebo); + gl::DeleteVertexArrays(1, &self.vao); + } + } +} + +#[derive(Debug)] +pub struct RenderApi<'a> { + active_tex: &'a mut GLuint, + batch: &'a mut Batch, + atlas: &'a mut Vec<Atlas>, + current_atlas: &'a mut usize, + program: &'a mut TextShaderProgram, +} + +impl<'a> TextRenderApi<Batch> for RenderApi<'a> { + fn batch(&mut self) -> &mut Batch { + self.batch + } + + fn render_batch(&mut self) { + unsafe { + gl::BufferSubData( + gl::ARRAY_BUFFER, + 0, + self.batch.size() as isize, + self.batch.instances.as_ptr() as *const _, + ); + } + + // Bind texture if necessary. + if *self.active_tex != self.batch.tex() { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.batch.tex()); + } + *self.active_tex = self.batch.tex(); + } + + unsafe { + self.program.set_background_pass(true); + gl::DrawElementsInstanced( + gl::TRIANGLES, + 6, + gl::UNSIGNED_INT, + ptr::null(), + self.batch.len() as GLsizei, + ); + self.program.set_background_pass(false); + gl::DrawElementsInstanced( + gl::TRIANGLES, + 6, + gl::UNSIGNED_INT, + ptr::null(), + self.batch.len() as GLsizei, + ); + } + + self.batch.clear(); + } +} + +impl<'a> LoadGlyph for RenderApi<'a> { + fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { + Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) + } + + fn clear(&mut self) { + Atlas::clear_atlas(self.atlas, self.current_atlas) + } +} + +impl<'a> Drop for RenderApi<'a> { + fn drop(&mut self) { + if !self.batch.is_empty() { + self.render_batch(); + } + } +} + +#[derive(Debug)] +#[repr(C)] +struct InstanceData { + // Coords. + col: u16, + row: u16, + + // Glyph offset. + left: i16, + top: i16, + + // Glyph size. + width: i16, + height: i16, + + // UV offset. + uv_left: f32, + uv_bot: f32, + + // uv scale. + uv_width: f32, + uv_height: f32, + + // Color. + r: u8, + g: u8, + b: u8, + + // Cell flags like multicolor or fullwidth character. + cell_flags: RenderingGlyphFlags, + + // Background color. + bg_r: u8, + bg_g: u8, + bg_b: u8, + bg_a: u8, +} + +#[derive(Debug, Default)] +pub struct Batch { + tex: GLuint, + instances: Vec<InstanceData>, +} + +impl TextRenderBatch for Batch { + #[inline] + fn tex(&self) -> GLuint { + self.tex + } + + #[inline] + fn full(&self) -> bool { + self.capacity() == self.len() + } + + #[inline] + fn is_empty(&self) -> bool { + self.len() == 0 + } + + fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, _: &SizeInfo) { + if self.is_empty() { + self.tex = glyph.tex_id; + } + + let mut cell_flags = RenderingGlyphFlags::empty(); + cell_flags.set(RenderingGlyphFlags::COLORED, glyph.multicolor); + cell_flags.set(RenderingGlyphFlags::WIDE_CHAR, cell.flags.contains(Flags::WIDE_CHAR)); + + self.instances.push(InstanceData { + col: cell.point.column.0 as u16, + row: cell.point.line as u16, + + top: glyph.top, + left: glyph.left, + width: glyph.width, + height: glyph.height, + + uv_bot: glyph.uv_bot, + uv_left: glyph.uv_left, + uv_width: glyph.uv_width, + uv_height: glyph.uv_height, + + r: cell.fg.r, + g: cell.fg.g, + b: cell.fg.b, + cell_flags, + + bg_r: cell.bg.r, + bg_g: cell.bg.g, + bg_b: cell.bg.b, + bg_a: (cell.bg_alpha * 255.0) as u8, + }); + } +} + +impl Batch { + #[inline] + pub fn new() -> Self { + Self { tex: 0, instances: Vec::with_capacity(BATCH_MAX) } + } + + #[inline] + pub fn len(&self) -> usize { + self.instances.len() + } + + #[inline] + pub fn capacity(&self) -> usize { + BATCH_MAX + } + + #[inline] + pub fn size(&self) -> usize { + self.len() * size_of::<InstanceData>() + } + + pub fn clear(&mut self) { + self.tex = 0; + self.instances.clear(); + } +} + +/// Text drawing program. +/// +/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". +#[derive(Debug)] +pub struct TextShaderProgram { + /// Shader program. + program: ShaderProgram, + + /// Projection scale and offset uniform. + u_projection: GLint, + + /// Cell dimensions (pixels). + u_cell_dim: GLint, + + /// Background pass flag. + /// + /// Rendering is split into two passes; 1 for backgrounds, and one for text. + u_background: GLint, +} + +impl TextShaderProgram { + pub fn new(shader_version: ShaderVersion) -> Result<TextShaderProgram, Error> { + let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, TEXT_SHADER_F)?; + Ok(Self { + u_projection: program.get_uniform_location(cstr!("projection"))?, + u_cell_dim: program.get_uniform_location(cstr!("cellDim"))?, + u_background: program.get_uniform_location(cstr!("backgroundPass"))?, + program, + }) + } + + fn set_term_uniforms(&self, props: &SizeInfo) { + unsafe { + gl::Uniform2f(self.u_cell_dim, props.cell_width(), props.cell_height()); + } + } + + fn set_background_pass(&self, background_pass: bool) { + let value = if background_pass { 1 } else { 0 }; + + unsafe { + gl::Uniform1i(self.u_background, value); + } + } +} + +impl TextShader for TextShaderProgram { + fn id(&self) -> GLuint { + self.program.id() + } + + fn projection_uniform(&self) -> GLint { + self.u_projection + } +} 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); + } +} diff --git a/alacritty/src/renderer/text/mod.rs b/alacritty/src/renderer/text/mod.rs new file mode 100644 index 00000000..05ac59e8 --- /dev/null +++ b/alacritty/src/renderer/text/mod.rs @@ -0,0 +1,202 @@ +use bitflags::bitflags; +use crossfont::{GlyphKey, RasterizedGlyph}; + +use alacritty_terminal::term::cell::Flags; +use alacritty_terminal::term::SizeInfo; + +use crate::display::content::RenderableCell; +use crate::gl; +use crate::gl::types::*; + +mod atlas; +mod builtin_font; +mod gles2; +mod glsl3; +pub mod glyph_cache; + +use atlas::Atlas; +pub use gles2::Gles2Renderer; +pub use glsl3::Glsl3Renderer; +pub use glyph_cache::GlyphCache; +use glyph_cache::{Glyph, LoadGlyph}; + +// NOTE: These flags must be in sync with their usage in the text.*.glsl shaders. +bitflags! { + #[repr(C)] + struct RenderingGlyphFlags: u8 { + const WIDE_CHAR = 0b0000_0001; + const COLORED = 0b0000_0010; + } +} + +pub trait TextRenderer<'a> { + type Shader: TextShader; + type RenderBatch: TextRenderBatch; + type RenderApi: TextRenderApi<Self::RenderBatch>; + + /// Get loader API for the renderer. + fn loader_api(&mut self) -> LoaderApi<'_>; + + /// Draw cells. + fn draw_cells<'b: 'a, I: Iterator<Item = RenderableCell>>( + &'b mut self, + size_info: &'b SizeInfo, + glyph_cache: &'a mut GlyphCache, + cells: I, + ) { + self.with_api(size_info, |mut api| { + for cell in cells { + api.draw_cell(cell, glyph_cache, size_info); + } + }) + } + + fn with_api<'b: 'a, F, T>(&'b mut self, size_info: &'b SizeInfo, func: F) -> T + where + F: FnOnce(Self::RenderApi) -> T; + + fn program(&self) -> &Self::Shader; + + /// Resize the text rendering. + fn resize(&self, size: &SizeInfo) { + unsafe { + let program = self.program(); + gl::UseProgram(program.id()); + update_projection(program.projection_uniform(), size); + gl::UseProgram(0); + } + } + + /// Invoke renderer with the loader. + fn with_loader<F: FnOnce(LoaderApi<'_>) -> T, T>(&mut self, func: F) -> T { + unsafe { + gl::ActiveTexture(gl::TEXTURE0); + } + + func(self.loader_api()) + } +} + +pub trait TextRenderBatch { + /// Check if `Batch` is empty. + fn is_empty(&self) -> bool; + + /// Check whether the `Batch` is full. + fn full(&self) -> bool; + + /// Get texture `Batch` is using. + fn tex(&self) -> GLuint; + + /// Add item to the batch. + fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo); +} + +pub trait TextRenderApi<T: TextRenderBatch>: LoadGlyph { + /// Get `Batch` the api is using. + fn batch(&mut self) -> &mut T; + + /// Render the underlying data. + fn render_batch(&mut self); + + /// Add item to the rendering queue. + #[inline] + fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo) { + // Flush batch if tex changing. + if !self.batch().is_empty() && self.batch().tex() != glyph.tex_id { + self.render_batch(); + } + + self.batch().add_item(cell, glyph, size_info); + + // Render batch and clear if it's full. + if self.batch().full() { + self.render_batch(); + } + } + + /// Draw cell. + fn draw_cell( + &mut self, + mut cell: RenderableCell, + glyph_cache: &mut GlyphCache, + size_info: &SizeInfo, + ) { + // Get font key for cell. + let font_key = match cell.flags & Flags::BOLD_ITALIC { + Flags::BOLD_ITALIC => glyph_cache.bold_italic_key, + Flags::ITALIC => glyph_cache.italic_key, + Flags::BOLD => glyph_cache.bold_key, + _ => glyph_cache.font_key, + }; + + // Ignore hidden cells and render tabs as spaces to prevent font issues. + let hidden = cell.flags.contains(Flags::HIDDEN); + if cell.character == '\t' || hidden { + cell.character = ' '; + } + + let mut glyph_key = + GlyphKey { font_key, size: glyph_cache.font_size, character: cell.character }; + + // Add cell to batch. + let glyph = glyph_cache.get(glyph_key, self, true); + self.add_render_item(&cell, &glyph, size_info); + + // Render visible zero-width characters. + if let Some(zerowidth) = cell.zerowidth.take().filter(|_| !hidden) { + for character in zerowidth { + glyph_key.character = character; + let glyph = glyph_cache.get(glyph_key, self, false); + self.add_render_item(&cell, &glyph, size_info); + } + } + } +} + +pub trait TextShader { + fn id(&self) -> GLuint; + + /// Id of the projection uniform. + fn projection_uniform(&self) -> GLint; +} + +#[derive(Debug)] +pub struct LoaderApi<'a> { + active_tex: &'a mut GLuint, + atlas: &'a mut Vec<Atlas>, + current_atlas: &'a mut usize, +} + +impl<'a> LoadGlyph for LoaderApi<'a> { + fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { + Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) + } + + fn clear(&mut self) { + Atlas::clear_atlas(self.atlas, self.current_atlas) + } +} + +fn update_projection(u_projection: GLint, size: &SizeInfo) { + let width = size.width(); + let height = size.height(); + let padding_x = size.padding_x(); + let padding_y = size.padding_y(); + + // Bounds check. + if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) { + return; + } + + // Compute scale and offset factors, from pixel to ndc space. Y is inverted. + // [0, width - 2 * padding_x] to [-1, 1] + // [height - 2 * padding_y, 0] to [-1, 1] + let scale_x = 2. / (width - 2. * padding_x); + let scale_y = -2. / (height - 2. * padding_y); + let offset_x = -1.; + let offset_y = 1.; + + unsafe { + gl::Uniform4f(u_projection, offset_x, offset_y, scale_x, scale_y); + } +} |