aboutsummaryrefslogtreecommitdiff
path: root/alacritty
diff options
context:
space:
mode:
authorJosh Rahm <rahm@google.com>2021-10-05 14:36:31 -0600
committerJosh Rahm <rahm@google.com>2021-10-05 14:36:31 -0600
commit7a209fa45f1f4d07cb4a885e8ea3d03e47cf48ae (patch)
tree026d75fdc19e19952cfba3020c118f24df4ac412 /alacritty
parent1725e30e144b04e2e2e30efc76eb968c97a0eabf (diff)
parent98fbb3f9285d8c00836e3bcfa6e1e13bf809e2a2 (diff)
downloadr-alacritty-7a209fa45f1f4d07cb4a885e8ea3d03e47cf48ae.tar.gz
r-alacritty-7a209fa45f1f4d07cb4a885e8ea3d03e47cf48ae.tar.bz2
r-alacritty-7a209fa45f1f4d07cb4a885e8ea3d03e47cf48ae.zip
Merge remote-tracking branch 'betaboon/graphics' into experimental
Diffstat (limited to 'alacritty')
-rw-r--r--alacritty/Cargo.toml1
-rw-r--r--alacritty/res/graphics.f.glsl40
-rw-r--r--alacritty/res/graphics.v.glsl79
-rw-r--r--alacritty/src/display/content.rs3
-rw-r--r--alacritty/src/display/mod.rs14
-rw-r--r--alacritty/src/renderer/graphics/draw.rs242
-rw-r--r--alacritty/src/renderer/graphics/mod.rs149
-rw-r--r--alacritty/src/renderer/graphics/shader.rs199
-rw-r--r--alacritty/src/renderer/mod.rs19
-rw-r--r--alacritty/src/url.rs276
10 files changed, 1021 insertions, 1 deletions
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml
index b1fee0c2..6ac20526 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 6fef9574..297aefd6 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 sp: Rgb, // Special
@@ -263,6 +265,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..e07cc078
--- /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: Line(cell.point.line as i32),
+ 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 0173769c..fba47c40 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,
@@ -843,6 +861,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));
+ }
+}