use std::collections::HashMap; use std::fmt::{self, Display, Formatter}; use std::hash::BuildHasherDefault; use std::io; use std::mem::size_of; use std::ptr; use bitflags::bitflags; use crossfont::{ BitmapBuffer, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer, Size, Slant, Style, Weight, }; use fnv::FnvHasher; use log::{error, info}; use alacritty_terminal::config::Cursor; use alacritty_terminal::index::{Column, Line}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::{CursorKey, RenderableCell, RenderableCellContent, SizeInfo}; use crate::config::font::{Font, FontDescription}; use crate::config::ui_config::{Delta, UIConfig}; use crate::cursor; use crate::gl; use crate::gl::types::*; use crate::renderer::rects::{RectRenderer, RenderRect}; pub mod rects; // Shader source. static TEXT_SHADER_F: &str = include_str!("../../res/text.f.glsl"); static TEXT_SHADER_V: &str = include_str!("../../res/text.v.glsl"); /// `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(Debug)] pub enum Error { ShaderCreation(ShaderCreationError), } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::ShaderCreation(err) => err.source(), } } } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Error::ShaderCreation(err) => { write!(f, "There was an error initializing the shaders: {}", err) }, } } } impl From for Error { fn from(val: ShaderCreationError) -> Self { Error::ShaderCreation(val) } } /// Text drawing program. /// /// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". #[derive(Debug)] pub struct TextShaderProgram { /// Program id. id: GLuint, /// 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, } #[derive(Copy, Debug, Clone)] pub struct Glyph { tex_id: GLuint, multicolor: bool, top: i16, left: i16, width: i16, height: i16, uv_bot: f32, uv_left: f32, uv_width: f32, 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>, /// Cache of buffered cursor glyphs. cursor_cache: HashMap>, /// Rasterizer for loading new glyphs. rasterizer: Rasterizer, /// Regular font. font_key: FontKey, /// Bold font. bold_key: FontKey, /// Italic font. italic_key: FontKey, /// Bold italic font. bold_italic_key: FontKey, /// Font size. font_size: crossfont::Size, /// Glyph offset. glyph_offset: Delta, /// Font metrics. metrics: crossfont::Metrics, } impl GlyphCache { pub fn new( mut rasterizer: Rasterizer, font: &Font, loader: &mut L, ) -> Result where L: LoadGlyph, { 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, c: 'm', size: font.size() })?; let metrics = rasterizer.metrics(regular, font.size())?; let mut cache = Self { cache: HashMap::default(), cursor_cache: HashMap::default(), rasterizer, font_size: font.size(), font_key: regular, bold_key: bold, italic_key: italic, bold_italic_key: bold_italic, glyph_offset: font.glyph_offset, metrics, }; cache.load_common_glyphs(loader); Ok(cache) } fn load_glyphs_for_font(&mut self, font: FontKey, loader: &mut L) { let size = self.font_size; for i in 32u8..=126u8 { self.get(GlyphKey { font_key: font, c: i as char, size }, loader); } } /// 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 { 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) } pub fn get(&mut self, glyph_key: GlyphKey, loader: &mut L) -> &Glyph where L: LoadGlyph, { let glyph_offset = self.glyph_offset; let rasterizer = &mut self.rasterizer; let metrics = &self.metrics; self.cache.entry(glyph_key).or_insert_with(|| { let mut rasterized = rasterizer.get_glyph(glyph_key).unwrap_or_else(|_| Default::default()); rasterized.left += i32::from(glyph_offset.x); rasterized.top += i32::from(glyph_offset.y); rasterized.top -= metrics.descent as i32; loader.load_glyph(&rasterized) }) } /// Clear currently cached data in both GL and the registry. pub fn clear_glyph_cache(&mut self, loader: &mut L) { loader.clear(); self.cache = HashMap::default(); self.cursor_cache = HashMap::default(); self.load_common_glyphs(loader); } pub fn update_font_size( &mut self, font: &Font, dpr: f64, loader: &mut L, ) -> Result<(), crossfont::Error> { // Update dpi scaling. self.rasterizer.update_dpr(dpr as f32); // 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, c: 'm', size: font.size() })?; let metrics = self.rasterizer.metrics(regular, font.size())?; info!("Font size changed to {:?} with DPR of {}", font.size(), dpr); 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.clear_glyph_cache(loader); Ok(()) } pub fn font_metrics(&self) -> crossfont::Metrics { self.metrics } /// Prefetch glyphs that are almost guaranteed to be loaded anyways. fn load_common_glyphs(&mut self, loader: &mut L) { self.load_glyphs_for_font(self.font_key, loader); self.load_glyphs_for_font(self.bold_italic_key, loader); self.load_glyphs_for_font(self.italic_key, loader); self.load_glyphs_for_font(self.bold_italic_key, loader); } /// Calculate font metrics without access to a glyph cache. pub fn static_metrics(font: Font, dpr: f64) -> Result { let mut rasterizer = crossfont::Rasterizer::new(dpr as f32, font.use_thin_strokes)?; let regular_desc = GlyphCache::make_desc(&font.normal(), Slant::Normal, Weight::Normal); let regular = Self::load_regular_font(&mut rasterizer, ®ular_desc, font.size())?; rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; rasterizer.metrics(regular, font.size()) } } // 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; } } #[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)] pub struct QuadRenderer { program: TextShaderProgram, vao: GLuint, ebo: GLuint, vbo_instance: GLuint, atlas: Vec, current_atlas: usize, active_tex: GLuint, batch: Batch, rect_renderer: RectRenderer, } #[derive(Debug)] pub struct RenderApi<'a> { active_tex: &'a mut GLuint, batch: &'a mut Batch, atlas: &'a mut Vec, current_atlas: &'a mut usize, program: &'a mut TextShaderProgram, config: &'a UIConfig, cursor_config: Cursor, } #[derive(Debug)] pub struct LoaderApi<'a> { active_tex: &'a mut GLuint, atlas: &'a mut Vec, current_atlas: &'a mut usize, } #[derive(Debug, Default)] pub struct Batch { tex: GLuint, instances: Vec, } impl Batch { #[inline] pub fn new() -> Self { Self { tex: 0, instances: Vec::with_capacity(BATCH_MAX) } } pub fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { 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.column.0 as u16, row: cell.line.0 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, }); } #[inline] pub fn full(&self) -> bool { self.capacity() == self.len() } #[inline] pub fn len(&self) -> usize { self.instances.len() } #[inline] pub fn capacity(&self) -> usize { BATCH_MAX } #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } #[inline] pub fn size(&self) -> usize { self.len() * size_of::() } pub fn clear(&mut self) { self.tex = 0; self.instances.clear(); } } /// Maximum items to be drawn in a batch. const BATCH_MAX: usize = 0x1_0000; const ATLAS_SIZE: i32 = 1024; impl QuadRenderer { pub fn new() -> Result { let program = TextShaderProgram::new()?; 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::()) 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::()) 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::() 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); } let mut renderer = Self { program, rect_renderer: RectRenderer::new()?, vao, ebo, vbo_instance, atlas: Vec::new(), current_atlas: 0, active_tex: 0, batch: Batch::new(), }; let atlas = Atlas::new(ATLAS_SIZE); renderer.atlas.push(atlas); Ok(renderer) } /// Draw all rectangles simultaneously to prevent excessive program swaps. pub fn draw_rects(&mut self, size_info: &SizeInfo, rects: Vec) { if rects.is_empty() { return; } // Prepare rect rendering state. unsafe { // Remove padding from viewport. gl::Viewport(0, 0, size_info.width() as i32, size_info.height() as i32); gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE); } self.rect_renderer.draw(size_info, rects); // Activate regular state again. unsafe { // Reset blending strategy. gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); // Restore viewport with padding. let padding_x = size_info.padding_x() as i32; let padding_y = size_info.padding_y() as i32; let width = size_info.width() as i32; let height = size_info.height() as i32; gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); } } pub fn with_api( &mut self, config: &UIConfig, cursor_config: Cursor, props: &SizeInfo, func: F, ) -> T where F: FnOnce(RenderApi<'_>) -> T, { unsafe { gl::UseProgram(self.program.id); self.program.set_term_uniforms(props); 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, config, cursor_config, }); unsafe { gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); gl::BindBuffer(gl::ARRAY_BUFFER, 0); gl::BindVertexArray(0); gl::UseProgram(0); } res } pub fn with_loader(&mut self, func: F) -> T where F: FnOnce(LoaderApi<'_>) -> T, { unsafe { gl::ActiveTexture(gl::TEXTURE0); } func(LoaderApi { active_tex: &mut self.active_tex, atlas: &mut self.atlas, current_atlas: &mut self.current_atlas, }) } pub fn resize(&mut self, size: &SizeInfo) { // Viewport. unsafe { gl::Viewport( size.padding_x() as i32, size.padding_y() as i32, size.width() as i32 - 2 * size.padding_x() as i32, size.height() as i32 - 2 * size.padding_y() as i32, ); // Update projection. gl::UseProgram(self.program.id); self.program.update_projection( size.width(), size.height(), size.padding_x(), size.padding_y(), ); gl::UseProgram(0); } } } impl<'a> RenderApi<'a> { pub fn clear(&self, color: Rgb) { unsafe { let alpha = self.config.background_opacity(); gl::ClearColor( (f32::from(color.r) / 255.0).min(1.0) * alpha, (f32::from(color.g) / 255.0).min(1.0) * alpha, (f32::from(color.b) / 255.0).min(1.0) * alpha, alpha, ); gl::Clear(gl::COLOR_BUFFER_BIT); } } #[cfg(not(any(target_os = "macos", windows)))] pub fn finish(&self) { unsafe { gl::Finish(); } } 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(); } /// Render a string in a variable location. Used for printing the render timer, warnings and /// errors. pub fn render_string( &mut self, glyph_cache: &mut GlyphCache, line: Line, string: &str, fg: Rgb, bg: Option, ) { let bg_alpha = bg.map(|_| 1.0).unwrap_or(0.0); let cells = string .chars() .enumerate() .map(|(i, c)| RenderableCell { line, column: Column(i), inner: RenderableCellContent::Chars((c, None)), flags: Flags::empty(), bg_alpha, fg, bg: bg.unwrap_or(Rgb { r: 0, g: 0, b: 0 }), is_match: false, }) .collect::>(); for cell in cells { self.render_cell(cell, glyph_cache); } } #[inline] fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { // 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); // Render batch and clear if it's full. if self.batch.full() { self.render_batch(); } } pub fn render_cell(&mut self, mut cell: RenderableCell, glyph_cache: &mut GlyphCache) { let (mut c, zerowidth) = match cell.inner { RenderableCellContent::Cursor(cursor_key) => { // Raw cell pixel buffers like cursors don't need to go through font lookup. let metrics = glyph_cache.metrics; let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| { self.load_glyph(&cursor::get_cursor_glyph( cursor_key.shape, metrics, self.config.font.offset.x, self.config.font.offset.y, cursor_key.is_wide, self.cursor_config.thickness(), )) }); self.add_render_item(&cell, glyph); return; }, RenderableCellContent::Chars((c, ref mut zerowidth)) => (c, zerowidth.take()), }; // 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 c == '\t' || hidden { c = ' '; } let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c }; // Add cell to batch. let glyph = glyph_cache.get(glyph_key, self); self.add_render_item(&cell, glyph); // Render visible zero-width characters. if let Some(zerowidth) = zerowidth.filter(|_| !hidden) { for c in zerowidth { glyph_key.c = c; let mut glyph = *glyph_cache.get(glyph_key, self); // 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. glyph.left += glyph_cache.metrics.average_advance as i16; self.add_render_item(&cell, &glyph); } } } } /// Load a glyph into a texture atlas. /// /// If the current atlas is full, a new one will be created. #[inline] fn load_glyph( active_tex: &mut GLuint, atlas: &mut Vec, 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); } 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] fn clear_atlas(atlas: &mut Vec, current_atlas: &mut usize) { for atlas in atlas.iter_mut() { atlas.clear(); } *current_atlas = 0; } impl<'a> LoadGlyph for LoaderApi<'a> { fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) } fn clear(&mut self) { clear_atlas(self.atlas, self.current_atlas) } } impl<'a> LoadGlyph for RenderApi<'a> { fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) } fn clear(&mut self) { 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(); } } } impl TextShaderProgram { pub fn new() -> Result { let vertex_shader = create_shader(gl::VERTEX_SHADER, TEXT_SHADER_V)?; let fragment_shader = create_shader(gl::FRAGMENT_SHADER, TEXT_SHADER_F)?; let program = create_program(vertex_shader, fragment_shader)?; unsafe { gl::DeleteShader(fragment_shader); gl::DeleteShader(vertex_shader); gl::UseProgram(program); } macro_rules! cptr { ($thing:expr) => { $thing.as_ptr() as *const _ }; } macro_rules! assert_uniform_valid { ($uniform:expr) => { assert!($uniform != gl::INVALID_VALUE as i32); assert!($uniform != gl::INVALID_OPERATION as i32); }; ( $( $uniform:expr ),* ) => { $( assert_uniform_valid!($uniform); )* }; } // get uniform locations let (projection, cell_dim, background) = unsafe { ( gl::GetUniformLocation(program, cptr!(b"projection\0")), gl::GetUniformLocation(program, cptr!(b"cellDim\0")), gl::GetUniformLocation(program, cptr!(b"backgroundPass\0")), ) }; assert_uniform_valid!(projection, cell_dim, background); let shader = Self { id: program, u_projection: projection, u_cell_dim: cell_dim, u_background: background, }; unsafe { gl::UseProgram(0); } Ok(shader) } fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) { // 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(self.u_projection, offset_x, offset_y, scale_x, scale_y); } } 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 Drop for TextShaderProgram { fn drop(&mut self) { unsafe { gl::DeleteProgram(self.id); } } } pub fn create_program(vertex: GLuint, fragment: GLuint) -> Result { unsafe { let program = gl::CreateProgram(); gl::AttachShader(program, vertex); gl::AttachShader(program, fragment); gl::LinkProgram(program); let mut success: GLint = 0; gl::GetProgramiv(program, gl::LINK_STATUS, &mut success); if success == i32::from(gl::TRUE) { Ok(program) } else { Err(ShaderCreationError::Link(get_program_info_log(program))) } } } pub fn create_shader(kind: GLenum, source: &'static str) -> Result { let len: [GLint; 1] = [source.len() as GLint]; let shader = unsafe { let shader = gl::CreateShader(kind); gl::ShaderSource(shader, 1, &(source.as_ptr() as *const _), len.as_ptr()); gl::CompileShader(shader); shader }; let mut success: GLint = 0; unsafe { gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success); } if success == GLint::from(gl::TRUE) { Ok(shader) } else { // Read log. let log = get_shader_info_log(shader); // Cleanup. unsafe { gl::DeleteShader(shader); } Err(ShaderCreationError::Compile(log)) } } fn get_program_info_log(program: GLuint) -> String { // Get expected log length. let mut max_length: GLint = 0; unsafe { gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut max_length); } // Read the info log. let mut actual_length: GLint = 0; let mut buf: Vec = Vec::with_capacity(max_length as usize); unsafe { gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); } // Build a string. unsafe { buf.set_len(actual_length as usize); } // XXX should we expect OpenGL to return garbage? String::from_utf8(buf).unwrap() } fn get_shader_info_log(shader: GLuint) -> String { // Get expected log length. let mut max_length: GLint = 0; unsafe { gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut max_length); } // Read the info log. let mut actual_length: GLint = 0; let mut buf: Vec = Vec::with_capacity(max_length as usize); unsafe { gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); } // Build a string. unsafe { buf.set_len(actual_length as usize); } // XXX should we expect OpenGL to return garbage? String::from_utf8(buf).unwrap() } #[derive(Debug)] pub enum ShaderCreationError { /// Error reading file. Io(io::Error), /// Error compiling shader. Compile(String), /// Problem linking. Link(String), } impl std::error::Error for ShaderCreationError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { ShaderCreationError::Io(err) => err.source(), _ => None, } } } impl Display for ShaderCreationError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { ShaderCreationError::Io(err) => write!(f, "Unable to read shader: {}", err), ShaderCreationError::Compile(log) => { write!(f, "Failed compiling shader: {}", log) }, ShaderCreationError::Link(log) => write!(f, "Failed linking shader: {}", log), } } } impl From for ShaderCreationError { fn from(val: io::Error) -> Self { ShaderCreationError::Io(val) } } /// 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)] 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. enum AtlasInsertError { /// Texture atlas is full. Full, /// The glyph cannot fit within a single texture. GlyphTooLarge, } impl Atlas { 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 { 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, buf) = match &glyph.buf { BitmapBuffer::RGB(buf) => { multicolor = false; (gl::RGB, buf) }, BitmapBuffer::RGBA(buf) => { multicolor = true; (gl::RGBA, buf) }, }; gl::TexSubImage2D( gl::TEXTURE_2D, 0, offset_x, offset_y, width, height, format, gl::UNSIGNED_BYTE, buf.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. 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. 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(()) } }