diff options
author | Joe Wilm <joe@jwilm.com> | 2016-06-06 13:20:35 -0700 |
---|---|---|
committer | Joe Wilm <joe@jwilm.com> | 2016-06-06 13:20:35 -0700 |
commit | ed7aa96907e8c5e51facb45f9b590ce25b4f3dd5 (patch) | |
tree | dfd2185e57f9ff792d25ae7ca81d56b69e96096d /src/renderer | |
parent | 1f3f9add49d9b6fae8f57bb907b278eb06b513c9 (diff) | |
download | alacritty-ed7aa96907e8c5e51facb45f9b590ce25b4f3dd5.tar.gz alacritty-ed7aa96907e8c5e51facb45f9b590ce25b4f3dd5.zip |
Refactor Instanced Drawing to use Vertex Arrays
Per-instanced data was previously stored in uniforms. This required
several OpenGL calls to upload all of the data, and it was more complex
to prepare (several vecs vs one).
Additionally, drawing APIs are now accessible through a `RenderApi`
(obtained through `QuadRenderer::with_api`) which enables some RAII
patterns. Specifically, checks for batch flushing are handled in Drop.
Diffstat (limited to 'src/renderer')
-rw-r--r-- | src/renderer/mod.rs | 485 |
1 files changed, 242 insertions, 243 deletions
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index fe7919fd..ff2963f7 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -7,7 +7,6 @@ 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::*; @@ -23,7 +22,29 @@ 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"); +/// Text drawing program +/// +/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". #[derive(Debug)] +pub struct ShaderProgram { + // Program id + id: GLuint, + + /// projection matrix uniform + u_projection: GLint, + + /// Terminal dimensions (pixels) + u_term_dim: GLint, + + /// Cell dimensions (pixels) + u_cell_dim: GLint, + + /// Cell separation (pixels) + u_cell_sep: GLint, +} + + +#[derive(Debug, Clone)] pub struct Glyph { tex_id: GLuint, top: f32, @@ -36,15 +57,46 @@ pub struct Glyph { uv_height: f32, } +#[derive(Debug)] +struct InstanceData { + // coords + col: f32, + row: f32, + // glyph offset + left: f32, + top: f32, + // glyph scale + width: f32, + height: f32, + // uv offset + uv_left: f32, + uv_bot: f32, + // uv scale + uv_width: f32, + uv_height: f32, + // color + r: f32, + g: f32, + b: f32, +} + +#[derive(Debug)] pub struct QuadRenderer { program: ShaderProgram, should_reload: Arc<AtomicBool>, vao: GLuint, vbo: GLuint, ebo: GLuint, - active_color: Rgb, + vbo_instance: GLuint, atlas: Vec<Atlas>, active_tex: GLuint, + batch: Batch, +} + +#[derive(Debug)] +pub struct RenderApi<'a> { + active_tex: &'a mut GLuint, + batch: &'a mut Batch, } #[derive(Debug)] @@ -60,44 +112,17 @@ struct ElementIndex { } #[derive(Debug)] -struct Batch { +pub 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, - } - } + instances: Vec<InstanceData>, } impl Batch { + #[inline] 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(), + instances: Vec::with_capacity(BATCH_MAX), } } @@ -106,12 +131,24 @@ impl Batch { 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)); + self.instances.push(InstanceData { + col: col, + row: row, + + top: glyph.top, + left: glyph.left, + width: glyph.width, + height: glyph.height, + + uv_bot: glyph.uv_bot, + uv_left: glyph.uv_left, + uv_width: glyph.uv_width, + uv_height: glyph.uv_height, + + r: color.r as f32, + g: color.g as f32, + b: color.b as f32, + }); } #[inline] @@ -121,7 +158,7 @@ impl Batch { #[inline] pub fn len(&self) -> usize { - self.color.len() + self.instances.len() } #[inline] @@ -134,24 +171,20 @@ impl Batch { 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(); + #[inline] + pub fn size(&self) -> usize { + self.len() * size_of::<InstanceData>() } - pub fn render(&mut self, renderer: &mut QuadRenderer) { - renderer.render_batch(self); - self.clear(); + pub fn clear(&mut self) { + self.tex = 0; + self.instances.clear(); } } /// Maximum items to be drawn in a batch. -const BATCH_MAX: usize = 32usize; +const BATCH_MAX: usize = 4096; +const ATLAS_SIZE: i32 = 512; impl QuadRenderer { // TODO should probably hand this a transform instead of width/height @@ -162,14 +195,19 @@ impl QuadRenderer { let mut vbo: GLuint = 0; let mut ebo: GLuint = 0; + let mut vbo_instance: GLuint = 0; + + unsafe { gl::GenVertexArrays(1, &mut vao); gl::GenBuffers(1, &mut vbo); gl::GenBuffers(1, &mut ebo); + gl::GenBuffers(1, &mut vbo_instance); gl::BindVertexArray(vao); - gl::BindBuffer(gl::ARRAY_BUFFER, vbo); - + // ---------------------------- + // setup vertex position buffer + // ---------------------------- // Top right, Bottom right, Bottom left, Top left let vertices = [ PackedVertex { x: 1.0, y: 1.0 }, @@ -178,33 +216,69 @@ impl QuadRenderer { PackedVertex { x: 0.0, y: 1.0 }, ]; - gl::BufferData( - gl::ARRAY_BUFFER, - (size_of::<PackedVertex>() * vertices.len()) as GLsizeiptr, - vertices.as_ptr() as *const _, - gl::STATIC_DRAW - ); + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + + gl::VertexAttribPointer(0, 2, + gl::FLOAT, gl::FALSE, + size_of::<PackedVertex>() as i32, + ptr::null()); + gl::EnableVertexAttribArray(0); + gl::BufferData(gl::ARRAY_BUFFER, + (size_of::<PackedVertex>() * vertices.len()) as GLsizeiptr, + vertices.as_ptr() as *const _, + gl::STATIC_DRAW); + + // --------------------- + // Set up element buffer + // --------------------- let indices: [u32; 6] = [0, 1, 3, 1, 2, 3]; gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, - 6 * size_of::<u32>() as isize, + (6 * size_of::<u32>()) as isize, indices.as_ptr() as *const _, gl::STATIC_DRAW); - gl::EnableVertexAttribArray(0); - - // positions - gl::VertexAttribPointer(0, 2, + // ---------------------------- + // Setup vertex instance buffer + // ---------------------------- + gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance); + gl::BufferData(gl::ARRAY_BUFFER, + (BATCH_MAX * size_of::<InstanceData>()) as isize, + ptr::null(), gl::STREAM_DRAW); + // coords + gl::VertexAttribPointer(1, 2, gl::FLOAT, gl::FALSE, - size_of::<PackedVertex>() as i32, + size_of::<InstanceData>() as i32, ptr::null()); + gl::EnableVertexAttribArray(1); + gl::VertexAttribDivisor(1, 1); + // glyphoffset + gl::VertexAttribPointer(2, 4, + gl::FLOAT, gl::FALSE, + size_of::<InstanceData>() as i32, + (2 * size_of::<f32>()) as *const _); + gl::EnableVertexAttribArray(2); + gl::VertexAttribDivisor(2, 1); + // uv + gl::VertexAttribPointer(3, 4, + gl::FLOAT, gl::FALSE, + size_of::<InstanceData>() as i32, + (6 * size_of::<f32>()) as *const _); + gl::EnableVertexAttribArray(3); + gl::VertexAttribDivisor(3, 1); + // color + gl::VertexAttribPointer(4, 3, + gl::FLOAT, gl::FALSE, + size_of::<InstanceData>() as i32, + (10 * size_of::<f32>()) as *const _); + gl::EnableVertexAttribArray(4); + gl::VertexAttribDivisor(4, 1); gl::BindVertexArray(0); gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); } let should_reload = Arc::new(AtomicBool::new(false)); @@ -245,118 +319,40 @@ impl QuadRenderer { vao: vao, vbo: vbo, ebo: ebo, - active_color: Rgb { r: 0, g: 0, b: 0 }, + vbo_instance: vbo_instance, atlas: Vec::new(), active_tex: 0, + batch: Batch::new(), }; - let atlas = renderer.create_atlas(1024); + let atlas = renderer.create_atlas(ATLAS_SIZE); renderer.atlas.push(atlas); renderer } - /// Render a string in a predefined location. Used for printing render time for profiling and - /// optimization. - pub fn render_string(&mut self, - s: &str, - glyph_cache: &GlyphCache, - props: &TermProps, - color: &Rgb) + pub fn with_api<F>(&mut self, props: &TermProps, mut func: F) + where F: FnMut(RenderApi) { - self.prepare_render(props); - - 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) { - 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(); - } - - pub fn render_cursor(&mut self, - cursor: term::Cursor, - glyph_cache: &GlyphCache, - props: &TermProps) - { - self.prepare_render(props); - - if let Some(glyph) = glyph_cache.get(&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(); - } - - 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 - if cell.c == ' ' { - continue; - } - - // Add cell to batch if the glyph is laoded - if let Some(glyph) = glyph_cache.get(&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); + if self.should_reload.load(Ordering::Relaxed) { + self.reload_shaders(props.width as u32, props.height as u32); } - self.finish_render(); - } - - fn prepare_render(&mut self, props: &TermProps) { unsafe { self.program.activate(); self.program.set_term_uniforms(props); gl::BindVertexArray(self.vao); - gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo); gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); + gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance); gl::ActiveTexture(gl::TEXTURE0); } - if self.should_reload.load(Ordering::Relaxed) { - self.reload_shaders(props.width as u32, props.height as u32); - } - } + func(RenderApi { + active_tex: &mut self.active_tex, + batch: &mut self.batch, + }); - fn finish_render(&mut self) { unsafe { gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); gl::BindBuffer(gl::ARRAY_BUFFER, 0); @@ -386,27 +382,9 @@ impl QuadRenderer { }; self.active_tex = 0; - self.active_color = Rgb { r: 0, g: 0, b: 0 }; self.program = program; } - fn render_batch(&mut self, batch: &Batch) { - self.program.set_uniforms(batch); - - // Bind texture if necessary - if self.active_tex != batch.tex { - unsafe { - gl::BindTexture(gl::TEXTURE_2D, batch.tex); - } - self.active_tex = batch.tex; - } - - unsafe { - let count = batch.len() as GLsizei; - gl::DrawElementsInstanced(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null(), count); - } - } - /// Load a glyph into a texture atlas /// /// If the current atlas is full, a new one will be created. @@ -414,7 +392,7 @@ impl QuadRenderer { match self.atlas.last_mut().unwrap().insert(rasterized, &mut self.active_tex) { Ok(glyph) => glyph, Err(_) => { - let atlas = self.create_atlas(1024); + let atlas = self.create_atlas(ATLAS_SIZE); self.atlas.push(atlas); self.load_glyph(rasterized) } @@ -459,46 +437,94 @@ impl QuadRenderer { } } -fn get_rect(glyph: &Glyph, x: f32, y: f32) -> Rect<f32> { - Rect::new( - Point2D::new(x + glyph.left as f32, y - (glyph.height - glyph.top) as f32), - Size2D::new(glyph.width as f32, glyph.height as f32) - ) -} +impl<'a> RenderApi<'a> { + fn render_batch(&mut self) { + unsafe { + gl::BufferSubData(gl::ARRAY_BUFFER, 0, self.batch.size() as isize, + self.batch.instances.as_ptr() as *const _); + } -pub struct ShaderProgram { - // Program id - id: GLuint, + // Bind texture if necessary + if *self.active_tex != self.batch.tex { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.batch.tex); + } + *self.active_tex = self.batch.tex; + } - /// projection matrix uniform - u_projection: GLint, + unsafe { + gl::DrawElementsInstanced(gl::TRIANGLES, + 6, gl::UNSIGNED_INT, ptr::null(), + self.batch.len() as GLsizei); + } - /// color uniform - u_color: GLint, + self.batch.clear(); + } + /// Render a string in a predefined location. Used for printing render time for profiling and + /// optimization. + pub fn render_string(&mut self, + s: &str, + glyph_cache: &GlyphCache, + color: &Rgb) + { + let row = 40.0; + let mut col = 100.0; - /// Terminal dimensions (pixels) - u_term_dim: GLint, + for c in s.chars() { + if let Some(glyph) = glyph_cache.get(&c) { + self.add_render_item(row, col, *color, glyph); + } - /// Cell dimensions (pixels) - u_cell_dim: GLint, + col += 1.0; + } + } - /// Cell separation (pixels) - u_cell_sep: GLint, + #[inline] + fn add_render_item(&mut self, row: f32, col: f32, color: Rgb, glyph: &Glyph) { + self.batch.add_item(row, col, color, glyph); - /// Cell coordinates in grid - u_cell_coord: GLint, + // Render batch and clear if it's full + if self.batch.full() { + self.render_batch(); + } + } - /// Glyph scale - u_glyph_scale: GLint, + pub fn render_cursor(&mut self, cursor: term::Cursor, glyph_cache: &GlyphCache) { + if let Some(glyph) = glyph_cache.get(&term::CURSOR_SHAPE) { + self.add_render_item(cursor.y as f32, cursor.x as f32, term::DEFAULT_FG, glyph); + } + } - /// Glyph offset - u_glyph_offset: GLint, + pub fn render_grid(&mut self, grid: &Grid, glyph_cache: &GlyphCache) { + for (i, row) in grid.rows().enumerate() { + for (j, cell) in row.cells().enumerate() { + // Skip empty cells + if cell.c == ' ' { + continue; + } - /// Atlas scale - u_uv_scale: GLint, + // Add cell to batch if the glyph is laoded + if let Some(glyph) = glyph_cache.get(&cell.c) { + self.add_render_item(i as f32, j as f32, cell.fg, glyph); + } + } + } + } +} - /// Atlas offset - u_uv_offset: GLint, +impl<'a> Drop for RenderApi<'a> { + fn drop(&mut self) { + if !self.batch.is_empty() { + self.render_batch(); + } + } +} + +fn get_rect(glyph: &Glyph, x: f32, y: f32) -> Rect<f32> { + Rect::new( + Point2D::new(x + glyph.left as f32, y - (glyph.height - glyph.top) as f32), + Size2D::new(glyph.width as f32, glyph.height as f32) + ) } impl ShaderProgram { @@ -541,47 +567,23 @@ impl ShaderProgram { } // get uniform locations - let (projection, color, term_dim, cell_dim, cell_sep) = unsafe { + let (projection, term_dim, cell_dim, cell_sep) = unsafe { ( gl::GetUniformLocation(program, cptr!(b"projection\0")), - gl::GetUniformLocation(program, cptr!(b"textColor\0")), gl::GetUniformLocation(program, cptr!(b"termDim\0")), gl::GetUniformLocation(program, cptr!(b"cellDim\0")), gl::GetUniformLocation(program, cptr!(b"cellSep\0")), ) }; - assert_uniform_valid!(projection, color, term_dim, cell_dim, cell_sep); - - 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")), - gl::GetUniformLocation(program, cptr!(b"glyphOffset\0")), - gl::GetUniformLocation(program, cptr!(b"uvScale\0")), - gl::GetUniformLocation(program, cptr!(b"uvOffset\0")), - ) - }; - - assert_uniform_valid!(cell_coord, glyph_scale, glyph_offset, uv_scale, uv_offset); - - // Initialize to known color (black) - unsafe { - gl::Uniform3i(color, 0, 0, 0); - } + assert_uniform_valid!(projection, term_dim, cell_dim, cell_sep); let shader = ShaderProgram { id: program, u_projection: projection, - u_color: color, u_term_dim: term_dim, u_cell_dim: cell_dim, u_cell_sep: cell_sep, - u_cell_coord: cell_coord, - u_glyph_scale: glyph_scale, - u_glyph_offset: glyph_offset, - u_uv_scale: uv_scale, - u_uv_offset: uv_offset, }; // set projection uniform @@ -608,18 +610,6 @@ impl ShaderProgram { } } - fn set_uniforms(&self, batch: &Batch) { - let len = batch.len(); - unsafe { - 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 _); - } - } - fn create_program(vertex: GLuint, fragment: GLuint) -> GLuint { unsafe { let program = gl::CreateProgram(); @@ -667,6 +657,14 @@ impl ShaderProgram { } } +impl Drop for ShaderProgram { + fn drop(&mut self) { + unsafe { + gl::DeleteProgram(self.id); + } + } +} + fn get_program_info_log(program: GLuint) -> String { // Get expected log length let mut max_length: GLint = 0; @@ -780,6 +778,7 @@ impl From<io::Error> for ShaderCreationError { /// │ │ │ │ │ <- Row considered full when next glyph doesn't /// └─────┴─────┴─────┴───────────┘ fit in the row. /// (0, 0) x-> +#[derive(Debug)] struct Atlas { /// Texture id for this atlas id: GLuint, |