diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 14 | ||||
-rw-r--r-- | src/renderer/mod.rs | 485 |
2 files changed, 251 insertions, 248 deletions
diff --git a/src/main.rs b/src/main.rs index 79c368df..e9817331 100644 --- a/src/main.rs +++ b/src/main.rs @@ -194,17 +194,21 @@ fn main() { { let _sampler = meter.sampler(); - // Draw the grid - renderer.render_grid(terminal.grid(), &glyph_cache, &props); + renderer.with_api(&props, |mut api| { + // Draw the grid + api.render_grid(terminal.grid(), &glyph_cache); - // Also draw the cursor - renderer.render_cursor(terminal.cursor(), &glyph_cache, &props); + // Also draw the cursor + api.render_cursor(terminal.cursor(), &glyph_cache); + }) } // Draw render timer let timing = format!("{:.3} usec", meter.average()); let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; - renderer.render_string(&timing[..], &glyph_cache, &props, &color); + renderer.with_api(&props, |mut api| { + api.render_string(&timing[..], &glyph_cache, &color); + }); window.swap_buffers().unwrap(); } 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, |