mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
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:
parent
4fdd5280f1
commit
1f3f9add49
6 changed files with 234 additions and 65 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
10
src/main.rs
10
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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue