Bug 1905611 - apply clipping to PictureCompositeMode::SVGFEGraph r=gfx-reviewers,gw

Differential Revision: https://phabricator.services.mozilla.com/D216637
This commit is contained in:
Ashley Hale 2024-09-12 10:19:43 +00:00
parent 203042622b
commit c976164f43
13 changed files with 594 additions and 618 deletions

View File

@ -94,7 +94,7 @@
//! blend the overlay tile (this is not always optimal right now, but will be
//! improved as a follow up).
use api::{MixBlendMode, PremultipliedColorF, FilterPrimitiveKind};
use api::{MixBlendMode, PremultipliedColorF, FilterPrimitiveKind, SVGFE_GRAPH_MAX};
use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FilterOpGraphPictureBufferId, RasterSpace};
use api::{DebugFlags, ImageKey, ColorF, ColorU, PrimitiveFlags};
use api::{ImageRendering, ColorDepth, YuvRangedColorSpace, YuvFormat, AlphaType};
@ -4154,6 +4154,9 @@ struct SurfaceAllocInfo {
needs_scissor_rect: bool,
clipped: DeviceRect,
unclipped: DeviceRect,
// Only used for SVGFEGraph currently, this is the source pixels needed to
// render the pixels in clipped.
source: DeviceRect,
clipped_local: PictureRect,
uv_rect_kind: UvRectKind,
}
@ -4299,7 +4302,10 @@ impl PictureCompositeMode {
result_rect
}
PictureCompositeMode::SVGFEGraph(ref filters) => {
self.get_coverage_svgfe(filters, surface_rect.cast_unit(), true, false).0
// Return prim_subregion for use in get_local_prim_rect, which
// is the polygon size.
// This must match surface_rects.unclipped_local.
self.get_coverage_target_svgfe(filters, surface_rect.cast_unit())
}
_ => {
surface_rect
@ -4397,12 +4403,11 @@ impl PictureCompositeMode {
result_rect
}
PictureCompositeMode::SVGFEGraph(ref filters) => {
let mut rect = self.get_coverage_svgfe(filters, surface_rect.cast_unit(), true, true).0;
// Inflate a bit for invalidation purposes, but we don't do this in get_surface_rects or get_surface_rect.'
if !rect.is_empty() {
rect = rect.inflate(1.0, 1.0);
}
rect
// surface_rect may be for source or target, so invalidate based
// on both interpretations
let target_subregion = self.get_coverage_source_svgfe(filters, surface_rect.cast());
let source_subregion = self.get_coverage_target_svgfe(filters, surface_rect.cast());
target_subregion.union(&source_subregion)
}
_ => {
surface_rect
@ -4441,292 +4446,277 @@ impl PictureCompositeMode {
}
}
/// Here we compute the source and target rects for SVGFEGraph by walking
/// Here we transform source rect to target rect for SVGFEGraph by walking
/// the whole graph and propagating subregions based on the provided
/// invalidation rect (in either source or target space), and we want it to
/// be a tight fit so we don't waste time applying multiple filters to
/// pixels that do not contribute to the invalidated rect.
/// invalidation rect, and we want it to be a tight fit so we don't waste
/// time applying multiple filters to pixels that do not contribute to the
/// invalidated rect.
///
/// The interesting parts of the handling of SVG filters are:
/// * scene_building.rs : wrap_prim_with_filters
/// * picture.rs : get_coverage_svgfe (you are here)
/// * picture.rs : get_coverage_target_svgfe (you are here)
/// * picture.rs : get_coverage_source_svgfe
/// * render_task.rs : new_svg_filter_graph
/// * render_target.rs : add_svg_filter_node_instances
pub fn get_coverage_svgfe(
pub fn get_coverage_target_svgfe(
&self,
filters: &[(FilterGraphNode, FilterGraphOp)],
surface_rect: LayoutRect,
surface_rect_is_source: bool,
skip_subregion_clips: bool,
) -> (LayoutRect, LayoutRect, LayoutRect) {
) -> LayoutRect {
// The value of BUFFER_LIMIT here must be the same as in
// scene_building.rs, or we'll hit asserts here.
const BUFFER_LIMIT: usize = 256;
const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
fn calc_target_from_source(
source_rect: LayoutRect,
filters: &[(FilterGraphNode, FilterGraphOp)],
skip_subregion_clips: bool,
) -> LayoutRect {
// We need to evaluate the subregions based on the proposed
// SourceGraphic rect as it isn't known at scene build time.
let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] = [LayoutRect::zero(); BUFFER_LIMIT];
for (id, (node, op)) in filters.iter().enumerate() {
let full_subregion = node.subregion;
let mut used_subregion = LayoutRect::zero();
// We need to evaluate the subregions based on the proposed
// SourceGraphic rect as it isn't known at scene build time.
let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] = [LayoutRect::zero(); BUFFER_LIMIT];
for (id, (node, op)) in filters.iter().enumerate() {
let full_subregion = node.subregion;
let mut used_subregion = LayoutRect::zero();
for input in &node.inputs {
match input.buffer_id {
FilterOpGraphPictureBufferId::BufferId(id) => {
assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
// This id lookup should always succeed.
let input_subregion = subregion_by_buffer_id[id as usize];
// Now add the padding that transforms from
// source to target, this was determined during
// scene build based on the operation.
let input_subregion =
LayoutRect::new(
LayoutPoint::new(
input_subregion.min.x + input.target_padding.min.x,
input_subregion.min.y + input.target_padding.min.y,
),
LayoutPoint::new(
input_subregion.max.x + input.target_padding.max.x,
input_subregion.max.y + input.target_padding.max.y,
),
);
used_subregion = used_subregion
.union(&input_subregion);
}
FilterOpGraphPictureBufferId::None => {
panic!("Unsupported BufferId type");
}
}
}
// We can clip the used subregion to the node subregion
used_subregion = used_subregion
.intersection(&full_subregion)
.unwrap_or(LayoutRect::zero());
match op {
FilterGraphOp::SVGFEBlendColor => {}
FilterGraphOp::SVGFEBlendColorBurn => {}
FilterGraphOp::SVGFEBlendColorDodge => {}
FilterGraphOp::SVGFEBlendDarken => {}
FilterGraphOp::SVGFEBlendDifference => {}
FilterGraphOp::SVGFEBlendExclusion => {}
FilterGraphOp::SVGFEBlendHardLight => {}
FilterGraphOp::SVGFEBlendHue => {}
FilterGraphOp::SVGFEBlendLighten => {}
FilterGraphOp::SVGFEBlendLuminosity => {}
FilterGraphOp::SVGFEBlendMultiply => {}
FilterGraphOp::SVGFEBlendNormal => {}
FilterGraphOp::SVGFEBlendOverlay => {}
FilterGraphOp::SVGFEBlendSaturation => {}
FilterGraphOp::SVGFEBlendScreen => {}
FilterGraphOp::SVGFEBlendSoftLight => {}
FilterGraphOp::SVGFEColorMatrix { values } => {
if values[3] != 0.0 ||
values[7] != 0.0 ||
values[11] != 0.0 ||
values[19] != 0.0 {
// Manipulating alpha can easily create new
// pixels outside of input subregions
used_subregion = full_subregion;
}
}
FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
FilterGraphOp::SVGFEComponentTransferInterned{handle: _, creates_pixels} => {
// Check if the value of alpha[0] is modified, if so
// the whole subregion is used because it will be
// creating new pixels outside of input subregions
if *creates_pixels {
used_subregion = full_subregion;
}
}
FilterGraphOp::SVGFECompositeArithmetic { k1, k2, k3, k4 } => {
// Optimization opportunity - some inputs may be
// smaller subregions due to the way the math works,
// k1 is the intersection of the two inputs, k2 is
// the first input only, k3 is the second input
// only, and k4 changes the whole subregion.
//
// See logic for SVG_FECOMPOSITE_OPERATOR_ARITHMETIC
// in FilterSupport.cpp
//
// We can at least ignore the entire node if
// everything is zero.
if *k1 <= 0.0 &&
*k2 <= 0.0 &&
*k3 <= 0.0 {
used_subregion = LayoutRect::zero();
}
// Check if alpha is added to pixels as it means it
// can fill pixels outside input subregions
if *k4 > 0.0 {
used_subregion = full_subregion;
}
}
FilterGraphOp::SVGFECompositeATop => {}
FilterGraphOp::SVGFECompositeIn => {}
FilterGraphOp::SVGFECompositeLighter => {}
FilterGraphOp::SVGFECompositeOut => {}
FilterGraphOp::SVGFECompositeOver => {}
FilterGraphOp::SVGFECompositeXOR => {}
FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => {}
FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => {}
FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => {}
FilterGraphOp::SVGFEDiffuseLightingDistant{..} => {}
FilterGraphOp::SVGFEDiffuseLightingPoint{..} => {}
FilterGraphOp::SVGFEDiffuseLightingSpot{..} => {}
FilterGraphOp::SVGFEDisplacementMap{..} => {}
FilterGraphOp::SVGFEDropShadow{..} => {}
FilterGraphOp::SVGFEFlood { color } => {
// Subregion needs to be set to the full node
// subregion for fills (unless the fill is a no-op)
if color.a > 0.0 {
used_subregion = full_subregion;
}
}
FilterGraphOp::SVGFEGaussianBlur{..} => {}
FilterGraphOp::SVGFEIdentity => {}
FilterGraphOp::SVGFEImage { sampling_filter: _sampling_filter, matrix: _matrix } => {
// TODO: calculate the actual subregion
used_subregion = full_subregion;
}
FilterGraphOp::SVGFEMorphologyDilate{..} => {}
FilterGraphOp::SVGFEMorphologyErode{..} => {}
FilterGraphOp::SVGFEOpacity { valuebinding: _valuebinding, value } => {
// If fully transparent, we can ignore this node
if *value <= 0.0 {
used_subregion = LayoutRect::zero();
}
}
FilterGraphOp::SVGFESourceAlpha |
FilterGraphOp::SVGFESourceGraphic => {
used_subregion = surface_rect;
}
FilterGraphOp::SVGFESpecularLightingDistant{..} => {}
FilterGraphOp::SVGFESpecularLightingPoint{..} => {}
FilterGraphOp::SVGFESpecularLightingSpot{..} => {}
FilterGraphOp::SVGFETile => {
// feTile fills the entire output with
// source pixels, so it's effectively a flood.
used_subregion = full_subregion;
}
FilterGraphOp::SVGFEToAlpha => {}
FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} |
FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} |
FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} |
FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => {
// Turbulence produces pixel values throughout the
// node subregion.
used_subregion = full_subregion;
}
}
// Store the subregion so later nodes can refer back
// to this and propagate rects properly
assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
subregion_by_buffer_id[id] = used_subregion;
}
subregion_by_buffer_id[filters.len() - 1]
}
/// Here we transform target rect to source rect for SVGFEGraph by walking
/// the whole graph and propagating subregions based on the provided
/// invalidation rect, and we want it to be a tight fit so we don't waste
/// time applying multiple filters to pixels that do not contribute to the
/// invalidated rect.
///
/// The interesting parts of the handling of SVG filters are:
/// * scene_building.rs : wrap_prim_with_filters
/// * picture.rs : get_coverage_target_svgfe
/// * picture.rs : get_coverage_source_svgfe (you are here)
/// * render_task.rs : new_svg_filter_graph
/// * render_target.rs : add_svg_filter_node_instances
pub fn get_coverage_source_svgfe(
&self,
filters: &[(FilterGraphNode, FilterGraphOp)],
surface_rect: LayoutRect,
) -> LayoutRect {
// The value of BUFFER_LIMIT here must be the same as in
// scene_building.rs, or we'll hit asserts here.
const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
// We're solving the source rect from target rect (e.g. due
// to invalidation of a region, we need to know how much of
// SourceGraphic is needed to draw that region accurately),
// so we need to walk the DAG in reverse and accumulate the source
// subregion for each input onto the referenced node, which can then
// propagate that to its inputs when it is iterated.
let mut source_subregion = LayoutRect::zero();
let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] =
[LayoutRect::zero(); BUFFER_LIMIT];
let final_buffer_id = filters.len() - 1;
assert!(final_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
subregion_by_buffer_id[final_buffer_id] = surface_rect;
for (node_buffer_id, (node, op)) in filters.iter().enumerate().rev() {
// This is the subregion this node outputs, we can clip
// the inputs based on source_padding relative to this,
// and accumulate a new subregion for them.
assert!(node_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
let full_subregion = node.subregion;
let mut used_subregion =
subregion_by_buffer_id[node_buffer_id];
// We can clip the propagated subregion to the node subregion before
// we add source_padding for each input and propogate to them
used_subregion = used_subregion
.intersection(&full_subregion)
.unwrap_or(LayoutRect::zero());
if !used_subregion.is_empty() {
for input in &node.inputs {
let input_subregion = LayoutRect::new(
LayoutPoint::new(
used_subregion.min.x + input.source_padding.min.x,
used_subregion.min.y + input.source_padding.min.y,
),
LayoutPoint::new(
used_subregion.max.x + input.source_padding.max.x,
used_subregion.max.y + input.source_padding.max.y,
),
);
match input.buffer_id {
FilterOpGraphPictureBufferId::BufferId(id) => {
assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
// This id lookup should always succeed.
let input_subregion = subregion_by_buffer_id[id as usize];
// Now add the padding that transforms from
// source to target, this was determined during
// scene build based on the operation.
let input_subregion =
LayoutRect::new(
LayoutPoint::new(
input_subregion.min.x + input.target_padding.min.x,
input_subregion.min.y + input.target_padding.min.y,
),
LayoutPoint::new(
input_subregion.max.x + input.target_padding.max.x,
input_subregion.max.y + input.target_padding.max.y,
),
);
used_subregion = used_subregion
// Add the used area to the input, later when
// the referneced node is iterated as a node it
// will propagate the used bounds.
subregion_by_buffer_id[id as usize] =
subregion_by_buffer_id[id as usize]
.union(&input_subregion);
}
FilterOpGraphPictureBufferId::None => {
panic!("Unsupported BufferId type");
}
FilterOpGraphPictureBufferId::None => {}
}
}
// We can clip the used subregion.
if !skip_subregion_clips {
used_subregion = used_subregion
.intersection(&full_subregion)
.unwrap_or(LayoutRect::zero());
}
match op {
FilterGraphOp::SVGFEBlendColor => {}
FilterGraphOp::SVGFEBlendColorBurn => {}
FilterGraphOp::SVGFEBlendColorDodge => {}
FilterGraphOp::SVGFEBlendDarken => {}
FilterGraphOp::SVGFEBlendDifference => {}
FilterGraphOp::SVGFEBlendExclusion => {}
FilterGraphOp::SVGFEBlendHardLight => {}
FilterGraphOp::SVGFEBlendHue => {}
FilterGraphOp::SVGFEBlendLighten => {}
FilterGraphOp::SVGFEBlendLuminosity => {}
FilterGraphOp::SVGFEBlendMultiply => {}
FilterGraphOp::SVGFEBlendNormal => {}
FilterGraphOp::SVGFEBlendOverlay => {}
FilterGraphOp::SVGFEBlendSaturation => {}
FilterGraphOp::SVGFEBlendScreen => {}
FilterGraphOp::SVGFEBlendSoftLight => {}
FilterGraphOp::SVGFEColorMatrix { values } => {
if values[3] != 0.0 ||
values[7] != 0.0 ||
values[11] != 0.0 ||
values[19] != 0.0 {
// Manipulating alpha can easily create new
// pixels outside of input subregions
used_subregion = full_subregion;
}
}
FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
FilterGraphOp::SVGFEComponentTransferInterned{handle: _, creates_pixels} => {
// Check if the value of alpha[0] is modified, if so
// the whole subregion is used because it will be
// creating new pixels outside of input subregions
if *creates_pixels {
used_subregion = full_subregion;
}
}
FilterGraphOp::SVGFECompositeArithmetic { k1, k2, k3, k4 } => {
// Optimization opportunity - some inputs may be
// smaller subregions due to the way the math works,
// k1 is the intersection of the two inputs, k2 is
// the first input only, k3 is the second input
// only, and k4 changes the whole subregion.
//
// See logic for SVG_FECOMPOSITE_OPERATOR_ARITHMETIC
// in FilterSupport.cpp
//
// We can at least ignore the entire node if
// everything is zero.
if *k1 <= 0.0 &&
*k2 <= 0.0 &&
*k3 <= 0.0 {
used_subregion = LayoutRect::zero();
}
// Check if alpha is added to pixels as it means it
// can fill pixels outside input subregions
if *k4 > 0.0 {
used_subregion = full_subregion;
}
}
FilterGraphOp::SVGFECompositeATop => {}
FilterGraphOp::SVGFECompositeIn => {}
FilterGraphOp::SVGFECompositeLighter => {}
FilterGraphOp::SVGFECompositeOut => {}
FilterGraphOp::SVGFECompositeOver => {}
FilterGraphOp::SVGFECompositeXOR => {}
FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => {}
FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => {}
FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => {}
FilterGraphOp::SVGFEDiffuseLightingDistant{..} => {}
FilterGraphOp::SVGFEDiffuseLightingPoint{..} => {}
FilterGraphOp::SVGFEDiffuseLightingSpot{..} => {}
FilterGraphOp::SVGFEDisplacementMap{..} => {}
FilterGraphOp::SVGFEDropShadow{..} => {}
FilterGraphOp::SVGFEFlood { color } => {
// Subregion needs to be set to the full node
// subregion for fills (unless the fill is a no-op)
if color.a > 0.0 {
used_subregion = full_subregion;
}
}
FilterGraphOp::SVGFEGaussianBlur{..} => {}
FilterGraphOp::SVGFEIdentity => {}
FilterGraphOp::SVGFEImage { sampling_filter: _sampling_filter, matrix: _matrix } => {
// TODO: calculate the actual subregion
used_subregion = full_subregion;
}
FilterGraphOp::SVGFEMorphologyDilate{..} => {}
FilterGraphOp::SVGFEMorphologyErode{..} => {}
FilterGraphOp::SVGFEOpacity { valuebinding: _valuebinding, value } => {
// If fully transparent, we can ignore this node
if *value <= 0.0 {
used_subregion = LayoutRect::zero();
}
}
FilterGraphOp::SVGFESourceAlpha |
FilterGraphOp::SVGFESourceGraphic => {
used_subregion = source_rect;
}
FilterGraphOp::SVGFESpecularLightingDistant{..} => {}
FilterGraphOp::SVGFESpecularLightingPoint{..} => {}
FilterGraphOp::SVGFESpecularLightingSpot{..} => {}
FilterGraphOp::SVGFETile => {
// feTile fills the entire output with
// source pixels, so it's effectively a flood.
used_subregion = full_subregion;
}
FilterGraphOp::SVGFEToAlpha => {}
FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} |
FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} |
FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} |
FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => {
// Turbulence produces pixel values throughout the
// node subregion.
used_subregion = full_subregion;
}
}
// Store the subregion so later nodes can refer back
// to this and propagate rects properly
assert!((id as usize) < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
subregion_by_buffer_id[id] = used_subregion;
}
subregion_by_buffer_id[filters.len() - 1]
// If this is the SourceGraphic or SourceAlpha, we now have the
// source subregion we're looking for. If both exist in the
// same graph, we need to combine them, so don't merely replace.
match op {
FilterGraphOp::SVGFESourceAlpha |
FilterGraphOp::SVGFESourceGraphic => {
source_subregion = source_subregion.union(&used_subregion);
}
_ => {}
}
}
fn calc_source_from_target(
target_rect: LayoutRect,
filters: &[(FilterGraphNode, FilterGraphOp)],
skip_subregion_clips: bool,
) -> LayoutRect {
// We're solving the source rect from target rect (e.g. due
// to invalidation of a region, we need to know how much of
// SourceGraphic is needed to draw that region accurately),
// so we need to walk the DAG in reverse and accumulate the source
// subregion for each input onto the referenced node, which can then
// propagate that to its inputs when it is iterated.
let mut source_subregion = LayoutRect::zero();
let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] =
[LayoutRect::zero(); BUFFER_LIMIT];
let final_buffer_id = filters.len() - 1;
assert!(final_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
subregion_by_buffer_id[final_buffer_id] = target_rect;
for (node_buffer_id, (node, op)) in filters.iter().enumerate().rev() {
// This is the subregion this node outputs, we can clip
// the inputs based on source_padding relative to this,
// and accumulate a new subregion for them.
assert!(node_buffer_id < BUFFER_LIMIT, "BUFFER_LIMIT must be the same in frame building and scene building");
let full_subregion = node.subregion;
let mut used_subregion =
subregion_by_buffer_id[node_buffer_id];
// We can clip the used subregion.
if !skip_subregion_clips {
used_subregion = used_subregion
.intersection(&full_subregion)
.unwrap_or(LayoutRect::zero());
}
if !used_subregion.is_empty() {
for input in &node.inputs {
let input_subregion = LayoutRect::new(
LayoutPoint::new(
used_subregion.min.x + input.source_padding.min.x,
used_subregion.min.y + input.source_padding.min.y,
),
LayoutPoint::new(
used_subregion.max.x + input.source_padding.max.x,
used_subregion.max.y + input.source_padding.max.y,
),
);
match input.buffer_id {
FilterOpGraphPictureBufferId::BufferId(id) => {
// Add the used area to the input, later when
// the referneced node is iterated as a node it
// will propagate the used bounds.
subregion_by_buffer_id[id as usize] =
subregion_by_buffer_id[id as usize]
.union(&input_subregion);
}
FilterOpGraphPictureBufferId::None => {}
}
}
}
// If this is the SourceGraphic, we now have the subregion.
match op {
FilterGraphOp::SVGFESourceAlpha |
FilterGraphOp::SVGFESourceGraphic => {
source_subregion = used_subregion;
}
_ => {}
}
}
// Note that this can be zero if SourceGraphic is not in the graph.
source_subregion
}
let (source, target) = match surface_rect_is_source {
true => {
// If we have a surface_rect for SourceGraphic, transform
// it to a target rect, and then transform the target
// rect back to a source rect (because blurs need the
// source to be enlarged).
let target = calc_target_from_source(surface_rect, filters, skip_subregion_clips);
let source = calc_source_from_target(target, filters, skip_subregion_clips);
(source, target)
}
false => {
// If we have a surface_rect for invalidation of target,
// we want to calculate the source rect from it
let target = surface_rect;
let source = calc_source_from_target(target, filters, skip_subregion_clips);
(source, target)
}
};
// Combine the source and target rect because other code assumes just
// a single rect expanded for blurs
let combined = source.union(&target);
(combined, source, target)
// Note that this can be zero if SourceGraphic/SourceAlpha is not used
// in this graph.
source_subregion
}
}
@ -6274,13 +6264,29 @@ impl PicturePrimitive {
PictureCompositeMode::SVGFEGraph(ref filters) => {
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
// Whole target without regard to clipping.
let prim_subregion = surface_rects.unclipped;
// Visible (clipped) subregion within prim_subregion.
let target_subregion = surface_rects.clipped;
// Subregion of the SourceGraphic that we need to render
// all pixels within target_subregion.
let source_subregion = surface_rects.source;
// Produce the source pixels, this task will be consumed
// by the RenderTask graph we build
let source_task_size = source_subregion.round_out().size().to_i32();
let source_task_size = if source_task_size.width > 0 && source_task_size.height > 0 {
source_task_size
} else {
DeviceIntSize::new(1,1)
};
let picture_task_id = frame_state.rg_builder.add().init(
RenderTask::new_dynamic(
surface_rects.task_size,
source_task_size,
RenderTaskKind::new_picture(
surface_rects.task_size,
source_task_size,
surface_rects.needs_scissor_rect,
surface_rects.clipped.min,
source_subregion.min,
surface_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
@ -6290,18 +6296,22 @@ impl PicturePrimitive {
cmd_buffer_index,
can_use_shared_surface,
)
).with_uv_rect_kind(surface_rects.uv_rect_kind)
)
);
// Produce the target pixels, this is the result of the
// composite op
let filter_task_id = RenderTask::new_svg_filter_graph(
filters,
frame_state,
data_stores,
surface_rects.uv_rect_kind,
picture_task_id,
surface_rects.task_size,
surface_rects.clipped,
surface_rects.clipped_local,
source_subregion.cast_unit(),
target_subregion.cast_unit(),
prim_subregion.cast_unit(),
surface_rects.clipped.cast_unit(),
surface_rects.clipped_local.cast_unit(),
);
primary_render_task_id = filter_task_id;
@ -7687,38 +7697,55 @@ fn get_surface_rects(
let surface = &mut surfaces[surface_index.0];
let (clipped_local, unclipped_local) = match composite_mode {
let (clipped_local, unclipped_local, source_local) = match composite_mode {
PictureCompositeMode::SVGFEGraph(ref filters) => {
// We need to get the primitive rect, and get_coverage for
// SVGFEGraph requires the provided rect is in user space (defined
// in SVG spec) for subregion calculations to work properly
let clipped: LayoutRect = surface.clipped_local_rect
.cast_unit();
let unclipped: LayoutRect = surface.unclipped_local_rect
.cast_unit();
// Get the rects of SourceGraphic and target based on the local rect
// and clip rect.
let (coverage, _source, target) = composite_mode.get_coverage_svgfe(
filters, clipped, true, false);
// If no part of the source rect contributes to target pixels, we're
// done here; this is the hot path for quick culling of composited
// pictures, where the view doesn't overlap the target.
// We need to get the primitive rect, and get_coverage_target_svgfe
// requires the provided rect is in user space (defined in SVG spec)
// for subregion calculations to work properly
//
// Note that the filter may contain fill regions such as feFlood
// which do not depend on the source at all, so the source rect is
// largely irrelevant to our decision here as it may be empty.
if target.is_empty() {
// Calculate the target rect from source rect, note that this can
// produce a valid target rect even with an empty source rect in the
// case of filters like feFlood, feComponentTransfer, feColorMatrix,
// feImage and feTurbulence which can fill their whole subregion
// even if given empty SourceGraphic. It can also produce a smaller
// rect than source if subregions or filter region apply clipping to
// the intermediate pictures or the final picture.
let prim_subregion = composite_mode.get_rect(surface, None);
// Clip the prim_subregion by the clip_rect, this will be put into
// surface_rects.clipped.
let visible_subregion: LayoutRect =
prim_subregion.cast_unit()
.intersection(&local_clip_rect)
.unwrap_or(PictureRect::zero())
.cast_unit();
// If the visible_subregion was empty to begin with, or clipped away
// entirely, then there is nothing to do here, this is the hot path
// for culling of composited pictures.
if visible_subregion.is_empty() {
return None;
}
// Since the design of WebRender PictureCompositeMode does not
// actually permit source and target rects as separate concepts, we
// have to use the combined coverage rect.
let clipped = coverage;
// Calculate the subregion for how much of SourceGraphic we may need
// to produce to satisfy the invalidation rect, then clip it by the
// original primitive rect because we have no reason to produce any
// out of bounds pixels; they would just be blank anyway.
let source_potential_subregion = composite_mode.get_coverage_source_svgfe(
filters, visible_subregion.cast_unit());
let source_subregion =
source_potential_subregion
.intersection(&surface.unclipped_local_rect.cast_unit())
.unwrap_or(LayoutRect::zero());
(clipped.cast_unit(), unclipped)
// For some reason, code assumes that the clipped_local rect we make
// here will enclose the source_subregion, and also be a valid
// prim_subregion, so we have to union the two rects to meet those
// expectations. This is an optimization opportunity - figure out
// how to make just the visible_subregion work here.
let coverage_subregion = source_subregion.union(&visible_subregion);
(coverage_subregion.cast_unit(), prim_subregion.cast_unit(), source_subregion.cast_unit())
}
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let local_prim_rect = surface.clipped_local_rect;
@ -7756,7 +7783,7 @@ fn get_surface_rects(
None => return None,
};
(clipped, unclipped)
(clipped, unclipped, clipped)
}
_ => {
let surface_origin = surface.clipped_local_rect.min.to_vector().cast_unit();
@ -7784,11 +7811,11 @@ fn get_surface_rects(
let unclipped = normalized_prim_rect.translate(surface_origin);
let clipped = norm_clipped_rect.translate(surface_origin);
(clipped.cast_unit(), unclipped.cast_unit())
(clipped.cast_unit(), unclipped.cast_unit(), clipped.cast_unit())
}
};
let (mut clipped, mut unclipped) = if surface.raster_spatial_node_index != surface.surface_spatial_node_index {
let (mut clipped, mut unclipped, mut source) = if surface.raster_spatial_node_index != surface.surface_spatial_node_index {
assert_eq!(surface.device_pixel_scale.0, 1.0);
let local_to_world = SpaceMapper::new_with_target(
@ -7800,25 +7827,41 @@ fn get_surface_rects(
let clipped = (local_to_world.map(&clipped_local.cast_unit()).unwrap() * surface.device_pixel_scale).round_out();
let unclipped = local_to_world.map(&unclipped_local).unwrap() * surface.device_pixel_scale;
let source = (local_to_world.map(&source_local.cast_unit()).unwrap() * surface.device_pixel_scale).round_out();
(clipped, unclipped)
(clipped, unclipped, source)
} else {
let clipped = (clipped_local.cast_unit() * surface.device_pixel_scale).round_out();
let unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
let source = (source_local.cast_unit() * surface.device_pixel_scale).round_out();
(clipped, unclipped)
(clipped, unclipped, source)
};
let task_size_f = clipped.size();
if task_size_f.width > max_surface_size || task_size_f.height > max_surface_size {
let max_dimension = clipped_local.width().max(clipped_local.height()).ceil();
// Limit rendering extremely large pictures to something the hardware can
// handle, considering both clipped (target subregion) and source subregion.
//
// If you change this, test with:
// ./mach crashtest layout/svg/crashtests/387290-1.svg
let max_dimension =
clipped.width().max(
clipped.height().max(
source.width().max(
source.height()
))).ceil();
if max_dimension > max_surface_size {
let max_dimension =
clipped_local.width().max(
clipped_local.height().max(
source_local.width().max(
source_local.height()
))).ceil();
surface.raster_spatial_node_index = surface.surface_spatial_node_index;
surface.device_pixel_scale = Scale::new(max_surface_size / max_dimension);
clipped = (clipped_local.cast_unit() * surface.device_pixel_scale).round();
unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
source = (source_local.cast_unit() * surface.device_pixel_scale).round();
}
let task_size = clipped.size().to_i32();
@ -7848,6 +7891,7 @@ fn get_surface_rects(
needs_scissor_rect,
clipped,
unclipped,
source,
clipped_local,
uv_rect_kind,
})

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{CompositeOperator, FilterPrimitive, FilterPrimitiveInput, FilterPrimitiveKind};
use api::{CompositeOperator, FilterPrimitive, FilterPrimitiveInput, FilterPrimitiveKind, SVGFE_GRAPH_MAX};
use api::{LineStyle, LineOrientation, ClipMode, MixBlendMode, ColorF, ColorSpace, FilterOpGraphPictureBufferId};
use api::MAX_RENDER_TASK_SIZE;
use api::units::*;
@ -1644,13 +1644,15 @@ impl RenderTask {
filter_nodes: &[(FilterGraphNode, FilterGraphOp)],
frame_state: &mut FrameBuildingState,
data_stores: &mut DataStores,
uv_rect_kind: UvRectKind,
_uv_rect_kind: UvRectKind,
original_task_id: RenderTaskId,
surface_rects_task_size: DeviceIntSize,
surface_rects_clipped: DeviceRect,
surface_rects_clipped_local: PictureRect,
source_subregion: LayoutRect,
target_subregion: LayoutRect,
prim_subregion: LayoutRect,
surface_rects_clipped: LayoutRect,
surface_rects_clipped_local: LayoutRect,
) -> RenderTaskId {
const BUFFER_LIMIT: usize = 256;
const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
let mut task_by_buffer_id: [RenderTaskId; BUFFER_LIMIT] = [RenderTaskId::INVALID; BUFFER_LIMIT];
let mut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] = [LayoutRect::zero(); BUFFER_LIMIT];
// If nothing replaces this value (all node subregions are empty), we
@ -1667,40 +1669,6 @@ impl RenderTask {
// These assumptions are verified with asserts in this function as
// appropriate.
// Converts a UvRectKind::Quad to a subregion, we need this for
// SourceGraphic because it could source from a larger image when doing
// a dirty rect update. In theory this can be used for blur output as
// well but it doesn't seem to be necessary from early testing.
//
// See calculate_uv_rect_kind in picture.rs for how these were generated.
fn subregion_for_uvrectkind(kind: &UvRectKind, rect: LayoutRect) -> LayoutRect {
let used =
match kind {
UvRectKind::Quad{top_left: tl, top_right: _tr, bottom_left: _bl, bottom_right: br} => {
LayoutRect::new(
LayoutPoint::new(
rect.min.x + rect.width() * tl.x / tl.w,
rect.min.y + rect.height() * tl.y / tl.w,
),
LayoutPoint::new(
rect.min.x + rect.width() * br.x / br.w,
rect.min.y + rect.height() * br.y / br.w,
),
)
}
UvRectKind::Rect => {
rect
}
};
// For some reason, the following test passes a uv_rect_kind that
// resolves to [-.2, -.2, -.2, -.2]
// reftest layout/reftests/svg/filters/dynamic-filter-invalidation-01.svg
match used.is_empty() {
true => rect,
false => used,
}
}
// Make a UvRectKind::Quad that represents a task for a node, which may
// have an inflate border, must be a Quad because the surface_rects
// compositing shader expects it to be one, we don't actually use this
@ -1708,24 +1676,7 @@ impl RenderTask {
// this works, it projects from clipped rect to unclipped rect, where
// our clipped rect is simply task_size minus the inflate, and unclipped
// is our full task_size
fn uv_rect_kind_for_task_size(task_size: DeviceIntSize, inflate: i16) -> UvRectKind {
let unclipped = DeviceRect::new(
DevicePoint::new(
inflate as f32,
inflate as f32,
),
DevicePoint::new(
task_size.width as f32 - inflate as f32,
task_size.height as f32 - inflate as f32,
),
);
let clipped = DeviceRect::new(
DevicePoint::zero(),
DevicePoint::new(
task_size.width as f32,
task_size.height as f32,
),
);
fn uv_rect_kind_for_task_size(clipped: DeviceRect, unclipped: DeviceRect) -> UvRectKind {
let scale_x = 1.0 / clipped.width();
let scale_y = 1.0 / clipped.height();
UvRectKind::Quad{
@ -1762,29 +1713,6 @@ impl RenderTask {
let subregion_to_device_offset_x = surface_rects_clipped.min.x - (surface_rects_clipped_local.min.x * subregion_to_device_scale_x).floor();
let subregion_to_device_offset_y = surface_rects_clipped.min.y - (surface_rects_clipped_local.min.y * subregion_to_device_scale_y).floor();
// We will treat the entire SourceGraphic coordinate space as being this
// subregion, which is how large the source picture task is.
let filter_subregion: LayoutRect = surface_rects_clipped.cast_unit();
// Calculate the used subregion (invalidation rect) for SourceGraphic
// that we are painting for, the intermediate task sizes are based on
// this portion of SourceGraphic, this also serves as a clip on the
// SourceGraphic, which is necessary for this reftest:
// layout/reftests/svg/filters/svg-filter-chains/clip-original-SourceGraphic.svg
let source_subregion =
subregion_for_uvrectkind(
&uv_rect_kind,
surface_rects_clipped.cast_unit(),
)
.intersection(&filter_subregion)
.unwrap_or(LayoutRect::zero())
.round_out();
// This is the rect for the output picture we are producing
let output_rect = filter_subregion.to_i32();
// Output to the same subregion we were provided
let output_subregion = filter_subregion;
// Iterate the filter nodes and create tasks
let mut made_dependency_on_source = false;
for (filter_index, (filter_node, op)) in filter_nodes.iter().enumerate() {
@ -2108,7 +2036,11 @@ impl RenderTask {
.round();
// Clip the used subregion we calculated from the inputs to fit
// within the node's specified subregion.
// within the node's specified subregion, but we want to keep a copy
// of the combined input subregion for sizing tasks that involve
// blurs as their intermediate stages will have to be downscaled if
// very large, and we want that to be at the same alignment as the
// node output itself.
used_subregion = used_subregion
.intersection(&full_subregion)
.unwrap_or(LayoutRect::zero())
@ -2159,10 +2091,10 @@ impl RenderTask {
// in FilterSupport.cpp for more information.
//
// Any other case uses the union of input subregions
if k4 != 0.0 {
if k4 > 0.0 {
// Can produce pixels anywhere in the subregion.
used_subregion = full_subregion;
} else if k1 != 0.0 && k2 == 0.0 && k3 == 0.0 && k4 == 0.0 {
} else if k1 > 0.0 && k2 == 0.0 && k3 == 0.0 {
// Can produce pixels where both exist.
used_subregion = full_subregion
.intersection(&node_inputs[0].0.subregion)
@ -2170,13 +2102,13 @@ impl RenderTask {
.intersection(&node_inputs[1].0.subregion)
.unwrap_or(LayoutRect::zero());
}
else if k2 != 0.0 && k3 == 0.0 && k4 == 0.0 {
else if k2 > 0.0 && k3 == 0.0 {
// Can produce pixels where source exists.
used_subregion = full_subregion
.intersection(&node_inputs[0].0.subregion)
.unwrap_or(LayoutRect::zero());
}
else if k2 == 0.0 && k3 != 0.0 && k4 == 0.0 {
else if k2 == 0.0 && k3 > 0.0 {
// Can produce pixels where background exists.
used_subregion = full_subregion
.intersection(&node_inputs[1].0.subregion)
@ -2239,7 +2171,9 @@ impl RenderTask {
},
FilterGraphOp::SVGFESourceAlpha |
FilterGraphOp::SVGFESourceGraphic => {
used_subregion = source_subregion;
used_subregion = source_subregion
.intersection(&full_subregion)
.unwrap_or(LayoutRect::zero());
},
FilterGraphOp::SVGFESpecularLightingDistant{..} => {},
FilterGraphOp::SVGFESpecularLightingPoint{..} => {},
@ -2262,65 +2196,90 @@ impl RenderTask {
},
}
// If this is the output node, apply the output clip.
let node_inflate = node.inflate;
let mut create_output_task = false;
if is_output {
// If we're drawing a subregion that encloses output_subregion
// we can just crop the node to output_subregion.
if used_subregion.to_i32().contains_box(&output_rect) {
used_subregion = output_subregion;
} else {
// We'll have to create an extra blit task after this task
// so that there is transparent black padding around it.
create_output_task = true;
}
}
// Convert subregion from layout pixels to integer device pixels and
// then calculate size afterwards so it reflects the used pixel area
//
// This can be an empty rect if the source_subregion invalidation
// rect didn't request any pixels of this node, but we can't skip
// creating tasks that have no size because they would leak in the
// render task graph with no consumers
let node_task_rect: DeviceIntRect = used_subregion.to_i32().cast_unit();
let mut node_task_size = node_task_rect.size().cast_unit();
// We have to limit the render target sizes we're asking for on the
// intermediate nodes; it's not feasible to allocate extremely large
// surfaces. Note that the SVGFEFilterTask code can adapt to any
// scaling that we use here, input subregions simply have to be in
// the same space as the target subregion, which we're not changing,
// and operator parameters like kernel_unit_length are also in that
// space. Blurs will do this same logic if their intermediate is
// too large. We use a simple halving calculation here so that
// pixel alignment is still vaguely sensible.
while node_task_size.width as usize + node_inflate as usize * 2 > MAX_SURFACE_SIZE ||
node_task_size.height as usize + node_inflate as usize * 2 > MAX_SURFACE_SIZE {
node_task_size.width >>= 1;
node_task_size.height >>= 1;
}
// SVG spec requires that a later node sampling pixels outside
// this node's subregion will receive a transparent black color
// for those samples, we achieve this by adding a 1 pixel border
// around the target rect, which works fine with the clamping of the
// texture fetch in the shader, and to account for the offset we
// have to make a UvRectKind::Quad mapping for later nodes to use
// when sampling this output, if they use feOffset or have a
// larger target rect those samples will be clamped to the
// transparent black border and thus meet spec.
node_task_size.width += node_inflate as i32 * 2;
node_task_size.height += node_inflate as i32 * 2;
// for those samples, we achieve this by adding a 1 pixel inflate
// around the target rect, which works fine with the
// edgemode=duplicate behavior of the texture fetch in the shader,
// all of the out of bounds reads are transparent black.
//
// If this is the output node, we don't apply the inflate, knowing
// that the pixels outside of the invalidation rect will not be used
// so it is okay if they duplicate outside the view.
let mut node_inflate = node.inflate;
if is_output {
// Use the provided target subregion (invalidation rect)
used_subregion = target_subregion;
node_inflate = 0;
}
// We can't render tasks larger than a certain size, if this node
// is too large (particularly with blur padding), we need to render
// at a reduced resolution, later nodes can still be full resolution
// but for example blurs are not significantly harmed by reduced
// resolution in most cases.
let mut device_to_render_scale = 1.0;
let mut render_to_device_scale = 1.0;
let mut subregion = used_subregion;
let padded_subregion = match op {
FilterGraphOp::SVGFEGaussianBlur{std_deviation_x, std_deviation_y} |
FilterGraphOp::SVGFEDropShadow{std_deviation_x, std_deviation_y, ..} => {
used_subregion
.inflate(
std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
std_deviation_y.ceil() * BLUR_SAMPLE_SCALE)
}
_ => used_subregion,
};
while
padded_subregion.scale(device_to_render_scale, device_to_render_scale).round().width() + node_inflate as f32 * 2.0 > MAX_SURFACE_SIZE as f32 ||
padded_subregion.scale(device_to_render_scale, device_to_render_scale).round().height() + node_inflate as f32 * 2.0 > MAX_SURFACE_SIZE as f32 {
device_to_render_scale *= 0.5;
render_to_device_scale *= 2.0;
// If the rendering was scaled, we need to snap used_subregion
// to the correct granularity or we'd have misaligned sampling
// when this is used as an input later.
subregion = used_subregion
.scale(device_to_render_scale, device_to_render_scale)
.round()
.scale(render_to_device_scale, render_to_device_scale);
}
// This is the rect we will be actually producing as a render task,
// it is sometimes the case that subregion is empty, but we
// must make a task or else the earlier tasks would not be properly
// linked into the frametree, causing a leak.
let node_task_rect: DeviceRect =
subregion
.scale(device_to_render_scale, device_to_render_scale)
.round()
.inflate(node_inflate as f32, node_inflate as f32)
.cast_unit();
let node_task_size = node_task_rect.to_i32().size();
let node_task_size =
if node_task_size.width < 1 || node_task_size.height < 1 {
DeviceIntSize::new(1, 1)
} else {
node_task_size
};
// Make the uv_rect_kind for this node's task to use, this matters
// only on the final node because we don't use it internally
let node_uv_rect_kind =
uv_rect_kind_for_task_size(node_task_size, node_inflate);
let node_uv_rect_kind = uv_rect_kind_for_task_size(
subregion
.scale(device_to_render_scale, device_to_render_scale)
.round()
.inflate(node_inflate as f32, node_inflate as f32)
.cast_unit(),
prim_subregion
.scale(device_to_render_scale, device_to_render_scale)
.round()
.inflate(node_inflate as f32, node_inflate as f32)
.cast_unit(),
);
// Create task for this node
let mut task_id;
let task_id;
match op {
FilterGraphOp::SVGFEGaussianBlur { std_deviation_x, std_deviation_y } => {
// Note: wrap_prim_with_filters copies the SourceGraphic to
@ -2336,65 +2295,49 @@ impl RenderTask {
// We have to make a copy of the input that is padded with
// transparent black for the area outside the subregion, so
// that the blur task does not duplicate at the edges, and
// this is also where we have to adjust size to account for
// for downscaling of the image in the blur task to avoid
// introducing sampling artifacts on the downscale
let mut adjusted_blur_std_deviation = DeviceSize::new(
std_deviation_x,
std_deviation_y,
// that the blur task does not duplicate at the edges
let adjusted_blur_std_deviation = DeviceSize::new(
std_deviation_x.clamp(0.0, (i32::MAX / 2) as f32) * device_to_render_scale,
std_deviation_y.clamp(0.0, (i32::MAX / 2) as f32) * device_to_render_scale,
);
let blur_subregion = blur_input.subregion
.scale(device_to_render_scale, device_to_render_scale)
.inflate(
std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
std_deviation_y.ceil() * BLUR_SAMPLE_SCALE);
adjusted_blur_std_deviation.width * BLUR_SAMPLE_SCALE,
adjusted_blur_std_deviation.height * BLUR_SAMPLE_SCALE)
.round_out();
let blur_task_size = blur_subregion.size().cast_unit();
// Adjust task size to prevent potential sampling errors
let mut adjusted_blur_task_size =
let adjusted_blur_task_size =
BlurTask::adjusted_blur_source_size(
blur_task_size,
adjusted_blur_std_deviation,
);
).to_f32();
// Now change the subregion to match the revised task size,
// keeping it centered should keep animated radius smooth.
let corner = LayoutPoint::new(
blur_subregion.min.x + ((
blur_task_size.width as i32 -
adjusted_blur_task_size.width) / 2) as f32,
blur_subregion.min.y + ((
blur_task_size.height as i32 -
adjusted_blur_task_size.height) / 2) as f32,
blur_subregion.min.x.floor() + ((
blur_task_size.width -
adjusted_blur_task_size.width) * 0.5).floor(),
blur_subregion.min.y.floor() + ((
blur_task_size.height -
adjusted_blur_task_size.height) * 0.5).floor(),
);
// Recalculate the blur_subregion to match, and if render
// scale is used, undo that so it is in the same subregion
// coordinate system as the node
let blur_subregion =
LayoutRect::new(
corner,
LayoutPoint::new(
corner.x + adjusted_blur_task_size.width,
corner.y + adjusted_blur_task_size.height,
),
)
.floor();
// Recalculate the blur_subregion to match, note that if the
// task was downsized it doesn't affect the size of this
// rect, so we don't have to scale blur_input.subregion for
// input purposes as they are the same scale.
let blur_subregion = LayoutRect::new(
corner,
LayoutPoint::new(
corner.x + adjusted_blur_task_size.width as f32,
corner.y + adjusted_blur_task_size.height as f32,
),
);
// For extremely large blur radius we have to limit size,
// see comments on node_task_size above for more details.
while adjusted_blur_task_size.to_i32().width as usize > MAX_SURFACE_SIZE ||
adjusted_blur_task_size.to_i32().height as usize > MAX_SURFACE_SIZE {
adjusted_blur_task_size.width >>= 1;
adjusted_blur_task_size.height >>= 1;
adjusted_blur_std_deviation.width *= 0.5;
adjusted_blur_std_deviation.height *= 0.5;
if adjusted_blur_task_size.width < 2 {
adjusted_blur_task_size.width = 2;
}
if adjusted_blur_task_size.height < 2 {
adjusted_blur_task_size.height = 2;
}
}
.scale(render_to_device_scale, render_to_device_scale);
let input_subregion_task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
adjusted_blur_task_size,
adjusted_blur_task_size.to_i32(),
RenderTaskKind::SVGFENode(
SVGFEFilterTask{
node: FilterGraphNode{
@ -2425,7 +2368,7 @@ impl RenderTask {
frame_state.rg_builder,
RenderTargetKind::Color,
None,
adjusted_blur_task_size,
adjusted_blur_task_size.to_i32(),
);
task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
@ -2445,10 +2388,10 @@ impl RenderTask {
source_padding: LayoutRect::zero(),
target_padding: LayoutRect::zero(),
}].to_vec(),
subregion: used_subregion,
subregion,
},
op: FilterGraphOp::SVGFEIdentity,
content_origin: DevicePoint::zero(),
content_origin: node_task_rect.min,
extra_gpu_cache_handle: None,
}
),
@ -2470,65 +2413,49 @@ impl RenderTask {
// We have to make a copy of the input that is padded with
// transparent black for the area outside the subregion, so
// that the blur task does not duplicate at the edges, and
// this is also where we have to adjust size to account for
// for downscaling of the image in the blur task to avoid
// introducing sampling artifacts on the downscale
let mut adjusted_blur_std_deviation = DeviceSize::new(
std_deviation_x,
std_deviation_y,
// that the blur task does not duplicate at the edges
let adjusted_blur_std_deviation = DeviceSize::new(
std_deviation_x.clamp(0.0, (i32::MAX / 2) as f32) * device_to_render_scale,
std_deviation_y.clamp(0.0, (i32::MAX / 2) as f32) * device_to_render_scale,
);
let blur_subregion = blur_input.subregion
.scale(device_to_render_scale, device_to_render_scale)
.inflate(
std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
std_deviation_y.ceil() * BLUR_SAMPLE_SCALE);
adjusted_blur_std_deviation.width * BLUR_SAMPLE_SCALE,
adjusted_blur_std_deviation.height * BLUR_SAMPLE_SCALE)
.round_out();
let blur_task_size = blur_subregion.size().cast_unit();
// Adjust task size to prevent potential sampling errors
let mut adjusted_blur_task_size =
let adjusted_blur_task_size =
BlurTask::adjusted_blur_source_size(
blur_task_size,
adjusted_blur_std_deviation,
);
).to_f32();
// Now change the subregion to match the revised task size,
// keeping it centered should keep animated radius smooth.
let corner = LayoutPoint::new(
blur_subregion.min.x + ((
blur_task_size.width as i32 -
adjusted_blur_task_size.width) / 2) as f32,
blur_subregion.min.y + ((
blur_task_size.height as i32 -
adjusted_blur_task_size.height) / 2) as f32,
blur_subregion.min.x.floor() + ((
blur_task_size.width -
adjusted_blur_task_size.width) * 0.5).floor(),
blur_subregion.min.y.floor() + ((
blur_task_size.height -
adjusted_blur_task_size.height) * 0.5).floor(),
);
// Recalculate the blur_subregion to match, and if render
// scale is used, undo that so it is in the same subregion
// coordinate system as the node
let blur_subregion =
LayoutRect::new(
corner,
LayoutPoint::new(
corner.x + adjusted_blur_task_size.width,
corner.y + adjusted_blur_task_size.height,
),
)
.floor();
// Recalculate the blur_subregion to match, note that if the
// task was downsized it doesn't affect the size of this
// rect, so we don't have to scale blur_input.subregion for
// input purposes as they are the same scale.
let blur_subregion = LayoutRect::new(
corner,
LayoutPoint::new(
corner.x + adjusted_blur_task_size.width as f32,
corner.y + adjusted_blur_task_size.height as f32,
),
);
// For extremely large blur radius we have to limit size,
// see comments on node_task_size above for more details.
while adjusted_blur_task_size.to_i32().width as usize > MAX_SURFACE_SIZE ||
adjusted_blur_task_size.to_i32().height as usize > MAX_SURFACE_SIZE {
adjusted_blur_task_size.width >>= 1;
adjusted_blur_task_size.height >>= 1;
adjusted_blur_std_deviation.width *= 0.5;
adjusted_blur_std_deviation.height *= 0.5;
if adjusted_blur_task_size.width < 2 {
adjusted_blur_task_size.width = 2;
}
if adjusted_blur_task_size.height < 2 {
adjusted_blur_task_size.height = 2;
}
}
.scale(render_to_device_scale, render_to_device_scale);
let input_subregion_task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
adjusted_blur_task_size,
adjusted_blur_task_size.to_i32(),
RenderTaskKind::SVGFENode(
SVGFEFilterTask{
node: FilterGraphNode{
@ -2547,7 +2474,7 @@ impl RenderTask {
inflate: 0,
},
op: FilterGraphOp::SVGFEIdentity,
content_origin: DevicePoint::zero(),
content_origin: node_task_rect.min,
extra_gpu_cache_handle: None,
}
),
@ -2566,12 +2493,12 @@ impl RenderTask {
frame_state.rg_builder,
RenderTargetKind::Color,
None,
adjusted_blur_task_size,
adjusted_blur_task_size.to_i32(),
);
// Now we make the compositing task, for this we need to put
// the blurred shadow image at the correct subregion offset
let blur_subregion = blur_subregion
let blur_subregion_translated = blur_subregion
.translate(LayoutVector2D::new(dx, dy));
task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
node_task_size,
@ -2587,13 +2514,13 @@ impl RenderTask {
// Shadow picture
FilterGraphPictureReference{
buffer_id: blur_input.buffer_id,
subregion: blur_subregion,
subregion: blur_subregion_translated,
inflate: 0,
offset: LayoutVector2D::zero(),
source_padding: LayoutRect::zero(),
target_padding: LayoutRect::zero(),
}].to_vec(),
subregion: used_subregion,
subregion,
},
op: FilterGraphOp::SVGFEDropShadow{
color,
@ -2601,7 +2528,7 @@ impl RenderTask {
dx: 0.0, dy: 0.0,
std_deviation_x: 0.0, std_deviation_y: 0.0,
},
content_origin: DevicePoint::zero(),
content_origin: node_task_rect.min,
extra_gpu_cache_handle: None,
}
),
@ -2628,22 +2555,18 @@ impl RenderTask {
FilterGraphPictureReference{
buffer_id: FilterOpGraphPictureBufferId::None,
// This is what makes the mapping
// actually work - this has to be
// the subregion of the whole filter
// because that is the size of the
// input task, it will be cropped to
// the used area (source_subregion).
subregion: filter_subregion,
// actually work.
subregion: source_subregion.cast_unit(),
offset: LayoutVector2D::zero(),
inflate: 0,
source_padding: LayoutRect::zero(),
target_padding: LayoutRect::zero(),
}
].to_vec(),
subregion: used_subregion,
subregion: source_subregion.cast_unit(),
},
op: op.clone(),
content_origin: DevicePoint::zero(),
content_origin: source_subregion.min.cast_unit(),
extra_gpu_cache_handle: None,
}
),
@ -2666,11 +2589,11 @@ impl RenderTask {
kept_by_optimizer: true,
linear: node.linear,
inputs: node_inputs.iter().map(|input| {input.0}).collect(),
subregion: used_subregion,
subregion,
inflate: node_inflate,
},
op: op.clone(),
content_origin: DevicePoint::zero(),
content_origin: node_task_rect.min,
extra_gpu_cache_handle: Some(filter_data.gpu_cache_handle),
}
),
@ -2698,11 +2621,11 @@ impl RenderTask {
kept_by_optimizer: true,
linear: node.linear,
inputs: node_inputs.iter().map(|input| {input.0}).collect(),
subregion: used_subregion,
subregion,
inflate: node_inflate,
},
op: op.clone(),
content_origin: DevicePoint::zero(),
content_origin: node_task_rect.min,
extra_gpu_cache_handle: None,
}
),
@ -2725,46 +2648,10 @@ impl RenderTask {
// to look them up quickly, since nodes can only depend on previous
// nodes in the same list
task_by_buffer_id[filter_index] = task_id;
subregion_by_buffer_id[filter_index] = used_subregion;
subregion_by_buffer_id[filter_index] = subregion;
// The final task we create is the output picture.
output_task_id = task_id;
if create_output_task {
// If the final node subregion is smaller than the output rect,
// we need to pad it with transparent black to match SVG spec,
// as the output task rect is larger than the invalidated area,
// ideally the origin and size of the picture we return should
// be used instead of the get_rect result for sizing geometry,
// as it would allow us to produce a much smaller rect.
let output_uv_rect_kind =
uv_rect_kind_for_task_size(surface_rects_task_size, 0);
task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
surface_rects_task_size,
RenderTaskKind::SVGFENode(
SVGFEFilterTask{
node: FilterGraphNode{
kept_by_optimizer: true,
linear: false,
inputs: [FilterGraphPictureReference{
buffer_id: FilterOpGraphPictureBufferId::None,
subregion: used_subregion,
offset: LayoutVector2D::zero(),
inflate: node_inflate,
source_padding: LayoutRect::zero(),
target_padding: LayoutRect::zero(),
}].to_vec(),
subregion: output_subregion,
inflate: 0,
},
op: FilterGraphOp::SVGFEIdentity,
content_origin: surface_rects_clipped.min,
extra_gpu_cache_handle: None,
}
),
).with_uv_rect_kind(output_uv_rect_kind));
frame_state.rg_builder.add_dependency(task_id, output_task_id);
output_task_id = task_id;
}
}
// If no tasks referenced the SourceGraphic, we actually have to create

View File

@ -45,7 +45,7 @@ use api::{PropertyBinding, ReferenceFrameKind, ScrollFrameDescriptor};
use api::{APZScrollGeneration, HasScrollLinkedEffect, Shadow, SpatialId, StickyFrameDescriptor, ImageMask, ItemTag};
use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData};
use api::{ReferenceTransformBinding, Rotation, FillRule, SpatialTreeItem, ReferenceFrameDescriptor};
use api::FilterOpGraphPictureBufferId;
use api::{FilterOpGraphPictureBufferId, SVGFE_GRAPH_MAX};
use api::channel::{unbounded_channel, Receiver, Sender};
use api::units::*;
use crate::image_tiling::simplify_repeated_primitive;
@ -3838,7 +3838,7 @@ impl<'a> SceneBuilder<'a> {
// The SVG spec allows us to drop the entire filter graph if it is
// unreasonable, so we limit the number of filters in a graph
const BUFFER_LIMIT: usize = 256;
const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX;
// Easily tunable for debugging proper handling of inflated rects,
// this should normally be 1
const SVGFE_INFLATE: i16 = 1;

View File

@ -1243,6 +1243,12 @@ pub struct FilterOpGraphNode {
pub subregion: LayoutRect,
}
/// Maximum number of SVGFE filters in one graph, this is constant size to avoid
/// allocating anything, and the SVG spec allows us to drop all filters on an
/// item if the graph is excessively complex - a graph this large will never be
/// a good user experience, performance-wise.
pub const SVGFE_GRAPH_MAX: usize = 256;
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PeekPoke)]
pub enum FilterOp {

View File

@ -3,6 +3,7 @@ fuzzy(2,10000) == svgfe-blendmultiply-linear.yaml svgfe-blendmultiply-linear-ref
fuzzy(2,10000) == svgfe-blendnormal-linear.yaml svgfe-blendnormal-linear-ref.yaml
fuzzy(2,10000) == svgfe-colormatrix.yaml svgfe-colormatrix-ref.yaml
fuzzy(4,10000) == svgfe-dropshadow.yaml svgfe-dropshadow-ref.yaml
fuzzy(12,10000) == svgfe-dropshadow-offscreen-source.yaml svgfe-dropshadow-offscreen-source-ref.yaml
fuzzy(2,10000) == svgfe-opacity-linear.yaml svgfe-opacity-linear-ref.yaml
fuzzy(2,10000) == svgfe-opacity.yaml svgfe-opacity-ref.yaml
fuzzy(12,10000) == svgfe-subregion-bigger.yaml svgfe-subregion-bigger-ref.yaml

View File

@ -0,0 +1,16 @@
---
root:
items:
- type: stacking-context
bounds: [-130, -94, 510, 510]
filters: drop-shadow([40, 40], 10, red)
items:
- type: stacking-context
bounds: [10, 10, 200, 200]
items:
- type: rect
bounds: [10, 10, 100, 100]
color: [127, 127, 127, 1]
- type: rect
bounds: [50, 30, 100, 60]
color: [192, 192, 192, 1]

View File

@ -0,0 +1,27 @@
---
root:
items:
- type: stacking-context
bounds: [-130, -94, 510, 510]
filters:
- svgfe: SourceGraphic
- svgfe: SourceAlpha
- svgfe: dropshadow
linear: false
subregion: [-200, -200, 510, 510]
color: red
dx: 40
dy: 40
std_deviation_x: 10
std_deviation_y: 10
in: 0
items:
- type: stacking-context
bounds: [10, 10, 200, 200]
items:
- type: rect
bounds: [10, 10, 100, 100]
color: [127, 127, 127, 1]
- type: rect
bounds: [50, 30, 100, 60]
color: [192, 192, 192, 1]

View File

@ -5,7 +5,7 @@
# for some reason this test ends up with a subpixel misalignment for the '.'
# character when using gfx.webrender.svg-filters.enable=true, but other text
# works, however the text is not the purpose of this test, so just use fuzzy.
fuzzy(0-73,0-8) == blend-difference-stacking.html blend-difference-stacking-ref.html
fuzzy(0-143,0-16) == blend-difference-stacking.html blend-difference-stacking-ref.html
fuzzy(0-1,0-30000) == background-blending-alpha.html background-blending-alpha-ref.html
== background-blending-gradient-color.html background-blending-gradient-color-ref.html

View File

@ -12,7 +12,7 @@ fuzzy-if(winWidget,47-132,47-54) == element-paint-simple.html element-paint-simp
== element-paint-recursion.html element-paint-recursion-ref.html
== element-paint-continuation.html element-paint-continuation-ref.html
== element-paint-transform-01.html element-paint-transform-01-ref.html
random-if(winWidget&&!gfxSVGFE) fuzzy-if(!useDrawSnapshot,255-255,39-42) fuzzy-if(gfxSVGFE,0-201,0-1486) == element-paint-transform-02.html element-paint-transform-02-ref.html # bug 587133
random-if(winWidget&&!gfxSVGFE) fuzzy-if(!useDrawSnapshot,255-255,39-42) fuzzy-if(gfxSVGFE,0-255,0-1983) == element-paint-transform-02.html element-paint-transform-02-ref.html # bug 587133
== element-paint-background-size-01.html element-paint-background-size-01-ref.html
== element-paint-background-size-02.html element-paint-background-size-02-ref.html
fuzzy(0-255,0-4) == element-paint-transform-repeated.html element-paint-transform-repeated-ref.html # Bug 1475907

View File

@ -4,13 +4,13 @@
== clip-input.svg clip-input-ref.svg
== clip-original-SourceGraphic.svg clip-original-SourceGraphic-ref.svg
== clip-output.svg clip-output-ref.svg
fuzzy(0-5,0-20300) fuzzy-if(Android&&device&&!swgl,5-5,21751-21751) fuzzy-if(gfxSVGFE,0-1,0-1000000) == default-subregion.svg default-subregion-ref.svg
fuzzy(0-5,0-20300) fuzzy-if(Android&&device&&!swgl,5-5,21751-21751) fuzzy-if(gfxSVGFE,0-9,0-1000000) == default-subregion.svg default-subregion-ref.svg
== different-FillPaint-filter-regions.svg different-FillPaint-filter-regions-ref.svg
== different-StrokePaint-filter-regions.svg different-StrokePaint-filter-regions-ref.svg
== dont-clip-previous-primitives.svg dont-clip-previous-primitives-ref.svg
== intersecting-filter-regions.svg intersecting-filter-regions-ref.svg
fuzzy-if(!useDrawSnapshot,9-9,5168-5536) fuzzy-if(!useDrawSnapshot&&swgl,7-7,13170-13184) fuzzy-if(Android&&device&&!swgl,8-8,12391-12391) fuzzy-if(gfxSVGFE,0-0,0-0) == long-chain.svg simple-chain-ref.svg
fuzzy-if(!useDrawSnapshot,9-9,5168-5536) fuzzy-if(!useDrawSnapshot&&swgl,7-7,13170-13184) fuzzy-if(Android&&device&&!swgl,8-8,12391-12391) fuzzy-if(gfxSVGFE,0-0,0-0) == multiple-primitives-per-filter.svg simple-chain-ref.svg
fuzzy-if(winWidget,0-1,0-173) fuzzy-if(!useDrawSnapshot||(winWidget&&isCoverageBuild),9-9,5128-5496) fuzzy-if(!useDrawSnapshot&&swgl,7-7,12820-12830) fuzzy-if(Android&&device&&!swgl,8-8,12355-12355) fuzzy-if(gfxSVGFE,0-1,0-1000000) == second-filter-uses-SourceAlpha.svg second-filter-uses-SourceAlpha-ref.svg
fuzzy-if(!useDrawSnapshot,9-9,5168-5536) fuzzy-if(!useDrawSnapshot&&swgl,7-7,13170-13180) fuzzy-if(Android&&device&&!swgl,8-8,12391-12391) fuzzy-if(gfxSVGFE,0-0,0-0) == second-filter-uses-SourceGraphic.svg simple-chain-ref.svg
fuzzy-if(!useDrawSnapshot,9-9,5168-5536) fuzzy-if(!useDrawSnapshot&&swgl,7-7,13170-13184) fuzzy-if(Android&&device&&!swgl,8-8,12391-12391) fuzzy-if(gfxSVGFE,0-9,0-1000000) == long-chain.svg simple-chain-ref.svg
fuzzy-if(!useDrawSnapshot,9-9,5168-5536) fuzzy-if(!useDrawSnapshot&&swgl,7-7,13170-13184) fuzzy-if(Android&&device&&!swgl,8-8,12391-12391) fuzzy-if(gfxSVGFE,0-9,0-1000000) == multiple-primitives-per-filter.svg simple-chain-ref.svg
fuzzy-if(winWidget,0-1,0-173) fuzzy-if(!useDrawSnapshot||(winWidget&&isCoverageBuild),9-9,5128-5496) fuzzy-if(!useDrawSnapshot&&swgl,7-7,12820-12830) fuzzy-if(Android&&device&&!swgl,8-8,12355-12355) fuzzy-if(gfxSVGFE,0-9,0-1000000) == second-filter-uses-SourceAlpha.svg second-filter-uses-SourceAlpha-ref.svg
fuzzy-if(!useDrawSnapshot,9-9,5168-5536) fuzzy-if(!useDrawSnapshot&&swgl,7-7,13170-13180) fuzzy-if(Android&&device&&!swgl,8-8,12391-12391) fuzzy-if(gfxSVGFE,0-9,0-1000000) == second-filter-uses-SourceGraphic.svg simple-chain-ref.svg
== simple-chain.svg simple-chain-ref.svg

View File

@ -1261,7 +1261,7 @@ WrFiltersStatus FilterInstance::BuildWebRenderSVGFiltersImpl(
// We have to remap the input nodes to a possibly larger number of output
// nodes due to expanding feMerge.
static constexpr size_t maxFilters = 256;
static constexpr size_t maxFilters = wr::SVGFE_GRAPH_MAX;
int16_t bufferIdMapping[maxFilters];
// Just drop the graph if there are too many filters to process.
if (instance.mFilterDescription.mPrimitives.Length() > maxFilters) {

View File

@ -6659,7 +6659,7 @@
- name: gfx.webrender.svg-filter-effects
type: RelaxedAtomicBool
value: @IS_NIGHTLY_BUILD@
value: true
mirror: always
- name: gfx.webrender.svg-filter-effects.also-convert-css-filters
@ -6747,10 +6747,9 @@
value: false
mirror: always
# Disabled for now due to https://bugzilla.mozilla.org/show_bug.cgi?id=1902136
- name: gfx.webrender.svg-filter-effects.feoffset
type: RelaxedAtomicBool
value: false
value: true
mirror: always
- name: gfx.webrender.svg-filter-effects.fespecularlighting

View File

@ -1,4 +0,0 @@
[filter-region-transformed-child-001.html]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1906212
expected:
if not nightly_build: FAIL