Skip to content

Commit

Permalink
Clip line segments before tiling them.
Browse files Browse the repository at this point in the history
This reduces pathological behavior from very large off-screen segments.

Part of rust-windowing#416.
  • Loading branch information
pcwalton committed Jul 28, 2020
1 parent 4c8699a commit 41ad372
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 2 deletions.
76 changes: 75 additions & 1 deletion content/src/clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<LineSegment2F> {
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;
Expand Down
2 changes: 1 addition & 1 deletion renderer/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
10 changes: 10 additions & 0 deletions renderer/src/tiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ 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;
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;

Expand Down Expand Up @@ -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;

Expand Down

0 comments on commit 41ad372

Please sign in to comment.