Implement `clear_rect()` in the canvas front-end by introducing the concept of

per-path blend modes.

These should be useful for some canvas composite operations as well.
This commit is contained in:
Patrick Walton 2020-02-20 15:52:40 -08:00
parent 0c3dad974f
commit 6acd2d91f7
9 changed files with 145 additions and 41 deletions

View File

@ -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

View File

@ -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
}
}

View File

@ -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<MaskTile>,
@ -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<BuiltPath>,
built_draw_paths: Vec<BuiltPath>)
built_draw_paths: Vec<BuiltDrawPath>)
-> 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<ZBuffer> {
fn build_solid_tiles(&self, built_draw_paths: &[BuiltDrawPath]) -> Vec<ZBuffer> {
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<BuiltPath>,
built_draw_paths: Vec<BuiltPath>) {
built_draw_paths: Vec<BuiltDrawPath>) {
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<SolidTileVertex>),
DrawAlphaTiles(Vec<AlphaTile>),
DrawAlphaTiles { tiles: Vec<AlphaTile>, blend_mode: BlendMode },
PushLayer { effects: Effects },
PopLayer,
}

View File

@ -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<D> 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()
}
}
}
}
}

View File

@ -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<MaskTile>, fill_rule: FillRule },
PushLayer { effects: Effects },
PopLayer,
DrawAlphaTiles(Vec<AlphaTile>),
DrawAlphaTiles { tiles: Vec<AlphaTile>, blend_mode: BlendMode },
DrawSolidTiles(Vec<SolidTileVertex>),
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())

View File

@ -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<ClipPathId>,
fill_rule: FillRule,
blend_mode: BlendMode,
name: String,
}
@ -244,9 +245,10 @@ impl DrawPath {
paint: PaintId,
clip_path: Option<ClipPathId>,
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 {

View File

@ -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));
}
}

View File

@ -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()
));
}

View File

@ -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(())
}