From a67b4dd5b75f310d68033c40321489c047687ab7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 15 May 2020 19:10:12 -0700 Subject: [PATCH] Replace the tiling algorithm with the one from "Random Access Vector Graphics". This is a significant improvement in CPU time, as well as an overall simplification. --- renderer/src/builder.rs | 131 +++------- renderer/src/lib.rs | 1 + renderer/src/tiler.rs | 261 ++++++++++++++++++++ renderer/src/tiles.rs | 518 +--------------------------------------- 4 files changed, 299 insertions(+), 612 deletions(-) create mode 100644 renderer/src/tiler.rs diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index 344438e8..e41ffe7a 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -20,16 +20,16 @@ use crate::options::{PreparedBuildOptions, PreparedRenderTransform, RenderComman use crate::paint::{PaintInfo, PaintMetadata}; use crate::scene::{DisplayItem, Scene}; use crate::tile_map::DenseTileMap; -use crate::tiles::{self, DrawTilingPathInfo, PackedTile, TILE_HEIGHT, TILE_WIDTH}; -use crate::tiles::{Tiler, TilingPathInfo}; +use crate::tiler::Tiler; +use crate::tiles::{self, DrawTilingPathInfo, PackedTile, TILE_HEIGHT, TILE_WIDTH, TilingPathInfo}; use crate::z_buffer::{DepthMetadata, ZBuffer}; use pathfinder_content::effects::{BlendMode, Filter}; use pathfinder_content::fill::FillRule; use pathfinder_content::render_target::RenderTargetId; use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmentU8}; -use pathfinder_geometry::rect::{RectF, RectI}; +use pathfinder_geometry::rect::RectF; use pathfinder_geometry::transform2d::Transform2F; -use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i}; +use pathfinder_geometry::vector::{Vector2I, vec2i}; use pathfinder_gpu::TextureSamplingFlags; use pathfinder_simd::default::{F32x4, I32x4}; use std::sync::atomic::AtomicUsize; @@ -49,6 +49,8 @@ pub(crate) struct SceneBuilder<'a, 'b> { #[derive(Debug)] pub(crate) struct ObjectBuilder { pub built_path: BuiltPath, + /// During tiling, this stores the sum of backdrops for tile columns above the viewport. + pub current_backdrops: Vec, pub fills: Vec, pub bounds: RectF, } @@ -593,24 +595,15 @@ impl ObjectBuilder { fill_rule: FillRule, tiling_path_info: &TilingPathInfo) -> ObjectBuilder { - ObjectBuilder { - built_path: BuiltPath::new(path_bounds, view_box_bounds, fill_rule, tiling_path_info), - bounds: path_bounds, - fills: vec![], - } + let built_path = BuiltPath::new(path_bounds, view_box_bounds, fill_rule, tiling_path_info); + let current_backdrops = vec![0; built_path.tiles.rect.width() as usize]; + ObjectBuilder { built_path, bounds: path_bounds, current_backdrops, fills: vec![] } } - #[inline] - pub(crate) fn tile_rect(&self) -> RectI { - self.built_path.tiles.rect - } - - fn add_fill( - &mut self, - scene_builder: &SceneBuilder, - segment: LineSegment2F, - tile_coords: Vector2I, - ) { + pub(crate) fn add_fill(&mut self, + scene_builder: &SceneBuilder, + segment: LineSegment2F, + tile_coords: Vector2I) { debug!("add_fill({:?} ({:?}))", segment, tile_coords); // Ensure this fill is in bounds. If not, cull it. @@ -676,87 +669,6 @@ impl ObjectBuilder { alpha_tile_id } - pub(crate) fn add_active_fill( - &mut self, - scene_builder: &SceneBuilder, - left: f32, - right: f32, - mut winding: i32, - tile_coords: Vector2I, - ) { - let tile_origin_y = (tile_coords.y() * TILE_HEIGHT as i32) as f32; - let left = vec2f(left, tile_origin_y); - let right = vec2f(right, tile_origin_y); - - let segment = if winding < 0 { - LineSegment2F::new(left, right) - } else { - LineSegment2F::new(right, left) - }; - - debug!( - "... emitting active fill {} -> {} winding {} @ tile {:?}", - left.x(), - right.x(), - winding, - tile_coords - ); - - while winding != 0 { - self.add_fill(scene_builder, segment, tile_coords); - if winding < 0 { - winding += 1 - } else { - winding -= 1 - } - } - } - - pub(crate) fn generate_fill_primitives_for_line( - &mut self, - scene_builder: &SceneBuilder, - mut segment: LineSegment2F, - tile_y: i32, - ) { - debug!( - "... generate_fill_primitives_for_line(): segment={:?} tile_y={} ({}-{})", - segment, - tile_y, - tile_y as f32 * TILE_HEIGHT as f32, - (tile_y + 1) as f32 * TILE_HEIGHT as f32 - ); - - let winding = segment.from_x() > segment.to_x(); - let (segment_left, segment_right) = if !winding { - (segment.from_x(), segment.to_x()) - } else { - (segment.to_x(), segment.from_x()) - }; - - let mut subsegment_x = (segment_left as i32 & !(TILE_WIDTH as i32 - 1)) as f32; - while subsegment_x < segment_right { - let (mut fill_from, mut fill_to) = (segment.from(), segment.to()); - let subsegment_x_next = subsegment_x + TILE_WIDTH as f32; - if subsegment_x_next < segment_right { - let x = subsegment_x_next; - let point = Vector2F::new(x, segment.solve_y_for_x(x)); - if !winding { - fill_to = point; - segment = LineSegment2F::new(point, segment.to()); - } else { - fill_from = point; - segment = LineSegment2F::new(segment.from(), point); - } - } - - let fill_segment = LineSegment2F::new(fill_from, fill_to); - let fill_tile_coords = vec2i(subsegment_x as i32 / TILE_WIDTH as i32, tile_y); - self.add_fill(scene_builder, fill_segment, fill_tile_coords); - - subsegment_x = subsegment_x_next; - } - } - #[inline] pub(crate) fn tile_coords_to_local_index(&self, coords: Vector2I) -> Option { self.built_path.tiles.coords_to_index(coords).map(|index| index as u32) @@ -766,6 +678,23 @@ impl ObjectBuilder { pub(crate) fn local_tile_index_to_coords(&self, tile_index: u32) -> Vector2I { self.built_path.tiles.index_to_coords(tile_index as usize) } + + #[inline] + pub(crate) fn adjust_alpha_tile_backdrop(&mut self, tile_coords: Vector2I, delta: i8) { + let tile_offset = tile_coords - self.built_path.tiles.rect.origin(); + if tile_offset.x() < 0 || tile_offset.x() >= self.built_path.tiles.rect.width() || + tile_offset.y() >= self.built_path.tiles.rect.height() { + return; + } + + if tile_offset.y() < 0 { + self.current_backdrops[tile_offset.x() as usize] += delta; + return; + } + + let local_tile_index = self.built_path.tiles.coords_to_index_unchecked(tile_coords); + self.built_path.tiles.data[local_tile_index].backdrop += delta; + } } impl<'a> PackedTile<'a> { diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index b12d8fbc..083f9ba3 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -25,5 +25,6 @@ pub mod scene; mod allocator; mod builder; mod tile_map; +mod tiler; mod tiles; mod z_buffer; diff --git a/renderer/src/tiler.rs b/renderer/src/tiler.rs new file mode 100644 index 00000000..9df4f93f --- /dev/null +++ b/renderer/src/tiler.rs @@ -0,0 +1,261 @@ +// pathfinder/renderer/src/tiler.rs +// +// Copyright © 2020 The Pathfinder Project Developers. +// +// 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. + +//! Implements the fast lattice-clipping algorithm from Nehab and Hoppe, "Random-Access Rendering +//! of General Vector Graphics" 2006. + +use crate::builder::{ObjectBuilder, Occluder, SceneBuilder, SolidTiles}; +use crate::tiles::{PackedTile, TILE_HEIGHT, TILE_WIDTH, TileType, TilingPathInfo}; +use pathfinder_content::fill::FillRule; +use pathfinder_content::outline::{ContourIterFlags, Outline}; +use pathfinder_content::segment::Segment; +use pathfinder_geometry::line_segment::LineSegment2F; +use pathfinder_geometry::rect::RectF; +use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i}; + +const FLATTENING_TOLERANCE: f32 = 0.25; + +pub(crate) struct Tiler<'a, 'b> { + scene_builder: &'a SceneBuilder<'b, 'a>, + pub(crate) object_builder: ObjectBuilder, + outline: &'a Outline, + path_info: TilingPathInfo<'a>, +} + +impl<'a, 'b> Tiler<'a, 'b> { + pub(crate) fn new(scene_builder: &'a SceneBuilder<'b, 'a>, + outline: &'a Outline, + fill_rule: FillRule, + view_box: RectF, + path_info: TilingPathInfo<'a>) + -> Tiler<'a, 'b> { + let bounds = outline.bounds().intersection(view_box).unwrap_or(RectF::default()); + let object_builder = ObjectBuilder::new(bounds, view_box, fill_rule, &path_info); + Tiler { scene_builder, object_builder, outline, path_info } + } + + pub(crate) fn generate_tiles(&mut self) { + for contour in self.outline.contours() { + for segment in contour.iter(ContourIterFlags::empty()) { + process_segment(&segment, self.scene_builder, &mut self.object_builder); + } + } + + self.propagate_backdrops(); + self.pack_and_cull(); + } + + fn propagate_backdrops(&mut self) { + let tiles_across = self.object_builder.built_path.tiles.rect.width() as usize; + for (draw_tile_index, draw_tile) in self.object_builder + .built_path + .tiles + .data + .iter_mut() + .enumerate() { + let column = draw_tile_index % tiles_across; + let delta = draw_tile.backdrop; + draw_tile.backdrop = self.object_builder.current_backdrops[column]; + self.object_builder.current_backdrops[column] += delta; + } + } + + fn pack_and_cull(&mut self) { + let draw_tiling_path_info = match self.path_info { + TilingPathInfo::Clip => return, + TilingPathInfo::Draw(draw_tiling_path_info) => draw_tiling_path_info, + }; + + let blend_mode_is_destructive = draw_tiling_path_info.blend_mode.is_destructive(); + + for (draw_tile_index, draw_tile) in self.object_builder + .built_path + .tiles + .data + .iter() + .enumerate() { + let packed_tile = PackedTile::new(draw_tile_index as u32, + draw_tile, + &draw_tiling_path_info, + &self.object_builder); + + match packed_tile.tile_type { + TileType::Solid => { + match self.object_builder.built_path.solid_tiles { + SolidTiles::Occluders(ref mut occluders) => { + occluders.push(Occluder::new(packed_tile.tile_coords)); + } + SolidTiles::Regular(ref mut solid_tiles) => { + packed_tile.add_to(solid_tiles, + &mut self.object_builder.built_path.clip_tiles, + &draw_tiling_path_info, + &self.scene_builder); + } + } + } + TileType::SingleMask => { + debug_assert_ne!(packed_tile.draw_tile.alpha_tile_id.page(), !0); + packed_tile.add_to(&mut self.object_builder.built_path.single_mask_tiles, + &mut self.object_builder.built_path.clip_tiles, + &draw_tiling_path_info, + &self.scene_builder); + } + TileType::Empty if blend_mode_is_destructive => { + packed_tile.add_to(&mut self.object_builder.built_path.empty_tiles, + &mut self.object_builder.built_path.clip_tiles, + &draw_tiling_path_info, + &self.scene_builder); + } + TileType::Empty => { + // Just cull. + } + } + } + } +} + +fn process_segment(segment: &Segment, + scene_builder: &SceneBuilder, + object_builder: &mut ObjectBuilder) { + // TODO(pcwalton): Stop degree elevating. + if segment.is_quadratic() { + let cubic = segment.to_cubic(); + return process_segment(&cubic, scene_builder, object_builder); + } + + if segment.is_line() || + (segment.is_cubic() && segment.as_cubic_segment().is_flat(FLATTENING_TOLERANCE)) { + return process_line_segment(segment.baseline, scene_builder, object_builder); + } + + // TODO(pcwalton): Use a smarter flattening algorithm. + let (prev, next) = segment.split(0.5); + process_segment(&prev, scene_builder, object_builder); + process_segment(&next, scene_builder, object_builder); +} + +// This is the meat of the technique. It implements the fast lattice-clipping algorithm from +// Nehab and Hoppe, "Random-Access Rendering of General Vector Graphics" 2006. +// +// The algorithm to step through tiles is Amanatides and Woo, "A Fast Voxel Traversal Algorithm for +// Ray Tracing" 1987: http://www.cse.yorku.ca/~amana/research/grid.pdf +fn process_line_segment(line_segment: LineSegment2F, + scene_builder: &SceneBuilder, + object_builder: &mut ObjectBuilder) { + let tile_size = vec2f(TILE_WIDTH as f32, TILE_HEIGHT as f32); + let tile_size_recip = Vector2F::splat(1.0) / tile_size; + + let tile_line_segment = + (line_segment.0 * tile_size_recip.0.concat_xy_xy(tile_size_recip.0)).floor().to_i32x4(); + let from_tile_coords = Vector2I(tile_line_segment.xy()); + let to_tile_coords = Vector2I(tile_line_segment.zw()); + + let vector = line_segment.vector(); + let step = vec2f(vector.x().signum(), vector.y().signum()).to_i32(); + + let first_tile_crossing = + (from_tile_coords + vec2i(if step.x() <= 0 { 0 } else { 1 }, + if step.y() <= 0 { 0 } else { 1 })).to_f32() * tile_size; + + let mut t_max = (first_tile_crossing - line_segment.from()) / vector; + let t_delta = (tile_size / vector).abs(); + + let (mut current_position, mut tile_coords) = (line_segment.from(), from_tile_coords); + let mut last_step_direction = None; + let mut iteration = 0; + + loop { + // Quick check to catch missing the end tile. + debug_assert!(iteration < MAX_ITERATIONS); + + let next_step_direction = if t_max.x() < t_max.y() { + StepDirection::X + } else if t_max.x() > t_max.y() { + StepDirection::Y + } else { + // This should only happen if the line's destination is precisely on a corner point + // between tiles: + // + // +-----+--O--+ + // | | / | + // | |/ | + // +-----O-----+ + // | | end | + // | | tile| + // +-----+-----+ + // + // In that case we just need to step in the positive direction to move to the lower + // right tile. + if step.x() > 0 { StepDirection::X } else { StepDirection::Y } + }; + + let next_t = + (if next_step_direction == StepDirection::X { t_max.x() } else { t_max.y() }).min(1.0); + + // If we've reached the end tile, don't step at all. + let next_step_direction = if tile_coords == to_tile_coords { + None + } else { + Some(next_step_direction) + }; + + let next_position = line_segment.sample(next_t); + let clipped_line_segment = LineSegment2F::new(current_position, next_position); + object_builder.add_fill(scene_builder, clipped_line_segment, tile_coords); + + // Add extra fills if necessary. + if step.y() < 0 && next_step_direction == Some(StepDirection::Y) { + // Leaves through top boundary. + let auxiliary_segment = LineSegment2F::new(clipped_line_segment.to(), + tile_coords.to_f32() * tile_size); + object_builder.add_fill(scene_builder, auxiliary_segment, tile_coords); + } else if step.y() > 0 && last_step_direction == Some(StepDirection::Y) { + // Enters through top boundary. + let auxiliary_segment = LineSegment2F::new(tile_coords.to_f32() * tile_size, + clipped_line_segment.from()); + object_builder.add_fill(scene_builder, auxiliary_segment, tile_coords); + } + + // Adjust backdrop if necessary. + if step.x() < 0 && last_step_direction == Some(StepDirection::X) { + // Entered through right boundary. + object_builder.adjust_alpha_tile_backdrop(tile_coords, 1); + } else if step.x() > 0 && next_step_direction == Some(StepDirection::X) { + // Leaving through right boundary. + object_builder.adjust_alpha_tile_backdrop(tile_coords, -1); + } + + // Take a step. + match next_step_direction { + None => break, + Some(StepDirection::X) => { + t_max += vec2f(t_delta.x(), 0.0); + tile_coords += vec2i(step.x(), 0); + } + Some(StepDirection::Y) => { + t_max += vec2f(0.0, t_delta.y()); + tile_coords += vec2i(0, step.y()); + } + } + + current_position = next_position; + last_step_direction = next_step_direction; + + iteration += 1; + } + + const MAX_ITERATIONS: u32 = 1024; +} + +#[derive(Clone, Copy, PartialEq, Debug)] +enum StepDirection { + X, + Y, +} diff --git a/renderer/src/tiles.rs b/renderer/src/tiles.rs index 86fc7faa..4b2777b6 100644 --- a/renderer/src/tiles.rs +++ b/renderer/src/tiles.rs @@ -8,36 +8,17 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::builder::{BuiltPath, ObjectBuilder, Occluder, SceneBuilder, SolidTiles}; +use crate::builder::{BuiltPath, ObjectBuilder}; use crate::gpu_data::{AlphaTileId, TileObjectPrimitive}; use crate::paint::{PaintId, PaintMetadata}; use pathfinder_content::effects::BlendMode; use pathfinder_content::fill::FillRule; -use pathfinder_content::outline::{Contour, Outline, PointIndex}; -use pathfinder_content::segment::Segment; -use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::{RectF, RectI}; -use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i}; -use std::mem; - -// TODO(pcwalton): Make this configurable. -const FLATTENING_TOLERANCE: f32 = 0.1; +use pathfinder_geometry::vector::{Vector2I, vec2f}; pub const TILE_WIDTH: u32 = 16; pub const TILE_HEIGHT: u32 = 16; -pub(crate) struct Tiler<'a, 'b> { - scene_builder: &'a SceneBuilder<'b, 'a>, - pub(crate) object_builder: ObjectBuilder, - outline: &'a Outline, - path_info: TilingPathInfo<'a>, - - point_queue: Vec, - next_point_queue: Vec, - active_edges: Vec, - old_active_edges: Vec, -} - #[derive(Clone, Copy)] pub(crate) enum TilingPathInfo<'a> { Clip, @@ -53,339 +34,6 @@ pub(crate) struct DrawTilingPathInfo<'a> { pub(crate) fill_rule: FillRule, } -impl<'a, 'b> Tiler<'a, 'b> { - #[allow(clippy::or_fun_call)] - pub(crate) fn new( - scene_builder: &'a SceneBuilder<'b, 'a>, - outline: &'a Outline, - fill_rule: FillRule, - view_box: RectF, - path_info: TilingPathInfo<'a>, - ) -> Tiler<'a, 'b> { - let bounds = outline - .bounds() - .intersection(view_box) - .unwrap_or(RectF::default()); - let object_builder = ObjectBuilder::new(bounds, view_box, fill_rule, &path_info); - - Tiler { - scene_builder, - object_builder, - outline, - path_info, - - point_queue: Vec::new(), - next_point_queue: Vec::new(), - active_edges: Vec::new(), - old_active_edges: Vec::new(), - } - } - - pub(crate) fn generate_tiles(&mut self) { - // Initialize the point queue. - self.init_point_queue(); - - // Reset active edges. - self.active_edges.clear(); - self.old_active_edges.clear(); - - // Generate strips. - let tile_rect = self.object_builder.tile_rect(); - for strip_origin_y in tile_rect.min_y()..tile_rect.max_y() { - self.generate_strip(strip_origin_y); - } - - // Pack and cull. - self.pack_and_cull(); - - // Done! - debug!("{:#?}", self.object_builder.built_path); - } - - fn generate_strip(&mut self, strip_origin_y: i32) { - // Process old active edges. - self.process_old_active_edges(strip_origin_y); - - // Add new active edges. - let strip_max_y = ((strip_origin_y + 1) * TILE_HEIGHT as i32) as f32; - while let Some(queued_endpoint) = self.point_queue.pop() { - // Note that this test must be `>`, not `>=`, in order to make sure we don't miss - // active edges that lie precisely on the tile strip boundary. - if queued_endpoint.y > strip_max_y { - self.next_point_queue.push(queued_endpoint); - continue; - } - - self.add_new_active_edge(strip_origin_y, queued_endpoint); - } - - mem::swap(&mut self.point_queue, &mut self.next_point_queue); - } - - fn pack_and_cull(&mut self) { - let draw_tiling_path_info = match self.path_info { - TilingPathInfo::Clip => return, - TilingPathInfo::Draw(draw_tiling_path_info) => draw_tiling_path_info, - }; - - let blend_mode_is_destructive = draw_tiling_path_info.blend_mode.is_destructive(); - - for (draw_tile_index, draw_tile) in self.object_builder - .built_path - .tiles - .data - .iter() - .enumerate() { - let packed_tile = PackedTile::new(draw_tile_index as u32, - draw_tile, - &draw_tiling_path_info, - &self.object_builder); - - match packed_tile.tile_type { - TileType::Solid => { - match self.object_builder.built_path.solid_tiles { - SolidTiles::Occluders(ref mut occluders) => { - occluders.push(Occluder::new(packed_tile.tile_coords)); - } - SolidTiles::Regular(ref mut solid_tiles) => { - packed_tile.add_to(solid_tiles, - &mut self.object_builder.built_path.clip_tiles, - &draw_tiling_path_info, - &self.scene_builder); - } - } - } - TileType::SingleMask => { - debug_assert_ne!(packed_tile.draw_tile.alpha_tile_id.page(), !0); - packed_tile.add_to(&mut self.object_builder.built_path.single_mask_tiles, - &mut self.object_builder.built_path.clip_tiles, - &draw_tiling_path_info, - &self.scene_builder); - } - TileType::Empty if blend_mode_is_destructive => { - packed_tile.add_to(&mut self.object_builder.built_path.empty_tiles, - &mut self.object_builder.built_path.clip_tiles, - &draw_tiling_path_info, - &self.scene_builder); - } - TileType::Empty => { - // Just cull. - } - } - } - } - - fn process_old_active_edges(&mut self, tile_y: i32) { - let mut current_tile_x = self.object_builder.tile_rect().min_x(); - let mut current_subtile_x = 0.0; - let mut current_winding = 0; - - debug_assert!(self.old_active_edges.is_empty()); - mem::swap(&mut self.old_active_edges, &mut self.active_edges); - - // FIXME(pcwalton): Yuck. - let mut last_segment_x = -9999.0; - - let tile_top = (tile_y * TILE_HEIGHT as i32) as f32; - - debug!("---------- tile y {}({}) ----------", tile_y, tile_top); - debug!("old active edges: {:#?}", self.old_active_edges); - - self.old_active_edges.sort_unstable_by(|a, b| a.crossing.x().partial_cmp(&b.crossing.x()).unwrap()); - for mut active_edge in self.old_active_edges.drain(..) { - // Determine x-intercept and winding. - let segment_x = active_edge.crossing.x(); - let edge_winding = - if active_edge.segment.baseline.from_y() < active_edge.segment.baseline.to_y() { - 1 - } else { - -1 - }; - - debug!( - "tile Y {}({}): segment_x={} edge_winding={} current_tile_x={} \ - current_subtile_x={} current_winding={}", - tile_y, - tile_top, - segment_x, - edge_winding, - current_tile_x, - current_subtile_x, - current_winding - ); - debug!( - "... segment={:#?} crossing={:?}", - active_edge.segment, active_edge.crossing - ); - - // FIXME(pcwalton): Remove this debug code! - debug_assert!(segment_x >= last_segment_x); - last_segment_x = segment_x; - - // Do initial subtile fill, if necessary. - let segment_tile_x = f32::floor(segment_x) as i32 / TILE_WIDTH as i32; - if current_tile_x < segment_tile_x && current_subtile_x > 0.0 { - let current_x = - (current_tile_x * TILE_WIDTH as i32) as f32 + current_subtile_x; - let tile_right_x = ((current_tile_x + 1) * TILE_WIDTH as i32) as f32; - let current_tile_coords = vec2i(current_tile_x, tile_y); - self.object_builder.add_active_fill( - self.scene_builder, - current_x, - tile_right_x, - current_winding, - current_tile_coords, - ); - current_tile_x += 1; - current_subtile_x = 0.0; - } - - // Move over to the correct tile, filling in as we go. - while current_tile_x < segment_tile_x { - debug!( - "... emitting backdrop {} @ tile {}", - current_winding, current_tile_x - ); - let current_tile_coords = vec2i(current_tile_x, tile_y); - if let Some(tile_index) = self.object_builder - .tile_coords_to_local_index(current_tile_coords) { - // FIXME(pcwalton): Handle winding overflow. - self.object_builder.built_path.tiles.data[tile_index as usize].backdrop = - current_winding as i8; - } - - current_tile_x += 1; - current_subtile_x = 0.0; - } - - // Do final subtile fill, if necessary. - debug_assert_eq!(current_tile_x, segment_tile_x); - let segment_subtile_x = - segment_x - (current_tile_x * TILE_WIDTH as i32) as f32; - if segment_subtile_x > current_subtile_x { - let current_x = - (current_tile_x * TILE_WIDTH as i32) as f32 + current_subtile_x; - let current_tile_coords = vec2i(current_tile_x, tile_y); - self.object_builder.add_active_fill( - self.scene_builder, - current_x, - segment_x, - current_winding, - current_tile_coords, - ); - current_subtile_x = segment_subtile_x; - } - - // Update winding. - current_winding += edge_winding; - - // Process the edge. - debug!("about to process existing active edge {:#?}", active_edge); - debug_assert!(f32::abs(active_edge.crossing.y() - tile_top) < 0.1); - active_edge.process(self.scene_builder, &mut self.object_builder, tile_y); - if !active_edge.segment.is_none() { - self.active_edges.push(active_edge); - } - } - } - - fn add_new_active_edge(&mut self, tile_y: i32, queued_endpoint: QueuedEndpoint) { - let outline = &self.outline; - let point_index = queued_endpoint.point_index; - - let contour = &outline.contours()[point_index.contour() as usize]; - - // TODO(pcwalton): Could use a bitset of processed edges… - let prev_endpoint_index = contour.prev_endpoint_index_of(point_index.point()); - let next_endpoint_index = contour.next_endpoint_index_of(point_index.point()); - - debug!( - "adding new active edge, tile_y={} point_index={} prev={} next={} pos={:?} \ - prevpos={:?} nextpos={:?}", - tile_y, - point_index.point(), - prev_endpoint_index, - next_endpoint_index, - contour.position_of(point_index.point()), - contour.position_of(prev_endpoint_index), - contour.position_of(next_endpoint_index) - ); - - if contour.point_is_logically_above(point_index.point(), prev_endpoint_index) { - debug!("... adding prev endpoint"); - - process_active_segment( - contour, - prev_endpoint_index, - &mut self.active_edges, - self.scene_builder, - &mut self.object_builder, - tile_y, - ); - - self.point_queue.push(QueuedEndpoint { - point_index: PointIndex::new(point_index.contour(), prev_endpoint_index), - y: contour.position_of(prev_endpoint_index).y(), - }); - - debug!("... done adding prev endpoint"); - } - - if contour.point_is_logically_above(point_index.point(), next_endpoint_index) { - debug!( - "... adding next endpoint {} -> {}", - point_index.point(), - next_endpoint_index - ); - - process_active_segment( - contour, - point_index.point(), - &mut self.active_edges, - self.scene_builder, - &mut self.object_builder, - tile_y, - ); - - self.point_queue.push(QueuedEndpoint { - point_index: PointIndex::new(point_index.contour(), next_endpoint_index), - y: contour.position_of(next_endpoint_index).y(), - }); - - debug!("... done adding next endpoint"); - } - } - - fn init_point_queue(&mut self) { - // Find MIN points. - self.point_queue.clear(); - for (contour_index, contour) in self.outline.contours().iter().enumerate() { - let contour_index = contour_index as u32; - let mut cur_endpoint_index = 0; - let mut prev_endpoint_index = contour.prev_endpoint_index_of(cur_endpoint_index); - let mut next_endpoint_index = contour.next_endpoint_index_of(cur_endpoint_index); - loop { - if contour.point_is_logically_above(cur_endpoint_index, prev_endpoint_index) - && contour.point_is_logically_above(cur_endpoint_index, next_endpoint_index) - { - self.point_queue.push(QueuedEndpoint { - point_index: PointIndex::new(contour_index, cur_endpoint_index), - y: contour.position_of(cur_endpoint_index).y(), - }); - } - - if cur_endpoint_index >= next_endpoint_index { - break; - } - - prev_endpoint_index = cur_endpoint_index; - cur_endpoint_index = next_endpoint_index; - next_endpoint_index = contour.next_endpoint_index_of(cur_endpoint_index); - } - } - } -} - impl<'a> TilingPathInfo<'a> { pub(crate) fn has_destructive_blend_mode(&self) -> bool { match *self { @@ -412,11 +60,11 @@ pub(crate) enum TileType { } impl<'a> PackedTile<'a> { - fn new(draw_tile_index: u32, - draw_tile: &'a TileObjectPrimitive, - draw_tiling_path_info: &DrawTilingPathInfo<'a>, - object_builder: &ObjectBuilder) - -> PackedTile<'a> { + pub(crate) fn new(draw_tile_index: u32, + draw_tile: &'a TileObjectPrimitive, + draw_tiling_path_info: &DrawTilingPathInfo<'a>, + object_builder: &ObjectBuilder) + -> PackedTile<'a> { let tile_coords = object_builder.local_tile_index_to_coords(draw_tile_index as u32); // First, if the draw tile is empty, cull it regardless of clip. @@ -522,158 +170,6 @@ pub fn round_rect_out_to_tile_bounds(rect: RectF) -> RectI { (rect * vec2f(1.0 / TILE_WIDTH as f32, 1.0 / TILE_HEIGHT as f32)).round_out().to_i32() } -fn process_active_segment( - contour: &Contour, - from_endpoint_index: u32, - active_edges: &mut Vec, - builder: &SceneBuilder, - object_builder: &mut ObjectBuilder, - tile_y: i32, -) { - let mut active_edge = ActiveEdge::from_segment(&contour.segment_after(from_endpoint_index)); - debug!("... process_active_segment({:#?})", active_edge); - active_edge.process(builder, object_builder, tile_y); - if !active_edge.segment.is_none() { - debug!("... ... pushing resulting active edge: {:#?}", active_edge); - active_edges.push(active_edge); - } -} - -// Queued endpoints - -struct QueuedEndpoint { - point_index: PointIndex, - y: f32, -} - -// Active edges - -#[derive(Clone, PartialEq, Debug)] -struct ActiveEdge { - segment: Segment, - // TODO(pcwalton): Shrink `crossing` down to just one f32? - crossing: Vector2F, -} - -impl ActiveEdge { - fn from_segment(segment: &Segment) -> ActiveEdge { - let crossing = if segment.baseline.from_y() < segment.baseline.to_y() { - segment.baseline.from() - } else { - segment.baseline.to() - }; - ActiveEdge::from_segment_and_crossing(segment, crossing) - } - - fn from_segment_and_crossing(segment: &Segment, crossing: Vector2F) -> ActiveEdge { - ActiveEdge { segment: *segment, crossing } - } - - fn process(&mut self, - builder: &SceneBuilder, - object_builder: &mut ObjectBuilder, - tile_y: i32) { - - let tile_bottom = ((tile_y + 1) * TILE_HEIGHT as i32) as f32; - debug!( - "process_active_edge({:#?}, tile_y={}({}))", - self, tile_y, tile_bottom - ); - - let mut segment = self.segment; - let winding = segment.baseline.y_winding(); - - if segment.is_line() { - let line_segment = segment.as_line_segment(); - self.segment = - match self.process_line_segment(line_segment, builder, object_builder, tile_y) { - Some(lower_part) => Segment::line(lower_part), - None => Segment::none(), - }; - return; - } - - // TODO(pcwalton): Don't degree elevate! - if !segment.is_cubic() { - segment = segment.to_cubic(); - } - - // If necessary, draw initial line. - if self.crossing.y() < segment.baseline.min_y() { - let first_line_segment = - LineSegment2F::new(self.crossing, segment.baseline.upper_point()).orient(winding); - if self.process_line_segment(first_line_segment, builder, object_builder, tile_y) - .is_some() { - return; - } - } - - let mut oriented_segment = segment.orient(winding); - loop { - let mut split_t = 1.0; - let mut before_segment = oriented_segment; - let mut after_segment = None; - - while !before_segment - .as_cubic_segment() - .is_flat(FLATTENING_TOLERANCE) - { - split_t *= 0.5; - let (before, after) = oriented_segment.as_cubic_segment().split(split_t); - before_segment = before; - after_segment = Some(after); - } - - debug!( - "... tile_y={} winding={} segment={:?} t={} before_segment={:?} - after_segment={:?}", - tile_y, winding, segment, split_t, before_segment, after_segment - ); - - let line = before_segment.baseline.orient(winding); - match self.process_line_segment(line, builder, object_builder, tile_y) { - Some(lower_part) if split_t == 1.0 => { - self.segment = Segment::line(lower_part); - return; - } - None if split_t == 1.0 => { - self.segment = Segment::none(); - return; - } - Some(_) => { - self.segment = after_segment.unwrap().orient(winding); - return; - } - None => oriented_segment = after_segment.unwrap(), - } - } - } - - fn process_line_segment( - &mut self, - line_segment: LineSegment2F, - builder: &SceneBuilder, - object_builder: &mut ObjectBuilder, - tile_y: i32, - ) -> Option { - let tile_bottom = ((tile_y + 1) * TILE_HEIGHT as i32) as f32; - debug!( - "process_line_segment({:?}, tile_y={}) tile_bottom={}", - line_segment, tile_y, tile_bottom - ); - - if line_segment.max_y() <= tile_bottom { - object_builder.generate_fill_primitives_for_line(builder, line_segment, tile_y); - return None; - } - - let (upper_part, lower_part) = line_segment.split_at_y(tile_bottom); - object_builder.generate_fill_primitives_for_line(builder, upper_part, tile_y); - self.crossing = lower_part.upper_point(); - Some(lower_part) - } -} - impl Default for TileObjectPrimitive { #[inline] fn default() -> TileObjectPrimitive {