use std::collections::HashMap; use std::mem; use crossfont::Metrics; use alacritty_terminal::grid::Dimensions; use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; use crate::display::content::RenderableCell; use crate::display::SizeInfo; use crate::gl; use crate::gl::types::*; use crate::renderer::shader::{ShaderError, ShaderProgram, ShaderVersion}; use crate::renderer::{self, cstr}; #[derive(Debug, Copy, Clone)] pub struct RenderRect { pub x: f32, pub y: f32, pub width: f32, pub height: f32, pub color: Rgb, pub alpha: f32, pub kind: RectKind, } impl RenderRect { pub fn new(x: f32, y: f32, width: f32, height: f32, color: Rgb, alpha: f32) -> Self { RenderRect { kind: RectKind::Normal, x, y, width, height, color, alpha } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct RenderLine { pub start: Point, pub end: Point, pub color: Rgb, } // NOTE: These flags must be in sync with their usage in the rect.*.glsl shaders. #[repr(u8)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum RectKind { Normal = 0, Undercurl = 1, DottedUnderline = 2, DashedUnderline = 3, NumKinds = 4, } impl RenderLine { pub fn rects(&self, flag: Flags, metrics: &Metrics, size: &SizeInfo) -> Vec { let mut rects = Vec::new(); let mut start = self.start; while start.line < self.end.line { let end = Point::new(start.line, size.last_column()); Self::push_rects(&mut rects, metrics, size, flag, start, end, self.color); start = Point::new(start.line + 1, Column(0)); } Self::push_rects(&mut rects, metrics, size, flag, start, self.end, self.color); rects } /// Push all rects required to draw the cell's line. fn push_rects( rects: &mut Vec, metrics: &Metrics, size: &SizeInfo, flag: Flags, start: Point, end: Point, color: Rgb, ) { let (position, thickness, ty) = match flag { Flags::DOUBLE_UNDERLINE => { // Position underlines so each one has 50% of descent available. let top_pos = 0.25 * metrics.descent; let bottom_pos = 0.75 * metrics.descent; rects.push(Self::create_rect( size, metrics.descent, start, end, top_pos, metrics.underline_thickness, color, )); (bottom_pos, metrics.underline_thickness, RectKind::Normal) }, // Make undercurl occupy the entire descent area. Flags::UNDERCURL => (metrics.descent, metrics.descent.abs(), RectKind::Undercurl), Flags::UNDERLINE => { (metrics.underline_position, metrics.underline_thickness, RectKind::Normal) }, // Make dotted occupy the entire descent area. Flags::DOTTED_UNDERLINE => { (metrics.descent, metrics.descent.abs(), RectKind::DottedUnderline) }, Flags::DASHED_UNDERLINE => { (metrics.underline_position, metrics.underline_thickness, RectKind::DashedUnderline) }, Flags::STRIKEOUT => { (metrics.strikeout_position, metrics.strikeout_thickness, RectKind::Normal) }, _ => unimplemented!("Invalid flag for cell line drawing specified"), }; let mut rect = Self::create_rect(size, metrics.descent, start, end, position, thickness, color); rect.kind = ty; rects.push(rect); } /// Create a line's rect at a position relative to the baseline. fn create_rect( size: &SizeInfo, descent: f32, start: Point, end: Point, position: f32, mut thickness: f32, color: Rgb, ) -> RenderRect { let start_x = start.column.0 as f32 * size.cell_width(); let end_x = (end.column.0 + 1) as f32 * size.cell_width(); let width = end_x - start_x; // Make sure lines are always visible. thickness = thickness.max(1.); let line_bottom = (start.line as f32 + 1.) * size.cell_height(); let baseline = line_bottom + descent; let mut y = (baseline - position - thickness / 2.).round(); let max_y = line_bottom - thickness; if y > max_y { y = max_y; } RenderRect::new( start_x + size.padding_x(), y + size.padding_y(), width, thickness, color, 1., ) } } /// Lines for underline and strikeout. #[derive(Default)] pub struct RenderLines { inner: HashMap>, } impl RenderLines { #[inline] pub fn new() -> Self { Self::default() } #[inline] pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec { self.inner .iter() .flat_map(|(flag, lines)| { lines.iter().flat_map(move |line| line.rects(*flag, metrics, size)) }) .collect() } /// Update the stored lines with the next cell info. #[inline] pub fn update(&mut self, cell: &RenderableCell) { self.update_flag(cell, Flags::UNDERLINE); self.update_flag(cell, Flags::DOUBLE_UNDERLINE); self.update_flag(cell, Flags::STRIKEOUT); self.update_flag(cell, Flags::UNDERCURL); self.update_flag(cell, Flags::DOTTED_UNDERLINE); self.update_flag(cell, Flags::DASHED_UNDERLINE); } /// Update the lines for a specific flag. fn update_flag(&mut self, cell: &RenderableCell, flag: Flags) { if !cell.flags.contains(flag) { return; } // The underline color escape does not apply to strikeout. let color = if flag.contains(Flags::STRIKEOUT) { cell.fg } else { cell.underline }; // Include wide char spacer if the current cell is a wide char. let mut end = cell.point; if cell.flags.contains(Flags::WIDE_CHAR) { end.column += 1; } // Check if there's an active line. if let Some(line) = self.inner.get_mut(&flag).and_then(|lines| lines.last_mut()) { if color == line.color && cell.point.column == line.end.column + 1 && cell.point.line == line.end.line { // Update the length of the line. line.end = end; return; } } // Start new line if there currently is none. let line = RenderLine { start: cell.point, end, color }; match self.inner.get_mut(&flag) { Some(lines) => lines.push(line), None => { self.inner.insert(flag, vec![line]); }, } } } /// Shader sources for rect rendering program. static RECT_SHADER_F: &str = include_str!("../../res/rect.f.glsl"); static RECT_SHADER_V: &str = include_str!("../../res/rect.v.glsl"); #[repr(C)] #[derive(Debug, Clone, Copy)] struct Vertex { // Normalized screen coordinates. x: f32, y: f32, // Color. r: u8, g: u8, b: u8, a: u8, } #[derive(Debug)] pub struct RectRenderer { // GL buffer objects. vao: GLuint, vbo: GLuint, program: RectShaderProgram, vertices: [Vec; 4], } impl RectRenderer { pub fn new(shader_version: ShaderVersion) -> Result { let mut vao: GLuint = 0; let mut vbo: GLuint = 0; let program = RectShaderProgram::new(shader_version)?; unsafe { // Allocate buffers. gl::GenVertexArrays(1, &mut vao); gl::GenBuffers(1, &mut vbo); gl::BindVertexArray(vao); // VBO binding is not part of VAO itself, but VBO binding is stored in attributes. gl::BindBuffer(gl::ARRAY_BUFFER, vbo); let mut attribute_offset = 0; // Position. gl::VertexAttribPointer( 0, 2, gl::FLOAT, gl::FALSE, mem::size_of::() as i32, attribute_offset as *const _, ); gl::EnableVertexAttribArray(0); attribute_offset += mem::size_of::() * 2; // Color. gl::VertexAttribPointer( 1, 4, gl::UNSIGNED_BYTE, gl::TRUE, mem::size_of::() as i32, attribute_offset as *const _, ); gl::EnableVertexAttribArray(1); // Reset buffer bindings. gl::BindVertexArray(0); gl::BindBuffer(gl::ARRAY_BUFFER, 0); } Ok(Self { vao, vbo, program, vertices: Default::default() }) } pub fn draw(&mut self, size_info: &SizeInfo, metrics: &Metrics, rects: Vec) { unsafe { // Bind VAO to enable vertex attribute slots. gl::BindVertexArray(self.vao); // Bind VBO only once for buffer data upload only. gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo); gl::UseProgram(self.program.id()); self.program.update_uniforms(size_info, metrics); } let half_width = size_info.width() / 2.; let half_height = size_info.height() / 2.; // Build rect vertices vector. self.vertices.iter_mut().for_each(|vertices| vertices.clear()); for rect in &rects { Self::add_rect(&mut self.vertices[rect.kind as usize], half_width, half_height, rect); } unsafe { // We iterate in reverse order to draw plain rects at the end, since we want visual // bell or damage rects be above the lines. for rect_kind in (RectKind::Normal as u8..RectKind::NumKinds as u8).rev() { let vertices = &mut self.vertices[rect_kind as usize]; if vertices.is_empty() { continue; } self.program.set_rect_kind(rect_kind as u8); // Upload accumulated undercurl vertices. gl::BufferData( gl::ARRAY_BUFFER, (vertices.len() * mem::size_of::()) as isize, vertices.as_ptr() as *const _, gl::STREAM_DRAW, ); // Draw all vertices as list of triangles. gl::DrawArrays(gl::TRIANGLES, 0, vertices.len() as i32); } // Disable program. gl::UseProgram(0); // Reset buffer bindings to nothing. gl::BindBuffer(gl::ARRAY_BUFFER, 0); gl::BindVertexArray(0); } } fn add_rect(vertices: &mut Vec, half_width: f32, half_height: f32, rect: &RenderRect) { // Calculate rectangle vertices positions in normalized device coordinates. // NDC range from -1 to +1, with Y pointing up. let x = rect.x / half_width - 1.0; let y = -rect.y / half_height + 1.0; let width = rect.width / half_width; let height = rect.height / half_height; let Rgb { r, g, b } = rect.color; let a = (rect.alpha * 255.) as u8; // Make quad vertices. let quad = [ Vertex { x, y, r, g, b, a }, Vertex { x, y: y - height, r, g, b, a }, Vertex { x: x + width, y, r, g, b, a }, Vertex { x: x + width, y: y - height, r, g, b, a }, ]; // Append the vertices to form two triangles. vertices.push(quad[0]); vertices.push(quad[1]); vertices.push(quad[2]); vertices.push(quad[2]); vertices.push(quad[3]); vertices.push(quad[1]); } } impl Drop for RectRenderer { fn drop(&mut self) { unsafe { gl::DeleteBuffers(1, &self.vbo); gl::DeleteVertexArrays(1, &self.vao); } } } /// Rectangle drawing program. #[derive(Debug)] pub struct RectShaderProgram { /// Shader program. program: ShaderProgram, /// Kind of rect we're drawing. u_rect_kind: GLint, /// Cell width. u_cell_width: GLint, /// Cell height. u_cell_height: GLint, /// Terminal padding. u_padding_x: GLint, /// A padding from the bottom of the screen to viewport. u_padding_y: GLint, /// Underline position. u_underline_position: GLint, /// Underline thickness. u_underline_thickness: GLint, /// Undercurl position. u_undercurl_position: GLint, } impl RectShaderProgram { pub fn new(shader_version: ShaderVersion) -> Result { let program = ShaderProgram::new(shader_version, RECT_SHADER_V, RECT_SHADER_F)?; Ok(Self { u_rect_kind: program.get_uniform_location(cstr!("rectKind"))?, u_cell_width: program.get_uniform_location(cstr!("cellWidth"))?, u_cell_height: program.get_uniform_location(cstr!("cellHeight"))?, u_padding_x: program.get_uniform_location(cstr!("paddingX"))?, u_padding_y: program.get_uniform_location(cstr!("paddingY"))?, u_underline_position: program.get_uniform_location(cstr!("underlinePosition"))?, u_underline_thickness: program.get_uniform_location(cstr!("underlineThickness"))?, u_undercurl_position: program.get_uniform_location(cstr!("undercurlPosition"))?, program, }) } fn id(&self) -> GLuint { self.program.id() } fn set_rect_kind(&self, ty: u8) { unsafe { gl::Uniform1i(self.u_rect_kind, ty as i32); } } pub fn update_uniforms(&self, size_info: &SizeInfo, metrics: &Metrics) { let position = (0.5 * metrics.descent).abs(); let underline_position = metrics.descent.abs() - metrics.underline_position.abs(); let viewport_height = size_info.height() - size_info.padding_y(); let padding_y = viewport_height - (viewport_height / size_info.cell_height()).floor() * size_info.cell_height(); unsafe { gl::Uniform1f(self.u_cell_width, size_info.cell_width()); gl::Uniform1f(self.u_cell_height, size_info.cell_height()); gl::Uniform1f(self.u_padding_y, padding_y); gl::Uniform1f(self.u_padding_x, size_info.padding_x()); gl::Uniform1f(self.u_underline_position, underline_position); gl::Uniform1f(self.u_underline_thickness, metrics.underline_thickness); gl::Uniform1f(self.u_undercurl_position, position); } } }