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)",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "phf"
|
||||
version = "0.7.24"
|
||||
|
@ -724,6 +748,8 @@ dependencies = [
|
|||
"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)",
|
||||
"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)",
|
||||
"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)",
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
members = [
|
||||
"geometry",
|
||||
"gfx-utils",
|
||||
"renderer",
|
||||
"svg",
|
||||
"utils/area-lut",
|
||||
"utils/gamma-lut",
|
||||
"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]
|
||||
path = "../../geometry"
|
||||
|
||||
[dependencies.pathfinder_renderer]
|
||||
path = "../../renderer"
|
||||
|
||||
[dependencies.pathfinder_svg]
|
||||
path = "../../svg"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.7"
|
||||
rand = "0.5"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue