diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index 577f0886..b24fa4e0 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -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 where W: Window { window_size: WindowSize, - scene_view_box: RectF32, - monochrome_scene_color: Option, + scene_metadata: SceneMetadata, + render_transform: Option, + render_command_stream: Option, camera: Camera, frame_counter: u32, @@ -129,9 +125,10 @@ pub struct DemoApp where W: Window { last_mouse_position: Point2DI32, current_frame: Option, + build_time: Option, ui: DemoUI, - scene_thread_proxy: SceneThreadProxy, + scene_proxy: SceneProxy, renderer: Renderer, scene_framebuffer: Option<::Framebuffer>, @@ -154,10 +151,8 @@ impl DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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) -> Vec { @@ -345,7 +342,8 @@ impl DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp 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 DemoApp where W: Window { } } -struct SceneThreadProxy { - sender: Sender, - receiver: Receiver, -} - -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, - receiver: Receiver, -} - -impl SceneThread { - fn new( - executor: DemoExecutor, - scene: Scene, - sender: SyncSender, - receiver: Receiver, - ) { - 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, - 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, @@ -1439,3 +1274,20 @@ impl BackgroundColor { } } } + +struct SceneMetadata { + view_box: RectF32, + bounds: RectF32, + monochrome_color: Option, +} + +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() } + } +} diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index d4c8bcb9..7ab83925 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -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(&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, - quad: [Point3DF32; 4], - }, -} - -impl PreparedRenderTransform { - #[inline] - pub fn is_2d(&self) -> bool { - match *self { - PreparedRenderTransform::Transform2D(_) => true, - _ => false, - } - } -} - -impl 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, diff --git a/renderer/src/concurrent/mod.rs b/renderer/src/concurrent/mod.rs index fd06407b..01ceffe3 100644 --- a/renderer/src/concurrent/mod.rs +++ b/renderer/src/concurrent/mod.rs @@ -12,3 +12,4 @@ pub mod executor; pub mod rayon; +pub mod scene_proxy; diff --git a/renderer/src/concurrent/scene_proxy.rs b/renderer/src/concurrent/scene_proxy.rs new file mode 100644 index 00000000..f59b3f7c --- /dev/null +++ b/renderer/src/concurrent/scene_proxy.rs @@ -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 or the MIT license +// , 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, +} + +impl SceneProxy { + pub fn new(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) { + 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(mut scene: Scene, + executor: E, + main_to_worker_receiver: Receiver) + 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), +} + +pub struct RenderCommandStream { + receiver: Receiver, + done: bool, +} + +impl RenderCommandStream { + fn new(receiver: Receiver) -> RenderCommandStream { + RenderCommandStream { receiver, done: false } + } +} + +impl Iterator for RenderCommandStream { + type Item = RenderCommand; + + #[inline] + fn next(&mut self) -> Option { + if self.done { + None + } else { + let command = self.receiver.recv().unwrap(); + if let RenderCommand::Finish { .. } = command { + self.done = true; + } + Some(command) + } + } +} diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index c36fb276..2a60e1f6 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -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 { .. } => {} } } diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index 465f58a8..5dd717f7 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -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), AddFills(Vec), FlushFills, AlphaTile(Vec), SolidTile(Vec), + 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"), } } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 2a834fa2..803e5e13 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -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; diff --git a/renderer/src/options.rs b/renderer/src/options.rs new file mode 100644 index 00000000..21e992df --- /dev/null +++ b/renderer/src/options.rs @@ -0,0 +1,158 @@ +// pathfinder/renderer/src/options.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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 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, + quad: [Point3DF32; 4], + }, +} + +impl PreparedRenderTransform { + #[inline] + pub fn is_2d(&self) -> bool { + match *self { + PreparedRenderTransform::Transform2D(_) => true, + _ => false, + } + } +} diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index a018dce3..a1aa8770 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -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, - pub paints: Vec, - pub paint_cache: HashMap, - pub bounds: RectF32, - pub view_box: RectF32, + pub(crate) objects: Vec, + paints: Vec, + paint_cache: HashMap, + 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 { + #[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 { self.objects .iter() .map(|object| { @@ -149,6 +172,15 @@ impl Scene { self.view_box } } + + #[inline] + pub fn build(&self, + built_options: &PreparedRenderOptions, + listener: Box, + 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, - 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, diff --git a/svg/src/lib.rs b/svg/src/lib.rs index 458a097d..269f058f 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -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(),