From d9e994e46d78182d7e514dee217a87387d3e84d1 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 19 Feb 2020 17:44:41 -0800 Subject: [PATCH] Transition the existing postprocessing system to a layers system. This is preparatory work for composite ops and blurs. Closes #261. --- color/src/lib.rs | 5 + demo/common/src/lib.rs | 112 ++++++++----- demo/common/src/renderer.rs | 23 +-- demo/common/src/ui.rs | 99 ++++++----- gl/src/lib.rs | 5 + gpu/src/lib.rs | 1 + metal/src/lib.rs | 11 ++ renderer/src/builder.rs | 184 ++++++++++++++++---- renderer/src/gpu/renderer.rs | 315 ++++++++++++++++++----------------- renderer/src/gpu_data.rs | 5 + renderer/src/scene.rs | 53 +++--- renderer/src/tiles.rs | 7 +- renderer/src/z_buffer.rs | 48 ++---- svg/src/lib.rs | 17 +- 14 files changed, 524 insertions(+), 361 deletions(-) diff --git a/color/src/lib.rs b/color/src/lib.rs index 0952fad5..afe1efdc 100644 --- a/color/src/lib.rs +++ b/color/src/lib.rs @@ -142,6 +142,11 @@ impl ColorF { ColorF::default() } + #[inline] + pub fn black() -> ColorF { + ColorF(F32x4::new(0.0, 0.0, 0.0, 1.0)) + } + #[inline] pub fn white() -> ColorF { ColorF(F32x4::splat(1.0)) diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index 8e49fd36..eccbf217 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -22,7 +22,6 @@ use crate::device::{GroundProgram, GroundVertexArray}; use crate::ui::{DemoUIModel, DemoUIPresenter, ScreenshotInfo, ScreenshotType, UIAction}; use crate::window::{Event, Keycode, SVGPath, Window, WindowSize}; use clap::{App, Arg}; -use pathfinder_color::ColorU; use pathfinder_export::{Export, FileFormat}; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::transform2d::Transform2F; @@ -32,9 +31,9 @@ use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::Device; use pathfinder_renderer::concurrent::scene_proxy::{RenderCommandStream, SceneProxy}; use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; -use pathfinder_renderer::gpu::renderer::{RenderStats, RenderTime, Renderer}; +use pathfinder_renderer::gpu::renderer::{PostprocessOptions, RenderStats, RenderTime, Renderer}; use pathfinder_renderer::options::{BuildOptions, RenderTransform}; -use pathfinder_renderer::post::STEM_DARKENING_FACTORS; +use pathfinder_renderer::post::{DEFRINGING_KERNEL_CORE_GRAPHICS, STEM_DARKENING_FACTORS}; use pathfinder_renderer::scene::Scene; use pathfinder_svg::BuiltSVG; use pathfinder_ui::{MousePosition, UIEvent}; @@ -63,25 +62,6 @@ const CAMERA_ZOOM_AMOUNT_2D: f32 = 0.1; // Half of the eye separation distance. const DEFAULT_EYE_OFFSET: f32 = 0.025; -const LIGHT_BG_COLOR: ColorU = ColorU { - r: 248, - g: 248, - b: 248, - a: 255, -}; -const DARK_BG_COLOR: ColorU = ColorU { - r: 32, - g: 32, - b: 32, - a: 255, -}; -const TRANSPARENT_BG_COLOR: ColorU = ColorU { - r: 0, - g: 0, - b: 0, - a: 0, -}; - const APPROX_FONT_SIZE: f32 = 16.0; const MESSAGE_TIMEOUT_SECS: u64 = 5; @@ -101,6 +81,7 @@ pub struct DemoApp where W: Window { window_size: WindowSize, + svg_tree: Tree, scene_metadata: SceneMetadata, render_transform: Option, render_command_stream: Option, @@ -151,7 +132,12 @@ impl DemoApp where W: Window { // Set up the executor. let executor = DemoExecutor::new(options.jobs); - let mut built_svg = load_scene(resources, &options.input_path); + let mut ui_model = DemoUIModel::new(&options); + let render_options = RendererOptions { background_color: None }; + + let effects = build_effects(&ui_model); + + let (mut built_svg, svg_tree) = load_scene(resources, &options.input_path, effects); let message = get_svg_building_message(&built_svg); let viewport = window.viewport(options.mode.view(0)); @@ -159,12 +145,9 @@ impl DemoApp where W: Window { viewport, window_size: window_size.device_size(), }; - // FIXME(pcwalton) - let render_options = RendererOptions { - background_color: None, - }; let renderer = Renderer::new(device, resources, dest_framebuffer, render_options); + 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()); @@ -177,8 +160,6 @@ impl DemoApp where W: Window { &renderer.quad_vertex_positions_buffer(), &renderer.quad_vertex_indices_buffer()); - let mut ui_model = DemoUIModel::new(&options); - let mut message_epoch = 0; emit_message::( &mut ui_model, @@ -196,6 +177,7 @@ impl DemoApp where W: Window { window_size, + svg_tree, scene_metadata, render_transform: None, render_command_stream: None, @@ -445,7 +427,11 @@ impl DemoApp where W: Window { } Event::OpenSVG(ref svg_path) => { - let mut built_svg = load_scene(self.window.resource_loader(), svg_path); + let effects = build_effects(&self.ui_model); + let (mut built_svg, svg_tree) = load_scene(self.window.resource_loader(), + svg_path, + effects); + self.ui_model.message = get_svg_building_message(&built_svg); let viewport_size = self.window.viewport(self.ui_model.mode.view(0)).size(); @@ -456,6 +442,7 @@ impl DemoApp where W: Window { viewport_size); self.scene_proxy.replace_scene(built_svg.scene); + self.svg_tree = svg_tree; self.dirty = true; } @@ -499,8 +486,6 @@ impl DemoApp where W: Window { .to_f32() .scale(self.window_size.backing_scale_factor); - self.ui_presenter.set_show_text_effects(self.scene_metadata.monochrome_color.is_some()); - let mut ui_action = UIAction::None; if self.options.ui == UIVisibility::All { self.ui_presenter.update( @@ -599,6 +584,15 @@ impl DemoApp where W: Window { match ui_action { UIAction::None => {} UIAction::ModelChanged => self.dirty = true, + UIAction::EffectsChanged => { + let effects = build_effects(&self.ui_model); + let mut built_svg = build_svg_tree(&self.svg_tree, effects); + let viewport_size = self.window.viewport(self.ui_model.mode.view(0)).size(); + self.scene_metadata = + SceneMetadata::new_clipping_view_box(&mut built_svg.scene, viewport_size); + self.scene_proxy.replace_scene(built_svg.scene); + self.dirty = true; + } UIAction::TakeScreenshot(ref info) => { self.pending_screenshot_info = Some((*info).clone()); self.dirty = true; @@ -636,14 +630,6 @@ impl DemoApp where W: Window { } } } - - fn background_color(&self) -> ColorU { - match self.ui_model.background_color { - BackgroundColor::Light => LIGHT_BG_COLOR, - BackgroundColor::Dark => DARK_BG_COLOR, - BackgroundColor::Transparent => TRANSPARENT_BG_COLOR, - } - } } #[derive(Clone)] @@ -756,7 +742,10 @@ pub enum UIVisibility { All, } -fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &SVGPath) -> BuiltSVG { +fn load_scene(resource_loader: &dyn ResourceLoader, + input_path: &SVGPath, + effects: Option) + -> (BuiltSVG, Tree) { let mut data; match *input_path { SVGPath::Default => data = resource_loader.slurp(DEFAULT_SVG_VIRTUAL_PATH).unwrap(), @@ -767,7 +756,24 @@ fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &SVGPath) -> Bui } }; - BuiltSVG::from_tree(Tree::from_data(&data, &UsvgOptions::default()).unwrap()) + let tree = Tree::from_data(&data, &UsvgOptions::default()).expect("Failed to parse the SVG!"); + let built_svg = build_svg_tree(&tree, effects); + (built_svg, tree) +} + +fn build_svg_tree(tree: &Tree, effects: Option) -> BuiltSVG { + let mut scene = Scene::new(); + if let Some(effects) = effects { + scene.push_layer(effects); + } + + let mut built_svg = BuiltSVG::from_tree_and_scene(&tree, scene); + + if effects.is_some() { + built_svg.scene.pop_layer(); + } + + built_svg } fn center_of_window(window_size: &WindowSize) -> Vector2F { @@ -842,7 +848,6 @@ impl BackgroundColor { struct SceneMetadata { view_box: RectF, - monochrome_color: Option, } impl SceneMetadata { @@ -850,8 +855,25 @@ impl SceneMetadata { // Can we simplify this? fn new_clipping_view_box(scene: &mut Scene, viewport_size: Vector2I) -> SceneMetadata { let view_box = scene.view_box(); - let monochrome_color = scene.monochrome_color(); scene.set_view_box(RectF::new(Vector2F::default(), viewport_size.to_f32())); - SceneMetadata { view_box, monochrome_color } + SceneMetadata { view_box } } } + +fn build_effects(ui_model: &DemoUIModel) -> Option { + if !ui_model.gamma_correction_effect_enabled && !ui_model.subpixel_aa_effect_enabled { + return None; + } + + Some(PostprocessOptions { + fg_color: ui_model.foreground_color().to_f32(), + bg_color: ui_model.background_color().to_f32(), + gamma_correction: ui_model.gamma_correction_effect_enabled, + defringing_kernel: if ui_model.subpixel_aa_effect_enabled { + // TODO(pcwalton): Select FreeType defringing kernel as necessary. + Some(DEFRINGING_KERNEL_CORE_GRAPHICS) + } else { + None + }, + }) +} diff --git a/demo/common/src/renderer.rs b/demo/common/src/renderer.rs index 2228f3c4..f44b0f91 100644 --- a/demo/common/src/renderer.rs +++ b/demo/common/src/renderer.rs @@ -21,10 +21,8 @@ use pathfinder_geometry::rect::RectI; use pathfinder_geometry::transform3d::Transform4F; use pathfinder_geometry::vector::{Vector2I, Vector4F}; use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; -use pathfinder_renderer::gpu::renderer::PostprocessOptions; use pathfinder_renderer::gpu_data::RenderCommand; use pathfinder_renderer::options::RenderTransform; -use pathfinder_renderer::post::DEFRINGING_KERNEL_CORE_GRAPHICS; use std::path::PathBuf; const GROUND_SOLID_COLOR: ColorU = ColorU { @@ -88,7 +86,7 @@ impl DemoApp where W: Window { // Clear to the appropriate color. let clear_color = match mode { - Mode::TwoD => Some(self.background_color().to_f32()), + Mode::TwoD => Some(self.ui_model.background_color().to_f32()), Mode::ThreeD => None, Mode::VR => Some(ColorF::transparent_black()), }; @@ -221,7 +219,7 @@ impl DemoApp where W: Window { // Don't clear the first scene after drawing it. let clear_color = if render_scene_index == 0 { - Some(self.background_color().to_f32()) + Some(self.ui_model.background_color().to_f32()) } else { None }; @@ -251,23 +249,6 @@ impl DemoApp where W: Window { } fn render_vector_scene(&mut self) { - match self.scene_metadata.monochrome_color { - None => self.renderer.set_postprocess_options(None), - Some(fg_color) => { - self.renderer.set_postprocess_options(Some(PostprocessOptions { - fg_color: fg_color.to_f32(), - bg_color: self.background_color().to_f32(), - gamma_correction: self.ui_model.gamma_correction_effect_enabled, - defringing_kernel: if self.ui_model.subpixel_aa_effect_enabled { - // TODO(pcwalton): Select FreeType defringing kernel as necessary. - Some(DEFRINGING_KERNEL_CORE_GRAPHICS) - } else { - None - }, - })) - } - } - if self.ui_model.mode == Mode::TwoD { self.renderer.disable_depth(); } else { diff --git a/demo/common/src/ui.rs b/demo/common/src/ui.rs index 2c7c8c5e..0f2e500f 100644 --- a/demo/common/src/ui.rs +++ b/demo/common/src/ui.rs @@ -11,8 +11,9 @@ use crate::camera::Mode; use crate::window::Window; use crate::{BackgroundColor, Options}; -use pathfinder_geometry::vector::Vector2I; +use pathfinder_color::ColorU; use pathfinder_geometry::rect::RectI; +use pathfinder_geometry::vector::Vector2I; use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::Device; use pathfinder_renderer::gpu::debug::DebugUIPresenter; @@ -39,6 +40,10 @@ const SCREENSHOT_PANEL_HEIGHT: i32 = BUTTON_HEIGHT * 2; const ROTATE_PANEL_WIDTH: i32 = SLIDER_WIDTH + PADDING * 2; const ROTATE_PANEL_HEIGHT: i32 = PADDING * 2 + SLIDER_HEIGHT; +const LIGHT_BG_COLOR: ColorU = ColorU { r: 248, g: 248, b: 248, a: 255, }; +const DARK_BG_COLOR: ColorU = ColorU { r: 32, g: 32, b: 32, a: 255, }; +const TRANSPARENT_BG_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 0, }; + static EFFECTS_PNG_NAME: &'static str = "demo-effects"; static OPEN_PNG_NAME: &'static str = "demo-open"; static ROTATE_PNG_NAME: &'static str = "demo-rotate"; @@ -74,6 +79,22 @@ impl DemoUIModel { fn rotation(&self) -> f32 { (self.rotation as f32 / SLIDER_WIDTH as f32 * 2.0 - 1.0) * PI } + + // Only relevant if in monochrome mode. + pub fn foreground_color(&self) -> ColorU { + match self.background_color { + BackgroundColor::Light | BackgroundColor::Transparent => ColorU::black(), + BackgroundColor::Dark => ColorU::white(), + } + } + + pub fn background_color(&self) -> ColorU { + match self.background_color { + BackgroundColor::Light => LIGHT_BG_COLOR, + BackgroundColor::Dark => DARK_BG_COLOR, + BackgroundColor::Transparent => TRANSPARENT_BG_COLOR, + } + } } pub struct DemoUIPresenter @@ -93,8 +114,6 @@ where background_panel_visible: bool, screenshot_panel_visible: bool, rotate_panel_visible: bool, - - show_text_effects: bool, } impl DemoUIPresenter @@ -126,15 +145,9 @@ where background_panel_visible: false, screenshot_panel_visible: false, rotate_panel_visible: false, - - show_text_effects: true, } } - pub fn set_show_text_effects(&mut self, show_text_effects: bool) { - self.show_text_effects = show_text_effects; - } - pub fn update( &mut self, device: &D, @@ -156,22 +169,18 @@ where let button_size = Vector2I::new(BUTTON_WIDTH, BUTTON_HEIGHT); - // Draw text effects button. - if self.show_text_effects { - if debug_ui_presenter.ui_presenter.draw_button(device, - position, - &self.effects_texture) { - self.effects_panel_visible = !self.effects_panel_visible; - } - if !self.effects_panel_visible { - debug_ui_presenter.ui_presenter.draw_tooltip( - device, - "Text Effects", - RectI::new(position, button_size), - ); - } - position += Vector2I::new(button_size.x() + PADDING, 0); + // Draw effects button. + if debug_ui_presenter.ui_presenter.draw_button(device, position, &self.effects_texture) { + self.effects_panel_visible = !self.effects_panel_visible; } + if !self.effects_panel_visible { + debug_ui_presenter.ui_presenter.draw_tooltip( + device, + "Effects", + RectI::new(position, button_size), + ); + } + position += Vector2I::new(button_size.x() + PADDING, 0); // Draw open button. if debug_ui_presenter.ui_presenter.draw_button(device, position, &self.open_texture) { @@ -245,7 +254,7 @@ where position += Vector2I::new(button_size.x() + PADDING, 0); // Draw effects panel, if necessary. - self.draw_effects_panel(device, debug_ui_presenter, model); + self.draw_effects_panel(device, debug_ui_presenter, model, action); // Draw rotate and zoom buttons, if applicable. if model.mode != Mode::TwoD { @@ -324,7 +333,8 @@ where fn draw_effects_panel(&mut self, device: &D, debug_ui_presenter: &mut DebugUIPresenter, - model: &mut DemoUIModel) { + model: &mut DemoUIModel, + action: &mut UIAction) { if !self.effects_panel_visible { return; } @@ -340,30 +350,30 @@ where WINDOW_COLOR, ); - model.gamma_correction_effect_enabled = self.draw_effects_switch( + self.draw_effects_switch( device, + action, debug_ui_presenter, "Gamma Correction", 0, effects_panel_y, - model.gamma_correction_effect_enabled, - ); - model.stem_darkening_effect_enabled = self.draw_effects_switch( + &mut model.gamma_correction_effect_enabled); + self.draw_effects_switch( device, + action, debug_ui_presenter, "Stem Darkening", 1, effects_panel_y, - model.stem_darkening_effect_enabled, - ); - model.subpixel_aa_effect_enabled = self.draw_effects_switch( + &mut model.stem_darkening_effect_enabled); + self.draw_effects_switch( device, + action, debug_ui_presenter, "Subpixel AA", 2, effects_panel_y, - model.subpixel_aa_effect_enabled, - ); + &mut model.subpixel_aa_effect_enabled); } fn draw_screenshot_panel( @@ -592,12 +602,12 @@ where fn draw_effects_switch( &self, device: &D, + action: &mut UIAction, debug_ui_presenter: &mut DebugUIPresenter, text: &str, index: i32, window_y: i32, - value: bool, - ) -> bool { + value: &mut bool) { let text_x = PADDING * 2; let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index; debug_ui_presenter @@ -608,10 +618,16 @@ where let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (switch_width + PADDING); let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index; let switch_position = Vector2I::new(switch_x, switch_y); - debug_ui_presenter - .ui_presenter - .draw_text_switch(device, switch_position, &["Off", "On"], value as u8) - != 0 + + let new_value = + debug_ui_presenter + .ui_presenter + .draw_text_switch(device, switch_position, &["Off", "On"], *value as u8) != 0; + + if new_value != *value { + *action = UIAction::EffectsChanged; + *value = new_value; + } } } @@ -619,6 +635,7 @@ where pub enum UIAction { None, ModelChanged, + EffectsChanged, TakeScreenshot(ScreenshotInfo), ZoomIn, ZoomActualSize, diff --git a/gl/src/lib.rs b/gl/src/lib.rs index ad1318dd..34684887 100644 --- a/gl/src/lib.rs +++ b/gl/src/lib.rs @@ -478,6 +478,11 @@ impl Device for GLDevice { &framebuffer.texture } + #[inline] + fn texture_format(&self, texture: &Self::Texture) -> TextureFormat { + texture.format + } + #[inline] fn texture_size(&self, texture: &Self::Texture) -> Vector2I { texture.size diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs index 70641620..c86b814b 100644 --- a/gpu/src/lib.rs +++ b/gpu/src/lib.rs @@ -70,6 +70,7 @@ pub trait Device: Sized { mode: BufferUploadMode, ); fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture; + fn texture_format(&self, texture: &Self::Texture) -> TextureFormat; fn texture_size(&self, texture: &Self::Texture) -> Vector2I; fn upload_to_texture(&self, texture: &Self::Texture, rect: RectI, data: TextureDataRef); fn read_pixels(&self, target: &RenderTarget, viewport: RectI) diff --git a/metal/src/lib.rs b/metal/src/lib.rs index f44411d5..01050d7e 100644 --- a/metal/src/lib.rs +++ b/metal/src/lib.rs @@ -437,6 +437,17 @@ impl Device for MetalDevice { &framebuffer.0 } + fn texture_format(&self, texture: &MetalTexture) -> TextureFormat { + match texture.texture.pixel_format() { + MTLPixelFormat::R8Unorm => TextureFormat::R8, + MTLPixelFormat::R16Float => TextureFormat::R16F, + MTLPixelFormat::RGBA8Unorm => TextureFormat::RGBA8, + MTLPixelFormat::RGBA16Float => TextureFormat::RGBA16F, + MTLPixelFormat::RGBA32Float => TextureFormat::RGBA32F, + _ => panic!("Unexpected Metal texture format!"), + } + } + fn texture_size(&self, texture: &MetalTexture) -> Vector2I { Vector2I::new(texture.texture.width() as i32, texture.texture.height() as i32) } diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index d22d163c..2f754b5a 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -11,12 +11,12 @@ //! Packs data onto the GPU. use crate::concurrent::executor::Executor; -use crate::gpu::renderer::MASK_TILES_ACROSS; +use crate::gpu::renderer::{MASK_TILES_ACROSS, PostprocessOptions}; use crate::gpu_data::{AlphaTile, AlphaTileVertex, FillBatchPrimitive, MaskTile, MaskTileVertex}; -use crate::gpu_data::{RenderCommand, TileObjectPrimitive}; +use crate::gpu_data::{RenderCommand, SolidTileVertex, TileObjectPrimitive}; use crate::options::{PreparedBuildOptions, RenderCommandListener}; use crate::paint::{PaintInfo, PaintMetadata}; -use crate::scene::Scene; +use crate::scene::{DisplayItem, Scene}; use crate::tile_map::DenseTileMap; use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH, Tiler, TilingPathInfo}; use crate::z_buffer::ZBuffer; @@ -37,7 +37,6 @@ pub(crate) struct SceneBuilder<'a> { next_alpha_tile_index: AtomicUsize, next_mask_tile_index: AtomicUsize, - pub(crate) z_buffer: ZBuffer, pub(crate) listener: Box, } @@ -52,17 +51,22 @@ pub(crate) struct ObjectBuilder { pub(crate) struct BuiltPath { pub mask_tiles: Vec, pub alpha_tiles: Vec, + pub solid_tiles: Vec, pub tiles: DenseTileMap, pub fill_rule: FillRule, } +#[derive(Clone, Copy, Debug)] +pub(crate) struct SolidTile { + pub(crate) coords: Vector2I, +} + impl<'a> SceneBuilder<'a> { pub(crate) fn new( scene: &'a Scene, built_options: &'a PreparedBuildOptions, listener: Box, ) -> SceneBuilder<'a> { - let effective_view_box = scene.effective_view_box(built_options); SceneBuilder { scene, built_options, @@ -70,7 +74,6 @@ impl<'a> SceneBuilder<'a> { next_alpha_tile_index: AtomicUsize::new(0), next_mask_tile_index: AtomicUsize::new(0), - z_buffer: ZBuffer::new(effective_view_box), listener, } } @@ -167,26 +170,84 @@ impl<'a> SceneBuilder<'a> { tiler.object_builder.built_path } - fn cull_tiles(&self, built_clip_paths: Vec, built_draw_paths: Vec) + fn cull_tiles(&self, + paint_metadata: &[PaintMetadata], + built_clip_paths: Vec, + built_draw_paths: Vec) -> CulledTiles { let mut culled_tiles = CulledTiles { mask_winding_tiles: vec![], mask_evenodd_tiles: vec![], - alpha_tiles: vec![], + display_list: vec![], }; for built_clip_path in built_clip_paths { culled_tiles.push_mask_tiles(&built_clip_path); } - for built_draw_path in built_draw_paths { - culled_tiles.push_mask_tiles(&built_draw_path); + let mut remaining_layer_z_buffers = self.build_solid_tiles(&built_draw_paths); + remaining_layer_z_buffers.reverse(); - for alpha_tile in built_draw_path.alpha_tiles { - let alpha_tile_coords = alpha_tile.upper_left.tile_position(); - if self.z_buffer.test(alpha_tile_coords, - alpha_tile.upper_left.object_index as u32) { - culled_tiles.alpha_tiles.push(alpha_tile); + // Process first Z-buffer. + let first_z_buffer = remaining_layer_z_buffers.pop().unwrap(); + let first_solid_tiles = first_z_buffer.build_solid_tiles(&self.scene.paths, + paint_metadata); + if !first_solid_tiles.is_empty() { + culled_tiles.display_list + .push(CulledDisplayItem::DrawSolidTiles(first_solid_tiles)); + } + + let mut layer_z_buffers_stack = vec![first_z_buffer]; + + for display_item in &self.scene.display_list { + // Just pass through `PushLayer` and `PopLayer` commands. + let (start_draw_path_index, end_draw_path_index) = match *display_item { + DisplayItem::PushLayer { effects } => { + culled_tiles.display_list.push(CulledDisplayItem::PushLayer { effects }); + + let z_buffer = remaining_layer_z_buffers.pop().unwrap(); + let solid_tiles = z_buffer.build_solid_tiles(&self.scene.paths, + paint_metadata); + if !solid_tiles.is_empty() { + culled_tiles.display_list + .push(CulledDisplayItem::DrawSolidTiles(solid_tiles)); + } + layer_z_buffers_stack.push(z_buffer); + continue; + } + DisplayItem::PopLayer => { + culled_tiles.display_list.push(CulledDisplayItem::PopLayer); + layer_z_buffers_stack.pop(); + continue; + } + DisplayItem::DrawPaths { start_index, end_index } => (start_index, end_index), + }; + + for draw_path_index in start_draw_path_index..end_draw_path_index { + let built_draw_path = &built_draw_paths[draw_path_index as usize]; + culled_tiles.push_mask_tiles(built_draw_path); + + // Create a `DrawAlphaTiles` display item if necessary. + match culled_tiles.display_list.last() { + Some(&CulledDisplayItem::DrawAlphaTiles(_)) => {} + _ => culled_tiles.display_list.push(CulledDisplayItem::DrawAlphaTiles(vec![])), + } + + // Fetch the destination alpha tiles buffer. + let culled_alpha_tiles = match *culled_tiles.display_list.last_mut().unwrap() { + CulledDisplayItem::DrawAlphaTiles(ref mut culled_alpha_tiles) => { + culled_alpha_tiles + } + _ => unreachable!(), + }; + + let layer_z_buffer = layer_z_buffers_stack.last().unwrap(); + for alpha_tile in &built_draw_path.alpha_tiles { + let alpha_tile_coords = alpha_tile.upper_left.tile_position(); + if layer_z_buffer.test(alpha_tile_coords, + alpha_tile.upper_left.object_index as u32) { + culled_alpha_tiles.push(*alpha_tile); + } } } } @@ -194,12 +255,37 @@ impl<'a> SceneBuilder<'a> { culled_tiles } - fn pack_tiles(&mut self, paint_metadata: &[PaintMetadata], culled_tiles: CulledTiles) { - let path_count = self.scene.paths.len() as u32; - let solid_tiles = self.z_buffer.build_solid_tiles(&self.scene.paths, - paint_metadata, - 0..path_count); + fn build_solid_tiles(&self, built_draw_paths: &[BuiltPath]) -> Vec { + let effective_view_box = self.scene.effective_view_box(self.built_options); + let mut z_buffers = vec![ZBuffer::new(effective_view_box)]; + let mut z_buffer_index_stack = vec![0]; + // Create Z-buffers. + for display_item in &self.scene.display_list { + match *display_item { + DisplayItem::PushLayer { .. } => { + z_buffer_index_stack.push(z_buffers.len()); + z_buffers.push(ZBuffer::new(effective_view_box)); + } + DisplayItem::PopLayer => { + z_buffer_index_stack.pop(); + } + DisplayItem::DrawPaths { start_index, end_index } => { + let (start_index, end_index) = (start_index as usize, end_index as usize); + let z_buffer = &mut z_buffers[*z_buffer_index_stack.last().unwrap()]; + for (path_index, built_draw_path) in + built_draw_paths[start_index..end_index].iter().enumerate() { + z_buffer.update(&built_draw_path.solid_tiles, path_index as u32); + } + } + } + } + debug_assert_eq!(z_buffer_index_stack.len(), 1); + + z_buffers + } + + fn pack_tiles(&mut self, culled_tiles: CulledTiles) { if !culled_tiles.mask_winding_tiles.is_empty() { self.listener.send(RenderCommand::RenderMaskTiles { tiles: culled_tiles.mask_winding_tiles, @@ -213,11 +299,19 @@ impl<'a> SceneBuilder<'a> { }); } - if !solid_tiles.is_empty() { - self.listener.send(RenderCommand::DrawSolidTiles(solid_tiles)); - } - if !culled_tiles.alpha_tiles.is_empty() { - self.listener.send(RenderCommand::DrawAlphaTiles(culled_tiles.alpha_tiles)); + for display_item in culled_tiles.display_list { + match display_item { + CulledDisplayItem::DrawSolidTiles(tiles) => { + self.listener.send(RenderCommand::DrawSolidTiles(tiles)) + } + CulledDisplayItem::DrawAlphaTiles(tiles) => { + self.listener.send(RenderCommand::DrawAlphaTiles(tiles)) + } + CulledDisplayItem::PushLayer { effects } => { + self.listener.send(RenderCommand::PushLayer { effects }) + } + CulledDisplayItem::PopLayer => self.listener.send(RenderCommand::PopLayer), + } } } @@ -226,8 +320,8 @@ impl<'a> SceneBuilder<'a> { built_clip_paths: Vec, built_draw_paths: Vec) { self.listener.send(RenderCommand::FlushFills); - let culled_tiles = self.cull_tiles(built_clip_paths, built_draw_paths); - self.pack_tiles(paint_metadata, culled_tiles); + let culled_tiles = self.cull_tiles(paint_metadata, built_clip_paths, built_draw_paths); + self.pack_tiles(culled_tiles); } pub(crate) fn allocate_mask_tile_index(&self) -> u16 { @@ -236,10 +330,36 @@ impl<'a> SceneBuilder<'a> { } } +impl BuiltPath { + fn new(bounds: RectF, fill_rule: FillRule) -> BuiltPath { + BuiltPath { + mask_tiles: vec![], + alpha_tiles: vec![], + solid_tiles: vec![], + tiles: DenseTileMap::new(tiles::round_rect_out_to_tile_bounds(bounds)), + fill_rule, + } + } +} + +impl SolidTile { + #[inline] + pub(crate) fn new(coords: Vector2I) -> SolidTile { + SolidTile { coords } + } +} + struct CulledTiles { mask_winding_tiles: Vec, mask_evenodd_tiles: Vec, - alpha_tiles: Vec, + display_list: Vec, +} + +enum CulledDisplayItem { + DrawSolidTiles(Vec), + DrawAlphaTiles(Vec), + PushLayer { effects: PostprocessOptions }, + PopLayer, } #[derive(Clone, Copy, Debug, Default)] @@ -252,13 +372,7 @@ pub struct TileStats { impl ObjectBuilder { pub(crate) fn new(bounds: RectF, fill_rule: FillRule) -> ObjectBuilder { - let tile_rect = tiles::round_rect_out_to_tile_bounds(bounds); - let tiles = DenseTileMap::new(tile_rect); - ObjectBuilder { - built_path: BuiltPath { mask_tiles: vec![], alpha_tiles: vec![], tiles, fill_rule }, - bounds, - fills: vec![], - } + ObjectBuilder { built_path: BuiltPath::new(bounds, fill_rule), bounds, fills: vec![] } } #[inline] diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index e8cdfb9f..bbec59d8 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -19,7 +19,7 @@ use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData}; use crate::gpu_data::{RenderCommand, SolidTileVertex}; use crate::post::DefringingKernel; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; -use pathfinder_color::{self as color, ColorF, ColorU}; +use pathfinder_color::{self as color, ColorF}; use pathfinder_content::fill::FillRule; use pathfinder_geometry::vector::{Vector2I, Vector4F}; use pathfinder_geometry::rect::RectI; @@ -43,6 +43,8 @@ static QUAD_VERTEX_INDICES: [u32; 6] = [0, 1, 3, 1, 2, 3]; pub(crate) const MASK_TILES_ACROSS: u32 = 256; pub(crate) const MASK_TILES_DOWN: u32 = 256; +const FRAMEBUFFER_CACHE_SIZE: usize = 8; + // FIXME(pcwalton): Shrink this again! const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * MASK_TILES_ACROSS as i32; const MASK_FRAMEBUFFER_HEIGHT: i32 = TILE_HEIGHT as i32 * MASK_TILES_DOWN as i32; @@ -75,9 +77,9 @@ where fill_framebuffer: D::Framebuffer, mask_framebuffer: D::Framebuffer, paint_texture: Option, + layer_framebuffer_stack: Vec>, // Postprocessing shader - postprocess_source_framebuffer: Option, postprocess_program: PostprocessProgram, postprocess_vertex_array: PostprocessVertexArray, gamma_lut_texture: D::Texture, @@ -93,6 +95,7 @@ where // Rendering state framebuffer_flags: FramebufferFlags, buffered_fills: Vec, + framebuffer_cache: FramebufferCache, // Debug pub stats: RenderStats, @@ -102,7 +105,6 @@ where pub debug_ui_presenter: DebugUIPresenter, // Extra info - postprocess_options: Option, use_depth: bool, } @@ -225,8 +227,8 @@ where fill_framebuffer, mask_framebuffer, paint_texture: None, + layer_framebuffer_stack: vec![], - postprocess_source_framebuffer: None, postprocess_program, postprocess_vertex_array, gamma_lut_texture, @@ -245,8 +247,8 @@ where framebuffer_flags: FramebufferFlags::empty(), buffered_fills: vec![], + framebuffer_cache: FramebufferCache::new(), - postprocess_options: None, use_depth: false, } } @@ -254,7 +256,6 @@ where pub fn begin_scene(&mut self) { self.framebuffer_flags = FramebufferFlags::empty(); self.device.begin_commands(); - self.init_postprocessing_framebuffer(); self.stats = RenderStats::default(); } @@ -277,6 +278,8 @@ where self.upload_mask_tiles(mask_tiles, fill_rule); self.draw_mask_tiles(count as u32, fill_rule); } + RenderCommand::PushLayer { effects } => self.push_layer(effects), + RenderCommand::PopLayer => self.pop_layer(), RenderCommand::DrawSolidTiles(ref solid_tile_vertices) => { let count = solid_tile_vertices.len() / 4; self.stats.solid_tile_count += count; @@ -294,10 +297,6 @@ where } pub fn end_scene(&mut self) { - if self.postprocess_options.is_some() { - self.postprocess(); - } - self.end_composite_timer_query(); self.pending_timers.push_back(mem::replace(&mut self.current_timers, RenderTimers::new())); @@ -360,11 +359,6 @@ where self.debug_ui_presenter.ui_presenter.set_framebuffer_size(new_framebuffer_size); } - #[inline] - pub fn set_postprocess_options(&mut self, new_options: Option) { - self.postprocess_options = new_options; - } - #[inline] pub fn disable_depth(&mut self) { self.use_depth = false; @@ -386,17 +380,8 @@ where } fn upload_paint_data(&mut self, paint_data: &PaintData) { - // FIXME(pcwalton): This is a hack. We shouldn't be generating paint data at all on the - // renderer side. - let (paint_size, paint_texels): (Vector2I, &[ColorU]); - let dummy_paint = [ColorU::white(); 1]; - if self.postprocess_options.is_some() { - paint_size = Vector2I::splat(1); - paint_texels = &dummy_paint; - } else { - paint_size = paint_data.size; - paint_texels = &paint_data.texels; - }; + let paint_size = paint_data.size; + let paint_texels = &paint_data.texels; match self.paint_texture { Some(ref paint_texture) if self.device.texture_size(paint_texture) == paint_size => {} @@ -675,68 +660,6 @@ where self.preserve_draw_framebuffer(); } - fn postprocess(&mut self) { - let mut clear_color = None; - if !self.framebuffer_flags - .contains(FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS) { - clear_color = self.options.background_color; - } - - let postprocess_options = match self.postprocess_options { - None => return, - Some(ref options) => options, - }; - - let postprocess_source_framebuffer = self.postprocess_source_framebuffer.as_ref().unwrap(); - let source_texture = self - .device - .framebuffer_texture(postprocess_source_framebuffer); - let source_texture_size = self.device.texture_size(source_texture); - let main_viewport = self.main_viewport(); - - let mut uniforms = vec![ - (&self.postprocess_program.framebuffer_size_uniform, - UniformData::Vec2(main_viewport.size().to_f32().0)), - (&self.postprocess_program.source_uniform, UniformData::TextureUnit(0)), - (&self.postprocess_program.source_size_uniform, - UniformData::Vec2(source_texture_size.0.to_f32x2())), - (&self.postprocess_program.gamma_lut_uniform, UniformData::TextureUnit(1)), - (&self.postprocess_program.fg_color_uniform, - UniformData::Vec4(postprocess_options.fg_color.0)), - (&self.postprocess_program.bg_color_uniform, - UniformData::Vec4(postprocess_options.bg_color.0)), - (&self.postprocess_program.gamma_correction_enabled_uniform, - UniformData::Int(postprocess_options.gamma_correction as i32)), - ]; - - match postprocess_options.defringing_kernel { - Some(ref kernel) => { - uniforms.push((&self.postprocess_program.kernel_uniform, - UniformData::Vec4(F32x4::from_slice(&kernel.0)))); - } - None => { - uniforms.push((&self.postprocess_program.kernel_uniform, - UniformData::Vec4(F32x4::default()))); - } - } - - self.device.draw_elements(6, &RenderState { - target: &self.dest_render_target(), - program: &self.postprocess_program.program, - vertex_array: &self.postprocess_vertex_array.vertex_array, - primitive: Primitive::Triangles, - textures: &[&source_texture, &self.gamma_lut_texture], - uniforms: &uniforms, - viewport: main_viewport, - options: RenderOptions { - clear_ops: ClearOps { color: clear_color, ..ClearOps::default() }, - ..RenderOptions::default() - }, - }); - - self.framebuffer_flags.insert(FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS); - } - fn draw_stencil(&mut self, quad_positions: &[Vector4F]) { self.device.allocate_buffer( &self.stencil_vertex_array.vertex_buffer, @@ -819,52 +742,96 @@ where } pub fn draw_render_target(&self) -> RenderTarget { - if self.postprocess_options.is_some() { - RenderTarget::Framebuffer(self.postprocess_source_framebuffer.as_ref().unwrap()) - } else { - self.dest_render_target() - } - } - - pub fn dest_render_target(&self) -> RenderTarget { - match self.dest_framebuffer { - DestFramebuffer::Default { .. } => RenderTarget::Default, - DestFramebuffer::Other(ref framebuffer) => RenderTarget::Framebuffer(framebuffer), - } - } - - fn init_postprocessing_framebuffer(&mut self) { - if !self.postprocess_options.is_some() { - self.postprocess_source_framebuffer = None; - return; - } - - let source_framebuffer_size = self.draw_viewport().size(); - match self.postprocess_source_framebuffer { - Some(ref framebuffer) - if self - .device - .texture_size(self.device.framebuffer_texture(framebuffer)) - == source_framebuffer_size => {} - _ => { - let texture = self - .device - .create_texture(TextureFormat::R8, source_framebuffer_size); - self.postprocess_source_framebuffer = - Some(self.device.create_framebuffer(texture)); + match self.layer_framebuffer_stack.last() { + Some(ref layer_framebuffer_info) => { + RenderTarget::Framebuffer(&layer_framebuffer_info.framebuffer) } + None => { + match self.dest_framebuffer { + DestFramebuffer::Default { .. } => RenderTarget::Default, + DestFramebuffer::Other(ref framebuffer) => { + RenderTarget::Framebuffer(framebuffer) + } + } + } + } + } + + fn push_layer(&mut self, effects: PostprocessOptions) { + let main_framebuffer_size = self.main_viewport().size(); + let framebuffer_size = if effects.defringing_kernel.is_some() { + main_framebuffer_size.scale_xy(Vector2I::new(3, 1)) + } else { + main_framebuffer_size }; - /* - self.device.clear(&RenderTarget::Framebuffer(self.postprocess_source_framebuffer - .as_ref() - .unwrap()), - RectI::new(Vector2I::default(), source_framebuffer_size), - &ClearParams { - color: Some(ColorF::transparent_black()), - ..ClearParams::default() - }); - */ + let framebuffer = self.framebuffer_cache.create_framebuffer(&mut self.device, + TextureFormat::RGBA8, + framebuffer_size); + self.layer_framebuffer_stack.push(LayerFramebufferInfo { + framebuffer, + effects, + must_preserve_contents: false, + }); + } + + fn pop_layer(&mut self) { + let layer_framebuffer_info = self.layer_framebuffer_stack + .pop() + .expect("Where's the layer?"); + + let clear_color = self.clear_color_for_draw_operation(); + + let postprocess_source_framebuffer = &layer_framebuffer_info.framebuffer; + let source_texture = self + .device + .framebuffer_texture(postprocess_source_framebuffer); + let source_texture_size = self.device.texture_size(source_texture); + let main_viewport = self.main_viewport(); + + let mut uniforms = vec![ + (&self.postprocess_program.framebuffer_size_uniform, + UniformData::Vec2(main_viewport.size().to_f32().0)), + (&self.postprocess_program.source_uniform, UniformData::TextureUnit(0)), + (&self.postprocess_program.source_size_uniform, + UniformData::Vec2(source_texture_size.0.to_f32x2())), + (&self.postprocess_program.gamma_lut_uniform, UniformData::TextureUnit(1)), + (&self.postprocess_program.fg_color_uniform, + UniformData::Vec4(layer_framebuffer_info.effects.fg_color.0)), + (&self.postprocess_program.bg_color_uniform, + UniformData::Vec4(layer_framebuffer_info.effects.bg_color.0)), + (&self.postprocess_program.gamma_correction_enabled_uniform, + UniformData::Int(layer_framebuffer_info.effects.gamma_correction as i32)), + ]; + + match layer_framebuffer_info.effects.defringing_kernel { + Some(ref kernel) => { + uniforms.push((&self.postprocess_program.kernel_uniform, + UniformData::Vec4(F32x4::from_slice(&kernel.0)))); + } + None => { + uniforms.push((&self.postprocess_program.kernel_uniform, + UniformData::Vec4(F32x4::default()))); + } + } + + self.device.draw_elements(6, &RenderState { + target: &self.draw_render_target(), + program: &self.postprocess_program.program, + vertex_array: &self.postprocess_vertex_array.vertex_array, + primitive: Primitive::Triangles, + textures: &[&source_texture, &self.gamma_lut_texture], + uniforms: &uniforms, + viewport: main_viewport, + options: RenderOptions { + clear_ops: ClearOps { color: clear_color, ..ClearOps::default() }, + ..RenderOptions::default() + }, + }); + + self.preserve_draw_framebuffer(); + + self.framebuffer_cache.release_framebuffer(layer_framebuffer_info.framebuffer); } fn stencil_state(&self) -> Option { @@ -881,16 +848,17 @@ where } fn clear_color_for_draw_operation(&mut self) -> Option { - let postprocessing_needed = self.postprocess_options.is_some(); - let flag = if postprocessing_needed { - FramebufferFlags::MUST_PRESERVE_POSTPROCESS_FRAMEBUFFER_CONTENTS - } else { - FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS + let must_preserve_contents = match self.layer_framebuffer_stack.last() { + Some(ref layer_framebuffer_info) => layer_framebuffer_info.must_preserve_contents, + None => { + self.framebuffer_flags + .contains(FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS) + } }; - if self.framebuffer_flags.contains(flag) { + if must_preserve_contents { None - } else if !postprocessing_needed { + } else if self.layer_framebuffer_stack.is_empty() { self.options.background_color } else { Some(ColorF::default()) @@ -898,21 +866,24 @@ where } fn preserve_draw_framebuffer(&mut self) { - let flag = if self.postprocess_options.is_some() { - FramebufferFlags::MUST_PRESERVE_POSTPROCESS_FRAMEBUFFER_CONTENTS - } else { - FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS - }; - self.framebuffer_flags.insert(flag); + match self.layer_framebuffer_stack.last_mut() { + Some(ref mut layer_framebuffer_info) => { + layer_framebuffer_info.must_preserve_contents = true; + } + None => { + self.framebuffer_flags + .insert(FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS); + } + } } pub fn draw_viewport(&self) -> RectI { - let main_viewport = self.main_viewport(); - match self.postprocess_options { - Some(PostprocessOptions { defringing_kernel: Some(_), .. }) => { - RectI::new(Vector2I::default(), main_viewport.size().scale_xy(Vector2I::new(3, 1))) + match self.layer_framebuffer_stack.last() { + Some(ref layer_framebuffer_info) => { + let texture = self.device.framebuffer_texture(&layer_framebuffer_info.framebuffer); + RectI::new(Vector2I::default(), self.device.texture_size(texture)) } - _ => main_viewport, + None => self.main_viewport(), } } @@ -953,7 +924,8 @@ where } } -#[derive(Clone, Copy, Default)] +// FIXME(pcwalton): Rename to `Effects` and move to `pathfinder_content`, perhaps? +#[derive(Clone, Copy, Default, Debug)] pub struct PostprocessOptions { pub fg_color: ColorF, pub bg_color: ColorF, @@ -1035,7 +1007,46 @@ bitflags! { struct FramebufferFlags: u8 { const MUST_PRESERVE_FILL_FRAMEBUFFER_CONTENTS = 0x01; const MUST_PRESERVE_MASK_FRAMEBUFFER_CONTENTS = 0x02; - const MUST_PRESERVE_POSTPROCESS_FRAMEBUFFER_CONTENTS = 0x04; - const MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS = 0x08; + const MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS = 0x04; } } + +struct FramebufferCache where D: Device { + framebuffers: Vec, +} + +impl FramebufferCache where D: Device { + fn new() -> FramebufferCache { + FramebufferCache { framebuffers: vec![] } + } + + fn create_framebuffer(&mut self, device: &mut D, format: TextureFormat, size: Vector2I) + -> D::Framebuffer { + for index in 0..self.framebuffers.len() { + { + let texture = device.framebuffer_texture(&self.framebuffers[index]); + if device.texture_size(texture) != size || + device.texture_format(texture) != format { + continue; + } + } + return self.framebuffers.remove(index); + } + + let texture = device.create_texture(format, size); + device.create_framebuffer(texture) + } + + fn release_framebuffer(&mut self, framebuffer: D::Framebuffer) { + if self.framebuffers.len() == FRAMEBUFFER_CACHE_SIZE { + self.framebuffers.pop(); + } + self.framebuffers.insert(0, framebuffer); + } +} + +struct LayerFramebufferInfo where D: Device { + framebuffer: D::Framebuffer, + effects: PostprocessOptions, + must_preserve_contents: bool, +} diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index 01e81a56..7318be65 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -10,6 +10,7 @@ //! Packed data ready to be sent to the GPU. +use crate::gpu::renderer::PostprocessOptions; use crate::options::BoundingQuad; use pathfinder_color::ColorU; use pathfinder_content::fill::FillRule; @@ -24,6 +25,8 @@ pub enum RenderCommand { AddFills(Vec), FlushFills, RenderMaskTiles { tiles: Vec, fill_rule: FillRule }, + PushLayer { effects: PostprocessOptions }, + PopLayer, DrawAlphaTiles(Vec), DrawSolidTiles(Vec), Finish { build_time: Duration }, @@ -125,6 +128,8 @@ impl Debug for RenderCommand { RenderCommand::RenderMaskTiles { ref tiles, fill_rule } => { write!(formatter, "RenderMaskTiles(x{}, {:?})", tiles.len(), fill_rule) } + RenderCommand::PushLayer { .. } => write!(formatter, "PushLayer"), + RenderCommand::PopLayer => write!(formatter, "PopLayer"), RenderCommand::DrawAlphaTiles(ref tiles) => { write!(formatter, "DrawAlphaTiles(x{})", tiles.len()) } diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index ea01dea9..a178d003 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -12,10 +12,10 @@ use crate::builder::SceneBuilder; use crate::concurrent::executor::Executor; +use crate::gpu::renderer::PostprocessOptions; use crate::options::{BuildOptions, PreparedBuildOptions}; use crate::options::{PreparedRenderTransform, RenderCommandListener}; use crate::paint::{Paint, PaintId, PaintInfo, Palette}; -use pathfinder_color::ColorU; use pathfinder_content::fill::FillRule; use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::rect::RectF; @@ -24,6 +24,7 @@ use pathfinder_content::outline::Outline; #[derive(Clone)] pub struct Scene { + pub(crate) display_list: Vec, pub(crate) paths: Vec, pub(crate) clip_paths: Vec, palette: Palette, @@ -35,6 +36,7 @@ impl Scene { #[inline] pub fn new() -> Scene { Scene { + display_list: vec![], paths: vec![], clip_paths: vec![], palette: Palette::new(), @@ -46,6 +48,19 @@ impl Scene { pub fn push_path(&mut self, path: DrawPath) { self.bounds = self.bounds.union_rect(path.outline.bounds()); self.paths.push(path); + + let new_path_count = self.paths.len() as u32; + if let Some(DisplayItem::DrawPaths { + start_index: _, + ref mut end_index + }) = self.display_list.last_mut() { + *end_index = new_path_count; + } else { + self.display_list.push(DisplayItem::DrawPaths { + start_index: new_path_count - 1, + end_index: new_path_count, + }); + } } pub fn push_clip_path(&mut self, clip_path: ClipPath) -> ClipPathId { @@ -55,6 +70,14 @@ impl Scene { clip_path_id } + pub fn push_layer(&mut self, effects: PostprocessOptions) { + self.display_list.push(DisplayItem::PushLayer { effects }); + } + + pub fn pop_layer(&mut self) { + self.display_list.push(DisplayItem::PopLayer); + } + #[inline] pub fn build_paint_info(&self) -> PaintInfo { self.palette.build_paint_info(self.view_box.size().to_i32()) @@ -142,27 +165,6 @@ impl Scene { outline } - pub fn monochrome_color(&self) -> Option { - if self.paths.is_empty() { - return None; - } - - let first_paint_id = self.paths[0].paint; - if self - .paths - .iter() - .skip(1) - .any(|path_object| path_object.paint != first_paint_id) { - return None; - } - - match self.palette.paints[first_paint_id.0 as usize] { - Paint::Color(color) => Some(color), - Paint::Gradient(_) => None, - Paint::Pattern(_) => None, - } - } - #[inline] pub(crate) fn effective_view_box(&self, render_options: &PreparedBuildOptions) -> RectF { if render_options.subpixel_aa_enabled { @@ -229,6 +231,13 @@ pub struct ClipPath { #[derive(Clone, Copy, Debug)] pub struct ClipPathId(pub u32); +#[derive(Clone, Debug)] +pub enum DisplayItem { + DrawPaths { start_index: u32, end_index: u32 }, + PushLayer { effects: PostprocessOptions }, + PopLayer, +} + impl DrawPath { #[inline] pub fn new(outline: Outline, diff --git a/renderer/src/tiles.rs b/renderer/src/tiles.rs index 6ef7ca7c..083bc0ea 100644 --- a/renderer/src/tiles.rs +++ b/renderer/src/tiles.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::builder::{BuiltPath, ObjectBuilder, SceneBuilder}; +use crate::builder::{BuiltPath, ObjectBuilder, SceneBuilder, SolidTile}; use crate::gpu_data::TileObjectPrimitive; use crate::paint::PaintMetadata; use pathfinder_content::fill::FillRule; @@ -174,10 +174,9 @@ impl<'a> Tiler<'a> { (FillRule::EvenOdd, _) => {} } - // Next, if this is a solid tile, just poke it into the Z-buffer. We don't need - // to do anything else here. + // Next, if this is a solid tile, record that fact and stop here. if paint_metadata.is_opaque { - self.scene_builder.z_buffer.update(tile_coords, self.object_index); + self.object_builder.built_path.solid_tiles.push(SolidTile::new(tile_coords)); continue; } } diff --git a/renderer/src/z_buffer.rs b/renderer/src/z_buffer.rs index dab5e8f0..c4e6472a 100644 --- a/renderer/src/z_buffer.rs +++ b/renderer/src/z_buffer.rs @@ -10,6 +10,7 @@ //! Software occlusion culling. +use crate::builder::SolidTile; use crate::gpu_data::SolidTileVertex; use crate::paint::PaintMetadata; use crate::scene::DrawPath; @@ -17,62 +18,43 @@ use crate::tile_map::DenseTileMap; use crate::tiles; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::vector::Vector2I; -use std::ops::Range; -use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; -pub struct ZBuffer { - buffer: DenseTileMap, +pub(crate) struct ZBuffer { + buffer: DenseTileMap, } impl ZBuffer { - pub fn new(view_box: RectF) -> ZBuffer { + pub(crate) fn new(view_box: RectF) -> ZBuffer { let tile_rect = tiles::round_rect_out_to_tile_bounds(view_box); ZBuffer { - buffer: DenseTileMap::from_builder(|_| AtomicUsize::new(0), tile_rect), + buffer: DenseTileMap::from_builder(|_| 0, tile_rect), } } - pub fn test(&self, coords: Vector2I, object_index: u32) -> bool { + pub(crate) fn test(&self, coords: Vector2I, object_index: u32) -> bool { let tile_index = self.buffer.coords_to_index_unchecked(coords); - let existing_depth = self.buffer.data[tile_index as usize].load(AtomicOrdering::SeqCst); - existing_depth < object_index as usize + 1 + self.buffer.data[tile_index as usize] < object_index + 1 } - pub fn update(&self, coords: Vector2I, object_index: u16) { - let tile_index = self.buffer.coords_to_index_unchecked(coords); - let mut old_depth = self.buffer.data[tile_index].load(AtomicOrdering::SeqCst); - let new_depth = (object_index + 1) as usize; - while old_depth < new_depth { - let prev_depth = self.buffer.data[tile_index].compare_and_swap( - old_depth, - new_depth, - AtomicOrdering::SeqCst, - ); - if prev_depth == old_depth { - // Successfully written. - return; - } - old_depth = prev_depth; + pub(crate) fn update(&mut self, solid_tiles: &[SolidTile], object_index: u32) { + for solid_tile in solid_tiles { + let tile_index = self.buffer.coords_to_index_unchecked(solid_tile.coords); + let z_dest = &mut self.buffer.data[tile_index as usize]; + *z_dest = u32::max(*z_dest, object_index + 1); } } - pub fn build_solid_tiles(&self, - paths: &[DrawPath], - paint_metadata: &[PaintMetadata], - object_range: Range) - -> Vec { + pub(crate) fn build_solid_tiles(&self, paths: &[DrawPath], paint_metadata: &[PaintMetadata]) + -> Vec { let mut solid_tiles = vec![]; for tile_index in 0..self.buffer.data.len() { - let depth = self.buffer.data[tile_index].load(AtomicOrdering::Relaxed); + let depth = self.buffer.data[tile_index]; if depth == 0 { continue; } let tile_coords = self.buffer.index_to_coords(tile_index); let object_index = (depth - 1) as u32; - if object_index < object_range.start || object_index >= object_range.end { - continue; - } let paint_id = paths[object_index as usize].paint(); let paint_metadata = &paint_metadata[paint_id.0 as usize]; diff --git a/svg/src/lib.rs b/svg/src/lib.rs index 9419f977..56c4d3d2 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -27,7 +27,6 @@ use pathfinder_geometry::vector::Vector2F; use pathfinder_renderer::paint::Paint; use pathfinder_renderer::scene::{ClipPath, ClipPathId, DrawPath, Scene}; use std::fmt::{Display, Formatter, Result as FormatResult}; -use std::mem; use usvg::{Color as SvgColor, FillRule as UsvgFillRule, LineCap as UsvgLineCap}; use usvg::{LineJoin as UsvgLineJoin, Node, NodeExt, NodeKind, Opacity, Paint as UsvgPaint}; use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform}; @@ -65,11 +64,17 @@ bitflags! { impl BuiltSVG { // TODO(pcwalton): Allow a global transform to be set. - pub fn from_tree(tree: Tree) -> BuiltSVG { + #[inline] + pub fn from_tree(tree: &Tree) -> BuiltSVG { + BuiltSVG::from_tree_and_scene(tree, Scene::new()) + } + + // TODO(pcwalton): Allow a global transform to be set. + pub fn from_tree_and_scene(tree: &Tree, scene: Scene) -> BuiltSVG { // TODO(pcwalton): Maybe have a `SVGBuilder` type to hold the clip path IDs and other // transient data separate from `BuiltSVG`? let mut built_svg = BuiltSVG { - scene: Scene::new(), + scene, result_flags: BuildResultFlags::empty(), clip_paths: HashMap::new(), }; @@ -83,11 +88,7 @@ impl BuiltSVG { } } _ => unreachable!(), - }; - - // FIXME(pcwalton): This is needed to avoid stack exhaustion in debug builds when - // recursively dropping reference counts on very large SVGs. :( - mem::forget(tree); + } built_svg }