diff options
author | Ayose <ayosec@gmail.com> | 2021-03-10 20:21:36 +0000 |
---|---|---|
committer | betaboon <betaboon@0x80.ninja> | 2021-08-17 15:44:40 +0200 |
commit | 0fd3793a963015e8f91021e35dbb2169d15c3211 (patch) | |
tree | a83c1f53a123f84376a58cf2a5114ad581092e0a /alacritty | |
parent | c24d7dfd0d2d8849f0398d7cb1a65d6562ee7a0d (diff) | |
download | r-alacritty-0fd3793a963015e8f91021e35dbb2169d15c3211.tar.gz r-alacritty-0fd3793a963015e8f91021e35dbb2169d15c3211.tar.bz2 r-alacritty-0fd3793a963015e8f91021e35dbb2169d15c3211.zip |
Add Sixel support
Fixes #910
Diffstat (limited to 'alacritty')
-rw-r--r-- | alacritty/Cargo.toml | 1 | ||||
-rw-r--r-- | alacritty/res/graphics.f.glsl | 40 | ||||
-rw-r--r-- | alacritty/res/graphics.v.glsl | 79 | ||||
-rw-r--r-- | alacritty/src/display/content.rs | 3 | ||||
-rw-r--r-- | alacritty/src/display/mod.rs | 14 | ||||
-rw-r--r-- | alacritty/src/renderer/graphics/draw.rs | 242 | ||||
-rw-r--r-- | alacritty/src/renderer/graphics/mod.rs | 149 | ||||
-rw-r--r-- | alacritty/src/renderer/graphics/shader.rs | 199 | ||||
-rw-r--r-- | alacritty/src/renderer/mod.rs | 19 | ||||
-rw-r--r-- | alacritty/src/url.rs | 276 |
10 files changed, 1021 insertions, 1 deletions
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 6984728a..cb269350 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -34,6 +34,7 @@ libc = "0.2" unicode-width = "0.1" bitflags = "1" dirs = "3.0.1" +memoffset = "0.6.1" [build-dependencies] gl_generator = "0.14.0" diff --git a/alacritty/res/graphics.f.glsl b/alacritty/res/graphics.f.glsl new file mode 100644 index 00000000..28da2a68 --- /dev/null +++ b/alacritty/res/graphics.f.glsl @@ -0,0 +1,40 @@ +#version 330 core + +// Index in the textures[] uniform. +flat in int texId; + +// Texture coordinates. +in vec2 texCoords; + +// Array with graphics data. +uniform sampler2D textures[16]; + +// Computed color. +out vec4 color; + +void main() { + // The expression `textures[texId]` can't be used in OpenGL 3.3. + // If we try to use it, the compiler throws this error: + // + // sampler arrays indexed with non-constant expressions + // are forbidden in GLSL 1.30 and later + // + // To overcome this limitation we use a switch for every valid + // value of `texId`. + // + // The first expression (`textures[texId]`) works with OpenGL 4.0 + // or later (using `#version 400 core`). If Alacritty drops support + // for OpenGL 3.3, this switch block can be replaced with it. + + +#define TEX(N) case N: color = texture(textures[N], texCoords); break; + + switch(texId) { + TEX( 0) TEX( 1) TEX( 2) TEX( 3) + TEX( 4) TEX( 5) TEX( 6) TEX( 7) + TEX( 8) TEX( 9) TEX(10) TEX(11) + TEX(12) TEX(13) TEX(14) TEX(15) + default: + discard; + } +} diff --git a/alacritty/res/graphics.v.glsl b/alacritty/res/graphics.v.glsl new file mode 100644 index 00000000..56998e9d --- /dev/null +++ b/alacritty/res/graphics.v.glsl @@ -0,0 +1,79 @@ +#version 330 core + +// ------ +// INPUTS + +// Texture associated to the graphic. +layout(location = 0) in int textureId; + +// Sides where the vertex is located. +// +// Bit 0 (LSB) is 0 for top and 1 for bottom. +// Bit 1 is 0 for left and 1 for right. +layout(location = 1) in int sides; + +// Column number in the grid where the left vertex is set. +layout(location = 2) in float column; + +// Line number in the grid where the left vertex is set. +layout(location = 3) in float line; + +// Height in pixels of the texture. +layout(location = 4) in float height; + +// Width in pixels of the texture. +layout(location = 5) in float width; + +// Offset in the X direction. +layout(location = 6) in float offsetX; + +// Offset in the Y direction. +layout(location = 7) in float offsetY; + +// Height in pixels of a single cell when the graphic was added. +layout(location = 8) in float baseCellHeight; + +// ------- +// OUTPUTS + +// Texture sent to the fragment shader. +flat out int texId; + +// Coordinates sent to the fragment shader. +out vec2 texCoords; + +// -------- +// UNIFORMS + +// Width and height of a single cell. +uniform vec2 cellDimensions; + +// Width and height of the view. +uniform vec2 viewDimensions; + + +#define IS_RIGHT_SIDE ((sides & 1) == 1) +#define IS_BOTTOM_SIDE ((sides & 2) == 2) + +void main() { + float scale = cellDimensions.y / baseCellHeight; + float x = (column * cellDimensions.x - offsetX * scale) / (viewDimensions.x / 2) - 1; + float y = -(line * cellDimensions.y - offsetY * scale) / (viewDimensions.y / 2) + 1; + + vec4 position = vec4(x, y, 0, 1); + vec2 coords = vec2(0, 0); + + if(IS_RIGHT_SIDE) { + position.x += scale * width / (viewDimensions.x / 2); + coords.x = 1; + } + + if(IS_BOTTOM_SIDE) { + position.y += -scale * height / (viewDimensions.y / 2); + coords.y = 1; + } + + gl_Position = position; + texCoords = coords; + texId = textureId; +} diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index 2deb3d3e..85719c06 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -6,6 +6,7 @@ use std::ops::{Deref, DerefMut, RangeInclusive}; use alacritty_terminal::ansi::{Color, CursorShape, NamedColor}; use alacritty_terminal::config::Config; use alacritty_terminal::event::EventListener; +use alacritty_terminal::graphics::GraphicCell; use alacritty_terminal::grid::{Dimensions, Indexed}; use alacritty_terminal::index::{Column, Direction, Line, Point}; use alacritty_terminal::term::cell::{Cell, Flags}; @@ -187,6 +188,7 @@ pub struct RenderableCell { pub character: char, pub zerowidth: Option<Vec<char>>, pub point: Point<usize>, + pub graphic: Option<GraphicCell>, pub fg: Rgb, pub bg: Rgb, pub bg_alpha: f32, @@ -257,6 +259,7 @@ impl RenderableCell { RenderableCell { zerowidth: cell.zerowidth().map(|zerowidth| zerowidth.to_vec()), + graphic: cell.graphic().cloned(), flags: cell.flags, character, bg_alpha, diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index d4c5c274..5e958f03 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -479,7 +479,7 @@ impl Display { /// This call may block if vsync is enabled. pub fn draw<T: EventListener>( &mut self, - terminal: MutexGuard<'_, Term<T>>, + mut terminal: MutexGuard<'_, Term<T>>, message_buffer: &MessageBuffer, config: &Config, search_state: &SearchState, @@ -502,6 +502,8 @@ impl Display { let vi_mode = terminal.mode().contains(TermMode::VI); let vi_mode_cursor = if vi_mode { Some(terminal.vi_mode_cursor) } else { None }; + let graphics_queues = terminal.graphics_take_queues(); + // Drop terminal as early as possible to free lock. drop(terminal); @@ -509,7 +511,12 @@ impl Display { api.clear(background_color); }); + if let Some(graphics_queues) = graphics_queues { + self.renderer.graphics_run_updates(graphics_queues, &size_info); + } + let mut lines = RenderLines::new(); + let mut graphics_list = renderer::graphics::RenderList::default(); // Draw grid. { @@ -532,12 +539,17 @@ impl Display { // Update underline/strikeout. lines.update(&cell); + // Track any graphic present in the cell. + graphics_list.update(&cell); + // Draw the cell. api.render_cell(cell, glyph_cache); } }); } + self.renderer.graphics_draw(graphics_list, &size_info); + let mut rects = lines.rects(&metrics, &size_info); if let Some(vi_mode_cursor) = vi_mode_cursor { diff --git a/alacritty/src/renderer/graphics/draw.rs b/alacritty/src/renderer/graphics/draw.rs new file mode 100644 index 00000000..81013b85 --- /dev/null +++ b/alacritty/src/renderer/graphics/draw.rs @@ -0,0 +1,242 @@ +//! This module implements the functionality to render graphic textures +//! in the display. +//! +//! [`RenderList`] is used to track graphics in the visible cells. When all +//! cells in the grid are read, graphics are rendered using the positions +//! found in those cells. + +use std::collections::BTreeMap; +use std::mem::{self, MaybeUninit}; + +use crate::display::content::RenderableCell; +use crate::gl::{self, types::*}; +use crate::renderer::graphics::{shader, GraphicsRenderer}; + +use alacritty_terminal::graphics::GraphicId; +use alacritty_terminal::index::{Column, Line}; +use alacritty_terminal::term::SizeInfo; + +use log::trace; + +/// Position to render each texture in the grid. +struct RenderPosition { + column: Column, + line: Line, + offset_x: u16, + offset_y: u16, +} + +/// Track textures to be rendered in the display. +#[derive(Default)] +pub struct RenderList { + items: BTreeMap<GraphicId, RenderPosition>, +} + +impl RenderList { + /// Detects if the cell contains a graphic, then add it to the render list. + /// + /// The graphic is added only the first time it is found in a cell. + #[inline] + pub fn update(&mut self, cell: &RenderableCell) { + if let Some(graphic) = &cell.graphic { + let graphic_id = graphic.graphic_id(); + if self.items.contains_key(&graphic_id) { + return; + } + + let render_item = RenderPosition { + column: cell.point.column, + line: cell.point.line, + offset_x: graphic.offset_x, + offset_y: graphic.offset_y, + }; + + self.items.insert(graphic_id, render_item); + } + } + + /// Returns `true` if there are no items to render. + #[inline] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Builds a list of vertex for the shader program. + pub fn build_vertices(self, renderer: &GraphicsRenderer) -> Vec<shader::Vertex> { + use shader::VertexSide::{BottomLeft, BottomRight, TopLeft, TopRight}; + + let mut vertices = Vec::new(); + + for (graphics_id, render_item) in self.items { + let graphic_texture = match renderer.graphic_textures.get(&graphics_id) { + Some(tex) => tex, + None => continue, + }; + + vertices.reserve(6); + + let vertex = shader::Vertex { + texture_id: graphic_texture.texture.0, + sides: TopLeft, + column: render_item.column.0 as GLuint, + line: render_item.line.0 as GLuint, + height: graphic_texture.height, + width: graphic_texture.width, + offset_x: render_item.offset_x, + offset_y: render_item.offset_y, + base_cell_height: graphic_texture.cell_height, + }; + + vertices.push(vertex); + + for &sides in &[TopRight, BottomLeft, TopRight, BottomRight, BottomLeft] { + vertices.push(shader::Vertex { sides, ..vertex }); + } + } + + vertices + } + + /// Draw graphics in the display, using the graphics rendering shader + /// program. + pub fn draw(self, renderer: &GraphicsRenderer, size_info: &SizeInfo) { + let vertices = self.build_vertices(renderer); + + // Initialize the rendering program. + unsafe { + gl::BindBuffer(gl::ARRAY_BUFFER, renderer.program.vbo); + gl::BindVertexArray(renderer.program.vao); + + gl::UseProgram(renderer.program.id); + + gl::Uniform2f( + renderer.program.u_cell_dimensions, + size_info.cell_width(), + size_info.cell_height(), + ); + gl::Uniform2f( + renderer.program.u_view_dimensions, + size_info.width(), + size_info.height(), + ); + + gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE); + } + + // Array for storing the batch to render multiple graphics in a single call to the + // shader program. + // + // Each graphic requires 6 vertices (2 triangles to make a rectangle), and we will + // never have more than `TEXTURES_ARRAY_SIZE` graphics in a single call, so we set + // the array size to the maximum value that we can use. + let mut batch = [MaybeUninit::uninit(); shader::TEXTURES_ARRAY_SIZE * 6]; + let mut batch_size = 0; + + macro_rules! send_batch { + () => { + #[allow(unused_assignments)] + if batch_size > 0 { + trace!("Call glDrawArrays with {} items", batch_size); + + unsafe { + gl::BufferData( + gl::ARRAY_BUFFER, + (batch_size * mem::size_of::<shader::Vertex>()) as isize, + batch.as_ptr().cast(), + gl::STREAM_DRAW, + ); + + gl::DrawArrays(gl::TRIANGLES, 0, batch_size as GLint); + } + + batch_size = 0; + } + }; + } + + // In order to send textures to the shader program we need to get a _slot_ + // for every texture associated to a graphic. + // + // We have `u_textures.len()` slots available in each execution of the + // shader. + // + // For each slot we need three values: + // + // - The texture unit for `glActiveTexture` (`GL_TEXTUREi`). + // - The uniform location for `textures[i]`. + // - The index `i`, used to set the value of the uniform. + // + // These values are generated using the `tex_slots_generator` iterator. + // + // A single graphic has 6 vertices. All vertices will use the same texture + // slot. To detect if a texture has already a slot, we only need to compare + // with the last texture (`last_tex_slot`) because all the vertices of a + // single graphic are consecutive. + // + // When all slots are occupied, or the batch array is full, the current + // batch is sent and the iterator is reset. + // + // This logic could be simplified using the [Bindless Texture extension], + // but it is not a core feature of any OpenGL version, so hardware support + // is uncertain. + // + // [Bindless Texture extension]: https://www.khronos.org/opengl/wiki/Bindless_Texture + + let tex_slots_generator = (gl::TEXTURE0..=gl::TEXTURE31) + .zip(renderer.program.u_textures.iter()) + .zip(0_u32..) + .map(|((tex_enum, &u_texture), index)| (tex_enum, u_texture, index)); + + let mut tex_slots = tex_slots_generator.clone(); + + // Keep the last allocated slot in a `(texture id, index)` tuple. + let mut last_tex_slot = (0, 0); + + for mut vertex in vertices { + // Check if we can reuse the last texture slot. + if last_tex_slot.0 != vertex.texture_id { + last_tex_slot = loop { + match tex_slots.next() { + None => { + // No more slots. Send the batch and reset the iterator. + send_batch!(); + tex_slots = tex_slots_generator.clone(); + }, + + Some((tex_enum, u_texture, index)) => { + unsafe { + gl::ActiveTexture(tex_enum); + gl::BindTexture(gl::TEXTURE_2D, vertex.texture_id); + gl::Uniform1i(u_texture, index as GLint); + } + + break (vertex.texture_id, index); + }, + } + }; + } + + vertex.texture_id = last_tex_slot.1; + batch[batch_size] = MaybeUninit::new(vertex); + batch_size += 1; + + if batch_size == batch.len() { + send_batch!(); + } + } + + send_batch!(); + + // Reset state. + unsafe { + gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); + + gl::ActiveTexture(gl::TEXTURE0); + gl::BindTexture(gl::TEXTURE_2D, 0); + + gl::UseProgram(0); + gl::BindVertexArray(0); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + } + } +} diff --git a/alacritty/src/renderer/graphics/mod.rs b/alacritty/src/renderer/graphics/mod.rs new file mode 100644 index 00000000..3fecd549 --- /dev/null +++ b/alacritty/src/renderer/graphics/mod.rs @@ -0,0 +1,149 @@ +//! This module implements the functionality to support graphics in the grid. + +use std::mem; + +use alacritty_terminal::graphics::{ColorType, GraphicData, GraphicId, UpdateQueues}; +use alacritty_terminal::term::SizeInfo; + +use log::trace; +use serde::{Deserialize, Serialize}; + +use crate::gl; +use crate::gl::types::*; +use crate::renderer; + +use std::collections::HashMap; + +mod draw; +mod shader; + +pub use draw::RenderList; + +/// Type for texture names generated in the GPU. +#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] +pub struct TextureName(GLuint); + +// In debug mode, check if the inner value was set to zero, so we can detect if +// the associated texture was deleted from the GPU. +#[cfg(debug_assertions)] +impl Drop for TextureName { + fn drop(&mut self) { + if self.0 != 0 { + log::error!("Texture {} was not deleted.", self.0); + } + } +} + +/// Texture for a graphic in the grid. +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct GraphicTexture { + /// Texture in the GPU where the graphic pixels are stored. + texture: TextureName, + + /// Cell height at the moment graphic was created. + /// + /// Used to scale it if the user increases or decreases the font size. + cell_height: f32, + + /// Width in pixels of the graphic. + width: u16, + + /// Height in pixels of the graphic. + height: u16, +} + +#[derive(Debug)] +pub struct GraphicsRenderer { + /// Program in the GPU to render graphics. + program: shader::GraphicsShaderProgram, + + /// Collection to associate graphic identifiers with their textures. + graphic_textures: HashMap<GraphicId, GraphicTexture>, +} + +impl GraphicsRenderer { + pub fn new() -> Result<GraphicsRenderer, renderer::Error> { + let program = shader::GraphicsShaderProgram::new()?; + Ok(GraphicsRenderer { program, graphic_textures: HashMap::default() }) + } + + /// Run the required actions to apply changes for the graphics in the grid. + #[inline] + pub fn run_updates(&mut self, update_queues: UpdateQueues, size_info: &SizeInfo) { + self.remove_graphics(update_queues.remove_queue); + self.upload_pending_graphics(update_queues.pending, size_info); + } + + /// Release resources used by removed graphics. + fn remove_graphics(&mut self, removed_ids: Vec<GraphicId>) { + let mut textures = Vec::with_capacity(removed_ids.len()); + for id in removed_ids { + if let Some(mut graphic_texture) = self.graphic_textures.remove(&id) { + // Reset the inner value of TextureName, so the Drop implementation + // (in debug mode) can verify that the texture was deleted. + textures.push(mem::take(&mut graphic_texture.texture.0)); + } + } + + trace!("Call glDeleteTextures with {} items", textures.len()); + + unsafe { + gl::DeleteTextures(textures.len() as GLint, textures.as_ptr()); + } + } + + /// Create new textures in the GPU, and upload the pixels to them. + fn upload_pending_graphics(&mut self, graphics: Vec<GraphicData>, size_info: &SizeInfo) { + for graphic in graphics { + let mut texture = 0; + + unsafe { + gl::GenTextures(1, &mut texture); + trace!("Texture generated: {}", texture); + + gl::BindTexture(gl::TEXTURE_2D, texture); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAX_LEVEL, 0); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); + + let pixel_format = match graphic.color_type { + ColorType::Rgb => gl::RGB, + ColorType::Rgba => gl::RGBA, + }; + + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA as GLint, + graphic.width as GLint, + graphic.height as GLint, + 0, + pixel_format, + gl::UNSIGNED_BYTE, + graphic.pixels.as_ptr().cast(), + ); + + gl::BindTexture(gl::TEXTURE_2D, 0); + } + + let graphic_texture = GraphicTexture { + texture: TextureName(texture), + cell_height: size_info.cell_height(), + width: graphic.width as u16, + height: graphic.height as u16, + }; + + self.graphic_textures.insert(graphic.id, graphic_texture); + } + } + + /// Draw graphics in the display. + #[inline] + pub fn draw(&mut self, render_list: RenderList, size_info: &SizeInfo) { + if !render_list.is_empty() { + render_list.draw(self, size_info); + } + } +} diff --git a/alacritty/src/renderer/graphics/shader.rs b/alacritty/src/renderer/graphics/shader.rs new file mode 100644 index 00000000..8d30d2a3 --- /dev/null +++ b/alacritty/src/renderer/graphics/shader.rs @@ -0,0 +1,199 @@ +use std::mem; + +use crate::gl; +use crate::gl::types::*; +use crate::renderer; + +/// Number of elements of the `textures[]` uniform. +/// +/// If the file `graphics.f.glsl` is modified, this value has to be updated. +pub(super) const TEXTURES_ARRAY_SIZE: usize = 16; + +/// Sides where the vertex is located. +/// +/// * Bit 0 (LSB) is 0 for top and 1 for bottom. +/// * Bit 1 is 0 for left and 1 for right. +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(u8)] +pub enum VertexSide { + TopLeft = 0b00, + TopRight = 0b10, + BottomLeft = 0b01, + BottomRight = 0b11, +} + +/// Vertex data to execute the graphics rendering program. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Vertex { + /// Texture associated to the graphic. + pub texture_id: GLuint, + + /// Sides where the vertex is located. + pub sides: VertexSide, + + /// Column number in the grid where the left vertex is set. + pub column: GLuint, + + /// Line where the top vertex is set. + pub line: GLuint, + + /// Height, in pixels, of the texture. + pub height: u16, + + /// Width, in pixels, of the texture. + pub width: u16, + + /// Offset in the x direction. + pub offset_x: u16, + + /// Offset in the y direction. + pub offset_y: u16, + + /// Height, in pixels, of a single cell when the graphic was added. + pub base_cell_height: f32, +} + +/// Sources for the graphics rendering program. +static GRAPHICS_SHADER_F: &str = include_str!("../../../res/graphics.f.glsl"); +static GRAPHICS_SHADER_V: &str = include_str!("../../../res/graphics.v.glsl"); + +/// Graphics rendering program. +#[derive(Debug)] +pub struct GraphicsShaderProgram { + /// Program id. + pub id: GLuint, + + /// Uniform of the cell dimensions. + pub u_cell_dimensions: GLint, + + /// Uniform of the view dimensions. + pub u_view_dimensions: GLint, + + /// Uniform array of the textures. + pub u_textures: Vec<GLint>, + + /// Vertex Array Object (VAO) for the fields of `Vertex`. + pub vao: GLuint, + + /// Vertex Buffer Object (VBO) to send instances of `Vertex`. + pub vbo: GLuint, +} + +impl GraphicsShaderProgram { + pub fn new() -> Result<Self, renderer::ShaderCreationError> { + let vertex_shader = renderer::create_shader(gl::VERTEX_SHADER, GRAPHICS_SHADER_V)?; + let fragment_shader = renderer::create_shader(gl::FRAGMENT_SHADER, GRAPHICS_SHADER_F)?; + let program = renderer::create_program(vertex_shader, fragment_shader)?; + + let u_cell_dimensions; + let u_view_dimensions; + let u_textures; + + unsafe { + gl::DeleteShader(fragment_shader); + gl::DeleteShader(vertex_shader); + + gl::UseProgram(program); + + // Uniform locations. + + macro_rules! uniform { + ($name:literal) => { + gl::GetUniformLocation( + program, + concat!($name, "\0").as_bytes().as_ptr().cast(), + ) + }; + + ($fmt:literal, $($arg:tt)+) => { + match format!(concat!($fmt, "\0"), $($arg)+) { + name => gl::GetUniformLocation( + program, + name.as_bytes().as_ptr().cast(), + ) + } + }; + } + + u_cell_dimensions = uniform!("cellDimensions"); + u_view_dimensions = uniform!("viewDimensions"); + u_textures = + (0..TEXTURES_ARRAY_SIZE).map(|unit| uniform!("textures[{}]", unit)).collect(); + + gl::UseProgram(0); + } + + let (vao, vbo) = define_vertex_attributes(); + + let shader = + Self { id: program, u_cell_dimensions, u_view_dimensions, u_textures, vao, vbo }; + + Ok(shader) + } +} + +/// Build a Vertex Array Object (VAO) and a Vertex Buffer Object (VBO) for +/// instances of the `Vertex` type. +fn define_vertex_attributes() -> (GLuint, GLuint) { + let mut vao = 0; + let mut vbo = 0; + + unsafe { + gl::GenVertexArrays(1, &mut vao); + gl::GenBuffers(1, &mut vbo); + + gl::BindVertexArray(vao); + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + + let mut attr_index = 0; + + macro_rules! int_attr { + ($type:ident, $field:ident) => { + gl::VertexAttribIPointer( + attr_index, + 1, + gl::$type, + mem::size_of::<Vertex>() as i32, + memoffset::offset_of!(Vertex, $field) as *const _, + ); + + attr_index += 1; + }; + } + + macro_rules! float_attr { + ($type:ident, $field:ident) => { + gl::VertexAttribPointer( + attr_index, + 1, + gl::$type, + gl::FALSE, + mem::size_of::<Vertex>() as i32, + memoffset::offset_of!(Vertex, $field) as *const _, + ); + + attr_index += 1; + }; + } + + int_attr!(UNSIGNED_INT, texture_id); + int_attr!(UNSIGNED_BYTE, sides); + + float_attr!(UNSIGNED_INT, column); + float_attr!(UNSIGNED_INT, line); + float_attr!(UNSIGNED_SHORT, height); + float_attr!(UNSIGNED_SHORT, width); + float_attr!(UNSIGNED_SHORT, offset_x); + float_attr!(UNSIGNED_SHORT, offset_y); + float_attr!(FLOAT, base_cell_height); + + for index in 0..attr_index { + gl::EnableVertexAttribArray(index); + } + + gl::BindVertexArray(0); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + } + + (vao, vbo) +} diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 11ccfc63..71ed6e28 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -14,6 +14,7 @@ use fnv::FnvHasher; use log::{error, info}; use unicode_width::UnicodeWidthChar; +use alacritty_terminal::graphics::UpdateQueues; use alacritty_terminal::index::Point; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; @@ -24,8 +25,10 @@ use crate::config::ui_config::{Delta, UiConfig}; use crate::display::content::RenderableCell; use crate::gl; use crate::gl::types::*; +use crate::renderer::graphics::GraphicsRenderer; use crate::renderer::rects::{RectRenderer, RenderRect}; +pub mod graphics; pub mod rects; // Shader source. @@ -439,6 +442,7 @@ pub struct QuadRenderer { batch: Batch, rect_renderer: RectRenderer, + graphics_renderer: GraphicsRenderer, } #[derive(Debug)] @@ -638,6 +642,7 @@ impl QuadRenderer { let mut renderer = Self { program, rect_renderer: RectRenderer::new()?, + graphics_renderer: GraphicsRenderer::new()?, vao, ebo, vbo_instance, @@ -682,6 +687,19 @@ impl QuadRenderer { } } + /// Run the required actions to apply changes for the graphics in the grid. + #[inline] + pub fn graphics_run_updates(&mut self, update_queues: UpdateQueues, size_info: &SizeInfo) { + self.graphics_renderer.run_updates(update_queues, size_info); + } + + /// Draw graphics visible in the display. + #[inline] + pub fn graphics_draw(&mut self, render_list: graphics::RenderList, size_info: &SizeInfo) { + self.graphics_renderer.draw(render_list, size_info); + self.active_tex = 0; + } + pub fn with_api<F, T>(&mut self, config: &UiConfig, props: &SizeInfo, func: F) -> T where F: FnOnce(RenderApi<'_>) -> T, @@ -842,6 +860,7 @@ impl<'a> RenderApi<'a> { point: Point::new(point.line, point.column + i), character, zerowidth: None, + graphic: None, flags: Flags::empty(), bg_alpha: 1.0, fg, diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs new file mode 100644 index 00000000..84b8584f --- /dev/null +++ b/alacritty/src/url.rs @@ -0,0 +1,276 @@ +use std::cmp::min; +use std::mem; + +use crossfont::Metrics; +use glutin::event::{ElementState, ModifiersState}; +use urlocator::{UrlLocation, UrlLocator}; + +use alacritty_terminal::index::{Column, Point}; +use alacritty_terminal::term::cell::Flags; +use alacritty_terminal::term::color::Rgb; +use alacritty_terminal::term::SizeInfo; + +use crate::config::Config; +use crate::display::content::RenderableCell; +use crate::event::Mouse; +use crate::renderer::rects::{RenderLine, RenderRect}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Url { + lines: Vec<RenderLine>, + end_offset: u16, + num_cols: Column, +} + +impl Url { + pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> { + let end = self.end(); + self.lines + .iter() + .filter(|line| line.start <= end) + .map(|line| { + let mut rect_line = *line; + rect_line.end = min(line.end, end); + rect_line.rects(Flags::UNDERLINE, metrics, size) + }) + .flatten() + .collect() + } + + pub fn start(&self) -> Point { + self.lines[0].start + } + + pub fn end(&self) -> Point { + self.lines[self.lines.len() - 1].end.sub(self.num_cols, self.end_offset as usize) + } +} + +pub struct Urls { + locator: UrlLocator, + urls: Vec<Url>, + scheme_buffer: Vec<(Point, Rgb)>, + last_point: Option<Point>, + state: UrlLocation, +} + +impl Default for Urls { + fn default() -> Self { + Self { + locator: UrlLocator::new(), + scheme_buffer: Vec::new(), + urls: Vec::new(), + state: UrlLocation::Reset, + last_point: None, + } + } +} + +impl Urls { + pub fn new() -> Self { + Self::default() + } + + // Update tracked URLs. + pub fn update(&mut self, num_cols: Column, cell: &RenderableCell) { + let point = cell.point; + let mut end = point; + + // Include the following wide char spacer. + if cell.flags.contains(Flags::WIDE_CHAR) { + end.column += 1; + } + + // Reset URL when empty cells have been skipped. + if point != Point::default() && Some(point.sub(num_cols, 1)) != self.last_point { + self.reset(); + } + + self.last_point = Some(end); + + // Extend current state if a leading wide char spacer is encountered. + if cell.flags.intersects(Flags::LEADING_WIDE_CHAR_SPACER) { + if let UrlLocation::Url(_, mut end_offset) = self.state { + if end_offset != 0 { + end_offset += 1; + } + + self.extend_url(point, end, cell.fg, end_offset); + } + + return; + } + + // Advance parser. + let last_state = mem::replace(&mut self.state, self.locator.advance(cell.character)); + match (self.state, last_state) { + (UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => { + // Create empty URL. + self.urls.push(Url { lines: Vec::new(), end_offset, num_cols }); + + // Push schemes into URL. + for (scheme_point, scheme_fg) in self.scheme_buffer.split_off(0) { + self.extend_url(scheme_point, scheme_point, scheme_fg, end_offset); + } + + // Push the new cell into URL. + self.extend_url(point, end, cell.fg, end_offset); + }, + (UrlLocation::Url(_length, end_offset), UrlLocation::Url(..)) => { + self.extend_url(point, end, cell.fg, end_offset); + }, + (UrlLocation::Scheme, _) => self.scheme_buffer.push((cell.point, cell.fg)), + (UrlLocation::Reset, _) => self.reset(), + _ => (), + } + + // Reset at un-wrapped linebreak. + if cell.point.column + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) { + self.reset(); + } + } + + /// Extend the last URL. + fn extend_url(&mut self, start: Point, end: Point, color: Rgb, end_offset: u16) { + let url = self.urls.last_mut().unwrap(); + + // If color changed, we need to insert a new line. + if url.lines.last().map(|last| last.color) == Some(color) { + url.lines.last_mut().unwrap().end = end; + } else { + url.lines.push(RenderLine { color, start, end }); + } + + // Update excluded cells at the end of the URL. + url.end_offset = end_offset; + } + + /// Find URL below the mouse cursor. + pub fn highlighted( + &self, + config: &Config, + mouse: &Mouse, + mods: ModifiersState, + mouse_mode: bool, + selection: bool, + ) -> Option<Url> { + // Require additional shift in mouse mode. + let mut required_mods = config.ui_config.mouse.url.mods(); + if mouse_mode { + required_mods |= ModifiersState::SHIFT; + } + + // Make sure all prerequisites for highlighting are met. + if selection + || !mouse.inside_text_area + || config.ui_config.mouse.url.launcher.is_none() + || required_mods != mods + || mouse.left_button_state == ElementState::Pressed + { + return None; + } + + self.find_at(Point::new(mouse.line, mouse.column)) + } + + /// Find URL at location. + pub fn find_at(&self, point: Point) -> Option<Url> { + for url in &self.urls { + if (url.start()..=url.end()).contains(&point) { + return Some(url.clone()); + } + } + None + } + + fn reset(&mut self) { + self.locator = UrlLocator::new(); + self.state = UrlLocation::Reset; + self.scheme_buffer.clear(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use alacritty_terminal::index::{Column, Line}; + + fn text_to_cells(text: &str) -> Vec<RenderableCell> { + text.chars() + .enumerate() + .map(|(i, character)| RenderableCell { + character, + zerowidth: None, + graphic: None, + point: Point::new(Line(0), Column(i)), + fg: Default::default(), + bg: Default::default(), + bg_alpha: 0., + flags: Flags::empty(), + is_match: false, + }) + .collect() + } + + #[test] + fn multi_color_url() { + let mut input = text_to_cells("test https://example.org ing"); + let num_cols = input.len(); + + input[10].fg = Rgb { r: 0xff, g: 0x00, b: 0xff }; + + let mut urls = Urls::new(); + + for cell in input { + urls.update(Column(num_cols), &cell); + } + + let url = urls.urls.first().unwrap(); + assert_eq!(url.start().column, Column(5)); + assert_eq!(url.end().column, Column(23)); + } + + #[test] + fn multiple_urls() { + let input = text_to_cells("test git:a git:b git:c ing"); + let num_cols = input.len(); + + let mut urls = Urls::new(); + + for cell in input { + urls.update(Column(num_cols), &cell); + } + + assert_eq!(urls.urls.len(), 3); + + assert_eq!(urls.urls[0].start().column, Column(5)); + assert_eq!(urls.urls[0].end().column, Column(9)); + + assert_eq!(urls.urls[1].start().column, Column(11)); + assert_eq!(urls.urls[1].end().column, Column(15)); + + assert_eq!(urls.urls[2].start().column, Column(17)); + assert_eq!(urls.urls[2].end().column, Column(21)); + } + + #[test] + fn wide_urls() { + let input = text_to_cells("test https://こんにちは (http:여보세요) ing"); + let num_cols = input.len() + 9; + + let mut urls = Urls::new(); + + for cell in input { + urls.update(Column(num_cols), &cell); + } + + assert_eq!(urls.urls.len(), 2); + + assert_eq!(urls.urls[0].start().column, Column(5)); + assert_eq!(urls.urls[0].end().column, Column(17)); + + assert_eq!(urls.urls[1].start().column, Column(20)); + assert_eq!(urls.urls[1].end().column, Column(28)); + } +} |