wip, too slow

This commit is contained in:
Patrick Walton 2018-12-13 14:04:17 -08:00
parent 4597414e21
commit dda3e16d2e
3 changed files with 384 additions and 25 deletions

50
Cargo.lock generated
View File

@ -358,6 +358,14 @@ dependencies = [
"termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "euclid"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "euclid"
version = "0.19.0"
@ -749,6 +757,24 @@ dependencies = [
"linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lyon_algorithms"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lyon_path 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lyon_geom"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lyon_geom"
version = "0.12.1"
@ -759,6 +785,14 @@ dependencies = [
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lyon_path"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lyon_geom 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lyon_path"
version = "0.12.0"
@ -868,6 +902,14 @@ dependencies = [
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.6"
@ -1452,7 +1494,8 @@ name = "tile-svg"
version = "0.1.0"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lyon_algorithms 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-xml 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)",
"quickcheck 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1674,6 +1717,7 @@ source = "git+https://github.com/SergioBenitez/ring?branch=v0.12#9ccfa153a27aecc
"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0"
"checksum encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1a8fa54e6689eb2549c4efed8d00d7f3b2b994a064555b0e8df4ae3764bcc4be"
"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38"
"checksum euclid 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)" = "59b34ec7d95d70d5cda27301d6182bc17abce8b5b52e260f5ff32c677923bbb0"
"checksum euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "70a2ebdf55fb9d6329046e026329a55ef8fbaae5ea833f56e170beb3125a8a5f"
"checksum expat-sys 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c470ccb972f2088549b023db8029ed9da9426f5affbf9b62efff7009ab8ed5b1"
"checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9"
@ -1715,7 +1759,10 @@ source = "git+https://github.com/SergioBenitez/ring?branch=v0.12#9ccfa153a27aecc
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
"checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21"
"checksum lyon_algorithms 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3ebe7b9bffa94e7d8d332992bd1d8b9c0318170862487f0a2d7e04bcc5aabbe"
"checksum lyon_geom 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ee0dc4aec93a8fd9109362bebfbad0ace69b8629937f954ecc8eea1de63146"
"checksum lyon_geom 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d69cc8d0b54ed6d49ed2ef6b465e67ee89e92dfcb4bd839cbd58dc189c14efe8"
"checksum lyon_path 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98c39b845796d4590197e2b2b97202e31b69071116a541bfddb52f50680318f0"
"checksum lyon_path 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9dc8e0746b7cca11960b602f7fe037bb067746a01eab4aa502fed1494544843"
"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
@ -1730,6 +1777,7 @@ source = "git+https://github.com/SergioBenitez/ring?branch=v0.12#9ccfa153a27aecc
"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"
"checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124"
"checksum num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9833ab0efe5361b1e2122a0544a5d3359576911a42cb098c2e59be8650807367"

View File

@ -6,7 +6,8 @@ edition = "2018"
[dependencies]
bitflags = "1.0"
euclid = "0.19"
euclid = "0.18"
lyon_algorithms = "0.11"
quick-xml = "0.12"
svgtypes = "0.2"

View File

@ -16,14 +16,16 @@ extern crate quickcheck;
#[cfg(test)]
extern crate rand;
use euclid::{Point2D, Transform2D};
use euclid::{Point2D, Rect, Transform2D, Vector2D};
use lyon_algorithms::geom::{CubicBezierSegment, LineSegment, QuadraticBezierSegment};
use quick_xml::Reader;
use quick_xml::events::Event;
use std::cmp::Ordering;
use std::env;
use std::mem;
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Instant;
use svgtypes::{Color as SvgColor, PathParser, PathSegment as SvgPathSegment, TransformListParser};
use svgtypes::{TransformListToken};
@ -57,7 +59,13 @@ impl ComputedStyle {
fn main() {
let path = PathBuf::from(env::args().skip(1).next().unwrap());
let scene = Scene::from_path(&path);
println!("{:#?}", scene);
let start_time = Instant::now();
scene.generate_tiles();
let elapsed_time = Instant::now() - start_time;
println!("{}ms elapsed",
elapsed_time.as_secs() as f64 * 1000.0 +
elapsed_time.subsec_micros() as f64 / 1000.0);
}
#[derive(Debug)]
@ -195,6 +203,14 @@ impl Scene {
fn get_style(&self, style: StyleId) -> &ComputedStyle {
&self.styles[style.0 as usize]
}
fn generate_tiles(&self) {
for object in &self.objects {
let mut tiler = Tiler::from_outline(&object.outline);
tiler.generate_tiles();
// TODO(pcwalton)
}
}
}
impl PathObject {
@ -211,6 +227,7 @@ impl PathObject {
#[derive(Debug)]
struct Outline {
contours: Vec<Contour>,
bounds: Rect<f32>,
}
#[derive(Debug)]
@ -230,6 +247,7 @@ impl Outline {
fn new() -> Outline {
Outline {
contours: vec![],
bounds: Rect::zero(),
}
}
@ -237,7 +255,9 @@ impl Outline {
where I: Iterator<Item = SvgPathSegment> {
let mut outline = Outline::new();
let mut current_contour = Contour::new();
let mut bounding_points = None;
let (mut first_point_in_path, mut last_ctrl_point, mut last_point) = (None, None, None);
for segment in segments {
match segment {
SvgPathSegment::MoveTo { abs, x, y } => {
@ -250,7 +270,8 @@ impl Outline {
last_ctrl_point = None;
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform);
&style.transform,
&mut bounding_points);
}
SvgPathSegment::LineTo { abs, x, y } => {
let to = compute_point(x, y, abs, &last_point);
@ -258,7 +279,8 @@ impl Outline {
last_ctrl_point = None;
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform);
&style.transform,
&mut bounding_points);
}
SvgPathSegment::HorizontalLineTo { abs, x } => {
let to = Point2D::new(compute_point(x, 0.0, abs, &last_point).x,
@ -267,7 +289,8 @@ impl Outline {
last_ctrl_point = None;
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform);
&style.transform,
&mut bounding_points);
}
SvgPathSegment::VerticalLineTo { abs, y } => {
let to = Point2D::new(last_point.unwrap_or(Point2D::zero()).x,
@ -276,7 +299,8 @@ impl Outline {
last_ctrl_point = None;
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform);
&style.transform,
&mut bounding_points);
}
SvgPathSegment::Quadratic { abs, x1, y1, x, y } => {
let ctrl = compute_point(x1, y1, abs, &last_point);
@ -285,10 +309,12 @@ impl Outline {
last_point = Some(to);
current_contour.push_transformed_point(&ctrl,
PointFlags::CONTROL_POINT_0,
&style.transform);
&style.transform,
&mut bounding_points);
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform);
&style.transform,
&mut bounding_points);
}
SvgPathSegment::SmoothQuadratic { abs, x, y } => {
let ctrl = last_point.unwrap_or(Point2D::zero()) +
@ -299,10 +325,12 @@ impl Outline {
last_point = Some(to);
current_contour.push_transformed_point(&ctrl,
PointFlags::CONTROL_POINT_0,
&style.transform);
&style.transform,
&mut bounding_points);
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform);
&style.transform,
&mut bounding_points);
}
SvgPathSegment::CurveTo { abs, x1, y1, x2, y2, x, y } => {
let ctrl0 = compute_point(x1, y1, abs, &last_point);
@ -312,13 +340,16 @@ impl Outline {
last_point = Some(to);
current_contour.push_transformed_point(&ctrl0,
PointFlags::CONTROL_POINT_0,
&style.transform);
&style.transform,
&mut bounding_points);
current_contour.push_transformed_point(&ctrl1,
PointFlags::CONTROL_POINT_1,
&style.transform);
&style.transform,
&mut bounding_points);
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform);
&style.transform,
&mut bounding_points);
}
SvgPathSegment::SmoothCurveTo { abs, x2, y2, x, y } => {
let ctrl0 = last_point.unwrap_or(Point2D::zero()) +
@ -330,13 +361,16 @@ impl Outline {
last_point = Some(to);
current_contour.push_transformed_point(&ctrl0,
PointFlags::CONTROL_POINT_0,
&style.transform);
&style.transform,
&mut bounding_points);
current_contour.push_transformed_point(&ctrl1,
PointFlags::CONTROL_POINT_1,
&style.transform);
&style.transform,
&mut bounding_points);
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform);
&style.transform,
&mut bounding_points);
}
SvgPathSegment::ClosePath { abs: _ } => {
if !current_contour.is_empty() {
@ -351,6 +385,11 @@ impl Outline {
if !current_contour.is_empty() {
outline.contours.push(current_contour)
}
if let Some((upper_left, lower_right)) = bounding_points {
outline.bounds = Rect::from_points([upper_left, lower_right].into_iter())
}
return outline;
fn compute_point(x: f64, y: f64, abs: bool, last_point: &Option<Point2D<f32>>)
@ -362,6 +401,10 @@ impl Outline {
}
}
}
fn segment_after(&self, endpoint_index: PointIndex) -> Segment {
self.contours[endpoint_index.contour_index].segment_after(endpoint_index.point_index)
}
}
impl Contour {
@ -379,24 +422,287 @@ impl Contour {
fn push_transformed_point(&mut self,
point: &Point2D<f32>,
flags: PointFlags,
transform: &Transform2D<f32>) {
self.points.push(transform.transform_point(point));
transform: &Transform2D<f32>,
bounding_points: &mut Option<(Point2D<f32>, Point2D<f32>)>) {
let point = transform.transform_point(point);
self.points.push(point);
self.flags.push(flags);
match *bounding_points {
Some((ref mut upper_left, ref mut lower_right)) => {
*upper_left = upper_left.min(point);
*lower_right = lower_right.max(point);
}
None => *bounding_points = Some((point, point)),
}
}
fn segment_after(&self, point_index: usize) -> Segment {
debug_assert!(self.point_is_endpoint(point_index));
let point1_index = self.add_to_point_index(point_index, 1);
if self.point_is_endpoint(point1_index) {
return Segment::Line(LineSegment {
from: self.points[point_index],
to: self.points[point1_index],
})
}
let point2_index = self.add_to_point_index(point_index, 2);
if self.point_is_endpoint(point2_index) {
return Segment::Quadratic(QuadraticBezierSegment {
from: self.points[point_index],
ctrl: self.points[point1_index],
to: self.points[point2_index],
})
}
let point3_index = self.add_to_point_index(point_index, 3);
Segment::Cubic(CubicBezierSegment {
from: self.points[point_index],
ctrl1: self.points[point1_index],
ctrl2: self.points[point2_index],
to: self.points[point3_index],
})
}
fn point_is_endpoint(&self, point_index: usize) -> bool {
self.flags[point_index].intersects(PointFlags::CONTROL_POINT_0 |
PointFlags::CONTROL_POINT_1)
}
fn add_to_point_index(&self, point_index: usize, addend: usize) -> usize {
(point_index + addend) % self.points.len()
}
}
#[derive(Clone, Copy, Debug)]
struct PointIndex {
contour_index: usize,
point_index: usize,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Segment {
Line(LineSegment<f32>),
Quadratic(QuadraticBezierSegment<f32>),
Cubic(CubicBezierSegment<f32>),
}
impl Segment {
fn endpoints(&self) -> (Point2D<f32>, Point2D<f32>) {
match *self {
Segment::Line(ref line) => (line.from, line.to),
Segment::Quadratic(ref curve) => (curve.from, curve.to),
Segment::Cubic(ref curve) => (curve.from, curve.to),
}
}
// Note: If we convert these to monotonic then we can optimize this method.
// TODO(pcwalton): Consider changing the representation of `Segment` to remove the code
// duplication in the branches here?
fn min_y(&self) -> f32 {
match *self {
Segment::Line(ref line) => f32::min(line.from.y, line.to.y),
Segment::Quadratic(ref curve) => {
f32::min(f32::min(curve.from.y, curve.ctrl.y), curve.to.y)
}
Segment::Cubic(ref curve) => {
f32::min(f32::min(f32::min(curve.from.y, curve.ctrl1.y), curve.ctrl2.y),
curve.to.y)
}
}
}
fn clip_y(&self, y: f32) -> ClippedSegments {
let (from, to) = self.endpoints();
if from.y < y && to.y < y {
return ClippedSegments { min: Some(*self), max: None }
}
if from.y > y && to.y > y {
return ClippedSegments { min: None, max: Some(*self) }
}
let (prev, next) = match *self {
Segment::Line(ref line) => {
let (prev, next) = line.split(line.solve_t_for_y(y));
(Segment::Line(prev), Segment::Line(next))
}
Segment::Quadratic(ref curve) => {
let (prev, next) = curve.split(curve.assume_monotonic().solve_t_for_y(y));
(Segment::Quadratic(prev), Segment::Quadratic(next))
}
Segment::Cubic(ref curve) => {
let swapped_curve = CubicBezierSegment {
from: curve.from.yx(),
ctrl1: curve.ctrl1.yx(),
ctrl2: curve.ctrl2.yx(),
to: curve.to.yx(),
};
let (prev, next) = curve.split(
swapped_curve.assume_monotonic().solve_t_for_x(y, 0.0..1.0, TOLERANCE));
(Segment::Cubic(prev), Segment::Cubic(next))
}
};
if from.y <= to.y {
return ClippedSegments { min: Some(prev), max: Some(next) };
} else {
return ClippedSegments { min: Some(next), max: Some(prev) };
}
const TOLERANCE: f32 = 0.01;
}
fn translate(&self, by: &Vector2D<f32>) -> Segment {
match *self {
Segment::Line(ref line) => {
Segment::Line(LineSegment {
from: line.from + *by,
to: line.to + *by,
})
}
Segment::Quadratic(ref curve) => {
Segment::Quadratic(QuadraticBezierSegment {
from: curve.from + *by,
ctrl: curve.ctrl + *by,
to: curve.to + *by,
})
}
Segment::Cubic(ref curve) => {
Segment::Cubic(CubicBezierSegment {
from: curve.from + *by,
ctrl1: curve.ctrl1 + *by,
ctrl2: curve.ctrl2 + *by,
to: curve.to + *by,
})
}
}
}
}
struct ClippedSegments {
min: Option<Segment>,
max: Option<Segment>,
}
// Tiling
struct Tiler {
outline: Outline,
const TILE_WIDTH: f32 = 4.0;
const TILE_HEIGHT: f32 = 4.0;
struct Tiler<'a> {
outline: &'a Outline,
}
impl Tiler {
fn from_outline(outline: Outline) -> Tiler {
impl<'a> Tiler<'a> {
fn from_outline(outline: &Outline) -> Tiler {
Tiler {
outline,
}
}
fn generate_tiles(&mut self) {
// Sort all edge indices.
let mut sorted_edge_indices = vec![];
for contour_index in 0..self.outline.contours.len() {
let contour = &self.outline.contours[contour_index];
for point_index in 0..contour.points.len() {
if contour.point_is_endpoint(point_index) {
sorted_edge_indices.push(PointIndex { contour_index, point_index })
}
}
}
sorted_edge_indices.sort_by(|edge_index_a, edge_index_b| {
let segment_a = self.outline.segment_after(*edge_index_a);
let segment_b = self.outline.segment_after(*edge_index_b);
segment_a.min_y().partial_cmp(&segment_b.min_y()).unwrap_or(Ordering::Equal)
});
let bounds = self.outline.bounds;
let (max_x, max_y) = (bounds.max_x(), bounds.max_y());
let mut active_intervals = Intervals::new(max_x);
let mut active_edges = vec![];
let mut next_edge_index_index = 0;
let mut tile_top = bounds.origin.y - bounds.origin.y % TILE_HEIGHT;
let mut strips = vec![];
while tile_top < max_y {
let mut strip = Strip::new(tile_top);
// TODO(pcwalton): Populate tile strip with active intervals.
for active_edge in mem::replace(&mut active_edges, vec![]) {
self.process_edge(active_edge,
&mut strip,
&mut active_edges,
&mut active_intervals);
}
while next_edge_index_index < sorted_edge_indices.len() {
let segment =
self.outline.segment_after(sorted_edge_indices[next_edge_index_index]);
if segment.min_y() > strip.tile_bottom() {
break
}
self.process_edge(segment,
&mut strip,
&mut active_edges,
&mut active_intervals);
next_edge_index_index += 1;
}
tile_top = strip.tile_bottom();
strips.push(strip);
}
}
fn process_edge(&mut self,
edge: Segment,
strip: &mut Strip,
active_edges: &mut Vec<Segment>,
intervals: &mut Intervals) {
let clipped = edge.clip_y(strip.tile_bottom());
if let Some(upper_segment) = clipped.min {
strip.push_segment(upper_segment);
// FIXME(pcwalton): Assumes x-monotonicity!
// FIXME(pcwalton): The min call below is a hack!
let (from, to) = upper_segment.endpoints();
let from_x = f32::max(0.0, f32::min(intervals.extent(), from.x));
let to_x = f32::max(0.0, f32::min(intervals.extent(), to.x));
if from_x < to_x {
intervals.add(IntervalRange::new(from_x, to_x, -1.0))
} else {
intervals.add(IntervalRange::new(to_x, from_x, 1.0))
}
}
if let Some(lower_segment) = clipped.max {
active_edges.push(lower_segment);
}
}
}
// Strips
struct Strip {
segments: Vec<Segment>,
tile_top: f32,
}
impl Strip {
fn new(tile_top: f32) -> Strip {
Strip {
segments: vec![],
tile_top,
}
}
fn push_segment(&mut self, segment: Segment) {
self.segments.push(segment.translate(&Vector2D::new(0.0, -self.tile_top)))
}
fn tile_bottom(&self) -> f32 {
self.tile_top + TILE_HEIGHT
}
}
// Intervals
@ -448,6 +754,10 @@ impl Intervals {
self.ranges[0] = IntervalRange::new(0.0, end, 0.0);
}
fn extent(&self) -> f32 {
self.ranges.last().unwrap().end
}
fn split_at(&mut self, value: f32) {
let mut range_index = 0;
while range_index < self.ranges.len() {