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.
This commit is contained in:
Joe Wilm 2016-06-04 21:26:28 -07:00
parent 4fdd5280f1
commit 1f3f9add49
No known key found for this signature in database
GPG Key ID: 39B57C6972F518DA
6 changed files with 234 additions and 65 deletions

23
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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,
}