summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoe Wilm <joe@jwilm.com>2016-06-04 21:26:28 -0700
committerJoe Wilm <joe@jwilm.com>2016-06-04 21:31:41 -0700
commit1f3f9add49d9b6fae8f57bb907b278eb06b513c9 (patch)
treed2413d48aa4f911b1e71386db9ef04478f7c859f
parent4fdd5280f1e79ea6575a6a110951c564a7dd235e (diff)
Optimize Rendering with batched draw calls
Draw calls are now batched for performance. Render times on git log at the default size are now ~200usec.
-rw-r--r--Cargo.lock23
-rw-r--r--Cargo.toml1
-rw-r--r--res/text.f.glsl6
-rw-r--r--res/text.v.glsl23
-rw-r--r--src/main.rs10
-rw-r--r--src/renderer/mod.rs236
6 files changed, 234 insertions, 65 deletions
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)",
@@ -18,6 +19,15 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -419,6 +429,14 @@ dependencies = [
]
[[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"
source = "git+https://github.com/jwilm/rsnotify?branch=add-ignore-op#0ca41a4807c427e6cf47d7e75735df62d2e86708"
@@ -513,6 +531,11 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
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<AtomicBool>,
@@ -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<f32>; BATCH_MAX]>,
+ color: ArrayVec<[RgbUpload; BATCH_MAX]>,
+ glyph_scale: ArrayVec<[Point2D<f32>; BATCH_MAX]>,
+ glyph_offset: ArrayVec<[Point2D<f32>; BATCH_MAX]>,
+ uv_scale: ArrayVec<[Point2D<f32>; BATCH_MAX]>,
+ uv_offset: ArrayVec<[Point2D<f32>; BATCH_MAX]>,
+}
+
+#[derive(Debug)]
+struct RgbUpload {
+ r: i32,
+ g: i32,
+ b: i32,
+}
+
+impl From<Rgb> 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<u8> = 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,
-}