From c4c19076c79d562bd2b4b7d44ecae21c621e1185 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 10 Feb 2017 17:20:05 -0800 Subject: [PATCH] Allow the same glyph at different sizes to be rendered into the same atlas. --- examples/benchmark.rs | 2 +- examples/lorem-ipsum.rs | 33 ++++---- src/atlas.rs | 170 ++++++++++++++++++++++++++-------------- src/rasterizer.rs | 3 +- 4 files changed, 131 insertions(+), 77 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 9e420d9f..f7122310 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -83,7 +83,7 @@ fn main() { let mut atlas_builder = AtlasBuilder::new(device_pixel_width as GLuint, shelf_height); for glyph_index in 0..(glyph_count as u16) { - atlas_builder.pack_glyph(&outlines, glyph_index, point_size as f32).unwrap() + atlas_builder.pack_glyph(&outlines, glyph_index, point_size as f32).unwrap(); } atlas = atlas_builder.create_atlas().unwrap(); } diff --git a/examples/lorem-ipsum.rs b/examples/lorem-ipsum.rs index 11ddc04b..517b4dfb 100644 --- a/examples/lorem-ipsum.rs +++ b/examples/lorem-ipsum.rs @@ -19,7 +19,7 @@ use gl::types::{GLchar, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid}; use glfw::{Action, Context, Key, OpenGlProfileHint, SwapInterval, WindowEvent}; use glfw::{WindowHint, WindowMode}; use memmap::{Mmap, Protection}; -use pathfinder::atlas::{Atlas, AtlasBuilder}; +use pathfinder::atlas::AtlasBuilder; use pathfinder::charmap::{CodepointRanges, GlyphMapping}; use pathfinder::coverage::CoverageBuffer; use pathfinder::otf::Font; @@ -161,7 +161,7 @@ fn main() { } let outlines = outline_builder.create_buffers().unwrap(); - let fps_atlas = renderer.create_fps_atlas(&font, &outlines, glyph_count); + let fps_atlas_origins = renderer.create_fps_atlas(&font, &outlines, glyph_count); while !window.should_close() { if dirty { @@ -185,9 +185,9 @@ fn main() { let timing = renderer.get_timing_in_ms(); renderer.draw_fps(&font, - &fps_atlas, &outlines, &device_pixel_size, + &fps_atlas_origins, &glyph_indices, &glyph_mapping, draw_time, @@ -438,9 +438,9 @@ impl Renderer { -> DrawAtlasProfilingEvents { let shelf_height = font.shelf_height(point_size); let mut atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height); - for glyph_index in 0..(glyph_count as u16) { + let atlas_origins: Vec<_> = (0..(glyph_count as u16)).map(|glyph_index| { atlas_builder.pack_glyph(&outlines, glyph_index, point_size).unwrap() - } + }).collect(); let atlas = atlas_builder.create_atlas().unwrap(); @@ -468,11 +468,11 @@ impl Renderer { } self.draw_glyphs(&font, - &atlas, outlines, &self.main_composite_vertex_array, glyph_indices, glyph_positions, + &atlas_origins, device_pixel_size, translation, self.main_gl_texture, @@ -492,11 +492,11 @@ impl Renderer { fn draw_glyphs(&self, font: &Font, - atlas: &Atlas, outlines: &Outlines, vertex_array: &CompositeVertexArray, glyph_indices: &[u16], glyph_positions: &[GlyphPos], + atlas_origins: &[Point2D], device_pixel_size: &Size2D, translation: &Point2D, texture: GLuint, @@ -509,10 +509,10 @@ impl Renderer { gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vertex_array.index_buffer); let vertex_count = self.upload_quads_for_text(font, - atlas, outlines, glyph_indices, glyph_positions, + atlas_origins, point_size); gl::ActiveTexture(gl::TEXTURE0); @@ -548,10 +548,10 @@ impl Renderer { fn upload_quads_for_text(&self, font: &Font, - atlas: &Atlas, outlines: &Outlines, glyph_indices: &[u16], glyph_positions: &[GlyphPos], + atlas_origins: &[Point2D], point_size: f32) -> usize { let pixels_per_unit = point_size as f32 / font.units_per_em() as f32; @@ -561,7 +561,7 @@ impl Renderer { let glyph_index = glyph_indices[position.glyph_id as usize]; let glyph_rect_i = outlines.glyph_pixel_bounds(glyph_index, point_size); - let uv_tl: Point2D = atlas.atlas_origin(glyph_index).floor().cast().unwrap(); + let uv_tl: Point2D = atlas_origins[glyph_index as usize].floor().cast().unwrap(); let uv_br = uv_tl + glyph_rect_i.size().cast().unwrap(); let bearing_pos = (position.x as f32 * pixels_per_unit).round() as i32; @@ -596,12 +596,13 @@ impl Renderer { indices.len() } - fn create_fps_atlas(&self, font: &Font, outlines: &Outlines, glyph_count: usize) -> Atlas { + fn create_fps_atlas(&self, font: &Font, outlines: &Outlines, glyph_count: usize) + -> Vec> { let shelf_height = font.shelf_height(FPS_DISPLAY_POINT_SIZE); let mut atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height); - for glyph_index in 0..(glyph_count as u16) { + let atlas_origins: Vec<_> = (0..(glyph_count as u16)).map(|glyph_index| { atlas_builder.pack_glyph(&outlines, glyph_index, FPS_DISPLAY_POINT_SIZE).unwrap() - } + }).collect(); let atlas = atlas_builder.create_atlas().unwrap(); @@ -611,14 +612,14 @@ impl Renderer { outlines, &self.fps_coverage_buffer).unwrap(); - atlas + atlas_origins } fn draw_fps(&self, font: &Font, - atlas: &Atlas, outlines: &Outlines, device_pixel_size: &Size2D, + atlas_origins: &[Point2D], glyph_indices: &[u16], glyph_mapping: &GlyphMapping, draw_time: f64, @@ -675,11 +676,11 @@ impl Renderer { } self.draw_glyphs(font, - atlas, outlines, &self.fps_composite_vertex_array, glyph_indices, &fps_glyphs, + atlas_origins, device_pixel_size, &Point2D::new(FPS_PADDING, device_pixel_size.height as i32 - FPS_PADDING), self.fps_gl_texture, diff --git a/src/atlas.rs b/src/atlas.rs index 933f23b4..6a1ab922 100644 --- a/src/atlas.rs +++ b/src/atlas.rs @@ -32,8 +32,7 @@ use std::u16; /// the screen. pub struct AtlasBuilder { rect_packer: RectPacker, - image_descriptors: Vec, - image_metadata: Vec, + batch_builders: Vec, } impl AtlasBuilder { @@ -50,8 +49,7 @@ impl AtlasBuilder { pub fn new(available_width: u32, shelf_height: u32) -> AtlasBuilder { AtlasBuilder { rect_packer: RectPacker::new(available_width, shelf_height), - image_descriptors: vec![], - image_metadata: vec![], + batch_builders: vec![], } } @@ -61,18 +59,77 @@ impl AtlasBuilder { /// separate from IDs; the indices are returned from each call to /// `OutlineBuilder::add_glyph()`. /// - /// Returns an error if there is no space left for the glyph. - /// - /// TODO(pcwalton): Support multiple outline buffers in the same atlas. - /// - /// TODO(pcwalton): Support the same glyph drawn at multiple point sizes. + /// Returns the subpixel origin of the glyph in the atlas if successful or an error if there is + /// no space left for the glyph. pub fn pack_glyph(&mut self, outlines: &Outlines, glyph_index: u16, point_size: f32) - -> Result<(), ()> { - let subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, point_size); + -> Result, ()> { let pixel_bounds = outlines.glyph_pixel_bounds(glyph_index, point_size); - let atlas_origin = try!(self.rect_packer.pack(&pixel_bounds.size().cast().unwrap())); + for batch_builder in &mut self.batch_builders { + if let Ok(atlas_origin) = batch_builder.add_glyph(outlines, + &atlas_origin, + glyph_index, + point_size) { + return Ok(atlas_origin) + } + } + + let mut batch_builder = BatchBuilder::new(); + let atlas_origin = try!(batch_builder.add_glyph(outlines, + &atlas_origin, + glyph_index, + point_size)); + self.batch_builders.push(batch_builder); + Ok(atlas_origin) + } + + /// Creates an atlas by uploading the atlas info to the GPU. + pub fn create_atlas(mut self) -> Result { + let mut batches = vec![]; + for batch_builder in self.batch_builders.into_iter() { + batches.push(try!(batch_builder.create_batch())) + } + + Ok(Atlas { + batches: batches, + shelf_height: self.rect_packer.shelf_height(), + shelf_columns: self.rect_packer.shelf_columns(), + }) + } +} + +struct BatchBuilder { + image_descriptors: Vec, + image_metadata: Vec, +} + +impl BatchBuilder { + fn new() -> BatchBuilder { + BatchBuilder { + image_descriptors: vec![], + image_metadata: vec![], + } + } + + fn add_glyph(&mut self, + outlines: &Outlines, + atlas_origin: &Point2D, + glyph_index: u16, + point_size: f32) + -> Result, ()> { + // Check to see if we're already rendering this glyph. + if let Some(image_descriptor) = self.image_descriptors.get(glyph_index as usize) { + if image_descriptor.point_size == point_size { + // Glyph is already present. + return Ok(Point2D::new(image_descriptor.atlas_x, image_descriptor.atlas_y)) + } else { + // Glyph is present at a different font size. We need a new batch. + return Err(()) + } + } + + let subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, point_size); let glyph_id = outlines.glyph_id(glyph_index); let glyph_index = self.image_descriptors.len() as u16; @@ -80,17 +137,19 @@ impl AtlasBuilder { self.image_descriptors.push(ImageDescriptor::default()) } - self.image_descriptors[glyph_index as usize] = ImageDescriptor { - atlas_x: atlas_origin.x as f32 + subpixel_bounds.left.fract(), - atlas_y: atlas_origin.y as f32 + (1.0 - subpixel_bounds.top.fract()), - point_size: point_size, - glyph_index: glyph_index as f32, - }; - while self.image_metadata.len() < glyph_index as usize + 1 { self.image_metadata.push(ImageMetadata::default()) } + let atlas_origin = Point2D::new(atlas_origin.x as f32 + subpixel_bounds.left.fract(), + atlas_origin.y as f32 + 1.0 - subpixel_bounds.top.fract()); + self.image_descriptors[glyph_index as usize] = ImageDescriptor { + atlas_x: atlas_origin.x, + atlas_y: atlas_origin.y, + point_size: point_size, + glyph_index: glyph_index as f32, + }; + self.image_metadata[glyph_index as usize] = ImageMetadata { glyph_index: glyph_index as u32, glyph_id: glyph_id, @@ -101,11 +160,11 @@ impl AtlasBuilder { }, }; - Ok(()) + Ok(atlas_origin) } - /// Creates an atlas by uploading the atlas info to the GPU. - pub fn create_atlas(mut self) -> Result { + /// Uploads this batch data to the GPU. + fn create_batch(mut self) -> Result { self.image_metadata.sort_by(|a, b| a.glyph_index.cmp(&b.glyph_index)); let (mut current_range, mut counts, mut start_indices) = (None, vec![], vec![]); @@ -141,15 +200,10 @@ impl AtlasBuilder { gl::BindBuffer(gl::UNIFORM_BUFFER, images); gl::BufferData(gl::UNIFORM_BUFFER, length as GLsizeiptr, ptr, gl::DYNAMIC_DRAW); - Ok(Atlas { + Ok(Batch { images_buffer: images, - images: self.image_descriptors, - start_indices: start_indices, counts: counts, - - shelf_height: self.rect_packer.shelf_height(), - shelf_columns: self.rect_packer.shelf_columns(), }) } } @@ -157,33 +211,17 @@ impl AtlasBuilder { /// An atlas holding rendered glyphs on the GPU. pub struct Atlas { - images_buffer: GLuint, - images: Vec, - - start_indices: Vec, - counts: Vec, - + batches: Vec, shelf_height: u32, shelf_columns: u32, } -impl Drop for Atlas { - fn drop(&mut self) { - unsafe { - gl::DeleteBuffers(1, &mut self.images_buffer); - } - } -} - impl Atlas { #[doc(hidden)] pub unsafe fn draw(&self, primitive: GLenum) { - debug_assert!(self.counts.len() == self.start_indices.len()); - gl::MultiDrawElements(primitive, - self.counts.as_ptr(), - gl::UNSIGNED_INT, - self.start_indices.as_ptr() as *const *const GLvoid, - self.counts.len() as GLsizei); + for batch in &self.batches { + batch.draw(primitive) + } } /// Returns the height of each shelf. @@ -197,20 +235,34 @@ impl Atlas { pub fn shelf_columns(&self) -> u32 { self.shelf_columns } +} - #[doc(hidden)] - #[inline] - pub fn images_buffer(&self) -> GLuint { - self.images_buffer +struct Batch { + images_buffer: GLuint, + start_indices: Vec, + counts: Vec, +} + +impl Drop for Batch { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &mut self.images_buffer); + } } +} - /// Returns the origin of the glyph with the given index in the atlas. - /// - /// This is the subpixel origin. - #[inline] - pub fn atlas_origin(&self, glyph_index: u16) -> Point2D { - let image = &self.images[glyph_index as usize]; - Point2D::new(image.atlas_x, image.atlas_y) +impl Batch { + unsafe fn draw(&self, primitive: GLenum) { + debug_assert!(self.counts.len() == self.start_indices.len()); + + // The image descriptors are bound to binding point 2. See `draw.vs.glsl`. + gl::BindBufferBase(gl::UNIFORM_BUFFER, 2, self.images_buffer); + + gl::MultiDrawElements(primitive, + self.counts.as_ptr(), + gl::UNSIGNED_INT, + self.start_indices.as_ptr() as *const *const GLvoid, + self.counts.len() as GLsizei); } } diff --git a/src/rasterizer.rs b/src/rasterizer.rs index 358a90c5..2092ddd2 100644 --- a/src/rasterizer.rs +++ b/src/rasterizer.rs @@ -216,8 +216,9 @@ impl Rasterizer { gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, outlines.indices_buffer()); + // Don't bind the atlas uniform buffers (binding point 2) here; the batches will do + // that on their own. gl::BindBufferBase(gl::UNIFORM_BUFFER, 1, outlines.descriptors_buffer()); - gl::BindBufferBase(gl::UNIFORM_BUFFER, 2, atlas.images_buffer()); gl::UniformBlockBinding(self.draw_program, self.draw_glyph_descriptors_uniform, 1); gl::UniformBlockBinding(self.draw_program, self.draw_image_descriptors_uniform, 2);