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:
parent
c61f3e5ae8
commit
a67b4dd5b7
|
@ -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> {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue