mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 00:32:11 +00:00
Bug 1905611 - apply clipping to PictureCompositeMode::SVGFEGraph r=gfx-reviewers,gw
Differential Revision: https://phabricator.services.mozilla.com/D216637
This commit is contained in:
parent
203042622b
commit
c976164f43
@ -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,
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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]
|
@ -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]
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user