aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src/renderer')
-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
4 files changed, 609 insertions, 0 deletions
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,