diff options
Diffstat (limited to 'alacritty/src/renderer/mod.rs')
-rw-r--r-- | alacritty/src/renderer/mod.rs | 1221 |
1 files changed, 91 insertions, 1130 deletions
diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 4aa562ad..cf9ee149 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -1,33 +1,27 @@ -use std::collections::HashMap; -use std::hash::BuildHasherDefault; -use std::mem::size_of; -use std::{fmt, ptr}; +use std::ffi::CStr; +use std::fmt; -use bitflags::bitflags; -use crossfont::{ - BitmapBuffer, 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 crossfont::Metrics; +use log::info; use alacritty_terminal::index::Point; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::SizeInfo; -use crate::config::font::{Font, FontDescription}; -use crate::config::ui_config::{Delta, UiConfig}; use crate::display::content::RenderableCell; use crate::gl; -use crate::gl::types::*; use crate::renderer::rects::{RectRenderer, RenderRect}; -use crate::renderer::shader::{ShaderError, ShaderProgram}; +use crate::renderer::shader::ShaderError; -pub mod builtin_font; pub mod rects; mod shader; +mod text; + +pub use text::{GlyphCache, LoaderApi}; + +use shader::ShaderVersion; +use text::{Gles2Renderer, Glsl3Renderer, TextRenderer}; macro_rules! cstr { ($s:literal) => { @@ -37,21 +31,6 @@ macro_rules! cstr { } pub(crate) use cstr; -// 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 { /// Shader error. @@ -82,588 +61,93 @@ impl From<ShaderError> for Error { } } -/// 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, -} - -#[derive(Copy, Clone, Debug)] -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, +enum TextRendererProvider { + Gles2(Gles2Renderer), + Glsl3(Glsl3Renderer), } -/// 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. - 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, - - /// 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, +#[derive(Debug)] +pub struct Renderer { + text_renderer: TextRendererProvider, + rect_renderer: RectRenderer, } -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. +impl Renderer { + /// Create a new renderer. /// - /// 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. - fn get<L>(&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; + /// This will automatically pick between the GLES2 and GLSL3 renderer based on the GPU's + /// supported OpenGL version. + pub fn new() -> Result<Self, Error> { + let (version, renderer) = unsafe { + let renderer = CStr::from_ptr(gl::GetString(gl::RENDERER) as *mut _); + let version = CStr::from_ptr(gl::GetString(gl::SHADING_LANGUAGE_VERSION) as *mut _); + (version.to_string_lossy(), renderer.to_string_lossy()) }; - // 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); + info!("Running on {}", renderer); - glyph - } - }, - Err(_) => self.load_glyph(loader, Default::default()), + let (text_renderer, rect_renderer) = if version.as_ref() >= "3.3" { + let text_renderer = TextRendererProvider::Glsl3(Glsl3Renderer::new()?); + let rect_renderer = RectRenderer::new(ShaderVersion::Glsl3)?; + (text_renderer, rect_renderer) + } else { + let text_renderer = TextRendererProvider::Gles2(Gles2Renderer::new()?); + let rect_renderer = RectRenderer::new(ShaderVersion::Gles2)?; + (text_renderer, rect_renderer) }; - // Cache rasterized glyph. - *self.cache.entry(glyph_key).or_insert(glyph) + Ok(Self { text_renderer, rect_renderer }) } - /// Load glyph into the atlas. - /// - /// This will apply all transforms defined for the glyph cache to the rasterized glyph before - /// insertion. - fn load_glyph<L>(&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; + pub fn draw_cells<I: Iterator<Item = RenderableCell>>( + &mut self, + size_info: &SizeInfo, + glyph_cache: &mut GlyphCache, + cells: I, + ) { + match &mut self.text_renderer { + TextRendererProvider::Gles2(renderer) => { + renderer.draw_cells(size_info, glyph_cache, cells) + }, + TextRendererProvider::Glsl3(renderer) => { + renderer.draw_cells(size_info, glyph_cache, cells) + }, } - - // 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>( + /// Draw a string in a variable location. Used for printing the render timer, warnings and + /// errors. + pub fn draw_string( &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); - } -} - -// 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<Atlas>, - 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<Atlas>, - current_atlas: &'a mut usize, - program: &'a mut TextShaderProgram, - config: &'a UiConfig, -} - -#[derive(Debug)] -pub struct LoaderApi<'a> { - active_tex: &'a mut GLuint, - atlas: &'a mut Vec<Atlas>, - current_atlas: &'a mut usize, -} - -#[derive(Debug, Default)] -pub struct Batch { - tex: GLuint, - instances: Vec<InstanceData>, -} - -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.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, + point: Point<usize>, + fg: Rgb, + bg: Rgb, + string: &str, + size_info: &SizeInfo, + glyph_cache: &mut GlyphCache, + ) { + let cells = string.chars().enumerate().map(|(i, character)| RenderableCell { + point: Point::new(point.line, point.column + i), + character, + zerowidth: None, + flags: Flags::empty(), + bg_alpha: 1.0, + fg, + bg, }); - } - #[inline] - pub fn full(&self) -> bool { - self.capacity() == self.len() + self.draw_cells(size_info, glyph_cache, cells); } - #[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::<InstanceData>() - } - - 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<QuadRenderer, Error> { - 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::<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); + pub fn with_loader<F, T>(&mut self, func: F) -> T + where + F: FnOnce(LoaderApi<'_>) -> T, + { + match &mut self.text_renderer { + TextRendererProvider::Gles2(renderer) => renderer.with_loader(func), + TextRendererProvider::Glsl3(renderer) => renderer.with_loader(func), } - - 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. @@ -691,99 +175,9 @@ impl QuadRenderer { } } - pub fn with_api<F, T>(&mut self, config: &UiConfig, props: &SizeInfo, func: F) -> T - where - F: FnOnce(RenderApi<'_>) -> T, - { + /// Fill the window with `color` and `alpha`. + pub fn clear(&self, color: Rgb, alpha: f32) { 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, - }); - - 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<F, T>(&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(&self, size: &SizeInfo) { - unsafe { - self.set_viewport(size); - - // Update projection. - gl::UseProgram(self.program.id()); - self.program.update_projection( - size.width(), - size.height(), - size.padding_x(), - size.padding_y(), - ); - gl::UseProgram(0); - } - } - - /// Set the viewport for cell rendering. - #[inline] - pub fn set_viewport(&self, size: &SizeInfo) { - 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, - ); - } - } -} - -impl Drop for QuadRenderer { - fn drop(&mut self) { - unsafe { - gl::DeleteBuffers(1, &self.vbo_instance); - gl::DeleteBuffers(1, &self.ebo); - gl::DeleteVertexArrays(1, &self.vao); - } - } -} - -impl<'a> RenderApi<'a> { - pub fn clear(&self, color: Rgb) { - unsafe { - let alpha = self.config.window_opacity(); gl::ClearColor( (f32::from(color.r) / 255.0).min(1.0) * alpha, (f32::from(color.g) / 255.0).min(1.0) * alpha, @@ -801,458 +195,25 @@ impl<'a> RenderApi<'a> { } } - 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(); - } - - /// Draw a string in a variable location. Used for printing the render timer, warnings and - /// errors. - pub fn draw_string( - &mut self, - glyph_cache: &mut GlyphCache, - point: Point<usize>, - fg: Rgb, - bg: Rgb, - string: &str, - ) { - let cells = string - .chars() - .enumerate() - .map(|(i, character)| RenderableCell { - point: Point::new(point.line, point.column + i), - character, - zerowidth: None, - flags: Flags::empty(), - bg_alpha: 1.0, - fg, - bg, - }) - .collect::<Vec<_>>(); - - for cell in cells { - self.draw_cell(cell, glyph_cache); - } - } - + /// Set the viewport for cell rendering. #[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 draw_cell(&mut self, mut cell: RenderableCell, glyph_cache: &mut GlyphCache) { - // 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); - - // 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); - } - } - } -} - -/// 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<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); - } - 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<Atlas>, 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<TextShaderProgram, Error> { - let program = ShaderProgram::new(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 id(&self) -> GLuint { - self.program.id() - } - - 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); - } - } -} - -/// 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<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; - + pub fn set_viewport(&self, size: &SizeInfo) { 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::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, ); - - 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(()) - } -} - -impl Drop for Atlas { - fn drop(&mut self) { - unsafe { - gl::DeleteTextures(1, &self.id); + /// Resize the renderer. + pub fn resize(&self, size_info: &SizeInfo) { + self.set_viewport(size_info); + match &self.text_renderer { + TextRendererProvider::Gles2(renderer) => renderer.resize(size_info), + TextRendererProvider::Glsl3(renderer) => renderer.resize(size_info), } } } |