Factor renderer and SVG code out into separate crates

This commit is contained in:
Patrick Walton 2019-01-14 14:20:36 -08:00
parent 3f28845157
commit 821b54b8f4
16 changed files with 1692 additions and 1438 deletions

26
Cargo.lock generated
View File

@ -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)",

View File

@ -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",

18
renderer/Cargo.toml Normal file
View File

@ -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"

119
renderer/src/builder.rs Normal file
View File

@ -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)
}
}
}

287
renderer/src/gpu_data.rs Normal file
View File

@ -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,
}
}
}

21
renderer/src/lib.rs Normal file
View File

@ -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;

47
renderer/src/paint.rs Normal file
View File

@ -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,
}

132
renderer/src/scene.rs Normal file
View File

@ -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
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}

467
renderer/src/tiles.rs Normal file
View File

@ -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())
}
}

82
renderer/src/z_buffer.rs Normal file
View File

@ -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
}
}

16
svg/Cargo.toml Normal file
View File

@ -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"

256
svg/src/lib.rs Normal file
View File

@ -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,
}
}
}

View File

@ -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