Allow the same glyph at different sizes to be rendered into the same atlas.

This commit is contained in:
Patrick Walton 2017-02-10 17:20:05 -08:00
parent d306ef01d1
commit c4c19076c7
4 changed files with 131 additions and 77 deletions

View File

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

View File

@ -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<f32>],
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>,
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<f32>],
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<u32> = atlas.atlas_origin(glyph_index).floor().cast().unwrap();
let uv_tl: Point2D<u32> = 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<Point2D<f32>> {
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<u32>,
atlas_origins: &[Point2D<f32>],
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,

View File

@ -32,8 +32,7 @@ use std::u16;
/// the screen.
pub struct AtlasBuilder {
rect_packer: RectPacker,
image_descriptors: Vec<ImageDescriptor>,
image_metadata: Vec<ImageMetadata>,
batch_builders: Vec<BatchBuilder>,
}
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<Point2D<f32>, ()> {
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<Atlas, GlError> {
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<ImageDescriptor>,
image_metadata: Vec<ImageMetadata>,
}
impl BatchBuilder {
fn new() -> BatchBuilder {
BatchBuilder {
image_descriptors: vec![],
image_metadata: vec![],
}
}
fn add_glyph(&mut self,
outlines: &Outlines,
atlas_origin: &Point2D<u32>,
glyph_index: u16,
point_size: f32)
-> Result<Point2D<f32>, ()> {
// 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<Atlas, GlError> {
/// Uploads this batch data to the GPU.
fn create_batch(mut self) -> Result<Batch, GlError> {
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<ImageDescriptor>,
start_indices: Vec<usize>,
counts: Vec<GLsizei>,
batches: Vec<Batch>,
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<usize>,
counts: Vec<GLsizei>,
}
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<f32> {
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);
}
}

View File

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