diff --git a/examples/canvas_nanovg/src/main.rs b/examples/canvas_nanovg/src/main.rs index 3906574e..7afa137e 100644 --- a/examples/canvas_nanovg/src/main.rs +++ b/examples/canvas_nanovg/src/main.rs @@ -533,12 +533,12 @@ fn main() { let mut event_pump = sdl_context.event_pump().unwrap(); let mut mouse_position = Vector2F::default(); let start_time = Instant::now(); + let font_context = CanvasFontContext::from_system_source(); // Enter the main loop. loop { // Make a canvas. - let mut canvas = CanvasRenderingContext2D::new(CanvasFontContext::from_system_source(), - window_size.to_f32()); + let mut canvas = CanvasRenderingContext2D::new(font_context.clone(), window_size.to_f32()); // Render the demo. let time = (Instant::now() - start_time).as_secs_f32(); diff --git a/gl/src/lib.rs b/gl/src/lib.rs index c2cd5970..b2742c33 100644 --- a/gl/src/lib.rs +++ b/gl/src/lib.rs @@ -449,6 +449,17 @@ impl Device for GLDevice { &framebuffer.texture } + #[inline] + fn destroy_framebuffer(&self, framebuffer: Self::Framebuffer) -> Self::Texture { + let texture = GLTexture { + gl_texture: framebuffer.texture.gl_texture, + size: framebuffer.texture.size, + format: framebuffer.texture.format, + }; + mem::forget(framebuffer); + texture + } + #[inline] fn texture_format(&self, texture: &Self::Texture) -> TextureFormat { texture.format diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs index 4787bafe..d66c067c 100644 --- a/gpu/src/lib.rs +++ b/gpu/src/lib.rs @@ -70,6 +70,7 @@ pub trait Device: Sized { mode: BufferUploadMode, ); fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture; + fn destroy_framebuffer(&self, framebuffer: Self::Framebuffer) -> Self::Texture; fn texture_format(&self, texture: &Self::Texture) -> TextureFormat; fn texture_size(&self, texture: &Self::Texture) -> Vector2I; fn upload_to_texture(&self, texture: &Self::Texture, rect: RectI, data: TextureDataRef); diff --git a/metal/src/lib.rs b/metal/src/lib.rs index 6876c1f0..c7978603 100644 --- a/metal/src/lib.rs +++ b/metal/src/lib.rs @@ -433,10 +433,16 @@ impl Device for MetalDevice { } } + #[inline] fn framebuffer_texture<'f>(&self, framebuffer: &'f MetalFramebuffer) -> &'f MetalTexture { &framebuffer.0 } + #[inline] + fn destroy_framebuffer(&self, framebuffer: MetalFramebuffer) -> MetalTexture { + framebuffer.0 + } + fn texture_format(&self, texture: &MetalTexture) -> TextureFormat { match texture.texture.pixel_format() { MTLPixelFormat::R8Unorm => TextureFormat::R8, diff --git a/renderer/src/allocator.rs b/renderer/src/allocator.rs index 0e6d5c72..2f7230c7 100644 --- a/renderer/src/allocator.rs +++ b/renderer/src/allocator.rs @@ -11,19 +11,33 @@ //! A simple quadtree-based texture allocator. use pathfinder_geometry::rect::RectI; -use pathfinder_geometry::vector::Vector2I; -use std::mem; +use pathfinder_geometry::vector::{Vector2F, Vector2I}; -const MAX_TEXTURE_LENGTH: u32 = 4096; +const ATLAS_TEXTURE_LENGTH: u32 = 1024; #[derive(Debug)] pub struct TextureAllocator { + pages: Vec, +} + +// TODO(pcwalton): Add layers, perhaps? +#[derive(Debug)] +pub enum TexturePageAllocator { + // An atlas allocated with our quadtree allocator. + Atlas(TextureAtlasAllocator), + // A single image. + Image { size: Vector2I }, +} + +#[derive(Debug)] +pub struct TextureAtlasAllocator { root: TreeNode, size: u32, } #[derive(Clone, Copy, PartialEq, Debug)] pub struct TextureLocation { + pub page: u32, pub rect: RectI, } @@ -37,80 +51,97 @@ enum TreeNode { impl TextureAllocator { #[inline] - pub fn new(size: u32) -> TextureAllocator { - // Make sure that the size is a power of two. - debug_assert_eq!(size & (size - 1), 0); - TextureAllocator { root: TreeNode::EmptyLeaf, size } + pub fn new() -> TextureAllocator { + TextureAllocator { pages: vec![] } } #[inline] - pub fn allocate(&mut self, requested_size: Vector2I) -> Option { - let requested_length = - (requested_size.x().max(requested_size.y()) as u32).next_power_of_two(); - loop { - if let Some(location) = self.root.allocate(Vector2I::default(), - self.size, - requested_length) { - return Some(location); - } - if !self.grow() { - return None; + pub fn allocate(&mut self, requested_size: Vector2I) -> TextureLocation { + // If too big, the image gets its own page. + if requested_size.x() > ATLAS_TEXTURE_LENGTH as i32 || + requested_size.y() > ATLAS_TEXTURE_LENGTH as i32 { + let page = self.pages.len() as u32; + let rect = RectI::new(Vector2I::default(), requested_size); + self.pages.push(TexturePageAllocator::Image { size: rect.size() }); + return TextureLocation { page, rect }; + } + + // Try to add to each atlas. + for (page_index, page) in self.pages.iter_mut().enumerate() { + match *page { + TexturePageAllocator::Image { .. } => {} + TexturePageAllocator::Atlas(ref mut allocator) => { + if let Some(rect) = allocator.allocate(requested_size) { + return TextureLocation { page: page_index as u32, rect }; + } + } } } + + // Add a new atlas. + let page = self.pages.len() as u32; + let mut allocator = TextureAtlasAllocator::new(); + let rect = allocator.allocate(requested_size).expect("Allocation failed!"); + self.pages.push(TexturePageAllocator::Atlas(allocator)); + TextureLocation { page, rect } + } + + pub fn page_size(&self, page_index: u32) -> Vector2I { + match self.pages[page_index as usize] { + TexturePageAllocator::Atlas(ref atlas) => Vector2I::splat(atlas.size as i32), + TexturePageAllocator::Image { size } => size, + } + } + + pub fn page_scale(&self, page_index: u32) -> Vector2F { + Vector2F::splat(1.0) / self.page_size(page_index).to_f32() } #[inline] - #[allow(dead_code)] - pub fn free(&mut self, location: TextureLocation) { - let requested_length = location.rect.width() as u32; - self.root.free(Vector2I::default(), self.size, location.rect.origin(), requested_length) + pub fn page_count(&self) -> u32 { + self.pages.len() as u32 + } +} + +impl TextureAtlasAllocator { + #[inline] + fn new() -> TextureAtlasAllocator { + TextureAtlasAllocator::with_length(ATLAS_TEXTURE_LENGTH) + } + + #[inline] + fn with_length(length: u32) -> TextureAtlasAllocator { + TextureAtlasAllocator { root: TreeNode::EmptyLeaf, size: length } + } + + #[inline] + fn allocate(&mut self, requested_size: Vector2I) -> Option { + let requested_length = + (requested_size.x().max(requested_size.y()) as u32).next_power_of_two(); + self.root.allocate(Vector2I::default(), self.size, requested_length) } #[inline] #[allow(dead_code)] - pub fn is_empty(&self) -> bool { + fn free(&mut self, rect: RectI) { + let requested_length = rect.width() as u32; + self.root.free(Vector2I::default(), self.size, rect.origin(), requested_length) + } + + #[inline] + #[allow(dead_code)] + fn is_empty(&self) -> bool { match self.root { TreeNode::EmptyLeaf => true, _ => false, } } - - // TODO(pcwalton): Make this more flexible. - pub fn grow(&mut self) -> bool { - if self.size >= MAX_TEXTURE_LENGTH { - return false; - } - - let old_root = mem::replace(&mut self.root, TreeNode::EmptyLeaf); - self.size *= 2; - - // NB: Don't change the order of the children, or else texture coordinates of - // already-allocated objects will become invalid. - self.root = TreeNode::Parent([ - Box::new(old_root), - Box::new(TreeNode::EmptyLeaf), - Box::new(TreeNode::EmptyLeaf), - Box::new(TreeNode::EmptyLeaf), - ]); - - true - } - - #[inline] - pub fn size(&self) -> u32 { - self.size - } - - #[inline] - pub fn scale(&self) -> f32 { - 1.0 / self.size as f32 - } } impl TreeNode { // Invariant: `requested_size` must be a power of two. fn allocate(&mut self, this_origin: Vector2I, this_size: u32, requested_size: u32) - -> Option { + -> Option { if let TreeNode::FullLeaf = *self { // No room here. return None; @@ -125,9 +156,7 @@ impl TreeNode { // Do we have a perfect fit? if this_size == requested_size { *self = TreeNode::FullLeaf; - return Some(TextureLocation { - rect: RectI::new(this_origin, Vector2I::splat(this_size as i32)), - }); + return Some(RectI::new(this_origin, Vector2I::splat(this_size as i32))); } // Split. @@ -240,7 +269,7 @@ mod test { use quickcheck; use std::u32; - use super::TextureAllocator; + use super::TextureAtlasAllocator; #[test] fn test_allocation_and_freeing() { @@ -255,7 +284,7 @@ mod test { *height = (*height).min(length).max(1); } - let mut allocator = TextureAllocator::new(length); + let mut allocator = TextureAtlasAllocator::with_length(length); let mut locations = vec![]; for &(width, height) in &sizes { let size = Vector2I::new(width as i32, height as i32); diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index 0de444d3..282ee953 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -13,7 +13,7 @@ use crate::concurrent::executor::Executor; use crate::gpu::renderer::MASK_TILES_ACROSS; use crate::gpu_data::{AlphaTile, AlphaTileVertex, FillBatchPrimitive, MaskTile, MaskTileVertex}; -use crate::gpu_data::{RenderCommand, SolidTileVertex, TileObjectPrimitive}; +use crate::gpu_data::{RenderCommand, SolidTileBatch, TileObjectPrimitive}; use crate::options::{PreparedBuildOptions, RenderCommandListener}; use crate::paint::{PaintInfo, PaintMetadata}; use crate::scene::{DisplayItem, Scene}; @@ -52,6 +52,7 @@ pub(crate) struct ObjectBuilder { struct BuiltDrawPath { path: BuiltPath, blend_mode: BlendMode, + paint_page: u32, } #[derive(Debug)] @@ -157,7 +158,7 @@ impl<'a> SceneBuilder<'a> { let path_object = &scene.paths[path_index]; let outline = scene.apply_render_options(path_object.outline(), built_options); let paint_id = path_object.paint(); - + let paint_metadata = &paint_metadata[paint_id.0 as usize]; let built_clip_path = path_object.clip_path().map(|clip_path_id| &built_clip_paths[clip_path_id.0 as usize]); @@ -167,7 +168,7 @@ impl<'a> SceneBuilder<'a> { view_box, path_index as u16, TilingPathInfo::Draw { - paint_metadata: &paint_metadata[paint_id.0 as usize], + paint_metadata, blend_mode: path_object.blend_mode(), built_clip_path, }); @@ -179,6 +180,7 @@ impl<'a> SceneBuilder<'a> { BuiltDrawPath { path: tiler.object_builder.built_path, blend_mode: path_object.blend_mode(), + paint_page: paint_metadata.tex_page, } } @@ -204,9 +206,8 @@ impl<'a> SceneBuilder<'a> { let first_z_buffer = remaining_layer_z_buffers.pop().unwrap(); let first_solid_tiles = first_z_buffer.build_solid_tiles(&self.scene.paths, paint_metadata); - if !first_solid_tiles.is_empty() { - culled_tiles.display_list - .push(CulledDisplayItem::DrawSolidTiles(first_solid_tiles)); + for batch in first_solid_tiles.batches { + culled_tiles.display_list.push(CulledDisplayItem::DrawSolidTiles(batch)); } let mut layer_z_buffers_stack = vec![first_z_buffer]; @@ -220,9 +221,8 @@ impl<'a> SceneBuilder<'a> { let z_buffer = remaining_layer_z_buffers.pop().unwrap(); let solid_tiles = z_buffer.build_solid_tiles(&self.scene.paths, paint_metadata); - if !solid_tiles.is_empty() { - culled_tiles.display_list - .push(CulledDisplayItem::DrawSolidTiles(solid_tiles)); + for batch in solid_tiles.batches { + culled_tiles.display_list.push(CulledDisplayItem::DrawSolidTiles(batch)); } layer_z_buffers_stack.push(z_buffer); continue; @@ -240,18 +240,21 @@ impl<'a> SceneBuilder<'a> { culled_tiles.push_mask_tiles(&built_draw_path.path); // Create a new `DrawAlphaTiles` display item if we don't have one or if we have to - // break a batch due to blend mode differences. + // break a batch due to blend mode or paint page. // // TODO(pcwalton): If we really wanted to, we could use tile maps to avoid batch // breaks in some cases… match culled_tiles.display_list.last() { Some(&CulledDisplayItem::DrawAlphaTiles { tiles: _, + paint_page, blend_mode - }) if blend_mode == built_draw_path.blend_mode => {} + }) if paint_page == built_draw_path.paint_page && + blend_mode == built_draw_path.blend_mode => {} _ => { culled_tiles.display_list.push(CulledDisplayItem::DrawAlphaTiles { tiles: vec![], + paint_page: built_draw_path.paint_page, blend_mode: built_draw_path.blend_mode, }) } @@ -326,11 +329,15 @@ impl<'a> SceneBuilder<'a> { for display_item in culled_tiles.display_list { match display_item { - CulledDisplayItem::DrawSolidTiles(tiles) => { - self.listener.send(RenderCommand::DrawSolidTiles(tiles)) + CulledDisplayItem::DrawSolidTiles(batch) => { + self.listener.send(RenderCommand::DrawSolidTiles(batch)) } - CulledDisplayItem::DrawAlphaTiles { tiles, blend_mode } => { - self.listener.send(RenderCommand::DrawAlphaTiles { tiles, blend_mode }) + CulledDisplayItem::DrawAlphaTiles { tiles, paint_page, blend_mode } => { + self.listener.send(RenderCommand::DrawAlphaTiles { + tiles, + paint_page, + blend_mode, + }) } CulledDisplayItem::PushLayer { effects } => { self.listener.send(RenderCommand::PushLayer { effects }) @@ -381,8 +388,8 @@ struct CulledTiles { } enum CulledDisplayItem { - DrawSolidTiles(Vec), - DrawAlphaTiles { tiles: Vec, blend_mode: BlendMode }, + DrawSolidTiles(SolidTileBatch), + DrawAlphaTiles { tiles: Vec, paint_page: u32, blend_mode: BlendMode }, PushLayer { effects: Effects }, PopLayer, } diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 0d8b5e28..6137e51c 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -44,7 +44,7 @@ static QUAD_VERTEX_INDICES: [u32; 6] = [0, 1, 3, 1, 2, 3]; pub(crate) const MASK_TILES_ACROSS: u32 = 256; pub(crate) const MASK_TILES_DOWN: u32 = 256; -const FRAMEBUFFER_CACHE_SIZE: usize = 8; +const TEXTURE_CACHE_SIZE: usize = 8; // FIXME(pcwalton): Shrink this again! const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * MASK_TILES_ACROSS as i32; @@ -77,7 +77,7 @@ where fill_vertex_array: FillVertexArray, fill_framebuffer: D::Framebuffer, mask_framebuffer: D::Framebuffer, - paint_texture: Option, + paint_textures: Vec, layer_framebuffer_stack: Vec>, // This is a dummy texture consisting solely of a single `rgba(0, 0, 0, 255)` texel. It serves @@ -103,7 +103,7 @@ where // Rendering state framebuffer_flags: FramebufferFlags, buffered_fills: Vec, - framebuffer_cache: FramebufferCache, + texture_cache: TextureCache, // Debug pub stats: RenderStats, @@ -246,7 +246,7 @@ where fill_vertex_array, fill_framebuffer, mask_framebuffer, - paint_texture: None, + paint_textures: vec![], layer_framebuffer_stack: vec![], clear_paint_texture, @@ -270,7 +270,7 @@ where framebuffer_flags: FramebufferFlags::empty(), buffered_fills: vec![], - framebuffer_cache: FramebufferCache::new(), + texture_cache: TextureCache::new(), use_depth: false, } @@ -283,6 +283,7 @@ where } pub fn render_command(&mut self, command: &RenderCommand) { + //println!("{:?}", command); match *command { RenderCommand::Start { bounding_quad, path_count } => { if self.use_depth { @@ -303,17 +304,17 @@ where } RenderCommand::PushLayer { effects } => self.push_layer(effects), RenderCommand::PopLayer => self.pop_layer(), - RenderCommand::DrawSolidTiles(ref solid_tile_vertices) => { - let count = solid_tile_vertices.len() / 4; + RenderCommand::DrawSolidTiles(ref batch) => { + let count = batch.vertices.len() / 4; self.stats.solid_tile_count += count; - self.upload_solid_tiles(solid_tile_vertices); - self.draw_solid_tiles(count as u32); + self.upload_solid_tiles(&batch.vertices); + self.draw_solid_tiles(count as u32, batch.paint_page); } - RenderCommand::DrawAlphaTiles { tiles: ref alpha_tiles, blend_mode } => { + RenderCommand::DrawAlphaTiles { tiles: ref alpha_tiles, paint_page, blend_mode } => { let count = alpha_tiles.len(); self.stats.alpha_tile_count += count; self.upload_alpha_tiles(alpha_tiles); - self.draw_alpha_tiles(count as u32, blend_mode); + self.draw_alpha_tiles(count as u32, paint_page, blend_mode); } RenderCommand::Finish { .. } => {} } @@ -403,21 +404,25 @@ where } fn upload_paint_data(&mut self, paint_data: &PaintData) { - let paint_size = paint_data.size; - let paint_texels = &paint_data.texels; - - match self.paint_texture { - Some(ref paint_texture) if self.device.texture_size(paint_texture) == paint_size => {} - _ => { - let texture = self.device.create_texture(TextureFormat::RGBA8, paint_size); - self.paint_texture = Some(texture) - } + for paint_texture in self.paint_textures.drain(..) { + self.texture_cache.release_texture(paint_texture); } - let texels = color::color_slice_to_u8_slice(paint_texels); - self.device.upload_to_texture(self.paint_texture.as_ref().unwrap(), - RectI::new(Vector2I::default(), paint_size), - TextureDataRef::U8(texels)); + for paint_page_data in &paint_data.pages { + let paint_size = paint_page_data.size; + let paint_texels = &paint_page_data.texels; + + let paint_texture = self.texture_cache.create_texture(&mut self.device, + TextureFormat::RGBA8, + paint_size); + + let texels = color::color_slice_to_u8_slice(paint_texels); + self.device.upload_to_texture(&paint_texture, + RectI::new(Vector2I::default(), paint_size), + TextureDataRef::U8(texels)); + + self.paint_textures.push(paint_texture); + } } fn upload_mask_tiles(&mut self, mask_tiles: &[MaskTile], fill_rule: FillRule) { @@ -606,7 +611,7 @@ where self.framebuffer_flags.insert(FramebufferFlags::MUST_PRESERVE_MASK_FRAMEBUFFER_CONTENTS); } - fn draw_alpha_tiles(&mut self, tile_count: u32, blend_mode: BlendMode) { + fn draw_alpha_tiles(&mut self, tile_count: u32, paint_page: u32, blend_mode: BlendMode) { let clear_color = self.clear_color_for_draw_operation(); let mut textures = vec![self.device.framebuffer_texture(&self.mask_framebuffer)]; @@ -627,7 +632,7 @@ where // transparent black paint color doesn't zero out the mask. &self.clear_paint_texture } - _ => self.paint_texture.as_ref().unwrap(), + _ => &self.paint_textures[paint_page as usize], }; textures.push(paint_texture); @@ -658,7 +663,7 @@ where self.preserve_draw_framebuffer(); } - fn draw_solid_tiles(&mut self, tile_count: u32) { + fn draw_solid_tiles(&mut self, tile_count: u32, paint_page: u32) { let clear_color = self.clear_color_for_draw_operation(); let mut textures = vec![]; @@ -669,7 +674,7 @@ where UniformData::Vec2(F32x2::new(TILE_WIDTH as f32, TILE_HEIGHT as f32))), ]; - let paint_texture = self.paint_texture.as_ref().unwrap(); + let paint_texture = &self.paint_textures[paint_page as usize]; textures.push(paint_texture); uniforms.push((&self.solid_tile_program.paint_texture_uniform, UniformData::TextureUnit(0))); @@ -797,9 +802,11 @@ where _ => main_framebuffer_size, }; - let framebuffer = self.framebuffer_cache.create_framebuffer(&mut self.device, - TextureFormat::RGBA8, - framebuffer_size); + let texture = self.texture_cache.create_texture(&mut self.device, + TextureFormat::RGBA8, + framebuffer_size); + let framebuffer = self.device.create_framebuffer(texture); + self.layer_framebuffer_stack.push(LayerFramebufferInfo { framebuffer, effects, @@ -827,7 +834,8 @@ where self.preserve_draw_framebuffer(); - self.framebuffer_cache.release_framebuffer(layer_framebuffer_info.framebuffer); + let texture = self.device.destroy_framebuffer(layer_framebuffer_info.framebuffer); + self.texture_cache.release_texture(texture); } fn composite_layer(&self, @@ -1086,37 +1094,33 @@ bitflags! { } } -struct FramebufferCache where D: Device { - framebuffers: Vec, +struct TextureCache where D: Device { + textures: Vec, } -impl FramebufferCache where D: Device { - fn new() -> FramebufferCache { - FramebufferCache { framebuffers: vec![] } +impl TextureCache where D: Device { + fn new() -> TextureCache { + TextureCache { textures: vec![] } } - fn create_framebuffer(&mut self, device: &mut D, format: TextureFormat, size: Vector2I) - -> D::Framebuffer { - for index in 0..self.framebuffers.len() { - { - let texture = device.framebuffer_texture(&self.framebuffers[index]); - if device.texture_size(texture) != size || - device.texture_format(texture) != format { - continue; - } + fn create_texture(&mut self, device: &mut D, format: TextureFormat, size: Vector2I) + -> D::Texture { + for index in 0..self.textures.len() { + if device.texture_size(&self.textures[index]) != size || + device.texture_format(&self.textures[index]) != format { + continue; } - return self.framebuffers.remove(index); + return self.textures.remove(index); } - let texture = device.create_texture(format, size); - device.create_framebuffer(texture) + device.create_texture(format, size) } - fn release_framebuffer(&mut self, framebuffer: D::Framebuffer) { - if self.framebuffers.len() == FRAMEBUFFER_CACHE_SIZE { - self.framebuffers.pop(); + fn release_texture(&mut self, texture: D::Texture) { + if self.textures.len() == TEXTURE_CACHE_SIZE { + self.textures.pop(); } - self.framebuffers.insert(0, framebuffer); + self.textures.insert(0, texture); } } diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index 949f5935..517b67fc 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -27,17 +27,28 @@ pub enum RenderCommand { RenderMaskTiles { tiles: Vec, fill_rule: FillRule }, PushLayer { effects: Effects }, PopLayer, - DrawAlphaTiles { tiles: Vec, blend_mode: BlendMode }, - DrawSolidTiles(Vec), + DrawAlphaTiles { tiles: Vec, paint_page: u32, blend_mode: BlendMode }, + DrawSolidTiles(SolidTileBatch), Finish { build_time: Duration }, } #[derive(Clone, Debug)] pub struct PaintData { + pub pages: Vec, +} + +#[derive(Clone, Debug)] +pub struct PaintPageData { pub size: Vector2I, pub texels: Vec, } +#[derive(Clone, Debug)] +pub struct SolidTileBatch { + pub vertices: Vec, + pub paint_page: u32, +} + #[derive(Clone, Copy, Debug)] pub struct FillObjectPrimitive { pub px: LineSegmentU4, @@ -121,7 +132,7 @@ impl Debug for RenderCommand { match *self { RenderCommand::Start { .. } => write!(formatter, "Start"), RenderCommand::AddPaintData(ref paint_data) => { - write!(formatter, "AddPaintData({}x{})", paint_data.size.x(), paint_data.size.y()) + write!(formatter, "AddPaintData(x{})", paint_data.pages.len()) } RenderCommand::AddFills(ref fills) => write!(formatter, "AddFills(x{})", fills.len()), RenderCommand::FlushFills => write!(formatter, "FlushFills"), @@ -130,11 +141,18 @@ impl Debug for RenderCommand { } RenderCommand::PushLayer { .. } => write!(formatter, "PushLayer"), RenderCommand::PopLayer => write!(formatter, "PopLayer"), - RenderCommand::DrawAlphaTiles { ref tiles, blend_mode } => { - write!(formatter, "DrawAlphaTiles(x{}, {:?})", tiles.len(), blend_mode) + RenderCommand::DrawAlphaTiles { ref tiles, paint_page, blend_mode } => { + write!(formatter, + "DrawAlphaTiles(x{}, {}, {:?})", + tiles.len(), + paint_page, + blend_mode) } - RenderCommand::DrawSolidTiles(ref tiles) => { - write!(formatter, "DrawSolidTiles(x{})", tiles.len()) + RenderCommand::DrawSolidTiles(ref batch) => { + write!(formatter, + "DrawSolidTiles(x{}, {})", + batch.vertices.len(), + batch.paint_page) } RenderCommand::Finish { .. } => write!(formatter, "Finish"), } diff --git a/renderer/src/paint.rs b/renderer/src/paint.rs index a86be1f9..ef60f205 100644 --- a/renderer/src/paint.rs +++ b/renderer/src/paint.rs @@ -9,7 +9,7 @@ // except according to those terms. use crate::allocator::{TextureAllocator, TextureLocation}; -use crate::gpu_data::PaintData; +use crate::gpu_data::{PaintData, PaintPageData}; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use hashbrown::HashMap; use pathfinder_color::ColorU; @@ -22,8 +22,6 @@ use pathfinder_geometry::vector::{Vector2F, Vector2I}; use pathfinder_simd::default::F32x4; use std::fmt::{self, Debug, Formatter}; -const INITIAL_PAINT_TEXTURE_LENGTH: u32 = 1024; - // The size of a gradient tile. // // TODO(pcwalton): Choose this size dynamically! @@ -174,6 +172,8 @@ pub struct PaintInfo { // TODO(pcwalton): Add clamp/repeat options. #[derive(Debug)] pub struct PaintMetadata { + /// The index of the texture page. + pub tex_page: u32, /// The rectangle within the texture atlas. pub tex_rect: RectI, /// The transform to apply to screen coordinates to translate them into UVs. @@ -196,7 +196,7 @@ impl Palette { } pub fn build_paint_info(&self, view_box_size: Vector2I) -> PaintInfo { - let mut allocator = TextureAllocator::new(INITIAL_PAINT_TEXTURE_LENGTH); + let mut allocator = TextureAllocator::new(); let mut metadata = vec![]; // Assign paint locations. @@ -210,15 +210,14 @@ impl Palette { // 2. Choose an optimal size for the gradient that minimizes memory usage while // retaining quality. allocator.allocate(Vector2I::splat(GRADIENT_TILE_LENGTH as i32)) - .expect("Failed to allocate space for the gradient!") } Paint::Pattern(ref pattern) => { allocator.allocate(pattern.image.size()) - .expect("Failed to allocate space for the image!") } }; metadata.push(PaintMetadata { + tex_page: tex_location.page, tex_rect: tex_location.rect, tex_transform: Transform2F::default(), is_opaque: paint.is_opaque(), @@ -226,25 +225,23 @@ impl Palette { } // Calculate texture transforms. - let texture_length = allocator.size(); - let texture_scale = allocator.scale(); for (paint, metadata) in self.paints.iter().zip(metadata.iter_mut()) { + let texture_scale = allocator.page_scale(metadata.tex_page); metadata.tex_transform = match paint { Paint::Color(_) => { - let vector = rect_to_inset_uv(metadata.tex_rect, texture_length).origin(); + let vector = rect_to_inset_uv(metadata.tex_rect, texture_scale).origin(); Transform2F { matrix: Matrix2x2F(F32x4::default()), vector } } Paint::Gradient(_) => { - let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_length).origin(); - let gradient_tile_scale = GRADIENT_TILE_LENGTH as f32 * texture_scale; + let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_scale).origin(); + let gradient_tile_scale = texture_scale.scale(GRADIENT_TILE_LENGTH as f32); Transform2F::from_translation(texture_origin_uv) * - Transform2F::from_scale(Vector2F::splat(gradient_tile_scale) / - view_box_size.to_f32()) + Transform2F::from_scale(gradient_tile_scale / view_box_size.to_f32()) } Paint::Pattern(_) => { - let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_length).origin(); + let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_scale).origin(); Transform2F::from_translation(texture_origin_uv) * - Transform2F::from_uniform_scale(texture_scale) + Transform2F::from_scale(texture_scale) } } } @@ -252,28 +249,40 @@ impl Palette { // Render the actual texels. // // TODO(pcwalton): This is slow. Do more on GPU. - let texture_area = texture_length as usize * texture_length as usize; - let mut texels = vec![ColorU::default(); texture_area]; + let mut paint_data = PaintData { pages: vec![] }; + for page_index in 0..allocator.page_count() { + let page_size = allocator.page_size(page_index); + let page_area = page_size.x() as usize * page_size.y() as usize; + let texels = vec![ColorU::default(); page_area]; + paint_data.pages.push(PaintPageData { size: page_size, texels }); + } + for (paint, metadata) in self.paints.iter().zip(metadata.iter()) { + let tex_page = metadata.tex_page; + let PaintPageData { + size: page_size, + ref mut texels, + } = paint_data.pages[tex_page as usize]; + let page_scale = allocator.page_scale(tex_page); match paint { Paint::Color(color) => { - put_pixel(metadata.tex_rect.origin(), *color, &mut texels, texture_length); + put_pixel(metadata.tex_rect.origin(), *color, texels, page_size); } Paint::Gradient(ref gradient) => { self.render_gradient(gradient, metadata.tex_rect, &metadata.tex_transform, - &mut texels, - texture_length); + texels, + page_size, + page_scale); } Paint::Pattern(ref pattern) => { - self.render_pattern(pattern, metadata.tex_rect, &mut texels, texture_length); + self.render_pattern(pattern, metadata.tex_rect, texels, page_size); } } } - let size = Vector2I::splat(texture_length as i32); - return PaintInfo { data: PaintData { size, texels }, metadata }; + return PaintInfo { data: paint_data, metadata }; } // TODO(pcwalton): This is slow. Do on GPU instead. @@ -282,7 +291,8 @@ impl Palette { tex_rect: RectI, tex_transform: &Transform2F, texels: &mut [ColorU], - texture_length: u32) { + tex_size: Vector2I, + tex_scale: Vector2F) { match *gradient.geometry() { GradientGeometry::Linear(gradient_line) => { // FIXME(pcwalton): Paint transparent if gradient line has zero size, per spec. @@ -294,13 +304,12 @@ impl Palette { for y in 0..(GRADIENT_TILE_LENGTH as i32) { for x in 0..(GRADIENT_TILE_LENGTH as i32) { let point = tex_rect.origin() + Vector2I::new(x, y); - let vector = point.to_f32().scale(1.0 / texture_length as f32) - - gradient_line.from(); + let vector = point.to_f32().scale_xy(tex_scale) - gradient_line.from(); let mut t = gradient_line.vector().projection_coefficient(vector); t = util::clamp(t, 0.0, 1.0); - put_pixel(point, gradient.sample(t), texels, texture_length); + put_pixel(point, gradient.sample(t), texels, tex_size); } } } @@ -319,13 +328,12 @@ impl Palette { for y in 0..(GRADIENT_TILE_LENGTH as i32) { for x in 0..(GRADIENT_TILE_LENGTH as i32) { let point = tex_rect.origin() + Vector2I::new(x, y); - let vector = tex_transform_inv * - point.to_f32().scale(1.0 / texture_length as f32); + let vector = tex_transform_inv * point.to_f32().scale_xy(tex_scale); let t = util::clamp((vector - center).length(), start_radius, end_radius) / (end_radius - start_radius); - put_pixel(point, gradient.sample(t), texels, texture_length); + put_pixel(point, gradient.sample(t), texels, tex_size); } } } @@ -336,11 +344,11 @@ impl Palette { pattern: &Pattern, tex_rect: RectI, texels: &mut [ColorU], - texture_length: u32) { + tex_size: Vector2I) { let image_size = pattern.image.size(); for y in 0..image_size.y() { let dest_origin = tex_rect.origin() + Vector2I::new(0, y); - let dest_start_index = paint_texel_index(dest_origin, texture_length); + let dest_start_index = paint_texel_index(dest_origin, tex_size); let src_start_index = y as usize * image_size.x() as usize; let dest_end_index = dest_start_index + image_size.x() as usize; let src_end_index = src_start_index + image_size.x() as usize; @@ -359,20 +367,20 @@ impl PaintMetadata { } } -fn paint_texel_index(position: Vector2I, texture_length: u32) -> usize { - position.y() as usize * texture_length as usize + position.x() as usize +fn paint_texel_index(position: Vector2I, tex_size: Vector2I) -> usize { + position.y() as usize * tex_size.x() as usize + position.x() as usize } -fn put_pixel(position: Vector2I, color: ColorU, texels: &mut [ColorU], texture_length: u32) { - texels[paint_texel_index(position, texture_length)] = color +fn put_pixel(position: Vector2I, color: ColorU, texels: &mut [ColorU], tex_size: Vector2I) { + texels[paint_texel_index(position, tex_size)] = color } -fn rect_to_uv(rect: RectI, texture_length: u32) -> RectF { - rect.to_f32().scale(1.0 / texture_length as f32) +fn rect_to_uv(rect: RectI, texture_scale: Vector2F) -> RectF { + rect.to_f32().scale_xy(texture_scale) } -fn rect_to_inset_uv(rect: RectI, texture_length: u32) -> RectF { - rect_to_uv(rect, texture_length).contract(Vector2F::splat(0.5 / texture_length as f32)) +fn rect_to_inset_uv(rect: RectI, texture_scale: Vector2F) -> RectF { + rect_to_uv(rect, texture_scale).contract(texture_scale.scale(0.5)) } // Solid color allocation @@ -393,8 +401,7 @@ impl SolidColorTileBuilder { if self.0.is_none() { // TODO(pcwalton): Handle allocation failure gracefully! self.0 = Some(SolidColorTileBuilderData { - tile_location: allocator.allocate(Vector2I::splat(SOLID_COLOR_TILE_LENGTH as i32)) - .expect("Failed to allocate a solid color tile!"), + tile_location: allocator.allocate(Vector2I::splat(SOLID_COLOR_TILE_LENGTH as i32)), next_index: 0, }); } @@ -405,6 +412,7 @@ impl SolidColorTileBuilder { let subtile_origin = Vector2I::new((data.next_index % SOLID_COLOR_TILE_LENGTH) as i32, (data.next_index / SOLID_COLOR_TILE_LENGTH) as i32); location = TextureLocation { + page: data.tile_location.page, rect: RectI::new(data.tile_location.rect.origin() + subtile_origin, Vector2I::splat(1)), }; diff --git a/renderer/src/z_buffer.rs b/renderer/src/z_buffer.rs index c4e6472a..66168dd9 100644 --- a/renderer/src/z_buffer.rs +++ b/renderer/src/z_buffer.rs @@ -11,7 +11,7 @@ //! Software occlusion culling. use crate::builder::SolidTile; -use crate::gpu_data::SolidTileVertex; +use crate::gpu_data::{SolidTileBatch, SolidTileVertex}; use crate::paint::PaintMetadata; use crate::scene::DrawPath; use crate::tile_map::DenseTileMap; @@ -23,6 +23,10 @@ pub(crate) struct ZBuffer { buffer: DenseTileMap, } +pub(crate) struct SolidTiles { + pub(crate) batches: Vec, +} + impl ZBuffer { pub(crate) fn new(view_box: RectF) -> ZBuffer { let tile_rect = tiles::round_rect_out_to_tile_bounds(view_box); @@ -45,8 +49,9 @@ impl ZBuffer { } pub(crate) fn build_solid_tiles(&self, paths: &[DrawPath], paint_metadata: &[PaintMetadata]) - -> Vec { - let mut solid_tiles = vec![]; + -> SolidTiles { + let mut solid_tiles = SolidTiles { batches: vec![] }; + for tile_index in 0..self.buffer.data.len() { let depth = self.buffer.data[tile_index]; if depth == 0 { @@ -62,7 +67,20 @@ impl ZBuffer { let tile_position = tile_coords + self.buffer.rect.origin(); let object_index = object_index as u16; - solid_tiles.extend_from_slice(&[ + // Create a batch if necessary. + match solid_tiles.batches.last() { + Some(ref batch) if batch.paint_page == paint_metadata.tex_page => {} + _ => { + // Batch break. + solid_tiles.batches.push(SolidTileBatch { + paint_page: paint_metadata.tex_page, + vertices: vec![], + }); + } + } + + let batch = solid_tiles.batches.last_mut().unwrap(); + batch.vertices.extend_from_slice(&[ SolidTileVertex::new(tile_position, object_index, paint_metadata), SolidTileVertex::new(tile_position + Vector2I::new(1, 0), object_index,