From 67d12adb6cabde84e61d2d014ca6ecec37d445a9 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 21 Feb 2020 21:42:15 -0800 Subject: [PATCH] Replace layers with render targets, which can be used as patterns. This allows us to efficiently handle a lot of workloads that require multiple HTML canvases, without CPU readback. For example, you can render paths to a render target, then turn that render target into a repeating pattern that you fill other paths with. Or you can render paths to a render target and then composite that render target as a whole with a blend mode. This introduces a new `DrawRenderTarget` render command that blits a render target without any paths involved. This is basically just a hack that works around the fact that our tiled renderer doesn't yet support effects that widen the ink region (i.e. blurs). It can be removed once we have that support. --- content/src/pattern.rs | 40 +++++++- demo/common/src/lib.rs | 47 ++++++--- renderer/src/allocator.rs | 51 +++++++--- renderer/src/builder.rs | 48 ++++++--- renderer/src/gpu/renderer.rs | 188 ++++++++++++++++++++--------------- renderer/src/gpu_data.rs | 60 +++++++++-- renderer/src/paint.rs | 112 ++++++++++++++------- renderer/src/scene.rs | 54 ++++++++-- 8 files changed, 426 insertions(+), 174 deletions(-) diff --git a/content/src/pattern.rs b/content/src/pattern.rs index d60d198c..de6eebe8 100644 --- a/content/src/pattern.rs +++ b/content/src/pattern.rs @@ -20,10 +20,19 @@ use image::RgbaImage; /// A raster image pattern. #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct Pattern { - pub image: Image, + pub source: PatternSource, pub repeat: Repeat, } +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum PatternSource { + Image(Image), + RenderTarget(RenderTargetId), +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct RenderTargetId(pub u32); + /// RGBA, non-premultiplied. // FIXME(pcwalton): Hash the pixel contents so that we don't have to compare every pixel! // TODO(pcwalton): Should the pixels be premultiplied? @@ -43,8 +52,8 @@ bitflags! { impl Pattern { #[inline] - pub fn new(image: Image, repeat: Repeat) -> Pattern { - Pattern { image, repeat } + pub fn new(source: PatternSource, repeat: Repeat) -> Pattern { + Pattern { source, repeat } } } @@ -90,6 +99,31 @@ impl Image { } } +impl PatternSource { + #[inline] + pub fn is_opaque(&self) -> bool { + match *self { + PatternSource::Image(ref image) => image.is_opaque(), + PatternSource::RenderTarget(_) => { + // TODO(pcwalton): Maybe do something smarter here? + false + } + } + } + + #[inline] + pub fn set_opacity(&mut self, alpha: f32) { + match *self { + PatternSource::Image(ref mut image) => image.set_opacity(alpha), + PatternSource::RenderTarget(_) => { + // TODO(pcwalton): We'll probably have to introduce and use an Opacity filter for + // this. + unimplemented!() + } + } + } +} + impl Debug for Image { #[inline] fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index 81968f07..fabeb13a 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -35,7 +35,7 @@ use pathfinder_renderer::concurrent::scene_proxy::{RenderCommandStream, ScenePro use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; use pathfinder_renderer::gpu::renderer::{RenderStats, RenderTime, Renderer}; use pathfinder_renderer::options::{BuildOptions, RenderTransform}; -use pathfinder_renderer::scene::Scene; +use pathfinder_renderer::scene::{RenderTarget, Scene}; use pathfinder_svg::BuiltSVG; use pathfinder_ui::{MousePosition, UIEvent}; use std::fs::File; @@ -138,10 +138,14 @@ impl DemoApp where W: Window { let effects = build_effects(&ui_model); - let (mut built_svg, svg_tree) = load_scene(resources, &options.input_path, effects); + let viewport = window.viewport(options.mode.view(0)); + let (mut built_svg, svg_tree) = load_scene(resources, + &options.input_path, + viewport.size(), + effects); + let message = get_svg_building_message(&built_svg); - let viewport = window.viewport(options.mode.view(0)); let dest_framebuffer = DestFramebuffer::Default { viewport, window_size: window_size.device_size(), @@ -428,9 +432,11 @@ impl DemoApp where W: Window { } Event::OpenSVG(ref svg_path) => { + let viewport = self.window.viewport(self.ui_model.mode.view(0)); let effects = build_effects(&self.ui_model); let (mut built_svg, svg_tree) = load_scene(self.window.resource_loader(), svg_path, + viewport.size(), effects); self.ui_model.message = get_svg_building_message(&built_svg); @@ -586,9 +592,9 @@ impl DemoApp where W: Window { 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(); + let effects = build_effects(&self.ui_model); + let mut built_svg = build_svg_tree(&self.svg_tree, viewport_size, effects); self.scene_metadata = SceneMetadata::new_clipping_view_box(&mut built_svg.scene, viewport_size); self.scene_proxy.replace_scene(built_svg.scene); @@ -743,7 +749,10 @@ pub enum UIVisibility { All, } -fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &SVGPath, effects: Option) +fn load_scene(resource_loader: &dyn ResourceLoader, + input_path: &SVGPath, + viewport_size: Vector2I, + effects: Option) -> (BuiltSVG, Tree) { let mut data; match *input_path { @@ -756,22 +765,32 @@ fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &SVGPath, effect }; let tree = Tree::from_data(&data, &UsvgOptions::default()).expect("Failed to parse the SVG!"); - let built_svg = build_svg_tree(&tree, effects); + let built_svg = build_svg_tree(&tree, viewport_size, effects); (built_svg, tree) } -fn build_svg_tree(tree: &Tree, effects: Option) -> BuiltSVG { +fn build_svg_tree(tree: &Tree, viewport_size: Vector2I, effects: Option) -> BuiltSVG { let mut scene = Scene::new(); - if let Some(effects) = effects { - scene.push_layer(effects); - } + + let render_target_id = match effects { + None => None, + Some(effects) => { + let scale = match effects.filter { + Filter::Text { defringing_kernel: Some(_), .. } => Vector2I::new(3, 1), + _ => Vector2I::splat(1), + }; + let name = "Text".to_owned(); + let render_target = RenderTarget::new(viewport_size.scale_xy(scale), name); + Some(scene.push_render_target(render_target)) + } + }; let mut built_svg = BuiltSVG::from_tree_and_scene(&tree, scene); - if effects.is_some() { - built_svg.scene.pop_layer(); + if let (Some(render_target_id), Some(effects)) = (render_target_id, effects) { + built_svg.scene.pop_render_target(); + built_svg.scene.draw_render_target(render_target_id, effects); } - built_svg } diff --git a/renderer/src/allocator.rs b/renderer/src/allocator.rs index 2f7230c7..9764ce36 100644 --- a/renderer/src/allocator.rs +++ b/renderer/src/allocator.rs @@ -10,6 +10,8 @@ //! A simple quadtree-based texture allocator. +use crate::gpu_data::PaintPageId; +use pathfinder_content::pattern::RenderTargetId; use pathfinder_geometry::rect::RectI; use pathfinder_geometry::vector::{Vector2F, Vector2I}; @@ -27,6 +29,8 @@ pub enum TexturePageAllocator { Atlas(TextureAtlasAllocator), // A single image. Image { size: Vector2I }, + // A render target. + RenderTarget { size: Vector2I, id: RenderTargetId }, } #[derive(Debug)] @@ -37,7 +41,7 @@ pub struct TextureAtlasAllocator { #[derive(Clone, Copy, PartialEq, Debug)] pub struct TextureLocation { - pub page: u32, + pub page: PaintPageId, pub rect: RectI, } @@ -55,45 +59,58 @@ impl TextureAllocator { TextureAllocator { pages: vec![] } } - #[inline] pub fn allocate(&mut self, requested_size: Vector2I) -> TextureLocation { // If too big, the image gets its own page. if requested_size.x() > ATLAS_TEXTURE_LENGTH as i32 || requested_size.y() > ATLAS_TEXTURE_LENGTH as i32 { - let page = self.pages.len() as u32; - let rect = RectI::new(Vector2I::default(), requested_size); - self.pages.push(TexturePageAllocator::Image { size: rect.size() }); - return TextureLocation { page, rect }; + return self.allocate_image(requested_size); } // Try to add to each atlas. for (page_index, page) in self.pages.iter_mut().enumerate() { match *page { - TexturePageAllocator::Image { .. } => {} + TexturePageAllocator::Image { .. } | + TexturePageAllocator::RenderTarget { .. } => {} TexturePageAllocator::Atlas(ref mut allocator) => { if let Some(rect) = allocator.allocate(requested_size) { - return TextureLocation { page: page_index as u32, rect }; + return TextureLocation { page: PaintPageId(page_index as u32), rect }; } } } } // Add a new atlas. - let page = self.pages.len() as u32; + let page = PaintPageId(self.pages.len() as u32); let mut allocator = TextureAtlasAllocator::new(); let rect = allocator.allocate(requested_size).expect("Allocation failed!"); self.pages.push(TexturePageAllocator::Atlas(allocator)); TextureLocation { page, rect } } - pub fn page_size(&self, page_index: u32) -> Vector2I { - match self.pages[page_index as usize] { + fn allocate_image(&mut self, requested_size: Vector2I) -> TextureLocation { + let page = PaintPageId(self.pages.len() as u32); + let rect = RectI::new(Vector2I::default(), requested_size); + self.pages.push(TexturePageAllocator::Image { size: rect.size() }); + TextureLocation { page, rect } + } + + pub fn allocate_render_target(&mut self, requested_size: Vector2I, id: RenderTargetId) + -> TextureLocation { + let page = PaintPageId(self.pages.len() as u32); + let rect = RectI::new(Vector2I::default(), requested_size); + self.pages.push(TexturePageAllocator::RenderTarget { size: rect.size(), id }); + TextureLocation { page, rect } + } + + pub fn page_size(&self, page_index: PaintPageId) -> Vector2I { + match self.pages[page_index.0 as usize] { TexturePageAllocator::Atlas(ref atlas) => Vector2I::splat(atlas.size as i32), - TexturePageAllocator::Image { size } => size, + TexturePageAllocator::Image { size } | + TexturePageAllocator::RenderTarget { size, .. } => size, } } - pub fn page_scale(&self, page_index: u32) -> Vector2F { + pub fn page_scale(&self, page_index: PaintPageId) -> Vector2F { Vector2F::splat(1.0) / self.page_size(page_index).to_f32() } @@ -101,6 +118,14 @@ impl TextureAllocator { pub fn page_count(&self) -> u32 { self.pages.len() as u32 } + + #[inline] + pub fn page_render_target_id(&self, page_index: PaintPageId) -> Option { + match self.pages[page_index.0 as usize] { + TexturePageAllocator::RenderTarget { id, .. } => Some(id), + TexturePageAllocator::Atlas(_) | TexturePageAllocator::Image { .. } => None, + } + } } impl TextureAtlasAllocator { diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index 282ee953..a71d8bfe 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -13,7 +13,7 @@ use crate::concurrent::executor::Executor; use crate::gpu::renderer::MASK_TILES_ACROSS; use crate::gpu_data::{AlphaTile, AlphaTileVertex, FillBatchPrimitive, MaskTile, MaskTileVertex}; -use crate::gpu_data::{RenderCommand, SolidTileBatch, TileObjectPrimitive}; +use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileBatch, TileObjectPrimitive}; use crate::options::{PreparedBuildOptions, RenderCommandListener}; use crate::paint::{PaintInfo, PaintMetadata}; use crate::scene::{DisplayItem, Scene}; @@ -22,6 +22,7 @@ use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH, Tiler, TilingPathInfo}; use crate::z_buffer::ZBuffer; use pathfinder_content::effects::{BlendMode, Effects}; use pathfinder_content::fill::FillRule; +use pathfinder_content::pattern::RenderTargetId; use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::vector::{Vector2F, Vector2I}; use pathfinder_geometry::rect::{RectF, RectI}; @@ -52,7 +53,7 @@ pub(crate) struct ObjectBuilder { struct BuiltDrawPath { path: BuiltPath, blend_mode: BlendMode, - paint_page: u32, + paint_page: PaintPageId, } #[derive(Debug)] @@ -213,10 +214,11 @@ impl<'a> SceneBuilder<'a> { 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. + // Pass all commands except `DrawPaths` through. let (start_draw_path_index, end_draw_path_index) = match *display_item { - DisplayItem::PushLayer { effects } => { - culled_tiles.display_list.push(CulledDisplayItem::PushLayer { effects }); + DisplayItem::PushRenderTarget(render_target_id) => { + culled_tiles.display_list + .push(CulledDisplayItem::PushRenderTarget(render_target_id)); let z_buffer = remaining_layer_z_buffers.pop().unwrap(); let solid_tiles = z_buffer.build_solid_tiles(&self.scene.paths, @@ -227,11 +229,18 @@ impl<'a> SceneBuilder<'a> { layer_z_buffers_stack.push(z_buffer); continue; } - DisplayItem::PopLayer => { - culled_tiles.display_list.push(CulledDisplayItem::PopLayer); + DisplayItem::PopRenderTarget => { + culled_tiles.display_list.push(CulledDisplayItem::PopRenderTarget); layer_z_buffers_stack.pop(); continue; } + DisplayItem::DrawRenderTarget { render_target, effects } => { + culled_tiles.display_list.push(CulledDisplayItem::DrawRenderTarget { + render_target, + effects, + }); + continue; + } DisplayItem::DrawPaths { start_index, end_index } => (start_index, end_index), }; @@ -291,11 +300,11 @@ impl<'a> SceneBuilder<'a> { // Create Z-buffers. for display_item in &self.scene.display_list { match *display_item { - DisplayItem::PushLayer { .. } => { + DisplayItem::PushRenderTarget { .. } => { z_buffer_index_stack.push(z_buffers.len()); z_buffers.push(ZBuffer::new(effective_view_box)); } - DisplayItem::PopLayer => { + DisplayItem::PopRenderTarget => { z_buffer_index_stack.pop(); } DisplayItem::DrawPaths { start_index, end_index } => { @@ -306,6 +315,9 @@ impl<'a> SceneBuilder<'a> { z_buffer.update(&built_draw_path.path.solid_tiles, path_index as u32); } } + DisplayItem::DrawRenderTarget { .. } => { + // FIXME(pcwalton): Not great that this doesn't participate in Z-buffering! + } } } debug_assert_eq!(z_buffer_index_stack.len(), 1); @@ -339,10 +351,15 @@ impl<'a> SceneBuilder<'a> { blend_mode, }) } - CulledDisplayItem::PushLayer { effects } => { - self.listener.send(RenderCommand::PushLayer { effects }) + CulledDisplayItem::DrawRenderTarget { render_target, effects } => { + self.listener.send(RenderCommand::DrawRenderTarget { render_target, effects }) + } + CulledDisplayItem::PushRenderTarget(render_target_id) => { + self.listener.send(RenderCommand::PushRenderTarget(render_target_id)) + } + CulledDisplayItem::PopRenderTarget => { + self.listener.send(RenderCommand::PopRenderTarget) } - CulledDisplayItem::PopLayer => self.listener.send(RenderCommand::PopLayer), } } } @@ -389,9 +406,10 @@ struct CulledTiles { enum CulledDisplayItem { DrawSolidTiles(SolidTileBatch), - DrawAlphaTiles { tiles: Vec, paint_page: u32, blend_mode: BlendMode }, - PushLayer { effects: Effects }, - PopLayer, + DrawAlphaTiles { tiles: Vec, paint_page: PaintPageId, blend_mode: BlendMode }, + DrawRenderTarget { render_target: RenderTargetId, effects: Effects }, + PushRenderTarget(RenderTargetId), + PopRenderTarget, } #[derive(Clone, Copy, Debug, Default)] diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 6137e51c..0146e207 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -16,12 +16,13 @@ use crate::gpu::shaders::{FilterTextVertexArray, MAX_FILLS_PER_BATCH, MaskTilePr use crate::gpu::shaders::{MaskTileVertexArray, ReprojectionProgram, ReprojectionVertexArray}; use crate::gpu::shaders::{SolidTileProgram, SolidTileVertexArray}; use crate::gpu::shaders::{StencilProgram, StencilVertexArray}; -use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData}; -use crate::gpu_data::{RenderCommand, SolidTileVertex}; +use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData, PaintPageContents}; +use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileVertex}; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use pathfinder_color::{self as color, ColorF}; use pathfinder_content::effects::{BlendMode, CompositeOp, DefringingKernel, Effects, Filter}; use pathfinder_content::fill::FillRule; +use pathfinder_content::pattern::RenderTargetId; use pathfinder_geometry::vector::{Vector2I, Vector4F}; use pathfinder_geometry::rect::RectI; use pathfinder_geometry::transform3d::Transform4F; @@ -77,8 +78,9 @@ where fill_vertex_array: FillVertexArray, fill_framebuffer: D::Framebuffer, mask_framebuffer: D::Framebuffer, - paint_textures: Vec, - layer_framebuffer_stack: Vec>, + paint_textures: Vec>, + render_targets: Vec>, + render_target_stack: Vec, // This is a dummy texture consisting solely of a single `rgba(0, 0, 0, 255)` texel. It serves // as the paint texture when drawing alpha tiles with the Clear blend mode. If this weren't @@ -247,7 +249,8 @@ where fill_framebuffer, mask_framebuffer, paint_textures: vec![], - layer_framebuffer_stack: vec![], + render_targets: vec![], + render_target_stack: vec![], clear_paint_texture, filter_basic_program, @@ -283,7 +286,6 @@ where } pub fn render_command(&mut self, command: &RenderCommand) { - //println!("{:?}", command); match *command { RenderCommand::Start { bounding_quad, path_count } => { if self.use_depth { @@ -302,8 +304,13 @@ 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::PushRenderTarget(render_target_id) => { + self.push_render_target(render_target_id) + } + RenderCommand::PopRenderTarget => self.pop_render_target(), + RenderCommand::DrawRenderTarget { render_target, effects } => { + self.draw_entire_render_target(render_target, effects) + } RenderCommand::DrawSolidTiles(ref batch) => { let count = batch.vertices.len() / 4; self.stats.solid_tile_count += count; @@ -404,24 +411,47 @@ where } fn upload_paint_data(&mut self, paint_data: &PaintData) { + // Clear out old paint textures. for paint_texture in self.paint_textures.drain(..) { - self.texture_cache.release_texture(paint_texture); + match paint_texture { + PaintTexture::Texture(paint_texture) => { + self.texture_cache.release_texture(paint_texture); + } + PaintTexture::RenderTarget(_) => {} + } } + // Clear out old render targets. + for render_target in self.render_targets.drain(..) { + let texture = self.device.destroy_framebuffer(render_target.framebuffer); + self.texture_cache.release_texture(texture); + } + + // Build up new paint textures and render targets. for paint_page_data in &paint_data.pages { let paint_size = paint_page_data.size; - let paint_texels = &paint_page_data.texels; - let paint_texture = self.texture_cache.create_texture(&mut self.device, TextureFormat::RGBA8, paint_size); + match paint_page_data.contents { + PaintPageContents::RenderTarget(render_target_id) => { + let framebuffer = self.device.create_framebuffer(paint_texture); + self.render_targets.push(RenderTargetInfo { + framebuffer, + must_preserve_contents: false + }); - let texels = color::color_slice_to_u8_slice(paint_texels); - self.device.upload_to_texture(&paint_texture, - RectI::new(Vector2I::default(), paint_size), - TextureDataRef::U8(texels)); + self.paint_textures.push(PaintTexture::RenderTarget(render_target_id)); + } + PaintPageContents::Texels(ref paint_texels) => { + let texels = color::color_slice_to_u8_slice(paint_texels); + self.device.upload_to_texture(&paint_texture, + RectI::new(Vector2I::default(), paint_size), + TextureDataRef::U8(texels)); - self.paint_textures.push(paint_texture); + self.paint_textures.push(PaintTexture::Texture(paint_texture)); + } + } } } @@ -611,7 +641,10 @@ where self.framebuffer_flags.insert(FramebufferFlags::MUST_PRESERVE_MASK_FRAMEBUFFER_CONTENTS); } - fn draw_alpha_tiles(&mut self, tile_count: u32, paint_page: u32, blend_mode: BlendMode) { + fn draw_alpha_tiles(&mut self, + tile_count: u32, + paint_page: PaintPageId, + blend_mode: BlendMode) { let clear_color = self.clear_color_for_draw_operation(); let mut textures = vec![self.device.framebuffer_texture(&self.mask_framebuffer)]; @@ -632,7 +665,7 @@ where // transparent black paint color doesn't zero out the mask. &self.clear_paint_texture } - _ => &self.paint_textures[paint_page as usize], + _ => self.paint_texture(paint_page), }; textures.push(paint_texture); @@ -663,7 +696,7 @@ where self.preserve_draw_framebuffer(); } - fn draw_solid_tiles(&mut self, tile_count: u32, paint_page: u32) { + fn draw_solid_tiles(&mut self, tile_count: u32, paint_page: PaintPageId) { let clear_color = self.clear_color_for_draw_operation(); let mut textures = vec![]; @@ -674,7 +707,7 @@ where UniformData::Vec2(F32x2::new(TILE_WIDTH as f32, TILE_HEIGHT as f32))), ]; - let paint_texture = &self.paint_textures[paint_page as usize]; + let paint_texture = self.paint_texture(paint_page); textures.push(paint_texture); uniforms.push((&self.solid_tile_program.paint_texture_uniform, UniformData::TextureUnit(0))); @@ -778,9 +811,10 @@ where } pub fn draw_render_target(&self) -> RenderTarget { - match self.layer_framebuffer_stack.last() { - Some(ref layer_framebuffer_info) => { - RenderTarget::Framebuffer(&layer_framebuffer_info.framebuffer) + match self.render_target_stack.last() { + Some(&render_target_id) => { + let framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer; + RenderTarget::Framebuffer(framebuffer) } None => { match self.dest_framebuffer { @@ -793,56 +827,37 @@ where } } - fn push_layer(&mut self, effects: Effects) { - let main_framebuffer_size = self.main_viewport().size(); - let framebuffer_size = match effects.filter { - Filter::Text { defringing_kernel: Some(_), .. } => { - main_framebuffer_size.scale_xy(Vector2I::new(3, 1)) - } - _ => main_framebuffer_size, - }; - - let texture = self.texture_cache.create_texture(&mut self.device, - TextureFormat::RGBA8, - framebuffer_size); - let framebuffer = self.device.create_framebuffer(texture); - - self.layer_framebuffer_stack.push(LayerFramebufferInfo { - framebuffer, - effects, - must_preserve_contents: false, - }); + fn push_render_target(&mut self, render_target_id: RenderTargetId) { + self.render_target_stack.push(render_target_id); } - fn pop_layer(&mut self) { - let layer_framebuffer_info = self.layer_framebuffer_stack - .pop() - .expect("Where's the layer?"); + fn pop_render_target(&mut self) { + self.render_target_stack.pop().expect("Render target stack underflow!"); + } - match layer_framebuffer_info.effects.filter { + // FIXME(pcwalton): This is inefficient and should eventually go away. + fn draw_entire_render_target(&mut self, render_target_id: RenderTargetId, effects: Effects) { + match effects.filter { Filter::Composite(composite_op) => { - self.composite_layer(&layer_framebuffer_info, composite_op) + self.composite_render_target(render_target_id, composite_op) } Filter::Text { fg_color, bg_color, defringing_kernel, gamma_correction } => { - self.draw_text_layer(&layer_framebuffer_info, - fg_color, - bg_color, - defringing_kernel, - gamma_correction) + self.draw_text_render_target(render_target_id, + fg_color, + bg_color, + defringing_kernel, + gamma_correction) } } self.preserve_draw_framebuffer(); - - let texture = self.device.destroy_framebuffer(layer_framebuffer_info.framebuffer); - self.texture_cache.release_texture(texture); } - fn composite_layer(&self, - layer_framebuffer_info: &LayerFramebufferInfo, - composite_op: CompositeOp) { + fn composite_render_target(&self, + render_target_id: RenderTargetId, + composite_op: CompositeOp) { let clear_color = self.clear_color_for_draw_operation(); - let source_framebuffer = &layer_framebuffer_info.framebuffer; + let source_framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer; let source_texture = self.device.framebuffer_texture(source_framebuffer); let source_texture_size = self.device.texture_size(source_texture); let main_viewport = self.main_viewport(); @@ -875,14 +890,14 @@ where }); } - fn draw_text_layer(&self, - layer_framebuffer_info: &LayerFramebufferInfo, - fg_color: ColorF, - bg_color: ColorF, - defringing_kernel: Option, - gamma_correction: bool) { + fn draw_text_render_target(&self, + render_target_id: RenderTargetId, + fg_color: ColorF, + bg_color: ColorF, + defringing_kernel: Option, + gamma_correction: bool) { let clear_color = self.clear_color_for_draw_operation(); - let source_framebuffer = &layer_framebuffer_info.framebuffer; + let source_framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer; let source_texture = self.device.framebuffer_texture(source_framebuffer); let source_texture_size = self.device.texture_size(source_texture); let main_viewport = self.main_viewport(); @@ -940,8 +955,10 @@ where } fn clear_color_for_draw_operation(&self) -> Option { - let must_preserve_contents = match self.layer_framebuffer_stack.last() { - Some(ref layer_framebuffer_info) => layer_framebuffer_info.must_preserve_contents, + let must_preserve_contents = match self.render_target_stack.last() { + Some(render_target_id) => { + self.render_targets[render_target_id.0 as usize].must_preserve_contents + } None => { self.framebuffer_flags .contains(FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS) @@ -950,7 +967,7 @@ where if must_preserve_contents { None - } else if self.layer_framebuffer_stack.is_empty() { + } else if self.render_target_stack.is_empty() { self.options.background_color } else { Some(ColorF::default()) @@ -958,9 +975,9 @@ where } fn preserve_draw_framebuffer(&mut self) { - match self.layer_framebuffer_stack.last_mut() { - Some(ref mut layer_framebuffer_info) => { - layer_framebuffer_info.must_preserve_contents = true; + match self.render_target_stack.last() { + Some(render_target_id) => { + self.render_targets[render_target_id.0 as usize].must_preserve_contents = true; } None => { self.framebuffer_flags @@ -970,9 +987,10 @@ where } pub fn draw_viewport(&self) -> RectI { - match self.layer_framebuffer_stack.last() { - Some(ref layer_framebuffer_info) => { - let texture = self.device.framebuffer_texture(&layer_framebuffer_info.framebuffer); + match self.render_target_stack.last() { + Some(render_target_id) => { + let framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer; + let texture = self.device.framebuffer_texture(framebuffer); RectI::new(Vector2I::default(), self.device.texture_size(texture)) } None => self.main_viewport(), @@ -996,6 +1014,16 @@ where Vector2I::new(MASK_FRAMEBUFFER_WIDTH, MASK_FRAMEBUFFER_HEIGHT)) } + fn paint_texture(&self, paint_page: PaintPageId) -> &D::Texture { + match self.paint_textures[paint_page.0 as usize] { + PaintTexture::Texture(ref texture) => texture, + PaintTexture::RenderTarget(render_target_id) => { + let framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer; + self.device.framebuffer_texture(framebuffer) + } + } + } + fn allocate_timer_query(&mut self) -> D::TimerQuery { match self.free_timer_queries.pop() { Some(query) => query, @@ -1124,9 +1152,13 @@ impl TextureCache where D: Device { } } -struct LayerFramebufferInfo where D: Device { +enum PaintTexture where D: Device { + Texture(D::Texture), + RenderTarget(RenderTargetId), +} + +struct RenderTargetInfo where D: Device { framebuffer: D::Framebuffer, - effects: Effects, must_preserve_contents: bool, } diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index 517b67fc..79490486 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -14,21 +14,49 @@ use crate::options::BoundingQuad; use pathfinder_color::ColorU; use pathfinder_content::effects::{BlendMode, Effects}; use pathfinder_content::fill::FillRule; +use pathfinder_content::pattern::RenderTargetId; use pathfinder_geometry::line_segment::{LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::vector::Vector2I; use std::fmt::{Debug, Formatter, Result as DebugResult}; use std::time::Duration; pub enum RenderCommand { + // Starts rendering a frame. Start { path_count: usize, bounding_quad: BoundingQuad }, + + // Uploads paint data for use with subsequent rendering commands to the GPU. AddPaintData(PaintData), + + // Adds fills to the queue. AddFills(Vec), + + // Flushes the queue of fills. FlushFills, + + // Render fills to a set of mask tiles. RenderMaskTiles { tiles: Vec, fill_rule: FillRule }, - PushLayer { effects: Effects }, - PopLayer, - DrawAlphaTiles { tiles: Vec, paint_page: u32, blend_mode: BlendMode }, + + // Pushes a render target onto the stack. Draw commands go to the render target on top of the + // stack. + PushRenderTarget(RenderTargetId), + + // Pops a render target from the stack. + PopRenderTarget, + + // Draws a batch of alpha tiles to the render target on top of the stack. + DrawAlphaTiles { tiles: Vec, paint_page: PaintPageId, blend_mode: BlendMode }, + + // Draws a batch of solid tiles to the render target on top of the stack. DrawSolidTiles(SolidTileBatch), + + // Draws an entire render target to the render target on top of the stack. + // + // FIXME(pcwalton): This draws the entire render target, so it's inefficient. We should get rid + // of this command and transition all uses to `DrawAlphaTiles`/`DrawSolidTiles`. The reason it + // exists is that we don't have logic to create tiles for blur bounding regions yet. + DrawRenderTarget { render_target: RenderTargetId, effects: Effects }, + + // Presents a rendered frame. Finish { build_time: Duration }, } @@ -37,16 +65,25 @@ pub struct PaintData { pub pages: Vec, } +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct PaintPageId(pub u32); + #[derive(Clone, Debug)] pub struct PaintPageData { pub size: Vector2I, - pub texels: Vec, + pub contents: PaintPageContents, +} + +#[derive(Clone, Debug)] +pub enum PaintPageContents { + Texels(Vec), + RenderTarget(RenderTargetId), } #[derive(Clone, Debug)] pub struct SolidTileBatch { pub vertices: Vec, - pub paint_page: u32, + pub paint_page: PaintPageId, } #[derive(Clone, Copy, Debug)] @@ -139,18 +176,23 @@ 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::PushRenderTarget(render_target_id) => { + write!(formatter, "PushRenderTarget({:?})", render_target_id) + } + RenderCommand::PopRenderTarget => write!(formatter, "PopRenderTarget"), + RenderCommand::DrawRenderTarget { render_target, .. } => { + write!(formatter, "DrawRenderTarget({:?})", render_target) + } RenderCommand::DrawAlphaTiles { ref tiles, paint_page, blend_mode } => { write!(formatter, - "DrawAlphaTiles(x{}, {}, {:?})", + "DrawAlphaTiles(x{}, {:?}, {:?})", tiles.len(), paint_page, blend_mode) } RenderCommand::DrawSolidTiles(ref batch) => { write!(formatter, - "DrawSolidTiles(x{}, {})", + "DrawSolidTiles(x{}, {:?})", batch.vertices.len(), batch.paint_page) } diff --git a/renderer/src/paint.rs b/renderer/src/paint.rs index ef60f205..cf706655 100644 --- a/renderer/src/paint.rs +++ b/renderer/src/paint.rs @@ -9,12 +9,13 @@ // except according to those terms. use crate::allocator::{TextureAllocator, TextureLocation}; -use crate::gpu_data::{PaintData, PaintPageData}; +use crate::gpu_data::{PaintData, PaintPageContents, PaintPageData, PaintPageId}; +use crate::scene::RenderTarget; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use hashbrown::HashMap; use pathfinder_color::ColorU; use pathfinder_content::gradient::{Gradient, GradientGeometry}; -use pathfinder_content::pattern::Pattern; +use pathfinder_content::pattern::{Image, Pattern, PatternSource, RenderTargetId}; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::transform2d::{Matrix2x2F, Transform2F}; use pathfinder_geometry::util; @@ -33,6 +34,7 @@ const MAX_SOLID_COLORS_PER_TILE: u32 = SOLID_COLOR_TILE_LENGTH * SOLID_COLOR_TIL #[derive(Clone)] pub struct Palette { pub(crate) paints: Vec, + pub(crate) render_targets: Vec, cache: HashMap, } @@ -65,7 +67,7 @@ impl Debug for Paint { impl Palette { #[inline] pub fn new() -> Palette { - Palette { paints: vec![], cache: HashMap::new() } + Palette { paints: vec![], render_targets: vec![], cache: HashMap::new() } } } @@ -86,7 +88,7 @@ impl Paint { Paint::Gradient(ref gradient) => { gradient.stops().iter().all(|stop| stop.color.is_opaque()) } - Paint::Pattern(ref pattern) => pattern.image.is_opaque(), + Paint::Pattern(ref pattern) => pattern.source.is_opaque(), } } @@ -119,7 +121,7 @@ impl Paint { match *self { Paint::Color(ref mut color) => color.a = (color.a as f32 * alpha).round() as u8, Paint::Gradient(ref mut gradient) => gradient.set_opacity(alpha), - Paint::Pattern(ref mut pattern) => pattern.image.set_opacity(alpha), + Paint::Pattern(ref mut pattern) => pattern.source.set_opacity(alpha), } } @@ -172,8 +174,8 @@ pub struct PaintInfo { // TODO(pcwalton): Add clamp/repeat options. #[derive(Debug)] pub struct PaintMetadata { - /// The index of the texture page. - pub tex_page: u32, + /// The location of the texture. + pub tex_page: PaintPageId, /// The rectangle within the texture atlas. pub tex_rect: RectI, /// The transform to apply to screen coordinates to translate them into UVs. @@ -195,10 +197,24 @@ impl Palette { paint_id } + pub fn push_render_target(&mut self, render_target: RenderTarget) -> RenderTargetId { + let id = RenderTargetId(self.render_targets.len() as u32); + self.render_targets.push(render_target); + id + } + pub fn build_paint_info(&self, view_box_size: Vector2I) -> PaintInfo { let mut allocator = TextureAllocator::new(); let mut metadata = vec![]; + // Assign render target locations. + let mut render_target_locations = vec![]; + for (render_target_index, render_target) in self.render_targets.iter().enumerate() { + let render_target_id = RenderTargetId(render_target_index as u32); + render_target_locations.push(allocator.allocate_render_target(render_target.size(), + render_target_id)); + } + // Assign paint locations. let mut solid_color_tile_builder = SolidColorTileBuilder::new(); for paint in &self.paints { @@ -212,7 +228,14 @@ impl Palette { allocator.allocate(Vector2I::splat(GRADIENT_TILE_LENGTH as i32)) } Paint::Pattern(ref pattern) => { - allocator.allocate(pattern.image.size()) + match pattern.source { + PatternSource::RenderTarget(render_target_id) => { + render_target_locations[render_target_id.0 as usize] + } + PatternSource::Image(ref image) => { + allocator.allocate(image.size()) + } + } } }; @@ -251,34 +274,55 @@ impl Palette { // TODO(pcwalton): This is slow. Do more on GPU. let mut paint_data = PaintData { pages: vec![] }; for page_index in 0..allocator.page_count() { + let page_index = PaintPageId(page_index); let page_size = allocator.page_size(page_index); + if let Some(render_target_id) = allocator.page_render_target_id(page_index) { + paint_data.pages.push(PaintPageData { + size: page_size, + contents: PaintPageContents::RenderTarget(render_target_id), + }); + continue; + } + let page_area = page_size.x() as usize * page_size.y() as usize; let texels = vec![ColorU::default(); page_area]; - paint_data.pages.push(PaintPageData { size: page_size, texels }); + paint_data.pages.push(PaintPageData { + size: page_size, + contents: PaintPageContents::Texels(texels), + }); } for (paint, metadata) in self.paints.iter().zip(metadata.iter()) { let tex_page = metadata.tex_page; - let PaintPageData { - size: page_size, - ref mut texels, - } = paint_data.pages[tex_page as usize]; + let paint_page_data = &mut paint_data.pages[tex_page.0 as usize]; + let page_size = paint_page_data.size; let page_scale = allocator.page_scale(tex_page); - match paint { - Paint::Color(color) => { - put_pixel(metadata.tex_rect.origin(), *color, texels, page_size); - } - Paint::Gradient(ref gradient) => { - self.render_gradient(gradient, - metadata.tex_rect, - &metadata.tex_transform, - texels, - page_size, - page_scale); - } - Paint::Pattern(ref pattern) => { - self.render_pattern(pattern, metadata.tex_rect, texels, page_size); + + match paint_page_data.contents { + PaintPageContents::Texels(ref mut texels) => { + match paint { + Paint::Color(color) => { + put_pixel(metadata.tex_rect.origin(), *color, texels, page_size); + } + Paint::Gradient(ref gradient) => { + self.render_gradient(gradient, + metadata.tex_rect, + &metadata.tex_transform, + texels, + page_size, + page_scale); + } + Paint::Pattern(ref pattern) => { + match pattern.source { + PatternSource::RenderTarget(_) => {} + PatternSource::Image(ref image) => { + self.render_image(image, metadata.tex_rect, texels, page_size); + } + } + } + } } + PaintPageContents::RenderTarget(_) => {} } } @@ -340,12 +384,12 @@ impl Palette { } } - fn render_pattern(&self, - pattern: &Pattern, - tex_rect: RectI, - texels: &mut [ColorU], - tex_size: Vector2I) { - let image_size = pattern.image.size(); + fn render_image(&self, + image: &Image, + tex_rect: RectI, + texels: &mut [ColorU], + tex_size: Vector2I) { + let image_size = image.size(); for y in 0..image_size.y() { let dest_origin = tex_rect.origin() + Vector2I::new(0, y); let dest_start_index = paint_texel_index(dest_origin, tex_size); @@ -353,7 +397,7 @@ impl Palette { let dest_end_index = dest_start_index + image_size.x() as usize; let src_end_index = src_start_index + image_size.x() as usize; texels[dest_start_index..dest_end_index].copy_from_slice( - &pattern.image.pixels()[src_start_index..src_end_index]); + &image.pixels()[src_start_index..src_end_index]); } } } diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index 451cc128..dc38968a 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -17,10 +17,11 @@ use crate::options::{PreparedRenderTransform, RenderCommandListener}; use crate::paint::{Paint, PaintId, PaintInfo, Palette}; use pathfinder_content::effects::{BlendMode, Effects}; use pathfinder_content::fill::FillRule; -use pathfinder_geometry::vector::Vector2F; +use pathfinder_content::outline::Outline; +use pathfinder_content::pattern::RenderTargetId; +use pathfinder_geometry::vector::{Vector2F, Vector2I}; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::transform2d::Transform2F; -use pathfinder_content::outline::Outline; #[derive(Clone)] pub struct Scene { @@ -70,12 +71,18 @@ impl Scene { clip_path_id } - pub fn push_layer(&mut self, effects: Effects) { - self.display_list.push(DisplayItem::PushLayer { effects }); + pub fn push_render_target(&mut self, render_target: RenderTarget) -> RenderTargetId { + let render_target_id = self.palette.push_render_target(render_target); + self.display_list.push(DisplayItem::PushRenderTarget(render_target_id)); + render_target_id } - pub fn pop_layer(&mut self) { - self.display_list.push(DisplayItem::PopLayer); + pub fn pop_render_target(&mut self) { + self.display_list.push(DisplayItem::PopRenderTarget); + } + + pub fn draw_render_target(&mut self, render_target: RenderTargetId, effects: Effects) { + self.display_list.push(DisplayItem::DrawRenderTarget { render_target, effects }); } #[inline] @@ -232,11 +239,30 @@ pub struct ClipPath { #[derive(Clone, Copy, Debug)] pub struct ClipPathId(pub u32); +#[derive(Clone, Debug)] +pub struct RenderTarget { + size: Vector2I, + name: String, +} + +/// Drawing commands. #[derive(Clone, Debug)] pub enum DisplayItem { + /// Draws paths to the render target on top of the stack. DrawPaths { start_index: u32, end_index: u32 }, - PushLayer { effects: Effects }, - PopLayer, + + /// Draws an entire render target to the render target on top of the stack. + /// + /// FIXME(pcwalton): This draws the entire render target, so it's inefficient. We should get + /// rid of this command and transition all uses to `DrawPaths`. The reason it exists is that we + /// don't have logic to create tiles for blur bounding regions yet. + DrawRenderTarget { render_target: RenderTargetId, effects: Effects }, + + /// Pushes a render target onto the top of the stack. + PushRenderTarget(RenderTargetId), + + /// Pops a render target from the stack. + PopRenderTarget, } impl DrawPath { @@ -293,3 +319,15 @@ impl ClipPath { self.fill_rule } } + +impl RenderTarget { + #[inline] + pub fn new(size: Vector2I, name: String) -> RenderTarget { + RenderTarget { size, name } + } + + #[inline] + pub fn size(&self) -> Vector2I { + self.size + } +}