Factor the scene thread out of the demo and into the renderer proper.

This simplifies Pathfinder's API considerably. Now users get off-main-thread
scene building "for free".
This commit is contained in:
Patrick Walton 2019-04-30 19:13:28 -07:00
parent a96ee73da6
commit 5c5e4e9313
10 changed files with 459 additions and 426 deletions

View File

@ -28,23 +28,20 @@ use pathfinder_gl::GLDevice;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::{ClearParams, DepthFunc, DepthState, Device, Primitive, RenderState};
use pathfinder_gpu::{StencilFunc, StencilState, TextureFormat, UniformData};
use pathfinder_renderer::builder::{RenderOptions, RenderTransform, SceneBuilder};
use pathfinder_renderer::concurrent::scene_proxy::{RenderCommandStream, SceneProxy};
use pathfinder_renderer::gpu::renderer::{DestFramebuffer, RenderMode, RenderStats, Renderer};
use pathfinder_renderer::gpu_data::RenderCommand;
use pathfinder_renderer::options::{RenderOptions, RenderTransform};
use pathfinder_renderer::post::{DEFRINGING_KERNEL_CORE_GRAPHICS, STEM_DARKENING_FACTORS};
use pathfinder_renderer::scene::{Scene, SceneDescriptor};
use pathfinder_renderer::scene::Scene;
use pathfinder_svg::BuiltSVG;
use pathfinder_ui::{MousePosition, UIEvent};
use std::f32::consts::FRAC_PI_4;
use std::fmt::{Debug, Formatter, Result as DebugResult};
use std::fs::File;
use std::io::Read;
use std::panic::{self, AssertUnwindSafe};
use std::path::PathBuf;
use std::process;
use std::sync::mpsc::{self, Receiver, Sender, SyncSender};
use std::thread;
use std::time::{Duration, Instant};
use std::time::Duration;
use usvg::{Options as UsvgOptions, Tree};
static DEFAULT_SVG_VIRTUAL_PATH: &'static str = "svg/Ghostscript_Tiger.svg";
@ -96,8 +93,6 @@ const APPROX_FONT_SIZE: f32 = 16.0;
const MESSAGE_TIMEOUT_SECS: u64 = 5;
const MAX_MESSAGES_IN_FLIGHT: usize = 256;
// Half of the eye separation distance.
const DEFAULT_EYE_OFFSET: f32 = 0.025;
@ -116,8 +111,9 @@ pub struct DemoApp<W> where W: Window {
window_size: WindowSize,
scene_view_box: RectF32,
monochrome_scene_color: Option<ColorU>,
scene_metadata: SceneMetadata,
render_transform: Option<RenderTransform>,
render_command_stream: Option<RenderCommandStream>,
camera: Camera,
frame_counter: u32,
@ -129,9 +125,10 @@ pub struct DemoApp<W> where W: Window {
last_mouse_position: Point2DI32,
current_frame: Option<Frame>,
build_time: Option<Duration>,
ui: DemoUI<GLDevice>,
scene_thread_proxy: SceneThreadProxy,
scene_proxy: SceneProxy,
renderer: Renderer<GLDevice>,
scene_framebuffer: Option<<GLDevice as Device>::Framebuffer>,
@ -154,10 +151,8 @@ impl<W> DemoApp<W> where W: Window {
// Set up the executor.
let executor = DemoExecutor::new(options.jobs);
let built_svg = load_scene(resources, &options.input_path);
let mut built_svg = load_scene(resources, &options.input_path);
let message = get_svg_building_message(&built_svg);
let scene_view_box = built_svg.scene.view_box;
let monochrome_scene_color = built_svg.scene.monochrome_color();
let viewport = window.viewport(options.mode.view(0));
let dest_framebuffer = DestFramebuffer::Default {
@ -166,10 +161,11 @@ impl<W> DemoApp<W> where W: Window {
};
let renderer = Renderer::new(device, resources, dest_framebuffer);
let scene_thread_proxy = SceneThreadProxy::new(built_svg.scene, executor);
scene_thread_proxy.set_drawable_size(viewport.size());
let scene_metadata = SceneMetadata::new_clipping_view_box(&mut built_svg.scene,
viewport.size());
let camera = Camera::new(options.mode, scene_metadata.view_box, viewport.size());
let camera = Camera::new(options.mode, scene_view_box, viewport.size());
let scene_proxy = SceneProxy::new(built_svg.scene, executor);
let ground_program = GroundProgram::new(&renderer.device, resources);
let ground_solid_vertex_array = GroundSolidVertexArray::new(
@ -196,8 +192,9 @@ impl<W> DemoApp<W> where W: Window {
window_size,
scene_view_box,
monochrome_scene_color,
scene_metadata,
render_transform: None,
render_command_stream: None,
camera,
frame_counter: 0,
@ -209,9 +206,10 @@ impl<W> DemoApp<W> where W: Window {
last_mouse_position: Point2DI32::default(),
current_frame: None,
build_time: None,
ui,
scene_thread_proxy,
scene_proxy,
renderer,
scene_framebuffer: None,
@ -232,13 +230,10 @@ impl<W> DemoApp<W> where W: Window {
// Update the scene.
self.build_scene();
// Get the render message, and determine how many scenes it contains.
let transform = match self.scene_thread_proxy.receiver.recv().unwrap() {
SceneToMainMsg::BeginFrame { transform } => transform,
msg => panic!("Expected BeginFrame message, found {:?}!", msg),
};
// Save the frame.
//
// FIXME(pcwalton): This is super ugly.
let transform = self.render_transform.clone().unwrap();
self.current_frame = Some(Frame::new(transform, ui_events));
// Initialize and set the appropriate framebuffer.
@ -299,7 +294,7 @@ impl<W> DemoApp<W> where W: Window {
}
fn build_scene(&mut self) {
let render_transform = match self.camera {
self.render_transform = match self.camera {
Camera::ThreeD {
ref scene_transform,
ref mut modelview_transform,
@ -313,23 +308,25 @@ impl<W> DemoApp<W> where W: Window {
.perspective
.post_mul(&scene_transform.modelview_to_eye)
.post_mul(&modelview_transform.to_transform());
RenderTransform::Perspective(perspective)
Some(RenderTransform::Perspective(perspective))
}
Camera::TwoD(transform) => RenderTransform::Transform2D(transform),
Camera::TwoD(transform) => Some(RenderTransform::Transform2D(transform)),
};
self.scene_thread_proxy
.sender
.send(MainToSceneMsg::Build(BuildOptions {
render_transform,
stem_darkening_font_size: if self.ui.stem_darkening_effect_enabled {
Some(APPROX_FONT_SIZE * self.window_size.backing_scale_factor)
} else {
None
},
subpixel_aa_enabled: self.ui.subpixel_aa_effect_enabled,
}))
.unwrap();
let render_options = RenderOptions {
transform: self.render_transform.clone().unwrap(),
dilation: if self.ui.stem_darkening_effect_enabled {
let font_size = APPROX_FONT_SIZE * self.window_size.backing_scale_factor;
let (x, y) = (STEM_DARKENING_FACTORS[0], STEM_DARKENING_FACTORS[1]);
Point2DF32::new(x, y).scale(font_size)
} else {
Point2DF32::default()
},
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(built_options));
}
fn handle_events(&mut self, events: Vec<Event>) -> Vec<UIEvent> {
@ -345,7 +342,8 @@ impl<W> DemoApp<W> where W: Window {
Event::WindowResized(new_size) => {
self.window_size = new_size;
let viewport = self.window.viewport(self.ui.mode.view(0));
self.scene_thread_proxy.set_drawable_size(viewport.size());
self.scene_proxy.set_view_box(RectF32::new(Point2DF32::default(),
viewport.size().to_f32()));
self.renderer
.set_main_framebuffer_size(self.window_size.device_size());
self.dirty = true;
@ -409,7 +407,7 @@ impl<W> DemoApp<W> where W: Window {
ref mut velocity, ..
} = self.camera
{
let scale_factor = scale_factor_for_view_box(self.scene_view_box);
let scale_factor = scale_factor_for_view_box(self.scene_metadata.view_box);
velocity.set_z(-CAMERA_VELOCITY / scale_factor);
self.dirty = true;
}
@ -419,7 +417,7 @@ impl<W> DemoApp<W> where W: Window {
ref mut velocity, ..
} = self.camera
{
let scale_factor = scale_factor_for_view_box(self.scene_view_box);
let scale_factor = scale_factor_for_view_box(self.scene_metadata.view_box);
velocity.set_z(CAMERA_VELOCITY / scale_factor);
self.dirty = true;
}
@ -429,7 +427,7 @@ impl<W> DemoApp<W> where W: Window {
ref mut velocity, ..
} = self.camera
{
let scale_factor = scale_factor_for_view_box(self.scene_view_box);
let scale_factor = scale_factor_for_view_box(self.scene_metadata.view_box);
velocity.set_x(-CAMERA_VELOCITY / scale_factor);
self.dirty = true;
}
@ -439,7 +437,7 @@ impl<W> DemoApp<W> where W: Window {
ref mut velocity, ..
} = self.camera
{
let scale_factor = scale_factor_for_view_box(self.scene_view_box);
let scale_factor = scale_factor_for_view_box(self.scene_metadata.view_box);
velocity.set_x(CAMERA_VELOCITY / scale_factor);
self.dirty = true;
}
@ -471,18 +469,23 @@ impl<W> DemoApp<W> where W: Window {
UIVisibility::All => UIVisibility::None,
}
}
Event::OpenSVG(ref svg_path) => {
let built_svg = load_scene(self.window.resource_loader(), svg_path);
let mut built_svg = load_scene(self.window.resource_loader(), svg_path);
self.ui.message = get_svg_building_message(&built_svg);
let viewport_size = self.window.viewport(self.ui.mode.view(0)).size();
self.scene_view_box = built_svg.scene.view_box;
self.monochrome_scene_color = built_svg.scene.monochrome_color();
self.camera = Camera::new(self.ui.mode, self.scene_view_box, viewport_size);
self.scene_thread_proxy
.load_scene(built_svg.scene, viewport_size);
self.scene_metadata =
SceneMetadata::new_clipping_view_box(&mut built_svg.scene, viewport_size);
self.camera = Camera::new(self.ui.mode,
self.scene_metadata.view_box,
viewport_size);
self.scene_proxy.replace_scene(built_svg.scene);
self.dirty = true;
}
Event::User {
message_type: event_id,
message_data: expected_epoch,
@ -574,8 +577,8 @@ impl<W> DemoApp<W> where W: Window {
let scene_texture = self.renderer.device.framebuffer_texture(scene_framebuffer);
let quad_scale_transform = Transform3DF32::from_scale(
self.scene_view_box.size().x(),
self.scene_view_box.size().y(),
self.scene_metadata.view_box.size().x(),
self.scene_metadata.view_box.size().y(),
1.0,
);
@ -618,11 +621,7 @@ impl<W> DemoApp<W> where W: Window {
.push(rendering_time);
}
let tile_time = match self.scene_thread_proxy.receiver.recv().unwrap() {
SceneToMainMsg::EndFrame { tile_time } => tile_time,
_ => panic!("Expected `EndFrame`!"),
};
let build_time = self.build_time.unwrap();
let mut frame = self.current_frame.take().unwrap();
if self.pending_screenshot_path.is_some() {
@ -645,7 +644,7 @@ impl<W> DemoApp<W> where W: Window {
};
self.renderer
.debug_ui
.add_sample(aggregate_stats, tile_time, total_rendering_time);
.add_sample(aggregate_stats, build_time, total_rendering_time);
}
if self.options.ui != UIVisibility::None {
@ -668,7 +667,7 @@ impl<W> DemoApp<W> where W: Window {
.last_mouse_position
.to_f32()
.scale(self.window_size.backing_scale_factor);
self.ui.show_text_effects = self.monochrome_scene_color.is_some();
self.ui.show_text_effects = self.scene_metadata.monochrome_color.is_some();
let mut ui_action = UIAction::None;
if self.options.ui == UIVisibility::All {
@ -687,7 +686,7 @@ impl<W> DemoApp<W> where W: Window {
// FIXME(pcwalton): This should really be an MVC setup.
if self.camera.mode() != self.ui.mode {
let viewport_size = self.window.viewport(self.ui.mode.view(0)).size();
self.camera = Camera::new(self.ui.mode, self.scene_view_box, viewport_size);
self.camera = Camera::new(self.ui.mode, self.scene_metadata.view_box, viewport_size);
}
for ui_event in frame.ui_events {
@ -723,12 +722,12 @@ impl<W> DemoApp<W> where W: Window {
return;
}
let ground_scale = self.scene_view_box.max_x() * 2.0;
let ground_scale = self.scene_metadata.view_box.max_x() * 2.0;
let mut base_transform = perspective.transform;
base_transform = base_transform.post_mul(&Transform3DF32::from_translation(
-0.5 * self.scene_view_box.max_x(),
self.scene_view_box.max_y(),
-0.5 * self.scene_metadata.view_box.max_x(),
self.scene_metadata.view_box.max_y(),
-0.5 * ground_scale,
));
@ -803,12 +802,7 @@ impl<W> DemoApp<W> where W: Window {
}
fn render_vector_scene(&mut self) {
let built_scene = match self.scene_thread_proxy.receiver.recv().unwrap() {
SceneToMainMsg::BeginRenderScene(built_scene) => built_scene,
_ => panic!("Expected `BeginRenderScene`!"),
};
match self.monochrome_scene_color {
match self.scene_metadata.monochrome_color {
None => self.renderer.set_render_mode(RenderMode::Multicolor),
Some(fg_color) => {
self.renderer.set_render_mode(RenderMode::Monochrome {
@ -831,13 +825,14 @@ impl<W> DemoApp<W> where W: Window {
self.renderer.enable_depth();
}
self.renderer.begin_scene(&built_scene);
self.renderer.begin_scene();
loop {
match self.scene_thread_proxy.receiver.recv().unwrap() {
SceneToMainMsg::Execute(command) => self.renderer.render_command(&command),
SceneToMainMsg::EndRenderScene => break,
_ => panic!("Expected `Execute` or `EndRenderScene`!"),
// Issue render commands!
for command in self.render_command_stream.as_mut().unwrap() {
self.renderer.render_command(&command);
if let RenderCommand::Finish { build_time } = command {
self.build_time = Some(build_time);
}
}
@ -918,166 +913,6 @@ impl<W> DemoApp<W> where W: Window {
}
}
struct SceneThreadProxy {
sender: Sender<MainToSceneMsg>,
receiver: Receiver<SceneToMainMsg>,
}
impl SceneThreadProxy {
fn new(scene: Scene, executor: DemoExecutor) -> SceneThreadProxy {
let (main_to_scene_sender, main_to_scene_receiver) = mpsc::channel();
let (scene_to_main_sender, scene_to_main_receiver) =
mpsc::sync_channel(MAX_MESSAGES_IN_FLIGHT);
SceneThread::new(executor, scene, scene_to_main_sender, main_to_scene_receiver);
SceneThreadProxy {
sender: main_to_scene_sender,
receiver: scene_to_main_receiver,
}
}
fn load_scene(&self, scene: Scene, view_box_size: Point2DI32) {
self.sender
.send(MainToSceneMsg::LoadScene {
scene,
view_box_size,
})
.unwrap();
}
fn set_drawable_size(&self, drawable_size: Point2DI32) {
self.sender
.send(MainToSceneMsg::SetDrawableSize(drawable_size))
.unwrap();
}
}
struct SceneThread {
executor: DemoExecutor,
scene: Scene,
sender: SyncSender<SceneToMainMsg>,
receiver: Receiver<MainToSceneMsg>,
}
impl SceneThread {
fn new(
executor: DemoExecutor,
scene: Scene,
sender: SyncSender<SceneToMainMsg>,
receiver: Receiver<MainToSceneMsg>,
) {
thread::spawn(move || (SceneThread { executor, scene, sender, receiver }).run());
}
fn run(mut self) {
while let Ok(msg) = self.receiver.recv() {
match msg {
MainToSceneMsg::LoadScene {
scene,
view_box_size,
} => {
self.scene = scene;
self.scene.view_box =
RectF32::new(Point2DF32::default(), view_box_size.to_f32());
}
MainToSceneMsg::SetDrawableSize(size) => {
self.scene.view_box = RectF32::new(Point2DF32::default(), size.to_f32());
}
MainToSceneMsg::Build(build_options) => {
self.sender
.send(SceneToMainMsg::BeginFrame {
transform: build_options.render_transform.clone(),
})
.unwrap();
let start_time = Instant::now();
self.build_scene(build_options);
let tile_time = Instant::now() - start_time;
self.sender.send(SceneToMainMsg::EndFrame { tile_time }).unwrap();
}
}
}
}
fn build_scene(&mut self, build_options: BuildOptions) {
let render_options = RenderOptions {
transform: build_options.render_transform.clone(),
dilation: match build_options.stem_darkening_font_size {
None => Point2DF32::default(),
Some(font_size) => {
let (x, y) = (STEM_DARKENING_FACTORS[0], STEM_DARKENING_FACTORS[1]);
Point2DF32::new(x, y).scale(font_size)
}
},
subpixel_aa_enabled: build_options.subpixel_aa_enabled,
};
let built_options = render_options.prepare(self.scene.bounds);
self.sender.send(SceneToMainMsg::BeginRenderScene(
self.scene.build_descriptor(&built_options),
))
.unwrap();
let inner_sink = AssertUnwindSafe(self.sender.clone());
let inner_scene = AssertUnwindSafe(&self.scene);
let inner_executor = AssertUnwindSafe(&self.executor);
let result = panic::catch_unwind(move || {
let sink = (*inner_sink).clone();
let listener =
Box::new(move |command| sink.send(SceneToMainMsg::Execute(command)).unwrap());
SceneBuilder::new(*inner_scene, &built_options, listener).build(*inner_executor)
});
if result.is_err() {
error!("Scene building crashed! Dumping scene:");
println!("{:?}", self.scene);
process::exit(1);
}
self.sender.send(SceneToMainMsg::EndRenderScene).unwrap();
}
}
#[derive(Clone)]
enum MainToSceneMsg {
LoadScene {
scene: Scene,
view_box_size: Point2DI32,
},
SetDrawableSize(Point2DI32),
Build(BuildOptions),
}
#[derive(Clone)]
struct BuildOptions {
render_transform: RenderTransform,
stem_darkening_font_size: Option<f32>,
subpixel_aa_enabled: bool,
}
enum SceneToMainMsg {
BeginFrame { transform: RenderTransform },
EndFrame { tile_time: Duration },
BeginRenderScene(SceneDescriptor),
Execute(RenderCommand),
EndRenderScene,
}
impl Debug for SceneToMainMsg {
fn fmt(&self, formatter: &mut Formatter) -> DebugResult {
let ident = match *self {
SceneToMainMsg::BeginFrame { .. } => "BeginFrame",
SceneToMainMsg::EndFrame { .. } => "EndFrame",
SceneToMainMsg::BeginRenderScene(..) => "BeginRenderScene",
SceneToMainMsg::Execute(..) => "Execute",
SceneToMainMsg::EndRenderScene => "EndRenderScene",
};
ident.fmt(formatter)
}
}
#[derive(Clone)]
pub struct Options {
pub jobs: Option<usize>,
@ -1439,3 +1274,20 @@ impl BackgroundColor {
}
}
}
struct SceneMetadata {
view_box: RectF32,
bounds: RectF32,
monochrome_color: Option<ColorU>,
}
impl SceneMetadata {
// FIXME(pcwalton): The fact that this mutates the scene is really ugly!
// Can we simplify this?
fn new_clipping_view_box(scene: &mut Scene, viewport_size: Point2DI32) -> SceneMetadata {
let view_box = scene.view_box();
let monochrome_color = scene.monochrome_color();
scene.set_view_box(RectF32::new(Point2DF32::default(), viewport_size.to_f32()));
SceneMetadata { view_box, monochrome_color, bounds: scene.bounds() }
}
}

View File

@ -12,21 +12,15 @@
use crate::concurrent::executor::Executor;
use crate::gpu_data::{AlphaTileBatchPrimitive, RenderCommand};
use crate::options::{PreparedRenderOptions, RenderCommandListener};
use crate::scene::Scene;
use crate::tiles::Tiler;
use crate::z_buffer::ZBuffer;
use pathfinder_geometry::basic::point::{Point2DF32, Point3DF32};
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::basic::transform3d::Perspective;
use pathfinder_geometry::clip::PolygonClipper3D;
use std::sync::atomic::AtomicUsize;
use std::time::Instant;
use std::u16;
pub trait RenderCommandListener: Send + Sync {
fn send(&self, command: RenderCommand);
}
pub struct SceneBuilder<'a> {
scene: &'a Scene,
built_options: &'a PreparedRenderOptions,
@ -54,12 +48,23 @@ impl<'a> SceneBuilder<'a> {
}
pub fn build<E>(&mut self, executor: &E) where E: Executor {
let effective_view_box = self.scene.effective_view_box(self.built_options);
let start_time = Instant::now();
let bounding_quad = self.built_options.bounding_quad();
let object_count = self.scene.objects.len();
self.listener.send(RenderCommand::Start { bounding_quad, object_count });
self.listener.send(RenderCommand::AddShaders(self.scene.build_shaders()));
let effective_view_box = self.scene.effective_view_box(self.built_options);
let alpha_tiles = executor.flatten_into_vector(object_count, |object_index| {
self.build_object(object_index, effective_view_box, &self.built_options, &self.scene)
});
self.finish_building(alpha_tiles)
self.finish_building(alpha_tiles);
let build_time = Instant::now() - start_time;
self.listener.send(RenderCommand::Finish { build_time });
}
fn build_object(
@ -75,8 +80,7 @@ impl<'a> SceneBuilder<'a> {
let mut tiler = Tiler::new(self, &outline, view_box, object_index as u16);
tiler.generate_tiles();
self.listener
.send(RenderCommand::AddFills(tiler.built_object.fills));
self.listener.send(RenderCommand::AddFills(tiler.built_object.fills));
tiler.built_object.alpha_tiles
}
@ -115,140 +119,6 @@ impl<'a> SceneBuilder<'a> {
}
}
#[derive(Clone, Default)]
pub struct RenderOptions {
pub transform: RenderTransform,
pub dilation: Point2DF32,
pub subpixel_aa_enabled: bool,
}
impl RenderOptions {
pub fn prepare(self, bounds: RectF32) -> PreparedRenderOptions {
PreparedRenderOptions {
transform: self.transform.prepare(bounds),
dilation: self.dilation,
subpixel_aa_enabled: self.subpixel_aa_enabled,
}
}
}
#[derive(Clone)]
pub enum RenderTransform {
Transform2D(Transform2DF32),
Perspective(Perspective),
}
impl Default for RenderTransform {
#[inline]
fn default() -> RenderTransform {
RenderTransform::Transform2D(Transform2DF32::default())
}
}
impl RenderTransform {
fn prepare(&self, bounds: RectF32) -> PreparedRenderTransform {
let perspective = match self {
RenderTransform::Transform2D(ref transform) => {
if transform.is_identity() {
return PreparedRenderTransform::None;
}
return PreparedRenderTransform::Transform2D(*transform);
}
RenderTransform::Perspective(ref perspective) => *perspective,
};
let mut points = vec![
bounds.origin().to_3d(),
bounds.upper_right().to_3d(),
bounds.lower_right().to_3d(),
bounds.lower_left().to_3d(),
];
debug!("-----");
debug!("bounds={:?} ORIGINAL quad={:?}", bounds, points);
for point in &mut points {
*point = perspective.transform.transform_point(*point);
}
debug!("... PERSPECTIVE quad={:?}", points);
// Compute depth.
let quad = [
points[0].perspective_divide(),
points[1].perspective_divide(),
points[2].perspective_divide(),
points[3].perspective_divide(),
];
debug!("... PERSPECTIVE-DIVIDED points = {:?}", quad);
points = PolygonClipper3D::new(points).clip();
debug!("... CLIPPED quad={:?}", points);
for point in &mut points {
*point = point.perspective_divide()
}
let inverse_transform = perspective.transform.inverse();
let clip_polygon = points
.into_iter()
.map(|point| {
inverse_transform
.transform_point(point)
.perspective_divide()
.to_2d()
})
.collect();
return PreparedRenderTransform::Perspective {
perspective,
clip_polygon,
quad,
};
}
}
pub struct PreparedRenderOptions {
pub transform: PreparedRenderTransform,
pub dilation: Point2DF32,
pub subpixel_aa_enabled: bool,
}
impl PreparedRenderOptions {
#[inline]
pub fn bounding_quad(&self) -> [Point3DF32; 4] {
match self.transform {
PreparedRenderTransform::Perspective { quad, .. } => quad,
_ => [Point3DF32::default(); 4],
}
}
}
pub enum PreparedRenderTransform {
None,
Transform2D(Transform2DF32),
Perspective {
perspective: Perspective,
clip_polygon: Vec<Point2DF32>,
quad: [Point3DF32; 4],
},
}
impl PreparedRenderTransform {
#[inline]
pub fn is_2d(&self) -> bool {
match *self {
PreparedRenderTransform::Transform2D(_) => true,
_ => false,
}
}
}
impl<F> RenderCommandListener for F
where
F: Fn(RenderCommand) + Send + Sync,
{
#[inline]
fn send(&self, command: RenderCommand) {
(*self)(command)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct TileStats {
pub solid_tile_count: u32,

View File

@ -12,3 +12,4 @@
pub mod executor;
pub mod rayon;
pub mod scene_proxy;

View File

@ -0,0 +1,116 @@
// pathfinder/renderer/src/concurrent/scene_proxy.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 version of `Scene` that proxies all method calls out to a separate
//! thread.
//!
//! This is useful for:
//!
//! * Avoiding GPU driver stalls on synchronous APIs such as OpenGL.
//!
//! * Avoiding UI latency by building scenes off the main thread.
//!
//! You don't need to use this API to use Pathfinder; it's only a convenience.
use crate::concurrent::executor::Executor;
use crate::gpu_data::RenderCommand;
use crate::options::{PreparedRenderOptions, RenderCommandListener};
use crate::scene::Scene;
use pathfinder_geometry::basic::rect::RectF32;
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread;
const MAX_MESSAGES_IN_FLIGHT: usize = 1024;
pub struct SceneProxy {
sender: Sender<MainToWorkerMsg>,
}
impl SceneProxy {
pub fn new<E>(scene: Scene, executor: E) -> SceneProxy where E: Executor + Send + 'static {
let (main_to_worker_sender, main_to_worker_receiver) = mpsc::channel();
thread::spawn(move || scene_thread(scene, executor, main_to_worker_receiver));
SceneProxy { sender: main_to_worker_sender }
}
#[inline]
pub fn replace_scene(&self, new_scene: Scene) {
self.sender.send(MainToWorkerMsg::ReplaceScene(new_scene)).unwrap();
}
#[inline]
pub fn set_view_box(&self, new_view_box: RectF32) {
self.sender.send(MainToWorkerMsg::SetViewBox(new_view_box)).unwrap();
}
#[inline]
pub fn build_with_listener(&self,
built_options: PreparedRenderOptions,
listener: Box<dyn RenderCommandListener>) {
self.sender.send(MainToWorkerMsg::Build(built_options, listener)).unwrap();
}
#[inline]
pub fn build_with_stream(&self, built_options: PreparedRenderOptions) -> RenderCommandStream {
let (sender, receiver) = mpsc::sync_channel(MAX_MESSAGES_IN_FLIGHT);
let listener = Box::new(move |command| sender.send(command).unwrap());
self.build_with_listener(built_options, listener);
RenderCommandStream::new(receiver)
}
}
fn scene_thread<E>(mut scene: Scene,
executor: E,
main_to_worker_receiver: Receiver<MainToWorkerMsg>)
where E: Executor {
while let Ok(msg) = main_to_worker_receiver.recv() {
match msg {
MainToWorkerMsg::ReplaceScene(new_scene) => scene = new_scene,
MainToWorkerMsg::SetViewBox(new_view_box) => scene.set_view_box(new_view_box),
MainToWorkerMsg::Build(options, listener) => {
scene.build(&options, listener, &executor);
}
}
}
}
enum MainToWorkerMsg {
ReplaceScene(Scene),
SetViewBox(RectF32),
Build(PreparedRenderOptions, Box<dyn RenderCommandListener>),
}
pub struct RenderCommandStream {
receiver: Receiver<RenderCommand>,
done: bool,
}
impl RenderCommandStream {
fn new(receiver: Receiver<RenderCommand>) -> RenderCommandStream {
RenderCommandStream { receiver, done: false }
}
}
impl Iterator for RenderCommandStream {
type Item = RenderCommand;
#[inline]
fn next(&mut self) -> Option<RenderCommand> {
if self.done {
None
} else {
let command = self.receiver.recv().unwrap();
if let RenderCommand::Finish { .. } = command {
self.done = true;
}
Some(command)
}
}
}

View File

@ -12,7 +12,7 @@ use crate::gpu::debug::DebugUI;
use crate::gpu_data::{AlphaTileBatchPrimitive, FillBatchPrimitive};
use crate::gpu_data::{RenderCommand, SolidTileBatchPrimitive};
use crate::post::DefringingKernel;
use crate::scene::{ObjectShader, SceneDescriptor};
use crate::scene::ObjectShader;
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
use pathfinder_geometry::basic::point::{Point2DI32, Point3DF32};
use pathfinder_geometry::basic::rect::RectI32;
@ -221,7 +221,7 @@ where
}
}
pub fn begin_scene(&mut self, scene: &SceneDescriptor) {
pub fn begin_scene(&mut self) {
self.init_postprocessing_framebuffer();
let timer_query = self
@ -231,22 +231,19 @@ where
self.device.begin_timer_query(&timer_query);
self.current_timer_query = Some(timer_query);
self.upload_shaders(&scene.shaders);
if self.use_depth {
self.draw_stencil(&scene.bounding_quad);
}
self.mask_framebuffer_cleared = false;
self.stats = RenderStats {
object_count: scene.object_count,
..RenderStats::default()
};
self.stats = RenderStats::default();
}
pub fn render_command(&mut self, command: &RenderCommand) {
match *command {
RenderCommand::Start { bounding_quad, object_count } => {
if self.use_depth {
self.draw_stencil(&bounding_quad);
}
self.stats.object_count = object_count;
}
RenderCommand::AddShaders(ref shaders) => self.upload_shaders(shaders),
RenderCommand::AddFills(ref fills) => self.add_fills(fills),
RenderCommand::FlushFills => self.draw_buffered_fills(),
RenderCommand::SolidTile(ref solid_tiles) => {
@ -261,6 +258,7 @@ where
self.upload_alpha_tiles(alpha_tiles);
self.draw_alpha_tiles(count as u32);
}
RenderCommand::Finish { .. } => {}
}
}

View File

@ -11,6 +11,8 @@
//! Packed data ready to be sent to the GPU.
use crate::builder::SceneBuilder;
use crate::options::BoundingQuad;
use crate::scene::ObjectShader;
use crate::tile_map::DenseTileMap;
use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH};
use pathfinder_geometry::basic::line_segment::{LineSegmentF32, LineSegmentU4, LineSegmentU8};
@ -20,6 +22,7 @@ use pathfinder_geometry::util;
use pathfinder_simd::default::{F32x4, I32x4};
use std::fmt::{Debug, Formatter, Result as DebugResult};
use std::sync::atomic::Ordering;
use std::time::Duration;
#[derive(Debug)]
pub(crate) struct BuiltObject {
@ -30,10 +33,13 @@ pub(crate) struct BuiltObject {
}
pub enum RenderCommand {
Start { object_count: usize, bounding_quad: BoundingQuad },
AddShaders(Vec<ObjectShader>),
AddFills(Vec<FillBatchPrimitive>),
FlushFills,
AlphaTile(Vec<AlphaTileBatchPrimitive>),
SolidTile(Vec<SolidTileBatchPrimitive>),
Finish { build_time: Duration },
}
#[derive(Clone, Copy, Debug)]
@ -319,10 +325,15 @@ impl AlphaTileBatchPrimitive {
impl Debug for RenderCommand {
fn fmt(&self, formatter: &mut Formatter) -> DebugResult {
match *self {
RenderCommand::Start { .. } => write!(formatter, "Start"),
RenderCommand::AddShaders(ref shaders) => {
write!(formatter, "AddShaders(x{})", shaders.len())
}
RenderCommand::AddFills(ref fills) => write!(formatter, "AddFills(x{})", fills.len()),
RenderCommand::FlushFills => write!(formatter, "FlushFills"),
RenderCommand::AlphaTile(ref tiles) => write!(formatter, "AlphaTile(x{})", tiles.len()),
RenderCommand::SolidTile(ref tiles) => write!(formatter, "SolidTile(x{})", tiles.len()),
RenderCommand::Finish { .. } => write!(formatter, "Finish"),
}
}
}

View File

@ -13,14 +13,15 @@
#[macro_use]
extern crate log;
pub mod builder;
pub mod concurrent;
pub mod gpu;
pub mod gpu_data;
pub mod options;
pub mod post;
pub mod scene;
pub mod tiles;
mod builder;
mod sorted_vector;
mod tile_map;
mod tiles;
mod z_buffer;

158
renderer/src/options.rs Normal file
View File

@ -0,0 +1,158 @@
// pathfinder/renderer/src/options.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.
//! Options that control how rendering is to be performed.
use crate::gpu_data::RenderCommand;
use pathfinder_geometry::basic::point::{Point2DF32, Point3DF32};
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::basic::transform3d::Perspective;
use pathfinder_geometry::clip::PolygonClipper3D;
pub trait RenderCommandListener: Send + Sync {
fn send(&self, command: RenderCommand);
}
impl<F> RenderCommandListener for F
where
F: Fn(RenderCommand) + Send + Sync,
{
#[inline]
fn send(&self, command: RenderCommand) {
(*self)(command)
}
}
#[derive(Clone, Default)]
pub struct RenderOptions {
pub transform: RenderTransform,
pub dilation: Point2DF32,
pub subpixel_aa_enabled: bool,
}
impl RenderOptions {
pub fn prepare(self, bounds: RectF32) -> PreparedRenderOptions {
PreparedRenderOptions {
transform: self.transform.prepare(bounds),
dilation: self.dilation,
subpixel_aa_enabled: self.subpixel_aa_enabled,
}
}
}
#[derive(Clone)]
pub enum RenderTransform {
Transform2D(Transform2DF32),
Perspective(Perspective),
}
impl Default for RenderTransform {
#[inline]
fn default() -> RenderTransform {
RenderTransform::Transform2D(Transform2DF32::default())
}
}
impl RenderTransform {
fn prepare(&self, bounds: RectF32) -> PreparedRenderTransform {
let perspective = match self {
RenderTransform::Transform2D(ref transform) => {
if transform.is_identity() {
return PreparedRenderTransform::None;
}
return PreparedRenderTransform::Transform2D(*transform);
}
RenderTransform::Perspective(ref perspective) => *perspective,
};
let mut points = vec![
bounds.origin().to_3d(),
bounds.upper_right().to_3d(),
bounds.lower_right().to_3d(),
bounds.lower_left().to_3d(),
];
debug!("-----");
debug!("bounds={:?} ORIGINAL quad={:?}", bounds, points);
for point in &mut points {
*point = perspective.transform.transform_point(*point);
}
debug!("... PERSPECTIVE quad={:?}", points);
// Compute depth.
let quad = [
points[0].perspective_divide(),
points[1].perspective_divide(),
points[2].perspective_divide(),
points[3].perspective_divide(),
];
debug!("... PERSPECTIVE-DIVIDED points = {:?}", quad);
points = PolygonClipper3D::new(points).clip();
debug!("... CLIPPED quad={:?}", points);
for point in &mut points {
*point = point.perspective_divide()
}
let inverse_transform = perspective.transform.inverse();
let clip_polygon = points
.into_iter()
.map(|point| {
inverse_transform
.transform_point(point)
.perspective_divide()
.to_2d()
})
.collect();
return PreparedRenderTransform::Perspective {
perspective,
clip_polygon,
quad,
};
}
}
pub struct PreparedRenderOptions {
pub transform: PreparedRenderTransform,
pub dilation: Point2DF32,
pub subpixel_aa_enabled: bool,
}
impl PreparedRenderOptions {
#[inline]
pub fn bounding_quad(&self) -> BoundingQuad {
match self.transform {
PreparedRenderTransform::Perspective { quad, .. } => quad,
_ => [Point3DF32::default(); 4],
}
}
}
pub type BoundingQuad = [Point3DF32; 4];
pub enum PreparedRenderTransform {
None,
Transform2D(Transform2DF32),
Perspective {
perspective: Perspective,
clip_polygon: Vec<Point2DF32>,
quad: [Point3DF32; 4],
},
}
impl PreparedRenderTransform {
#[inline]
pub fn is_2d(&self) -> bool {
match *self {
PreparedRenderTransform::Transform2D(_) => true,
_ => false,
}
}
}

View File

@ -10,9 +10,11 @@
//! A set of paths to be rendered.
use crate::builder::{PreparedRenderOptions, PreparedRenderTransform};
use crate::builder::SceneBuilder;
use crate::concurrent::executor::Executor;
use crate::options::{PreparedRenderOptions, PreparedRenderTransform, RenderCommandListener};
use hashbrown::HashMap;
use pathfinder_geometry::basic::point::{Point2DF32, Point3DF32};
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::color::ColorU;
@ -21,11 +23,11 @@ use std::fmt::{self, Debug, Formatter};
#[derive(Clone)]
pub struct Scene {
pub objects: Vec<PathObject>,
pub paints: Vec<Paint>,
pub paint_cache: HashMap<Paint, PaintId>,
pub bounds: RectF32,
pub view_box: RectF32,
pub(crate) objects: Vec<PathObject>,
paints: Vec<Paint>,
paint_cache: HashMap<Paint, PaintId>,
bounds: RectF32,
view_box: RectF32,
}
impl Scene {
@ -40,6 +42,10 @@ impl Scene {
}
}
pub fn push_object(&mut self, object: PathObject) {
self.objects.push(object);
}
#[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) {
@ -52,15 +58,32 @@ impl Scene {
paint_id
}
pub fn build_descriptor(&self, built_options: &PreparedRenderOptions) -> SceneDescriptor {
SceneDescriptor {
shaders: self.build_shaders(),
bounding_quad: built_options.bounding_quad(),
object_count: self.objects.len(),
}
#[inline]
pub fn object_count(&self) -> usize {
self.objects.len()
}
fn build_shaders(&self) -> Vec<ObjectShader> {
#[inline]
pub fn bounds(&self) -> RectF32 {
self.bounds
}
#[inline]
pub fn set_bounds(&mut self, new_bounds: RectF32) {
self.bounds = new_bounds;
}
#[inline]
pub fn view_box(&self) -> RectF32 {
self.view_box
}
#[inline]
pub fn set_view_box(&mut self, new_view_box: RectF32) {
self.view_box = new_view_box;
}
pub fn build_shaders(&self) -> Vec<ObjectShader> {
self.objects
.iter()
.map(|object| {
@ -149,6 +172,15 @@ impl Scene {
self.view_box
}
}
#[inline]
pub fn build<E>(&self,
built_options: &PreparedRenderOptions,
listener: Box<dyn RenderCommandListener>,
executor: &E)
where E: Executor {
SceneBuilder::new(self, built_options, listener).build(executor)
}
}
impl Debug for Scene {
@ -178,13 +210,6 @@ impl Debug for Scene {
}
}
#[derive(Clone, Debug)]
pub struct SceneDescriptor {
pub shaders: Vec<ObjectShader>,
pub bounding_quad: [Point3DF32; 4],
pub object_count: usize,
}
#[derive(Clone, Debug)]
pub struct PathObject {
outline: Outline,
@ -201,7 +226,8 @@ pub enum PathObjectKind {
impl PathObject {
#[inline]
pub fn new(outline: Outline, paint: PaintId, name: String, kind: PathObjectKind) -> PathObject {
pub fn new(outline: Outline, paint: PaintId, name: String, kind: PathObjectKind)
-> PathObject {
PathObject {
outline,
paint,

View File

@ -70,7 +70,7 @@ impl BuiltSVG {
let root = &tree.root();
match *root.borrow() {
NodeKind::Svg(ref svg) => {
built_svg.scene.view_box = usvg_rect_to_euclid_rect(&svg.view_box.rect);
built_svg.scene.set_view_box(usvg_rect_to_euclid_rect(&svg.view_box.rect));
for kid in root.children() {
built_svg.process_node(&kid, &global_transform);
}
@ -122,8 +122,8 @@ impl BuiltSVG {
let path = Transform2DF32PathIter::new(path, &transform);
let outline = Outline::from_segments(path);
self.scene.bounds = self.scene.bounds.union_rect(outline.bounds());
self.scene.objects.push(PathObject::new(
self.scene.set_bounds(self.scene.bounds().union_rect(outline.bounds()));
self.scene.push_object(PathObject::new(
outline,
style,
node.id().to_string(),
@ -146,8 +146,8 @@ impl BuiltSVG {
let mut outline = stroke_to_fill.outline;
outline.transform(&transform);
self.scene.bounds = self.scene.bounds.union_rect(outline.bounds());
self.scene.objects.push(PathObject::new(
self.scene.set_bounds(self.scene.bounds().union_rect(outline.bounds()));
self.scene.push_object(PathObject::new(
outline,
style,
node.id().to_string(),