diff --git a/content/src/gradient.rs b/content/src/gradient.rs index 11155970..3deb8ba9 100644 --- a/content/src/gradient.rs +++ b/content/src/gradient.rs @@ -8,7 +8,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::sorted_vector::SortedVector; use crate::util; use pathfinder_color::ColorU; use pathfinder_geometry::line_segment::LineSegment2F; @@ -16,7 +15,7 @@ use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::util as geometry_util; use pathfinder_simd::default::F32x2; -use std::cmp::{Ordering, PartialOrd}; +use std::cmp::Ordering; use std::convert; use std::hash::{Hash, Hasher}; use std::mem; @@ -24,10 +23,10 @@ use std::mem; #[derive(Clone, PartialEq, Debug)] pub struct Gradient { pub geometry: GradientGeometry, - stops: SortedVector, + stops: Vec, } -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +#[derive(Clone, Copy, PartialEq, Debug)] pub struct ColorStop { pub offset: f32, pub color: ColorU, @@ -91,7 +90,7 @@ impl Hash for ColorStop { impl Gradient { #[inline] pub fn linear(line: LineSegment2F) -> Gradient { - Gradient { geometry: GradientGeometry::Linear(line), stops: SortedVector::new() } + Gradient { geometry: GradientGeometry::Linear(line), stops: Vec::new() } } #[inline] @@ -104,13 +103,16 @@ impl Gradient { let transform = Transform2F::default(); Gradient { geometry: GradientGeometry::Radial { line: line.to_line(), radii, transform }, - stops: SortedVector::new(), + stops: Vec::new(), } } #[inline] pub fn add(&mut self, stop: ColorStop) { - self.stops.push(stop); + let index = self.stops.binary_search_by(|other| { + if other.offset <= stop.offset { Ordering::Less } else { Ordering::Greater } + }).unwrap_or_else(convert::identity); + self.stops.insert(index, stop); } /// A convenience method to add a color stop. @@ -121,12 +123,12 @@ impl Gradient { #[inline] pub fn stops(&self) -> &[ColorStop] { - &self.stops.array + &self.stops } #[inline] pub fn stops_mut(&mut self) -> &mut [ColorStop] { - &mut self.stops.array + &mut self.stops } pub fn sample(&self, mut t: f32) -> ColorU { @@ -136,13 +138,14 @@ impl Gradient { t = geometry_util::clamp(t, 0.0, 1.0); let last_index = self.stops.len() - 1; + let upper_index = self.stops.binary_search_by(|stop| { - stop.offset.partial_cmp(&t).unwrap_or(Ordering::Less) + if stop.offset < t || stop.offset == 0.0 { Ordering::Less } else { Ordering::Greater } }).unwrap_or_else(convert::identity).min(last_index); let lower_index = if upper_index > 0 { upper_index - 1 } else { upper_index }; - let lower_stop = &self.stops.array[lower_index]; - let upper_stop = &self.stops.array[upper_index]; + let lower_stop = &self.stops[lower_index]; + let upper_stop = &self.stops[upper_index]; let denom = upper_stop.offset - lower_stop.offset; if denom == 0.0 { @@ -157,12 +160,12 @@ impl Gradient { #[inline] pub fn is_opaque(&self) -> bool { - self.stops.array.iter().all(|stop| stop.color.is_opaque()) + self.stops.iter().all(|stop| stop.color.is_opaque()) } #[inline] pub fn is_fully_transparent(&self) -> bool { - self.stops.array.iter().all(|stop| stop.color.is_fully_transparent()) + self.stops.iter().all(|stop| stop.color.is_fully_transparent()) } pub fn apply_transform(&mut self, new_transform: Transform2F) { @@ -203,3 +206,37 @@ impl RadialGradientLine for Vector2F { LineSegment2F::new(self, self) } } + +#[cfg(test)] +mod test { + use crate::gradient::Gradient; + use pathfinder_color::ColorU; + use pathfinder_geometry::vector::Vector2F; + + #[test] + fn stable_order() { + let mut grad = Gradient::linear_from_points(Vector2F::default(), Vector2F::default()); + for i in 0..110 { + grad.add_color_stop(ColorU::new(i, 0, 0, 1), (i % 11) as f32 / 10.0); + } + + // Check that it sorted stably + assert!(grad.stops.windows(2).all(|w| { + w[0].offset < w[1].offset || w[0].color.r < w[1].color.r + })); + } + + #[test] + fn never_sample_zero_width() { + let mut grad = Gradient::linear_from_points(Vector2F::default(), Vector2F::default()); + for i in 0..110 { + let zero_width = (i == 0) || (11 <= i && i < 99) || (i == 109); + grad.add_color_stop(ColorU::new(if zero_width { 255 } else { 0 }, 0, 0, 1), (i % 11) as f32 / 10.0); + } + + for i in 0..11 { + let sample = grad.sample(i as f32 / 10.0); + assert!(sample.r == 0, "{} {}", i, sample.r); + } + } +} diff --git a/content/src/lib.rs b/content/src/lib.rs index 86747602..db751ec1 100644 --- a/content/src/lib.rs +++ b/content/src/lib.rs @@ -27,7 +27,6 @@ pub mod outline; pub mod pattern; pub mod render_target; pub mod segment; -pub mod sorted_vector; pub mod stroke; pub mod transform; diff --git a/content/src/sorted_vector.rs b/content/src/sorted_vector.rs deleted file mode 100644 index 21c584f5..00000000 --- a/content/src/sorted_vector.rs +++ /dev/null @@ -1,100 +0,0 @@ -// pathfinder/content/src/sorted_vector.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. - -//! A vector that maintains sorted order with insertion sort. - -use std::cmp::Ordering; -use std::convert; - -#[derive(Clone, PartialEq, Eq, Hash, Debug)] -pub struct SortedVector -where - T: PartialOrd, -{ - pub array: Vec, -} - -impl SortedVector -where - T: PartialOrd, -{ - #[inline] - pub fn new() -> SortedVector { - SortedVector { array: vec![] } - } - - #[inline] - pub fn push(&mut self, value: T) { - let index = self.binary_search_by(|other| { - other.partial_cmp(&value).unwrap_or(Ordering::Less) - }).unwrap_or_else(convert::identity); - self.array.insert(index, value); - } - - #[inline] - pub fn peek(&self) -> Option<&T> { - self.array.last() - } - - #[inline] - pub fn pop(&mut self) -> Option { - self.array.pop() - } - - #[inline] - pub fn clear(&mut self) { - self.array.clear() - } - - #[allow(dead_code)] - #[inline] - pub fn is_empty(&self) -> bool { - self.array.is_empty() - } - - #[inline] - pub fn len(&self) -> usize { - self.array.len() - } - - #[inline] - pub fn binary_search_by<'a, F>(&'a self, f: F) -> Result - where F: FnMut(&'a T) -> Ordering { - self.array.binary_search_by(f) - } -} - -#[cfg(test)] -mod test { - use crate::sorted_vector::SortedVector; - use quickcheck; - - #[test] - fn test_sorted_vec() { - quickcheck::quickcheck(prop_sorted_vec as fn(Vec) -> bool); - - fn prop_sorted_vec(mut values: Vec) -> bool { - let mut sorted_vec = SortedVector::new(); - for &value in &values { - sorted_vec.push(value) - } - - values.sort(); - let mut results = Vec::with_capacity(values.len()); - while !sorted_vec.is_empty() { - results.push(sorted_vec.pop().unwrap()); - } - results.reverse(); - assert_eq!(&values, &results); - - true - } - } -} diff --git a/renderer/src/tiles.rs b/renderer/src/tiles.rs index 678e6019..86fc7faa 100644 --- a/renderer/src/tiles.rs +++ b/renderer/src/tiles.rs @@ -15,11 +15,9 @@ use pathfinder_content::effects::BlendMode; use pathfinder_content::fill::FillRule; use pathfinder_content::outline::{Contour, Outline, PointIndex}; use pathfinder_content::segment::Segment; -use pathfinder_content::sorted_vector::SortedVector; use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i}; -use std::cmp::Ordering; use std::mem; // TODO(pcwalton): Make this configurable. @@ -34,8 +32,9 @@ pub(crate) struct Tiler<'a, 'b> { outline: &'a Outline, path_info: TilingPathInfo<'a>, - point_queue: SortedVector, - active_edges: SortedVector, + point_queue: Vec, + next_point_queue: Vec, + active_edges: Vec, old_active_edges: Vec, } @@ -75,9 +74,10 @@ impl<'a, 'b> Tiler<'a, 'b> { outline, path_info, - point_queue: SortedVector::new(), - active_edges: SortedVector::new(), - old_active_edges: vec![], + point_queue: Vec::new(), + next_point_queue: Vec::new(), + active_edges: Vec::new(), + old_active_edges: Vec::new(), } } @@ -107,18 +107,19 @@ impl<'a, 'b> Tiler<'a, 'b> { self.process_old_active_edges(strip_origin_y); // Add new active edges. - let strip_max_y = ((i32::from(strip_origin_y) + 1) * TILE_HEIGHT as i32) as f32; - while let Some(queued_endpoint) = self.point_queue.peek() { - // We're done when we see an endpoint that belongs to the next tile strip. - // + 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 { - break; + self.next_point_queue.push(queued_endpoint); + continue; } - self.add_new_active_edge(strip_origin_y); + 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) { @@ -180,16 +181,17 @@ impl<'a, 'b> Tiler<'a, 'b> { let mut current_winding = 0; debug_assert!(self.old_active_edges.is_empty()); - mem::swap(&mut self.old_active_edges, &mut self.active_edges.array); + mem::swap(&mut self.old_active_edges, &mut self.active_edges); // FIXME(pcwalton): Yuck. let mut last_segment_x = -9999.0; - let tile_top = (i32::from(tile_y) * TILE_HEIGHT as i32) as f32; + 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(); @@ -224,8 +226,8 @@ impl<'a, 'b> Tiler<'a, 'b> { 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 = - (i32::from(current_tile_x) * TILE_WIDTH as i32) as f32 + current_subtile_x; - let tile_right_x = ((i32::from(current_tile_x) + 1) * TILE_WIDTH as i32) as f32; + (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, @@ -259,10 +261,10 @@ impl<'a, 'b> Tiler<'a, 'b> { // Do final subtile fill, if necessary. debug_assert_eq!(current_tile_x, segment_tile_x); let segment_subtile_x = - segment_x - (i32::from(current_tile_x) * TILE_WIDTH as i32) as f32; + segment_x - (current_tile_x * TILE_WIDTH as i32) as f32; if segment_subtile_x > current_subtile_x { let current_x = - (i32::from(current_tile_x) * TILE_WIDTH as i32) as f32 + current_subtile_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, @@ -287,9 +289,9 @@ impl<'a, 'b> Tiler<'a, 'b> { } } - fn add_new_active_edge(&mut self, tile_y: i32) { + fn add_new_active_edge(&mut self, tile_y: i32, queued_endpoint: QueuedEndpoint) { let outline = &self.outline; - let point_index = self.point_queue.pop().unwrap().point_index; + let point_index = queued_endpoint.point_index; let contour = &outline.contours()[point_index.contour() as usize]; @@ -523,7 +525,7 @@ pub fn round_rect_out_to_tile_bounds(rect: RectF) -> RectI { fn process_active_segment( contour: &Contour, from_endpoint_index: u32, - active_edges: &mut SortedVector, + active_edges: &mut Vec, builder: &SceneBuilder, object_builder: &mut ObjectBuilder, tile_y: i32, @@ -539,21 +541,11 @@ fn process_active_segment( // Queued endpoints -#[derive(PartialEq)] struct QueuedEndpoint { point_index: PointIndex, y: f32, } -impl Eq for QueuedEndpoint {} - -impl PartialOrd for QueuedEndpoint { - fn partial_cmp(&self, other: &QueuedEndpoint) -> Option { - // NB: Reversed! - (other.y, other.point_index).partial_cmp(&(self.y, self.point_index)) - } -} - // Active edges #[derive(Clone, PartialEq, Debug)] @@ -581,7 +573,8 @@ impl ActiveEdge { builder: &SceneBuilder, object_builder: &mut ObjectBuilder, tile_y: i32) { - let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32; + + let tile_bottom = ((tile_y + 1) * TILE_HEIGHT as i32) as f32; debug!( "process_active_edge({:#?}, tile_y={}({}))", self, tile_y, tile_bottom @@ -625,11 +618,10 @@ impl ActiveEdge { .as_cubic_segment() .is_flat(FLATTENING_TOLERANCE) { - let next_t = 0.5 * split_t; - let (before, after) = oriented_segment.as_cubic_segment().split(next_t); + split_t *= 0.5; + let (before, after) = oriented_segment.as_cubic_segment().split(split_t); before_segment = before; after_segment = Some(after); - split_t = next_t; } debug!( @@ -664,7 +656,7 @@ impl ActiveEdge { object_builder: &mut ObjectBuilder, tile_y: i32, ) -> Option { - let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32; + 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 @@ -682,12 +674,6 @@ impl ActiveEdge { } } -impl PartialOrd for ActiveEdge { - fn partial_cmp(&self, other: &ActiveEdge) -> Option { - self.crossing.x().partial_cmp(&other.crossing.x()) - } -} - impl Default for TileObjectPrimitive { #[inline] fn default() -> TileObjectPrimitive {