diff --git a/Cargo.lock b/Cargo.lock index bad16a9f..4d5f1ec3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,6 +429,7 @@ name = "pathfinder_geometry" version = "0.3.0" dependencies = [ "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.19.4 (registry+https://github.com/rust-lang/crates.io-index)", "lyon_geom 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", "lyon_path 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/geometry/Cargo.toml b/geometry/Cargo.toml index ccfced40..7b0af913 100644 --- a/geometry/Cargo.toml +++ b/geometry/Cargo.toml @@ -6,6 +6,7 @@ authors = ["Patrick Walton "] [dependencies] arrayvec = "0.4" +bitflags = "1.0" euclid = "0.19" lyon_geom = "0.12" lyon_path = "0.12" diff --git a/geometry/src/lib.rs b/geometry/src/lib.rs index de78f8c4..c0373896 100644 --- a/geometry/src/lib.rs +++ b/geometry/src/lib.rs @@ -12,6 +12,9 @@ //! //! These may be merged into upstream Lyon eventually. +#[macro_use] +extern crate bitflags; + use simdeez::sse41::Sse41; // TODO(pcwalton): Make this configurable. @@ -23,6 +26,7 @@ pub mod line_segment; pub mod normals; pub mod orientation; pub mod point; +pub mod segment; pub mod segments; pub mod stroke; pub mod transform; diff --git a/geometry/src/segment.rs b/geometry/src/segment.rs new file mode 100644 index 00000000..ea423c6f --- /dev/null +++ b/geometry/src/segment.rs @@ -0,0 +1,306 @@ +// pathfinder/geometry/src/segment.rs +// +// Copyright © 2019 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. + +//! Line or curve segments, optimized with SIMD. + +use crate::SimdImpl; +use crate::line_segment::LineSegmentF32; +use crate::point::Point2DF32; +use simdeez::Simd; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Segment { + pub baseline: LineSegmentF32, + pub ctrl: LineSegmentF32, + pub kind: SegmentKind, + pub flags: SegmentFlags, +} + +impl Segment { + #[inline] + pub fn none() -> Segment { + Segment { + baseline: LineSegmentF32::default(), + ctrl: LineSegmentF32::default(), + kind: SegmentKind::None, + flags: SegmentFlags::empty(), + } + } + + #[inline] + pub fn line(line: &LineSegmentF32) -> Segment { + Segment { + baseline: *line, + ctrl: LineSegmentF32::default(), + kind: SegmentKind::Line, + flags: SegmentFlags::empty(), + } + } + + #[inline] + pub fn quadratic(baseline: &LineSegmentF32, ctrl: &Point2DF32) -> Segment { + Segment { + baseline: *baseline, + ctrl: LineSegmentF32::new(ctrl, &Point2DF32::default()), + kind: SegmentKind::Cubic, + flags: SegmentFlags::empty(), + } + } + + #[inline] + pub fn cubic(baseline: &LineSegmentF32, ctrl: &LineSegmentF32) -> Segment { + Segment { + baseline: *baseline, + ctrl: *ctrl, + kind: SegmentKind::Cubic, + flags: SegmentFlags::empty(), + } + } + + #[inline] + pub fn as_line_segment(&self) -> LineSegmentF32 { + debug_assert!(self.is_line()); + self.baseline + } + + #[inline] + pub fn is_none(&self) -> bool { + self.kind == SegmentKind::None + } + + #[inline] + pub fn is_line(&self) -> bool { + self.kind == SegmentKind::Line + } + + #[inline] + pub fn is_quadratic(&self) -> bool { + self.kind == SegmentKind::Quadratic + } + + #[inline] + pub fn is_cubic(&self) -> bool { + self.kind == SegmentKind::Cubic + } + + #[inline] + pub fn as_cubic_segment(&self) -> CubicSegment { + debug_assert!(self.is_cubic()); + CubicSegment(self) + } + + // FIXME(pcwalton): We should basically never use this function. + // FIXME(pcwalton): Handle lines! + #[inline] + pub fn to_cubic(&self) -> Segment { + if self.is_cubic() { + return *self; + } + + let mut new_segment = *self; + let p1_2 = self.ctrl.from() + self.ctrl.from(); + new_segment.ctrl = + LineSegmentF32::new(&(self.baseline.from() + p1_2), &(p1_2 + self.baseline.to())) + .scale(1.0 / 3.0); + new_segment + } + + #[inline] + pub fn reversed(&self) -> Segment { + Segment { + baseline: self.baseline.reversed(), + ctrl: if self.is_quadratic() { + self.ctrl + } else { + self.ctrl.reversed() + }, + kind: self.kind, + flags: self.flags, + } + } + + // Reverses if necessary so that the from point is above the to point. Calling this method + // again will undo the transformation. + #[inline] + pub fn orient(&self, y_winding: i32) -> Segment { + if y_winding >= 0 { + *self + } else { + self.reversed() + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +pub enum SegmentKind { + None, + Line, + Quadratic, + Cubic, +} + +bitflags! { + pub struct SegmentFlags: u8 { + const FIRST_IN_SUBPATH = 0x01; + const CLOSES_SUBPATH = 0x02; + } +} + +#[derive(Clone, Copy, Debug)] +pub struct CubicSegment<'s>(&'s Segment); + +impl<'s> CubicSegment<'s> { + #[inline] + pub fn flatten_once(self, tolerance: f32) -> Option { + let s2inv; + unsafe { + let (baseline, ctrl) = (self.0.baseline.0, self.0.ctrl.0); + let from_from = SimdImpl::shuffle_ps(baseline, baseline, 0b0100_0100); + + let v0102 = SimdImpl::sub_ps(ctrl, from_from); + + // v01.x v01.y v02.x v02.y + // * v01.x v01.y v01.y v01.x + // ------------------------- + // v01.x^2 v01.y^2 ad bc + // | | | | + // +-------+ +-----+ + // + - + // v01 len^2 determinant + let products = SimdImpl::mul_ps(v0102, SimdImpl::shuffle_ps(v0102, v0102, 0b0001_0100)); + + let det = products[2] - products[3]; + if det == 0.0 { + return None; + } + + s2inv = (products[0] + products[1]).sqrt() / det; + } + + let t = 2.0 * ((tolerance / 3.0) * s2inv.abs()).sqrt(); + if t >= 1.0 - EPSILON || t == 0.0 { + return None; + } + + return Some(self.split_after(t)); + + const EPSILON: f32 = 0.005; + } + + #[inline] + pub fn split(self, t: f32) -> (Segment, Segment) { + unsafe { + let tttt = SimdImpl::set1_ps(t); + + let p0p3 = self.0.baseline.0; + let p1p2 = self.0.ctrl.0; + let p0p1 = assemble(&p0p3, &p1p2, 0, 0); + + // p01 = lerp(p0, p1, t), p12 = lerp(p1, p2, t), p23 = lerp(p2, p3, t) + let p01p12 = SimdImpl::add_ps(p0p1, SimdImpl::mul_ps(tttt, SimdImpl::sub_ps(p1p2, p0p1))); + let pxxp23 = SimdImpl::add_ps(p1p2, SimdImpl::mul_ps(tttt, SimdImpl::sub_ps(p0p3, p1p2))); + + let p12p23 = assemble(&p01p12, &pxxp23, 1, 1); + + // p012 = lerp(p01, p12, t), p123 = lerp(p12, p23, t) + let p012p123 = + SimdImpl::add_ps(p01p12, SimdImpl::mul_ps(tttt, SimdImpl::sub_ps(p12p23, p01p12))); + + let p123 = pluck(&p012p123, 1); + + // p0123 = lerp(p012, p123, t) + let p0123 = SimdImpl::add_ps(p012p123, SimdImpl::mul_ps(tttt, SimdImpl::sub_ps(p123, p012p123))); + + let baseline0 = assemble(&p0p3, &p0123, 0, 0); + let ctrl0 = assemble(&p01p12, &p012p123, 0, 0); + let baseline1 = assemble(&p0123, &p0p3, 0, 1); + let ctrl1 = assemble(&p012p123, &p12p23, 1, 1); + + // FIXME(pcwalton): Set flags appropriately! + return ( + Segment { + baseline: LineSegmentF32(baseline0), + ctrl: LineSegmentF32(ctrl0), + kind: SegmentKind::Cubic, + flags: self.0.flags & SegmentFlags::FIRST_IN_SUBPATH, + }, + Segment { + baseline: LineSegmentF32(baseline1), + ctrl: LineSegmentF32(ctrl1), + kind: SegmentKind::Cubic, + flags: self.0.flags & SegmentFlags::CLOSES_SUBPATH, + }, + ); + } + + // Constructs a new 4-element vector from two pairs of adjacent lanes in two input vectors. + unsafe fn assemble( + a_data: &::Vf32, + b_data: &::Vf32, + a_index: usize, + b_index: usize, + ) -> ::Vf32 { + let (a_data, b_data) = (SimdImpl::castps_pd(*a_data), SimdImpl::castps_pd(*b_data)); + let mut result = SimdImpl::setzero_pd(); + result[0] = a_data[a_index]; + result[1] = b_data[b_index]; + SimdImpl::castpd_ps(result) + } + + // Constructs a new 2-element vector from a pair of adjacent lanes in an input vector. + unsafe fn pluck(data: &::Vf32, index: usize) -> ::Vf32 { + let data = SimdImpl::castps_pd(*data); + let mut result = SimdImpl::setzero_pd(); + result[0] = data[index]; + SimdImpl::castpd_ps(result) + } + } + + #[inline] + pub fn split_after(self, t: f32) -> Segment { + self.split(t).1 + } + + #[inline] + pub fn y_extrema(self) -> (Option, Option) { + let (t0, t1); + unsafe { + let mut p0p1p2p3 = SimdImpl::setzero_ps(); + p0p1p2p3[0] = self.0.baseline.from_y(); + p0p1p2p3[1] = self.0.ctrl.from_y(); + p0p1p2p3[2] = self.0.ctrl.to_y(); + p0p1p2p3[3] = self.0.baseline.to_y(); + + let pxp0p1p2 = SimdImpl::shuffle_ps(p0p1p2p3, p0p1p2p3, 0b1001_0000); + let pxv0v1v2 = SimdImpl::sub_ps(p0p1p2p3, pxp0p1p2); + let (v0, v1, v2) = (pxv0v1v2[1], pxv0v1v2[2], pxv0v1v2[3]); + + let (v0_to_v1, v2_to_v1) = (v0 - v1, v2 - v1); + let discrim = f32::sqrt(v1 * v1 - v0 * v2); + let denom = 1.0 / (v0_to_v1 + v2_to_v1); + + t0 = (v0_to_v1 + discrim) * denom; + t1 = (v0_to_v1 - discrim) * denom; + } + + return match ( + t0 > EPSILON && t0 < 1.0 - EPSILON, + t1 > EPSILON && t1 < 1.0 - EPSILON, + ) { + (false, false) => (None, None), + (true, false) => (Some(t0), None), + (false, true) => (Some(t1), None), + (true, true) => (Some(f32::min(t0, t1)), Some(f32::max(t0, t1))), + }; + + const EPSILON: f32 = 0.001; + } +} diff --git a/utils/tile-svg/src/main.rs b/utils/tile-svg/src/main.rs index 86488ab0..86a3438d 100644 --- a/utils/tile-svg/src/main.rs +++ b/utils/tile-svg/src/main.rs @@ -29,6 +29,7 @@ use lyon_path::PathEvent; use lyon_path::iterator::PathIter; use pathfinder_geometry::line_segment::{LineSegmentF32, LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::point::Point2DF32; +use pathfinder_geometry::segment::{Segment, SegmentFlags, SegmentKind}; use pathfinder_geometry::stroke::{StrokeStyle, StrokeToFillIter}; use pathfinder_geometry::util; use rayon::ThreadPoolBuilder; @@ -648,257 +649,6 @@ impl<'a> Iterator for ContourIter<'a> { } } -#[derive(Clone, Copy, Debug, PartialEq)] -struct Segment { - baseline: LineSegmentF32, - ctrl: LineSegmentF32, - kind: SegmentKind, - flags: SegmentFlags, -} - -impl Segment { - fn none() -> Segment { - Segment { - baseline: LineSegmentF32::default(), - ctrl: LineSegmentF32::default(), - kind: SegmentKind::None, - flags: SegmentFlags::empty(), - } - } - - fn line(line: &LineSegmentF32) -> Segment { - Segment { - baseline: *line, - ctrl: LineSegmentF32::default(), - kind: SegmentKind::Line, - flags: SegmentFlags::empty(), - } - } - - fn quadratic(baseline: &LineSegmentF32, ctrl: &Point2DF32) -> Segment { - Segment { - baseline: *baseline, - ctrl: LineSegmentF32::new(ctrl, &Point2DF32::default()), - kind: SegmentKind::Cubic, - flags: SegmentFlags::empty(), - } - } - - fn cubic(baseline: &LineSegmentF32, ctrl: &LineSegmentF32) -> Segment { - Segment { - baseline: *baseline, - ctrl: *ctrl, - kind: SegmentKind::Cubic, - flags: SegmentFlags::empty(), - } - } - - fn as_line_segment(&self) -> LineSegmentF32 { - debug_assert!(self.is_line()); - self.baseline - } - - fn is_none(&self) -> bool { self.kind == SegmentKind::None } - fn is_line(&self) -> bool { self.kind == SegmentKind::Line } - fn is_quadratic(&self) -> bool { self.kind == SegmentKind::Quadratic } - fn is_cubic(&self) -> bool { self.kind == SegmentKind::Cubic } - - fn as_cubic_segment(&self) -> CubicSegment { - debug_assert!(self.is_cubic()); - CubicSegment(self) - } - - // FIXME(pcwalton): We should basically never use this function. - // FIXME(pcwalton): Handle lines! - fn to_cubic(&self) -> Segment { - if self.is_cubic() { - return *self; - } - - let mut new_segment = *self; - let p1_2 = self.ctrl.from() + self.ctrl.from(); - new_segment.ctrl = LineSegmentF32::new(&(self.baseline.from() + p1_2), - &(p1_2 + self.baseline.to())).scale(1.0 / 3.0); - new_segment - } - - fn reversed(&self) -> Segment { - Segment { - baseline: self.baseline.reversed(), - ctrl: if self.is_quadratic() { self.ctrl } else { self.ctrl.reversed() }, - kind: self.kind, - flags: self.flags, - } - } - - // Reverses if necessary so that the from point is above the to point. Calling this method - // again will undo the transformation. - fn orient(&self, y_winding: i32) -> Segment { - if y_winding >= 0 { - *self - } else { - self.reversed() - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -#[repr(u8)] -enum SegmentKind { - None, - Line, - Quadratic, - Cubic, -} - -bitflags! { - struct SegmentFlags: u8 { - const FIRST_IN_SUBPATH = 0x01; - const CLOSES_SUBPATH = 0x02; - } -} - -#[derive(Clone, Copy, Debug)] -struct CubicSegment<'s>(&'s Segment); - -impl<'s> CubicSegment<'s> { - fn flatten_once(self) -> Option { - let s2inv; - unsafe { - let (baseline, ctrl) = (self.0.baseline.0, self.0.ctrl.0); - let from_from = Sse41::shuffle_ps(baseline, baseline, 0b0100_0100); - - let v0102 = Sse41::sub_ps(ctrl, from_from); - - // v01.x v01.y v02.x v02.y - // * v01.x v01.y v01.y v01.x - // ------------------------- - // v01.x^2 v01.y^2 ad bc - // | | | | - // +-------+ +-----+ - // + - - // v01 len^2 determinant - let products = Sse41::mul_ps(v0102, Sse41::shuffle_ps(v0102, v0102, 0b0001_0100)); - - let det = products[2] - products[3]; - if det == 0.0 { - return None; - } - - s2inv = (products[0] + products[1]).sqrt() / det; - } - - let t = 2.0 * ((FLATTENING_TOLERANCE / 3.0) * s2inv.abs()).sqrt(); - if t >= 1.0 - EPSILON || t == 0.0 { - return None; - } - - return Some(self.split_after(t)); - - const EPSILON: f32 = 0.005; - } - - fn split(self, t: f32) -> (Segment, Segment) { - unsafe { - let tttt = Sse41::set1_ps(t); - - let p0p3 = self.0.baseline.0; - let p1p2 = self.0.ctrl.0; - let p0p1 = assemble(&p0p3, &p1p2, 0, 0); - - // p01 = lerp(p0, p1, t), p12 = lerp(p1, p2, t), p23 = lerp(p2, p3, t) - let p01p12 = Sse41::add_ps(p0p1, Sse41::mul_ps(tttt, Sse41::sub_ps(p1p2, p0p1))); - let pxxp23 = Sse41::add_ps(p1p2, Sse41::mul_ps(tttt, Sse41::sub_ps(p0p3, p1p2))); - - let p12p23 = assemble(&p01p12, &pxxp23, 1, 1); - - // p012 = lerp(p01, p12, t), p123 = lerp(p12, p23, t) - let p012p123 = Sse41::add_ps(p01p12, Sse41::mul_ps(tttt, - Sse41::sub_ps(p12p23, p01p12))); - - let p123 = pluck(&p012p123, 1); - - // p0123 = lerp(p012, p123, t) - let p0123 = Sse41::add_ps(p012p123, - Sse41::mul_ps(tttt, Sse41::sub_ps(p123, p012p123))); - - let baseline0 = assemble(&p0p3, &p0123, 0, 0); - let ctrl0 = assemble(&p01p12, &p012p123, 0, 0); - let baseline1 = assemble(&p0123, &p0p3, 0, 1); - let ctrl1 = assemble(&p012p123, &p12p23, 1, 1); - - // FIXME(pcwalton): Set flags appropriately! - return (Segment { - baseline: LineSegmentF32(baseline0), - ctrl: LineSegmentF32(ctrl0), - kind: SegmentKind::Cubic, - flags: self.0.flags & SegmentFlags::FIRST_IN_SUBPATH, - }, Segment { - baseline: LineSegmentF32(baseline1), - ctrl: LineSegmentF32(ctrl1), - kind: SegmentKind::Cubic, - flags: self.0.flags & SegmentFlags::CLOSES_SUBPATH, - }) - } - - // Constructs a new 4-element vector from two pairs of adjacent lanes in two input vectors. - unsafe fn assemble(a_data: &::Vf32, - b_data: &::Vf32, - a_index: usize, - b_index: usize) - -> ::Vf32 { - let (a_data, b_data) = (Sse41::castps_pd(*a_data), Sse41::castps_pd(*b_data)); - let mut result = Sse41::setzero_pd(); - result[0] = a_data[a_index]; - result[1] = b_data[b_index]; - Sse41::castpd_ps(result) - } - - // Constructs a new 2-element vector from a pair of adjacent lanes in an input vector. - unsafe fn pluck(data: &::Vf32, index: usize) -> ::Vf32 { - let data = Sse41::castps_pd(*data); - let mut result = Sse41::setzero_pd(); - result[0] = data[index]; - Sse41::castpd_ps(result) - } - } - - fn split_after(self, t: f32) -> Segment { - self.split(t).1 - } - - fn y_extrema(self) -> (Option, Option) { - let (t0, t1); - unsafe { - let mut p0p1p2p3 = Sse41::setzero_ps(); - p0p1p2p3[0] = self.0.baseline.from_y(); - p0p1p2p3[1] = self.0.ctrl.from_y(); - p0p1p2p3[2] = self.0.ctrl.to_y(); - p0p1p2p3[3] = self.0.baseline.to_y(); - - let pxp0p1p2 = Sse41::shuffle_ps(p0p1p2p3, p0p1p2p3, 0b1001_0000); - let pxv0v1v2 = Sse41::sub_ps(p0p1p2p3, pxp0p1p2); - let (v0, v1, v2) = (pxv0v1v2[1], pxv0v1v2[2], pxv0v1v2[3]); - - let (v0_to_v1, v2_to_v1) = (v0 - v1, v2 - v1); - let discrim = f32::sqrt(v1 * v1 - v0 * v2); - let denom = 1.0 / (v0_to_v1 + v2_to_v1); - - t0 = (v0_to_v1 + discrim) * denom; - t1 = (v0_to_v1 - discrim) * denom; - } - - return match (t0 > EPSILON && t0 < 1.0 - EPSILON, t1 > EPSILON && t1 < 1.0 - EPSILON) { - (false, false) => (None, None), - (true, false) => (Some(t0), None), - (false, true) => (Some(t1), None), - (true, true) => (Some(f32::min(t0, t1)), Some(f32::max(t0, t1))), - }; - - const EPSILON: f32 = 0.001; - } -} - // Tiling const TILE_WIDTH: u32 = 16; @@ -2119,7 +1869,9 @@ impl ActiveEdge { } loop { - let rest_segment = match segment.orient(winding).as_cubic_segment().flatten_once() { + let rest_segment = match segment.orient(winding) + .as_cubic_segment() + .flatten_once(FLATTENING_TOLERANCE) { None => { let line_segment = segment.baseline; self.segment = match self.process_line_segment(&line_segment,