Faster sorting strategy, remove SortedVector

point_queue is only sorted by y coordinate to coordinate
work to one strip at a time. This is more efficiently handled
by punting later work into a next_point_queue,
and never explicitly sorting the data.

active_edges is only sorted by x coordinate for the tile-by-tile
work in process_old_active_edges. It is much more efficient
to sort once inside that function.
This commit is contained in:
Veedrac 2020-05-11 13:00:15 +01:00
parent 2ad02b0b78
commit 1c48e8900e
4 changed files with 32 additions and 145 deletions

View File

@ -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;
@ -24,10 +23,10 @@ use std::mem;
#[derive(Clone, PartialEq, Debug)]
pub struct Gradient {
pub geometry: GradientGeometry,
stops: SortedVector<ColorStop>,
stops: Vec<ColorStop>,
}
#[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| {
other.offset.partial_cmp(&stop.offset).unwrap()
}).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 {
@ -141,8 +143,8 @@ impl Gradient {
}).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 +159,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) {

View File

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

View File

@ -1,100 +0,0 @@
// pathfinder/content/src/sorted_vector.rs
//
// Copyright © 2020 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<T>
where
T: PartialOrd,
{
pub array: Vec<T>,
}
impl<T> SortedVector<T>
where
T: PartialOrd,
{
#[inline]
pub fn new() -> SortedVector<T> {
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<T> {
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<usize, usize>
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<i32>) -> bool);
fn prop_sorted_vec(mut values: Vec<i32>) -> 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
}
}
}

View File

@ -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<QueuedEndpoint>,
active_edges: SortedVector<ActiveEdge>,
point_queue: Vec<QueuedEndpoint>,
next_point_queue: Vec<QueuedEndpoint>,
active_edges: Vec<ActiveEdge>,
old_active_edges: Vec<ActiveEdge>,
}
@ -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(),
}
}
@ -108,17 +108,18 @@ impl<'a, 'b> Tiler<'a, 'b> {
// 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.
//
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,7 +181,7 @@ 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;
@ -190,6 +191,7 @@ impl<'a, 'b> Tiler<'a, 'b> {
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();
@ -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<ActiveEdge>,
active_edges: &mut Vec<ActiveEdge>,
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<QueuedEndpoint> for QueuedEndpoint {
fn partial_cmp(&self, other: &QueuedEndpoint) -> Option<Ordering> {
// NB: Reversed!
(other.y, other.point_index).partial_cmp(&(self.y, self.point_index))
}
}
// Active edges
#[derive(Clone, PartialEq, Debug)]
@ -682,12 +674,6 @@ impl ActiveEdge {
}
}
impl PartialOrd<ActiveEdge> for ActiveEdge {
fn partial_cmp(&self, other: &ActiveEdge) -> Option<Ordering> {
self.crossing.x().partial_cmp(&other.crossing.x())
}
}
impl Default for TileObjectPrimitive {
#[inline]
fn default() -> TileObjectPrimitive {