From 41ad372253b7104f4934e4cec02a96ef219a7a65 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 28 Jul 2020 12:56:05 -0700 Subject: [PATCH] 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;