From 02928f295d961088dae30da3e7d63dcd913455e0 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Jan 2019 13:49:26 -0800 Subject: [PATCH] Basic rect clipping --- demo3/src/main.rs | 12 +++- geometry/src/clip.rs | 65 +++++++++++++++++++++ geometry/src/outline.rs | 116 ++++++++++++++++++++------------------ geometry/src/transform.rs | 26 ++++++++- renderer/src/gpu_data.rs | 2 + renderer/src/scene.rs | 18 ++++-- renderer/src/tiles.rs | 3 +- 7 files changed, 177 insertions(+), 65 deletions(-) diff --git a/demo3/src/main.rs b/demo3/src/main.rs index c944d979..2baa196c 100644 --- a/demo3/src/main.rs +++ b/demo3/src/main.rs @@ -47,7 +47,7 @@ const MASK_TILE_INSTANCE_SIZE: GLint = 8; const MASK_FRAMEBUFFER_WIDTH: u32 = TILE_WIDTH * 256; const MASK_FRAMEBUFFER_HEIGHT: u32 = TILE_HEIGHT * 256; -const MAIN_FRAMEBUFFER_WIDTH: u32 = 800; +const MAIN_FRAMEBUFFER_WIDTH: u32 = 1067; const MAIN_FRAMEBUFFER_HEIGHT: u32 = 800; const FILL_COLORS_TEXTURE_WIDTH: u32 = 256; @@ -81,11 +81,15 @@ fn main() { let mut renderer = Renderer::new(&Size2D::new(drawable_width, drawable_height)); let mut scale = 1.0; + //let mut theta = 0.0; while !exit { let mut scene = base_scene.clone(); - scene.transform(&Transform2DF32::from_scale(&Point2DF32::new(scale, scale))); - scale -= 0.1; + //scene.transform(&Transform2DF32::from_rotation(theta)); + scene.transform(&Transform2DF32::from_scale(&Point2DF32::splat(scale))); + //theta += 0.01; + //scale -= 0.0003; + scale += 0.0001; let built_scene = build_scene(&scene, &options); @@ -189,6 +193,7 @@ fn build_scene(scene: &Scene, options: &Options) -> BuiltScene { let total_elapsed_time = elapsed_object_build_time + elapsed_scene_build_time; + /* println!( "{:.3}ms ({:.3}ms objects, {:.3}ms scene) elapsed", total_elapsed_time, elapsed_object_build_time, elapsed_scene_build_time @@ -203,6 +208,7 @@ fn build_scene(scene: &Scene, options: &Options) -> BuiltScene { batch.mask_tiles.len() ); } + */ built_scene } diff --git a/geometry/src/clip.rs b/geometry/src/clip.rs index b46dce2b..91533095 100644 --- a/geometry/src/clip.rs +++ b/geometry/src/clip.rs @@ -8,6 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use crate::outline::{Contour, PointFlags}; +use crate::point::Point2DF32; use euclid::{Point2D, Rect, Vector3D}; use lyon_path::PathEvent; use std::mem; @@ -115,3 +117,66 @@ impl Edge { Point2D::new(intersection.x / intersection.z, intersection.y / intersection.z) } } + +pub(crate) struct ContourRectClipper { + clip_rect: Rect, + contour: Contour, +} + +impl ContourRectClipper { + #[inline] + pub(crate) fn new(clip_rect: &Rect, contour: Contour) -> ContourRectClipper { + ContourRectClipper { clip_rect: *clip_rect, contour } + } + + pub(crate) fn clip(mut self) -> Contour { + if self.clip_rect.contains_rect(&self.contour.bounds()) { + return self.contour + } + + self.clip_against(Edge::Left(self.clip_rect.origin.x)); + self.clip_against(Edge::Top(self.clip_rect.origin.y)); + self.clip_against(Edge::Right(self.clip_rect.max_x())); + self.clip_against(Edge::Bottom(self.clip_rect.max_y())); + self.contour + } + + fn clip_against(&mut self, edge: Edge) { + let (mut from, mut first_point) = (Point2D::zero(), false); + let input = mem::replace(&mut self.contour, Contour::new()); + for event in input.iter() { + let to = match event { + PathEvent::MoveTo(to) => { + from = to; + first_point = true; + continue + } + PathEvent::Close => break, + PathEvent::LineTo(to) | + PathEvent::QuadraticTo(_, to) | + PathEvent::CubicTo(_, _, to) => to, + PathEvent::Arc(..) => panic!("Arcs unsupported!"), + }; + + if edge.point_is_inside(&to) { + if !edge.point_is_inside(&from) { + //println!("clip: {:?} {:?}", from, to); + add_line(&edge.line_intersection(&from, &to), + &mut self.contour, + &mut first_point); + } + add_line(&to, &mut self.contour, &mut first_point); + } else if edge.point_is_inside(&from) { + //println!("clip: {:?} {:?}", from, to); + add_line(&edge.line_intersection(&from, &to), &mut self.contour, &mut first_point); + } + + from = to; + } + + fn add_line(to: &Point2D, output: &mut Contour, first_point: &mut bool) { + output.push_point(Point2DF32::from_euclid(*to), PointFlags::empty()); + *first_point = false; + } + } +} diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index deaf439c..0186c4af 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -10,10 +10,11 @@ //! A compressed in-memory representation of paths. +use crate::clip::ContourRectClipper; use crate::point::Point2DF32; use crate::segment::{Segment, SegmentFlags, SegmentKind}; use crate::transform::Transform2DF32; -use euclid::{Point2D, Rect}; +use euclid::{Point2D, Rect, Size2D}; use lyon_path::PathEvent; use std::fmt::{self, Debug, Formatter}; use std::mem; @@ -28,6 +29,7 @@ pub struct Outline { pub struct Contour { points: Vec, flags: Vec, + bounds: Rect, } bitflags! { @@ -53,7 +55,6 @@ impl Outline { { let mut outline = Outline::new(); let mut current_contour = Contour::new(); - let mut bounding_points = None; for segment in segments { if segment.flags.contains(SegmentFlags::FIRST_IN_SUBPATH) { @@ -62,18 +63,12 @@ impl Outline { .contours .push(mem::replace(&mut current_contour, Contour::new())); } - current_contour.push_point( - segment.baseline.from(), - PointFlags::empty(), - &mut bounding_points, - ); + current_contour.push_point(segment.baseline.from(), PointFlags::empty()); } if segment.flags.contains(SegmentFlags::CLOSES_SUBPATH) { if !current_contour.is_empty() { - outline - .contours - .push(mem::replace(&mut current_contour, Contour::new())); + outline.push_contour(mem::replace(&mut current_contour, Contour::new())); } continue; } @@ -83,34 +78,17 @@ impl Outline { } if !segment.is_line() { - current_contour.push_point( - segment.ctrl.from(), - PointFlags::CONTROL_POINT_0, - &mut bounding_points, - ); + current_contour.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0); if !segment.is_quadratic() { - current_contour.push_point( - segment.ctrl.to(), - PointFlags::CONTROL_POINT_1, - &mut bounding_points, - ); + current_contour.push_point(segment.ctrl.to(), PointFlags::CONTROL_POINT_1); } } - current_contour.push_point( - segment.baseline.to(), - PointFlags::empty(), - &mut bounding_points, - ); + current_contour.push_point(segment.baseline.to(), PointFlags::empty()); } 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.as_euclid(), lower_right.as_euclid()].iter()) + outline.push_contour(current_contour); } outline @@ -124,24 +102,38 @@ impl Outline { #[inline] pub fn transform(&mut self, transform: &Transform2DF32) { self.contours.iter_mut().for_each(|contour| contour.transform(transform)); + self.bounds = transform.transform_rect(&self.bounds); + } + + #[inline] + fn push_contour(&mut self, contour: Contour) { + if self.contours.is_empty() { + self.bounds = contour.bounds; + } else { + self.bounds = self.bounds.union(&contour.bounds); + } + self.contours.push(contour); + } + + pub fn clip_against_rect(&mut self, clip_rect: &Rect) { + for contour in mem::replace(&mut self.contours, vec![]) { + let contour = ContourRectClipper::new(clip_rect, contour).clip(); + if !contour.is_empty() { + self.push_contour(contour); + } + } } } impl Contour { #[inline] pub fn new() -> Contour { - Contour { - points: vec![], - flags: vec![], - } + Contour { points: vec![], flags: vec![], bounds: Rect::zero() } } #[inline] pub fn iter(&self) -> ContourIter { - ContourIter { - contour: self, - index: 0, - } + ContourIter { contour: self, index: 0 } } #[inline] @@ -154,29 +146,24 @@ impl Contour { self.points.len() as u32 } + #[inline] + pub fn bounds(&self) -> &Rect { + &self.bounds + } + #[inline] pub fn position_of(&self, index: u32) -> Point2DF32 { self.points[index as usize] } - // TODO(pcwalton): Pack both min and max into a single SIMD register? + // TODO(pcwalton): SIMD. #[inline] - fn push_point( - &mut self, - point: Point2DF32, - flags: PointFlags, - bounding_points: &mut Option<(Point2DF32, Point2DF32)>, - ) { + pub(crate) fn push_point(&mut self, point: Point2DF32, flags: PointFlags) { + let first = self.is_empty(); + union_rect(&mut self.bounds, point, first); + 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)), - } } #[inline] @@ -271,8 +258,9 @@ impl Contour { #[inline] pub fn transform(&mut self, transform: &Transform2DF32) { - for point in &mut self.points { - *point = transform.transform_point(point) + for (point_index, point) in self.points.iter_mut().enumerate() { + *point = transform.transform_point(point); + union_rect(&mut self.bounds, *point, point_index == 0); } } } @@ -408,3 +396,19 @@ impl<'a> Iterator for ContourIter<'a> { )) } } + +#[inline] +fn union_rect(bounds: &mut Rect, new_point: Point2DF32, first: bool) { + if first { + *bounds = Rect::new(new_point.as_euclid(), Size2D::zero()); + return; + } + + let (mut min_x, mut min_y) = (bounds.origin.x, bounds.origin.y); + let (mut max_x, mut max_y) = (bounds.max_x(), bounds.max_y()); + min_x = min_x.min(new_point.x()); + min_y = min_y.min(new_point.y()); + max_x = max_x.max(new_point.x()); + max_y = max_y.max(new_point.y()); + *bounds = Rect::new(Point2D::new(min_x, min_y), Size2D::new(max_x - min_x, max_y - min_y)); +} diff --git a/geometry/src/transform.rs b/geometry/src/transform.rs index 1f7cff0e..f490e2be 100644 --- a/geometry/src/transform.rs +++ b/geometry/src/transform.rs @@ -13,7 +13,7 @@ use crate::point::Point2DF32; use crate::segment::Segment; use crate::simd::F32x4; -use euclid::Transform2D; +use euclid::{Point2D, Rect, Size2D, Transform2D}; use lyon_path::PathEvent; /// An affine transform, optimized with SIMD. @@ -40,6 +40,15 @@ impl Transform2DF32 { } } + #[inline] + pub fn from_rotation(theta: f32) -> Transform2DF32 { + let (sin_theta, cos_theta) = (theta.sin(), theta.cos()); + Transform2DF32 { + matrix: F32x4::new(cos_theta, -sin_theta, sin_theta, cos_theta), + vector: Point2DF32::default(), + } + } + #[inline] pub fn row_major(m11: f32, m12: f32, m21: f32, m22: f32, m31: f32, m32: f32) -> Transform2DF32 { @@ -55,6 +64,21 @@ impl Transform2DF32 { Point2DF32(x11x12y21y22 + x11x12y21y22.zwzw() + self.vector.0) } + // TODO(pcwalton): SIMD. + #[inline] + pub fn transform_rect(&self, rect: &Rect) -> Rect { + let upper_left = self.transform_point(&Point2DF32::from_euclid(rect.origin)); + let upper_right = self.transform_point(&Point2DF32::from_euclid(rect.top_right())); + let lower_left = self.transform_point(&Point2DF32::from_euclid(rect.bottom_left())); + let lower_right = self.transform_point(&Point2DF32::from_euclid(rect.bottom_right())); + let min_x = upper_left.x().min(upper_right.x()).min(lower_left.x()).min(lower_right.x()); + let min_y = upper_left.y().min(upper_right.y()).min(lower_left.y()).min(lower_right.y()); + let max_x = upper_left.x().max(upper_right.x()).max(lower_left.x()).max(lower_right.x()); + let max_y = upper_left.y().max(upper_right.y()).max(lower_left.y()).max(lower_right.y()); + let (width, height) = (max_x - min_x, max_y - min_y); + Rect::new(Point2D::new(min_x, min_y), Size2D::new(width, height)) + } + #[inline] pub fn post_mul(&self, other: &Transform2DF32) -> Transform2DF32 { let lhs = self.matrix.xzxz() * other.matrix.xxyy(); diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index 816a28ce..c1cc54fb 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -215,6 +215,8 @@ impl BuiltObject { let segment_tile_left = (f32::floor(segment_left) as i32 / TILE_WIDTH as i32) as i16; let segment_tile_right = util::alignup_i32(f32::ceil(segment_right) as i32, TILE_WIDTH as i32) as i16; + /*println!("segment_tile_left={} segment_tile_right={} tile_rect={:?}", + segment_tile_left, segment_tile_right, self.tile_rect);*/ for subsegment_tile_x in segment_tile_left..segment_tile_right { let (mut fill_from, mut fill_to) = (segment.from(), segment.to()); diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index 96d2079a..e23bbe48 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -99,10 +99,20 @@ impl Scene { } pub fn transform(&mut self, transform: &Transform2DF32) { - // TODO(pcwalton): Transform bounds? - for object in &mut self.objects { - object.outline.transform(transform) + let mut bounds = Rect::zero(); + for (object_index, object) in self.objects.iter_mut().enumerate() { + object.outline.transform(transform); + object.outline.clip_against_rect(&self.view_box); + + if object_index == 0 { + bounds = *object.outline.bounds(); + } else { + bounds = bounds.union(object.outline.bounds()); + } } + + //println!("new bounds={:?}", bounds); + self.bounds = bounds; } } @@ -137,4 +147,4 @@ impl PathObject { pub fn scene_tile_index(tile_x: i16, tile_y: i16, tile_rect: Rect) -> u32 { (tile_y - tile_rect.origin.y) as u32 * tile_rect.size.width as u32 + (tile_x - tile_rect.origin.x) as u32 -} \ No newline at end of file +} diff --git a/renderer/src/tiles.rs b/renderer/src/tiles.rs index c7c2cd25..4ef00b19 100644 --- a/renderer/src/tiles.rs +++ b/renderer/src/tiles.rs @@ -116,6 +116,7 @@ impl<'o, 'z> Tiler<'o, 'z> { debug_assert!(self.old_active_edges.is_empty()); mem::swap(&mut self.old_active_edges, &mut self.active_edges.array); + // FIXME(pcwalton): Yuck. let mut last_segment_x = -9999.0; let tile_top = (i32::from(tile_y) * TILE_HEIGHT as i32) as f32; @@ -178,7 +179,7 @@ impl<'o, 'z> Tiler<'o, 'z> { // Do final subtile fill, if necessary. debug_assert_eq!(current_tile_x, segment_tile_x); - debug_assert!(current_tile_x < self.built_object.tile_rect.max_x()); + debug_assert!(current_tile_x <= self.built_object.tile_rect.max_x()); let segment_subtile_x = segment_x - (i32::from(current_tile_x) * TILE_WIDTH as i32) as f32; if segment_subtile_x > current_subtile_x {