From 41ad372253b7104f4934e4cec02a96ef219a7a65 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 28 Jul 2020 12:56:05 -0700 Subject: [PATCH 1/2] Clip line segments before tiling them. This reduces pathological behavior from very large off-screen segments. Part of #416. --- content/src/clip.rs | 76 ++++++++++++++++++++++++++++++++++++++++- renderer/src/builder.rs | 2 +- renderer/src/tiler.rs | 10 ++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/content/src/clip.rs b/content/src/clip.rs index b92abe52..34fee613 100644 --- a/content/src/clip.rs +++ b/content/src/clip.rs @@ -18,7 +18,7 @@ use arrayvec::ArrayVec; use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::util::lerp; -use pathfinder_geometry::vector::{Vector2F, Vector4F}; +use pathfinder_geometry::vector::{Vector2F, Vector4F, vec2f}; use smallvec::SmallVec; use std::fmt::Debug; use std::mem; @@ -490,6 +490,80 @@ pub(crate) fn rect_is_inside_polygon(rect: RectF, polygon_points: &[Vector2F]) - true } +/// Clips a line segment to an axis-aligned rectangle using Cohen-Sutherland clipping. +pub fn clip_line_segment_to_rect(mut line_segment: LineSegment2F, rect: RectF) + -> Option { + let mut outcode_from = compute_outcode(line_segment.from(), rect); + let mut outcode_to = compute_outcode(line_segment.to(), rect); + + loop { + if outcode_from.is_empty() && outcode_to.is_empty() { + return Some(line_segment); + } + if !(outcode_from & outcode_to).is_empty() { + return None; + } + + let clip_from = outcode_from.bits() > outcode_to.bits(); + let (mut point, outcode) = if clip_from { + (line_segment.from(), outcode_from) + } else { + (line_segment.to(), outcode_to) + }; + + if outcode.contains(Outcode::LEFT) { + point = vec2f(rect.min_x(), + lerp(line_segment.from_y(), + line_segment.to_y(), + (line_segment.min_x() - line_segment.from_x()) / + (line_segment.max_x() - line_segment.min_x()))); + } else if outcode.contains(Outcode::RIGHT) { + point = vec2f(rect.max_x(), + lerp(line_segment.from_y(), + line_segment.to_y(), + (line_segment.max_x() - line_segment.from_x()) / + (line_segment.max_x() - line_segment.min_x()))); + } else if outcode.contains(Outcode::TOP) { + point = vec2f(lerp(line_segment.from_x(), + line_segment.to_x(), + (line_segment.min_y() - line_segment.from_y()) / + (line_segment.max_y() - line_segment.min_y())), + rect.min_y()); + } else if outcode.contains(Outcode::LEFT) { + point = vec2f(lerp(line_segment.from_x(), + line_segment.to_x(), + (line_segment.max_y() - line_segment.from_y()) / + (line_segment.max_y() - line_segment.min_y())), + rect.min_y()); + } + + if clip_from { + line_segment.set_from(point); + outcode_from = compute_outcode(point, rect); + } else { + line_segment.set_to(point); + outcode_to = compute_outcode(point, rect); + } + } + + fn compute_outcode(point: Vector2F, rect: RectF) -> Outcode { + let mut outcode = Outcode::empty(); + if point.x() < rect.min_x() { + outcode.insert(Outcode::LEFT); + } + if point.y() < rect.min_y() { + outcode.insert(Outcode::TOP); + } + if point.x() > rect.max_x() { + outcode.insert(Outcode::RIGHT); + } + if point.y() > rect.max_y() { + outcode.insert(Outcode::BOTTOM); + } + outcode + } +} + bitflags! { struct Outcode: u8 { const LEFT = 0x01; diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index 904d4f97..0a391cc2 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -50,7 +50,7 @@ const CURVE_IS_CUBIC: u32 = 0x40000000; const MAX_CLIP_BATCHES: u32 = 32; pub(crate) struct SceneBuilder<'a, 'b, 'c, 'd> { - scene: &'a mut Scene, + pub(crate) scene: &'a mut Scene, built_options: &'b PreparedBuildOptions, next_alpha_tile_indices: [AtomicUsize; ALPHA_TILE_LEVEL_COUNT], pub(crate) sink: &'c mut SceneSink<'d>, diff --git a/renderer/src/tiler.rs b/renderer/src/tiler.rs index f2a088a2..f7a021d0 100644 --- a/renderer/src/tiler.rs +++ b/renderer/src/tiler.rs @@ -17,6 +17,7 @@ use crate::gpu_data::AlphaTileId; use crate::options::PrepareMode; use crate::scene::{ClipPathId, PathId}; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH, TilingPathInfo}; +use pathfinder_content::clip; use pathfinder_content::fill::FillRule; use pathfinder_content::outline::{ContourIterFlags, Outline}; use pathfinder_content::segment::Segment; @@ -24,6 +25,7 @@ use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i}; use pathfinder_simd::default::{F32x2, U32x2}; +use std::f32::NEG_INFINITY; const FLATTENING_TOLERANCE: f32 = 0.25; @@ -189,6 +191,14 @@ fn process_segment(segment: &Segment, fn process_line_segment(line_segment: LineSegment2F, scene_builder: &SceneBuilder, object_builder: &mut ObjectBuilder) { + let view_box = scene_builder.scene.view_box(); + let clip_box = RectF::from_points(vec2f(view_box.min_x(), NEG_INFINITY), + view_box.lower_right()); + let line_segment = match clip::clip_line_segment_to_rect(line_segment, clip_box) { + None => return, + Some(line_segment) => line_segment, + }; + let tile_size = vec2f(TILE_WIDTH as f32, TILE_HEIGHT as f32); let tile_size_recip = Vector2F::splat(1.0) / tile_size; From 441051a8ac1c5460eddca80a54d78e55985ee0f4 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 28 Jul 2020 12:56:40 -0700 Subject: [PATCH 2/2] Remove the cap on the number of iterations when tiling. This could trigger spuriously for very long lines outside the view box. It still indicates potential performance problem, but we shouldn't crash at least. Closes #416. --- renderer/src/tiler.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/renderer/src/tiler.rs b/renderer/src/tiler.rs index f7a021d0..32a1fc4c 100644 --- a/renderer/src/tiler.rs +++ b/renderer/src/tiler.rs @@ -224,12 +224,8 @@ fn process_line_segment(line_segment: LineSegment2F, let (mut current_position, mut tile_coords) = (line_segment.from(), from_tile_coords); let mut last_step_direction = None; - let mut iteration = 0; loop { - // Quick check to catch missing the end tile. - debug_assert!(iteration < MAX_ITERATIONS); - let next_step_direction = if t_max.x() < t_max.y() { StepDirection::X } else if t_max.x() > t_max.y() { @@ -302,11 +298,7 @@ fn process_line_segment(line_segment: LineSegment2F, current_position = next_position; last_step_direction = next_step_direction; - - iteration += 1; } - - const MAX_ITERATIONS: u32 = 1024; } #[derive(Clone, Copy, PartialEq, Debug)]