Auto merge of #340 - pcwalton:new-tiling, r=pcwalton

Replace the tiling algorithm with the one from "Random Access Vector Graphics".

This is a significant improvement in CPU time, as well as an overall
simplification.
This commit is contained in:
bors-servo 2020-05-15 22:38:27 -04:00 committed by GitHub
commit 28c4bc194d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 333 additions and 855 deletions

View File

@ -41,38 +41,6 @@ impl TEdge for Edge {
} }
} }
#[derive(Clone, Copy, Debug)]
enum AxisAlignedEdge {
Left(f32),
Top(f32),
Right(f32),
Bottom(f32),
}
impl TEdge for AxisAlignedEdge {
#[inline]
fn point_is_inside(&self, point: Vector2F) -> bool {
match *self {
AxisAlignedEdge::Left(x) => point.x() >= x,
AxisAlignedEdge::Top(y) => point.y() >= y,
AxisAlignedEdge::Right(x) => point.x() <= x,
AxisAlignedEdge::Bottom(y) => point.y() <= y,
}
}
fn intersect_line_segment(&self, segment: LineSegment2F) -> ArrayVec<[f32; 3]> {
let mut results = ArrayVec::new();
let t = match *self {
AxisAlignedEdge::Left(x) | AxisAlignedEdge::Right(x) => segment.solve_t_for_x(x),
AxisAlignedEdge::Top(y) | AxisAlignedEdge::Bottom(y) => segment.solve_t_for_y(y),
};
if t >= 0.0 && t <= 1.0 {
results.push(t);
}
results
}
}
trait TEdge: Debug { trait TEdge: Debug {
fn point_is_inside(&self, point: Vector2F) -> bool; fn point_is_inside(&self, point: Vector2F) -> bool;
fn intersect_line_segment(&self, segment: LineSegment2F) -> ArrayVec<[f32; 3]>; fn intersect_line_segment(&self, segment: LineSegment2F) -> ArrayVec<[f32; 3]>;
@ -340,42 +308,6 @@ enum EdgeRelativeLocation {
Outside, Outside,
} }
// Fast axis-aligned box 2D clipping
pub(crate) struct ContourRectClipper {
clip_rect: RectF,
contour: Contour,
}
impl ContourClipper for ContourRectClipper {
type Edge = AxisAlignedEdge;
#[inline]
fn contour_mut(&mut self) -> &mut Contour {
&mut self.contour
}
}
impl ContourRectClipper {
#[inline]
pub(crate) fn new(clip_rect: RectF, contour: Contour) -> ContourRectClipper {
ContourRectClipper { 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(AxisAlignedEdge::Left(self.clip_rect.min_x()));
self.clip_against(AxisAlignedEdge::Top(self.clip_rect.min_y()));
self.clip_against(AxisAlignedEdge::Right(self.clip_rect.max_x()));
self.clip_against(AxisAlignedEdge::Bottom(self.clip_rect.max_y()));
self.contour
}
}
// 3D quad clipping // 3D quad clipping
pub struct PolygonClipper3D { pub struct PolygonClipper3D {

View File

@ -10,7 +10,7 @@
//! A compressed in-memory representation of paths. //! A compressed in-memory representation of paths.
use crate::clip::{self, ContourPolygonClipper, ContourRectClipper}; use crate::clip::{self, ContourPolygonClipper};
use crate::dilation::ContourDilator; use crate::dilation::ContourDilator;
use crate::orientation::Orientation; use crate::orientation::Orientation;
use crate::segment::{Segment, SegmentFlags, SegmentKind}; use crate::segment::{Segment, SegmentFlags, SegmentKind};
@ -195,16 +195,6 @@ impl Outline {
self.bounds = self.bounds.dilate(amount); self.bounds = self.bounds.dilate(amount);
} }
pub fn prepare_for_tiling(&mut self, view_box: RectF) {
self.contours
.iter_mut()
.for_each(|contour| contour.prepare_for_tiling(view_box));
self.bounds = self
.bounds
.intersection(view_box)
.unwrap_or_else(|| RectF::default());
}
pub fn is_outside_polygon(&self, clip_polygon: &[Vector2F]) -> bool { pub fn is_outside_polygon(&self, clip_polygon: &[Vector2F]) -> bool {
clip::rect_is_outside_polygon(self.bounds, clip_polygon) clip::rect_is_outside_polygon(self.bounds, clip_polygon)
} }
@ -224,16 +214,6 @@ impl Outline {
} }
} }
pub fn clip_against_rect(&mut self, clip_rect: RectF) {
if clip_rect.contains_rect(self.bounds) {
return;
}
for contour in mem::replace(&mut self.contours, vec![]) {
self.push_contour(ContourRectClipper::new(clip_rect, contour).clip());
}
}
#[inline] #[inline]
pub fn close_all_contours(&mut self) { pub fn close_all_contours(&mut self) {
self.contours.iter_mut().for_each(|contour| contour.close()); self.contours.iter_mut().for_each(|contour| contour.close());
@ -621,149 +601,6 @@ impl Contour {
self.bounds = self.bounds.dilate(amount); self.bounds = self.bounds.dilate(amount);
} }
fn prepare_for_tiling(&mut self, view_box: RectF) {
// Snap points to the view box bounds. This mops up floating point error from the clipping
// process.
let (mut last_endpoint_index, mut contour_is_monotonic) = (None, true);
for point_index in 0..(self.points.len() as u32) {
if contour_is_monotonic {
if self.point_is_endpoint(point_index) {
if let Some(last_endpoint_index) = last_endpoint_index {
if !self.curve_with_endpoints_is_monotonic(last_endpoint_index,
point_index) {
contour_is_monotonic = false;
}
}
last_endpoint_index = Some(point_index);
}
}
}
// Convert to monotonic, if necessary.
if !contour_is_monotonic {
self.make_monotonic();
}
// Update bounds.
self.bounds = self
.bounds
.intersection(view_box)
.unwrap_or_else(|| RectF::default());
}
fn make_monotonic(&mut self) {
debug!("--- make_monotonic() ---");
let contour = self.take();
self.bounds = contour.bounds;
let mut last_endpoint_index = None;
let input_point_count = contour.points.len() as u32;
for point_index in 0..(input_point_count + 1) {
if point_index < input_point_count && !contour.point_is_endpoint(point_index) {
continue;
}
if let Some(last_endpoint_index) = last_endpoint_index {
let position_index = if point_index == input_point_count {
0
} else {
point_index
};
let baseline = LineSegment2F::new(
contour.points[last_endpoint_index as usize],
contour.points[position_index as usize],
);
let point_count = point_index - last_endpoint_index + 1;
if point_count == 3 {
let ctrl_point_index = last_endpoint_index as usize + 1;
let ctrl_position = &contour.points[ctrl_point_index];
handle_cubic(
self,
&Segment::quadratic(baseline, *ctrl_position).to_cubic(),
);
} else if point_count == 4 {
let first_ctrl_point_index = last_endpoint_index as usize + 1;
let ctrl_position_0 = &contour.points[first_ctrl_point_index + 0];
let ctrl_position_1 = &contour.points[first_ctrl_point_index + 1];
let ctrl = LineSegment2F::new(*ctrl_position_0, *ctrl_position_1);
handle_cubic(self, &Segment::cubic(baseline, ctrl));
}
self.push_point(
contour.points[position_index as usize],
PointFlags::empty(),
false,
);
}
last_endpoint_index = Some(point_index);
}
fn handle_cubic(contour: &mut Contour, segment: &Segment) {
debug!("handle_cubic({:?})", segment);
match segment.as_cubic_segment().y_extrema() {
(Some(t0), Some(t1)) => {
let (segments_01, segment_2) = segment.as_cubic_segment().split(t1);
let (segment_0, segment_1) = segments_01.as_cubic_segment().split(t0 / t1);
contour.push_segment(&segment_0, PushSegmentFlags::empty());
contour.push_segment(&segment_1, PushSegmentFlags::empty());
contour.push_segment(&segment_2, PushSegmentFlags::empty());
}
(Some(t0), None) | (None, Some(t0)) => {
let (segment_0, segment_1) = segment.as_cubic_segment().split(t0);
contour.push_segment(&segment_0, PushSegmentFlags::empty());
contour.push_segment(&segment_1, PushSegmentFlags::empty());
}
(None, None) => contour.push_segment(segment, PushSegmentFlags::empty()),
}
}
}
fn curve_with_endpoints_is_monotonic(
&self,
start_endpoint_index: u32,
end_endpoint_index: u32,
) -> bool {
let start_position = self.points[start_endpoint_index as usize];
let end_position = self.points[end_endpoint_index as usize];
if start_position.x() <= end_position.x() {
for point_index in start_endpoint_index..end_endpoint_index {
if self.points[point_index as usize].x() > self.points[point_index as usize + 1].x()
{
return false;
}
}
} else {
for point_index in start_endpoint_index..end_endpoint_index {
if self.points[point_index as usize].x() < self.points[point_index as usize + 1].x()
{
return false;
}
}
}
if start_position.y() <= end_position.y() {
for point_index in start_endpoint_index..end_endpoint_index {
if self.points[point_index as usize].y() > self.points[point_index as usize + 1].y()
{
return false;
}
}
} else {
for point_index in start_endpoint_index..end_endpoint_index {
if self.points[point_index as usize].y() < self.points[point_index as usize + 1].y()
{
return false;
}
}
}
true
}
// Use this function to keep bounds up to date when mutating paths. See `Outline::transform()` // Use this function to keep bounds up to date when mutating paths. See `Outline::transform()`
// for an example of use. // for an example of use.
pub(crate) fn update_bounds(&self, bounds: &mut Option<RectF>) { pub(crate) fn update_bounds(&self, bounds: &mut Option<RectF>) {

View File

@ -34,7 +34,7 @@ use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions};
use pathfinder_renderer::gpu::renderer::Renderer; use pathfinder_renderer::gpu::renderer::Renderer;
use pathfinder_renderer::options::BuildOptions; use pathfinder_renderer::options::BuildOptions;
use pathfinder_resources::ResourceLoader; use pathfinder_resources::ResourceLoader;
use pathfinder_resources::embedded::EmbeddedResourceLoader; use pathfinder_resources::fs::FilesystemResourceLoader;
use pathfinder_simd::default::F32x2; use pathfinder_simd::default::F32x2;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::f32::consts::PI; use std::f32::consts::PI;
@ -44,7 +44,7 @@ use std::time::Instant;
use surfman::{Connection, ContextAttributeFlags, ContextAttributes, GLVersion as SurfmanGLVersion}; use surfman::{Connection, ContextAttributeFlags, ContextAttributes, GLVersion as SurfmanGLVersion};
use surfman::{SurfaceAccess, SurfaceType}; use surfman::{SurfaceAccess, SurfaceType};
use winit::dpi::LogicalSize; use winit::dpi::LogicalSize;
use winit::{Event, EventsLoop, WindowBuilder, WindowEvent}; use winit::{Event, EventsLoop, KeyboardInput, VirtualKeyCode, WindowBuilder, WindowEvent};
#[cfg(not(windows))] #[cfg(not(windows))]
use jemallocator; use jemallocator;
@ -1501,7 +1501,7 @@ fn main() {
let framebuffer_size = vec2i(physical_size.width as i32, physical_size.height as i32); let framebuffer_size = vec2i(physical_size.width as i32, physical_size.height as i32);
// Load demo data. // Load demo data.
let resources = EmbeddedResourceLoader; let resources = FilesystemResourceLoader::locate();
let font_data = vec![ let font_data = vec![
Handle::from_memory(Arc::new(resources.slurp("fonts/Roboto-Regular.ttf").unwrap()), 0), Handle::from_memory(Arc::new(resources.slurp("fonts/Roboto-Regular.ttf").unwrap()), 0),
Handle::from_memory(Arc::new(resources.slurp("fonts/Roboto-Bold.ttf").unwrap()), 0), Handle::from_memory(Arc::new(resources.slurp("fonts/Roboto-Bold.ttf").unwrap()), 0),
@ -1586,7 +1586,13 @@ fn main() {
event_loop.poll_events(|event| { event_loop.poll_events(|event| {
match event { match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } | Event::WindowEvent { event: WindowEvent::CloseRequested, .. } |
Event::WindowEvent { event: WindowEvent::KeyboardInput { .. }, .. } => exit = true, Event::WindowEvent {
event: WindowEvent::KeyboardInput {
input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Escape), .. },
..
},
..
} => exit = true,
Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => { Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => {
mouse_position = vec2f(position.x as f32, position.y as f32); mouse_position = vec2f(position.x as f32, position.y as f32);
} }

View File

@ -11,8 +11,8 @@
//! Line segment types, optimized with SIMD. //! Line segment types, optimized with SIMD.
use crate::transform2d::Matrix2x2F; use crate::transform2d::Matrix2x2F;
use crate::vector::{Vector2F, vec2f};
use crate::util; use crate::util;
use crate::vector::{Vector2F, vec2f};
use pathfinder_simd::default::F32x4; use pathfinder_simd::default::F32x4;
use std::ops::{Add, Mul, MulAssign, Sub}; use std::ops::{Add, Mul, MulAssign, Sub};

View File

@ -411,3 +411,19 @@ impl RectI {
RectF(self.0.to_f32x4()) RectF(self.0.to_f32x4())
} }
} }
impl Mul<Vector2I> for RectI {
type Output = RectI;
#[inline]
fn mul(self, factors: Vector2I) -> RectI {
RectI(self.0 * factors.0.concat_xy_xy(factors.0))
}
}
impl Mul<i32> for RectI {
type Output = RectI;
#[inline]
fn mul(self, factor: i32) -> RectI {
RectI(self.0 * I32x4::splat(factor))
}
}

View File

@ -131,6 +131,12 @@ impl Vector2F {
Vector2F(self.0.yx()) Vector2F(self.0.yx())
} }
/// Returns the vector (|x|, |y|).
#[inline]
pub fn abs(self) -> Vector2F {
Vector2F(self.0.abs())
}
/// Returns the coefficient when the given vector `a` is projected onto this one. /// Returns the coefficient when the given vector `a` is projected onto this one.
/// ///
/// That is, if this vector is `v` and this function returns `c`, then `proj_v a = cv`. In /// That is, if this vector is `v` and this function returns `c`, then `proj_v a = cv`. In

View File

@ -20,16 +20,16 @@ use crate::options::{PreparedBuildOptions, PreparedRenderTransform, RenderComman
use crate::paint::{PaintInfo, PaintMetadata}; use crate::paint::{PaintInfo, PaintMetadata};
use crate::scene::{DisplayItem, Scene}; use crate::scene::{DisplayItem, Scene};
use crate::tile_map::DenseTileMap; use crate::tile_map::DenseTileMap;
use crate::tiles::{self, DrawTilingPathInfo, PackedTile, TILE_HEIGHT, TILE_WIDTH}; use crate::tiler::Tiler;
use crate::tiles::{Tiler, TilingPathInfo}; use crate::tiles::{self, DrawTilingPathInfo, PackedTile, TILE_HEIGHT, TILE_WIDTH, TilingPathInfo};
use crate::z_buffer::{DepthMetadata, ZBuffer}; use crate::z_buffer::{DepthMetadata, ZBuffer};
use pathfinder_content::effects::{BlendMode, Filter}; use pathfinder_content::effects::{BlendMode, Filter};
use pathfinder_content::fill::FillRule; use pathfinder_content::fill::FillRule;
use pathfinder_content::render_target::RenderTargetId; use pathfinder_content::render_target::RenderTargetId;
use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmentU8};
use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::transform2d::Transform2F;
use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i}; use pathfinder_geometry::vector::{Vector2I, vec2i};
use pathfinder_gpu::TextureSamplingFlags; use pathfinder_gpu::TextureSamplingFlags;
use pathfinder_simd::default::{F32x4, I32x4}; use pathfinder_simd::default::{F32x4, I32x4};
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
@ -49,6 +49,8 @@ pub(crate) struct SceneBuilder<'a, 'b> {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ObjectBuilder { pub(crate) struct ObjectBuilder {
pub built_path: BuiltPath, pub built_path: BuiltPath,
/// During tiling, this stores the sum of backdrops for tile columns above the viewport.
pub current_backdrops: Vec<i8>,
pub fills: Vec<FillBatchEntry>, pub fills: Vec<FillBatchEntry>,
pub bounds: RectF, pub bounds: RectF,
} }
@ -593,24 +595,15 @@ impl ObjectBuilder {
fill_rule: FillRule, fill_rule: FillRule,
tiling_path_info: &TilingPathInfo) tiling_path_info: &TilingPathInfo)
-> ObjectBuilder { -> ObjectBuilder {
ObjectBuilder { let built_path = BuiltPath::new(path_bounds, view_box_bounds, fill_rule, tiling_path_info);
built_path: BuiltPath::new(path_bounds, view_box_bounds, fill_rule, tiling_path_info), let current_backdrops = vec![0; built_path.tiles.rect.width() as usize];
bounds: path_bounds, ObjectBuilder { built_path, bounds: path_bounds, current_backdrops, fills: vec![] }
fills: vec![],
}
} }
#[inline] pub(crate) fn add_fill(&mut self,
pub(crate) fn tile_rect(&self) -> RectI { scene_builder: &SceneBuilder,
self.built_path.tiles.rect segment: LineSegment2F,
} tile_coords: Vector2I) {
fn add_fill(
&mut self,
scene_builder: &SceneBuilder,
segment: LineSegment2F,
tile_coords: Vector2I,
) {
debug!("add_fill({:?} ({:?}))", segment, tile_coords); debug!("add_fill({:?} ({:?}))", segment, tile_coords);
// Ensure this fill is in bounds. If not, cull it. // Ensure this fill is in bounds. If not, cull it.
@ -676,87 +669,6 @@ impl ObjectBuilder {
alpha_tile_id alpha_tile_id
} }
pub(crate) fn add_active_fill(
&mut self,
scene_builder: &SceneBuilder,
left: f32,
right: f32,
mut winding: i32,
tile_coords: Vector2I,
) {
let tile_origin_y = (tile_coords.y() * TILE_HEIGHT as i32) as f32;
let left = vec2f(left, tile_origin_y);
let right = vec2f(right, tile_origin_y);
let segment = if winding < 0 {
LineSegment2F::new(left, right)
} else {
LineSegment2F::new(right, left)
};
debug!(
"... emitting active fill {} -> {} winding {} @ tile {:?}",
left.x(),
right.x(),
winding,
tile_coords
);
while winding != 0 {
self.add_fill(scene_builder, segment, tile_coords);
if winding < 0 {
winding += 1
} else {
winding -= 1
}
}
}
pub(crate) fn generate_fill_primitives_for_line(
&mut self,
scene_builder: &SceneBuilder,
mut segment: LineSegment2F,
tile_y: i32,
) {
debug!(
"... generate_fill_primitives_for_line(): segment={:?} tile_y={} ({}-{})",
segment,
tile_y,
tile_y as f32 * TILE_HEIGHT as f32,
(tile_y + 1) as f32 * TILE_HEIGHT as f32
);
let winding = segment.from_x() > segment.to_x();
let (segment_left, segment_right) = if !winding {
(segment.from_x(), segment.to_x())
} else {
(segment.to_x(), segment.from_x())
};
let mut subsegment_x = (segment_left as i32 & !(TILE_WIDTH as i32 - 1)) as f32;
while subsegment_x < segment_right {
let (mut fill_from, mut fill_to) = (segment.from(), segment.to());
let subsegment_x_next = subsegment_x + TILE_WIDTH as f32;
if subsegment_x_next < segment_right {
let x = subsegment_x_next;
let point = Vector2F::new(x, segment.solve_y_for_x(x));
if !winding {
fill_to = point;
segment = LineSegment2F::new(point, segment.to());
} else {
fill_from = point;
segment = LineSegment2F::new(segment.from(), point);
}
}
let fill_segment = LineSegment2F::new(fill_from, fill_to);
let fill_tile_coords = vec2i(subsegment_x as i32 / TILE_WIDTH as i32, tile_y);
self.add_fill(scene_builder, fill_segment, fill_tile_coords);
subsegment_x = subsegment_x_next;
}
}
#[inline] #[inline]
pub(crate) fn tile_coords_to_local_index(&self, coords: Vector2I) -> Option<u32> { pub(crate) fn tile_coords_to_local_index(&self, coords: Vector2I) -> Option<u32> {
self.built_path.tiles.coords_to_index(coords).map(|index| index as u32) self.built_path.tiles.coords_to_index(coords).map(|index| index as u32)
@ -766,6 +678,23 @@ impl ObjectBuilder {
pub(crate) fn local_tile_index_to_coords(&self, tile_index: u32) -> Vector2I { pub(crate) fn local_tile_index_to_coords(&self, tile_index: u32) -> Vector2I {
self.built_path.tiles.index_to_coords(tile_index as usize) self.built_path.tiles.index_to_coords(tile_index as usize)
} }
#[inline]
pub(crate) fn adjust_alpha_tile_backdrop(&mut self, tile_coords: Vector2I, delta: i8) {
let tile_offset = tile_coords - self.built_path.tiles.rect.origin();
if tile_offset.x() < 0 || tile_offset.x() >= self.built_path.tiles.rect.width() ||
tile_offset.y() >= self.built_path.tiles.rect.height() {
return;
}
if tile_offset.y() < 0 {
self.current_backdrops[tile_offset.x() as usize] += delta;
return;
}
let local_tile_index = self.built_path.tiles.coords_to_index_unchecked(tile_coords);
self.built_path.tiles.data[local_tile_index].backdrop += delta;
}
} }
impl<'a> PackedTile<'a> { impl<'a> PackedTile<'a> {

View File

@ -25,5 +25,6 @@ pub mod scene;
mod allocator; mod allocator;
mod builder; mod builder;
mod tile_map; mod tile_map;
mod tiler;
mod tiles; mod tiles;
mod z_buffer; mod z_buffer;

View File

@ -185,8 +185,6 @@ impl Scene {
original_outline: &Outline, original_outline: &Outline,
options: &PreparedBuildOptions, options: &PreparedBuildOptions,
) -> Outline { ) -> Outline {
let effective_view_box = self.effective_view_box(options);
let mut outline; let mut outline;
match options.transform { match options.transform {
PreparedRenderTransform::Perspective { PreparedRenderTransform::Perspective {
@ -220,7 +218,6 @@ impl Scene {
} }
outline.transform(&transform); outline.transform(&transform);
} }
outline.clip_against_rect(effective_view_box);
} }
} }
@ -228,9 +225,6 @@ impl Scene {
outline.dilate(options.dilation); outline.dilate(options.dilation);
} }
// TODO(pcwalton): Fold this into previous passes to avoid unnecessary clones during
// monotonic conversion.
outline.prepare_for_tiling(self.effective_view_box(options));
outline outline
} }

261
renderer/src/tiler.rs Normal file
View File

@ -0,0 +1,261 @@
// pathfinder/renderer/src/tiler.rs
//
// Copyright © 2020 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Implements the fast lattice-clipping algorithm from Nehab and Hoppe, "Random-Access Rendering
//! of General Vector Graphics" 2006.
use crate::builder::{ObjectBuilder, Occluder, SceneBuilder, SolidTiles};
use crate::tiles::{PackedTile, TILE_HEIGHT, TILE_WIDTH, TileType, TilingPathInfo};
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};
const FLATTENING_TOLERANCE: f32 = 0.25;
pub(crate) struct Tiler<'a, 'b> {
scene_builder: &'a SceneBuilder<'b, 'a>,
pub(crate) object_builder: ObjectBuilder,
outline: &'a Outline,
path_info: TilingPathInfo<'a>,
}
impl<'a, 'b> Tiler<'a, 'b> {
pub(crate) fn new(scene_builder: &'a SceneBuilder<'b, 'a>,
outline: &'a Outline,
fill_rule: FillRule,
view_box: RectF,
path_info: TilingPathInfo<'a>)
-> Tiler<'a, 'b> {
let bounds = outline.bounds().intersection(view_box).unwrap_or(RectF::default());
let object_builder = ObjectBuilder::new(bounds, view_box, fill_rule, &path_info);
Tiler { scene_builder, object_builder, outline, path_info }
}
pub(crate) fn generate_tiles(&mut self) {
for contour in self.outline.contours() {
for segment in contour.iter(ContourIterFlags::empty()) {
process_segment(&segment, self.scene_builder, &mut self.object_builder);
}
}
self.propagate_backdrops();
self.pack_and_cull();
}
fn propagate_backdrops(&mut self) {
let tiles_across = self.object_builder.built_path.tiles.rect.width() as usize;
for (draw_tile_index, draw_tile) in self.object_builder
.built_path
.tiles
.data
.iter_mut()
.enumerate() {
let column = draw_tile_index % tiles_across;
let delta = draw_tile.backdrop;
draw_tile.backdrop = self.object_builder.current_backdrops[column];
self.object_builder.current_backdrops[column] += delta;
}
}
fn pack_and_cull(&mut self) {
let draw_tiling_path_info = match self.path_info {
TilingPathInfo::Clip => return,
TilingPathInfo::Draw(draw_tiling_path_info) => draw_tiling_path_info,
};
let blend_mode_is_destructive = draw_tiling_path_info.blend_mode.is_destructive();
for (draw_tile_index, draw_tile) in self.object_builder
.built_path
.tiles
.data
.iter()
.enumerate() {
let packed_tile = PackedTile::new(draw_tile_index as u32,
draw_tile,
&draw_tiling_path_info,
&self.object_builder);
match packed_tile.tile_type {
TileType::Solid => {
match self.object_builder.built_path.solid_tiles {
SolidTiles::Occluders(ref mut occluders) => {
occluders.push(Occluder::new(packed_tile.tile_coords));
}
SolidTiles::Regular(ref mut solid_tiles) => {
packed_tile.add_to(solid_tiles,
&mut self.object_builder.built_path.clip_tiles,
&draw_tiling_path_info,
&self.scene_builder);
}
}
}
TileType::SingleMask => {
debug_assert_ne!(packed_tile.draw_tile.alpha_tile_id.page(), !0);
packed_tile.add_to(&mut self.object_builder.built_path.single_mask_tiles,
&mut self.object_builder.built_path.clip_tiles,
&draw_tiling_path_info,
&self.scene_builder);
}
TileType::Empty if blend_mode_is_destructive => {
packed_tile.add_to(&mut self.object_builder.built_path.empty_tiles,
&mut self.object_builder.built_path.clip_tiles,
&draw_tiling_path_info,
&self.scene_builder);
}
TileType::Empty => {
// Just cull.
}
}
}
}
}
fn process_segment(segment: &Segment,
scene_builder: &SceneBuilder,
object_builder: &mut ObjectBuilder) {
// TODO(pcwalton): Stop degree elevating.
if segment.is_quadratic() {
let cubic = segment.to_cubic();
return process_segment(&cubic, scene_builder, object_builder);
}
if segment.is_line() ||
(segment.is_cubic() && segment.as_cubic_segment().is_flat(FLATTENING_TOLERANCE)) {
return process_line_segment(segment.baseline, scene_builder, object_builder);
}
// TODO(pcwalton): Use a smarter flattening algorithm.
let (prev, next) = segment.split(0.5);
process_segment(&prev, scene_builder, object_builder);
process_segment(&next, scene_builder, object_builder);
}
// This is the meat of the technique. It implements the fast lattice-clipping algorithm from
// Nehab and Hoppe, "Random-Access Rendering of General Vector Graphics" 2006.
//
// The algorithm to step through tiles is Amanatides and Woo, "A Fast Voxel Traversal Algorithm for
// Ray Tracing" 1987: http://www.cse.yorku.ca/~amana/research/grid.pdf
fn process_line_segment(line_segment: LineSegment2F,
scene_builder: &SceneBuilder,
object_builder: &mut ObjectBuilder) {
let tile_size = vec2f(TILE_WIDTH as f32, TILE_HEIGHT as f32);
let tile_size_recip = Vector2F::splat(1.0) / tile_size;
let tile_line_segment =
(line_segment.0 * tile_size_recip.0.concat_xy_xy(tile_size_recip.0)).floor().to_i32x4();
let from_tile_coords = Vector2I(tile_line_segment.xy());
let to_tile_coords = Vector2I(tile_line_segment.zw());
let vector = line_segment.vector();
let step = vec2f(vector.x().signum(), vector.y().signum()).to_i32();
let first_tile_crossing =
(from_tile_coords + vec2i(if step.x() <= 0 { 0 } else { 1 },
if step.y() <= 0 { 0 } else { 1 })).to_f32() * tile_size;
let mut t_max = (first_tile_crossing - line_segment.from()) / vector;
let t_delta = (tile_size / vector).abs();
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() {
StepDirection::Y
} else {
// This should only happen if the line's destination is precisely on a corner point
// between tiles:
//
// +-----+--O--+
// | | / |
// | |/ |
// +-----O-----+
// | | end |
// | | tile|
// +-----+-----+
//
// In that case we just need to step in the positive direction to move to the lower
// right tile.
if step.x() > 0 { StepDirection::X } else { StepDirection::Y }
};
let next_t =
(if next_step_direction == StepDirection::X { t_max.x() } else { t_max.y() }).min(1.0);
// If we've reached the end tile, don't step at all.
let next_step_direction = if tile_coords == to_tile_coords {
None
} else {
Some(next_step_direction)
};
let next_position = line_segment.sample(next_t);
let clipped_line_segment = LineSegment2F::new(current_position, next_position);
object_builder.add_fill(scene_builder, clipped_line_segment, tile_coords);
// Add extra fills if necessary.
if step.y() < 0 && next_step_direction == Some(StepDirection::Y) {
// Leaves through top boundary.
let auxiliary_segment = LineSegment2F::new(clipped_line_segment.to(),
tile_coords.to_f32() * tile_size);
object_builder.add_fill(scene_builder, auxiliary_segment, tile_coords);
} else if step.y() > 0 && last_step_direction == Some(StepDirection::Y) {
// Enters through top boundary.
let auxiliary_segment = LineSegment2F::new(tile_coords.to_f32() * tile_size,
clipped_line_segment.from());
object_builder.add_fill(scene_builder, auxiliary_segment, tile_coords);
}
// Adjust backdrop if necessary.
if step.x() < 0 && last_step_direction == Some(StepDirection::X) {
// Entered through right boundary.
object_builder.adjust_alpha_tile_backdrop(tile_coords, 1);
} else if step.x() > 0 && next_step_direction == Some(StepDirection::X) {
// Leaving through right boundary.
object_builder.adjust_alpha_tile_backdrop(tile_coords, -1);
}
// Take a step.
match next_step_direction {
None => break,
Some(StepDirection::X) => {
t_max += vec2f(t_delta.x(), 0.0);
tile_coords += vec2i(step.x(), 0);
}
Some(StepDirection::Y) => {
t_max += vec2f(0.0, t_delta.y());
tile_coords += vec2i(0, step.y());
}
}
current_position = next_position;
last_step_direction = next_step_direction;
iteration += 1;
}
const MAX_ITERATIONS: u32 = 1024;
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum StepDirection {
X,
Y,
}

View File

@ -8,36 +8,17 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
use crate::builder::{BuiltPath, ObjectBuilder, Occluder, SceneBuilder, SolidTiles}; use crate::builder::{BuiltPath, ObjectBuilder};
use crate::gpu_data::{AlphaTileId, TileObjectPrimitive}; use crate::gpu_data::{AlphaTileId, TileObjectPrimitive};
use crate::paint::{PaintId, PaintMetadata}; use crate::paint::{PaintId, PaintMetadata};
use pathfinder_content::effects::BlendMode; use pathfinder_content::effects::BlendMode;
use pathfinder_content::fill::FillRule; use pathfinder_content::fill::FillRule;
use pathfinder_content::outline::{Contour, Outline, PointIndex};
use pathfinder_content::segment::Segment;
use pathfinder_geometry::line_segment::LineSegment2F;
use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::rect::{RectF, RectI};
use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i}; use pathfinder_geometry::vector::{Vector2I, vec2f};
use std::mem;
// TODO(pcwalton): Make this configurable.
const FLATTENING_TOLERANCE: f32 = 0.1;
pub const TILE_WIDTH: u32 = 16; pub const TILE_WIDTH: u32 = 16;
pub const TILE_HEIGHT: u32 = 16; pub const TILE_HEIGHT: u32 = 16;
pub(crate) struct Tiler<'a, 'b> {
scene_builder: &'a SceneBuilder<'b, 'a>,
pub(crate) object_builder: ObjectBuilder,
outline: &'a Outline,
path_info: TilingPathInfo<'a>,
point_queue: Vec<QueuedEndpoint>,
next_point_queue: Vec<QueuedEndpoint>,
active_edges: Vec<ActiveEdge>,
old_active_edges: Vec<ActiveEdge>,
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub(crate) enum TilingPathInfo<'a> { pub(crate) enum TilingPathInfo<'a> {
Clip, Clip,
@ -53,339 +34,6 @@ pub(crate) struct DrawTilingPathInfo<'a> {
pub(crate) fill_rule: FillRule, pub(crate) fill_rule: FillRule,
} }
impl<'a, 'b> Tiler<'a, 'b> {
#[allow(clippy::or_fun_call)]
pub(crate) fn new(
scene_builder: &'a SceneBuilder<'b, 'a>,
outline: &'a Outline,
fill_rule: FillRule,
view_box: RectF,
path_info: TilingPathInfo<'a>,
) -> Tiler<'a, 'b> {
let bounds = outline
.bounds()
.intersection(view_box)
.unwrap_or(RectF::default());
let object_builder = ObjectBuilder::new(bounds, view_box, fill_rule, &path_info);
Tiler {
scene_builder,
object_builder,
outline,
path_info,
point_queue: Vec::new(),
next_point_queue: Vec::new(),
active_edges: Vec::new(),
old_active_edges: Vec::new(),
}
}
pub(crate) fn generate_tiles(&mut self) {
// Initialize the point queue.
self.init_point_queue();
// Reset active edges.
self.active_edges.clear();
self.old_active_edges.clear();
// Generate strips.
let tile_rect = self.object_builder.tile_rect();
for strip_origin_y in tile_rect.min_y()..tile_rect.max_y() {
self.generate_strip(strip_origin_y);
}
// Pack and cull.
self.pack_and_cull();
// Done!
debug!("{:#?}", self.object_builder.built_path);
}
fn generate_strip(&mut self, strip_origin_y: i32) {
// Process old active edges.
self.process_old_active_edges(strip_origin_y);
// Add new active edges.
let strip_max_y = ((strip_origin_y + 1) * TILE_HEIGHT as i32) as f32;
while let Some(queued_endpoint) = self.point_queue.pop() {
// Note that this test must be `>`, not `>=`, in order to make sure we don't miss
// active edges that lie precisely on the tile strip boundary.
if queued_endpoint.y > strip_max_y {
self.next_point_queue.push(queued_endpoint);
continue;
}
self.add_new_active_edge(strip_origin_y, queued_endpoint);
}
mem::swap(&mut self.point_queue, &mut self.next_point_queue);
}
fn pack_and_cull(&mut self) {
let draw_tiling_path_info = match self.path_info {
TilingPathInfo::Clip => return,
TilingPathInfo::Draw(draw_tiling_path_info) => draw_tiling_path_info,
};
let blend_mode_is_destructive = draw_tiling_path_info.blend_mode.is_destructive();
for (draw_tile_index, draw_tile) in self.object_builder
.built_path
.tiles
.data
.iter()
.enumerate() {
let packed_tile = PackedTile::new(draw_tile_index as u32,
draw_tile,
&draw_tiling_path_info,
&self.object_builder);
match packed_tile.tile_type {
TileType::Solid => {
match self.object_builder.built_path.solid_tiles {
SolidTiles::Occluders(ref mut occluders) => {
occluders.push(Occluder::new(packed_tile.tile_coords));
}
SolidTiles::Regular(ref mut solid_tiles) => {
packed_tile.add_to(solid_tiles,
&mut self.object_builder.built_path.clip_tiles,
&draw_tiling_path_info,
&self.scene_builder);
}
}
}
TileType::SingleMask => {
debug_assert_ne!(packed_tile.draw_tile.alpha_tile_id.page(), !0);
packed_tile.add_to(&mut self.object_builder.built_path.single_mask_tiles,
&mut self.object_builder.built_path.clip_tiles,
&draw_tiling_path_info,
&self.scene_builder);
}
TileType::Empty if blend_mode_is_destructive => {
packed_tile.add_to(&mut self.object_builder.built_path.empty_tiles,
&mut self.object_builder.built_path.clip_tiles,
&draw_tiling_path_info,
&self.scene_builder);
}
TileType::Empty => {
// Just cull.
}
}
}
}
fn process_old_active_edges(&mut self, tile_y: i32) {
let mut current_tile_x = self.object_builder.tile_rect().min_x();
let mut current_subtile_x = 0.0;
let mut current_winding = 0;
debug_assert!(self.old_active_edges.is_empty());
mem::swap(&mut self.old_active_edges, &mut self.active_edges);
// FIXME(pcwalton): Yuck.
let mut last_segment_x = -9999.0;
let tile_top = (tile_y * TILE_HEIGHT as i32) as f32;
debug!("---------- tile y {}({}) ----------", tile_y, tile_top);
debug!("old active edges: {:#?}", self.old_active_edges);
self.old_active_edges.sort_unstable_by(|a, b| a.crossing.x().partial_cmp(&b.crossing.x()).unwrap());
for mut active_edge in self.old_active_edges.drain(..) {
// Determine x-intercept and winding.
let segment_x = active_edge.crossing.x();
let edge_winding =
if active_edge.segment.baseline.from_y() < active_edge.segment.baseline.to_y() {
1
} else {
-1
};
debug!(
"tile Y {}({}): segment_x={} edge_winding={} current_tile_x={} \
current_subtile_x={} current_winding={}",
tile_y,
tile_top,
segment_x,
edge_winding,
current_tile_x,
current_subtile_x,
current_winding
);
debug!(
"... segment={:#?} crossing={:?}",
active_edge.segment, active_edge.crossing
);
// FIXME(pcwalton): Remove this debug code!
debug_assert!(segment_x >= last_segment_x);
last_segment_x = segment_x;
// Do initial subtile fill, if necessary.
let segment_tile_x = f32::floor(segment_x) as i32 / TILE_WIDTH as i32;
if current_tile_x < segment_tile_x && current_subtile_x > 0.0 {
let current_x =
(current_tile_x * TILE_WIDTH as i32) as f32 + current_subtile_x;
let tile_right_x = ((current_tile_x + 1) * TILE_WIDTH as i32) as f32;
let current_tile_coords = vec2i(current_tile_x, tile_y);
self.object_builder.add_active_fill(
self.scene_builder,
current_x,
tile_right_x,
current_winding,
current_tile_coords,
);
current_tile_x += 1;
current_subtile_x = 0.0;
}
// Move over to the correct tile, filling in as we go.
while current_tile_x < segment_tile_x {
debug!(
"... emitting backdrop {} @ tile {}",
current_winding, current_tile_x
);
let current_tile_coords = vec2i(current_tile_x, tile_y);
if let Some(tile_index) = self.object_builder
.tile_coords_to_local_index(current_tile_coords) {
// FIXME(pcwalton): Handle winding overflow.
self.object_builder.built_path.tiles.data[tile_index as usize].backdrop =
current_winding as i8;
}
current_tile_x += 1;
current_subtile_x = 0.0;
}
// Do final subtile fill, if necessary.
debug_assert_eq!(current_tile_x, segment_tile_x);
let segment_subtile_x =
segment_x - (current_tile_x * TILE_WIDTH as i32) as f32;
if segment_subtile_x > current_subtile_x {
let current_x =
(current_tile_x * TILE_WIDTH as i32) as f32 + current_subtile_x;
let current_tile_coords = vec2i(current_tile_x, tile_y);
self.object_builder.add_active_fill(
self.scene_builder,
current_x,
segment_x,
current_winding,
current_tile_coords,
);
current_subtile_x = segment_subtile_x;
}
// Update winding.
current_winding += edge_winding;
// Process the edge.
debug!("about to process existing active edge {:#?}", active_edge);
debug_assert!(f32::abs(active_edge.crossing.y() - tile_top) < 0.1);
active_edge.process(self.scene_builder, &mut self.object_builder, tile_y);
if !active_edge.segment.is_none() {
self.active_edges.push(active_edge);
}
}
}
fn add_new_active_edge(&mut self, tile_y: i32, queued_endpoint: QueuedEndpoint) {
let outline = &self.outline;
let point_index = queued_endpoint.point_index;
let contour = &outline.contours()[point_index.contour() as usize];
// TODO(pcwalton): Could use a bitset of processed edges…
let prev_endpoint_index = contour.prev_endpoint_index_of(point_index.point());
let next_endpoint_index = contour.next_endpoint_index_of(point_index.point());
debug!(
"adding new active edge, tile_y={} point_index={} prev={} next={} pos={:?} \
prevpos={:?} nextpos={:?}",
tile_y,
point_index.point(),
prev_endpoint_index,
next_endpoint_index,
contour.position_of(point_index.point()),
contour.position_of(prev_endpoint_index),
contour.position_of(next_endpoint_index)
);
if contour.point_is_logically_above(point_index.point(), prev_endpoint_index) {
debug!("... adding prev endpoint");
process_active_segment(
contour,
prev_endpoint_index,
&mut self.active_edges,
self.scene_builder,
&mut self.object_builder,
tile_y,
);
self.point_queue.push(QueuedEndpoint {
point_index: PointIndex::new(point_index.contour(), prev_endpoint_index),
y: contour.position_of(prev_endpoint_index).y(),
});
debug!("... done adding prev endpoint");
}
if contour.point_is_logically_above(point_index.point(), next_endpoint_index) {
debug!(
"... adding next endpoint {} -> {}",
point_index.point(),
next_endpoint_index
);
process_active_segment(
contour,
point_index.point(),
&mut self.active_edges,
self.scene_builder,
&mut self.object_builder,
tile_y,
);
self.point_queue.push(QueuedEndpoint {
point_index: PointIndex::new(point_index.contour(), next_endpoint_index),
y: contour.position_of(next_endpoint_index).y(),
});
debug!("... done adding next endpoint");
}
}
fn init_point_queue(&mut self) {
// Find MIN points.
self.point_queue.clear();
for (contour_index, contour) in self.outline.contours().iter().enumerate() {
let contour_index = contour_index as u32;
let mut cur_endpoint_index = 0;
let mut prev_endpoint_index = contour.prev_endpoint_index_of(cur_endpoint_index);
let mut next_endpoint_index = contour.next_endpoint_index_of(cur_endpoint_index);
loop {
if contour.point_is_logically_above(cur_endpoint_index, prev_endpoint_index)
&& contour.point_is_logically_above(cur_endpoint_index, next_endpoint_index)
{
self.point_queue.push(QueuedEndpoint {
point_index: PointIndex::new(contour_index, cur_endpoint_index),
y: contour.position_of(cur_endpoint_index).y(),
});
}
if cur_endpoint_index >= next_endpoint_index {
break;
}
prev_endpoint_index = cur_endpoint_index;
cur_endpoint_index = next_endpoint_index;
next_endpoint_index = contour.next_endpoint_index_of(cur_endpoint_index);
}
}
}
}
impl<'a> TilingPathInfo<'a> { impl<'a> TilingPathInfo<'a> {
pub(crate) fn has_destructive_blend_mode(&self) -> bool { pub(crate) fn has_destructive_blend_mode(&self) -> bool {
match *self { match *self {
@ -412,11 +60,11 @@ pub(crate) enum TileType {
} }
impl<'a> PackedTile<'a> { impl<'a> PackedTile<'a> {
fn new(draw_tile_index: u32, pub(crate) fn new(draw_tile_index: u32,
draw_tile: &'a TileObjectPrimitive, draw_tile: &'a TileObjectPrimitive,
draw_tiling_path_info: &DrawTilingPathInfo<'a>, draw_tiling_path_info: &DrawTilingPathInfo<'a>,
object_builder: &ObjectBuilder) object_builder: &ObjectBuilder)
-> PackedTile<'a> { -> PackedTile<'a> {
let tile_coords = object_builder.local_tile_index_to_coords(draw_tile_index as u32); let tile_coords = object_builder.local_tile_index_to_coords(draw_tile_index as u32);
// First, if the draw tile is empty, cull it regardless of clip. // First, if the draw tile is empty, cull it regardless of clip.
@ -522,158 +170,6 @@ pub fn round_rect_out_to_tile_bounds(rect: RectF) -> RectI {
(rect * vec2f(1.0 / TILE_WIDTH as f32, 1.0 / TILE_HEIGHT as f32)).round_out().to_i32() (rect * vec2f(1.0 / TILE_WIDTH as f32, 1.0 / TILE_HEIGHT as f32)).round_out().to_i32()
} }
fn process_active_segment(
contour: &Contour,
from_endpoint_index: u32,
active_edges: &mut Vec<ActiveEdge>,
builder: &SceneBuilder,
object_builder: &mut ObjectBuilder,
tile_y: i32,
) {
let mut active_edge = ActiveEdge::from_segment(&contour.segment_after(from_endpoint_index));
debug!("... process_active_segment({:#?})", active_edge);
active_edge.process(builder, object_builder, tile_y);
if !active_edge.segment.is_none() {
debug!("... ... pushing resulting active edge: {:#?}", active_edge);
active_edges.push(active_edge);
}
}
// Queued endpoints
struct QueuedEndpoint {
point_index: PointIndex,
y: f32,
}
// Active edges
#[derive(Clone, PartialEq, Debug)]
struct ActiveEdge {
segment: Segment,
// TODO(pcwalton): Shrink `crossing` down to just one f32?
crossing: Vector2F,
}
impl ActiveEdge {
fn from_segment(segment: &Segment) -> ActiveEdge {
let crossing = if segment.baseline.from_y() < segment.baseline.to_y() {
segment.baseline.from()
} else {
segment.baseline.to()
};
ActiveEdge::from_segment_and_crossing(segment, crossing)
}
fn from_segment_and_crossing(segment: &Segment, crossing: Vector2F) -> ActiveEdge {
ActiveEdge { segment: *segment, crossing }
}
fn process(&mut self,
builder: &SceneBuilder,
object_builder: &mut ObjectBuilder,
tile_y: i32) {
let tile_bottom = ((tile_y + 1) * TILE_HEIGHT as i32) as f32;
debug!(
"process_active_edge({:#?}, tile_y={}({}))",
self, tile_y, tile_bottom
);
let mut segment = self.segment;
let winding = segment.baseline.y_winding();
if segment.is_line() {
let line_segment = segment.as_line_segment();
self.segment =
match self.process_line_segment(line_segment, builder, object_builder, tile_y) {
Some(lower_part) => Segment::line(lower_part),
None => Segment::none(),
};
return;
}
// TODO(pcwalton): Don't degree elevate!
if !segment.is_cubic() {
segment = segment.to_cubic();
}
// If necessary, draw initial line.
if self.crossing.y() < segment.baseline.min_y() {
let first_line_segment =
LineSegment2F::new(self.crossing, segment.baseline.upper_point()).orient(winding);
if self.process_line_segment(first_line_segment, builder, object_builder, tile_y)
.is_some() {
return;
}
}
let mut oriented_segment = segment.orient(winding);
loop {
let mut split_t = 1.0;
let mut before_segment = oriented_segment;
let mut after_segment = None;
while !before_segment
.as_cubic_segment()
.is_flat(FLATTENING_TOLERANCE)
{
split_t *= 0.5;
let (before, after) = oriented_segment.as_cubic_segment().split(split_t);
before_segment = before;
after_segment = Some(after);
}
debug!(
"... tile_y={} winding={} segment={:?} t={} before_segment={:?}
after_segment={:?}",
tile_y, winding, segment, split_t, before_segment, after_segment
);
let line = before_segment.baseline.orient(winding);
match self.process_line_segment(line, builder, object_builder, tile_y) {
Some(lower_part) if split_t == 1.0 => {
self.segment = Segment::line(lower_part);
return;
}
None if split_t == 1.0 => {
self.segment = Segment::none();
return;
}
Some(_) => {
self.segment = after_segment.unwrap().orient(winding);
return;
}
None => oriented_segment = after_segment.unwrap(),
}
}
}
fn process_line_segment(
&mut self,
line_segment: LineSegment2F,
builder: &SceneBuilder,
object_builder: &mut ObjectBuilder,
tile_y: i32,
) -> Option<LineSegment2F> {
let tile_bottom = ((tile_y + 1) * TILE_HEIGHT as i32) as f32;
debug!(
"process_line_segment({:?}, tile_y={}) tile_bottom={}",
line_segment, tile_y, tile_bottom
);
if line_segment.max_y() <= tile_bottom {
object_builder.generate_fill_primitives_for_line(builder, line_segment, tile_y);
return None;
}
let (upper_part, lower_part) = line_segment.split_at_y(tile_bottom);
object_builder.generate_fill_primitives_for_line(builder, upper_part, tile_y);
self.crossing = lower_part.upper_point();
Some(lower_part)
}
}
impl Default for TileObjectPrimitive { impl Default for TileObjectPrimitive {
#[inline] #[inline]
fn default() -> TileObjectPrimitive { fn default() -> TileObjectPrimitive {