Start a simple HTML canvas-like API, and add a minimal example to show how to
use it.
This commit is contained in:
parent
4a4011d303
commit
9de7d95d33
|
@ -156,6 +156,20 @@ name = "byteorder"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "canvas_minimal"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"gl 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pathfinder_canvas 0.1.0",
|
||||||
|
"pathfinder_geometry 0.3.0",
|
||||||
|
"pathfinder_gl 0.1.0",
|
||||||
|
"pathfinder_gpu 0.1.0",
|
||||||
|
"pathfinder_renderer 0.1.0",
|
||||||
|
"sdl2 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"sdl2-sys 0.32.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.29"
|
version = "1.0.29"
|
||||||
|
@ -1013,6 +1027,14 @@ dependencies = [
|
||||||
"pathfinder_gpu 0.1.0",
|
"pathfinder_gpu 0.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathfinder_canvas"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"pathfinder_geometry 0.3.0",
|
||||||
|
"pathfinder_renderer 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathfinder_demo"
|
name = "pathfinder_demo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"canvas",
|
||||||
"demo/android/rust",
|
"demo/android/rust",
|
||||||
"demo/common",
|
"demo/common",
|
||||||
"demo/magicleap",
|
"demo/magicleap",
|
||||||
"demo/native",
|
"demo/native",
|
||||||
|
"examples/canvas_minimal",
|
||||||
"geometry",
|
"geometry",
|
||||||
"gl",
|
"gl",
|
||||||
"gpu",
|
"gpu",
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "pathfinder_canvas"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[dependencies.pathfinder_geometry]
|
||||||
|
path = "../geometry"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_renderer]
|
||||||
|
path = "../renderer"
|
|
@ -0,0 +1,145 @@
|
||||||
|
// pathfinder/canvas/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.
|
||||||
|
|
||||||
|
//! A simple API for Pathfinder that mirrors a subset of HTML canvas.
|
||||||
|
|
||||||
|
use pathfinder_geometry::basic::point::Point2DF32;
|
||||||
|
use pathfinder_geometry::basic::rect::RectF32;
|
||||||
|
use pathfinder_geometry::color::ColorU;
|
||||||
|
use pathfinder_geometry::outline::{Contour, Outline};
|
||||||
|
use pathfinder_geometry::stroke::OutlineStrokeToFill;
|
||||||
|
use pathfinder_renderer::scene::{Paint, PathObject, Scene};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
|
||||||
|
|
||||||
|
pub struct CanvasRenderingContext2D {
|
||||||
|
scene: Scene,
|
||||||
|
current_paint: Paint,
|
||||||
|
current_line_width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanvasRenderingContext2D {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(size: Point2DF32) -> CanvasRenderingContext2D {
|
||||||
|
let mut scene = Scene::new();
|
||||||
|
scene.set_view_box(RectF32::new(Point2DF32::default(), size));
|
||||||
|
CanvasRenderingContext2D::from_scene(scene)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_scene(scene: Scene) -> CanvasRenderingContext2D {
|
||||||
|
CanvasRenderingContext2D {
|
||||||
|
scene,
|
||||||
|
current_paint: Paint { color: ColorU::black() },
|
||||||
|
current_line_width: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn into_scene(self) -> Scene {
|
||||||
|
self.scene
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn fill_rect(&mut self, rect: RectF32) {
|
||||||
|
let mut path = Path2D::new();
|
||||||
|
path.rect(rect);
|
||||||
|
self.fill_path(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn stroke_rect(&mut self, rect: RectF32) {
|
||||||
|
let mut path = Path2D::new();
|
||||||
|
path.rect(rect);
|
||||||
|
self.stroke_path(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_line_width(&mut self, new_line_width: f32) {
|
||||||
|
self.current_line_width = new_line_width
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn fill_path(&mut self, path: Path2D) {
|
||||||
|
let paint_id = self.scene.push_paint(&self.current_paint);
|
||||||
|
self.scene.push_object(PathObject::new(path.into_outline(), paint_id, String::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn stroke_path(&mut self, path: Path2D) {
|
||||||
|
let paint_id = self.scene.push_paint(&self.current_paint);
|
||||||
|
let stroke_width = f32::max(self.current_line_width, HAIRLINE_STROKE_WIDTH);
|
||||||
|
let mut stroke_to_fill = OutlineStrokeToFill::new(path.into_outline(), stroke_width);
|
||||||
|
stroke_to_fill.offset();
|
||||||
|
self.scene.push_object(PathObject::new(stroke_to_fill.outline, paint_id, String::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Path2D {
|
||||||
|
outline: Outline,
|
||||||
|
current_contour: Contour,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(pcwalton): `arc`, `ellipse`
|
||||||
|
impl Path2D {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Path2D {
|
||||||
|
Path2D { outline: Outline::new(), current_contour: Contour::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn close_path(&mut self) {
|
||||||
|
self.current_contour.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn move_to(&mut self, to: Point2DF32) {
|
||||||
|
// TODO(pcwalton): Cull degenerate contours.
|
||||||
|
self.flush_current_contour();
|
||||||
|
self.current_contour.push_endpoint(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn line_to(&mut self, to: Point2DF32) {
|
||||||
|
self.current_contour.push_endpoint(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn quadratic_curve_to(&mut self, ctrl: Point2DF32, to: Point2DF32) {
|
||||||
|
self.current_contour.push_quadratic(ctrl, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn bezier_curve_to(&mut self, ctrl0: Point2DF32, ctrl1: Point2DF32, to: Point2DF32) {
|
||||||
|
self.current_contour.push_cubic(ctrl0, ctrl1, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rect(&mut self, rect: RectF32) {
|
||||||
|
self.flush_current_contour();
|
||||||
|
self.current_contour.push_endpoint(rect.origin());
|
||||||
|
self.current_contour.push_endpoint(rect.upper_right());
|
||||||
|
self.current_contour.push_endpoint(rect.lower_right());
|
||||||
|
self.current_contour.push_endpoint(rect.lower_left());
|
||||||
|
self.current_contour.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_outline(mut self) -> Outline {
|
||||||
|
self.flush_current_contour();
|
||||||
|
self.outline
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_current_contour(&mut self) {
|
||||||
|
if !self.current_contour.is_empty() {
|
||||||
|
self.outline.push_contour(mem::replace(&mut self.current_contour, Contour::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -242,8 +242,7 @@ impl<W> DemoApp<W> where W: Window {
|
||||||
subpixel_aa_enabled: self.ui.subpixel_aa_effect_enabled,
|
subpixel_aa_enabled: self.ui.subpixel_aa_effect_enabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
let built_options = render_options.prepare(self.scene_metadata.bounds);
|
self.render_command_stream = Some(self.scene_proxy.build_with_stream(render_options));
|
||||||
self.render_command_stream = Some(self.scene_proxy.build_with_stream(built_options));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self, events: Vec<Event>) -> Vec<UIEvent> {
|
fn handle_events(&mut self, events: Vec<Event>) -> Vec<UIEvent> {
|
||||||
|
@ -769,7 +768,6 @@ impl BackgroundColor {
|
||||||
|
|
||||||
struct SceneMetadata {
|
struct SceneMetadata {
|
||||||
view_box: RectF32,
|
view_box: RectF32,
|
||||||
bounds: RectF32,
|
|
||||||
monochrome_color: Option<ColorU>,
|
monochrome_color: Option<ColorU>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -780,6 +778,6 @@ impl SceneMetadata {
|
||||||
let view_box = scene.view_box();
|
let view_box = scene.view_box();
|
||||||
let monochrome_color = scene.monochrome_color();
|
let monochrome_color = scene.monochrome_color();
|
||||||
scene.set_view_box(RectF32::new(Point2DF32::default(), viewport_size.to_f32()));
|
scene.set_view_box(RectF32::new(Point2DF32::default(), viewport_size.to_f32()));
|
||||||
SceneMetadata { view_box, monochrome_color, bounds: scene.bounds() }
|
SceneMetadata { view_box, monochrome_color }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
[package]
|
||||||
|
name = "canvas_minimal"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gl = "0.6"
|
||||||
|
sdl2 = "0.32"
|
||||||
|
sdl2-sys = "0.32"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_canvas]
|
||||||
|
path = "../../canvas"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_geometry]
|
||||||
|
path = "../../geometry"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_gl]
|
||||||
|
path = "../../gl"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_gpu]
|
||||||
|
path = "../../gpu"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_renderer]
|
||||||
|
path = "../../renderer"
|
|
@ -0,0 +1,89 @@
|
||||||
|
// pathfinder/canvas_minimal/src/main.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 pathfinder_canvas::{CanvasRenderingContext2D, Path2D};
|
||||||
|
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32};
|
||||||
|
use pathfinder_geometry::basic::rect::RectF32;
|
||||||
|
use pathfinder_geometry::color::ColorF;
|
||||||
|
use pathfinder_gl::{GLDevice, GLVersion};
|
||||||
|
use pathfinder_gpu::resources::FilesystemResourceLoader;
|
||||||
|
use pathfinder_gpu::{ClearParams, Device};
|
||||||
|
use pathfinder_renderer::concurrent::rayon::RayonExecutor;
|
||||||
|
use pathfinder_renderer::concurrent::scene_proxy::SceneProxy;
|
||||||
|
use pathfinder_renderer::gpu::renderer::{DestFramebuffer, Renderer};
|
||||||
|
use pathfinder_renderer::options::RenderOptions;
|
||||||
|
use sdl2::event::Event;
|
||||||
|
use sdl2::keyboard::Keycode;
|
||||||
|
use sdl2::video::GLProfile;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Set up SDL2.
|
||||||
|
let sdl_context = sdl2::init().unwrap();
|
||||||
|
let video = sdl_context.video().unwrap();
|
||||||
|
|
||||||
|
// Make sure we have at least a GL 3.0 context. Pathfinder requires this.
|
||||||
|
let gl_attributes = video.gl_attr();
|
||||||
|
gl_attributes.set_context_profile(GLProfile::Core);
|
||||||
|
gl_attributes.set_context_version(3, 3);
|
||||||
|
|
||||||
|
// Open a window.
|
||||||
|
let window_size = Point2DI32::new(640, 480);
|
||||||
|
let window = video.window("Minimal example", window_size.x() as u32, window_size.y() as u32)
|
||||||
|
.opengl()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Create the GL context, and make it current.
|
||||||
|
let gl_context = window.gl_create_context().unwrap();
|
||||||
|
gl::load_with(|name| video.gl_get_proc_address(name) as *const _);
|
||||||
|
window.gl_make_current(&gl_context).unwrap();
|
||||||
|
|
||||||
|
// Create a Pathfinder renderer.
|
||||||
|
let mut renderer = Renderer::new(GLDevice::new(GLVersion::GL3, 0),
|
||||||
|
&FilesystemResourceLoader::locate(),
|
||||||
|
DestFramebuffer::full_window(window_size));
|
||||||
|
|
||||||
|
// Clear to white.
|
||||||
|
renderer.device.clear(&ClearParams { color: Some(ColorF::white()), ..ClearParams::default() });
|
||||||
|
|
||||||
|
// Make a canvas. We're going to draw a house.
|
||||||
|
let mut canvas = CanvasRenderingContext2D::new(window_size.to_f32());
|
||||||
|
|
||||||
|
// Set line width.
|
||||||
|
canvas.set_line_width(10.0);
|
||||||
|
|
||||||
|
// Draw walls.
|
||||||
|
canvas.stroke_rect(RectF32::new(Point2DF32::new(75.0, 140.0), Point2DF32::new(150.0, 110.0)));
|
||||||
|
|
||||||
|
// Draw door.
|
||||||
|
canvas.fill_rect(RectF32::new(Point2DF32::new(130.0, 190.0), Point2DF32::new(40.0, 60.0)));
|
||||||
|
|
||||||
|
// Draw roof.
|
||||||
|
let mut path = Path2D::new();
|
||||||
|
path.move_to(Point2DF32::new(50.0, 140.0));
|
||||||
|
path.line_to(Point2DF32::new(150.0, 60.0));
|
||||||
|
path.line_to(Point2DF32::new(250.0, 140.0));
|
||||||
|
path.close_path();
|
||||||
|
canvas.stroke_path(path);
|
||||||
|
|
||||||
|
// Render the canvas to screen.
|
||||||
|
let scene = SceneProxy::new(canvas.into_scene(), RayonExecutor);
|
||||||
|
scene.build_and_render(&mut renderer, RenderOptions::default());
|
||||||
|
window.gl_swap_window();
|
||||||
|
|
||||||
|
// Wait for a keypress.
|
||||||
|
let mut event_pump = sdl_context.event_pump().unwrap();
|
||||||
|
loop {
|
||||||
|
match event_pump.wait_event() {
|
||||||
|
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => return,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,6 +63,11 @@ impl ColorF {
|
||||||
ColorF(F32x4::default())
|
ColorF(F32x4::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn white() -> ColorF {
|
||||||
|
ColorF(F32x4::splat(1.0))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn r(&self) -> f32 {
|
pub fn r(&self) -> f32 {
|
||||||
self.0[0]
|
self.0[0]
|
||||||
|
|
|
@ -24,7 +24,7 @@ use std::mem;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Outline {
|
pub struct Outline {
|
||||||
pub contours: Vec<Contour>,
|
pub(crate) contours: Vec<Contour>,
|
||||||
pub(crate) bounds: RectF32,
|
pub(crate) bounds: RectF32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,6 @@ impl Outline {
|
||||||
{
|
{
|
||||||
let mut outline = Outline::new();
|
let mut outline = Outline::new();
|
||||||
let mut current_contour = Contour::new();
|
let mut current_contour = Contour::new();
|
||||||
let mut bounds = None;
|
|
||||||
|
|
||||||
for segment in segments {
|
for segment in segments {
|
||||||
if segment.flags.contains(SegmentFlags::FIRST_IN_SUBPATH) {
|
if segment.flags.contains(SegmentFlags::FIRST_IN_SUBPATH) {
|
||||||
|
@ -75,8 +74,7 @@ impl Outline {
|
||||||
if !current_contour.is_empty() {
|
if !current_contour.is_empty() {
|
||||||
current_contour.close();
|
current_contour.close();
|
||||||
let contour = mem::replace(&mut current_contour, Contour::new());
|
let contour = mem::replace(&mut current_contour, Contour::new());
|
||||||
contour.update_bounds(&mut bounds);
|
outline.push_contour(contour);
|
||||||
outline.contours.push(contour);
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -99,15 +97,7 @@ impl Outline {
|
||||||
current_contour.push_point(segment.baseline.to(), PointFlags::empty(), true);
|
current_contour.push_point(segment.baseline.to(), PointFlags::empty(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !current_contour.is_empty() {
|
outline.push_contour(current_contour);
|
||||||
current_contour.update_bounds(&mut bounds);
|
|
||||||
outline.contours.push(current_contour);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(bounds) = bounds {
|
|
||||||
outline.bounds = bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
outline
|
outline
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +106,25 @@ impl Outline {
|
||||||
self.bounds
|
self.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn contours(&self) -> &[Contour] {
|
||||||
|
&self.contours
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_contour(&mut self, contour: Contour) {
|
||||||
|
if contour.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.contours.is_empty() {
|
||||||
|
self.bounds = contour.bounds;
|
||||||
|
} else {
|
||||||
|
self.bounds = self.bounds.union_rect(contour.bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contours.push(contour);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, transform: &Transform2DF32) {
|
pub fn transform(&mut self, transform: &Transform2DF32) {
|
||||||
let mut new_bounds = None;
|
let mut new_bounds = None;
|
||||||
for contour in &mut self.contours {
|
for contour in &mut self.contours {
|
||||||
|
@ -166,15 +175,9 @@ impl Outline {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut new_bounds = None;
|
|
||||||
for contour in mem::replace(&mut self.contours, vec![]) {
|
for contour in mem::replace(&mut self.contours, vec![]) {
|
||||||
let contour = ContourPolygonClipper::new(clip_polygon, contour).clip();
|
self.push_contour(ContourPolygonClipper::new(clip_polygon, contour).clip());
|
||||||
if !contour.is_empty() {
|
|
||||||
contour.update_bounds(&mut new_bounds);
|
|
||||||
self.contours.push(contour);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_against_rect(&mut self, clip_rect: RectF32) {
|
pub fn clip_against_rect(&mut self, clip_rect: RectF32) {
|
||||||
|
@ -182,15 +185,9 @@ impl Outline {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut new_bounds = None;
|
|
||||||
for contour in mem::replace(&mut self.contours, vec![]) {
|
for contour in mem::replace(&mut self.contours, vec![]) {
|
||||||
let contour = ContourRectClipper::new(clip_rect, contour).clip();
|
self.push_contour(ContourRectClipper::new(clip_rect, contour).clip());
|
||||||
if !contour.is_empty() {
|
|
||||||
contour.update_bounds(&mut new_bounds);
|
|
||||||
self.contours.push(contour);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +268,25 @@ impl Contour {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn close(&mut self) {
|
pub fn push_endpoint(&mut self, point: Point2DF32) {
|
||||||
|
self.push_point(point, PointFlags::empty(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn push_quadratic(&mut self, ctrl: Point2DF32, point: Point2DF32) {
|
||||||
|
self.push_point(ctrl, PointFlags::CONTROL_POINT_0, true);
|
||||||
|
self.push_point(point, PointFlags::empty(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn push_cubic(&mut self, ctrl0: Point2DF32, ctrl1: Point2DF32, point: Point2DF32) {
|
||||||
|
self.push_point(ctrl0, PointFlags::CONTROL_POINT_0, true);
|
||||||
|
self.push_point(ctrl1, PointFlags::CONTROL_POINT_1, true);
|
||||||
|
self.push_point(point, PointFlags::empty(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn close(&mut self) {
|
||||||
self.closed = true;
|
self.closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,6 +611,8 @@ impl Contour {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use this function to keep bounds up to date when mutating paths. See `Outline::transform()`
|
||||||
|
// for an example of use.
|
||||||
pub(crate) fn update_bounds(&self, bounds: &mut Option<RectF32>) {
|
pub(crate) fn update_bounds(&self, bounds: &mut Option<RectF32>) {
|
||||||
*bounds = Some(match *bounds {
|
*bounds = Some(match *bounds {
|
||||||
None => self.bounds,
|
None => self.bounds,
|
||||||
|
|
|
@ -21,7 +21,7 @@ use std::sync::atomic::AtomicUsize;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::u16;
|
use std::u16;
|
||||||
|
|
||||||
pub struct SceneBuilder<'a> {
|
pub(crate) struct SceneBuilder<'a> {
|
||||||
scene: &'a Scene,
|
scene: &'a Scene,
|
||||||
built_options: &'a PreparedRenderOptions,
|
built_options: &'a PreparedRenderOptions,
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ pub struct SceneBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SceneBuilder<'a> {
|
impl<'a> SceneBuilder<'a> {
|
||||||
pub fn new(
|
pub(crate) fn new(
|
||||||
scene: &'a Scene,
|
scene: &'a Scene,
|
||||||
built_options: &'a PreparedRenderOptions,
|
built_options: &'a PreparedRenderOptions,
|
||||||
listener: Box<dyn RenderCommandListener>,
|
listener: Box<dyn RenderCommandListener>,
|
||||||
|
|
|
@ -20,10 +20,12 @@
|
||||||
//! You don't need to use this API to use Pathfinder; it's only a convenience.
|
//! You don't need to use this API to use Pathfinder; it's only a convenience.
|
||||||
|
|
||||||
use crate::concurrent::executor::Executor;
|
use crate::concurrent::executor::Executor;
|
||||||
|
use crate::gpu::renderer::Renderer;
|
||||||
use crate::gpu_data::RenderCommand;
|
use crate::gpu_data::RenderCommand;
|
||||||
use crate::options::{PreparedRenderOptions, RenderCommandListener};
|
use crate::options::{RenderCommandListener, RenderOptions};
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
use pathfinder_geometry::basic::rect::RectF32;
|
use pathfinder_geometry::basic::rect::RectF32;
|
||||||
|
use pathfinder_gpu::Device;
|
||||||
use std::sync::mpsc::{self, Receiver, Sender};
|
use std::sync::mpsc::{self, Receiver, Sender};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
|
@ -52,18 +54,34 @@ impl SceneProxy {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build_with_listener(&self,
|
pub fn build_with_listener(&self,
|
||||||
built_options: PreparedRenderOptions,
|
options: RenderOptions,
|
||||||
listener: Box<dyn RenderCommandListener>) {
|
listener: Box<dyn RenderCommandListener>) {
|
||||||
self.sender.send(MainToWorkerMsg::Build(built_options, listener)).unwrap();
|
self.sender.send(MainToWorkerMsg::Build(options, listener)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build_with_stream(&self, built_options: PreparedRenderOptions) -> RenderCommandStream {
|
pub fn build_with_stream(&self, options: RenderOptions) -> RenderCommandStream {
|
||||||
let (sender, receiver) = mpsc::sync_channel(MAX_MESSAGES_IN_FLIGHT);
|
let (sender, receiver) = mpsc::sync_channel(MAX_MESSAGES_IN_FLIGHT);
|
||||||
let listener = Box::new(move |command| sender.send(command).unwrap());
|
let listener = Box::new(move |command| sender.send(command).unwrap());
|
||||||
self.build_with_listener(built_options, listener);
|
self.build_with_listener(options, listener);
|
||||||
RenderCommandStream::new(receiver)
|
RenderCommandStream::new(receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A convenience method to build a scene and send the resulting commands
|
||||||
|
/// to the given renderer.
|
||||||
|
///
|
||||||
|
/// Exactly equivalent to:
|
||||||
|
///
|
||||||
|
/// for command in scene_proxy.build_with_stream(options) {
|
||||||
|
/// renderer.render_command(&command)
|
||||||
|
/// }
|
||||||
|
#[inline]
|
||||||
|
pub fn build_and_render<D>(&self, renderer: &mut Renderer<D>, options: RenderOptions)
|
||||||
|
where D: Device {
|
||||||
|
for command in self.build_with_stream(options) {
|
||||||
|
renderer.render_command(&command)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scene_thread<E>(mut scene: Scene,
|
fn scene_thread<E>(mut scene: Scene,
|
||||||
|
@ -74,9 +92,7 @@ fn scene_thread<E>(mut scene: Scene,
|
||||||
match msg {
|
match msg {
|
||||||
MainToWorkerMsg::ReplaceScene(new_scene) => scene = new_scene,
|
MainToWorkerMsg::ReplaceScene(new_scene) => scene = new_scene,
|
||||||
MainToWorkerMsg::SetViewBox(new_view_box) => scene.set_view_box(new_view_box),
|
MainToWorkerMsg::SetViewBox(new_view_box) => scene.set_view_box(new_view_box),
|
||||||
MainToWorkerMsg::Build(options, listener) => {
|
MainToWorkerMsg::Build(options, listener) => scene.build(options, listener, &executor),
|
||||||
scene.build(&options, listener, &executor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +100,7 @@ fn scene_thread<E>(mut scene: Scene,
|
||||||
enum MainToWorkerMsg {
|
enum MainToWorkerMsg {
|
||||||
ReplaceScene(Scene),
|
ReplaceScene(Scene),
|
||||||
SetViewBox(RectF32),
|
SetViewBox(RectF32),
|
||||||
Build(PreparedRenderOptions, Box<dyn RenderCommandListener>),
|
Build(RenderOptions, Box<dyn RenderCommandListener>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RenderCommandStream {
|
pub struct RenderCommandStream {
|
||||||
|
|
|
@ -177,7 +177,7 @@ where
|
||||||
|
|
||||||
let debug_ui = DebugUI::new(&device, resources, dest_framebuffer.window_size(&device));
|
let debug_ui = DebugUI::new(&device, resources, dest_framebuffer.window_size(&device));
|
||||||
|
|
||||||
Renderer {
|
let renderer = Renderer {
|
||||||
device,
|
device,
|
||||||
|
|
||||||
dest_framebuffer,
|
dest_framebuffer,
|
||||||
|
@ -218,7 +218,12 @@ where
|
||||||
|
|
||||||
render_mode: RenderMode::default(),
|
render_mode: RenderMode::default(),
|
||||||
use_depth: false,
|
use_depth: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// As a convenience, bind the destination framebuffer.
|
||||||
|
renderer.bind_dest_framebuffer();
|
||||||
|
|
||||||
|
renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin_scene(&mut self) {
|
pub fn begin_scene(&mut self) {
|
||||||
|
@ -1466,6 +1471,12 @@ impl<D> DestFramebuffer<D>
|
||||||
where
|
where
|
||||||
D: Device,
|
D: Device,
|
||||||
{
|
{
|
||||||
|
#[inline]
|
||||||
|
pub fn full_window(window_size: Point2DI32) -> DestFramebuffer<D> {
|
||||||
|
let viewport = RectI32::new(Point2DI32::default(), window_size);
|
||||||
|
DestFramebuffer::Default { viewport, window_size }
|
||||||
|
}
|
||||||
|
|
||||||
fn window_size(&self, device: &D) -> Point2DI32 {
|
fn window_size(&self, device: &D) -> Point2DI32 {
|
||||||
match *self {
|
match *self {
|
||||||
DestFramebuffer::Default { window_size, .. } => window_size,
|
DestFramebuffer::Default { window_size, .. } => window_size,
|
||||||
|
|
|
@ -39,7 +39,7 @@ pub struct RenderOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOptions {
|
impl RenderOptions {
|
||||||
pub fn prepare(self, bounds: RectF32) -> PreparedRenderOptions {
|
pub(crate) fn prepare(self, bounds: RectF32) -> PreparedRenderOptions {
|
||||||
PreparedRenderOptions {
|
PreparedRenderOptions {
|
||||||
transform: self.transform.prepare(bounds),
|
transform: self.transform.prepare(bounds),
|
||||||
dilation: self.dilation,
|
dilation: self.dilation,
|
||||||
|
@ -119,15 +119,15 @@ impl RenderTransform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PreparedRenderOptions {
|
pub(crate) struct PreparedRenderOptions {
|
||||||
pub transform: PreparedRenderTransform,
|
pub(crate) transform: PreparedRenderTransform,
|
||||||
pub dilation: Point2DF32,
|
pub(crate) dilation: Point2DF32,
|
||||||
pub subpixel_aa_enabled: bool,
|
pub(crate) subpixel_aa_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreparedRenderOptions {
|
impl PreparedRenderOptions {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn bounding_quad(&self) -> BoundingQuad {
|
pub(crate) fn bounding_quad(&self) -> BoundingQuad {
|
||||||
match self.transform {
|
match self.transform {
|
||||||
PreparedRenderTransform::Perspective { quad, .. } => quad,
|
PreparedRenderTransform::Perspective { quad, .. } => quad,
|
||||||
_ => [Point3DF32::default(); 4],
|
_ => [Point3DF32::default(); 4],
|
||||||
|
@ -135,9 +135,9 @@ impl PreparedRenderOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type BoundingQuad = [Point3DF32; 4];
|
pub(crate) type BoundingQuad = [Point3DF32; 4];
|
||||||
|
|
||||||
pub enum PreparedRenderTransform {
|
pub(crate) enum PreparedRenderTransform {
|
||||||
None,
|
None,
|
||||||
Transform2D(Transform2DF32),
|
Transform2D(Transform2DF32),
|
||||||
Perspective {
|
Perspective {
|
||||||
|
@ -149,7 +149,7 @@ pub enum PreparedRenderTransform {
|
||||||
|
|
||||||
impl PreparedRenderTransform {
|
impl PreparedRenderTransform {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_2d(&self) -> bool {
|
pub(crate) fn is_2d(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
PreparedRenderTransform::Transform2D(_) => true,
|
PreparedRenderTransform::Transform2D(_) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
|
|
||||||
use crate::builder::SceneBuilder;
|
use crate::builder::SceneBuilder;
|
||||||
use crate::concurrent::executor::Executor;
|
use crate::concurrent::executor::Executor;
|
||||||
use crate::options::{PreparedRenderOptions, PreparedRenderTransform, RenderCommandListener};
|
use crate::options::{PreparedRenderOptions, PreparedRenderTransform};
|
||||||
|
use crate::options::{RenderCommandListener, RenderOptions};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use pathfinder_geometry::basic::point::Point2DF32;
|
use pathfinder_geometry::basic::point::Point2DF32;
|
||||||
use pathfinder_geometry::basic::rect::RectF32;
|
use pathfinder_geometry::basic::rect::RectF32;
|
||||||
|
@ -43,6 +44,7 @@ impl Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_object(&mut self, object: PathObject) {
|
pub fn push_object(&mut self, object: PathObject) {
|
||||||
|
self.bounds = self.bounds.union_rect(object.outline.bounds());
|
||||||
self.objects.push(object);
|
self.objects.push(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +167,7 @@ impl Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn effective_view_box(&self, render_options: &PreparedRenderOptions) -> RectF32 {
|
pub(crate) fn effective_view_box(&self, render_options: &PreparedRenderOptions) -> RectF32 {
|
||||||
if render_options.subpixel_aa_enabled {
|
if render_options.subpixel_aa_enabled {
|
||||||
self.view_box.scale_xy(Point2DF32::new(3.0, 1.0))
|
self.view_box.scale_xy(Point2DF32::new(3.0, 1.0))
|
||||||
} else {
|
} else {
|
||||||
|
@ -175,11 +177,12 @@ impl Scene {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build<E>(&self,
|
pub fn build<E>(&self,
|
||||||
built_options: &PreparedRenderOptions,
|
options: RenderOptions,
|
||||||
listener: Box<dyn RenderCommandListener>,
|
listener: Box<dyn RenderCommandListener>,
|
||||||
executor: &E)
|
executor: &E)
|
||||||
where E: Executor {
|
where E: Executor {
|
||||||
SceneBuilder::new(self, built_options, listener).build(executor)
|
let prepared_options = options.prepare(self.bounds);
|
||||||
|
SceneBuilder::new(self, &prepared_options, listener).build(executor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,25 +218,12 @@ pub struct PathObject {
|
||||||
outline: Outline,
|
outline: Outline,
|
||||||
paint: PaintId,
|
paint: PaintId,
|
||||||
name: String,
|
name: String,
|
||||||
kind: PathObjectKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum PathObjectKind {
|
|
||||||
Fill,
|
|
||||||
Stroke,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PathObject {
|
impl PathObject {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(outline: Outline, paint: PaintId, name: String, kind: PathObjectKind)
|
pub fn new(outline: Outline, paint: PaintId, name: String) -> PathObject {
|
||||||
-> PathObject {
|
PathObject { outline, paint, name }
|
||||||
PathObject {
|
|
||||||
outline,
|
|
||||||
paint,
|
|
||||||
name,
|
|
||||||
kind,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -245,7 +245,7 @@ impl<'a> Tiler<'a> {
|
||||||
let outline = &self.outline;
|
let outline = &self.outline;
|
||||||
let point_index = self.point_queue.pop().unwrap().point_index;
|
let point_index = self.point_queue.pop().unwrap().point_index;
|
||||||
|
|
||||||
let contour = &outline.contours[point_index.contour() as usize];
|
let contour = &outline.contours()[point_index.contour() as usize];
|
||||||
|
|
||||||
// TODO(pcwalton): Could use a bitset of processed edges…
|
// TODO(pcwalton): Could use a bitset of processed edges…
|
||||||
let prev_endpoint_index = contour.prev_endpoint_index_of(point_index.point());
|
let prev_endpoint_index = contour.prev_endpoint_index_of(point_index.point());
|
||||||
|
@ -311,7 +311,7 @@ impl<'a> Tiler<'a> {
|
||||||
fn init_point_queue(&mut self) {
|
fn init_point_queue(&mut self) {
|
||||||
// Find MIN points.
|
// Find MIN points.
|
||||||
self.point_queue.clear();
|
self.point_queue.clear();
|
||||||
for (contour_index, contour) in self.outline.contours.iter().enumerate() {
|
for (contour_index, contour) in self.outline.contours().iter().enumerate() {
|
||||||
let contour_index = contour_index as u32;
|
let contour_index = contour_index as u32;
|
||||||
let mut cur_endpoint_index = 0;
|
let mut cur_endpoint_index = 0;
|
||||||
let mut prev_endpoint_index = contour.prev_endpoint_index_of(cur_endpoint_index);
|
let mut prev_endpoint_index = contour.prev_endpoint_index_of(cur_endpoint_index);
|
||||||
|
|
|
@ -21,7 +21,7 @@ use pathfinder_geometry::color::ColorU;
|
||||||
use pathfinder_geometry::outline::Outline;
|
use pathfinder_geometry::outline::Outline;
|
||||||
use pathfinder_geometry::segment::{Segment, SegmentFlags};
|
use pathfinder_geometry::segment::{Segment, SegmentFlags};
|
||||||
use pathfinder_geometry::stroke::OutlineStrokeToFill;
|
use pathfinder_geometry::stroke::OutlineStrokeToFill;
|
||||||
use pathfinder_renderer::scene::{Paint, PathObject, PathObjectKind, Scene};
|
use pathfinder_renderer::scene::{Paint, PathObject, Scene};
|
||||||
use std::fmt::{Display, Formatter, Result as FormatResult};
|
use std::fmt::{Display, Formatter, Result as FormatResult};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint};
|
use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint};
|
||||||
|
@ -122,12 +122,10 @@ impl BuiltSVG {
|
||||||
let path = Transform2DF32PathIter::new(path, &transform);
|
let path = Transform2DF32PathIter::new(path, &transform);
|
||||||
let outline = Outline::from_segments(path);
|
let outline = Outline::from_segments(path);
|
||||||
|
|
||||||
self.scene.set_bounds(self.scene.bounds().union_rect(outline.bounds()));
|
|
||||||
self.scene.push_object(PathObject::new(
|
self.scene.push_object(PathObject::new(
|
||||||
outline,
|
outline,
|
||||||
style,
|
style,
|
||||||
node.id().to_string(),
|
format!("Fill({})", node.id()),
|
||||||
PathObjectKind::Fill,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,12 +144,10 @@ impl BuiltSVG {
|
||||||
let mut outline = stroke_to_fill.outline;
|
let mut outline = stroke_to_fill.outline;
|
||||||
outline.transform(&transform);
|
outline.transform(&transform);
|
||||||
|
|
||||||
self.scene.set_bounds(self.scene.bounds().union_rect(outline.bounds()));
|
|
||||||
self.scene.push_object(PathObject::new(
|
self.scene.push_object(PathObject::new(
|
||||||
outline,
|
outline,
|
||||||
style,
|
style,
|
||||||
node.id().to_string(),
|
format!("Stroke({})", node.id()),
|
||||||
PathObjectKind::Stroke,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue