diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 3d851681..4652c06e 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -12,6 +12,7 @@ use pathfinder_color::ColorU; use pathfinder_content::dash::OutlineDash; +use pathfinder_content::effects::BlendMode; use pathfinder_content::fill::FillRule; use pathfinder_content::gradient::Gradient; use pathfinder_content::outline::{ArcDirection, Contour, Outline}; @@ -91,6 +92,25 @@ impl CanvasRenderingContext2D { self.stroke_path(path); } + pub fn clear_rect(&mut self, rect: RectF) { + let mut path = Path2D::new(); + path.rect(rect); + + let mut outline = path.into_outline(); + outline.transform(&self.current_state.transform); + + let paint = Paint::black(); + let paint = self.current_state.resolve_paint(&paint); + let paint_id = self.scene.push_paint(&paint); + + self.scene.push_path(DrawPath::new(outline, + paint_id, + None, + FillRule::Winding, + BlendMode::Clear, + String::new())) + } + // Line styles #[inline] @@ -223,10 +243,16 @@ impl CanvasRenderingContext2D { paint_id, clip_path, fill_rule, + BlendMode::SourceOver, String::new())) } - self.scene.push_path(DrawPath::new(outline, paint_id, clip_path, fill_rule, String::new())) + self.scene.push_path(DrawPath::new(outline, + paint_id, + clip_path, + fill_rule, + BlendMode::SourceOver, + String::new())) } // Transformations diff --git a/content/src/effects.rs b/content/src/effects.rs index 92ded519..c20ee417 100644 --- a/content/src/effects.rs +++ b/content/src/effects.rs @@ -61,6 +61,13 @@ pub enum CompositeOp { SourceOver, } +/// Blend modes that can be applied to individual paths without creating layers for them. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum BlendMode { + SourceOver, + Clear, +} + #[derive(Clone, Copy, PartialEq, Debug)] pub struct DefringingKernel(pub [f32; 4]); @@ -70,3 +77,10 @@ impl Default for CompositeOp { CompositeOp::SourceOver } } + +impl Default for BlendMode { + #[inline] + fn default() -> BlendMode { + BlendMode::SourceOver + } +} diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index fdc3d39a..cbd076e9 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -20,7 +20,7 @@ use crate::scene::{DisplayItem, Scene}; use crate::tile_map::DenseTileMap; use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH, Tiler, TilingPathInfo}; use crate::z_buffer::ZBuffer; -use pathfinder_content::effects::Effects; +use pathfinder_content::effects::{BlendMode, Effects}; use pathfinder_content::fill::FillRule; use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::vector::{Vector2F, Vector2I}; @@ -48,6 +48,12 @@ pub(crate) struct ObjectBuilder { pub bounds: RectF, } +#[derive(Debug)] +struct BuiltDrawPath { + path: BuiltPath, + blend_mode: BlendMode, +} + #[derive(Debug)] pub(crate) struct BuiltPath { pub mask_tiles: Vec, @@ -147,7 +153,7 @@ impl<'a> SceneBuilder<'a> { scene: &Scene, paint_metadata: &[PaintMetadata], built_clip_paths: &[BuiltPath], - ) -> BuiltPath { + ) -> BuiltDrawPath { let path_object = &scene.paths[path_index]; let outline = scene.apply_render_options(path_object.outline(), built_options); let paint_id = path_object.paint(); @@ -168,13 +174,17 @@ impl<'a> SceneBuilder<'a> { tiler.generate_tiles(); self.listener.send(RenderCommand::AddFills(tiler.object_builder.fills)); - tiler.object_builder.built_path + + BuiltDrawPath { + path: tiler.object_builder.built_path, + blend_mode: path_object.blend_mode(), + } } fn cull_tiles(&self, paint_metadata: &[PaintMetadata], built_clip_paths: Vec, - built_draw_paths: Vec) + built_draw_paths: Vec) -> CulledTiles { let mut culled_tiles = CulledTiles { mask_winding_tiles: vec![], @@ -226,24 +236,37 @@ impl<'a> SceneBuilder<'a> { 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); + culled_tiles.push_mask_tiles(&built_draw_path.path); - // Create a `DrawAlphaTiles` display item if necessary. + // Create a new `DrawAlphaTiles` display item if we don't have one or if we have to + // break a batch due to blend mode differences. + // + // TODO(pcwalton): If we really wanted to, we could use tile maps to avoid batch + // breaks in some cases… match culled_tiles.display_list.last() { - Some(&CulledDisplayItem::DrawAlphaTiles(_)) => {} - _ => culled_tiles.display_list.push(CulledDisplayItem::DrawAlphaTiles(vec![])), + Some(&CulledDisplayItem::DrawAlphaTiles { + tiles: _, + blend_mode + }) if blend_mode == built_draw_path.blend_mode => {} + _ => { + culled_tiles.display_list.push(CulledDisplayItem::DrawAlphaTiles { + tiles: vec![], + blend_mode: built_draw_path.blend_mode, + }) + } } // 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 - } + CulledDisplayItem::DrawAlphaTiles { + tiles: 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 { + for alpha_tile in &built_draw_path.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) { @@ -256,7 +279,7 @@ impl<'a> SceneBuilder<'a> { culled_tiles } - fn build_solid_tiles(&self, built_draw_paths: &[BuiltPath]) -> Vec { + fn build_solid_tiles(&self, built_draw_paths: &[BuiltDrawPath]) -> Vec { let effective_view_box = self.scene.effective_view_box(self.built_options); let mut z_buffers = vec![ZBuffer::new(effective_view_box)]; let mut z_buffer_index_stack = vec![0]; @@ -276,7 +299,7 @@ impl<'a> SceneBuilder<'a> { 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); + z_buffer.update(&built_draw_path.path.solid_tiles, path_index as u32); } } } @@ -305,8 +328,8 @@ impl<'a> SceneBuilder<'a> { CulledDisplayItem::DrawSolidTiles(tiles) => { self.listener.send(RenderCommand::DrawSolidTiles(tiles)) } - CulledDisplayItem::DrawAlphaTiles(tiles) => { - self.listener.send(RenderCommand::DrawAlphaTiles(tiles)) + CulledDisplayItem::DrawAlphaTiles { tiles, blend_mode } => { + self.listener.send(RenderCommand::DrawAlphaTiles { tiles, blend_mode }) } CulledDisplayItem::PushLayer { effects } => { self.listener.send(RenderCommand::PushLayer { effects }) @@ -319,7 +342,7 @@ impl<'a> SceneBuilder<'a> { fn finish_building(&mut self, paint_metadata: &[PaintMetadata], built_clip_paths: Vec, - built_draw_paths: Vec) { + built_draw_paths: Vec) { self.listener.send(RenderCommand::FlushFills); let culled_tiles = self.cull_tiles(paint_metadata, built_clip_paths, built_draw_paths); self.pack_tiles(culled_tiles); @@ -358,7 +381,7 @@ struct CulledTiles { enum CulledDisplayItem { DrawSolidTiles(Vec), - DrawAlphaTiles(Vec), + DrawAlphaTiles { tiles: Vec, blend_mode: BlendMode }, PushLayer { effects: Effects }, PopLayer, } diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 9204a372..1c30ea11 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -20,7 +20,7 @@ use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData}; use crate::gpu_data::{RenderCommand, SolidTileVertex}; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use pathfinder_color::{self as color, ColorF}; -use pathfinder_content::effects::{CompositeOp, DefringingKernel, Effects, Filter}; +use pathfinder_content::effects::{BlendMode, CompositeOp, DefringingKernel, Effects, Filter}; use pathfinder_content::fill::FillRule; use pathfinder_geometry::vector::{Vector2I, Vector4F}; use pathfinder_geometry::rect::RectI; @@ -298,11 +298,11 @@ where self.upload_solid_tiles(solid_tile_vertices); self.draw_solid_tiles(count as u32); } - RenderCommand::DrawAlphaTiles(ref alpha_tiles) => { + RenderCommand::DrawAlphaTiles { tiles: ref alpha_tiles, blend_mode } => { let count = alpha_tiles.len(); self.stats.alpha_tile_count += count; self.upload_alpha_tiles(alpha_tiles); - self.draw_alpha_tiles(count as u32); + self.draw_alpha_tiles(count as u32, blend_mode); } RenderCommand::Finish { .. } => {} } @@ -595,7 +595,7 @@ where self.framebuffer_flags.insert(FramebufferFlags::MUST_PRESERVE_MASK_FRAMEBUFFER_CONTENTS); } - fn draw_alpha_tiles(&mut self, tile_count: u32) { + fn draw_alpha_tiles(&mut self, tile_count: u32, blend_mode: BlendMode) { let clear_color = self.clear_color_for_draw_operation(); let mut textures = vec![self.device.framebuffer_texture(&self.mask_framebuffer)]; @@ -629,7 +629,7 @@ where uniforms: &uniforms, viewport: self.draw_viewport(), options: RenderOptions { - blend: Some(alpha_blend_state()), + blend: Some(blend_mode.to_blend_state()), stencil: self.stencil_state(), clear_ops: ClearOps { color: clear_color, ..ClearOps::default() }, ..RenderOptions::default() @@ -743,7 +743,7 @@ where ], viewport: self.draw_viewport(), options: RenderOptions { - blend: Some(alpha_blend_state()), + blend: Some(BlendMode::SourceOver.to_blend_state()), depth: Some(DepthState { func: DepthFunc::Less, write: false, }), clear_ops: ClearOps { color: clear_color, ..ClearOps::default() }, ..RenderOptions::default() @@ -829,7 +829,7 @@ where ]; let blend_state = match composite_op { - CompositeOp::SourceOver => alpha_blend_state(), + CompositeOp::SourceOver => BlendMode::SourceOver.to_blend_state(), }; self.device.draw_elements(6, &RenderState { @@ -1107,12 +1107,31 @@ struct LayerFramebufferInfo where D: Device { must_preserve_contents: bool, } -fn alpha_blend_state() -> BlendState { - BlendState { - src_rgb_factor: BlendFactor::SrcAlpha, - dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, - src_alpha_factor: BlendFactor::One, - dest_alpha_factor: BlendFactor::One, - ..BlendState::default() +trait BlendModeExt { + fn to_blend_state(self) -> BlendState; +} + +impl BlendModeExt for BlendMode { + fn to_blend_state(self) -> BlendState { + match self { + BlendMode::SourceOver => { + BlendState { + src_rgb_factor: BlendFactor::SrcAlpha, + dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, + src_alpha_factor: BlendFactor::One, + dest_alpha_factor: BlendFactor::One, + ..BlendState::default() + } + } + BlendMode::Clear => { + BlendState { + src_rgb_factor: BlendFactor::Zero, + dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, + src_alpha_factor: BlendFactor::Zero, + dest_alpha_factor: BlendFactor::OneMinusSrcAlpha, + ..BlendState::default() + } + } + } } } diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index c8e6f623..949f5935 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -12,7 +12,7 @@ use crate::options::BoundingQuad; use pathfinder_color::ColorU; -use pathfinder_content::effects::Effects; +use pathfinder_content::effects::{BlendMode, Effects}; use pathfinder_content::fill::FillRule; use pathfinder_geometry::line_segment::{LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::vector::Vector2I; @@ -27,7 +27,7 @@ pub enum RenderCommand { RenderMaskTiles { tiles: Vec, fill_rule: FillRule }, PushLayer { effects: Effects }, PopLayer, - DrawAlphaTiles(Vec), + DrawAlphaTiles { tiles: Vec, blend_mode: BlendMode }, DrawSolidTiles(Vec), Finish { build_time: Duration }, } @@ -130,8 +130,8 @@ impl Debug for RenderCommand { } RenderCommand::PushLayer { .. } => write!(formatter, "PushLayer"), RenderCommand::PopLayer => write!(formatter, "PopLayer"), - RenderCommand::DrawAlphaTiles(ref tiles) => { - write!(formatter, "DrawAlphaTiles(x{})", tiles.len()) + RenderCommand::DrawAlphaTiles { ref tiles, blend_mode } => { + write!(formatter, "DrawAlphaTiles(x{}, {:?})", tiles.len(), blend_mode) } RenderCommand::DrawSolidTiles(ref tiles) => { write!(formatter, "DrawSolidTiles(x{})", tiles.len()) diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index 383a108b..451cc128 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -15,7 +15,7 @@ use crate::concurrent::executor::Executor; use crate::options::{BuildOptions, PreparedBuildOptions}; use crate::options::{PreparedRenderTransform, RenderCommandListener}; use crate::paint::{Paint, PaintId, PaintInfo, Palette}; -use pathfinder_content::effects::Effects; +use pathfinder_content::effects::{BlendMode, Effects}; use pathfinder_content::fill::FillRule; use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::rect::RectF; @@ -218,6 +218,7 @@ pub struct DrawPath { paint: PaintId, clip_path: Option, fill_rule: FillRule, + blend_mode: BlendMode, name: String, } @@ -244,9 +245,10 @@ impl DrawPath { paint: PaintId, clip_path: Option, fill_rule: FillRule, + blend_mode: BlendMode, name: String) -> DrawPath { - DrawPath { outline, paint, clip_path, fill_rule, name } + DrawPath { outline, paint, clip_path, fill_rule, blend_mode, name } } #[inline] @@ -268,6 +270,11 @@ impl DrawPath { pub(crate) fn fill_rule(&self) -> FillRule { self.fill_rule } + + #[inline] + pub(crate) fn blend_mode(&self) -> BlendMode { + self.blend_mode + } } impl ClipPath { diff --git a/svg/src/lib.rs b/svg/src/lib.rs index 56c4d3d2..022cd574 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -15,6 +15,7 @@ extern crate bitflags; use hashbrown::HashMap; use pathfinder_color::ColorU; +use pathfinder_content::effects::BlendMode; use pathfinder_content::fill::FillRule; use pathfinder_content::outline::Outline; use pathfinder_content::segment::{Segment, SegmentFlags}; @@ -235,7 +236,12 @@ impl BuiltSVG { opacity, &mut self.result_flags)); let fill_rule = FillRule::from_usvg_fill_rule(fill_rule); - self.scene.push_path(DrawPath::new(outline, style, state.clip_path, fill_rule, name)); + self.scene.push_path(DrawPath::new(outline, + style, + state.clip_path, + fill_rule, + BlendMode::SourceOver, + name)); } } diff --git a/swf/src/lib.rs b/swf/src/lib.rs index e9d75166..97ea146f 100644 --- a/swf/src/lib.rs +++ b/swf/src/lib.rs @@ -10,6 +10,7 @@ use std::ops::Add; use pathfinder_color::{ColorF, ColorU}; +use pathfinder_content::effects::BlendMode; use pathfinder_content::fill::FillRule; use pathfinder_content::outline::{Outline, Contour}; use pathfinder_content::stroke::{OutlineStrokeToFill, StrokeStyle}; @@ -200,6 +201,7 @@ pub fn draw_paths_into_scene(library: &SymbolLibrary, scene: &mut Scene) { paint_id, None, FillRule::EvenOdd, + BlendMode::SourceOver, String::new() )); } diff --git a/text/src/lib.rs b/text/src/lib.rs index 068a1b7b..5f6bfc06 100644 --- a/text/src/lib.rs +++ b/text/src/lib.rs @@ -14,6 +14,7 @@ use font_kit::error::GlyphLoadingError; use font_kit::hinting::HintingOptions; use font_kit::loader::Loader; use lyon_path::builder::{FlatPathBuilder, PathBuilder, Build}; +use pathfinder_content::effects::BlendMode; use pathfinder_content::fill::FillRule; use pathfinder_content::outline::{Contour, Outline}; use pathfinder_content::stroke::{OutlineStrokeToFill, StrokeStyle}; @@ -77,7 +78,13 @@ impl SceneExt for Scene { outline = stroke_to_fill.into_outline(); } - self.push_path(DrawPath::new(outline, paint_id, None, FillRule::Winding, String::new())); + self.push_path(DrawPath::new(outline, + paint_id, + None, + FillRule::Winding, + BlendMode::SourceOver, + String::new())); + Ok(()) }