Factor renderer and SVG code out into separate crates
This commit is contained in:
parent
3f28845157
commit
821b54b8f4
|
@ -444,6 +444,30 @@ dependencies = [
|
||||||
"euclid 0.19.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"euclid 0.19.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathfinder_renderer"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"euclid 0.19.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"hashbrown 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pathfinder_geometry 0.3.0",
|
||||||
|
"quickcheck 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathfinder_svg"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"euclid 0.19.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lyon_path 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pathfinder_geometry 0.3.0",
|
||||||
|
"pathfinder_renderer 0.1.0",
|
||||||
|
"usvg 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.7.24"
|
version = "0.7.24"
|
||||||
|
@ -724,6 +748,8 @@ dependencies = [
|
||||||
"lyon_geom 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lyon_geom 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lyon_path 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lyon_path 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"pathfinder_geometry 0.3.0",
|
"pathfinder_geometry 0.3.0",
|
||||||
|
"pathfinder_renderer 0.1.0",
|
||||||
|
"pathfinder_svg 0.1.0",
|
||||||
"quickcheck 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"quickcheck 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
members = [
|
members = [
|
||||||
"geometry",
|
"geometry",
|
||||||
"gfx-utils",
|
"gfx-utils",
|
||||||
|
"renderer",
|
||||||
|
"svg",
|
||||||
"utils/area-lut",
|
"utils/area-lut",
|
||||||
"utils/gamma-lut",
|
"utils/gamma-lut",
|
||||||
"utils/tile-svg",
|
"utils/tile-svg",
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "pathfinder_renderer"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = "1.2"
|
||||||
|
euclid = "0.19"
|
||||||
|
fixedbitset = "0.1"
|
||||||
|
hashbrown = "0.1"
|
||||||
|
rayon = "1.0"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_geometry]
|
||||||
|
path = "../geometry"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
quickcheck = "0.7"
|
|
@ -0,0 +1,119 @@
|
||||||
|
// pathfinder/renderer/src/builder.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
//! Packs data onto the GPU.
|
||||||
|
|
||||||
|
use crate::gpu_data::{Batch, BuiltObject, FillBatchPrimitive};
|
||||||
|
use crate::gpu_data::{MaskTileBatchPrimitive, SolidTileScenePrimitive};
|
||||||
|
use crate::scene;
|
||||||
|
use crate::tiles;
|
||||||
|
use crate::z_buffer::ZBuffer;
|
||||||
|
use euclid::Rect;
|
||||||
|
use std::iter;
|
||||||
|
use std::u16;
|
||||||
|
|
||||||
|
const MAX_FILLS_PER_BATCH: usize = 0x0002_0000;
|
||||||
|
const MAX_MASKS_PER_BATCH: u16 = 0xffff;
|
||||||
|
|
||||||
|
pub struct SceneBuilder {
|
||||||
|
objects: Vec<BuiltObject>,
|
||||||
|
z_buffer: ZBuffer,
|
||||||
|
tile_rect: Rect<i16>,
|
||||||
|
|
||||||
|
current_object_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SceneBuilder {
|
||||||
|
pub fn new(objects: Vec<BuiltObject>, z_buffer: ZBuffer, view_box: &Rect<f32>)
|
||||||
|
-> SceneBuilder {
|
||||||
|
let tile_rect = tiles::round_rect_out_to_tile_bounds(view_box);
|
||||||
|
SceneBuilder {
|
||||||
|
objects,
|
||||||
|
z_buffer,
|
||||||
|
tile_rect,
|
||||||
|
current_object_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_solid_tiles(&self) -> Vec<SolidTileScenePrimitive> {
|
||||||
|
self.z_buffer
|
||||||
|
.build_solid_tiles(&self.objects, &self.tile_rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_batch(&mut self) -> Option<Batch> {
|
||||||
|
let mut batch = Batch::new();
|
||||||
|
|
||||||
|
let mut object_tile_index_to_batch_mask_tile_index = vec![];
|
||||||
|
while self.current_object_index < self.objects.len() {
|
||||||
|
let object = &self.objects[self.current_object_index];
|
||||||
|
|
||||||
|
if batch.fills.len() + object.fills.len() > MAX_FILLS_PER_BATCH {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
object_tile_index_to_batch_mask_tile_index.clear();
|
||||||
|
object_tile_index_to_batch_mask_tile_index
|
||||||
|
.extend(iter::repeat(u16::MAX).take(object.tiles.len()));
|
||||||
|
|
||||||
|
// Copy mask tiles.
|
||||||
|
for (tile_index, tile) in object.tiles.iter().enumerate() {
|
||||||
|
// Skip solid tiles, since we handled them above already.
|
||||||
|
if object.solid_tiles[tile_index] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cull occluded tiles.
|
||||||
|
let scene_tile_index =
|
||||||
|
scene::scene_tile_index(tile.tile_x, tile.tile_y, self.tile_rect);
|
||||||
|
if !self
|
||||||
|
.z_buffer
|
||||||
|
.test(scene_tile_index, self.current_object_index as u32)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visible mask tile.
|
||||||
|
let batch_mask_tile_index = batch.mask_tiles.len() as u16;
|
||||||
|
if batch_mask_tile_index == MAX_MASKS_PER_BATCH {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
object_tile_index_to_batch_mask_tile_index[tile_index] = batch_mask_tile_index;
|
||||||
|
|
||||||
|
batch.mask_tiles.push(MaskTileBatchPrimitive {
|
||||||
|
tile: *tile,
|
||||||
|
shader: object.shader,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap and copy fills, culling as necessary.
|
||||||
|
for fill in &object.fills {
|
||||||
|
let object_tile_index = object.tile_coords_to_index(fill.tile_x, fill.tile_y);
|
||||||
|
let mask_tile_index =
|
||||||
|
object_tile_index_to_batch_mask_tile_index[object_tile_index as usize];
|
||||||
|
if mask_tile_index < u16::MAX {
|
||||||
|
batch.fills.push(FillBatchPrimitive {
|
||||||
|
px: fill.px,
|
||||||
|
subpx: fill.subpx,
|
||||||
|
mask_tile_index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_object_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if batch.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(batch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,287 @@
|
||||||
|
// pathfinder/renderer/src/gpu_data.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
//! Packed data ready to be sent to the GPU.
|
||||||
|
|
||||||
|
use crate::paint::{ObjectShader, ShaderId};
|
||||||
|
use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH};
|
||||||
|
use euclid::Rect;
|
||||||
|
use fixedbitset::FixedBitSet;
|
||||||
|
use pathfinder_geometry::line_segment::{LineSegmentF32, LineSegmentU4, LineSegmentU8};
|
||||||
|
use pathfinder_geometry::point::Point2DF32;
|
||||||
|
use pathfinder_geometry::simd::{F32x4, I32x4};
|
||||||
|
use pathfinder_geometry::util;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BuiltObject {
|
||||||
|
pub bounds: Rect<f32>,
|
||||||
|
pub tile_rect: Rect<i16>,
|
||||||
|
pub tiles: Vec<TileObjectPrimitive>,
|
||||||
|
pub fills: Vec<FillObjectPrimitive>,
|
||||||
|
pub solid_tiles: FixedBitSet,
|
||||||
|
pub shader: ShaderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BuiltScene {
|
||||||
|
pub view_box: Rect<f32>,
|
||||||
|
pub batches: Vec<Batch>,
|
||||||
|
pub solid_tiles: Vec<SolidTileScenePrimitive>,
|
||||||
|
pub shaders: Vec<ObjectShader>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Batch {
|
||||||
|
pub fills: Vec<FillBatchPrimitive>,
|
||||||
|
pub mask_tiles: Vec<MaskTileBatchPrimitive>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct FillObjectPrimitive {
|
||||||
|
pub px: LineSegmentU4,
|
||||||
|
pub subpx: LineSegmentU8,
|
||||||
|
pub tile_x: i16,
|
||||||
|
pub tile_y: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct TileObjectPrimitive {
|
||||||
|
pub tile_x: i16,
|
||||||
|
pub tile_y: i16,
|
||||||
|
pub backdrop: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct FillBatchPrimitive {
|
||||||
|
pub px: LineSegmentU4,
|
||||||
|
pub subpx: LineSegmentU8,
|
||||||
|
pub mask_tile_index: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct SolidTileScenePrimitive {
|
||||||
|
pub tile_x: i16,
|
||||||
|
pub tile_y: i16,
|
||||||
|
pub shader: ShaderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct MaskTileBatchPrimitive {
|
||||||
|
pub tile: TileObjectPrimitive,
|
||||||
|
pub shader: ShaderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utilities for built objects
|
||||||
|
|
||||||
|
impl BuiltObject {
|
||||||
|
pub fn new(bounds: &Rect<f32>, shader: ShaderId) -> BuiltObject {
|
||||||
|
// Compute the tile rect.
|
||||||
|
let tile_rect = tiles::round_rect_out_to_tile_bounds(&bounds);
|
||||||
|
|
||||||
|
// Allocate tiles.
|
||||||
|
let tile_count = tile_rect.size.width as usize * tile_rect.size.height as usize;
|
||||||
|
let mut tiles = Vec::with_capacity(tile_count);
|
||||||
|
for y in tile_rect.origin.y..tile_rect.max_y() {
|
||||||
|
for x in tile_rect.origin.x..tile_rect.max_x() {
|
||||||
|
tiles.push(TileObjectPrimitive::new(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut solid_tiles = FixedBitSet::with_capacity(tile_count);
|
||||||
|
solid_tiles.insert_range(..);
|
||||||
|
|
||||||
|
BuiltObject {
|
||||||
|
bounds: *bounds,
|
||||||
|
tile_rect,
|
||||||
|
tiles,
|
||||||
|
fills: vec![],
|
||||||
|
solid_tiles,
|
||||||
|
shader,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(pcwalton): SIMD-ify `tile_x` and `tile_y`.
|
||||||
|
fn add_fill(&mut self, segment: &LineSegmentF32, tile_x: i16, tile_y: i16) {
|
||||||
|
//println!("add_fill({:?} ({}, {}))", segment, tile_x, tile_y);
|
||||||
|
let mut segment = (segment.0 * F32x4::splat(256.0)).to_i32x4();
|
||||||
|
|
||||||
|
let tile_origin_x = (TILE_WIDTH as i32) * 256 * (tile_x as i32);
|
||||||
|
let tile_origin_y = (TILE_HEIGHT as i32) * 256 * (tile_y as i32);
|
||||||
|
let tile_origin = I32x4::new(tile_origin_x, tile_origin_y, tile_origin_x, tile_origin_y);
|
||||||
|
|
||||||
|
segment = segment - tile_origin;
|
||||||
|
/*
|
||||||
|
println!("... before min: {} {} {} {}",
|
||||||
|
segment[0], segment[1], segment[2], segment[3]);
|
||||||
|
*/
|
||||||
|
//segment = Sse41::max_epi32(segment, Sse41::setzero_epi32());
|
||||||
|
segment = segment.min(I32x4::splat(0x0fff));
|
||||||
|
//println!("... after min: {} {} {} {}", segment[0], segment[1], segment[2], segment[3]);
|
||||||
|
|
||||||
|
let shuffle_mask = I32x4::new(0x0c08_0400, 0x0d05_0901, 0, 0);
|
||||||
|
segment = segment
|
||||||
|
.as_u8x16()
|
||||||
|
.shuffle(shuffle_mask.as_u8x16())
|
||||||
|
.as_i32x4();
|
||||||
|
|
||||||
|
let px = LineSegmentU4((segment[1] | (segment[1] >> 12)) as u16);
|
||||||
|
let subpx = LineSegmentU8(segment[0] as u32);
|
||||||
|
|
||||||
|
let tile_index = self.tile_coords_to_index(tile_x, tile_y);
|
||||||
|
|
||||||
|
/*
|
||||||
|
// TODO(pcwalton): Cull degenerate fills again.
|
||||||
|
// Cull degenerate fills.
|
||||||
|
let (from_px, to_px) = (from.to_u8(), to.to_u8());
|
||||||
|
if from_px.x == to_px.x && from_subpx.x == to_subpx.x {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
self.fills.push(FillObjectPrimitive {
|
||||||
|
px,
|
||||||
|
subpx,
|
||||||
|
tile_x,
|
||||||
|
tile_y,
|
||||||
|
});
|
||||||
|
self.solid_tiles.set(tile_index as usize, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_active_fill(
|
||||||
|
&mut self,
|
||||||
|
left: f32,
|
||||||
|
right: f32,
|
||||||
|
mut winding: i16,
|
||||||
|
tile_x: i16,
|
||||||
|
tile_y: i16,
|
||||||
|
) {
|
||||||
|
let tile_origin_y = (i32::from(tile_y) * TILE_HEIGHT as i32) as f32;
|
||||||
|
let left = Point2DF32::new(left, tile_origin_y);
|
||||||
|
let right = Point2DF32::new(right, tile_origin_y);
|
||||||
|
|
||||||
|
let segment = if winding < 0 {
|
||||||
|
LineSegmentF32::new(&left, &right)
|
||||||
|
} else {
|
||||||
|
LineSegmentF32::new(&right, &left)
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
println!("... emitting active fill {} -> {} winding {} @ tile {}",
|
||||||
|
left.x(),
|
||||||
|
right.x(),
|
||||||
|
winding,
|
||||||
|
tile_x);
|
||||||
|
*/
|
||||||
|
|
||||||
|
while winding != 0 {
|
||||||
|
self.add_fill(&segment, tile_x, tile_y);
|
||||||
|
if winding < 0 {
|
||||||
|
winding += 1
|
||||||
|
} else {
|
||||||
|
winding -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(pcwalton): Optimize this better with SIMD!
|
||||||
|
pub fn generate_fill_primitives_for_line(&mut self, mut segment: LineSegmentF32, tile_y: i16) {
|
||||||
|
/*
|
||||||
|
println!("... 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 segment_tile_left = (f32::floor(segment_left) as i32 / TILE_WIDTH as i32) as i16;
|
||||||
|
let segment_tile_right =
|
||||||
|
util::alignup_i32(f32::ceil(segment_right) as i32, TILE_WIDTH as i32) as i16;
|
||||||
|
|
||||||
|
for subsegment_tile_x in segment_tile_left..segment_tile_right {
|
||||||
|
let (mut fill_from, mut fill_to) = (segment.from(), segment.to());
|
||||||
|
let subsegment_tile_right =
|
||||||
|
((i32::from(subsegment_tile_x) + 1) * TILE_HEIGHT as i32) as f32;
|
||||||
|
if subsegment_tile_right < segment_right {
|
||||||
|
let x = subsegment_tile_right;
|
||||||
|
let point = Point2DF32::new(x, segment.solve_y_for_x(x));
|
||||||
|
if !winding {
|
||||||
|
fill_to = point;
|
||||||
|
segment = LineSegmentF32::new(&point, &segment.to());
|
||||||
|
} else {
|
||||||
|
fill_from = point;
|
||||||
|
segment = LineSegmentF32::new(&segment.from(), &point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fill_segment = LineSegmentF32::new(&fill_from, &fill_to);
|
||||||
|
self.add_fill(&fill_segment, subsegment_tile_x, tile_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(pcwalton): Use a `Point2D<i16>` instead?
|
||||||
|
pub fn tile_coords_to_index(&self, tile_x: i16, tile_y: i16) -> u32 {
|
||||||
|
/*println!("tile_coords_to_index(x={}, y={}, tile_rect={:?})",
|
||||||
|
tile_x,
|
||||||
|
tile_y,
|
||||||
|
self.tile_rect);*/
|
||||||
|
(tile_y - self.tile_rect.origin.y) as u32 * self.tile_rect.size.width as u32
|
||||||
|
+ (tile_x - self.tile_rect.origin.x) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tile_mut(&mut self, tile_x: i16, tile_y: i16) -> &mut TileObjectPrimitive {
|
||||||
|
let tile_index = self.tile_coords_to_index(tile_x, tile_y);
|
||||||
|
&mut self.tiles[tile_index as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuiltScene {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(view_box: &Rect<f32>) -> BuiltScene {
|
||||||
|
BuiltScene {
|
||||||
|
view_box: *view_box,
|
||||||
|
batches: vec![],
|
||||||
|
solid_tiles: vec![],
|
||||||
|
shaders: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Batch {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Batch {
|
||||||
|
Batch {
|
||||||
|
fills: vec![],
|
||||||
|
mask_tiles: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.mask_tiles.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileObjectPrimitive {
|
||||||
|
#[inline]
|
||||||
|
fn new(tile_x: i16, tile_y: i16) -> TileObjectPrimitive {
|
||||||
|
TileObjectPrimitive {
|
||||||
|
tile_x,
|
||||||
|
tile_y,
|
||||||
|
backdrop: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// pathfinder/renderer/src/lib.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
//! The CPU portion of Pathfinder's renderer.
|
||||||
|
|
||||||
|
pub mod builder;
|
||||||
|
pub mod gpu_data;
|
||||||
|
pub mod paint;
|
||||||
|
pub mod scene;
|
||||||
|
pub mod serialization;
|
||||||
|
pub mod tiles;
|
||||||
|
pub mod z_buffer;
|
||||||
|
|
||||||
|
mod sorted_vector;
|
|
@ -0,0 +1,47 @@
|
||||||
|
// pathfinder/renderer/src/paint.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
//! How a path is to be filled.
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Paint {
|
||||||
|
pub color: ColorU,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub struct PaintId(pub u16);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
|
||||||
|
pub struct ColorU {
|
||||||
|
pub r: u8,
|
||||||
|
pub g: u8,
|
||||||
|
pub b: u8,
|
||||||
|
pub a: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorU {
|
||||||
|
#[inline]
|
||||||
|
pub fn black() -> ColorU {
|
||||||
|
ColorU {
|
||||||
|
r: 0,
|
||||||
|
g: 0,
|
||||||
|
b: 0,
|
||||||
|
a: 255,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct ShaderId(pub u16);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct ObjectShader {
|
||||||
|
pub fill_color: ColorU,
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
// pathfinder/renderer/src/scene.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
//! A set of paths to be rendered.
|
||||||
|
|
||||||
|
use crate::gpu_data::BuiltObject;
|
||||||
|
use crate::paint::{ObjectShader, Paint, PaintId, ShaderId};
|
||||||
|
use crate::tiles::Tiler;
|
||||||
|
use crate::z_buffer::ZBuffer;
|
||||||
|
use euclid::Rect;
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use pathfinder_geometry::outline::Outline;
|
||||||
|
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Scene {
|
||||||
|
pub objects: Vec<PathObject>,
|
||||||
|
pub paints: Vec<Paint>,
|
||||||
|
pub paint_cache: HashMap<Paint, PaintId>,
|
||||||
|
pub bounds: Rect<f32>,
|
||||||
|
pub view_box: Rect<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Scene {
|
||||||
|
Scene {
|
||||||
|
objects: vec![],
|
||||||
|
paints: vec![],
|
||||||
|
paint_cache: HashMap::new(),
|
||||||
|
bounds: Rect::zero(),
|
||||||
|
view_box: Rect::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
pub fn push_paint(&mut self, paint: &Paint) -> PaintId {
|
||||||
|
if let Some(paint_id) = self.paint_cache.get(paint) {
|
||||||
|
return *paint_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
let paint_id = PaintId(self.paints.len() as u16);
|
||||||
|
self.paint_cache.insert(*paint, paint_id);
|
||||||
|
self.paints.push(*paint);
|
||||||
|
paint_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_shaders(&self) -> Vec<ObjectShader> {
|
||||||
|
self.paints
|
||||||
|
.iter()
|
||||||
|
.map(|paint| ObjectShader {
|
||||||
|
fill_color: paint.color,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_objects_sequentially(&self, z_buffer: &ZBuffer) -> Vec<BuiltObject> {
|
||||||
|
self.objects
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(object_index, object)| {
|
||||||
|
let mut tiler = Tiler::new(
|
||||||
|
&object.outline,
|
||||||
|
&self.view_box,
|
||||||
|
object_index as u16,
|
||||||
|
ShaderId(object.paint.0),
|
||||||
|
z_buffer,
|
||||||
|
);
|
||||||
|
tiler.generate_tiles();
|
||||||
|
tiler.built_object
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_objects(&self, z_buffer: &ZBuffer) -> Vec<BuiltObject> {
|
||||||
|
self.objects
|
||||||
|
.par_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(object_index, object)| {
|
||||||
|
let mut tiler = Tiler::new(
|
||||||
|
&object.outline,
|
||||||
|
&self.view_box,
|
||||||
|
object_index as u16,
|
||||||
|
ShaderId(object.paint.0),
|
||||||
|
z_buffer,
|
||||||
|
);
|
||||||
|
tiler.generate_tiles();
|
||||||
|
tiler.built_object
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PathObject {
|
||||||
|
outline: Outline,
|
||||||
|
paint: PaintId,
|
||||||
|
name: String,
|
||||||
|
kind: PathObjectKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum PathObjectKind {
|
||||||
|
Fill,
|
||||||
|
Stroke,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathObject {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(outline: Outline, paint: PaintId, name: String, kind: PathObjectKind)
|
||||||
|
-> PathObject {
|
||||||
|
PathObject {
|
||||||
|
outline,
|
||||||
|
paint,
|
||||||
|
name,
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn scene_tile_index(tile_x: i16, tile_y: i16, tile_rect: Rect<i16>) -> u32 {
|
||||||
|
(tile_y - tile_rect.origin.y) as u32 * tile_rect.size.width as u32
|
||||||
|
+ (tile_x - tile_rect.origin.x) as u32
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
// pathfinder/renderer/src/serialization.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
|
use crate::gpu_data::{BuiltScene, FillBatchPrimitive};
|
||||||
|
use crate::gpu_data::{MaskTileBatchPrimitive, SolidTileScenePrimitive};
|
||||||
|
use crate::paint::ObjectShader;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
pub trait RiffSerialize {
|
||||||
|
fn write<W>(&self, writer: &mut W) -> io::Result<()> where W: Write;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RiffSerialize for BuiltScene {
|
||||||
|
fn write<W>(&self, writer: &mut W) -> io::Result<()>
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
writer.write_all(b"RIFF")?;
|
||||||
|
|
||||||
|
let header_size = 4 * 6;
|
||||||
|
|
||||||
|
let solid_tiles_size = self.solid_tiles.len() * mem::size_of::<SolidTileScenePrimitive>();
|
||||||
|
|
||||||
|
let batch_sizes: Vec<_> = self
|
||||||
|
.batches
|
||||||
|
.iter()
|
||||||
|
.map(|batch| BatchSizes {
|
||||||
|
fills: (batch.fills.len() * mem::size_of::<FillBatchPrimitive>()),
|
||||||
|
mask_tiles: (batch.mask_tiles.len() * mem::size_of::<MaskTileBatchPrimitive>()),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let total_batch_sizes: usize = batch_sizes.iter().map(|sizes| 8 + sizes.total()).sum();
|
||||||
|
|
||||||
|
let shaders_size = self.shaders.len() * mem::size_of::<ObjectShader>();
|
||||||
|
|
||||||
|
writer.write_u32::<LittleEndian>(
|
||||||
|
(4 + 8 + header_size + 8 + solid_tiles_size + 8 + shaders_size + total_batch_sizes)
|
||||||
|
as u32,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
writer.write_all(b"PF3S")?;
|
||||||
|
|
||||||
|
writer.write_all(b"head")?;
|
||||||
|
writer.write_u32::<LittleEndian>(header_size as u32)?;
|
||||||
|
writer.write_u32::<LittleEndian>(FILE_VERSION)?;
|
||||||
|
writer.write_u32::<LittleEndian>(self.batches.len() as u32)?;
|
||||||
|
writer.write_f32::<LittleEndian>(self.view_box.origin.x)?;
|
||||||
|
writer.write_f32::<LittleEndian>(self.view_box.origin.y)?;
|
||||||
|
writer.write_f32::<LittleEndian>(self.view_box.size.width)?;
|
||||||
|
writer.write_f32::<LittleEndian>(self.view_box.size.height)?;
|
||||||
|
|
||||||
|
writer.write_all(b"shad")?;
|
||||||
|
writer.write_u32::<LittleEndian>(shaders_size as u32)?;
|
||||||
|
for &shader in &self.shaders {
|
||||||
|
let fill_color = shader.fill_color;
|
||||||
|
writer.write_all(&[fill_color.r, fill_color.g, fill_color.b, fill_color.a])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_all(b"soli")?;
|
||||||
|
writer.write_u32::<LittleEndian>(solid_tiles_size as u32)?;
|
||||||
|
for &tile_primitive in &self.solid_tiles {
|
||||||
|
writer.write_i16::<LittleEndian>(tile_primitive.tile_x)?;
|
||||||
|
writer.write_i16::<LittleEndian>(tile_primitive.tile_y)?;
|
||||||
|
writer.write_u16::<LittleEndian>(tile_primitive.shader.0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (batch, sizes) in self.batches.iter().zip(batch_sizes.iter()) {
|
||||||
|
writer.write_all(b"batc")?;
|
||||||
|
writer.write_u32::<LittleEndian>(sizes.total() as u32)?;
|
||||||
|
|
||||||
|
writer.write_all(b"fill")?;
|
||||||
|
writer.write_u32::<LittleEndian>(sizes.fills as u32)?;
|
||||||
|
for fill_primitive in &batch.fills {
|
||||||
|
writer.write_u16::<LittleEndian>(fill_primitive.px.0)?;
|
||||||
|
writer.write_u32::<LittleEndian>(fill_primitive.subpx.0)?;
|
||||||
|
writer.write_u16::<LittleEndian>(fill_primitive.mask_tile_index)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_all(b"mask")?;
|
||||||
|
writer.write_u32::<LittleEndian>(sizes.mask_tiles as u32)?;
|
||||||
|
for &tile_primitive in &batch.mask_tiles {
|
||||||
|
writer.write_i16::<LittleEndian>(tile_primitive.tile.tile_x)?;
|
||||||
|
writer.write_i16::<LittleEndian>(tile_primitive.tile.tile_y)?;
|
||||||
|
writer.write_i16::<LittleEndian>(tile_primitive.tile.backdrop)?;
|
||||||
|
writer.write_u16::<LittleEndian>(tile_primitive.shader.0)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
|
||||||
|
const FILE_VERSION: u32 = 0;
|
||||||
|
|
||||||
|
struct BatchSizes {
|
||||||
|
fills: usize,
|
||||||
|
mask_tiles: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BatchSizes {
|
||||||
|
fn total(&self) -> usize {
|
||||||
|
8 + self.fills + 8 + self.mask_tiles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
// pathfinder/renderer/src/sorted_vector.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
//! A vector that maintains sorted order with insertion sort.
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SortedVector<T>
|
||||||
|
where
|
||||||
|
T: PartialOrd,
|
||||||
|
{
|
||||||
|
pub array: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SortedVector<T>
|
||||||
|
where
|
||||||
|
T: PartialOrd,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> SortedVector<T> {
|
||||||
|
SortedVector { array: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn push(&mut self, value: T) {
|
||||||
|
self.array.push(value);
|
||||||
|
let mut index = self.array.len() - 1;
|
||||||
|
while index > 0 {
|
||||||
|
index -= 1;
|
||||||
|
if self.array[index] <= self.array[index + 1] {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.array.swap(index, index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn peek(&self) -> Option<&T> {
|
||||||
|
self.array.last()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn pop(&mut self) -> Option<T> {
|
||||||
|
self.array.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.array.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.array.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::sorted_vector::SortedVector;
|
||||||
|
use quickcheck;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sorted_vec() {
|
||||||
|
quickcheck::quickcheck(prop_sorted_vec as fn(Vec<i32>) -> bool);
|
||||||
|
|
||||||
|
fn prop_sorted_vec(mut values: Vec<i32>) -> bool {
|
||||||
|
let mut sorted_vec = SortedVector::new();
|
||||||
|
for &value in &values {
|
||||||
|
sorted_vec.push(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
values.sort();
|
||||||
|
let mut results = Vec::with_capacity(values.len());
|
||||||
|
while !sorted_vec.is_empty() {
|
||||||
|
results.push(sorted_vec.pop().unwrap());
|
||||||
|
}
|
||||||
|
results.reverse();
|
||||||
|
assert_eq!(&values, &results);
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,467 @@
|
||||||
|
// pathfinder/renderer/src/tiles.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
use crate::gpu_data::BuiltObject;
|
||||||
|
use crate::paint::ShaderId;
|
||||||
|
use crate::sorted_vector::SortedVector;
|
||||||
|
use crate::z_buffer::ZBuffer;
|
||||||
|
use euclid::{Point2D, Rect, Size2D};
|
||||||
|
use pathfinder_geometry::line_segment::LineSegmentF32;
|
||||||
|
use pathfinder_geometry::outline::{Contour, Outline, PointIndex};
|
||||||
|
use pathfinder_geometry::point::Point2DF32;
|
||||||
|
use pathfinder_geometry::segment::Segment;
|
||||||
|
use pathfinder_geometry::util;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
// TODO(pcwalton): Make this configurable.
|
||||||
|
const FLATTENING_TOLERANCE: f32 = 0.1;
|
||||||
|
|
||||||
|
pub const TILE_WIDTH: u32 = 16;
|
||||||
|
pub const TILE_HEIGHT: u32 = 16;
|
||||||
|
|
||||||
|
pub struct Tiler<'o, 'z> {
|
||||||
|
outline: &'o Outline,
|
||||||
|
pub built_object: BuiltObject,
|
||||||
|
object_index: u16,
|
||||||
|
z_buffer: &'z ZBuffer,
|
||||||
|
|
||||||
|
point_queue: SortedVector<QueuedEndpoint>,
|
||||||
|
active_edges: SortedVector<ActiveEdge>,
|
||||||
|
old_active_edges: Vec<ActiveEdge>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'o, 'z> Tiler<'o, 'z> {
|
||||||
|
#[allow(clippy::or_fun_call)]
|
||||||
|
pub fn new(
|
||||||
|
outline: &'o Outline,
|
||||||
|
view_box: &Rect<f32>,
|
||||||
|
object_index: u16,
|
||||||
|
shader: ShaderId,
|
||||||
|
z_buffer: &'z ZBuffer,
|
||||||
|
) -> Tiler<'o, 'z> {
|
||||||
|
let bounds = outline
|
||||||
|
.bounds()
|
||||||
|
.intersection(&view_box)
|
||||||
|
.unwrap_or(Rect::zero());
|
||||||
|
let built_object = BuiltObject::new(&bounds, shader);
|
||||||
|
|
||||||
|
Tiler {
|
||||||
|
outline,
|
||||||
|
built_object,
|
||||||
|
object_index,
|
||||||
|
z_buffer,
|
||||||
|
|
||||||
|
point_queue: SortedVector::new(),
|
||||||
|
active_edges: SortedVector::new(),
|
||||||
|
old_active_edges: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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.built_object.tile_rect;
|
||||||
|
for strip_origin_y in tile_rect.origin.y..tile_rect.max_y() {
|
||||||
|
self.generate_strip(strip_origin_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cull.
|
||||||
|
self.cull();
|
||||||
|
//println!("{:#?}", self.built_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_strip(&mut self, strip_origin_y: i16) {
|
||||||
|
// Process old active edges.
|
||||||
|
self.process_old_active_edges(strip_origin_y);
|
||||||
|
|
||||||
|
// Add new active edges.
|
||||||
|
let strip_max_y = ((i32::from(strip_origin_y) + 1) * TILE_HEIGHT as i32) as f32;
|
||||||
|
while let Some(queued_endpoint) = self.point_queue.peek() {
|
||||||
|
if queued_endpoint.y >= strip_max_y {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.add_new_active_edge(strip_origin_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cull(&self) {
|
||||||
|
for solid_tile_index in self.built_object.solid_tiles.ones() {
|
||||||
|
let tile = &self.built_object.tiles[solid_tile_index];
|
||||||
|
if tile.backdrop != 0 {
|
||||||
|
self.z_buffer
|
||||||
|
.update(tile.tile_x, tile.tile_y, self.object_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_old_active_edges(&mut self, tile_y: i16) {
|
||||||
|
let mut current_tile_x = self.built_object.tile_rect.origin.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.array);
|
||||||
|
|
||||||
|
let mut last_segment_x = -9999.0;
|
||||||
|
|
||||||
|
let tile_top = (i32::from(tile_y) * TILE_HEIGHT as i32) as f32;
|
||||||
|
//println!("---------- tile y {}({}) ----------", tile_y, tile_top);
|
||||||
|
//println!("old active edges: {:#?}", self.old_active_edges);
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
println!("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);
|
||||||
|
println!("... 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) as i16;
|
||||||
|
if current_tile_x < segment_tile_x && current_subtile_x > 0.0 {
|
||||||
|
let current_x =
|
||||||
|
(i32::from(current_tile_x) * TILE_WIDTH as i32) as f32 + current_subtile_x;
|
||||||
|
let tile_right_x = ((i32::from(current_tile_x) + 1) * TILE_WIDTH as i32) as f32;
|
||||||
|
self.built_object.add_active_fill(
|
||||||
|
current_x,
|
||||||
|
tile_right_x,
|
||||||
|
current_winding,
|
||||||
|
current_tile_x,
|
||||||
|
tile_y,
|
||||||
|
);
|
||||||
|
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 {
|
||||||
|
//println!("... emitting backdrop {} @ tile {}", current_winding, current_tile_x);
|
||||||
|
self.built_object
|
||||||
|
.get_tile_mut(current_tile_x, tile_y)
|
||||||
|
.backdrop = current_winding;
|
||||||
|
current_tile_x += 1;
|
||||||
|
current_subtile_x = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do final subtile fill, if necessary.
|
||||||
|
debug_assert!(current_tile_x == segment_tile_x);
|
||||||
|
debug_assert!(current_tile_x < self.built_object.tile_rect.max_x());
|
||||||
|
let segment_subtile_x =
|
||||||
|
segment_x - (i32::from(current_tile_x) * TILE_WIDTH as i32) as f32;
|
||||||
|
if segment_subtile_x > current_subtile_x {
|
||||||
|
let current_x =
|
||||||
|
(i32::from(current_tile_x) * TILE_WIDTH as i32) as f32 + current_subtile_x;
|
||||||
|
self.built_object.add_active_fill(
|
||||||
|
current_x,
|
||||||
|
segment_x,
|
||||||
|
current_winding,
|
||||||
|
current_tile_x,
|
||||||
|
tile_y,
|
||||||
|
);
|
||||||
|
current_subtile_x = segment_subtile_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update winding.
|
||||||
|
current_winding += edge_winding;
|
||||||
|
|
||||||
|
// Process the edge.
|
||||||
|
//println!("about to process existing active edge {:#?}", active_edge);
|
||||||
|
debug_assert!(f32::abs(active_edge.crossing.y() - tile_top) < 0.1);
|
||||||
|
active_edge.process(&mut self.built_object, tile_y);
|
||||||
|
if !active_edge.segment.is_none() {
|
||||||
|
self.active_edges.push(active_edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//debug_assert_eq!(current_winding, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_new_active_edge(&mut self, tile_y: i16) {
|
||||||
|
let outline = &self.outline;
|
||||||
|
let point_index = self.point_queue.pop().unwrap().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());
|
||||||
|
/*
|
||||||
|
println!("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) {
|
||||||
|
//println!("... adding prev endpoint");
|
||||||
|
process_active_segment(
|
||||||
|
contour,
|
||||||
|
prev_endpoint_index,
|
||||||
|
&mut self.active_edges,
|
||||||
|
&mut self.built_object,
|
||||||
|
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(),
|
||||||
|
});
|
||||||
|
//println!("... done adding prev endpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
if contour.point_is_logically_above(point_index.point(), next_endpoint_index) {
|
||||||
|
/*
|
||||||
|
println!("... adding next endpoint {} -> {}",
|
||||||
|
point_index.point(),
|
||||||
|
next_endpoint_index);
|
||||||
|
*/
|
||||||
|
process_active_segment(
|
||||||
|
contour,
|
||||||
|
point_index.point(),
|
||||||
|
&mut self.active_edges,
|
||||||
|
&mut self.built_object,
|
||||||
|
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(),
|
||||||
|
});
|
||||||
|
//println!("... 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn round_rect_out_to_tile_bounds(rect: &Rect<f32>) -> Rect<i16> {
|
||||||
|
let tile_origin = Point2D::new(
|
||||||
|
(f32::floor(rect.origin.x) as i32 / TILE_WIDTH as i32) as i16,
|
||||||
|
(f32::floor(rect.origin.y) as i32 / TILE_HEIGHT as i32) as i16,
|
||||||
|
);
|
||||||
|
let tile_extent = Point2D::new(
|
||||||
|
util::alignup_i32(f32::ceil(rect.max_x()) as i32, TILE_WIDTH as i32) as i16,
|
||||||
|
util::alignup_i32(f32::ceil(rect.max_y()) as i32, TILE_HEIGHT as i32) as i16,
|
||||||
|
);
|
||||||
|
let tile_size = Size2D::new(tile_extent.x - tile_origin.x, tile_extent.y - tile_origin.y);
|
||||||
|
Rect::new(tile_origin, tile_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_active_segment(
|
||||||
|
contour: &Contour,
|
||||||
|
from_endpoint_index: u32,
|
||||||
|
active_edges: &mut SortedVector<ActiveEdge>,
|
||||||
|
built_object: &mut BuiltObject,
|
||||||
|
tile_y: i16,
|
||||||
|
) {
|
||||||
|
let mut active_edge = ActiveEdge::from_segment(&contour.segment_after(from_endpoint_index));
|
||||||
|
//println!("... process_active_segment({:#?})", active_edge);
|
||||||
|
active_edge.process(built_object, tile_y);
|
||||||
|
if !active_edge.segment.is_none() {
|
||||||
|
active_edges.push(active_edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queued endpoints
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
struct QueuedEndpoint {
|
||||||
|
point_index: PointIndex,
|
||||||
|
y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for QueuedEndpoint {}
|
||||||
|
|
||||||
|
impl PartialOrd<QueuedEndpoint> for QueuedEndpoint {
|
||||||
|
fn partial_cmp(&self, other: &QueuedEndpoint) -> Option<Ordering> {
|
||||||
|
// NB: Reversed!
|
||||||
|
(other.y, other.point_index).partial_cmp(&(self.y, self.point_index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active edges
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
struct ActiveEdge {
|
||||||
|
segment: Segment,
|
||||||
|
// TODO(pcwalton): Shrink `crossing` down to just one f32?
|
||||||
|
crossing: Point2DF32,
|
||||||
|
}
|
||||||
|
|
||||||
|
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: &Point2DF32) -> ActiveEdge {
|
||||||
|
ActiveEdge {
|
||||||
|
segment: *segment,
|
||||||
|
crossing: *crossing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(&mut self, built_object: &mut BuiltObject, tile_y: i16) {
|
||||||
|
let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32;
|
||||||
|
// println!("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, built_object, 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 =
|
||||||
|
LineSegmentF32::new(&self.crossing, &segment.baseline.upper_point())
|
||||||
|
.orient(winding);
|
||||||
|
if self
|
||||||
|
.process_line_segment(&first_line_segment, built_object, tile_y)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let rest_segment = match segment
|
||||||
|
.orient(winding)
|
||||||
|
.as_cubic_segment()
|
||||||
|
.flatten_once(FLATTENING_TOLERANCE)
|
||||||
|
{
|
||||||
|
None => {
|
||||||
|
let line_segment = segment.baseline;
|
||||||
|
self.segment =
|
||||||
|
match self.process_line_segment(&line_segment, built_object, tile_y) {
|
||||||
|
Some(ref lower_part) => Segment::line(lower_part),
|
||||||
|
None => Segment::none(),
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(rest_segment) => rest_segment.orient(winding),
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert!(segment.baseline.min_y() <= tile_bottom);
|
||||||
|
|
||||||
|
let line_segment = LineSegmentF32::new(
|
||||||
|
&segment.baseline.upper_point(),
|
||||||
|
&rest_segment.baseline.upper_point(),
|
||||||
|
)
|
||||||
|
.orient(winding);
|
||||||
|
if self
|
||||||
|
.process_line_segment(&line_segment, built_object, tile_y)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self.segment = rest_segment;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
segment = rest_segment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_line_segment(
|
||||||
|
&mut self,
|
||||||
|
line_segment: &LineSegmentF32,
|
||||||
|
built_object: &mut BuiltObject,
|
||||||
|
tile_y: i16,
|
||||||
|
) -> Option<LineSegmentF32> {
|
||||||
|
let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32;
|
||||||
|
if line_segment.max_y() <= tile_bottom {
|
||||||
|
built_object.generate_fill_primitives_for_line(*line_segment, tile_y);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (upper_part, lower_part) = line_segment.split_at_y(tile_bottom);
|
||||||
|
built_object.generate_fill_primitives_for_line(upper_part, tile_y);
|
||||||
|
self.crossing = lower_part.upper_point();
|
||||||
|
Some(lower_part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd<ActiveEdge> for ActiveEdge {
|
||||||
|
fn partial_cmp(&self, other: &ActiveEdge) -> Option<Ordering> {
|
||||||
|
self.crossing.x().partial_cmp(&other.crossing.x())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// pathfinder/renderer/src/z_buffer.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
//! Software occlusion culling.
|
||||||
|
|
||||||
|
use crate::gpu_data::{BuiltObject, SolidTileScenePrimitive};
|
||||||
|
use crate::scene;
|
||||||
|
use crate::tiles;
|
||||||
|
use euclid::Rect;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||||
|
|
||||||
|
pub struct ZBuffer {
|
||||||
|
buffer: Vec<AtomicUsize>,
|
||||||
|
tile_rect: Rect<i16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZBuffer {
|
||||||
|
pub fn new(view_box: &Rect<f32>) -> ZBuffer {
|
||||||
|
let tile_rect = tiles::round_rect_out_to_tile_bounds(view_box);
|
||||||
|
let tile_area = tile_rect.size.width as usize * tile_rect.size.height as usize;
|
||||||
|
ZBuffer {
|
||||||
|
buffer: (0..tile_area).map(|_| AtomicUsize::new(0)).collect(),
|
||||||
|
tile_rect,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test(&self, scene_tile_index: u32, object_index: u32) -> bool {
|
||||||
|
let existing_depth = self.buffer[scene_tile_index as usize].load(AtomicOrdering::SeqCst);
|
||||||
|
existing_depth < object_index as usize + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&self, tile_x: i16, tile_y: i16, object_index: u16) {
|
||||||
|
let scene_tile_index = scene::scene_tile_index(tile_x, tile_y, self.tile_rect) as usize;
|
||||||
|
let mut old_depth = self.buffer[scene_tile_index].load(AtomicOrdering::SeqCst);
|
||||||
|
let new_depth = (object_index + 1) as usize;
|
||||||
|
while old_depth < new_depth {
|
||||||
|
let prev_depth = self.buffer[scene_tile_index].compare_and_swap(
|
||||||
|
old_depth,
|
||||||
|
new_depth,
|
||||||
|
AtomicOrdering::SeqCst,
|
||||||
|
);
|
||||||
|
if prev_depth == old_depth {
|
||||||
|
// Successfully written.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
old_depth = prev_depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_solid_tiles(
|
||||||
|
&self,
|
||||||
|
objects: &[BuiltObject],
|
||||||
|
tile_rect: &Rect<i16>,
|
||||||
|
) -> Vec<SolidTileScenePrimitive> {
|
||||||
|
let mut solid_tiles = vec![];
|
||||||
|
for scene_tile_y in 0..tile_rect.size.height {
|
||||||
|
for scene_tile_x in 0..tile_rect.size.width {
|
||||||
|
let scene_tile_index =
|
||||||
|
scene_tile_y as usize * tile_rect.size.width as usize + scene_tile_x as usize;
|
||||||
|
let depth = self.buffer[scene_tile_index].load(AtomicOrdering::Relaxed);
|
||||||
|
if depth == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let object_index = (depth - 1) as usize;
|
||||||
|
solid_tiles.push(SolidTileScenePrimitive {
|
||||||
|
tile_x: scene_tile_x + tile_rect.origin.x,
|
||||||
|
tile_y: scene_tile_y + tile_rect.origin.y,
|
||||||
|
shader: objects[object_index].shader,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
solid_tiles
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "pathfinder_svg"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
euclid = "0.19"
|
||||||
|
lyon_path = "0.12"
|
||||||
|
usvg = "0.4"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_geometry]
|
||||||
|
path = "../geometry"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_renderer]
|
||||||
|
path = "../renderer"
|
|
@ -0,0 +1,256 @@
|
||||||
|
// pathfinder/svg/src/lib.rs
|
||||||
|
//
|
||||||
|
// Copyright © 2019 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.
|
||||||
|
|
||||||
|
//! Converts a subset of SVG to a Pathfinder scene.
|
||||||
|
|
||||||
|
use euclid::{Point2D, Rect, Size2D};
|
||||||
|
use lyon_path::iterator::PathIter;
|
||||||
|
use pathfinder_geometry::line_segment::LineSegmentF32;
|
||||||
|
use pathfinder_geometry::monotonic::MonotonicConversionIter;
|
||||||
|
use pathfinder_geometry::outline::Outline;
|
||||||
|
use pathfinder_geometry::point::Point2DF32;
|
||||||
|
use pathfinder_geometry::segment::{PathEventsToSegments, Segment};
|
||||||
|
use pathfinder_geometry::segment::{SegmentFlags, SegmentsToPathEvents};
|
||||||
|
use pathfinder_geometry::stroke::{StrokeStyle, StrokeToFillIter};
|
||||||
|
use pathfinder_geometry::transform::{Transform2DF32, Transform2DF32PathIter};
|
||||||
|
use pathfinder_renderer::paint::{ColorU, Paint};
|
||||||
|
use pathfinder_renderer::scene::{PathObject, PathObjectKind, Scene};
|
||||||
|
use std::mem;
|
||||||
|
use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint};
|
||||||
|
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform, Tree};
|
||||||
|
|
||||||
|
const HAIRLINE_STROKE_WIDTH: f32 = 0.1;
|
||||||
|
|
||||||
|
pub trait SceneExt {
|
||||||
|
fn from_tree(tree: Tree) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SceneExt for Scene {
|
||||||
|
// TODO(pcwalton): Allow a global transform to be set.
|
||||||
|
fn from_tree(tree: Tree) -> Scene {
|
||||||
|
let global_transform = Transform2DF32::default();
|
||||||
|
|
||||||
|
let mut scene = Scene::new();
|
||||||
|
|
||||||
|
let root = &tree.root();
|
||||||
|
match *root.borrow() {
|
||||||
|
NodeKind::Svg(ref svg) => {
|
||||||
|
scene.view_box = usvg_rect_to_euclid_rect(&svg.view_box.rect);
|
||||||
|
for kid in root.children() {
|
||||||
|
process_node(&mut scene, &kid, &global_transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME(pcwalton): This is needed to avoid stack exhaustion in debug builds when
|
||||||
|
// recursively dropping reference counts on very large SVGs. :(
|
||||||
|
mem::forget(tree);
|
||||||
|
|
||||||
|
scene
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2DF32) {
|
||||||
|
let node_transform = usvg_transform_to_transform_2d(&node.transform());
|
||||||
|
let transform = transform.pre_mul(&node_transform);
|
||||||
|
|
||||||
|
match *node.borrow() {
|
||||||
|
NodeKind::Group(_) => {
|
||||||
|
for kid in node.children() {
|
||||||
|
process_node(scene, &kid, &transform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NodeKind::Path(ref path) => {
|
||||||
|
if let Some(ref fill) = path.fill {
|
||||||
|
let style = scene.push_paint(&Paint::from_svg_paint(&fill.paint));
|
||||||
|
|
||||||
|
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
|
||||||
|
let path = Transform2DF32PathIter::new(path, &transform);
|
||||||
|
let path = MonotonicConversionIter::new(path);
|
||||||
|
let outline = Outline::from_segments(path);
|
||||||
|
|
||||||
|
scene.bounds = scene.bounds.union(outline.bounds());
|
||||||
|
scene.objects.push(PathObject::new(
|
||||||
|
outline,
|
||||||
|
style,
|
||||||
|
node.id().to_string(),
|
||||||
|
PathObjectKind::Fill,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref stroke) = path.stroke {
|
||||||
|
let style = scene.push_paint(&Paint::from_svg_paint(&stroke.paint));
|
||||||
|
let stroke_width =
|
||||||
|
f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH);
|
||||||
|
|
||||||
|
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
|
||||||
|
let path = SegmentsToPathEvents::new(path);
|
||||||
|
let path = PathIter::new(path);
|
||||||
|
let path = StrokeToFillIter::new(path, StrokeStyle::new(stroke_width));
|
||||||
|
let path = PathEventsToSegments::new(path);
|
||||||
|
let path = Transform2DF32PathIter::new(path, &transform);
|
||||||
|
let path = MonotonicConversionIter::new(path);
|
||||||
|
let outline = Outline::from_segments(path);
|
||||||
|
|
||||||
|
scene.bounds = scene.bounds.union(outline.bounds());
|
||||||
|
scene.objects.push(PathObject::new(
|
||||||
|
outline,
|
||||||
|
style,
|
||||||
|
node.id().to_string(),
|
||||||
|
PathObjectKind::Stroke,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// TODO(pcwalton): Handle these by punting to WebRender.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait PaintExt {
|
||||||
|
fn from_svg_paint(svg_paint: &UsvgPaint) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaintExt for Paint {
|
||||||
|
#[inline]
|
||||||
|
fn from_svg_paint(svg_paint: &UsvgPaint) -> Paint {
|
||||||
|
Paint {
|
||||||
|
color: match *svg_paint {
|
||||||
|
UsvgPaint::Color(color) => ColorU::from_svg_color(color),
|
||||||
|
UsvgPaint::Link(_) => {
|
||||||
|
// TODO(pcwalton)
|
||||||
|
ColorU::black()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usvg_rect_to_euclid_rect(rect: &UsvgRect) -> Rect<f32> {
|
||||||
|
Rect::new(
|
||||||
|
Point2D::new(rect.x, rect.y),
|
||||||
|
Size2D::new(rect.width, rect.height),
|
||||||
|
)
|
||||||
|
.to_f32()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usvg_transform_to_transform_2d(transform: &UsvgTransform) -> Transform2DF32 {
|
||||||
|
Transform2DF32::row_major(
|
||||||
|
transform.a as f32,
|
||||||
|
transform.b as f32,
|
||||||
|
transform.c as f32,
|
||||||
|
transform.d as f32,
|
||||||
|
transform.e as f32,
|
||||||
|
transform.f as f32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UsvgPathToSegments<I>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = UsvgPathSegment>,
|
||||||
|
{
|
||||||
|
iter: I,
|
||||||
|
first_subpath_point: Point2DF32,
|
||||||
|
last_subpath_point: Point2DF32,
|
||||||
|
just_moved: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> UsvgPathToSegments<I>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = UsvgPathSegment>,
|
||||||
|
{
|
||||||
|
fn new(iter: I) -> UsvgPathToSegments<I> {
|
||||||
|
UsvgPathToSegments {
|
||||||
|
iter,
|
||||||
|
first_subpath_point: Point2DF32::default(),
|
||||||
|
last_subpath_point: Point2DF32::default(),
|
||||||
|
just_moved: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> Iterator for UsvgPathToSegments<I>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = UsvgPathSegment>,
|
||||||
|
{
|
||||||
|
type Item = Segment;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Segment> {
|
||||||
|
match self.iter.next()? {
|
||||||
|
UsvgPathSegment::MoveTo { x, y } => {
|
||||||
|
let to = Point2DF32::new(x as f32, y as f32);
|
||||||
|
self.first_subpath_point = to;
|
||||||
|
self.last_subpath_point = to;
|
||||||
|
self.just_moved = true;
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
UsvgPathSegment::LineTo { x, y } => {
|
||||||
|
let to = Point2DF32::new(x as f32, y as f32);
|
||||||
|
let mut segment =
|
||||||
|
Segment::line(&LineSegmentF32::new(&self.last_subpath_point, &to));
|
||||||
|
if self.just_moved {
|
||||||
|
segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH);
|
||||||
|
}
|
||||||
|
self.last_subpath_point = to;
|
||||||
|
self.just_moved = false;
|
||||||
|
Some(segment)
|
||||||
|
}
|
||||||
|
UsvgPathSegment::CurveTo {
|
||||||
|
x1,
|
||||||
|
y1,
|
||||||
|
x2,
|
||||||
|
y2,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
} => {
|
||||||
|
let ctrl0 = Point2DF32::new(x1 as f32, y1 as f32);
|
||||||
|
let ctrl1 = Point2DF32::new(x2 as f32, y2 as f32);
|
||||||
|
let to = Point2DF32::new(x as f32, y as f32);
|
||||||
|
let mut segment = Segment::cubic(
|
||||||
|
&LineSegmentF32::new(&self.last_subpath_point, &to),
|
||||||
|
&LineSegmentF32::new(&ctrl0, &ctrl1),
|
||||||
|
);
|
||||||
|
if self.just_moved {
|
||||||
|
segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH);
|
||||||
|
}
|
||||||
|
self.last_subpath_point = to;
|
||||||
|
self.just_moved = false;
|
||||||
|
Some(segment)
|
||||||
|
}
|
||||||
|
UsvgPathSegment::ClosePath => {
|
||||||
|
let mut segment = Segment::line(&LineSegmentF32::new(
|
||||||
|
&self.last_subpath_point,
|
||||||
|
&self.first_subpath_point,
|
||||||
|
));
|
||||||
|
segment.flags.insert(SegmentFlags::CLOSES_SUBPATH);
|
||||||
|
self.just_moved = false;
|
||||||
|
self.last_subpath_point = self.first_subpath_point;
|
||||||
|
Some(segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ColorUExt {
|
||||||
|
fn from_svg_color(svg_color: SvgColor) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorUExt for ColorU {
|
||||||
|
#[inline]
|
||||||
|
fn from_svg_color(svg_color: SvgColor) -> ColorU {
|
||||||
|
ColorU {
|
||||||
|
r: svg_color.red,
|
||||||
|
g: svg_color.green,
|
||||||
|
b: svg_color.blue,
|
||||||
|
a: 255,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,12 @@ usvg = "0.4"
|
||||||
[dependencies.pathfinder_geometry]
|
[dependencies.pathfinder_geometry]
|
||||||
path = "../../geometry"
|
path = "../../geometry"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_renderer]
|
||||||
|
path = "../../renderer"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_svg]
|
||||||
|
path = "../../svg"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
quickcheck = "0.7"
|
quickcheck = "0.7"
|
||||||
rand = "0.5"
|
rand = "0.5"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue