aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/renderer/graphics/draw.rs
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/src/renderer/graphics/draw.rs
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/src/renderer/graphics/draw.rs')
-rw-r--r--alacritty/src/renderer/graphics/draw.rs242
1 files changed, 242 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..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);
+ }
+ }
+}