aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/renderer/text/gles2.rs
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src/renderer/text/gles2.rs')
-rw-r--r--alacritty/src/renderer/text/gles2.rs478
1 files changed, 478 insertions, 0 deletions
diff --git a/alacritty/src/renderer/text/gles2.rs b/alacritty/src/renderer/text/gles2.rs
new file mode 100644
index 00000000..9b161081
--- /dev/null
+++ b/alacritty/src/renderer/text/gles2.rs
@@ -0,0 +1,478 @@
+use std::mem::size_of;
+use std::ptr;
+
+use crossfont::RasterizedGlyph;
+use log::info;
+
+use alacritty_terminal::term::cell::Flags;
+use alacritty_terminal::term::SizeInfo;
+
+use crate::display::content::RenderableCell;
+use crate::gl;
+use crate::gl::types::*;
+use crate::renderer::shader::{ShaderProgram, ShaderVersion};
+use crate::renderer::{cstr, Error};
+
+use super::atlas::{Atlas, ATLAS_SIZE};
+use super::{
+ Glyph, LoadGlyph, LoaderApi, TextRenderApi, TextRenderBatch, TextRenderer, TextShader,
+};
+
+// Shader source.
+static TEXT_SHADER_F: &str = include_str!("../../../res/gles2/text.f.glsl");
+static TEXT_SHADER_V: &str = include_str!("../../../res/gles2/text.v.glsl");
+
+#[derive(Debug)]
+pub struct Gles2Renderer {
+ program: TextShaderProgram,
+ vao: GLuint,
+ vbo: GLuint,
+ ebo: GLuint,
+ atlas: Vec<Atlas>,
+ batch: Batch,
+ current_atlas: usize,
+ active_tex: GLuint,
+}
+
+impl Gles2Renderer {
+ pub fn new() -> Result<Self, Error> {
+ info!("Using OpenGL ES 2.0 renderer");
+
+ let program = TextShaderProgram::new(ShaderVersion::Gles2)?;
+ let mut vao: GLuint = 0;
+ let mut vbo: GLuint = 0;
+ let mut ebo: GLuint = 0;
+
+ let mut vertex_indices = Vec::with_capacity(BATCH_MAX / 4 * 6);
+ for index in 0..(BATCH_MAX / 4) as u16 {
+ let index = index * 4;
+ vertex_indices.push(index);
+ vertex_indices.push(index + 1);
+ vertex_indices.push(index + 3);
+
+ vertex_indices.push(index + 1);
+ vertex_indices.push(index + 2);
+ vertex_indices.push(index + 3);
+ }
+
+ unsafe {
+ gl::Enable(gl::BLEND);
+
+ gl::DepthMask(gl::FALSE);
+
+ gl::GenVertexArrays(1, &mut vao);
+ gl::GenBuffers(1, &mut ebo);
+ gl::GenBuffers(1, &mut vbo);
+ gl::BindVertexArray(vao);
+
+ // Elements buffer.
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
+ gl::BufferData(
+ gl::ELEMENT_ARRAY_BUFFER,
+ (vertex_indices.capacity() * size_of::<u16>()) as isize,
+ vertex_indices.as_ptr() as *const _,
+ gl::STATIC_DRAW,
+ );
+
+ // Vertex buffer.
+ gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
+ gl::BufferData(
+ gl::ARRAY_BUFFER,
+ (BATCH_MAX * size_of::<TextVertex>()) as isize,
+ ptr::null(),
+ gl::STREAM_DRAW,
+ );
+
+ let mut index = 0;
+ let mut size = 0;
+
+ macro_rules! add_attr {
+ ($count:expr, $gl_type:expr, $type:ty) => {
+ gl::VertexAttribPointer(
+ index,
+ $count,
+ $gl_type,
+ gl::FALSE,
+ size_of::<TextVertex>() as i32,
+ size as *const _,
+ );
+ gl::EnableVertexAttribArray(index);
+
+ #[allow(unused_assignments)]
+ {
+ size += $count * size_of::<$type>();
+ index += 1;
+ }
+ };
+ }
+
+ // Cell coords.
+ add_attr!(2, gl::SHORT, i16);
+
+ // Glyph coords.
+ add_attr!(2, gl::SHORT, i16);
+
+ // UV.
+ add_attr!(2, gl::FLOAT, u32);
+
+ // Color and bitmap color.
+ //
+ // These are packed together because of an OpenGL driver issue on macOS, which caused a
+ // `vec3(u8)` text color and a `u8` for glyph color to cause performance regressions.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Background color.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Cleanup.
+ gl::BindVertexArray(0);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ }
+
+ Ok(Self {
+ program,
+ vao,
+ vbo,
+ ebo,
+ atlas: vec![Atlas::new(ATLAS_SIZE)],
+ batch: Batch::new(),
+ current_atlas: 0,
+ active_tex: 0,
+ })
+ }
+}
+
+impl Drop for Gles2Renderer {
+ fn drop(&mut self) {
+ unsafe {
+ gl::DeleteBuffers(1, &self.vbo);
+ gl::DeleteBuffers(1, &self.ebo);
+ gl::DeleteVertexArrays(1, &self.vao);
+ }
+ }
+}
+
+impl<'a> TextRenderer<'a> for Gles2Renderer {
+ type RenderApi = RenderApi<'a>;
+ type RenderBatch = Batch;
+ type Shader = TextShaderProgram;
+
+ fn program(&self) -> &Self::Shader {
+ &self.program
+ }
+
+ fn with_api<'b: 'a, F, T>(&'b mut self, _: &'b SizeInfo, func: F) -> T
+ where
+ F: FnOnce(Self::RenderApi) -> T,
+ {
+ unsafe {
+ gl::UseProgram(self.program.id());
+ gl::BindVertexArray(self.vao);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
+ gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
+ gl::ActiveTexture(gl::TEXTURE0);
+ }
+
+ let res = func(RenderApi {
+ active_tex: &mut self.active_tex,
+ batch: &mut self.batch,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ program: &mut self.program,
+ });
+
+ unsafe {
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ gl::BindVertexArray(0);
+
+ gl::UseProgram(0);
+ }
+
+ res
+ }
+
+ fn loader_api(&mut self) -> LoaderApi<'_> {
+ LoaderApi {
+ active_tex: &mut self.active_tex,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ }
+ }
+}
+
+/// Maximum items to be drawn in a batch.
+///
+/// We use the closest number to `u16::MAX` dividable by 4 (amount of vertices we push for a glyph),
+/// since it's the maximum possible index in `glDrawElements` in gles2.
+const BATCH_MAX: usize = (u16::MAX - u16::MAX % 4) as usize;
+
+#[derive(Debug)]
+pub struct Batch {
+ tex: GLuint,
+ vertices: Vec<TextVertex>,
+}
+
+impl Batch {
+ fn new() -> Self {
+ Self { tex: 0, vertices: Vec::with_capacity(BATCH_MAX) }
+ }
+
+ #[inline]
+ fn len(&self) -> usize {
+ self.vertices.len()
+ }
+
+ #[inline]
+ fn capacity(&self) -> usize {
+ BATCH_MAX
+ }
+
+ #[inline]
+ fn size(&self) -> usize {
+ self.len() * size_of::<TextVertex>()
+ }
+
+ #[inline]
+ fn clear(&mut self) {
+ self.vertices.clear();
+ }
+}
+
+impl TextRenderBatch for Batch {
+ #[inline]
+ fn tex(&self) -> GLuint {
+ self.tex
+ }
+
+ #[inline]
+ fn full(&self) -> bool {
+ self.capacity() == self.len()
+ }
+
+ #[inline]
+ fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo) {
+ if self.is_empty() {
+ self.tex = glyph.tex_id;
+ }
+
+ // Calculate the cell position.
+ let x = cell.point.column.0 as i16 * size_info.cell_width() as i16;
+ let y = cell.point.line as i16 * size_info.cell_height() as i16;
+
+ // Calculate the glyph position.
+ let glyph_x = cell.point.column.0 as i16 * size_info.cell_width() as i16 + glyph.left;
+ let glyph_y = (cell.point.line + 1) as i16 * size_info.cell_height() as i16 - glyph.top;
+
+ let colored = if glyph.multicolor { 1 } else { 0 };
+ let is_wide = if cell.flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 };
+
+ let mut vertex = TextVertex {
+ x,
+ y: y + size_info.cell_height() as i16,
+
+ glyph_x,
+ glyph_y: glyph_y + glyph.height,
+
+ u: glyph.uv_left,
+ v: glyph.uv_bot + glyph.uv_height,
+ r: cell.fg.r,
+ g: cell.fg.g,
+ b: cell.fg.b,
+ colored,
+ bg_r: cell.bg.r,
+ bg_g: cell.bg.g,
+ bg_b: cell.bg.b,
+ bg_a: (cell.bg_alpha * 255.0) as u8,
+ };
+
+ self.vertices.push(vertex);
+
+ vertex.y = y;
+ vertex.glyph_y = glyph_y;
+ vertex.u = glyph.uv_left;
+ vertex.v = glyph.uv_bot;
+ self.vertices.push(vertex);
+
+ vertex.x = x + is_wide * size_info.cell_width() as i16;
+ vertex.glyph_x = glyph_x + glyph.width;
+ vertex.u = glyph.uv_left + glyph.uv_width;
+ vertex.v = glyph.uv_bot;
+ self.vertices.push(vertex);
+
+ vertex.x = x + is_wide * size_info.cell_width() as i16;
+ vertex.y = y + size_info.cell_height() as i16;
+ vertex.glyph_x = glyph_x + glyph.width;
+ vertex.glyph_y = glyph_y + glyph.height;
+ vertex.u = glyph.uv_left + glyph.uv_width;
+ vertex.v = glyph.uv_bot + glyph.uv_height;
+ self.vertices.push(vertex);
+ }
+}
+
+#[derive(Debug)]
+pub struct RenderApi<'a> {
+ active_tex: &'a mut GLuint,
+ batch: &'a mut Batch,
+ atlas: &'a mut Vec<Atlas>,
+ current_atlas: &'a mut usize,
+ program: &'a mut TextShaderProgram,
+}
+
+impl<'a> Drop for RenderApi<'a> {
+ fn drop(&mut self) {
+ if !self.batch.is_empty() {
+ self.render_batch();
+ }
+ }
+}
+
+impl<'a> LoadGlyph for RenderApi<'a> {
+ fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
+ Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
+ }
+
+ fn clear(&mut self) {
+ Atlas::clear_atlas(self.atlas, self.current_atlas)
+ }
+}
+
+impl<'a> TextRenderApi<Batch> for RenderApi<'a> {
+ fn batch(&mut self) -> &mut Batch {
+ self.batch
+ }
+
+ fn render_batch(&mut self) {
+ unsafe {
+ gl::BufferSubData(
+ gl::ARRAY_BUFFER,
+ 0,
+ self.batch.size() as isize,
+ self.batch.vertices.as_ptr() as *const _,
+ );
+ }
+
+ if *self.active_tex != self.batch.tex() {
+ unsafe {
+ gl::BindTexture(gl::TEXTURE_2D, self.batch.tex());
+ }
+ *self.active_tex = self.batch.tex();
+ }
+
+ unsafe {
+ let num_indices = (self.batch.len() / 4 * 6) as i32;
+
+ // The rendering is inspired by
+ // https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md.
+
+ // Draw background.
+ self.program.set_rendering_pass(RenderingPass::Background);
+ gl::BlendFunc(gl::ONE, gl::ZERO);
+ gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
+
+ // First text rendering pass.
+ self.program.set_rendering_pass(RenderingPass::SubpixelPass1);
+ gl::BlendFuncSeparate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE);
+ gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
+
+ // Second text rendering pass.
+ self.program.set_rendering_pass(RenderingPass::SubpixelPass2);
+ gl::BlendFuncSeparate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
+ gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
+
+ // Third pass.
+ self.program.set_rendering_pass(RenderingPass::SubpixelPass3);
+ gl::BlendFuncSeparate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
+ gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
+ }
+
+ self.batch.clear();
+ }
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+struct TextVertex {
+ // Cell coordinates.
+ x: i16,
+ y: i16,
+
+ // Glyph coordinates.
+ glyph_x: i16,
+ glyph_y: i16,
+
+ // Offsets into Atlas.
+ u: f32,
+ v: f32,
+
+ // Color.
+ r: u8,
+ g: u8,
+ b: u8,
+
+ // Whether the glyph is colored.
+ colored: u8,
+
+ // Background color.
+ bg_r: u8,
+ bg_g: u8,
+ bg_b: u8,
+ bg_a: u8,
+}
+
+// NOTE: These flags must be in sync with their usage in the gles2/text.*.glsl shaders.
+#[repr(u8)]
+enum RenderingPass {
+ Background = 0,
+ SubpixelPass1 = 1,
+ SubpixelPass2 = 2,
+ SubpixelPass3 = 3,
+}
+
+#[derive(Debug)]
+pub struct TextShaderProgram {
+ /// Shader program.
+ program: ShaderProgram,
+
+ /// Projection scale and offset uniform.
+ u_projection: GLint,
+
+ /// Rendering pass.
+ ///
+ /// The rendering is split into 4 passes. One is used for the background and the rest to
+ /// perform subpixel text rendering according to
+ /// https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md.
+ ///
+ /// Rendering is split into three passes.
+ u_rendering_pass: GLint,
+}
+
+impl TextShaderProgram {
+ pub fn new(shader_version: ShaderVersion) -> Result<Self, Error> {
+ let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, TEXT_SHADER_F)?;
+ Ok(Self {
+ u_projection: program.get_uniform_location(cstr!("projection"))?,
+ u_rendering_pass: program.get_uniform_location(cstr!("renderingPass"))?,
+ program,
+ })
+ }
+
+ fn set_rendering_pass(&self, rendering_pass: RenderingPass) {
+ unsafe { gl::Uniform1i(self.u_rendering_pass, rendering_pass as i32) }
+ }
+}
+
+impl TextShader for TextShaderProgram {
+ fn id(&self) -> GLuint {
+ self.program.id()
+ }
+
+ fn projection_uniform(&self) -> GLint {
+ self.u_projection
+ }
+}