From d5be9e1f164110bed95773f6f70aa77682df47e0 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 3 Feb 2017 17:04:57 -0800 Subject: [PATCH] Rename "batch" to "atlas" and "atlas" to "rect packer" --- examples/benchmark.rs | 29 ++- examples/dump-outlines.rs | 2 +- examples/generate-atlas.rs | 18 +- examples/lorem-ipsum.rs | 67 ++++--- src/atlas.rs | 264 ++++++++++++++++--------- src/batch.rs | 183 ----------------- src/glyph_buffer.rs | 14 +- src/lib.rs | 2 +- src/rasterizer.rs | 33 ++-- src/rect_packer.rs | 116 +++++++++++ src/tests/mod.rs | 2 +- src/tests/{atlas.rs => rect_packer.rs} | 22 +-- 12 files changed, 383 insertions(+), 369 deletions(-) delete mode 100644 src/batch.rs create mode 100644 src/rect_packer.rs rename src/tests/{atlas.rs => rect_packer.rs} (59%) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 05cd8573..24af2e8f 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -19,7 +19,7 @@ use euclid::{Point2D, Rect, Size2D}; use gl::types::GLuint; use glfw::{Context, OpenGlProfileHint, WindowHint, WindowMode}; use memmap::{Mmap, Protection}; -use pathfinder::batch::BatchBuilder; +use pathfinder::atlas::AtlasBuilder; use pathfinder::charmap::CodepointRange; use pathfinder::coverage::CoverageBuffer; use pathfinder::glyph_buffer::GlyphBufferBuilder; @@ -63,11 +63,12 @@ fn main() { let mut results = vec![]; let start = time::precise_time_ns(); let mut last_time = start; - let (mut glyph_buffer_builder, mut batch_builder, mut glyph_count); - let (mut glyph_buffers, mut batch); + let (mut glyph_buffer_builder, mut glyph_buffers, mut glyph_count); + let (mut atlas_builder, mut atlas); + loop { glyph_buffer_builder = GlyphBufferBuilder::new(); - batch_builder = BatchBuilder::new(device_pixel_width as GLuint, shelf_height); + atlas_builder = AtlasBuilder::new(device_pixel_width as GLuint, shelf_height); glyph_count = 0; unsafe { let font = Font::new(file.as_slice()).unwrap(); @@ -77,17 +78,16 @@ fn main() { .unwrap(); for (glyph_index, glyph_id) in glyph_ranges.iter().enumerate() { glyph_buffer_builder.add_glyph(&font, glyph_id).unwrap(); - batch_builder.add_glyph(&glyph_buffer_builder, - glyph_index as u32, - point_size as f32) - .unwrap(); + atlas_builder.pack_glyph(&glyph_buffer_builder, + glyph_index as u32, + point_size as f32).unwrap(); glyph_count += 1 } } glyph_buffers = glyph_buffer_builder.create_buffers().unwrap(); - batch = batch_builder.create_batch(&glyph_buffer_builder).unwrap(); + atlas = atlas_builder.create_atlas(&glyph_buffer_builder).unwrap(); let end = time::precise_time_ns(); results.push((end - last_time) as f64); @@ -108,15 +108,14 @@ fn main() { .create_image(Format::R8, buffer::Protection::WriteOnly, &atlas_size) .unwrap(); + let rect = Rect::new(Point2D::new(0, 0), atlas_size); + let mut results = vec![]; let start_time = time::precise_time_ns(); loop { - let events = rasterizer.draw_atlas(&Rect::new(Point2D::new(0, 0), atlas_size), - &batch_builder.atlas, - &glyph_buffers, - &batch, - &coverage_buffer, - &image).unwrap(); + let events = + rasterizer.draw_atlas(&image, &rect, &atlas, &glyph_buffers, &coverage_buffer) + .unwrap(); let mut draw_time = 0u64; unsafe { diff --git a/examples/dump-outlines.rs b/examples/dump-outlines.rs index 4ad7211e..7d27f5c4 100644 --- a/examples/dump-outlines.rs +++ b/examples/dump-outlines.rs @@ -28,7 +28,7 @@ fn main() { let mut last_point: Option> = None; let mut last_point_was_off_curve = false; font.for_each_point(glyph_id, |point| { - if point.first_point_in_contour { + if point.index_in_contour == 0 { println!("M {},{}", point.position.x, point.position.y); } else { let last = last_point.unwrap(); diff --git a/examples/generate-atlas.rs b/examples/generate-atlas.rs index f7b05ed4..c6c1b046 100644 --- a/examples/generate-atlas.rs +++ b/examples/generate-atlas.rs @@ -16,7 +16,7 @@ use euclid::{Point2D, Rect, Size2D}; use gl::types::{GLint, GLuint}; use glfw::{Action, Context, Key, OpenGlProfileHint, WindowEvent, WindowHint, WindowMode}; use memmap::{Mmap, Protection}; -use pathfinder::batch::BatchBuilder; +use pathfinder::atlas::AtlasBuilder; use pathfinder::charmap::CodepointRange; use pathfinder::coverage::CoverageBuffer; use pathfinder::glyph_buffer::GlyphBufferBuilder; @@ -59,7 +59,7 @@ fn main() { let shelf_height = (point_size * 2.0).ceil() as u32; let mut glyph_buffer_builder = GlyphBufferBuilder::new(); - let mut batch_builder = BatchBuilder::new(device_pixel_width as GLuint, shelf_height); + let mut atlas_builder = AtlasBuilder::new(device_pixel_width as GLuint, shelf_height); unsafe { let font = Font::new(file.as_slice()).unwrap(); @@ -68,12 +68,13 @@ fn main() { let glyph_ranges = font.glyph_ranges_for_codepoint_ranges(&codepoint_ranges).unwrap(); for (glyph_index, glyph_id) in glyph_ranges.iter().enumerate() { glyph_buffer_builder.add_glyph(&font, glyph_id).unwrap(); - batch_builder.add_glyph(&glyph_buffer_builder, glyph_index as u32, point_size).unwrap() + atlas_builder.pack_glyph(&glyph_buffer_builder, glyph_index as u32, point_size) + .unwrap() } } let glyph_buffers = glyph_buffer_builder.create_buffers().unwrap(); - let batch = batch_builder.create_batch(&glyph_buffer_builder).unwrap(); + let atlas = atlas_builder.create_atlas(&glyph_buffer_builder).unwrap(); let atlas_size = Size2D::new(device_pixel_width as GLuint, device_pixel_height as GLuint); let coverage_buffer = CoverageBuffer::new(&rasterizer.device, &atlas_size).unwrap(); @@ -82,12 +83,9 @@ fn main() { .create_image(Format::R8, buffer::Protection::WriteOnly, &atlas_size) .unwrap(); - rasterizer.draw_atlas(&Rect::new(Point2D::new(0, 0), atlas_size), - &batch_builder.atlas, - &glyph_buffers, - &batch, - &coverage_buffer, - &image).unwrap(); + let rect = Rect::new(Point2D::new(0, 0), atlas_size); + + rasterizer.draw_atlas(&image, &rect, &atlas, &glyph_buffers, &coverage_buffer).unwrap(); rasterizer.queue.flush().unwrap(); let draw_context = lord_drawquaad::Context::new(); diff --git a/examples/lorem-ipsum.rs b/examples/lorem-ipsum.rs index b33bf6a9..a92d66c3 100644 --- a/examples/lorem-ipsum.rs +++ b/examples/lorem-ipsum.rs @@ -19,7 +19,7 @@ use euclid::{Point2D, Rect, Size2D}; use gl::types::{GLchar, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid}; use glfw::{Action, Context, Key, OpenGlProfileHint, WindowEvent, WindowHint, WindowMode}; use memmap::{Mmap, Protection}; -use pathfinder::batch::BatchBuilder; +use pathfinder::atlas::AtlasBuilder; use pathfinder::charmap::CodepointRange; use pathfinder::coverage::CoverageBuffer; use pathfinder::glyph_buffer::{GlyphBufferBuilder, GlyphBuffers}; @@ -115,7 +115,7 @@ fn main() { } let glyph_buffers = glyph_buffer_builder.create_buffers().unwrap(); - let mut fps_batch = renderer.create_fps_batch(&glyph_buffer_builder, + let mut fps_atlas = renderer.create_fps_atlas(&glyph_buffer_builder, &glyph_buffers, glyph_count); @@ -139,7 +139,7 @@ fn main() { let timing = renderer.get_timing_in_ms(); renderer.draw_fps(&font, - &mut fps_batch, + &mut fps_atlas, &glyph_buffer_builder, &device_pixel_size, &glyph_ranges, @@ -384,18 +384,20 @@ impl Renderer { // FIXME(pcwalton) let shelf_height = (point_size * 2.0).ceil() as u32; - let mut batch_builder = BatchBuilder::new(ATLAS_SIZE, shelf_height); + let mut atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height); for glyph_index in 0..(glyph_count as u32) { - batch_builder.add_glyph(&glyph_buffer_builder, glyph_index, point_size).unwrap() + atlas_builder.pack_glyph(&glyph_buffer_builder, glyph_index, point_size).unwrap() } - let batch = batch_builder.create_batch(&glyph_buffer_builder).unwrap(); - let events = self.rasterizer.draw_atlas(&Rect::new(Point2D::new(0, 0), self.atlas_size), - &batch_builder.atlas, + let atlas = atlas_builder.create_atlas(&glyph_buffer_builder).unwrap(); + + let rect = Rect::new(Point2D::new(0, 0), self.atlas_size); + + let events = self.rasterizer.draw_atlas(&self.main_compute_image, + &rect, + &atlas, glyph_buffers, - &batch, - &self.coverage_buffer, - &self.main_compute_image).unwrap(); + &self.coverage_buffer).unwrap(); self.rasterizer.queue.flush().unwrap(); unsafe { @@ -407,7 +409,7 @@ impl Renderer { gl::Clear(gl::COLOR_BUFFER_BIT); } - self.draw_glyphs(&mut batch_builder, + self.draw_glyphs(&mut atlas_builder, glyph_buffer_builder, glyph_positions, device_pixel_size, @@ -428,7 +430,7 @@ impl Renderer { } fn draw_glyphs(&self, - batch_builder: &mut BatchBuilder, + atlas_builder: &mut AtlasBuilder, glyph_buffer_builder: &GlyphBufferBuilder, glyph_positions: &[GlyphPos], device_pixel_size: &Size2D, @@ -442,7 +444,7 @@ impl Renderer { gl::BindBuffer(gl::ARRAY_BUFFER, self.composite_vertex_buffer); gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.composite_index_buffer); - let vertex_count = self.upload_quads_for_text(batch_builder, + let vertex_count = self.upload_quads_for_text(atlas_builder, glyph_buffer_builder, glyph_positions, point_size); @@ -479,7 +481,7 @@ impl Renderer { } fn upload_quads_for_text(&self, - batch_builder: &mut BatchBuilder, + atlas_builder: &mut AtlasBuilder, glyph_buffer_builder: &GlyphBufferBuilder, glyph_positions: &[GlyphPos], point_size: f32) @@ -488,12 +490,12 @@ impl Renderer { let (mut vertices, mut indices) = (vec![], vec![]); for position in glyph_positions { - let glyph_index = match batch_builder.glyph_index_for(position.glyph_id) { + let glyph_index = match atlas_builder.glyph_index_for(position.glyph_id) { None => continue, Some(glyph_index) => glyph_index, }; let glyph_bounds = glyph_buffer_builder.glyph_bounds(glyph_index); - let uv_rect = batch_builder.atlas_rect(glyph_index); + let uv_rect = atlas_builder.atlas_rect(glyph_index); let (uv_bl, uv_tr) = (uv_rect.origin, uv_rect.bottom_right()); let left_pos = (position.x as f32 * pixels_per_unit).round() as i32; @@ -526,35 +528,36 @@ impl Renderer { indices.len() } - fn create_fps_batch(&self, + fn create_fps_atlas(&self, glyph_buffer_builder: &GlyphBufferBuilder, glyph_buffers: &GlyphBuffers, glyph_count: usize) - -> BatchBuilder { + -> AtlasBuilder { // FIXME(pcwalton) let shelf_height = (FPS_DISPLAY_POINT_SIZE * 2.0).ceil() as u32; - let mut batch_builder = BatchBuilder::new(ATLAS_SIZE, shelf_height); + let mut atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height); for glyph_index in 0..(glyph_count as u32) { - batch_builder.add_glyph(&glyph_buffer_builder, - glyph_index, - FPS_DISPLAY_POINT_SIZE).unwrap() + atlas_builder.pack_glyph(&glyph_buffer_builder, glyph_index, FPS_DISPLAY_POINT_SIZE) + .unwrap() } - let batch = batch_builder.create_batch(&glyph_buffer_builder).unwrap(); - self.rasterizer.draw_atlas(&Rect::new(Point2D::new(0, 0), self.atlas_size), - &batch_builder.atlas, + let atlas = atlas_builder.create_atlas(&glyph_buffer_builder).unwrap(); + + let rect = Rect::new(Point2D::new(0, 0), self.atlas_size); + + self.rasterizer.draw_atlas(&self.fps_compute_image, + &rect, + &atlas, glyph_buffers, - &batch, - &self.coverage_buffer, - &self.fps_compute_image).unwrap(); + &self.coverage_buffer).unwrap(); - batch_builder + atlas_builder } fn draw_fps(&self, font: &Font, - batch_builder: &mut BatchBuilder, + atlas_builder: &mut AtlasBuilder, glyph_buffer_builder: &GlyphBufferBuilder, device_pixel_size: &Size2D, glyph_ranges: &GlyphRanges, @@ -611,7 +614,7 @@ impl Renderer { }); } - self.draw_glyphs(batch_builder, + self.draw_glyphs(atlas_builder, glyph_buffer_builder, &fps_glyphs, device_pixel_size, diff --git a/src/atlas.rs b/src/atlas.rs index aad54d5a..c6311fa9 100644 --- a/src/atlas.rs +++ b/src/atlas.rs @@ -9,108 +9,188 @@ // except according to those terms. use euclid::{Point2D, Rect, Size2D}; +use gl::types::{GLenum, GLsizei, GLsizeiptr, GLuint, GLvoid}; +use gl; +use glyph_buffer::GlyphBufferBuilder; +use rect_packer::RectPacker; +use std::mem; +use std::os::raw::c_void; +use std::u16; + +pub struct AtlasBuilder { + pub rect_packer: RectPacker, + image_descriptors: Vec, + image_metadata: Vec, +} + +impl AtlasBuilder { + /// FIXME(pcwalton): Including the shelf height here may be a bad API. + #[inline] + 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![], + } + } + + /// FIXME(pcwalton): Support the same glyph drawn at multiple point sizes. + pub fn pack_glyph(&mut self, + glyph_buffer_builder: &GlyphBufferBuilder, + glyph_index: u32, + point_size: f32) + -> Result<(), ()> { + // FIXME(pcwalton): I think this will check for negative values and panic, which is + // unnecessary. + let pixel_size = glyph_buffer_builder.glyph_pixel_bounds(glyph_index, point_size) + .size + .ceil() + .cast() + .unwrap(); + + let glyph_id = glyph_buffer_builder.glyph_id(glyph_index); + + let atlas_origin = try!(self.rect_packer.pack(&pixel_size)); + + let glyph_index = self.image_descriptors.len() as u32; + + while self.image_descriptors.len() < glyph_index as usize + 1 { + self.image_descriptors.push(ImageDescriptor::default()) + } + + self.image_descriptors[glyph_index as usize] = ImageDescriptor { + atlas_x: atlas_origin.x, + atlas_y: atlas_origin.y, + point_size: (point_size * 65536.0) as u32, + glyph_index: glyph_index, + }; + + self.image_metadata.push(ImageMetadata { + atlas_size: pixel_size, + glyph_index: glyph_index, + glyph_id: glyph_id, + }); + + Ok(()) + } + + pub fn create_atlas(&mut self, glyph_buffer_builder: &GlyphBufferBuilder) + -> 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![]); + for image_metadata in &self.image_metadata { + let glyph_index = image_metadata.glyph_index; + + let first_index = glyph_buffer_builder.descriptors[glyph_index as usize].start_index as + usize; + let last_index = match glyph_buffer_builder.descriptors.get(glyph_index as usize + 1) { + Some(ref descriptor) => descriptor.start_index as usize, + None => glyph_buffer_builder.indices.len(), + }; + + match current_range { + Some((current_first, current_last)) if first_index == current_last => { + current_range = Some((current_first, last_index)) + } + Some((current_first, current_last)) => { + counts.push((current_last - current_first) as GLsizei); + start_indices.push(current_first); + current_range = Some((first_index, last_index)) + } + None => current_range = Some((first_index, last_index)), + } + } + if let Some((current_first, current_last)) = current_range { + counts.push((current_last - current_first) as GLsizei); + start_indices.push(current_first); + } + + // TODO(pcwalton): Try using `glMapBuffer` here. + unsafe { + let mut images = 0; + gl::GenBuffers(1, &mut images); + + let length = self.image_descriptors.len() * mem::size_of::(); + let ptr = self.image_descriptors.as_ptr() as *const ImageDescriptor as *const c_void; + gl::BindBuffer(gl::UNIFORM_BUFFER, images); + gl::BufferData(gl::UNIFORM_BUFFER, length as GLsizeiptr, ptr, gl::DYNAMIC_DRAW); + + Ok(Atlas { + start_indices: start_indices, + counts: counts, + images: images, + + shelf_height: self.rect_packer.shelf_height(), + shelf_columns: self.rect_packer.shelf_columns(), + }) + } + } + + #[inline] + pub fn glyph_index_for(&self, glyph_id: u16) -> Option { + match self.image_metadata.binary_search_by(|metadata| metadata.glyph_id.cmp(&glyph_id)) { + Ok(glyph_index) => Some(self.image_metadata[glyph_index].glyph_index), + Err(_) => None, + } + } + + #[inline] + pub fn atlas_rect(&self, glyph_index: u32) -> Rect { + let descriptor = &self.image_descriptors[glyph_index as usize]; + let metadata = &self.image_metadata[glyph_index as usize]; + Rect::new(Point2D::new(descriptor.atlas_x, descriptor.atlas_y), metadata.atlas_size) + } +} pub struct Atlas { - free_rects: Vec>, - available_width: u32, - shelf_height: u32, - shelf_count: u32, - /// The amount of horizontal space allocated in the last shelf. - width_of_last_shelf: u32, + start_indices: Vec, + counts: Vec, + images: GLuint, + + pub shelf_height: u32, + pub shelf_columns: u32, +} + +impl Drop for Atlas { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &mut self.images); + } + } } impl Atlas { - #[inline] - pub fn new(available_width: u32, shelf_height: u32) -> Atlas { - Atlas { - free_rects: vec![], - available_width: available_width, - shelf_height: shelf_height, - shelf_count: 0, - width_of_last_shelf: 0, - } - } - - pub fn place(&mut self, size: &Size2D) -> Result, ()> { - // Add a one-pixel border to prevent bleed. - let alloc_size = *size + Size2D::new(2, 2); - - let chosen_index_and_rect = - self.free_rects - .iter() - .enumerate() - .filter(|&(_, rect)| { - alloc_size.width <= rect.size.width && alloc_size.height <= rect.size.height - }) - .min_by(|&(_, a), &(_, b)| area(a).cmp(&area(b))) - .map(|(index, rect)| (index, *rect)); - - let chosen_rect; - match chosen_index_and_rect { - None => { - // Make a new shelf. - chosen_rect = Rect::new(Point2D::new(0, self.shelf_height * self.shelf_count), - Size2D::new(self.available_width, self.shelf_height)); - self.shelf_count += 1; - self.width_of_last_shelf = 0 - } - Some((index, rect)) => { - self.free_rects.swap_remove(index); - chosen_rect = rect; - } - } - - // Guillotine to bottom. - let free_below = - Rect::new(Point2D::new(chosen_rect.origin.x, chosen_rect.origin.y + alloc_size.height), - Size2D::new(alloc_size.width, chosen_rect.size.height - alloc_size.height)); - if !free_below.is_empty() { - self.free_rects.push(free_below); - } - - // Guillotine to right. - let free_to_right = - Rect::new(Point2D::new(chosen_rect.origin.x + alloc_size.width, chosen_rect.origin.y), - Size2D::new(chosen_rect.size.width - alloc_size.width, - chosen_rect.size.height)); - if !free_to_right.is_empty() { - self.free_rects.push(free_to_right); - } - - // Update width of last shelf if necessary. - let on_last_shelf = chosen_rect.max_y() >= self.shelf_height * (self.shelf_count - 1); - if on_last_shelf && self.width_of_last_shelf < chosen_rect.max_x() { - self.width_of_last_shelf = chosen_rect.max_x() - } - - let object_origin = chosen_rect.origin + Point2D::new(1, 1); - Ok(object_origin) + 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); } #[inline] - pub fn available_width(&self) -> u32 { - self.available_width - } - - #[inline] - pub fn shelf_height(&self) -> u32 { - self.shelf_height - } - - #[inline] - pub fn shelf_columns(&self) -> u32 { - let full_shelf_count = if self.shelf_count == 0 { - 0 - } else { - self.shelf_count - 1 - }; - - full_shelf_count * self.available_width + self.width_of_last_shelf + pub fn images(&self) -> GLuint { + self.images } } -#[inline] -fn area(rect: &Rect) -> u32 { - rect.size.width * rect.size.height +/// Information about each image that we send to the GPU. +#[repr(C)] +#[derive(Clone, Copy, Default, Debug)] +pub struct ImageDescriptor { + atlas_x: u32, + atlas_y: u32, + point_size: u32, + glyph_index: u32, +} + +/// Information about each image that we keep around ourselves. +#[derive(Clone, Copy, Debug)] +pub struct ImageMetadata { + atlas_size: Size2D, + glyph_index: u32, + glyph_id: u16, } diff --git a/src/batch.rs b/src/batch.rs deleted file mode 100644 index b350885b..00000000 --- a/src/batch.rs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2017 The Servo Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use atlas::Atlas; -use euclid::{Point2D, Rect, Size2D}; -use gl::types::{GLenum, GLsizei, GLsizeiptr, GLuint, GLvoid}; -use gl; -use glyph_buffer::GlyphBufferBuilder; -use std::mem; -use std::os::raw::c_void; -use std::u16; - -pub struct BatchBuilder { - pub atlas: Atlas, - image_descriptors: Vec, - image_metadata: Vec, -} - -impl BatchBuilder { - /// FIXME(pcwalton): Including the shelf height here may be a bad API. - #[inline] - pub fn new(available_width: u32, shelf_height: u32) -> BatchBuilder { - BatchBuilder { - atlas: Atlas::new(available_width, shelf_height), - image_descriptors: vec![], - image_metadata: vec![], - } - } - - /// FIXME(pcwalton): Support the same glyph drawn at multiple point sizes. - pub fn add_glyph(&mut self, - glyph_buffer_builder: &GlyphBufferBuilder, - glyph_index: u32, - point_size: f32) - -> Result<(), ()> { - let descriptor = &glyph_buffer_builder.descriptors[glyph_index as usize]; - - // FIXME(pcwalton): I think this will check for negative values and panic, which is - // unnecessary. - let pixel_size = descriptor.pixel_rect(point_size).size.ceil().cast().unwrap(); - let atlas_origin = try!(self.atlas.place(&pixel_size)); - - while self.image_descriptors.len() < glyph_index as usize + 1 { - self.image_descriptors.push(ImageDescriptor::default()) - } - - self.image_descriptors[glyph_index as usize] = ImageDescriptor { - atlas_x: atlas_origin.x, - atlas_y: atlas_origin.y, - point_size: (point_size * 65536.0) as u32, - glyph_index: glyph_index, - }; - - self.image_metadata.push(ImageMetadata { - atlas_size: pixel_size, - glyph_index: glyph_index, - glyph_id: descriptor.glyph_id, - }); - - Ok(()) - } - - pub fn create_batch(&mut self, glyph_buffer_builder: &GlyphBufferBuilder) - -> 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![]); - for image_metadata in &self.image_metadata { - let glyph_index = image_metadata.glyph_index; - - let first_index = glyph_buffer_builder.descriptors[glyph_index as usize].start_index as - usize; - let last_index = match glyph_buffer_builder.descriptors.get(glyph_index as usize + 1) { - Some(ref descriptor) => descriptor.start_index as usize, - None => glyph_buffer_builder.indices.len(), - }; - - match current_range { - Some((current_first, current_last)) if first_index == current_last => { - current_range = Some((current_first, last_index)) - } - Some((current_first, current_last)) => { - counts.push((current_last - current_first) as GLsizei); - start_indices.push(current_first); - current_range = Some((first_index, last_index)) - } - None => current_range = Some((first_index, last_index)), - } - } - if let Some((current_first, current_last)) = current_range { - counts.push((current_last - current_first) as GLsizei); - start_indices.push(current_first); - } - - // TODO(pcwalton): Try using `glMapBuffer` here. - unsafe { - let mut images = 0; - gl::GenBuffers(1, &mut images); - - let length = self.image_descriptors.len() * mem::size_of::(); - let ptr = self.image_descriptors.as_ptr() as *const ImageDescriptor as *const c_void; - gl::BindBuffer(gl::UNIFORM_BUFFER, images); - gl::BufferData(gl::UNIFORM_BUFFER, length as GLsizeiptr, ptr, gl::DYNAMIC_DRAW); - - Ok(Batch { - start_indices: start_indices, - counts: counts, - images: images, - }) - } - } - - #[inline] - pub fn glyph_index_for(&self, glyph_id: u16) -> Option { - match self.image_metadata.binary_search_by(|metadata| metadata.glyph_id.cmp(&glyph_id)) { - Ok(glyph_index) => Some(self.image_metadata[glyph_index].glyph_index), - Err(_) => None, - } - } - - #[inline] - pub fn atlas_rect(&self, glyph_index: u32) -> Rect { - let descriptor = &self.image_descriptors[glyph_index as usize]; - let metadata = &self.image_metadata[glyph_index as usize]; - Rect::new(Point2D::new(descriptor.atlas_x, descriptor.atlas_y), metadata.atlas_size) - } -} - -pub struct Batch { - start_indices: Vec, - counts: Vec, - images: GLuint, -} - -impl Drop for Batch { - fn drop(&mut self) { - unsafe { - gl::DeleteBuffers(1, &mut self.images); - } - } -} - -impl Batch { - 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); - } - - #[inline] - pub fn images(&self) -> GLuint { - self.images - } -} - -/// Information about each image that we send to the GPU. -#[repr(C)] -#[derive(Clone, Copy, Default, Debug)] -pub struct ImageDescriptor { - atlas_x: u32, - atlas_y: u32, - point_size: u32, - glyph_index: u32, -} - -/// Information about each image that we keep around ourselves. -#[derive(Clone, Copy, Debug)] -pub struct ImageMetadata { - atlas_size: Size2D, - glyph_index: u32, - glyph_id: u16, -} - diff --git a/src/glyph_buffer.rs b/src/glyph_buffer.rs index 3978d4dd..75bdde96 100644 --- a/src/glyph_buffer.rs +++ b/src/glyph_buffer.rs @@ -83,6 +83,18 @@ impl GlyphBufferBuilder { self.descriptors[glyph_index as usize].bounds } + /// Returns the glyph rectangle in pixels. + #[inline] + pub fn glyph_pixel_bounds(&self, glyph_index: u32, point_size: f32) -> Rect { + self.descriptors[glyph_index as usize].pixel_rect(point_size) + } + + /// Returns the ID of the glyph with the given index. + #[inline] + pub fn glyph_id(&self, glyph_index: u32) -> u16 { + self.descriptors[glyph_index as usize].glyph_id + } + pub fn create_buffers(&self) -> Result { // TODO(pcwalton): Try using `glMapBuffer` here. Requires precomputing contour types and // counts. @@ -148,7 +160,7 @@ pub struct GlyphDescriptor { impl GlyphDescriptor { #[inline] - pub fn pixel_rect(&self, point_size: f32) -> Rect { + fn pixel_rect(&self, point_size: f32) -> Rect { let pixels_per_unit = point_size / self.units_per_em as f32; Rect::new(Point2D::new(self.bounds.left as f32, self.bounds.bottom as f32), Size2D::new((self.bounds.right - self.bounds.left) as f32, diff --git a/src/lib.rs b/src/lib.rs index f5845f53..962290a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,13 +25,13 @@ extern crate quickcheck; extern crate test; pub mod atlas; -pub mod batch; pub mod charmap; pub mod coverage; pub mod glyph_buffer; pub mod glyph_range; pub mod otf; pub mod rasterizer; +pub mod rect_packer; pub mod shaper; mod util; diff --git a/src/rasterizer.rs b/src/rasterizer.rs index 0631b9b4..dd979d3d 100644 --- a/src/rasterizer.rs +++ b/src/rasterizer.rs @@ -9,7 +9,6 @@ // except according to those terms. use atlas::Atlas; -use batch::Batch; use compute_shader::device::Device; use compute_shader::image::Image; use compute_shader::instance::{Instance, ShadingLanguage}; @@ -144,18 +143,17 @@ impl Rasterizer { } pub fn draw_atlas(&self, - atlas_rect: &Rect, + image: &Image, + rect: &Rect, atlas: &Atlas, glyph_buffers: &GlyphBuffers, - batch: &Batch, - coverage_buffer: &CoverageBuffer, - image: &Image) + coverage_buffer: &CoverageBuffer) -> Result { unsafe { gl::BindFramebuffer(gl::FRAMEBUFFER, coverage_buffer.framebuffer()); - gl::Viewport(0, 0, atlas_rect.size.width as GLint, atlas_rect.size.height as GLint); + gl::Viewport(0, 0, rect.size.width as GLint, rect.size.height as GLint); - // TODO(pcwalton): Scissor to the atlas rect to clear faster? + // TODO(pcwalton): Scissor to the image rect to clear faster? gl::ClearColor(0.0, 0.0, 0.0, 1.0); gl::Clear(gl::COLOR_BUFFER_BIT); @@ -180,13 +178,11 @@ impl Rasterizer { gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, glyph_buffers.indices); gl::BindBufferBase(gl::UNIFORM_BUFFER, 1, glyph_buffers.descriptors); - gl::BindBufferBase(gl::UNIFORM_BUFFER, 2, batch.images()); + gl::BindBufferBase(gl::UNIFORM_BUFFER, 2, atlas.images()); gl::UniformBlockBinding(self.draw_program, self.draw_glyph_descriptors_uniform, 1); gl::UniformBlockBinding(self.draw_program, self.draw_image_descriptors_uniform, 2); - gl::Uniform2ui(self.draw_atlas_size_uniform, - atlas_rect.size.width, - atlas_rect.size.height); + gl::Uniform2ui(self.draw_atlas_size_uniform, rect.size.width, rect.size.height); gl::PatchParameteri(gl::PATCH_VERTICES, 3); @@ -210,7 +206,7 @@ impl Rasterizer { }; // Now draw the glyph ranges. gl::BeginQuery(gl::TIME_ELAPSED, self.draw_query); - batch.draw(primitive); + atlas.draw(primitive); gl::EndQuery(gl::TIME_ELAPSED); gl::Disable(gl::CULL_FACE); @@ -224,22 +220,15 @@ impl Rasterizer { gl::Flush(); } - let atlas_rect_uniform = [ - atlas_rect.origin.x, - atlas_rect.origin.y, - atlas_rect.max_x(), - atlas_rect.max_y() - ]; - let accum_uniforms = [ (0, Uniform::Image(image)), (1, Uniform::Image(coverage_buffer.image())), - (2, Uniform::UVec4(atlas_rect_uniform)), - (3, Uniform::U32(atlas.shelf_height())), + (2, Uniform::UVec4([rect.origin.x, rect.origin.y, rect.max_x(), rect.max_y()])), + (3, Uniform::U32(atlas.shelf_height)), ]; let accum_event = try!(self.queue.submit_compute(&self.accum_program, - &[atlas.shelf_columns()], + &[atlas.shelf_columns], &accum_uniforms, &[]).map_err(drop)); diff --git a/src/rect_packer.rs b/src/rect_packer.rs new file mode 100644 index 00000000..bb4b5997 --- /dev/null +++ b/src/rect_packer.rs @@ -0,0 +1,116 @@ +// Copyright 2017 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use euclid::{Point2D, Rect, Size2D}; + +pub struct RectPacker { + free_rects: Vec>, + available_width: u32, + shelf_height: u32, + shelf_count: u32, + /// The amount of horizontal space allocated in the last shelf. + width_of_last_shelf: u32, +} + +impl RectPacker { + #[inline] + pub fn new(available_width: u32, shelf_height: u32) -> RectPacker { + RectPacker { + free_rects: vec![], + available_width: available_width, + shelf_height: shelf_height, + shelf_count: 0, + width_of_last_shelf: 0, + } + } + + pub fn pack(&mut self, size: &Size2D) -> Result, ()> { + // Add a one-pixel border to prevent bleed. + let alloc_size = *size + Size2D::new(2, 2); + + let chosen_index_and_rect = + self.free_rects + .iter() + .enumerate() + .filter(|&(_, rect)| { + alloc_size.width <= rect.size.width && alloc_size.height <= rect.size.height + }) + .min_by(|&(_, a), &(_, b)| area(a).cmp(&area(b))) + .map(|(index, rect)| (index, *rect)); + + let chosen_rect; + match chosen_index_and_rect { + None => { + // Make a new shelf. + chosen_rect = Rect::new(Point2D::new(0, self.shelf_height * self.shelf_count), + Size2D::new(self.available_width, self.shelf_height)); + self.shelf_count += 1; + self.width_of_last_shelf = 0 + } + Some((index, rect)) => { + self.free_rects.swap_remove(index); + chosen_rect = rect; + } + } + + // Guillotine to bottom. + let free_below = + Rect::new(Point2D::new(chosen_rect.origin.x, chosen_rect.origin.y + alloc_size.height), + Size2D::new(alloc_size.width, chosen_rect.size.height - alloc_size.height)); + if !free_below.is_empty() { + self.free_rects.push(free_below); + } + + // Guillotine to right. + let free_to_right = + Rect::new(Point2D::new(chosen_rect.origin.x + alloc_size.width, chosen_rect.origin.y), + Size2D::new(chosen_rect.size.width - alloc_size.width, + chosen_rect.size.height)); + if !free_to_right.is_empty() { + self.free_rects.push(free_to_right); + } + + // Update width of last shelf if necessary. + let on_last_shelf = chosen_rect.max_y() >= self.shelf_height * (self.shelf_count - 1); + if on_last_shelf && self.width_of_last_shelf < chosen_rect.max_x() { + self.width_of_last_shelf = chosen_rect.max_x() + } + + let object_origin = chosen_rect.origin + Point2D::new(1, 1); + Ok(object_origin) + } + + #[inline] + pub fn available_width(&self) -> u32 { + self.available_width + } + + #[inline] + pub fn shelf_height(&self) -> u32 { + self.shelf_height + } + + #[inline] + pub fn shelf_columns(&self) -> u32 { + let full_shelf_count = if self.shelf_count == 0 { + 0 + } else { + self.shelf_count - 1 + }; + + full_shelf_count * self.available_width + self.width_of_last_shelf + } +} + +#[inline] +fn area(rect: &Rect) -> u32 { + rect.size.width * rect.size.height +} + diff --git a/src/tests/mod.rs b/src/tests/mod.rs index b03ba9ad..4b8516b9 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -8,6 +8,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -mod atlas; mod buffers; +mod rect_packer; diff --git a/src/tests/atlas.rs b/src/tests/rect_packer.rs similarity index 59% rename from src/tests/atlas.rs rename to src/tests/rect_packer.rs index 43af38d5..040ea675 100644 --- a/src/tests/atlas.rs +++ b/src/tests/rect_packer.rs @@ -1,11 +1,11 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -use atlas::Atlas; +use rect_packer::RectPacker; use euclid::{Rect, Size2D}; use std::cmp; -fn place_objects(available_width: u32, objects: Vec<(u32, u32)>) -> (Atlas, Vec>) { +fn pack_objects(available_width: u32, objects: Vec<(u32, u32)>) -> (RectPacker, Vec>) { let objects: Vec<_> = objects.iter() .map(|&(width, height)| Size2D::new(width, height)) .collect(); @@ -14,16 +14,16 @@ fn place_objects(available_width: u32, objects: Vec<(u32, u32)>) -> (Atlas, Vec< cmp::max(available_width, objects.iter().map(|object| object.width).max().unwrap_or(0)); let shelf_height = objects.iter().map(|object| object.height).max().unwrap_or(0) + 2; - let mut atlas = Atlas::new(available_width, shelf_height); + let mut rect_packer = RectPacker::new(available_width, shelf_height); let rects = objects.iter() - .map(|object| Rect::new(atlas.place(object).unwrap(), *object)) + .map(|object| Rect::new(rect_packer.pack(object).unwrap(), *object)) .collect(); - (atlas, rects) + (rect_packer, rects) } quickcheck! { fn objects_dont_overlap(available_width: u32, objects: Vec<(u32, u32)>) -> bool { - let (_, rects) = place_objects(available_width, objects); + let (_, rects) = pack_objects(available_width, objects); for (i, a) in rects.iter().enumerate() { for b in &rects[(i + 1)..] { assert!(!a.intersects(b)) @@ -33,15 +33,15 @@ quickcheck! { } fn objects_dont_exceed_available_width(available_width: u32, objects: Vec<(u32, u32)>) -> bool { - let (atlas, rects) = place_objects(available_width, objects); - rects.iter().all(|rect| rect.max_x() <= atlas.available_width()) + let (rect_packer, rects) = pack_objects(available_width, objects); + rects.iter().all(|rect| rect.max_x() <= rect_packer.available_width()) } fn objects_dont_cross_shelves(available_width: u32, objects: Vec<(u32, u32)>) -> bool { - let (atlas, rects) = place_objects(available_width, objects); + let (rect_packer, rects) = pack_objects(available_width, objects); rects.iter().all(|rect| { - rect.is_empty() || - rect.origin.y / atlas.shelf_height() == (rect.max_y() - 1) / atlas.shelf_height() + let shelf_height = rect_packer.shelf_height(); + rect.is_empty() || rect.origin.y / shelf_height == (rect.max_y() - 1) / shelf_height }) } }