Transition the existing postprocessing system to a layers system.

This is preparatory work for composite ops and blurs.

Closes #261.
This commit is contained in:
Patrick Walton 2020-02-19 17:44:41 -08:00
parent 25f9346160
commit d9e994e46d
14 changed files with 524 additions and 361 deletions

View File

@ -142,6 +142,11 @@ impl ColorF {
ColorF::default() ColorF::default()
} }
#[inline]
pub fn black() -> ColorF {
ColorF(F32x4::new(0.0, 0.0, 0.0, 1.0))
}
#[inline] #[inline]
pub fn white() -> ColorF { pub fn white() -> ColorF {
ColorF(F32x4::splat(1.0)) ColorF(F32x4::splat(1.0))

View File

@ -22,7 +22,6 @@ use crate::device::{GroundProgram, GroundVertexArray};
use crate::ui::{DemoUIModel, DemoUIPresenter, ScreenshotInfo, ScreenshotType, UIAction}; use crate::ui::{DemoUIModel, DemoUIPresenter, ScreenshotInfo, ScreenshotType, UIAction};
use crate::window::{Event, Keycode, SVGPath, Window, WindowSize}; use crate::window::{Event, Keycode, SVGPath, Window, WindowSize};
use clap::{App, Arg}; use clap::{App, Arg};
use pathfinder_color::ColorU;
use pathfinder_export::{Export, FileFormat}; use pathfinder_export::{Export, FileFormat};
use pathfinder_geometry::rect::RectF; use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::transform2d::Transform2F;
@ -32,9 +31,9 @@ use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::Device; use pathfinder_gpu::Device;
use pathfinder_renderer::concurrent::scene_proxy::{RenderCommandStream, SceneProxy}; use pathfinder_renderer::concurrent::scene_proxy::{RenderCommandStream, SceneProxy};
use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; 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::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_renderer::scene::Scene;
use pathfinder_svg::BuiltSVG; use pathfinder_svg::BuiltSVG;
use pathfinder_ui::{MousePosition, UIEvent}; use pathfinder_ui::{MousePosition, UIEvent};
@ -63,25 +62,6 @@ const CAMERA_ZOOM_AMOUNT_2D: f32 = 0.1;
// Half of the eye separation distance. // Half of the eye separation distance.
const DEFAULT_EYE_OFFSET: f32 = 0.025; 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 APPROX_FONT_SIZE: f32 = 16.0;
const MESSAGE_TIMEOUT_SECS: u64 = 5; const MESSAGE_TIMEOUT_SECS: u64 = 5;
@ -101,6 +81,7 @@ pub struct DemoApp<W> where W: Window {
window_size: WindowSize, window_size: WindowSize,
svg_tree: Tree,
scene_metadata: SceneMetadata, scene_metadata: SceneMetadata,
render_transform: Option<RenderTransform>, render_transform: Option<RenderTransform>,
render_command_stream: Option<RenderCommandStream>, render_command_stream: Option<RenderCommandStream>,
@ -151,7 +132,12 @@ impl<W> DemoApp<W> where W: Window {
// Set up the executor. // Set up the executor.
let executor = DemoExecutor::new(options.jobs); 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 message = get_svg_building_message(&built_svg);
let viewport = window.viewport(options.mode.view(0)); let viewport = window.viewport(options.mode.view(0));
@ -159,12 +145,9 @@ impl<W> DemoApp<W> where W: Window {
viewport, viewport,
window_size: window_size.device_size(), 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 renderer = Renderer::new(device, resources, dest_framebuffer, render_options);
let scene_metadata = SceneMetadata::new_clipping_view_box(&mut built_svg.scene, let scene_metadata = SceneMetadata::new_clipping_view_box(&mut built_svg.scene,
viewport.size()); viewport.size());
let camera = Camera::new(options.mode, scene_metadata.view_box, viewport.size()); let camera = Camera::new(options.mode, scene_metadata.view_box, viewport.size());
@ -177,8 +160,6 @@ impl<W> DemoApp<W> where W: Window {
&renderer.quad_vertex_positions_buffer(), &renderer.quad_vertex_positions_buffer(),
&renderer.quad_vertex_indices_buffer()); &renderer.quad_vertex_indices_buffer());
let mut ui_model = DemoUIModel::new(&options);
let mut message_epoch = 0; let mut message_epoch = 0;
emit_message::<W>( emit_message::<W>(
&mut ui_model, &mut ui_model,
@ -196,6 +177,7 @@ impl<W> DemoApp<W> where W: Window {
window_size, window_size,
svg_tree,
scene_metadata, scene_metadata,
render_transform: None, render_transform: None,
render_command_stream: None, render_command_stream: None,
@ -445,7 +427,11 @@ impl<W> DemoApp<W> where W: Window {
} }
Event::OpenSVG(ref svg_path) => { 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); self.ui_model.message = get_svg_building_message(&built_svg);
let viewport_size = self.window.viewport(self.ui_model.mode.view(0)).size(); let viewport_size = self.window.viewport(self.ui_model.mode.view(0)).size();
@ -456,6 +442,7 @@ impl<W> DemoApp<W> where W: Window {
viewport_size); viewport_size);
self.scene_proxy.replace_scene(built_svg.scene); self.scene_proxy.replace_scene(built_svg.scene);
self.svg_tree = svg_tree;
self.dirty = true; self.dirty = true;
} }
@ -499,8 +486,6 @@ impl<W> DemoApp<W> where W: Window {
.to_f32() .to_f32()
.scale(self.window_size.backing_scale_factor); .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; let mut ui_action = UIAction::None;
if self.options.ui == UIVisibility::All { if self.options.ui == UIVisibility::All {
self.ui_presenter.update( self.ui_presenter.update(
@ -599,6 +584,15 @@ impl<W> DemoApp<W> where W: Window {
match ui_action { match ui_action {
UIAction::None => {} UIAction::None => {}
UIAction::ModelChanged => self.dirty = true, 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) => { UIAction::TakeScreenshot(ref info) => {
self.pending_screenshot_info = Some((*info).clone()); self.pending_screenshot_info = Some((*info).clone());
self.dirty = true; self.dirty = true;
@ -636,14 +630,6 @@ impl<W> DemoApp<W> 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)] #[derive(Clone)]
@ -756,7 +742,10 @@ pub enum UIVisibility {
All, All,
} }
fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &SVGPath) -> BuiltSVG { fn load_scene(resource_loader: &dyn ResourceLoader,
input_path: &SVGPath,
effects: Option<PostprocessOptions>)
-> (BuiltSVG, Tree) {
let mut data; let mut data;
match *input_path { match *input_path {
SVGPath::Default => data = resource_loader.slurp(DEFAULT_SVG_VIRTUAL_PATH).unwrap(), 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<PostprocessOptions>) -> 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 { fn center_of_window(window_size: &WindowSize) -> Vector2F {
@ -842,7 +848,6 @@ impl BackgroundColor {
struct SceneMetadata { struct SceneMetadata {
view_box: RectF, view_box: RectF,
monochrome_color: Option<ColorU>,
} }
impl SceneMetadata { impl SceneMetadata {
@ -850,8 +855,25 @@ impl SceneMetadata {
// Can we simplify this? // Can we simplify this?
fn new_clipping_view_box(scene: &mut Scene, viewport_size: Vector2I) -> SceneMetadata { fn new_clipping_view_box(scene: &mut Scene, viewport_size: Vector2I) -> SceneMetadata {
let view_box = scene.view_box(); let view_box = scene.view_box();
let monochrome_color = scene.monochrome_color();
scene.set_view_box(RectF::new(Vector2F::default(), viewport_size.to_f32())); 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<PostprocessOptions> {
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
},
})
}

View File

@ -21,10 +21,8 @@ use pathfinder_geometry::rect::RectI;
use pathfinder_geometry::transform3d::Transform4F; use pathfinder_geometry::transform3d::Transform4F;
use pathfinder_geometry::vector::{Vector2I, Vector4F}; use pathfinder_geometry::vector::{Vector2I, Vector4F};
use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions};
use pathfinder_renderer::gpu::renderer::PostprocessOptions;
use pathfinder_renderer::gpu_data::RenderCommand; use pathfinder_renderer::gpu_data::RenderCommand;
use pathfinder_renderer::options::RenderTransform; use pathfinder_renderer::options::RenderTransform;
use pathfinder_renderer::post::DEFRINGING_KERNEL_CORE_GRAPHICS;
use std::path::PathBuf; use std::path::PathBuf;
const GROUND_SOLID_COLOR: ColorU = ColorU { const GROUND_SOLID_COLOR: ColorU = ColorU {
@ -88,7 +86,7 @@ impl<W> DemoApp<W> where W: Window {
// Clear to the appropriate color. // Clear to the appropriate color.
let clear_color = match mode { 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::ThreeD => None,
Mode::VR => Some(ColorF::transparent_black()), Mode::VR => Some(ColorF::transparent_black()),
}; };
@ -221,7 +219,7 @@ impl<W> DemoApp<W> where W: Window {
// Don't clear the first scene after drawing it. // Don't clear the first scene after drawing it.
let clear_color = if render_scene_index == 0 { let clear_color = if render_scene_index == 0 {
Some(self.background_color().to_f32()) Some(self.ui_model.background_color().to_f32())
} else { } else {
None None
}; };
@ -251,23 +249,6 @@ impl<W> DemoApp<W> where W: Window {
} }
fn render_vector_scene(&mut self) { 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 { if self.ui_model.mode == Mode::TwoD {
self.renderer.disable_depth(); self.renderer.disable_depth();
} else { } else {

View File

@ -11,8 +11,9 @@
use crate::camera::Mode; use crate::camera::Mode;
use crate::window::Window; use crate::window::Window;
use crate::{BackgroundColor, Options}; use crate::{BackgroundColor, Options};
use pathfinder_geometry::vector::Vector2I; use pathfinder_color::ColorU;
use pathfinder_geometry::rect::RectI; use pathfinder_geometry::rect::RectI;
use pathfinder_geometry::vector::Vector2I;
use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::Device; use pathfinder_gpu::Device;
use pathfinder_renderer::gpu::debug::DebugUIPresenter; 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_WIDTH: i32 = SLIDER_WIDTH + PADDING * 2;
const ROTATE_PANEL_HEIGHT: i32 = PADDING * 2 + SLIDER_HEIGHT; 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 EFFECTS_PNG_NAME: &'static str = "demo-effects";
static OPEN_PNG_NAME: &'static str = "demo-open"; static OPEN_PNG_NAME: &'static str = "demo-open";
static ROTATE_PNG_NAME: &'static str = "demo-rotate"; static ROTATE_PNG_NAME: &'static str = "demo-rotate";
@ -74,6 +79,22 @@ impl DemoUIModel {
fn rotation(&self) -> f32 { fn rotation(&self) -> f32 {
(self.rotation as f32 / SLIDER_WIDTH as f32 * 2.0 - 1.0) * PI (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<D> pub struct DemoUIPresenter<D>
@ -93,8 +114,6 @@ where
background_panel_visible: bool, background_panel_visible: bool,
screenshot_panel_visible: bool, screenshot_panel_visible: bool,
rotate_panel_visible: bool, rotate_panel_visible: bool,
show_text_effects: bool,
} }
impl<D> DemoUIPresenter<D> impl<D> DemoUIPresenter<D>
@ -126,15 +145,9 @@ where
background_panel_visible: false, background_panel_visible: false,
screenshot_panel_visible: false, screenshot_panel_visible: false,
rotate_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<W>( pub fn update<W>(
&mut self, &mut self,
device: &D, device: &D,
@ -156,22 +169,18 @@ where
let button_size = Vector2I::new(BUTTON_WIDTH, BUTTON_HEIGHT); let button_size = Vector2I::new(BUTTON_WIDTH, BUTTON_HEIGHT);
// Draw text effects button. // Draw effects button.
if self.show_text_effects { if debug_ui_presenter.ui_presenter.draw_button(device, position, &self.effects_texture) {
if debug_ui_presenter.ui_presenter.draw_button(device, self.effects_panel_visible = !self.effects_panel_visible;
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);
} }
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. // Draw open button.
if debug_ui_presenter.ui_presenter.draw_button(device, position, &self.open_texture) { 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); position += Vector2I::new(button_size.x() + PADDING, 0);
// Draw effects panel, if necessary. // 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. // Draw rotate and zoom buttons, if applicable.
if model.mode != Mode::TwoD { if model.mode != Mode::TwoD {
@ -324,7 +333,8 @@ where
fn draw_effects_panel(&mut self, fn draw_effects_panel(&mut self,
device: &D, device: &D,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
model: &mut DemoUIModel) { model: &mut DemoUIModel,
action: &mut UIAction) {
if !self.effects_panel_visible { if !self.effects_panel_visible {
return; return;
} }
@ -340,30 +350,30 @@ where
WINDOW_COLOR, WINDOW_COLOR,
); );
model.gamma_correction_effect_enabled = self.draw_effects_switch( self.draw_effects_switch(
device, device,
action,
debug_ui_presenter, debug_ui_presenter,
"Gamma Correction", "Gamma Correction",
0, 0,
effects_panel_y, effects_panel_y,
model.gamma_correction_effect_enabled, &mut model.gamma_correction_effect_enabled);
); self.draw_effects_switch(
model.stem_darkening_effect_enabled = self.draw_effects_switch(
device, device,
action,
debug_ui_presenter, debug_ui_presenter,
"Stem Darkening", "Stem Darkening",
1, 1,
effects_panel_y, effects_panel_y,
model.stem_darkening_effect_enabled, &mut model.stem_darkening_effect_enabled);
); self.draw_effects_switch(
model.subpixel_aa_effect_enabled = self.draw_effects_switch(
device, device,
action,
debug_ui_presenter, debug_ui_presenter,
"Subpixel AA", "Subpixel AA",
2, 2,
effects_panel_y, effects_panel_y,
model.subpixel_aa_effect_enabled, &mut model.subpixel_aa_effect_enabled);
);
} }
fn draw_screenshot_panel<W>( fn draw_screenshot_panel<W>(
@ -592,12 +602,12 @@ where
fn draw_effects_switch( fn draw_effects_switch(
&self, &self,
device: &D, device: &D,
action: &mut UIAction,
debug_ui_presenter: &mut DebugUIPresenter<D>, debug_ui_presenter: &mut DebugUIPresenter<D>,
text: &str, text: &str,
index: i32, index: i32,
window_y: i32, window_y: i32,
value: bool, value: &mut bool) {
) -> bool {
let text_x = PADDING * 2; let text_x = PADDING * 2;
let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index; let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index;
debug_ui_presenter debug_ui_presenter
@ -608,10 +618,16 @@ where
let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (switch_width + PADDING); let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (switch_width + PADDING);
let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index; let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index;
let switch_position = Vector2I::new(switch_x, switch_y); let switch_position = Vector2I::new(switch_x, switch_y);
debug_ui_presenter
.ui_presenter let new_value =
.draw_text_switch(device, switch_position, &["Off", "On"], value as u8) debug_ui_presenter
!= 0 .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 { pub enum UIAction {
None, None,
ModelChanged, ModelChanged,
EffectsChanged,
TakeScreenshot(ScreenshotInfo), TakeScreenshot(ScreenshotInfo),
ZoomIn, ZoomIn,
ZoomActualSize, ZoomActualSize,

View File

@ -478,6 +478,11 @@ impl Device for GLDevice {
&framebuffer.texture &framebuffer.texture
} }
#[inline]
fn texture_format(&self, texture: &Self::Texture) -> TextureFormat {
texture.format
}
#[inline] #[inline]
fn texture_size(&self, texture: &Self::Texture) -> Vector2I { fn texture_size(&self, texture: &Self::Texture) -> Vector2I {
texture.size texture.size

View File

@ -70,6 +70,7 @@ pub trait Device: Sized {
mode: BufferUploadMode, mode: BufferUploadMode,
); );
fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture; 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 texture_size(&self, texture: &Self::Texture) -> Vector2I;
fn upload_to_texture(&self, texture: &Self::Texture, rect: RectI, data: TextureDataRef); fn upload_to_texture(&self, texture: &Self::Texture, rect: RectI, data: TextureDataRef);
fn read_pixels(&self, target: &RenderTarget<Self>, viewport: RectI) fn read_pixels(&self, target: &RenderTarget<Self>, viewport: RectI)

View File

@ -437,6 +437,17 @@ impl Device for MetalDevice {
&framebuffer.0 &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 { fn texture_size(&self, texture: &MetalTexture) -> Vector2I {
Vector2I::new(texture.texture.width() as i32, texture.texture.height() as i32) Vector2I::new(texture.texture.width() as i32, texture.texture.height() as i32)
} }

View File

@ -11,12 +11,12 @@
//! Packs data onto the GPU. //! Packs data onto the GPU.
use crate::concurrent::executor::Executor; 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::{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::options::{PreparedBuildOptions, RenderCommandListener};
use crate::paint::{PaintInfo, PaintMetadata}; use crate::paint::{PaintInfo, PaintMetadata};
use crate::scene::Scene; use crate::scene::{DisplayItem, Scene};
use crate::tile_map::DenseTileMap; use crate::tile_map::DenseTileMap;
use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH, Tiler, TilingPathInfo}; use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH, Tiler, TilingPathInfo};
use crate::z_buffer::ZBuffer; use crate::z_buffer::ZBuffer;
@ -37,7 +37,6 @@ pub(crate) struct SceneBuilder<'a> {
next_alpha_tile_index: AtomicUsize, next_alpha_tile_index: AtomicUsize,
next_mask_tile_index: AtomicUsize, next_mask_tile_index: AtomicUsize,
pub(crate) z_buffer: ZBuffer,
pub(crate) listener: Box<dyn RenderCommandListener>, pub(crate) listener: Box<dyn RenderCommandListener>,
} }
@ -52,17 +51,22 @@ pub(crate) struct ObjectBuilder {
pub(crate) struct BuiltPath { pub(crate) struct BuiltPath {
pub mask_tiles: Vec<MaskTile>, pub mask_tiles: Vec<MaskTile>,
pub alpha_tiles: Vec<AlphaTile>, pub alpha_tiles: Vec<AlphaTile>,
pub solid_tiles: Vec<SolidTile>,
pub tiles: DenseTileMap<TileObjectPrimitive>, pub tiles: DenseTileMap<TileObjectPrimitive>,
pub fill_rule: FillRule, pub fill_rule: FillRule,
} }
#[derive(Clone, Copy, Debug)]
pub(crate) struct SolidTile {
pub(crate) coords: Vector2I,
}
impl<'a> SceneBuilder<'a> { impl<'a> SceneBuilder<'a> {
pub(crate) fn new( pub(crate) fn new(
scene: &'a Scene, scene: &'a Scene,
built_options: &'a PreparedBuildOptions, built_options: &'a PreparedBuildOptions,
listener: Box<dyn RenderCommandListener>, listener: Box<dyn RenderCommandListener>,
) -> SceneBuilder<'a> { ) -> SceneBuilder<'a> {
let effective_view_box = scene.effective_view_box(built_options);
SceneBuilder { SceneBuilder {
scene, scene,
built_options, built_options,
@ -70,7 +74,6 @@ impl<'a> SceneBuilder<'a> {
next_alpha_tile_index: AtomicUsize::new(0), next_alpha_tile_index: AtomicUsize::new(0),
next_mask_tile_index: AtomicUsize::new(0), next_mask_tile_index: AtomicUsize::new(0),
z_buffer: ZBuffer::new(effective_view_box),
listener, listener,
} }
} }
@ -167,26 +170,84 @@ impl<'a> SceneBuilder<'a> {
tiler.object_builder.built_path tiler.object_builder.built_path
} }
fn cull_tiles(&self, built_clip_paths: Vec<BuiltPath>, built_draw_paths: Vec<BuiltPath>) fn cull_tiles(&self,
paint_metadata: &[PaintMetadata],
built_clip_paths: Vec<BuiltPath>,
built_draw_paths: Vec<BuiltPath>)
-> CulledTiles { -> CulledTiles {
let mut culled_tiles = CulledTiles { let mut culled_tiles = CulledTiles {
mask_winding_tiles: vec![], mask_winding_tiles: vec![],
mask_evenodd_tiles: vec![], mask_evenodd_tiles: vec![],
alpha_tiles: vec![], display_list: vec![],
}; };
for built_clip_path in built_clip_paths { for built_clip_path in built_clip_paths {
culled_tiles.push_mask_tiles(&built_clip_path); culled_tiles.push_mask_tiles(&built_clip_path);
} }
for built_draw_path in built_draw_paths { let mut remaining_layer_z_buffers = self.build_solid_tiles(&built_draw_paths);
culled_tiles.push_mask_tiles(&built_draw_path); remaining_layer_z_buffers.reverse();
for alpha_tile in built_draw_path.alpha_tiles { // Process first Z-buffer.
let alpha_tile_coords = alpha_tile.upper_left.tile_position(); let first_z_buffer = remaining_layer_z_buffers.pop().unwrap();
if self.z_buffer.test(alpha_tile_coords, let first_solid_tiles = first_z_buffer.build_solid_tiles(&self.scene.paths,
alpha_tile.upper_left.object_index as u32) { paint_metadata);
culled_tiles.alpha_tiles.push(alpha_tile); 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 culled_tiles
} }
fn pack_tiles(&mut self, paint_metadata: &[PaintMetadata], culled_tiles: CulledTiles) { fn build_solid_tiles(&self, built_draw_paths: &[BuiltPath]) -> Vec<ZBuffer> {
let path_count = self.scene.paths.len() as u32; let effective_view_box = self.scene.effective_view_box(self.built_options);
let solid_tiles = self.z_buffer.build_solid_tiles(&self.scene.paths, let mut z_buffers = vec![ZBuffer::new(effective_view_box)];
paint_metadata, let mut z_buffer_index_stack = vec![0];
0..path_count);
// 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() { if !culled_tiles.mask_winding_tiles.is_empty() {
self.listener.send(RenderCommand::RenderMaskTiles { self.listener.send(RenderCommand::RenderMaskTiles {
tiles: culled_tiles.mask_winding_tiles, tiles: culled_tiles.mask_winding_tiles,
@ -213,11 +299,19 @@ impl<'a> SceneBuilder<'a> {
}); });
} }
if !solid_tiles.is_empty() { for display_item in culled_tiles.display_list {
self.listener.send(RenderCommand::DrawSolidTiles(solid_tiles)); match display_item {
} CulledDisplayItem::DrawSolidTiles(tiles) => {
if !culled_tiles.alpha_tiles.is_empty() { self.listener.send(RenderCommand::DrawSolidTiles(tiles))
self.listener.send(RenderCommand::DrawAlphaTiles(culled_tiles.alpha_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<BuiltPath>, built_clip_paths: Vec<BuiltPath>,
built_draw_paths: Vec<BuiltPath>) { built_draw_paths: Vec<BuiltPath>) {
self.listener.send(RenderCommand::FlushFills); self.listener.send(RenderCommand::FlushFills);
let culled_tiles = self.cull_tiles(built_clip_paths, built_draw_paths); let culled_tiles = self.cull_tiles(paint_metadata, built_clip_paths, built_draw_paths);
self.pack_tiles(paint_metadata, culled_tiles); self.pack_tiles(culled_tiles);
} }
pub(crate) fn allocate_mask_tile_index(&self) -> u16 { 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 { struct CulledTiles {
mask_winding_tiles: Vec<MaskTile>, mask_winding_tiles: Vec<MaskTile>,
mask_evenodd_tiles: Vec<MaskTile>, mask_evenodd_tiles: Vec<MaskTile>,
alpha_tiles: Vec<AlphaTile>, display_list: Vec<CulledDisplayItem>,
}
enum CulledDisplayItem {
DrawSolidTiles(Vec<SolidTileVertex>),
DrawAlphaTiles(Vec<AlphaTile>),
PushLayer { effects: PostprocessOptions },
PopLayer,
} }
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
@ -252,13 +372,7 @@ pub struct TileStats {
impl ObjectBuilder { impl ObjectBuilder {
pub(crate) fn new(bounds: RectF, fill_rule: FillRule) -> ObjectBuilder { pub(crate) fn new(bounds: RectF, fill_rule: FillRule) -> ObjectBuilder {
let tile_rect = tiles::round_rect_out_to_tile_bounds(bounds); ObjectBuilder { built_path: BuiltPath::new(bounds, fill_rule), bounds, fills: vec![] }
let tiles = DenseTileMap::new(tile_rect);
ObjectBuilder {
built_path: BuiltPath { mask_tiles: vec![], alpha_tiles: vec![], tiles, fill_rule },
bounds,
fills: vec![],
}
} }
#[inline] #[inline]

View File

@ -19,7 +19,7 @@ use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData};
use crate::gpu_data::{RenderCommand, SolidTileVertex}; use crate::gpu_data::{RenderCommand, SolidTileVertex};
use crate::post::DefringingKernel; use crate::post::DefringingKernel;
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; 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_content::fill::FillRule;
use pathfinder_geometry::vector::{Vector2I, Vector4F}; use pathfinder_geometry::vector::{Vector2I, Vector4F};
use pathfinder_geometry::rect::RectI; 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_ACROSS: u32 = 256;
pub(crate) const MASK_TILES_DOWN: u32 = 256; pub(crate) const MASK_TILES_DOWN: u32 = 256;
const FRAMEBUFFER_CACHE_SIZE: usize = 8;
// FIXME(pcwalton): Shrink this again! // FIXME(pcwalton): Shrink this again!
const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * MASK_TILES_ACROSS as i32; 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; const MASK_FRAMEBUFFER_HEIGHT: i32 = TILE_HEIGHT as i32 * MASK_TILES_DOWN as i32;
@ -75,9 +77,9 @@ where
fill_framebuffer: D::Framebuffer, fill_framebuffer: D::Framebuffer,
mask_framebuffer: D::Framebuffer, mask_framebuffer: D::Framebuffer,
paint_texture: Option<D::Texture>, paint_texture: Option<D::Texture>,
layer_framebuffer_stack: Vec<LayerFramebufferInfo<D>>,
// Postprocessing shader // Postprocessing shader
postprocess_source_framebuffer: Option<D::Framebuffer>,
postprocess_program: PostprocessProgram<D>, postprocess_program: PostprocessProgram<D>,
postprocess_vertex_array: PostprocessVertexArray<D>, postprocess_vertex_array: PostprocessVertexArray<D>,
gamma_lut_texture: D::Texture, gamma_lut_texture: D::Texture,
@ -93,6 +95,7 @@ where
// Rendering state // Rendering state
framebuffer_flags: FramebufferFlags, framebuffer_flags: FramebufferFlags,
buffered_fills: Vec<FillBatchPrimitive>, buffered_fills: Vec<FillBatchPrimitive>,
framebuffer_cache: FramebufferCache<D>,
// Debug // Debug
pub stats: RenderStats, pub stats: RenderStats,
@ -102,7 +105,6 @@ where
pub debug_ui_presenter: DebugUIPresenter<D>, pub debug_ui_presenter: DebugUIPresenter<D>,
// Extra info // Extra info
postprocess_options: Option<PostprocessOptions>,
use_depth: bool, use_depth: bool,
} }
@ -225,8 +227,8 @@ where
fill_framebuffer, fill_framebuffer,
mask_framebuffer, mask_framebuffer,
paint_texture: None, paint_texture: None,
layer_framebuffer_stack: vec![],
postprocess_source_framebuffer: None,
postprocess_program, postprocess_program,
postprocess_vertex_array, postprocess_vertex_array,
gamma_lut_texture, gamma_lut_texture,
@ -245,8 +247,8 @@ where
framebuffer_flags: FramebufferFlags::empty(), framebuffer_flags: FramebufferFlags::empty(),
buffered_fills: vec![], buffered_fills: vec![],
framebuffer_cache: FramebufferCache::new(),
postprocess_options: None,
use_depth: false, use_depth: false,
} }
} }
@ -254,7 +256,6 @@ where
pub fn begin_scene(&mut self) { pub fn begin_scene(&mut self) {
self.framebuffer_flags = FramebufferFlags::empty(); self.framebuffer_flags = FramebufferFlags::empty();
self.device.begin_commands(); self.device.begin_commands();
self.init_postprocessing_framebuffer();
self.stats = RenderStats::default(); self.stats = RenderStats::default();
} }
@ -277,6 +278,8 @@ where
self.upload_mask_tiles(mask_tiles, fill_rule); self.upload_mask_tiles(mask_tiles, fill_rule);
self.draw_mask_tiles(count as u32, 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) => { RenderCommand::DrawSolidTiles(ref solid_tile_vertices) => {
let count = solid_tile_vertices.len() / 4; let count = solid_tile_vertices.len() / 4;
self.stats.solid_tile_count += count; self.stats.solid_tile_count += count;
@ -294,10 +297,6 @@ where
} }
pub fn end_scene(&mut self) { pub fn end_scene(&mut self) {
if self.postprocess_options.is_some() {
self.postprocess();
}
self.end_composite_timer_query(); self.end_composite_timer_query();
self.pending_timers.push_back(mem::replace(&mut self.current_timers, RenderTimers::new())); 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); self.debug_ui_presenter.ui_presenter.set_framebuffer_size(new_framebuffer_size);
} }
#[inline]
pub fn set_postprocess_options(&mut self, new_options: Option<PostprocessOptions>) {
self.postprocess_options = new_options;
}
#[inline] #[inline]
pub fn disable_depth(&mut self) { pub fn disable_depth(&mut self) {
self.use_depth = false; self.use_depth = false;
@ -386,17 +380,8 @@ where
} }
fn upload_paint_data(&mut self, paint_data: &PaintData) { 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 let paint_size = paint_data.size;
// renderer side. let paint_texels = &paint_data.texels;
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;
};
match self.paint_texture { match self.paint_texture {
Some(ref paint_texture) if self.device.texture_size(paint_texture) == paint_size => {} Some(ref paint_texture) if self.device.texture_size(paint_texture) == paint_size => {}
@ -675,68 +660,6 @@ where
self.preserve_draw_framebuffer(); 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]) { fn draw_stencil(&mut self, quad_positions: &[Vector4F]) {
self.device.allocate_buffer( self.device.allocate_buffer(
&self.stencil_vertex_array.vertex_buffer, &self.stencil_vertex_array.vertex_buffer,
@ -819,52 +742,96 @@ where
} }
pub fn draw_render_target(&self) -> RenderTarget<D> { pub fn draw_render_target(&self) -> RenderTarget<D> {
if self.postprocess_options.is_some() { match self.layer_framebuffer_stack.last() {
RenderTarget::Framebuffer(self.postprocess_source_framebuffer.as_ref().unwrap()) Some(ref layer_framebuffer_info) => {
} else { RenderTarget::Framebuffer(&layer_framebuffer_info.framebuffer)
self.dest_render_target()
}
}
pub fn dest_render_target(&self) -> RenderTarget<D> {
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));
} }
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
}; };
/* let framebuffer = self.framebuffer_cache.create_framebuffer(&mut self.device,
self.device.clear(&RenderTarget::Framebuffer(self.postprocess_source_framebuffer TextureFormat::RGBA8,
.as_ref() framebuffer_size);
.unwrap()), self.layer_framebuffer_stack.push(LayerFramebufferInfo {
RectI::new(Vector2I::default(), source_framebuffer_size), framebuffer,
&ClearParams { effects,
color: Some(ColorF::transparent_black()), must_preserve_contents: false,
..ClearParams::default() });
}); }
*/
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<StencilState> { fn stencil_state(&self) -> Option<StencilState> {
@ -881,16 +848,17 @@ where
} }
fn clear_color_for_draw_operation(&mut self) -> Option<ColorF> { fn clear_color_for_draw_operation(&mut self) -> Option<ColorF> {
let postprocessing_needed = self.postprocess_options.is_some(); let must_preserve_contents = match self.layer_framebuffer_stack.last() {
let flag = if postprocessing_needed { Some(ref layer_framebuffer_info) => layer_framebuffer_info.must_preserve_contents,
FramebufferFlags::MUST_PRESERVE_POSTPROCESS_FRAMEBUFFER_CONTENTS None => {
} else { self.framebuffer_flags
FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS .contains(FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS)
}
}; };
if self.framebuffer_flags.contains(flag) { if must_preserve_contents {
None None
} else if !postprocessing_needed { } else if self.layer_framebuffer_stack.is_empty() {
self.options.background_color self.options.background_color
} else { } else {
Some(ColorF::default()) Some(ColorF::default())
@ -898,21 +866,24 @@ where
} }
fn preserve_draw_framebuffer(&mut self) { fn preserve_draw_framebuffer(&mut self) {
let flag = if self.postprocess_options.is_some() { match self.layer_framebuffer_stack.last_mut() {
FramebufferFlags::MUST_PRESERVE_POSTPROCESS_FRAMEBUFFER_CONTENTS Some(ref mut layer_framebuffer_info) => {
} else { layer_framebuffer_info.must_preserve_contents = true;
FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS }
}; None => {
self.framebuffer_flags.insert(flag); self.framebuffer_flags
.insert(FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS);
}
}
} }
pub fn draw_viewport(&self) -> RectI { pub fn draw_viewport(&self) -> RectI {
let main_viewport = self.main_viewport(); match self.layer_framebuffer_stack.last() {
match self.postprocess_options { Some(ref layer_framebuffer_info) => {
Some(PostprocessOptions { defringing_kernel: Some(_), .. }) => { let texture = self.device.framebuffer_texture(&layer_framebuffer_info.framebuffer);
RectI::new(Vector2I::default(), main_viewport.size().scale_xy(Vector2I::new(3, 1))) 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 struct PostprocessOptions {
pub fg_color: ColorF, pub fg_color: ColorF,
pub bg_color: ColorF, pub bg_color: ColorF,
@ -1035,7 +1007,46 @@ bitflags! {
struct FramebufferFlags: u8 { struct FramebufferFlags: u8 {
const MUST_PRESERVE_FILL_FRAMEBUFFER_CONTENTS = 0x01; const MUST_PRESERVE_FILL_FRAMEBUFFER_CONTENTS = 0x01;
const MUST_PRESERVE_MASK_FRAMEBUFFER_CONTENTS = 0x02; const MUST_PRESERVE_MASK_FRAMEBUFFER_CONTENTS = 0x02;
const MUST_PRESERVE_POSTPROCESS_FRAMEBUFFER_CONTENTS = 0x04; const MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS = 0x04;
const MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS = 0x08;
} }
} }
struct FramebufferCache<D> where D: Device {
framebuffers: Vec<D::Framebuffer>,
}
impl<D> FramebufferCache<D> where D: Device {
fn new() -> FramebufferCache<D> {
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<D> where D: Device {
framebuffer: D::Framebuffer,
effects: PostprocessOptions,
must_preserve_contents: bool,
}

View File

@ -10,6 +10,7 @@
//! Packed data ready to be sent to the GPU. //! Packed data ready to be sent to the GPU.
use crate::gpu::renderer::PostprocessOptions;
use crate::options::BoundingQuad; use crate::options::BoundingQuad;
use pathfinder_color::ColorU; use pathfinder_color::ColorU;
use pathfinder_content::fill::FillRule; use pathfinder_content::fill::FillRule;
@ -24,6 +25,8 @@ pub enum RenderCommand {
AddFills(Vec<FillBatchPrimitive>), AddFills(Vec<FillBatchPrimitive>),
FlushFills, FlushFills,
RenderMaskTiles { tiles: Vec<MaskTile>, fill_rule: FillRule }, RenderMaskTiles { tiles: Vec<MaskTile>, fill_rule: FillRule },
PushLayer { effects: PostprocessOptions },
PopLayer,
DrawAlphaTiles(Vec<AlphaTile>), DrawAlphaTiles(Vec<AlphaTile>),
DrawSolidTiles(Vec<SolidTileVertex>), DrawSolidTiles(Vec<SolidTileVertex>),
Finish { build_time: Duration }, Finish { build_time: Duration },
@ -125,6 +128,8 @@ impl Debug for RenderCommand {
RenderCommand::RenderMaskTiles { ref tiles, fill_rule } => { RenderCommand::RenderMaskTiles { ref tiles, fill_rule } => {
write!(formatter, "RenderMaskTiles(x{}, {:?})", tiles.len(), fill_rule) write!(formatter, "RenderMaskTiles(x{}, {:?})", tiles.len(), fill_rule)
} }
RenderCommand::PushLayer { .. } => write!(formatter, "PushLayer"),
RenderCommand::PopLayer => write!(formatter, "PopLayer"),
RenderCommand::DrawAlphaTiles(ref tiles) => { RenderCommand::DrawAlphaTiles(ref tiles) => {
write!(formatter, "DrawAlphaTiles(x{})", tiles.len()) write!(formatter, "DrawAlphaTiles(x{})", tiles.len())
} }

View File

@ -12,10 +12,10 @@
use crate::builder::SceneBuilder; use crate::builder::SceneBuilder;
use crate::concurrent::executor::Executor; use crate::concurrent::executor::Executor;
use crate::gpu::renderer::PostprocessOptions;
use crate::options::{BuildOptions, PreparedBuildOptions}; use crate::options::{BuildOptions, PreparedBuildOptions};
use crate::options::{PreparedRenderTransform, RenderCommandListener}; use crate::options::{PreparedRenderTransform, RenderCommandListener};
use crate::paint::{Paint, PaintId, PaintInfo, Palette}; use crate::paint::{Paint, PaintId, PaintInfo, Palette};
use pathfinder_color::ColorU;
use pathfinder_content::fill::FillRule; use pathfinder_content::fill::FillRule;
use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::vector::Vector2F;
use pathfinder_geometry::rect::RectF; use pathfinder_geometry::rect::RectF;
@ -24,6 +24,7 @@ use pathfinder_content::outline::Outline;
#[derive(Clone)] #[derive(Clone)]
pub struct Scene { pub struct Scene {
pub(crate) display_list: Vec<DisplayItem>,
pub(crate) paths: Vec<DrawPath>, pub(crate) paths: Vec<DrawPath>,
pub(crate) clip_paths: Vec<ClipPath>, pub(crate) clip_paths: Vec<ClipPath>,
palette: Palette, palette: Palette,
@ -35,6 +36,7 @@ impl Scene {
#[inline] #[inline]
pub fn new() -> Scene { pub fn new() -> Scene {
Scene { Scene {
display_list: vec![],
paths: vec![], paths: vec![],
clip_paths: vec![], clip_paths: vec![],
palette: Palette::new(), palette: Palette::new(),
@ -46,6 +48,19 @@ impl Scene {
pub fn push_path(&mut self, path: DrawPath) { pub fn push_path(&mut self, path: DrawPath) {
self.bounds = self.bounds.union_rect(path.outline.bounds()); self.bounds = self.bounds.union_rect(path.outline.bounds());
self.paths.push(path); 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 { pub fn push_clip_path(&mut self, clip_path: ClipPath) -> ClipPathId {
@ -55,6 +70,14 @@ impl Scene {
clip_path_id 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] #[inline]
pub fn build_paint_info(&self) -> PaintInfo { pub fn build_paint_info(&self) -> PaintInfo {
self.palette.build_paint_info(self.view_box.size().to_i32()) self.palette.build_paint_info(self.view_box.size().to_i32())
@ -142,27 +165,6 @@ impl Scene {
outline outline
} }
pub fn monochrome_color(&self) -> Option<ColorU> {
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] #[inline]
pub(crate) fn effective_view_box(&self, render_options: &PreparedBuildOptions) -> RectF { pub(crate) fn effective_view_box(&self, render_options: &PreparedBuildOptions) -> RectF {
if render_options.subpixel_aa_enabled { if render_options.subpixel_aa_enabled {
@ -229,6 +231,13 @@ pub struct ClipPath {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct ClipPathId(pub u32); pub struct ClipPathId(pub u32);
#[derive(Clone, Debug)]
pub enum DisplayItem {
DrawPaths { start_index: u32, end_index: u32 },
PushLayer { effects: PostprocessOptions },
PopLayer,
}
impl DrawPath { impl DrawPath {
#[inline] #[inline]
pub fn new(outline: Outline, pub fn new(outline: Outline,

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // 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::gpu_data::TileObjectPrimitive;
use crate::paint::PaintMetadata; use crate::paint::PaintMetadata;
use pathfinder_content::fill::FillRule; use pathfinder_content::fill::FillRule;
@ -174,10 +174,9 @@ impl<'a> Tiler<'a> {
(FillRule::EvenOdd, _) => {} (FillRule::EvenOdd, _) => {}
} }
// Next, if this is a solid tile, just poke it into the Z-buffer. We don't need // Next, if this is a solid tile, record that fact and stop here.
// to do anything else here.
if paint_metadata.is_opaque { 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; continue;
} }
} }

View File

@ -10,6 +10,7 @@
//! Software occlusion culling. //! Software occlusion culling.
use crate::builder::SolidTile;
use crate::gpu_data::SolidTileVertex; use crate::gpu_data::SolidTileVertex;
use crate::paint::PaintMetadata; use crate::paint::PaintMetadata;
use crate::scene::DrawPath; use crate::scene::DrawPath;
@ -17,62 +18,43 @@ use crate::tile_map::DenseTileMap;
use crate::tiles; use crate::tiles;
use pathfinder_geometry::rect::RectF; use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::vector::Vector2I; use pathfinder_geometry::vector::Vector2I;
use std::ops::Range;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
pub struct ZBuffer { pub(crate) struct ZBuffer {
buffer: DenseTileMap<AtomicUsize>, buffer: DenseTileMap<u32>,
} }
impl ZBuffer { 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); let tile_rect = tiles::round_rect_out_to_tile_bounds(view_box);
ZBuffer { 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 tile_index = self.buffer.coords_to_index_unchecked(coords);
let existing_depth = self.buffer.data[tile_index as usize].load(AtomicOrdering::SeqCst); self.buffer.data[tile_index as usize] < object_index + 1
existing_depth < object_index as usize + 1
} }
pub fn update(&self, coords: Vector2I, object_index: u16) { pub(crate) fn update(&mut self, solid_tiles: &[SolidTile], object_index: u32) {
let tile_index = self.buffer.coords_to_index_unchecked(coords); for solid_tile in solid_tiles {
let mut old_depth = self.buffer.data[tile_index].load(AtomicOrdering::SeqCst); let tile_index = self.buffer.coords_to_index_unchecked(solid_tile.coords);
let new_depth = (object_index + 1) as usize; let z_dest = &mut self.buffer.data[tile_index as usize];
while old_depth < new_depth { *z_dest = u32::max(*z_dest, object_index + 1);
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 fn build_solid_tiles(&self, pub(crate) fn build_solid_tiles(&self, paths: &[DrawPath], paint_metadata: &[PaintMetadata])
paths: &[DrawPath], -> Vec<SolidTileVertex> {
paint_metadata: &[PaintMetadata],
object_range: Range<u32>)
-> Vec<SolidTileVertex> {
let mut solid_tiles = vec![]; let mut solid_tiles = vec![];
for tile_index in 0..self.buffer.data.len() { 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 { if depth == 0 {
continue; continue;
} }
let tile_coords = self.buffer.index_to_coords(tile_index); let tile_coords = self.buffer.index_to_coords(tile_index);
let object_index = (depth - 1) as u32; 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_id = paths[object_index as usize].paint();
let paint_metadata = &paint_metadata[paint_id.0 as usize]; let paint_metadata = &paint_metadata[paint_id.0 as usize];

View File

@ -27,7 +27,6 @@ use pathfinder_geometry::vector::Vector2F;
use pathfinder_renderer::paint::Paint; use pathfinder_renderer::paint::Paint;
use pathfinder_renderer::scene::{ClipPath, ClipPathId, DrawPath, Scene}; use pathfinder_renderer::scene::{ClipPath, ClipPathId, DrawPath, Scene};
use std::fmt::{Display, Formatter, Result as FormatResult}; use std::fmt::{Display, Formatter, Result as FormatResult};
use std::mem;
use usvg::{Color as SvgColor, FillRule as UsvgFillRule, LineCap as UsvgLineCap}; 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::{LineJoin as UsvgLineJoin, Node, NodeExt, NodeKind, Opacity, Paint as UsvgPaint};
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform}; use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform};
@ -65,11 +64,17 @@ bitflags! {
impl BuiltSVG { impl BuiltSVG {
// TODO(pcwalton): Allow a global transform to be set. // 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 // TODO(pcwalton): Maybe have a `SVGBuilder` type to hold the clip path IDs and other
// transient data separate from `BuiltSVG`? // transient data separate from `BuiltSVG`?
let mut built_svg = BuiltSVG { let mut built_svg = BuiltSVG {
scene: Scene::new(), scene,
result_flags: BuildResultFlags::empty(), result_flags: BuildResultFlags::empty(),
clip_paths: HashMap::new(), clip_paths: HashMap::new(),
}; };
@ -83,11 +88,7 @@ impl BuiltSVG {
} }
} }
_ => unreachable!(), _ => 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 built_svg
} }