diff --git a/Cargo.lock b/Cargo.lock index 565684bc..7633fc26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,7 @@ name = "alacritty" version = "0.1.0" dependencies = [ + "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "cgmath 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "freetype-rs 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -17,6 +18,15 @@ name = "android_glue" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "arrayvec" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "odds 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bitflags" version = "0.3.3" @@ -418,6 +428,14 @@ dependencies = [ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nodrop" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "odds 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "notify" version = "2.5.5" @@ -512,6 +530,11 @@ dependencies = [ "malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "odds" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "osmesa-sys" version = "0.0.5" diff --git a/Cargo.toml b/Cargo.toml index b658930e..2de24e9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ libc = "*" cgmath = "0.7" euclid = "0.6" notify = { git = "https://github.com/jwilm/rsnotify", branch = "add-ignore-op" } +arrayvec = "0.3" [build-dependencies] gl_generator = "0.5" diff --git a/res/text.f.glsl b/res/text.f.glsl index e817626f..d3265712 100644 --- a/res/text.f.glsl +++ b/res/text.f.glsl @@ -1,15 +1,17 @@ #version 330 core in vec2 TexCoords; +flat in int InstanceId; layout(location = 0, index = 0) out vec4 color; layout(location = 0, index = 1) out vec4 alphaMask; uniform sampler2D mask; -uniform ivec3 textColor; +uniform ivec3 textColor[32]; void main() { + int i = InstanceId; alphaMask = vec4(texture(mask, TexCoords).rgb, 1.0); - vec3 textColorF = vec3(textColor) / vec3(255.0, 255.0, 255.0); + vec3 textColorF = vec3(textColor[i]) / vec3(255.0, 255.0, 255.0); color = vec4(textColorF, 1.0); } diff --git a/res/text.v.glsl b/res/text.v.glsl index c6543352..66bfad0c 100644 --- a/res/text.v.glsl +++ b/res/text.v.glsl @@ -2,6 +2,7 @@ layout (location = 0) in vec2 position; out vec2 TexCoords; +flat out int InstanceId; // Terminal properties uniform vec2 termDim; @@ -9,33 +10,37 @@ uniform vec2 cellDim; uniform vec2 cellSep; // Cell properties -uniform vec2 gridCoords; +uniform vec2 gridCoords[32]; // glyph properties -uniform vec2 glyphScale; -uniform vec2 glyphOffset; +uniform vec2 glyphScale[32]; +uniform vec2 glyphOffset[32]; // uv mapping -uniform vec2 uvScale; -uniform vec2 uvOffset; +uniform vec2 uvScale[32]; +uniform vec2 uvOffset[32]; // Orthographic projection uniform mat4 projection; void main() { + int i = gl_InstanceID; + // Position of cell from top-left - vec2 cellPosition = (cellDim + cellSep) * gridCoords; + vec2 cellPosition = (cellDim + cellSep) * gridCoords[i]; // Invert Y since framebuffer origin is bottom-left cellPosition.y = termDim.y - cellPosition.y - cellDim.y; // Glyphs are offset within their cell; account for y-flip - vec2 cellOffset = vec2(glyphOffset.x, glyphOffset.y - glyphScale.y); + vec2 cellOffset = vec2(glyphOffset[i].x, + glyphOffset[i].y - glyphScale[i].y); // position coordinates are normalized on [0, 1] - vec2 finalPosition = glyphScale * position + cellPosition + cellOffset; + vec2 finalPosition = glyphScale[i] * position + cellPosition + cellOffset; gl_Position = projection * vec4(finalPosition.xy, 0.0, 1.0); - TexCoords = vec2(position.x, 1 - position.y) * uvScale + uvOffset; + TexCoords = vec2(position.x, 1 - position.y) * uvScale[i] + uvOffset[i]; + InstanceId = i; } diff --git a/src/main.rs b/src/main.rs index 733c4c6d..79c368df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ extern crate glutin; extern crate cgmath; extern crate euclid; extern crate notify; +extern crate arrayvec; #[macro_use] mod macros; @@ -190,15 +191,6 @@ fn main() { width: width as f32, }; - let props = TermProps { - cell_width: cell_width as f32, - cell_height: cell_height as f32, - sep_x: sep_x as f32, - sep_y: sep_y as f32, - height: height as f32, - width: width as f32, - }; - { let _sampler = meter.sampler(); diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 2ba5b94c..fe7919fd 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -7,11 +7,13 @@ use std::ptr; use std::sync::Arc; use std::sync::atomic::{Ordering, AtomicBool}; +use arrayvec::ArrayVec; use cgmath::{self, Matrix}; use euclid::{Rect, Size2D, Point2D}; use gl::types::*; use gl; use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op}; + use text::RasterizedGlyph; use grid::Grid; use term; @@ -21,6 +23,19 @@ use super::{Rgb, TermProps, GlyphCache}; static TEXT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl"); static TEXT_SHADER_V_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl"); +#[derive(Debug)] +pub struct Glyph { + tex_id: GLuint, + top: f32, + left: f32, + width: f32, + height: f32, + uv_bot: f32, + uv_left: f32, + uv_width: f32, + uv_height: f32, +} + pub struct QuadRenderer { program: ShaderProgram, should_reload: Arc, @@ -32,13 +47,111 @@ pub struct QuadRenderer { active_tex: GLuint, } -#[allow(dead_code)] #[derive(Debug)] pub struct PackedVertex { x: f32, y: f32, } +#[derive(Debug)] +struct ElementIndex { + col: u32, // x + row: u32, // y +} + +#[derive(Debug)] +struct Batch { + tex: GLuint, + coords: ArrayVec<[Point2D; BATCH_MAX]>, + color: ArrayVec<[RgbUpload; BATCH_MAX]>, + glyph_scale: ArrayVec<[Point2D; BATCH_MAX]>, + glyph_offset: ArrayVec<[Point2D; BATCH_MAX]>, + uv_scale: ArrayVec<[Point2D; BATCH_MAX]>, + uv_offset: ArrayVec<[Point2D; BATCH_MAX]>, +} + +#[derive(Debug)] +struct RgbUpload { + r: i32, + g: i32, + b: i32, +} + +impl From for RgbUpload { + #[inline] + fn from(color: Rgb) -> RgbUpload { + RgbUpload { + r: color.r as i32, + g: color.g as i32, + b: color.b as i32, + } + } +} + +impl Batch { + pub fn new() -> Batch { + Batch { + tex: 0, + coords: ArrayVec::new(), + color: ArrayVec::new(), + glyph_scale: ArrayVec::new(), + glyph_offset: ArrayVec::new(), + uv_scale: ArrayVec::new(), + uv_offset: ArrayVec::new(), + } + } + + pub fn add_item(&mut self, row: f32, col: f32, color: Rgb, glyph: &Glyph) { + if self.is_empty() { + self.tex = glyph.tex_id; + } + + self.coords.push(Point2D::new(col, row)); + self.color.push(RgbUpload::from(color)); + self.glyph_scale.push(Point2D::new(glyph.width, glyph.height)); + self.glyph_offset.push(Point2D::new(glyph.left, glyph.top)); + self.uv_scale.push(Point2D::new(glyph.uv_width, glyph.uv_height)); + self.uv_offset.push(Point2D::new(glyph.uv_left, glyph.uv_bot)); + } + + #[inline] + pub fn full(&self) -> bool { + self.capacity() == self.len() + } + + #[inline] + pub fn len(&self) -> usize { + self.color.len() + } + + #[inline] + pub fn capacity(&self) -> usize { + BATCH_MAX + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn clear(&mut self) { + self.tex = 0; + self.coords.clear(); + self.color.clear(); + self.glyph_scale.clear(); + self.glyph_offset.clear(); + self.uv_scale.clear(); + self.uv_offset.clear(); + } + + pub fn render(&mut self, renderer: &mut QuadRenderer) { + renderer.render_batch(self); + self.clear(); + } +} + +/// Maximum items to be drawn in a batch. +const BATCH_MAX: usize = 32usize; impl QuadRenderer { // TODO should probably hand this a transform instead of width/height @@ -155,12 +268,24 @@ impl QuadRenderer { let row = 40.0; let mut col = 100.0; + + let mut batch = Batch::new(); + for c in s.chars() { if let Some(glyph) = glyph_cache.get(&c) { - self.render(glyph, row, col, color, c); + batch.add_item(row, col, *color, glyph); } col += 1.0; + + // Render batch and clear if it's full + if batch.full() { + batch.render(self); + } + } + + if !batch.is_empty() { + batch.render(self); } self.finish_render(); @@ -172,9 +297,11 @@ impl QuadRenderer { props: &TermProps) { self.prepare_render(props); + if let Some(glyph) = glyph_cache.get(&term::CURSOR_SHAPE) { - self.render(glyph, cursor.y as f32, cursor.x as f32, - &term::DEFAULT_FG, term::CURSOR_SHAPE); + let mut batch = Batch::new(); + batch.add_item(cursor.y as f32, cursor.x as f32, term::DEFAULT_FG, glyph); + batch.render(self); } self.finish_render(); @@ -183,6 +310,9 @@ impl QuadRenderer { pub fn render_grid(&mut self, grid: &Grid, glyph_cache: &GlyphCache, props: &TermProps) { self.prepare_render(props); + // All draws are batched + let mut batch = Batch::new(); + for (i, row) in grid.rows().enumerate() { for (j, cell) in row.cells().enumerate() { // Skip empty cells @@ -190,13 +320,23 @@ impl QuadRenderer { continue; } - // Render if glyph is loaded + // Add cell to batch if the glyph is laoded if let Some(glyph) = glyph_cache.get(&cell.c) { - self.render(glyph, i as f32, j as f32, &cell.fg, cell.c); + batch.add_item(i as f32, j as f32, cell.fg, glyph); + } + + // Render batch and clear if it's full + if batch.full() { + batch.render(self); } } } + // Could have some data in a batch still; render it. + if !batch.is_empty() { + batch.render(self); + } + self.finish_render(); } @@ -250,27 +390,20 @@ impl QuadRenderer { self.program = program; } - fn render(&mut self, glyph: &Glyph, row: f32, col: f32, color: &Rgb, c: char) { - if &self.active_color != color { + fn render_batch(&mut self, batch: &Batch) { + self.program.set_uniforms(batch); + + // Bind texture if necessary + if self.active_tex != batch.tex { unsafe { - gl::Uniform3i(self.program.u_color, - color.r as i32, - color.g as i32, - color.b as i32); + gl::BindTexture(gl::TEXTURE_2D, batch.tex); } - self.active_color = color.to_owned(); + self.active_tex = batch.tex; } - self.program.set_glyph_uniforms(row, col, glyph); - unsafe { - // Bind texture if it changed - if self.active_tex != glyph.tex_id { - gl::BindTexture(gl::TEXTURE_2D, glyph.tex_id); - self.active_tex = glyph.tex_id; - } - - gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null()); + let count = batch.len() as GLsizei; + gl::DrawElementsInstanced(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null(), count); } } @@ -359,7 +492,7 @@ pub struct ShaderProgram { u_glyph_scale: GLint, /// Glyph offset - u_glyph_offest: GLint, + u_glyph_offset: GLint, /// Atlas scale u_uv_scale: GLint, @@ -420,7 +553,7 @@ impl ShaderProgram { assert_uniform_valid!(projection, color, term_dim, cell_dim, cell_sep); - let (cell_coord, glyph_scale, glyph_offest, uv_scale, uv_offset) = unsafe { + let (cell_coord, glyph_scale, glyph_offset, uv_scale, uv_offset) = unsafe { ( gl::GetUniformLocation(program, cptr!(b"gridCoords\0")), gl::GetUniformLocation(program, cptr!(b"glyphScale\0")), @@ -430,7 +563,7 @@ impl ShaderProgram { ) }; - assert_uniform_valid!(cell_coord, glyph_scale, glyph_offest, uv_scale, uv_offset); + assert_uniform_valid!(cell_coord, glyph_scale, glyph_offset, uv_scale, uv_offset); // Initialize to known color (black) unsafe { @@ -446,7 +579,7 @@ impl ShaderProgram { u_cell_sep: cell_sep, u_cell_coord: cell_coord, u_glyph_scale: glyph_scale, - u_glyph_offest: glyph_offest, + u_glyph_offset: glyph_offset, u_uv_scale: uv_scale, u_uv_offset: uv_offset, }; @@ -475,13 +608,15 @@ impl ShaderProgram { } } - fn set_glyph_uniforms(&self, row: f32, col: f32, glyph: &Glyph) { + fn set_uniforms(&self, batch: &Batch) { + let len = batch.len(); unsafe { - gl::Uniform2f(self.u_cell_coord, col, row); // col = x; row = y - gl::Uniform2f(self.u_glyph_scale, glyph.width, glyph.height); - gl::Uniform2f(self.u_glyph_offest, glyph.left, glyph.top); - gl::Uniform2f(self.u_uv_scale, glyph.uv_width, glyph.uv_height); - gl::Uniform2f(self.u_uv_offset, glyph.uv_left, glyph.uv_bot); + gl::Uniform2fv(self.u_cell_coord, len as i32, batch.coords.as_ptr() as *const _); + gl::Uniform2fv(self.u_glyph_scale, len as i32, batch.glyph_scale.as_ptr() as *const _); + gl::Uniform2fv(self.u_glyph_offset, len as i32, batch.glyph_offset.as_ptr() as *const _); + gl::Uniform2fv(self.u_uv_scale, len as i32, batch.uv_scale.as_ptr() as *const _); + gl::Uniform2fv(self.u_uv_offset, len as i32, batch.uv_offset.as_ptr() as *const _); + gl::Uniform3iv(self.u_color, len as i32, batch.color.as_ptr() as *const _); } } @@ -496,6 +631,7 @@ impl ShaderProgram { gl::GetProgramiv(program, gl::LINK_STATUS, &mut success); if success != (gl::TRUE as GLint) { + println!("{}", get_program_info_log(program)); panic!("failed to link shader program"); } program @@ -531,6 +667,29 @@ impl ShaderProgram { } } +fn get_program_info_log(program: GLuint) -> String { + // Get expected log length + let mut max_length: GLint = 0; + unsafe { + gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut max_length); + } + + // Read the info log + let mut actual_length: GLint = 0; + let mut buf: Vec = Vec::with_capacity(max_length as usize); + unsafe { + gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); + } + + // Build a string + unsafe { + buf.set_len(actual_length as usize); + } + + // XXX should we expect opengl to return garbage? + String::from_utf8(buf).unwrap() +} + fn get_shader_info_log(shader: GLuint) -> String { // Get expected log length let mut max_length: GLint = 0; @@ -759,16 +918,3 @@ impl Atlas { Ok(()) } } - -#[derive(Debug)] -pub struct Glyph { - tex_id: GLuint, - top: f32, - left: f32, - width: f32, - height: f32, - uv_bot: f32, - uv_left: f32, - uv_width: f32, - uv_height: f32, -}