summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src')
-rw-r--r--gfx/wr/webrender/src/batch.rs69
-rw-r--r--gfx/wr/webrender/src/frame_builder.rs1
-rw-r--r--gfx/wr/webrender/src/gpu_types.rs20
-rw-r--r--gfx/wr/webrender/src/internal_types.rs628
-rw-r--r--gfx/wr/webrender/src/pattern.rs21
-rw-r--r--gfx/wr/webrender/src/picture.rs439
-rw-r--r--gfx/wr/webrender/src/prepare.rs135
-rw-r--r--gfx/wr/webrender/src/prim_store/gradient/conic.rs49
-rw-r--r--gfx/wr/webrender/src/prim_store/gradient/linear.rs6
-rw-r--r--gfx/wr/webrender/src/prim_store/gradient/radial.rs50
-rw-r--r--gfx/wr/webrender/src/prim_store/image.rs6
-rw-r--r--gfx/wr/webrender/src/prim_store/mod.rs16
-rw-r--r--gfx/wr/webrender/src/prim_store/picture.rs768
-rw-r--r--gfx/wr/webrender/src/quad.rs284
-rw-r--r--gfx/wr/webrender/src/render_target.rs196
-rw-r--r--gfx/wr/webrender/src/render_task.rs1295
-rw-r--r--gfx/wr/webrender/src/render_task_cache.rs7
-rw-r--r--gfx/wr/webrender/src/render_task_graph.rs20
-rw-r--r--gfx/wr/webrender/src/renderer/mod.rs49
-rw-r--r--gfx/wr/webrender/src/renderer/shade.rs48
-rw-r--r--gfx/wr/webrender/src/renderer/vertex.rs55
-rw-r--r--gfx/wr/webrender/src/scene_building.rs513
-rw-r--r--gfx/wr/webrender/src/spatial_node.rs59
-rw-r--r--gfx/wr/webrender/src/spatial_tree.rs122
-rw-r--r--gfx/wr/webrender/src/tile_cache.rs8
25 files changed, 4593 insertions, 271 deletions
diff --git a/gfx/wr/webrender/src/batch.rs b/gfx/wr/webrender/src/batch.rs
index 7cf9341515..78adcd036b 100644
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -437,16 +437,19 @@ impl OpaqueBatchList {
// `current_batch_index` instead of iterating the batches.
z_bounding_rect: &PictureRect,
) -> &mut Vec<PrimitiveInstanceData> {
- if self.current_batch_index == usize::MAX ||
+ // If the area of this primitive is larger than the given threshold,
+ // then it is large enough to warrant breaking a batch for. In this
+ // case we just see if it can be added to the existing batch or
+ // create a new one.
+ let is_large_occluder = z_bounding_rect.area() > self.pixel_area_threshold_for_new_batch;
+ // Since primitives of the same kind tend to come in succession, we keep track
+ // of the current batch index to skip the search in some cases. We ignore the
+ // current batch index in the case of large occluders to make sure they get added
+ // at the top of the bach list.
+ if is_large_occluder || self.current_batch_index == usize::MAX ||
!self.batches[self.current_batch_index].key.is_compatible_with(&key) {
let mut selected_batch_index = None;
- let item_area = z_bounding_rect.area();
-
- // If the area of this primitive is larger than the given threshold,
- // then it is large enough to warrant breaking a batch for. In this
- // case we just see if it can be added to the existing batch or
- // create a new one.
- if item_area > self.pixel_area_threshold_for_new_batch {
+ if is_large_occluder {
if let Some(batch) = self.batches.last() {
if batch.key.is_compatible_with(&key) {
selected_batch_index = Some(self.batches.len() - 1);
@@ -1742,7 +1745,8 @@ impl BatchBuilder {
Filter::ComponentTransfer |
Filter::Blur { .. } |
Filter::DropShadows(..) |
- Filter::Opacity(..) => unreachable!(),
+ Filter::Opacity(..) |
+ Filter::SVGGraphNode(..) => unreachable!(),
};
// Other filters that may introduce opacity are handled via different
@@ -2183,6 +2187,53 @@ impl BatchBuilder {
uv_rect_address.as_int(),
);
}
+ PictureCompositeMode::SVGFEGraph(..) => {
+ let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
+ prim_info.clip_task_index,
+ render_tasks,
+ ).unwrap();
+
+ let kind = BatchKind::Brush(
+ BrushBatchKind::Image(ImageBufferKind::Texture2D)
+ );
+ let (uv_rect_address, texture) = render_tasks.resolve_location(
+ pic_task_id,
+ gpu_cache,
+ ).unwrap();
+ let textures = BatchTextures::prim_textured(
+ texture,
+ clip_mask_texture_id,
+ );
+ let key = BatchKey::new(
+ kind,
+ blend_mode,
+ textures,
+ );
+ let prim_header_index = prim_headers.push(
+ &prim_header,
+ z_id,
+ self.batcher.render_task_address,
+ ImageBrushData {
+ color_mode: ShaderColorMode::Image,
+ alpha_type: AlphaType::PremultipliedAlpha,
+ raster_space: RasterizationSpace::Screen,
+ opacity: 1.0,
+ }.encode(),
+ );
+
+ self.add_brush_instance_to_batches(
+ key,
+ batch_features,
+ bounding_rect,
+ z_id,
+ INVALID_SEGMENT_INDEX,
+ EdgeAaSegmentMask::all(),
+ clip_task_address,
+ brush_flags,
+ prim_header_index,
+ uv_rect_address.as_int(),
+ );
+ }
}
}
None => {
diff --git a/gfx/wr/webrender/src/frame_builder.rs b/gfx/wr/webrender/src/frame_builder.rs
index b975c960eb..c9e66d2aff 100644
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -453,6 +453,7 @@ impl FrameBuilder {
SubpixelMode::Allow,
&mut frame_state,
&frame_context,
+ data_stores,
&mut scratch.primitive,
tile_caches,
)
diff --git a/gfx/wr/webrender/src/gpu_types.rs b/gfx/wr/webrender/src/gpu_types.rs
index e222ebed04..38e7fbb717 100644
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -129,6 +129,21 @@ pub struct SvgFilterInstance {
pub extra_data_address: GpuCacheAddress,
}
+#[derive(Clone, Debug)]
+#[repr(C)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SVGFEFilterInstance {
+ pub target_rect: DeviceRect,
+ pub input_1_content_scale_and_offset: [f32; 4],
+ pub input_2_content_scale_and_offset: [f32; 4],
+ pub input_1_task_address: RenderTaskAddress,
+ pub input_2_task_address: RenderTaskAddress,
+ pub kind: u16,
+ pub input_count: u16,
+ pub extra_data_address: GpuCacheAddress,
+}
+
#[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
#[repr(C)]
#[cfg_attr(feature = "capture", derive(Serialize))]
@@ -535,7 +550,7 @@ impl From<SplitCompositeInstance> for PrimitiveInstanceData {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct QuadInstance {
- pub render_task_address: RenderTaskAddress,
+ pub dst_task_address: RenderTaskAddress,
pub prim_address_i: GpuBufferAddress,
pub prim_address_f: GpuBufferAddress,
pub z_id: ZBufferId,
@@ -565,12 +580,13 @@ impl From<QuadInstance> for PrimitiveInstanceData {
((instance.part_index as i32) << 8) |
((instance.segment_index as i32) << 0),
- instance.render_task_address.0,
+ instance.dst_task_address.0,
],
}
}
}
+#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct QuadSegment {
pub rect: LayoutRect,
diff --git a/gfx/wr/webrender/src/internal_types.rs b/gfx/wr/webrender/src/internal_types.rs
index 97827a98fe..660f8d6da1 100644
--- a/gfx/wr/webrender/src/internal_types.rs
+++ b/gfx/wr/webrender/src/internal_types.rs
@@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{ColorF, DocumentId, ExternalImageId, PrimitiveFlags, Parameter, RenderReasons};
-use api::{ImageFormat, NotificationRequest, Shadow, FilterOp, ImageBufferKind};
+use api::{ImageFormat, NotificationRequest, Shadow, FilterOpGraphPictureBufferId, FilterOpGraphPictureReference, FilterOpGraphNode, FilterOp, ImageBufferKind};
use api::FramePublishId;
use api::units::*;
use crate::render_api::DebugCommand;
@@ -15,6 +15,7 @@ use crate::frame_builder::Frame;
use crate::profiler::TransactionProfile;
use crate::spatial_tree::SpatialNodeIndex;
use crate::prim_store::PrimitiveInstanceIndex;
+use crate::filterdata::FilterDataHandle;
use fxhash::FxHasher;
use plane_split::BspSplitter;
use smallvec::SmallVec;
@@ -208,8 +209,557 @@ pub struct PlaneSplitterIndex(pub usize);
/// An arbitrary number which we assume opacity is invisible below.
const OPACITY_EPSILON: f32 = 0.001;
+#[derive(Clone, Copy, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FilterGraphPictureReference {
+ /// Id of the picture in question in a namespace unique to this filter DAG,
+ /// some are special values like
+ /// FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic.
+ pub buffer_id: FilterOpGraphPictureBufferId,
+ /// Set by wrap_prim_with_filters to the subregion of the input node, may
+ /// also have been offset for feDropShadow or feOffset
+ pub subregion: LayoutRect,
+ /// During scene build this is the offset to apply to the input subregion
+ /// for feOffset, which can be optimized away by pushing its offset and
+ /// subregion crop to downstream nodes. This is always zero in render tasks
+ /// where it has already been applied to subregion by that point. Not used
+ /// in get_coverage_svgfe because source_padding/target_padding represent
+ /// the offset there.
+ pub offset: LayoutVector2D,
+ /// Equal to the inflate value of the referenced buffer, or 0
+ pub inflate: i16,
+ /// Padding on each side to represent how this input is read relative to the
+ /// node's output subregion, this represents what the operation needs to
+ /// read from ths input, which may be blurred or offset.
+ pub source_padding: LayoutRect,
+ /// Padding on each side to represent how this input affects the node's
+ /// subregion, this can be used to calculate target subregion based on
+ /// SourceGraphic subregion. This is usually equal to source_padding except
+ /// offset in the opposite direction, inflates typically do the same thing
+ /// to both types of padding.
+ pub target_padding: LayoutRect,
+}
+
+impl From<FilterOpGraphPictureReference> for FilterGraphPictureReference {
+ fn from(pic: FilterOpGraphPictureReference) -> Self {
+ FilterGraphPictureReference{
+ buffer_id: pic.buffer_id,
+ // All of these are set by wrap_prim_with_filters
+ subregion: LayoutRect::zero(),
+ offset: LayoutVector2D::zero(),
+ inflate: 0,
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ }
+ }
+}
+
+pub const SVGFE_CONVOLVE_DIAMETER_LIMIT: usize = 5;
+pub const SVGFE_CONVOLVE_VALUES_LIMIT: usize = SVGFE_CONVOLVE_DIAMETER_LIMIT *
+ SVGFE_CONVOLVE_DIAMETER_LIMIT;
+
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum FilterGraphOp {
+ /// Filter that copies the SourceGraphic image into the specified subregion,
+ /// This is intentionally the only way to get SourceGraphic into the graph,
+ /// as the filter region must be applied before it is used.
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - no inputs, no linear
+ SVGFESourceGraphic,
+ /// Filter that copies the SourceAlpha image into the specified subregion,
+ /// This is intentionally the only way to get SourceAlpha into the graph,
+ /// as the filter region must be applied before it is used.
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - no inputs, no linear
+ SVGFESourceAlpha,
+ /// Filter that does no transformation of the colors, used to implement a
+ /// few things like SVGFEOffset, and this is the default value in
+ /// impl_default_for_enums.
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input with offset
+ SVGFEIdentity,
+ /// represents CSS opacity property as a graph node like the rest of the
+ /// SVGFE* filters
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ SVGFEOpacity{valuebinding: api::PropertyBinding<f32>, value: f32},
+ /// convert a color image to an alpha channel - internal use; generated by
+ /// SVGFilterInstance::GetOrCreateSourceAlphaIndex().
+ SVGFEToAlpha,
+ /// combine 2 images with SVG_FEBLEND_MODE_DARKEN
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendDarken,
+ /// combine 2 images with SVG_FEBLEND_MODE_LIGHTEN
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendLighten,
+ /// combine 2 images with SVG_FEBLEND_MODE_MULTIPLY
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendMultiply,
+ /// combine 2 images with SVG_FEBLEND_MODE_NORMAL
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendNormal,
+ /// combine 2 images with SVG_FEBLEND_MODE_SCREEN
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendScreen,
+ /// combine 2 images with SVG_FEBLEND_MODE_OVERLAY
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendOverlay,
+ /// combine 2 images with SVG_FEBLEND_MODE_COLOR_DODGE
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendColorDodge,
+ /// combine 2 images with SVG_FEBLEND_MODE_COLOR_BURN
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendColorBurn,
+ /// combine 2 images with SVG_FEBLEND_MODE_HARD_LIGHT
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendHardLight,
+ /// combine 2 images with SVG_FEBLEND_MODE_SOFT_LIGHT
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendSoftLight,
+ /// combine 2 images with SVG_FEBLEND_MODE_DIFFERENCE
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendDifference,
+ /// combine 2 images with SVG_FEBLEND_MODE_EXCLUSION
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendExclusion,
+ /// combine 2 images with SVG_FEBLEND_MODE_HUE
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendHue,
+ /// combine 2 images with SVG_FEBLEND_MODE_SATURATION
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendSaturation,
+ /// combine 2 images with SVG_FEBLEND_MODE_COLOR
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendColor,
+ /// combine 2 images with SVG_FEBLEND_MODE_LUMINOSITY
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendLuminosity,
+ /// transform colors of image through 5x4 color matrix (transposed for
+ /// efficiency)
+ /// parameters: FilterGraphNode, matrix[5][4]
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feColorMatrixElement
+ SVGFEColorMatrix{values: [f32; 20]},
+ /// transform colors of image through configurable gradients with component
+ /// swizzle
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feComponentTransferElement
+ SVGFEComponentTransfer,
+ /// Processed version of SVGFEComponentTransfer with the FilterData
+ /// replaced by an interned handle, this is made in wrap_prim_with_filters.
+ /// Aside from the interned handle, creates_pixels indicates if the transfer
+ /// parameters will probably fill the entire subregion with non-zero alpha.
+ SVGFEComponentTransferInterned{handle: FilterDataHandle, creates_pixels: bool},
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode, k1, k2, k3, k4
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeArithmetic{k1: f32, k2: f32, k3: f32, k4: f32},
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeATop,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeIn,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Docs: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite
+ SVGFECompositeLighter,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeOut,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeOver,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeXOR,
+ /// transform image through convolution matrix of up to 25 values (spec
+ /// allows more but for performance reasons we do not)
+ /// parameters: FilterGraphNode, orderX, orderY, kernelValues[25], divisor,
+ /// bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
+ /// preserveAlpha
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
+ SVGFEConvolveMatrixEdgeModeDuplicate{order_x: i32, order_y: i32,
+ kernel: [f32; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: f32, bias: f32,
+ target_x: i32, target_y: i32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, preserve_alpha: i32},
+ /// transform image through convolution matrix of up to 25 values (spec
+ /// allows more but for performance reasons we do not)
+ /// parameters: FilterGraphNode, orderX, orderY, kernelValues[25], divisor,
+ /// bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
+ /// preserveAlpha
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
+ SVGFEConvolveMatrixEdgeModeNone{order_x: i32, order_y: i32,
+ kernel: [f32; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: f32, bias: f32,
+ target_x: i32, target_y: i32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, preserve_alpha: i32},
+ /// transform image through convolution matrix of up to 25 values (spec
+ /// allows more but for performance reasons we do not)
+ /// parameters: FilterGraphNode, orderX, orderY, kernelValues[25], divisor,
+ /// bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
+ /// preserveAlpha
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
+ SVGFEConvolveMatrixEdgeModeWrap{order_x: i32, order_y: i32,
+ kernel: [f32; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: f32, bias: f32,
+ target_x: i32, target_y: i32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, preserve_alpha: i32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// distant light source with specified direction
+ /// parameters: FilterGraphNode, surfaceScale, diffuseConstant,
+ /// kernelUnitLengthX, kernelUnitLengthY, azimuth, elevation
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDistantLightElement
+ SVGFEDiffuseLightingDistant{surface_scale: f32, diffuse_constant: f32,
+ kernel_unit_length_x: f32, kernel_unit_length_y: f32, azimuth: f32,
+ elevation: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// point light source at specified location
+ /// parameters: FilterGraphNode, surfaceScale, diffuseConstant,
+ /// kernelUnitLengthX, kernelUnitLengthY, x, y, z
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEPointLightElement
+ SVGFEDiffuseLightingPoint{surface_scale: f32, diffuse_constant: f32,
+ kernel_unit_length_x: f32, kernel_unit_length_y: f32, x: f32, y: f32,
+ z: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// spot light source at specified location pointing at specified target
+ /// location with specified hotspot sharpness and cone angle
+ /// parameters: FilterGraphNode, surfaceScale, diffuseConstant,
+ /// kernelUnitLengthX, kernelUnitLengthY, x, y, z, pointsAtX, pointsAtY,
+ /// pointsAtZ, specularExponent, limitingConeAngle
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpotLightElement
+ SVGFEDiffuseLightingSpot{surface_scale: f32, diffuse_constant: f32,
+ kernel_unit_length_x: f32, kernel_unit_length_y: f32, x: f32, y: f32,
+ z: f32, points_at_x: f32, points_at_y: f32, points_at_z: f32,
+ cone_exponent: f32, limiting_cone_angle: f32},
+ /// calculate a distorted version of first input image using offset values
+ /// from second input image at specified intensity
+ /// parameters: FilterGraphNode, scale, xChannelSelector, yChannelSelector
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDisplacementMapElement
+ SVGFEDisplacementMap{scale: f32, x_channel_selector: u32,
+ y_channel_selector: u32},
+ /// create and merge a dropshadow version of the specified image's alpha
+ /// channel with specified offset and blur radius
+ /// parameters: FilterGraphNode, flood_color, flood_opacity, dx, dy,
+ /// stdDeviationX, stdDeviationY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDropShadowElement
+ SVGFEDropShadow{color: ColorF, dx: f32, dy: f32, std_deviation_x: f32,
+ std_deviation_y: f32},
+ /// synthesize a new image of specified size containing a solid color
+ /// parameters: FilterGraphNode, color
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEFloodElement
+ SVGFEFlood{color: ColorF},
+ /// create a blurred version of the input image
+ /// parameters: FilterGraphNode, stdDeviationX, stdDeviationY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEGaussianBlurElement
+ SVGFEGaussianBlur{std_deviation_x: f32, std_deviation_y: f32},
+ /// synthesize a new image based on a url (i.e. blob image source)
+ /// parameters: FilterGraphNode,
+ /// samplingFilter (see SamplingFilter in Types.h), transform
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEImageElement
+ SVGFEImage{sampling_filter: u32, matrix: [f32; 6]},
+ /// create a new image based on the input image with the contour stretched
+ /// outward (dilate operator)
+ /// parameters: FilterGraphNode, radiusX, radiusY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMorphologyElement
+ SVGFEMorphologyDilate{radius_x: f32, radius_y: f32},
+ /// create a new image based on the input image with the contour shrunken
+ /// inward (erode operator)
+ /// parameters: FilterGraphNode, radiusX, radiusY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMorphologyElement
+ SVGFEMorphologyErode{radius_x: f32, radius_y: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// distant light source with specified direction
+ /// parameters: FilerData, surfaceScale, specularConstant, specularExponent,
+ /// kernelUnitLengthX, kernelUnitLengthY, azimuth, elevation
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDistantLightElement
+ SVGFESpecularLightingDistant{surface_scale: f32, specular_constant: f32,
+ specular_exponent: f32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, azimuth: f32, elevation: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// point light source at specified location
+ /// parameters: FilterGraphNode, surfaceScale, specularConstant,
+ /// specularExponent, kernelUnitLengthX, kernelUnitLengthY, x, y, z
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEPointLightElement
+ SVGFESpecularLightingPoint{surface_scale: f32, specular_constant: f32,
+ specular_exponent: f32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, x: f32, y: f32, z: f32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// spot light source at specified location pointing at specified target
+ /// location with specified hotspot sharpness and cone angle
+ /// parameters: FilterGraphNode, surfaceScale, specularConstant,
+ /// specularExponent, kernelUnitLengthX, kernelUnitLengthY, x, y, z,
+ /// pointsAtX, pointsAtY, pointsAtZ, specularExponent, limitingConeAngle
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpotLightElement
+ SVGFESpecularLightingSpot{surface_scale: f32, specular_constant: f32,
+ specular_exponent: f32, kernel_unit_length_x: f32,
+ kernel_unit_length_y: f32, x: f32, y: f32, z: f32, points_at_x: f32,
+ points_at_y: f32, points_at_z: f32, cone_exponent: f32,
+ limiting_cone_angle: f32},
+ /// create a new image based on the input image, repeated throughout the
+ /// output rectangle
+ /// parameters: FilterGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETileElement
+ SVGFETile,
+ /// synthesize a new image based on Fractal Noise (Perlin) with the chosen
+ /// stitching mode
+ /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
+ /// seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithFractalNoiseWithNoStitching{base_frequency_x: f32,
+ base_frequency_y: f32, num_octaves: u32, seed: u32},
+ /// synthesize a new image based on Fractal Noise (Perlin) with the chosen
+ /// stitching mode
+ /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
+ /// seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithFractalNoiseWithStitching{base_frequency_x: f32,
+ base_frequency_y: f32, num_octaves: u32, seed: u32},
+ /// synthesize a new image based on Turbulence Noise (offset vectors)
+ /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
+ /// seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{base_frequency_x: f32,
+ base_frequency_y: f32, num_octaves: u32, seed: u32},
+ /// synthesize a new image based on Turbulence Noise (offset vectors)
+ /// parameters: FilterGraphNode, baseFrequencyX, baseFrequencyY, numOctaves,
+ /// seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithTurbulenceNoiseWithStitching{base_frequency_x: f32,
+ base_frequency_y: f32, num_octaves: u32, seed: u32},
+}
+
+impl FilterGraphOp {
+ pub fn kind(&self) -> &'static str {
+ match *self {
+ FilterGraphOp::SVGFEBlendColor => "SVGFEBlendColor",
+ FilterGraphOp::SVGFEBlendColorBurn => "SVGFEBlendColorBurn",
+ FilterGraphOp::SVGFEBlendColorDodge => "SVGFEBlendColorDodge",
+ FilterGraphOp::SVGFEBlendDarken => "SVGFEBlendDarken",
+ FilterGraphOp::SVGFEBlendDifference => "SVGFEBlendDifference",
+ FilterGraphOp::SVGFEBlendExclusion => "SVGFEBlendExclusion",
+ FilterGraphOp::SVGFEBlendHardLight => "SVGFEBlendHardLight",
+ FilterGraphOp::SVGFEBlendHue => "SVGFEBlendHue",
+ FilterGraphOp::SVGFEBlendLighten => "SVGFEBlendLighten",
+ FilterGraphOp::SVGFEBlendLuminosity => "SVGFEBlendLuminosity",
+ FilterGraphOp::SVGFEBlendMultiply => "SVGFEBlendMultiply",
+ FilterGraphOp::SVGFEBlendNormal => "SVGFEBlendNormal",
+ FilterGraphOp::SVGFEBlendOverlay => "SVGFEBlendOverlay",
+ FilterGraphOp::SVGFEBlendSaturation => "SVGFEBlendSaturation",
+ FilterGraphOp::SVGFEBlendScreen => "SVGFEBlendScreen",
+ FilterGraphOp::SVGFEBlendSoftLight => "SVGFEBlendSoftLight",
+ FilterGraphOp::SVGFEColorMatrix{..} => "SVGFEColorMatrix",
+ FilterGraphOp::SVGFEComponentTransfer => "SVGFEComponentTransfer",
+ FilterGraphOp::SVGFEComponentTransferInterned{..} => "SVGFEComponentTransferInterned",
+ FilterGraphOp::SVGFECompositeArithmetic{..} => "SVGFECompositeArithmetic",
+ FilterGraphOp::SVGFECompositeATop => "SVGFECompositeATop",
+ FilterGraphOp::SVGFECompositeIn => "SVGFECompositeIn",
+ FilterGraphOp::SVGFECompositeLighter => "SVGFECompositeLighter",
+ FilterGraphOp::SVGFECompositeOut => "SVGFECompositeOut",
+ FilterGraphOp::SVGFECompositeOver => "SVGFECompositeOver",
+ FilterGraphOp::SVGFECompositeXOR => "SVGFECompositeXOR",
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => "SVGFEConvolveMatrixEdgeModeDuplicate",
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => "SVGFEConvolveMatrixEdgeModeNone",
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => "SVGFEConvolveMatrixEdgeModeWrap",
+ FilterGraphOp::SVGFEDiffuseLightingDistant{..} => "SVGFEDiffuseLightingDistant",
+ FilterGraphOp::SVGFEDiffuseLightingPoint{..} => "SVGFEDiffuseLightingPoint",
+ FilterGraphOp::SVGFEDiffuseLightingSpot{..} => "SVGFEDiffuseLightingSpot",
+ FilterGraphOp::SVGFEDisplacementMap{..} => "SVGFEDisplacementMap",
+ FilterGraphOp::SVGFEDropShadow{..} => "SVGFEDropShadow",
+ FilterGraphOp::SVGFEFlood{..} => "SVGFEFlood",
+ FilterGraphOp::SVGFEGaussianBlur{..} => "SVGFEGaussianBlur",
+ FilterGraphOp::SVGFEIdentity => "SVGFEIdentity",
+ FilterGraphOp::SVGFEImage{..} => "SVGFEImage",
+ FilterGraphOp::SVGFEMorphologyDilate{..} => "SVGFEMorphologyDilate",
+ FilterGraphOp::SVGFEMorphologyErode{..} => "SVGFEMorphologyErode",
+ FilterGraphOp::SVGFEOpacity{..} => "SVGFEOpacity",
+ FilterGraphOp::SVGFESourceAlpha => "SVGFESourceAlpha",
+ FilterGraphOp::SVGFESourceGraphic => "SVGFESourceGraphic",
+ FilterGraphOp::SVGFESpecularLightingDistant{..} => "SVGFESpecularLightingDistant",
+ FilterGraphOp::SVGFESpecularLightingPoint{..} => "SVGFESpecularLightingPoint",
+ FilterGraphOp::SVGFESpecularLightingSpot{..} => "SVGFESpecularLightingSpot",
+ FilterGraphOp::SVGFETile => "SVGFETile",
+ FilterGraphOp::SVGFEToAlpha => "SVGFEToAlpha",
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} => "SVGFETurbulenceWithFractalNoiseWithNoStitching",
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} => "SVGFETurbulenceWithFractalNoiseWithStitching",
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} => "SVGFETurbulenceWithTurbulenceNoiseWithNoStitching",
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => "SVGFETurbulenceWithTurbulenceNoiseWithStitching",
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FilterGraphNode {
+ /// Indicates this graph node was marked as necessary by the DAG optimizer
+ pub kept_by_optimizer: bool,
+ /// true if color_interpolation_filter == LinearRgb; shader will convert
+ /// sRGB texture pixel colors on load and convert back on store, for correct
+ /// interpolation
+ pub linear: bool,
+ /// padding for output rect if we need a border to get correct clamping, or
+ /// to account for larger final subregion than source rect (see bug 1869672)
+ pub inflate: i16,
+ /// virtualized picture input bindings, these refer to other filter outputs
+ /// by number within the graph, usually there is one element
+ pub inputs: Vec<FilterGraphPictureReference>,
+ /// clipping rect for filter node output
+ pub subregion: LayoutRect,
+}
+
+impl From<FilterOpGraphNode> for FilterGraphNode {
+ fn from(node: FilterOpGraphNode) -> Self {
+ let mut inputs: Vec<FilterGraphPictureReference> = Vec::new();
+ if node.input.buffer_id != FilterOpGraphPictureBufferId::None {
+ inputs.push(node.input.into());
+ }
+ if node.input2.buffer_id != FilterOpGraphPictureBufferId::None {
+ inputs.push(node.input2.into());
+ }
+ // If the op used by this node is a feMerge, it will add more inputs
+ // after this invocation.
+ FilterGraphNode{
+ linear: node.linear,
+ inputs,
+ subregion: node.subregion,
+ // These are computed later in scene_building
+ kept_by_optimizer: true,
+ inflate: 0,
+ }
+ }
+}
+
+
/// Equivalent to api::FilterOp with added internal information
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum Filter {
@@ -233,6 +783,7 @@ pub enum Filter {
LinearToSrgb,
ComponentTransfer,
Flood(ColorF),
+ SVGGraphNode(FilterGraphNode, FilterGraphOp),
}
impl Filter {
@@ -258,6 +809,7 @@ impl Filter {
Filter::Flood(color) => {
color.a > OPACITY_EPSILON
}
+ Filter::SVGGraphNode(..) => true,
}
}
@@ -296,6 +848,7 @@ impl Filter {
Filter::LinearToSrgb |
Filter::ComponentTransfer |
Filter::Flood(..) => false,
+ Filter::SVGGraphNode(..) => false,
}
}
@@ -319,6 +872,7 @@ impl Filter {
Filter::Blur { .. } => 12,
Filter::DropShadows(..) => 13,
Filter::Opacity(..) => 14,
+ Filter::SVGGraphNode(..) => unreachable!("SVGGraphNode handled elsewhere"),
}
}
}
@@ -342,6 +896,76 @@ impl From<FilterOp> for Filter {
FilterOp::ComponentTransfer => Filter::ComponentTransfer,
FilterOp::DropShadow(shadow) => Filter::DropShadows(smallvec![shadow]),
FilterOp::Flood(color) => Filter::Flood(color),
+ FilterOp::SVGFEBlendColor{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendColor),
+ FilterOp::SVGFEBlendColorBurn{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendColorBurn),
+ FilterOp::SVGFEBlendColorDodge{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendColorDodge),
+ FilterOp::SVGFEBlendDarken{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendDarken),
+ FilterOp::SVGFEBlendDifference{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendDifference),
+ FilterOp::SVGFEBlendExclusion{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendExclusion),
+ FilterOp::SVGFEBlendHardLight{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendHardLight),
+ FilterOp::SVGFEBlendHue{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendHue),
+ FilterOp::SVGFEBlendLighten{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendLighten),
+ FilterOp::SVGFEBlendLuminosity{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendLuminosity),
+ FilterOp::SVGFEBlendMultiply{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendMultiply),
+ FilterOp::SVGFEBlendNormal{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendNormal),
+ FilterOp::SVGFEBlendOverlay{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendOverlay),
+ FilterOp::SVGFEBlendSaturation{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendSaturation),
+ FilterOp::SVGFEBlendScreen{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendScreen),
+ FilterOp::SVGFEBlendSoftLight{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEBlendSoftLight),
+ FilterOp::SVGFEColorMatrix{node, values} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEColorMatrix{values}),
+ FilterOp::SVGFEComponentTransfer{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEComponentTransfer),
+ FilterOp::SVGFECompositeArithmetic{node, k1, k2, k3, k4} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFECompositeArithmetic{k1, k2, k3, k4}),
+ FilterOp::SVGFECompositeATop{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFECompositeATop),
+ FilterOp::SVGFECompositeIn{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFECompositeIn),
+ FilterOp::SVGFECompositeLighter{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFECompositeLighter),
+ FilterOp::SVGFECompositeOut{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFECompositeOut),
+ FilterOp::SVGFECompositeOver{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFECompositeOver),
+ FilterOp::SVGFECompositeXOR{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFECompositeXOR),
+ FilterOp::SVGFEConvolveMatrixEdgeModeDuplicate{node, order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha}),
+ FilterOp::SVGFEConvolveMatrixEdgeModeNone{node, order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha}),
+ FilterOp::SVGFEConvolveMatrixEdgeModeWrap{node, order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha}),
+ FilterOp::SVGFEDiffuseLightingDistant{node, surface_scale, diffuse_constant, kernel_unit_length_x, kernel_unit_length_y, azimuth, elevation} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEDiffuseLightingDistant{surface_scale, diffuse_constant, kernel_unit_length_x, kernel_unit_length_y, azimuth, elevation}),
+ FilterOp::SVGFEDiffuseLightingPoint{node, surface_scale, diffuse_constant, kernel_unit_length_x, kernel_unit_length_y, x, y, z} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEDiffuseLightingPoint{surface_scale, diffuse_constant, kernel_unit_length_x, kernel_unit_length_y, x, y, z}),
+ FilterOp::SVGFEDiffuseLightingSpot{node, surface_scale, diffuse_constant, kernel_unit_length_x, kernel_unit_length_y, x, y, z, points_at_x, points_at_y, points_at_z, cone_exponent, limiting_cone_angle} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEDiffuseLightingSpot{surface_scale, diffuse_constant, kernel_unit_length_x, kernel_unit_length_y, x, y, z, points_at_x, points_at_y, points_at_z, cone_exponent, limiting_cone_angle}),
+ FilterOp::SVGFEDisplacementMap{node, scale, x_channel_selector, y_channel_selector} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEDisplacementMap{scale, x_channel_selector, y_channel_selector}),
+ FilterOp::SVGFEDropShadow{node, color, dx, dy, std_deviation_x, std_deviation_y} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEDropShadow{color, dx, dy, std_deviation_x, std_deviation_y}),
+ FilterOp::SVGFEFlood{node, color} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEFlood{color}),
+ FilterOp::SVGFEGaussianBlur{node, std_deviation_x, std_deviation_y} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEGaussianBlur{std_deviation_x, std_deviation_y}),
+ FilterOp::SVGFEIdentity{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEIdentity),
+ FilterOp::SVGFEImage{node, sampling_filter, matrix} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEImage{sampling_filter, matrix}),
+ FilterOp::SVGFEMorphologyDilate{node, radius_x, radius_y} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEMorphologyDilate{radius_x, radius_y}),
+ FilterOp::SVGFEMorphologyErode{node, radius_x, radius_y} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEMorphologyErode{radius_x, radius_y}),
+ FilterOp::SVGFEOffset{node, offset_x, offset_y} => {
+ Filter::SVGGraphNode(
+ FilterGraphNode {
+ kept_by_optimizer: true, // computed later in scene_building
+ linear: node.linear,
+ inflate: 0, // computed later in scene_building
+ inputs: [FilterGraphPictureReference {
+ buffer_id: node.input.buffer_id,
+ offset: LayoutVector2D::new(offset_x, offset_y),
+ subregion: LayoutRect::zero(),
+ inflate: 0,
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ }].to_vec(),
+ subregion: node.subregion,
+ },
+ FilterGraphOp::SVGFEIdentity,
+ )
+ },
+ FilterOp::SVGFEOpacity{node, valuebinding, value} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEOpacity{valuebinding, value}),
+ FilterOp::SVGFESourceAlpha{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFESourceAlpha),
+ FilterOp::SVGFESourceGraphic{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFESourceGraphic),
+ FilterOp::SVGFESpecularLightingDistant{node, surface_scale, specular_constant, specular_exponent, kernel_unit_length_x, kernel_unit_length_y, azimuth, elevation} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFESpecularLightingDistant{surface_scale, specular_constant, specular_exponent, kernel_unit_length_x, kernel_unit_length_y, azimuth, elevation}),
+ FilterOp::SVGFESpecularLightingPoint{node, surface_scale, specular_constant, specular_exponent, kernel_unit_length_x, kernel_unit_length_y, x, y, z} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFESpecularLightingPoint{surface_scale, specular_constant, specular_exponent, kernel_unit_length_x, kernel_unit_length_y, x, y, z}),
+ FilterOp::SVGFESpecularLightingSpot{node, surface_scale, specular_constant, specular_exponent, kernel_unit_length_x, kernel_unit_length_y, x, y, z, points_at_x, points_at_y, points_at_z, cone_exponent, limiting_cone_angle} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFESpecularLightingSpot{surface_scale, specular_constant, specular_exponent, kernel_unit_length_x, kernel_unit_length_y, x, y, z, points_at_x, points_at_y, points_at_z, cone_exponent, limiting_cone_angle}),
+ FilterOp::SVGFETile{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFETile),
+ FilterOp::SVGFEToAlpha{node} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFEToAlpha),
+ FilterOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{node, base_frequency_x, base_frequency_y, num_octaves, seed} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{base_frequency_x, base_frequency_y, num_octaves, seed}),
+ FilterOp::SVGFETurbulenceWithFractalNoiseWithStitching{node, base_frequency_x, base_frequency_y, num_octaves, seed} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{base_frequency_x, base_frequency_y, num_octaves, seed}),
+ FilterOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{node, base_frequency_x, base_frequency_y, num_octaves, seed} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{base_frequency_x, base_frequency_y, num_octaves, seed}),
+ FilterOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{node, base_frequency_x, base_frequency_y, num_octaves, seed} => Filter::SVGGraphNode(node.into(), FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{base_frequency_x, base_frequency_y, num_octaves, seed}),
}
}
}
diff --git a/gfx/wr/webrender/src/pattern.rs b/gfx/wr/webrender/src/pattern.rs
index 36a06fa2b9..f4ddd51f9f 100644
--- a/gfx/wr/webrender/src/pattern.rs
+++ b/gfx/wr/webrender/src/pattern.rs
@@ -10,12 +10,14 @@ use api::{ColorF, PremultipliedColorF};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum PatternKind {
ColorOrTexture = 0,
+ RadialGradient = 1,
+ ConicGradient = 2,
- Mask = 1,
+ Mask = 3,
// When adding patterns, don't forget to update the NUM_PATTERNS constant.
}
-pub const NUM_PATTERNS: u32 = 2;
+pub const NUM_PATTERNS: u32 = 4;
impl PatternKind {
pub fn from_u32(val: u32) -> Self {
@@ -61,8 +63,21 @@ impl Pattern {
Pattern {
kind: PatternKind::ColorOrTexture,
shader_input: PatternShaderInput::default(),
- base_color: PremultipliedColorF::BLACK,
+ base_color: PremultipliedColorF::WHITE,
is_opaque: false,
}
}
+
+ pub fn supports_segmented_rendering(&self) -> bool {
+ match self.kind {
+ PatternKind::ColorOrTexture | PatternKind::Mask => {
+ true
+ }
+ PatternKind::RadialGradient | PatternKind::ConicGradient => {
+ // TODO: We need to fix up the layout coords mismatch in pattern
+ // and quad rendering to allow these to be segmented.
+ false
+ }
+ }
+ }
}
diff --git a/gfx/wr/webrender/src/picture.rs b/gfx/wr/webrender/src/picture.rs
index 1f1fd5e4f6..f22bcadd06 100644
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -95,7 +95,7 @@
//! improved as a follow up).
use api::{MixBlendMode, PremultipliedColorF, FilterPrimitiveKind};
-use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, RasterSpace};
+use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FilterOpGraphPictureBufferId, RasterSpace};
use api::{DebugFlags, ImageKey, ColorF, ColorU, PrimitiveFlags};
use api::{ImageRendering, ColorDepth, YuvRangedColorSpace, YuvFormat, AlphaType};
use api::units::*;
@@ -111,7 +111,7 @@ use euclid::{vec3, Point2D, Scale, Vector2D, Box2D};
use euclid::approxeq::ApproxEq;
use crate::filterdata::SFilterData;
use crate::intern::ItemUid;
-use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter, FrameId};
+use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, FilterGraphOp, FilterGraphNode, Filter, FrameId};
use crate::internal_types::{PlaneSplitterIndex, PlaneSplitAnchor, TextureSource};
use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
@@ -281,9 +281,10 @@ pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize {
_unit: marker::PhantomData,
};
-/// The maximum size per axis of a surface,
-/// in WorldPixel coordinates.
-const MAX_SURFACE_SIZE: usize = 4096;
+/// The maximum size per axis of a surface, in DevicePixel coordinates.
+/// Render tasks larger than this size are scaled down to fit, which may cause
+/// some blurriness.
+pub const MAX_SURFACE_SIZE: usize = 4096;
/// Maximum size of a compositor surface.
const MAX_COMPOSITOR_SURFACES_SIZE: f32 = 8192.0;
@@ -3821,7 +3822,7 @@ pub struct SurfaceIndex(pub usize);
/// frames and display lists.
pub struct SurfaceInfo {
/// A local rect defining the size of this surface, in the
- /// coordinate system of the surface itself. This contains
+ /// coordinate system of the parent surface. This contains
/// the unclipped bounding rect of child primitives.
pub unclipped_local_rect: PictureRect,
/// The local space coverage of child primitives after they are
@@ -4048,6 +4049,8 @@ pub enum PictureCompositeMode {
},
/// Apply an SVG filter
SvgFilter(Vec<FilterPrimitive>, Vec<SFilterData>),
+ /// Apply an SVG filter graph
+ SVGFEGraph(Vec<(FilterGraphNode, FilterGraphOp)>),
/// A surface that is used as an input to another primitive
IntermediateSurface,
}
@@ -4137,6 +4140,9 @@ impl PictureCompositeMode {
}
result_rect
}
+ PictureCompositeMode::SVGFEGraph(ref filters) => {
+ self.get_coverage_svgfe(filters, surface_rect.cast_unit(), true, false).0
+ }
_ => {
surface_rect
}
@@ -4232,11 +4238,338 @@ 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
}
}
}
+
+ /// Returns a static str describing the type of PictureCompositeMode (and
+ /// filter type if applicable)
+ pub fn kind(&self) -> &'static str {
+ match *self {
+ PictureCompositeMode::Blit(..) => "Blit",
+ PictureCompositeMode::ComponentTransferFilter(..) => "ComponentTransferFilter",
+ PictureCompositeMode::IntermediateSurface => "IntermediateSurface",
+ PictureCompositeMode::MixBlend(..) => "MixBlend",
+ PictureCompositeMode::SVGFEGraph(..) => "SVGFEGraph",
+ PictureCompositeMode::SvgFilter(..) => "SvgFilter",
+ PictureCompositeMode::TileCache{..} => "TileCache",
+ PictureCompositeMode::Filter(Filter::Blur{..}) => "Filter::Blur",
+ PictureCompositeMode::Filter(Filter::Brightness(..)) => "Filter::Brightness",
+ PictureCompositeMode::Filter(Filter::ColorMatrix(..)) => "Filter::ColorMatrix",
+ PictureCompositeMode::Filter(Filter::ComponentTransfer) => "Filter::ComponentTransfer",
+ PictureCompositeMode::Filter(Filter::Contrast(..)) => "Filter::Contrast",
+ PictureCompositeMode::Filter(Filter::DropShadows(..)) => "Filter::DropShadows",
+ PictureCompositeMode::Filter(Filter::Flood(..)) => "Filter::Flood",
+ PictureCompositeMode::Filter(Filter::Grayscale(..)) => "Filter::Grayscale",
+ PictureCompositeMode::Filter(Filter::HueRotate(..)) => "Filter::HueRotate",
+ PictureCompositeMode::Filter(Filter::Identity) => "Filter::Identity",
+ PictureCompositeMode::Filter(Filter::Invert(..)) => "Filter::Invert",
+ PictureCompositeMode::Filter(Filter::LinearToSrgb) => "Filter::LinearToSrgb",
+ PictureCompositeMode::Filter(Filter::Opacity(..)) => "Filter::Opacity",
+ PictureCompositeMode::Filter(Filter::Saturate(..)) => "Filter::Saturate",
+ PictureCompositeMode::Filter(Filter::Sepia(..)) => "Filter::Sepia",
+ PictureCompositeMode::Filter(Filter::SrgbToLinear) => "Filter::SrgbToLinear",
+ PictureCompositeMode::Filter(Filter::SVGGraphNode(..)) => "Filter::SVGGraphNode",
+ }
+ }
+
+ /// Here we compute the source and target rects 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.
+ ///
+ /// 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)
+ /// * render_task.rs : new_svg_filter_graph
+ /// * render_target.rs : add_svg_filter_node_instances
+ pub fn get_coverage_svgfe(
+ &self,
+ filters: &[(FilterGraphNode, FilterGraphOp)],
+ surface_rect: LayoutRect,
+ surface_rect_is_source: bool,
+ skip_subregion_clips: bool,
+ ) -> (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;
+
+ 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();
+ 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.
+ 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]
+ }
+
+ 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)
+ }
}
/// Enum value describing the place of a picture in a 3D context.
@@ -4500,6 +4833,7 @@ pub struct PicturePrimitive {
/// it will be considered invisible.
pub is_backface_visible: bool,
+ /// All render tasks have 0-2 input tasks.
pub primary_render_task_id: Option<RenderTaskId>,
/// If a mix-blend-mode, contains the render task for
/// the readback of the framebuffer that we use to sample
@@ -4507,6 +4841,8 @@ pub struct PicturePrimitive {
/// For drop-shadow filter, this will store the original
/// picture task which would be rendered on screen after
/// blur pass.
+ /// This is also used by SVGFEBlend, SVGFEComposite and
+ /// SVGFEDisplacementMap filters.
pub secondary_render_task_id: Option<RenderTaskId>,
/// How this picture should be composited.
/// If None, don't composite - just draw directly on parent surface.
@@ -4646,6 +4982,7 @@ impl PicturePrimitive {
parent_subpixel_mode: SubpixelMode,
frame_state: &mut FrameBuildingState,
frame_context: &FrameBuildingContext,
+ data_stores: &mut DataStores,
scratch: &mut PrimitiveScratchBuffer,
tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
) -> Option<(PictureContext, PictureState, PrimitiveList)> {
@@ -4837,7 +5174,7 @@ impl PicturePrimitive {
// Ensure that the dirty rect doesn't extend outside the local valid rect.
tile.local_dirty_rect = tile.local_dirty_rect
.intersection(&tile.current_descriptor.local_valid_rect)
- .unwrap_or_else(PictureRect::zero);
+ .unwrap_or_else(|| { tile.is_valid = true; PictureRect::zero() });
surface_local_dirty_rect = surface_local_dirty_rect.union(&tile.local_dirty_rect);
@@ -5746,6 +6083,47 @@ impl PicturePrimitive {
surface_rects.clipped_local,
);
}
+ PictureCompositeMode::SVGFEGraph(ref filters) => {
+ let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
+
+ let picture_task_id = frame_state.rg_builder.add().init(
+ RenderTask::new_dynamic(
+ surface_rects.task_size,
+ RenderTaskKind::new_picture(
+ surface_rects.task_size,
+ surface_rects.needs_scissor_rect,
+ surface_rects.clipped.min,
+ surface_spatial_node_index,
+ raster_spatial_node_index,
+ device_pixel_scale,
+ None,
+ None,
+ None,
+ cmd_buffer_index,
+ can_use_shared_surface,
+ )
+ ).with_uv_rect_kind(surface_rects.uv_rect_kind)
+ );
+
+ 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,
+ );
+
+ primary_render_task_id = filter_task_id;
+
+ surface_descriptor = SurfaceDescriptor::new_chained(
+ picture_task_id,
+ filter_task_id,
+ surface_rects.clipped_local,
+ );
+ }
}
let is_sub_graph = self.flags.contains(PictureFlags::IS_SUB_GRAPH);
@@ -5792,7 +6170,8 @@ impl PicturePrimitive {
PictureCompositeMode::Filter(..) |
PictureCompositeMode::MixBlend(..) |
PictureCompositeMode::IntermediateSurface |
- PictureCompositeMode::SvgFilter(..) => {
+ PictureCompositeMode::SvgFilter(..) |
+ PictureCompositeMode::SVGFEGraph(..) => {
// TODO(gw): We can take advantage of the same logic that
// exists in the opaque rect detection for tile
// caches, to allow subpixel text on other surfaces
@@ -6425,6 +6804,18 @@ impl PicturePrimitive {
PictureCompositeMode::Blit(_) |
PictureCompositeMode::IntermediateSurface |
PictureCompositeMode::SvgFilter(..) => {}
+ PictureCompositeMode::SVGFEGraph(ref filters) => {
+ // Update interned filter data
+ for (_node, op) in filters {
+ match op {
+ FilterGraphOp::SVGFEComponentTransferInterned { handle, creates_pixels: _ } => {
+ let filter_data = &mut data_stores.filter_data[*handle];
+ filter_data.update(frame_state);
+ }
+ _ => {}
+ }
+ }
+ }
}
true
@@ -7109,6 +7500,38 @@ fn get_surface_rects(
let surface = &mut surfaces[surface_index.0];
let (clipped_local, unclipped_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.
+ //
+ // 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() {
+ 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;
+
+ (clipped.cast_unit(), unclipped)
+ }
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let local_prim_rect = surface.clipped_local_rect;
diff --git a/gfx/wr/webrender/src/prepare.rs b/gfx/wr/webrender/src/prepare.rs
index d9b4521cfc..a7eca830f8 100644
--- a/gfx/wr/webrender/src/prepare.rs
+++ b/gfx/wr/webrender/src/prepare.rs
@@ -28,7 +28,7 @@ use crate::prim_store::line_dec::MAX_LINE_DECORATION_RESOLUTION;
use crate::prim_store::*;
use crate::quad;
use crate::pattern::Pattern;
-use crate::prim_store::gradient::GradientGpuBlockBuilder;
+use crate::prim_store::gradient::{radial_gradient_pattern, conic_gradient_pattern, GradientGpuBlockBuilder};
use crate::render_backend::DataStores;
use crate::render_task_graph::RenderTaskId;
use crate::render_task_cache::RenderTaskCacheKeyKind;
@@ -174,6 +174,7 @@ fn prepare_prim_for_render(
pic_context.subpixel_mode,
frame_state,
frame_context,
+ data_stores,
scratch,
tile_caches,
) {
@@ -211,6 +212,34 @@ fn prepare_prim_for_render(
let prim_instance = &mut prim_instances[prim_instance_index];
if !is_passthrough {
+ fn may_need_repetition(stretch_size: LayoutSize, prim_rect: LayoutRect) -> bool {
+ stretch_size.width < prim_rect.width() ||
+ stretch_size.height < prim_rect.height()
+ }
+ // Bug 1887841: At the moment the quad shader does not support repetitions.
+ // Bug 1888349: Some primitives have brush segments that aren't handled by
+ // the quad infrastructure yet.
+ let disable_quad_path = match &prim_instance.kind {
+ PrimitiveInstanceKind::Rectangle { .. } => false,
+ PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
+ let prim_data = &data_stores.linear_grad[*data_handle];
+ !prim_data.brush_segments.is_empty() ||
+ may_need_repetition(prim_data.stretch_size, prim_data.common.prim_rect)
+ }
+ PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
+ let prim_data = &data_stores.radial_grad[*data_handle];
+ !prim_data.brush_segments.is_empty() ||
+ may_need_repetition(prim_data.stretch_size, prim_data.common.prim_rect)
+ }
+ PrimitiveInstanceKind::ConicGradient { .. } => {
+ // TODO(nical) Enable quad conic gradients.
+ true
+ // let prim_data = &data_stores.conic_grad[*data_handle];
+ // !prim_data.brush_segments.is_empty() ||
+ // may_need_repetition(prim_data.stretch_size, prim_data.common.prim_rect)
+ }
+ _ => true,
+ };
// In this initial patch, we only support non-masked primitives through the new
// quad rendering path. Follow up patches will extend this to support masks, and
@@ -218,18 +247,19 @@ fn prepare_prim_for_render(
// to skip the entry point to `update_clip_task` as that does old-style segmenting
// and mask generation.
let should_update_clip_task = match prim_instance.kind {
- PrimitiveInstanceKind::Rectangle { ref mut use_legacy_path, .. } => {
- *use_legacy_path = !can_use_clip_chain_for_quad_path(
+ PrimitiveInstanceKind::Rectangle { use_legacy_path: ref mut no_quads, .. }
+ | PrimitiveInstanceKind::RadialGradient { cached: ref mut no_quads, .. }
+ | PrimitiveInstanceKind::ConicGradient { cached: ref mut no_quads, .. }
+ => {
+ *no_quads = disable_quad_path || !can_use_clip_chain_for_quad_path(
&prim_instance.vis.clip_chain,
frame_state.clip_store,
data_stores,
);
- *use_legacy_path
- }
- PrimitiveInstanceKind::Picture { .. } => {
- false
+ *no_quads
}
+ PrimitiveInstanceKind::Picture { .. } => false,
_ => true,
};
@@ -365,7 +395,7 @@ fn prepare_interned_prim_for_render(
frame_state.rg_builder,
None,
false,
- RenderTaskParent::Surface(pic_context.surface_index),
+ RenderTaskParent::Surface,
&mut frame_state.surface_builder,
|rg_builder, _| {
rg_builder.add().init(RenderTask::new_dynamic(
@@ -520,7 +550,7 @@ fn prepare_interned_prim_for_render(
frame_state.rg_builder,
None,
false, // TODO(gw): We don't calculate opacity for borders yet!
- RenderTaskParent::Surface(pic_context.surface_index),
+ RenderTaskParent::Surface,
&mut frame_state.surface_builder,
|rg_builder, _| {
rg_builder.add().init(RenderTask::new_dynamic(
@@ -669,7 +699,6 @@ fn prepare_interned_prim_for_render(
image_data.update(
common_data,
image_instance,
- pic_context.surface_index,
prim_spatial_node_index,
frame_state,
frame_context,
@@ -692,7 +721,7 @@ fn prepare_interned_prim_for_render(
// Update the template this instane references, which may refresh the GPU
// cache with any shared template data.
- prim_data.update(frame_state, pic_context.surface_index);
+ prim_data.update(frame_state);
if prim_data.stretch_size.width >= prim_data.common.prim_rect.width() &&
prim_data.stretch_size.height >= prim_data.common.prim_rect.height() {
@@ -758,7 +787,7 @@ fn prepare_interned_prim_for_render(
// Update the template this instance references, which may refresh the GPU
// cache with any shared template data.
- prim_data.update(frame_state, pic_context.surface_index);
+ prim_data.update(frame_state);
if prim_data.tile_spacing != LayoutSize::zero() {
prim_data.common.may_need_repetition = false;
@@ -780,16 +809,49 @@ fn prepare_interned_prim_for_render(
}
}
}
- PrimitiveInstanceKind::RadialGradient { data_handle, ref mut visible_tiles_range, .. } => {
+ PrimitiveInstanceKind::RadialGradient { data_handle, ref mut visible_tiles_range, cached, .. } => {
profile_scope!("RadialGradient");
let prim_data = &mut data_stores.radial_grad[*data_handle];
+ if !*cached {
+ // The scaling parameter is used to compensate for when we reduce the size
+ // of the render task for cached gradients. Here we aren't applying any.
+ let no_scale = DeviceVector2D::one();
+
+ let pattern = radial_gradient_pattern(
+ prim_data.center,
+ no_scale,
+ &prim_data.params,
+ prim_data.extend_mode,
+ &prim_data.stops,
+ &mut frame_state.frame_gpu_data,
+ );
+
+ quad::push_quad(
+ &pattern,
+ &prim_data.common.prim_rect,
+ prim_instance_index,
+ prim_spatial_node_index,
+ &prim_instance.vis.clip_chain,
+ device_pixel_scale,
+ frame_context,
+ pic_context,
+ targets,
+ &data_stores.clip,
+ frame_state,
+ pic_state,
+ scratch,
+ );
+
+ return;
+ }
+
prim_data.common.may_need_repetition = prim_data.stretch_size.width < prim_data.common.prim_rect.width()
- || prim_data.stretch_size.height < prim_data.common.prim_rect.height();
+ || prim_data.stretch_size.height < prim_data.common.prim_rect.height();
// Update the template this instane references, which may refresh the GPU
// cache with any shared template data.
- prim_data.update(frame_state, pic_context.surface_index);
+ prim_data.update(frame_state);
if prim_data.tile_spacing != LayoutSize::zero() {
prim_data.common.may_need_repetition = false;
@@ -810,20 +872,50 @@ fn prepare_interned_prim_for_render(
prim_instance.clear_visibility();
}
}
-
- // TODO(gw): Consider whether it's worth doing segment building
- // for gradient primitives.
}
- PrimitiveInstanceKind::ConicGradient { data_handle, ref mut visible_tiles_range, .. } => {
+ PrimitiveInstanceKind::ConicGradient { data_handle, ref mut visible_tiles_range, cached, .. } => {
profile_scope!("ConicGradient");
let prim_data = &mut data_stores.conic_grad[*data_handle];
+ if !*cached {
+ // The scaling parameter is used to compensate for when we reduce the size
+ // of the render task for cached gradients. Here we aren't applying any.
+ let no_scale = DeviceVector2D::one();
+
+ let pattern = conic_gradient_pattern(
+ prim_data.center,
+ no_scale,
+ &prim_data.params,
+ prim_data.extend_mode,
+ &prim_data.stops,
+ &mut frame_state.frame_gpu_data,
+ );
+
+ quad::push_quad(
+ &pattern,
+ &prim_data.common.prim_rect,
+ prim_instance_index,
+ prim_spatial_node_index,
+ &prim_instance.vis.clip_chain,
+ device_pixel_scale,
+ frame_context,
+ pic_context,
+ targets,
+ &data_stores.clip,
+ frame_state,
+ pic_state,
+ scratch,
+ );
+
+ return;
+ }
+
prim_data.common.may_need_repetition = prim_data.stretch_size.width < prim_data.common.prim_rect.width()
|| prim_data.stretch_size.height < prim_data.common.prim_rect.height();
// Update the template this instane references, which may refresh the GPU
// cache with any shared template data.
- prim_data.update(frame_state, pic_context.surface_index);
+ prim_data.update(frame_state);
if prim_data.tile_spacing != LayoutSize::zero() {
prim_data.common.may_need_repetition = false;
@@ -870,7 +962,8 @@ fn prepare_interned_prim_for_render(
// may have changed due to downscaling. We could handle this separate
// case as a follow up.
Some(PictureCompositeMode::Filter(Filter::Blur { .. })) |
- Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) => {
+ Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) |
+ Some(PictureCompositeMode::SVGFEGraph( .. )) => {
true
}
_ => {
diff --git a/gfx/wr/webrender/src/prim_store/gradient/conic.rs b/gfx/wr/webrender/src/prim_store/gradient/conic.rs
index 2c4818095e..9f993b9758 100644
--- a/gfx/wr/webrender/src/prim_store/gradient/conic.rs
+++ b/gfx/wr/webrender/src/prim_store/gradient/conic.rs
@@ -11,6 +11,7 @@
use euclid::vec2;
use api::{ExtendMode, GradientStop, PremultipliedColorF};
use api::units::*;
+use crate::pattern::{Pattern, PatternKind, PatternShaderInput};
use crate::scene_building::IsVisible;
use crate::frame_builder::FrameBuildingState;
use crate::intern::{Internable, InternDebug, Handle as InternHandle};
@@ -22,8 +23,7 @@ use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimit
use crate::render_task::{RenderTask, RenderTaskKind};
use crate::render_task_graph::RenderTaskId;
use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
-use crate::renderer::GpuBufferAddress;
-use crate::picture::{SurfaceIndex};
+use crate::renderer::{GpuBufferAddress, GpuBufferBuilder};
use std::{hash, ops::{Deref, DerefMut}};
use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder};
@@ -213,7 +213,6 @@ impl ConicGradientTemplate {
pub fn update(
&mut self,
frame_state: &mut FrameBuildingState,
- parent_surface: SurfaceIndex,
) {
if let Some(mut request) =
frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
@@ -258,7 +257,7 @@ impl ConicGradientTemplate {
frame_state.rg_builder,
None,
false,
- RenderTaskParent::Surface(parent_surface),
+ RenderTaskParent::Surface,
&mut frame_state.surface_builder,
|rg_builder, gpu_buffer_builder| {
let stops = GradientGpuBlockBuilder::build(
@@ -329,6 +328,7 @@ impl InternablePrimitive for ConicGradient {
PrimitiveInstanceKind::ConicGradient {
data_handle,
visible_tiles_range: GradientTileRange::empty(),
+ cached: true,
}
}
}
@@ -397,3 +397,44 @@ pub struct ConicGradientCacheKey {
pub stops: Vec<GradientStopKey>,
}
+pub fn conic_gradient_pattern(
+ center: DevicePoint,
+ scale: DeviceVector2D,
+ params: &ConicGradientParams,
+ extend_mode: ExtendMode,
+ stops: &[GradientStop],
+ gpu_buffer_builder: &mut GpuBufferBuilder
+) -> Pattern {
+ let mut writer = gpu_buffer_builder.f32.write_blocks(2);
+ writer.push_one([
+ center.x,
+ center.y,
+ scale.x,
+ scale.y,
+ ]);
+ writer.push_one([
+ params.start_offset,
+ params.end_offset,
+ params.angle,
+ if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 }
+ ]);
+ let gradient_address = writer.finish();
+
+ let stops_address = GradientGpuBlockBuilder::build(
+ false,
+ &mut gpu_buffer_builder.f32,
+ &stops,
+ );
+
+ let is_opaque = stops.iter().all(|stop| stop.color.a >= 1.0);
+
+ Pattern {
+ kind: PatternKind::ConicGradient,
+ shader_input: PatternShaderInput(
+ gradient_address.as_int(),
+ stops_address.as_int(),
+ ),
+ base_color: PremultipliedColorF::WHITE,
+ is_opaque,
+ }
+} \ No newline at end of file
diff --git a/gfx/wr/webrender/src/prim_store/gradient/linear.rs b/gfx/wr/webrender/src/prim_store/gradient/linear.rs
index 7075daac0d..0fdb268449 100644
--- a/gfx/wr/webrender/src/prim_store/gradient/linear.rs
+++ b/gfx/wr/webrender/src/prim_store/gradient/linear.rs
@@ -26,7 +26,6 @@ use crate::render_task_graph::RenderTaskId;
use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
use crate::renderer::GpuBufferAddress;
use crate::segment::EdgeAaSegmentMask;
-use crate::picture::{SurfaceIndex};
use crate::util::pack_as_float;
use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder, apply_gradient_local_clip};
use std::ops::{Deref, DerefMut};
@@ -450,7 +449,6 @@ impl LinearGradientTemplate {
pub fn update(
&mut self,
frame_state: &mut FrameBuildingState,
- parent_surface: SurfaceIndex,
) {
if let Some(mut request) = frame_state.gpu_cache.request(
&mut self.common.gpu_cache_handle
@@ -526,7 +524,7 @@ impl LinearGradientTemplate {
frame_state.rg_builder,
None,
false,
- RenderTaskParent::Surface(parent_surface),
+ RenderTaskParent::Surface,
&mut frame_state.surface_builder,
|rg_builder, _| {
rg_builder.add().init(RenderTask::new_dynamic(
@@ -556,7 +554,7 @@ impl LinearGradientTemplate {
frame_state.rg_builder,
None,
false,
- RenderTaskParent::Surface(parent_surface),
+ RenderTaskParent::Surface,
&mut frame_state.surface_builder,
|rg_builder, gpu_buffer_builder| {
let stops = Some(GradientGpuBlockBuilder::build(
diff --git a/gfx/wr/webrender/src/prim_store/gradient/radial.rs b/gfx/wr/webrender/src/prim_store/gradient/radial.rs
index 4d91b28633..4d655ffe7e 100644
--- a/gfx/wr/webrender/src/prim_store/gradient/radial.rs
+++ b/gfx/wr/webrender/src/prim_store/gradient/radial.rs
@@ -11,6 +11,7 @@
use euclid::{vec2, size2};
use api::{ExtendMode, GradientStop, PremultipliedColorF, ColorU};
use api::units::*;
+use crate::pattern::{Pattern, PatternKind, PatternShaderInput};
use crate::scene_building::IsVisible;
use crate::frame_builder::FrameBuildingState;
use crate::intern::{Internable, InternDebug, Handle as InternHandle};
@@ -22,8 +23,7 @@ use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, FloatKey};
use crate::render_task::{RenderTask, RenderTaskKind};
use crate::render_task_graph::RenderTaskId;
use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
-use crate::renderer::GpuBufferAddress;
-use crate::picture::{SurfaceIndex};
+use crate::renderer::{GpuBufferAddress, GpuBufferBuilder};
use std::{hash, ops::{Deref, DerefMut}};
use super::{
@@ -178,7 +178,6 @@ impl RadialGradientTemplate {
pub fn update(
&mut self,
frame_state: &mut FrameBuildingState,
- parent_surface: SurfaceIndex,
) {
if let Some(mut request) =
frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
@@ -224,7 +223,7 @@ impl RadialGradientTemplate {
frame_state.rg_builder,
None,
false,
- RenderTaskParent::Surface(parent_surface),
+ RenderTaskParent::Surface,
&mut frame_state.surface_builder,
|rg_builder, gpu_buffer_builder| {
let stops = GradientGpuBlockBuilder::build(
@@ -295,6 +294,7 @@ impl InternablePrimitive for RadialGradient {
PrimitiveInstanceKind::RadialGradient {
data_handle,
visible_tiles_range: GradientTileRange::empty(),
+ cached: true,
}
}
}
@@ -529,3 +529,45 @@ pub fn optimize_radial_gradient(
tile_spacing.width += l + r;
tile_spacing.height += t + b;
}
+
+pub fn radial_gradient_pattern(
+ center: DevicePoint,
+ scale: DeviceVector2D,
+ params: &RadialGradientParams,
+ extend_mode: ExtendMode,
+ stops: &[GradientStop],
+ gpu_buffer_builder: &mut GpuBufferBuilder
+) -> Pattern {
+ let mut writer = gpu_buffer_builder.f32.write_blocks(2);
+ writer.push_one([
+ center.x,
+ center.y,
+ scale.x,
+ scale.y,
+ ]);
+ writer.push_one([
+ params.start_radius,
+ params.end_radius,
+ params.ratio_xy,
+ if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 }
+ ]);
+ let gradient_address = writer.finish();
+
+ let stops_address = GradientGpuBlockBuilder::build(
+ false,
+ &mut gpu_buffer_builder.f32,
+ &stops,
+ );
+
+ let is_opaque = stops.iter().all(|stop| stop.color.a >= 1.0);
+
+ Pattern {
+ kind: PatternKind::RadialGradient,
+ shader_input: PatternShaderInput(
+ gradient_address.as_int(),
+ stops_address.as_int(),
+ ),
+ base_color: PremultipliedColorF::WHITE,
+ is_opaque,
+ }
+} \ No newline at end of file
diff --git a/gfx/wr/webrender/src/prim_store/image.rs b/gfx/wr/webrender/src/prim_store/image.rs
index 8a05965536..b10695f095 100644
--- a/gfx/wr/webrender/src/prim_store/image.rs
+++ b/gfx/wr/webrender/src/prim_store/image.rs
@@ -13,8 +13,7 @@ use crate::scene_building::{CreateShadow, IsVisible};
use crate::frame_builder::{FrameBuildingContext, FrameBuildingState};
use crate::gpu_cache::{GpuCache, GpuDataRequest};
use crate::intern::{Internable, InternDebug, Handle as InternHandle};
-use crate::internal_types::{LayoutPrimitiveInfo};
-use crate::picture::SurfaceIndex;
+use crate::internal_types::LayoutPrimitiveInfo;
use crate::prim_store::{
EdgeAaSegmentMask, PrimitiveInstanceKind,
PrimitiveOpacity, PrimKey,
@@ -136,7 +135,6 @@ impl ImageData {
&mut self,
common: &mut PrimTemplateCommonData,
image_instance: &mut ImageInstance,
- parent_surface: SurfaceIndex,
prim_spatial_node_index: SpatialNodeIndex,
frame_state: &mut FrameBuildingState,
frame_context: &FrameBuildingContext,
@@ -261,7 +259,7 @@ impl ImageData {
frame_state.rg_builder,
None,
descriptor.is_opaque(),
- RenderTaskParent::Surface(parent_surface),
+ RenderTaskParent::Surface,
&mut frame_state.surface_builder,
|rg_builder, _| {
// Create a task to blit from the texture cache to
diff --git a/gfx/wr/webrender/src/prim_store/mod.rs b/gfx/wr/webrender/src/prim_store/mod.rs
index cc09eab6b1..902ecff43a 100644
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1029,11 +1029,13 @@ pub enum PrimitiveInstanceKind {
/// Handle to the common interned data for this primitive.
data_handle: RadialGradientDataHandle,
visible_tiles_range: GradientTileRange,
+ cached: bool,
},
ConicGradient {
/// Handle to the common interned data for this primitive.
data_handle: ConicGradientDataHandle,
visible_tiles_range: GradientTileRange,
+ cached: bool,
},
/// Clear out a rect, used for special effects.
Clear {
@@ -1214,8 +1216,9 @@ pub struct PrimitiveScratchBuffer {
/// Set of sub-graphs that are required, determined during visibility pass
pub required_sub_graphs: FastHashSet<PictureIndex>,
- /// Temporary buffer for building segments in to during prepare pass
- pub quad_segments: Vec<QuadSegment>,
+ /// Temporary buffers for building segments in to during prepare pass
+ pub quad_direct_segments: Vec<QuadSegment>,
+ pub quad_indirect_segments: Vec<QuadSegment>,
}
impl Default for PrimitiveScratchBuffer {
@@ -1230,7 +1233,8 @@ impl Default for PrimitiveScratchBuffer {
debug_items: Vec::new(),
messages: Vec::new(),
required_sub_graphs: FastHashSet::default(),
- quad_segments: Vec::new(),
+ quad_direct_segments: Vec::new(),
+ quad_indirect_segments: Vec::new(),
}
}
}
@@ -1244,7 +1248,8 @@ impl PrimitiveScratchBuffer {
self.segment_instances.recycle(recycler);
self.gradient_tiles.recycle(recycler);
recycler.recycle_vec(&mut self.debug_items);
- recycler.recycle_vec(&mut self.quad_segments);
+ recycler.recycle_vec(&mut self.quad_direct_segments);
+ recycler.recycle_vec(&mut self.quad_indirect_segments);
}
pub fn begin_frame(&mut self) {
@@ -1253,7 +1258,8 @@ impl PrimitiveScratchBuffer {
// location.
self.clip_mask_instances.clear();
self.clip_mask_instances.push(ClipMaskKind::None);
- self.quad_segments.clear();
+ self.quad_direct_segments.clear();
+ self.quad_indirect_segments.clear();
self.border_cache_handles.clear();
diff --git a/gfx/wr/webrender/src/prim_store/picture.rs b/gfx/wr/webrender/src/prim_store/picture.rs
index c3ec88783a..f8857a4f11 100644
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -3,15 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{
- ColorU, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveKind, ColorSpace,
- PropertyBinding, PropertyBindingId, CompositeOperator, RasterSpace,
+ ColorU, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveKind,
+ ColorSpace, PropertyBinding, PropertyBindingId, CompositeOperator,
+ RasterSpace, FilterOpGraphPictureBufferId,
};
use api::units::{Au, LayoutVector2D};
use crate::scene_building::IsVisible;
use crate::filterdata::SFilterData;
use crate::intern::ItemUid;
use crate::intern::{Internable, InternDebug, Handle as InternHandle};
-use crate::internal_types::{LayoutPrimitiveInfo, Filter};
+use crate::internal_types::{LayoutPrimitiveInfo, FilterGraphPictureReference,
+ FilterGraphOp, FilterGraphNode, SVGFE_CONVOLVE_VALUES_LIMIT, Filter};
use crate::picture::PictureCompositeMode;
use crate::prim_store::{
PrimitiveInstanceKind, PrimitiveStore, VectorKey,
@@ -69,6 +71,758 @@ pub enum FilterPrimitiveKey {
Composite(ColorSpace, FilterPrimitiveInput, FilterPrimitiveInput, CompositeOperatorKey),
}
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Copy, Default, MallocSizeOf, PartialEq, Hash, Eq)]
+pub enum FilterGraphPictureBufferIdKey {
+ #[default]
+ /// empty slot in feMerge inputs
+ None,
+ /// reference to another (earlier) node in filter graph
+ BufferId(i16),
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Copy, Default, MallocSizeOf, PartialEq, Hash, Eq)]
+pub struct FilterGraphPictureReferenceKey {
+ /// Id of the picture in question in a namespace unique to this filter DAG,
+ /// some are special values like
+ /// FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic.
+ pub buffer_id: FilterGraphPictureBufferIdKey,
+ /// Place the input image here in Layout space (like node.subregion)
+ pub subregion: [Au; 4],
+ /// Translate the subregion by this amount
+ pub offset: [Au; 2],
+}
+
+impl From<FilterGraphPictureReference> for FilterGraphPictureReferenceKey {
+ fn from(pic: FilterGraphPictureReference) -> Self {
+ FilterGraphPictureReferenceKey{
+ buffer_id: match pic.buffer_id {
+ FilterOpGraphPictureBufferId::None => FilterGraphPictureBufferIdKey::None,
+ FilterOpGraphPictureBufferId::BufferId(id) => FilterGraphPictureBufferIdKey::BufferId(id),
+ },
+ subregion: [
+ Au::from_f32_px(pic.subregion.min.x),
+ Au::from_f32_px(pic.subregion.min.y),
+ Au::from_f32_px(pic.subregion.max.x),
+ Au::from_f32_px(pic.subregion.max.y),
+ ],
+ offset: [
+ Au::from_f32_px(pic.offset.x),
+ Au::from_f32_px(pic.offset.y),
+ ],
+ }
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)]
+pub enum FilterGraphOpKey {
+ /// combine 2 images with SVG_FEBLEND_MODE_DARKEN
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendDarken,
+ /// combine 2 images with SVG_FEBLEND_MODE_LIGHTEN
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendLighten,
+ /// combine 2 images with SVG_FEBLEND_MODE_MULTIPLY
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendMultiply,
+ /// combine 2 images with SVG_FEBLEND_MODE_NORMAL
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendNormal,
+ /// combine 2 images with SVG_FEBLEND_MODE_SCREEN
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feBlendElement
+ SVGFEBlendScreen,
+ /// combine 2 images with SVG_FEBLEND_MODE_OVERLAY
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendOverlay,
+ /// combine 2 images with SVG_FEBLEND_MODE_COLOR_DODGE
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendColorDodge,
+ /// combine 2 images with SVG_FEBLEND_MODE_COLOR_BURN
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendColorBurn,
+ /// combine 2 images with SVG_FEBLEND_MODE_HARD_LIGHT
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendHardLight,
+ /// combine 2 images with SVG_FEBLEND_MODE_SOFT_LIGHT
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendSoftLight,
+ /// combine 2 images with SVG_FEBLEND_MODE_DIFFERENCE
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendDifference,
+ /// combine 2 images with SVG_FEBLEND_MODE_EXCLUSION
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendExclusion,
+ /// combine 2 images with SVG_FEBLEND_MODE_HUE
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendHue,
+ /// combine 2 images with SVG_FEBLEND_MODE_SATURATION
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendSaturation,
+ /// combine 2 images with SVG_FEBLEND_MODE_COLOR
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendColor,
+ /// combine 2 images with SVG_FEBLEND_MODE_LUMINOSITY
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Source: https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
+ SVGFEBlendLuminosity,
+ /// transform colors of image through 5x4 color matrix (transposed for
+ /// efficiency)
+ /// parameters: FilterOpGraphNode, matrix[5][4]
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feColorMatrixElement
+ SVGFEColorMatrix{values: [Au; 20]},
+ /// transform colors of image through configurable gradients with component
+ /// swizzle
+ /// parameters: FilterOpGraphNode, FilterData
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feComponentTransferElement
+ SVGFEComponentTransferInterned{handle: ItemUid, creates_pixels: bool},
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterOpGraphNode, k1, k2, k3, k4
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeArithmetic{k1: Au, k2: Au, k3: Au, k4: Au},
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeATop,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeIn,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Docs: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite
+ SVGFECompositeLighter,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeOut,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeOver,
+ /// composite 2 images with chosen composite mode with parameters for that
+ /// mode
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feCompositeElement
+ SVGFECompositeXOR,
+ /// transform image through convolution matrix of up to 25 values (spec
+ /// allows more but for performance reasons we do not)
+ /// parameters: FilterOpGraphNode, orderX, orderY, kernelValues[25],
+ /// divisor, bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
+ /// preserveAlpha
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
+ SVGFEConvolveMatrixEdgeModeDuplicate{order_x: i32, order_y: i32,
+ kernel: [Au; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: Au, bias: Au,
+ target_x: i32, target_y: i32, kernel_unit_length_x: Au,
+ kernel_unit_length_y: Au, preserve_alpha: i32},
+ /// transform image through convolution matrix of up to 25 values (spec
+ /// allows more but for performance reasons we do not)
+ /// parameters: FilterOpGraphNode, orderX, orderY, kernelValues[25],
+ /// divisor, bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
+ /// preserveAlpha
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
+ SVGFEConvolveMatrixEdgeModeNone{order_x: i32, order_y: i32,
+ kernel: [Au; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: Au, bias: Au,
+ target_x: i32, target_y: i32, kernel_unit_length_x: Au,
+ kernel_unit_length_y: Au, preserve_alpha: i32},
+ /// transform image through convolution matrix of up to 25 values (spec
+ /// allows more but for performance reasons we do not)
+ /// parameters: FilterOpGraphNode, orderX, orderY, kernelValues[25],
+ /// divisor, bias, targetX, targetY, kernelUnitLengthX, kernelUnitLengthY,
+ /// preserveAlpha
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#feConvolveMatrixElement
+ SVGFEConvolveMatrixEdgeModeWrap{order_x: i32, order_y: i32,
+ kernel: [Au; SVGFE_CONVOLVE_VALUES_LIMIT], divisor: Au, bias: Au,
+ target_x: i32, target_y: i32, kernel_unit_length_x: Au,
+ kernel_unit_length_y: Au, preserve_alpha: i32},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// distant light source with specified direction
+ /// parameters: FilterOpGraphNode, surfaceScale, diffuseConstant,
+ /// kernelUnitLengthX, kernelUnitLengthY, azimuth, elevation
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDistantLightElement
+ SVGFEDiffuseLightingDistant{surface_scale: Au, diffuse_constant: Au,
+ kernel_unit_length_x: Au, kernel_unit_length_y: Au, azimuth: Au,
+ elevation: Au},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// point light source at specified location
+ /// parameters: FilterOpGraphNode, surfaceScale, diffuseConstant,
+ /// kernelUnitLengthX, kernelUnitLengthY, x, y, z
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEPointLightElement
+ SVGFEDiffuseLightingPoint{surface_scale: Au, diffuse_constant: Au,
+ kernel_unit_length_x: Au, kernel_unit_length_y: Au, x: Au, y: Au,
+ z: Au},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// spot light source at specified location pointing at specified target
+ /// location with specified hotspot sharpness and cone angle
+ /// parameters: FilterOpGraphNode, surfaceScale, diffuseConstant,
+ /// kernelUnitLengthX, kernelUnitLengthY, x, y, z, pointsAtX, pointsAtY,
+ /// pointsAtZ, specularExponent, limitingConeAngle
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDiffuseLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpotLightElement
+ SVGFEDiffuseLightingSpot{surface_scale: Au, diffuse_constant: Au,
+ kernel_unit_length_x: Au, kernel_unit_length_y: Au, x: Au, y: Au, z: Au,
+ points_at_x: Au, points_at_y: Au, points_at_z: Au, cone_exponent: Au,
+ limiting_cone_angle: Au},
+ /// calculate a distorted version of first input image using offset values
+ /// from second input image at specified intensity
+ /// parameters: FilterOpGraphNode, scale, xChannelSelector, yChannelSelector
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDisplacementMapElement
+ SVGFEDisplacementMap{scale: Au, x_channel_selector: u32,
+ y_channel_selector: u32},
+ /// create and merge a dropshadow version of the specified image's alpha
+ /// channel with specified offset and blur radius
+ /// parameters: FilterOpGraphNode, flood_color, flood_opacity, dx, dy,
+ /// stdDeviationX, stdDeviationY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDropShadowElement
+ SVGFEDropShadow{color: ColorU, dx: Au, dy: Au, std_deviation_x: Au,
+ std_deviation_y: Au},
+ /// synthesize a new image of specified size containing a solid color
+ /// parameters: FilterOpGraphNode, color
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEFloodElement
+ SVGFEFlood{color: ColorU},
+ /// create a blurred version of the input image
+ /// parameters: FilterOpGraphNode, stdDeviationX, stdDeviationY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEGaussianBlurElement
+ SVGFEGaussianBlur{std_deviation_x: Au, std_deviation_y: Au},
+ /// Filter that does no transformation of the colors, needed for
+ /// debug purposes, and is the default value in impl_default_for_enums.
+ SVGFEIdentity,
+ /// synthesize a new image based on a url (i.e. blob image source)
+ /// parameters: FilterOpGraphNode, sampling_filter (see SamplingFilter in
+ /// Types.h), transform
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEImageElement
+ SVGFEImage{sampling_filter: u32, matrix: [Au; 6]},
+ /// create a new image based on the input image with the contour stretched
+ /// outward (dilate operator)
+ /// parameters: FilterOpGraphNode, radiusX, radiusY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMorphologyElement
+ SVGFEMorphologyDilate{radius_x: Au, radius_y: Au},
+ /// create a new image based on the input image with the contour shrunken
+ /// inward (erode operator)
+ /// parameters: FilterOpGraphNode, radiusX, radiusY
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEMorphologyElement
+ SVGFEMorphologyErode{radius_x: Au, radius_y: Au},
+ /// represents CSS opacity property as a graph node like the rest of the
+ /// SVGFE* filters
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ SVGFEOpacity{value: Au},
+ /// represents CSS opacity property as a graph node like the rest of the
+ /// SVGFE* filters
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ SVGFEOpacityBinding{valuebindingid: PropertyBindingId, value: Au},
+ /// Filter that copies the SourceGraphic image into the specified subregion,
+ /// This is intentionally the only way to get SourceGraphic into the graph,
+ /// as the filter region must be applied before it is used.
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - no inputs, no linear
+ SVGFESourceGraphic,
+ /// Filter that copies the SourceAlpha image into the specified subregion,
+ /// This is intentionally the only way to get SourceAlpha into the graph,
+ /// as the filter region must be applied before it is used.
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - no inputs, no linear
+ SVGFESourceAlpha,
+ /// calculate lighting based on heightmap image with provided values for a
+ /// distant light source with specified direction
+ /// parameters: FilerData, surfaceScale, specularConstant, specularExponent,
+ /// kernelUnitLengthX, kernelUnitLengthY, azimuth, elevation
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEDistantLightElement
+ SVGFESpecularLightingDistant{surface_scale: Au, specular_constant: Au,
+ specular_exponent: Au, kernel_unit_length_x: Au,
+ kernel_unit_length_y: Au, azimuth: Au, elevation: Au},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// point light source at specified location
+ /// parameters: FilterOpGraphNode, surfaceScale, specularConstant,
+ /// specularExponent, kernelUnitLengthX, kernelUnitLengthY, x, y, z
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFEPointLightElement
+ SVGFESpecularLightingPoint{surface_scale: Au, specular_constant: Au,
+ specular_exponent: Au, kernel_unit_length_x: Au,
+ kernel_unit_length_y: Au, x: Au, y: Au, z: Au},
+ /// calculate lighting based on heightmap image with provided values for a
+ /// spot light source at specified location pointing at specified target
+ /// location with specified hotspot sharpness and cone angle
+ /// parameters: FilterOpGraphNode, surfaceScale, specularConstant,
+ /// specularExponent, kernelUnitLengthX, kernelUnitLengthY, x, y, z,
+ /// pointsAtX, pointsAtY, pointsAtZ, specularExponent, limitingConeAngle
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpecularLightingElement
+ /// https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFESpotLightElement
+ SVGFESpecularLightingSpot{surface_scale: Au, specular_constant: Au,
+ specular_exponent: Au, kernel_unit_length_x: Au,
+ kernel_unit_length_y: Au, x: Au, y: Au, z: Au, points_at_x: Au,
+ points_at_y: Au, points_at_z: Au, cone_exponent: Au,
+ limiting_cone_angle: Au},
+ /// create a new image based on the input image, repeated throughout the
+ /// output rectangle
+ /// parameters: FilterOpGraphNode
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETileElement
+ SVGFETile,
+ /// convert a color image to an alpha channel - internal use; generated by
+ /// SVGFilterInstance::GetOrCreateSourceAlphaIndex().
+ SVGFEToAlpha,
+ /// synthesize a new image based on Fractal Noise (Perlin) with the chosen
+ /// stitching mode
+ /// parameters: FilterOpGraphNode, baseFrequencyX, baseFrequencyY,
+ /// numOctaves, seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithFractalNoiseWithNoStitching{base_frequency_x: Au,
+ base_frequency_y: Au, num_octaves: u32, seed: u32},
+ /// synthesize a new image based on Fractal Noise (Perlin) with the chosen
+ /// stitching mode
+ /// parameters: FilterOpGraphNode, baseFrequencyX, baseFrequencyY,
+ /// numOctaves, seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithFractalNoiseWithStitching{base_frequency_x: Au,
+ base_frequency_y: Au, num_octaves: u32, seed: u32},
+ /// synthesize a new image based on Turbulence Noise (offset vectors)
+ /// parameters: FilterOpGraphNode, baseFrequencyX, baseFrequencyY,
+ /// numOctaves, seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{base_frequency_x: Au,
+ base_frequency_y: Au, num_octaves: u32, seed: u32},
+ /// synthesize a new image based on Turbulence Noise (offset vectors)
+ /// parameters: FilterOpGraphNode, baseFrequencyX, baseFrequencyY,
+ /// numOctaves, seed
+ /// SVG filter semantics - selectable input(s), selectable between linear
+ /// (default) and sRGB color space for calculations
+ /// Spec: https://www.w3.org/TR/filter-effects-1/#InterfaceSVGFETurbulenceElement
+ SVGFETurbulenceWithTurbulenceNoiseWithStitching{base_frequency_x: Au,
+ base_frequency_y: Au, num_octaves: u32, seed: u32},
+}
+
+impl From<FilterGraphOp> for FilterGraphOpKey {
+ fn from(op: FilterGraphOp) -> Self {
+ match op {
+ FilterGraphOp::SVGFEBlendDarken => FilterGraphOpKey::SVGFEBlendDarken,
+ FilterGraphOp::SVGFEBlendLighten => FilterGraphOpKey::SVGFEBlendLighten,
+ FilterGraphOp::SVGFEBlendMultiply => FilterGraphOpKey::SVGFEBlendMultiply,
+ FilterGraphOp::SVGFEBlendNormal => FilterGraphOpKey::SVGFEBlendNormal,
+ FilterGraphOp::SVGFEBlendScreen => FilterGraphOpKey::SVGFEBlendScreen,
+ FilterGraphOp::SVGFEBlendOverlay => FilterGraphOpKey::SVGFEBlendOverlay,
+ FilterGraphOp::SVGFEBlendColorDodge => FilterGraphOpKey::SVGFEBlendColorDodge,
+ FilterGraphOp::SVGFEBlendColorBurn => FilterGraphOpKey::SVGFEBlendColorBurn,
+ FilterGraphOp::SVGFEBlendHardLight => FilterGraphOpKey::SVGFEBlendHardLight,
+ FilterGraphOp::SVGFEBlendSoftLight => FilterGraphOpKey::SVGFEBlendSoftLight,
+ FilterGraphOp::SVGFEBlendDifference => FilterGraphOpKey::SVGFEBlendDifference,
+ FilterGraphOp::SVGFEBlendExclusion => FilterGraphOpKey::SVGFEBlendExclusion,
+ FilterGraphOp::SVGFEBlendHue => FilterGraphOpKey::SVGFEBlendHue,
+ FilterGraphOp::SVGFEBlendSaturation => FilterGraphOpKey::SVGFEBlendSaturation,
+ FilterGraphOp::SVGFEBlendColor => FilterGraphOpKey::SVGFEBlendColor,
+ FilterGraphOp::SVGFEBlendLuminosity => FilterGraphOpKey::SVGFEBlendLuminosity,
+ FilterGraphOp::SVGFEColorMatrix { values: color_matrix } => {
+ let mut quantized_values: [Au; 20] = [Au(0); 20];
+ for (value, result) in color_matrix.iter().zip(quantized_values.iter_mut()) {
+ *result = Au::from_f32_px(*value);
+ }
+ FilterGraphOpKey::SVGFEColorMatrix{values: quantized_values}
+ }
+ FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
+ FilterGraphOp::SVGFEComponentTransferInterned { handle, creates_pixels } => FilterGraphOpKey::SVGFEComponentTransferInterned{
+ handle: handle.uid(),
+ creates_pixels,
+ },
+ FilterGraphOp::SVGFECompositeArithmetic { k1, k2, k3, k4 } => {
+ FilterGraphOpKey::SVGFECompositeArithmetic{
+ k1: Au::from_f32_px(k1),
+ k2: Au::from_f32_px(k2),
+ k3: Au::from_f32_px(k3),
+ k4: Au::from_f32_px(k4),
+ }
+ }
+ FilterGraphOp::SVGFECompositeATop => FilterGraphOpKey::SVGFECompositeATop,
+ FilterGraphOp::SVGFECompositeIn => FilterGraphOpKey::SVGFECompositeIn,
+ FilterGraphOp::SVGFECompositeLighter => FilterGraphOpKey::SVGFECompositeLighter,
+ FilterGraphOp::SVGFECompositeOut => FilterGraphOpKey::SVGFECompositeOut,
+ FilterGraphOp::SVGFECompositeOver => FilterGraphOpKey::SVGFECompositeOver,
+ FilterGraphOp::SVGFECompositeXOR => FilterGraphOpKey::SVGFECompositeXOR,
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate { order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha } => {
+ let mut values: [Au; SVGFE_CONVOLVE_VALUES_LIMIT] = [Au(0); SVGFE_CONVOLVE_VALUES_LIMIT];
+ for (value, result) in kernel.iter().zip(values.iter_mut()) {
+ *result = Au::from_f32_px(*value)
+ }
+ FilterGraphOpKey::SVGFEConvolveMatrixEdgeModeDuplicate{
+ order_x,
+ order_y,
+ kernel: values,
+ divisor: Au::from_f32_px(divisor),
+ bias: Au::from_f32_px(bias),
+ target_x,
+ target_y,
+ kernel_unit_length_x: Au::from_f32_px(kernel_unit_length_x),
+ kernel_unit_length_y: Au::from_f32_px(kernel_unit_length_y),
+ preserve_alpha,
+ }
+ }
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone { order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha } => {
+ let mut values: [Au; SVGFE_CONVOLVE_VALUES_LIMIT] = [Au(0); SVGFE_CONVOLVE_VALUES_LIMIT];
+ for (value, result) in kernel.iter().zip(values.iter_mut()) {
+ *result = Au::from_f32_px(*value)
+ }
+ FilterGraphOpKey::SVGFEConvolveMatrixEdgeModeNone{
+ order_x,
+ order_y,
+ kernel: values,
+ divisor: Au::from_f32_px(divisor),
+ bias: Au::from_f32_px(bias),
+ target_x,
+ target_y,
+ kernel_unit_length_x: Au::from_f32_px(kernel_unit_length_x),
+ kernel_unit_length_y: Au::from_f32_px(kernel_unit_length_y),
+ preserve_alpha,
+ }
+ }
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap { order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha } => {
+ let mut values: [Au; SVGFE_CONVOLVE_VALUES_LIMIT] = [Au(0); SVGFE_CONVOLVE_VALUES_LIMIT];
+ for (value, result) in kernel.iter().zip(values.iter_mut()) {
+ *result = Au::from_f32_px(*value)
+ }
+ FilterGraphOpKey::SVGFEConvolveMatrixEdgeModeWrap{
+ order_x,
+ order_y,
+ kernel: values,
+ divisor: Au::from_f32_px(divisor),
+ bias: Au::from_f32_px(bias),
+ target_x,
+ target_y,
+ kernel_unit_length_x: Au::from_f32_px(kernel_unit_length_x),
+ kernel_unit_length_y: Au::from_f32_px(kernel_unit_length_y),
+ preserve_alpha,
+ }
+ }
+ FilterGraphOp::SVGFEDiffuseLightingDistant { surface_scale, diffuse_constant, kernel_unit_length_x, kernel_unit_length_y, azimuth, elevation } => {
+ FilterGraphOpKey::SVGFEDiffuseLightingDistant{
+ surface_scale: Au::from_f32_px(surface_scale),
+ diffuse_constant: Au::from_f32_px(diffuse_constant),
+ kernel_unit_length_x: Au::from_f32_px(kernel_unit_length_x),
+ kernel_unit_length_y: Au::from_f32_px(kernel_unit_length_y),
+ azimuth: Au::from_f32_px(azimuth),
+ elevation: Au::from_f32_px(elevation),
+ }
+ }
+ FilterGraphOp::SVGFEDiffuseLightingPoint { surface_scale, diffuse_constant, kernel_unit_length_x, kernel_unit_length_y, x, y, z } => {
+ FilterGraphOpKey::SVGFEDiffuseLightingPoint{
+ surface_scale: Au::from_f32_px(surface_scale),
+ diffuse_constant: Au::from_f32_px(diffuse_constant),
+ kernel_unit_length_x: Au::from_f32_px(kernel_unit_length_x),
+ kernel_unit_length_y: Au::from_f32_px(kernel_unit_length_y),
+ x: Au::from_f32_px(x),
+ y: Au::from_f32_px(y),
+ z: Au::from_f32_px(z),
+ }
+ }
+ FilterGraphOp::SVGFEDiffuseLightingSpot { surface_scale, diffuse_constant, kernel_unit_length_x, kernel_unit_length_y, x, y, z, points_at_x, points_at_y, points_at_z, cone_exponent, limiting_cone_angle } => {
+ FilterGraphOpKey::SVGFEDiffuseLightingSpot{
+ surface_scale: Au::from_f32_px(surface_scale),
+ diffuse_constant: Au::from_f32_px(diffuse_constant),
+ kernel_unit_length_x: Au::from_f32_px(kernel_unit_length_x),
+ kernel_unit_length_y: Au::from_f32_px(kernel_unit_length_y),
+ x: Au::from_f32_px(x),
+ y: Au::from_f32_px(y),
+ z: Au::from_f32_px(z),
+ points_at_x: Au::from_f32_px(points_at_x),
+ points_at_y: Au::from_f32_px(points_at_y),
+ points_at_z: Au::from_f32_px(points_at_z),
+ cone_exponent: Au::from_f32_px(cone_exponent),
+ limiting_cone_angle: Au::from_f32_px(limiting_cone_angle),
+ }
+ }
+ FilterGraphOp::SVGFEDisplacementMap { scale, x_channel_selector, y_channel_selector } => {
+ FilterGraphOpKey::SVGFEDisplacementMap{
+ scale: Au::from_f32_px(scale),
+ x_channel_selector,
+ y_channel_selector,
+ }
+ }
+ FilterGraphOp::SVGFEDropShadow { color, dx, dy, std_deviation_x, std_deviation_y } => {
+ FilterGraphOpKey::SVGFEDropShadow{
+ color: color.into(),
+ dx: Au::from_f32_px(dx),
+ dy: Au::from_f32_px(dy),
+ std_deviation_x: Au::from_f32_px(std_deviation_x),
+ std_deviation_y: Au::from_f32_px(std_deviation_y),
+ }
+ }
+ FilterGraphOp::SVGFEFlood { color } => FilterGraphOpKey::SVGFEFlood{color: color.into()},
+ FilterGraphOp::SVGFEGaussianBlur { std_deviation_x, std_deviation_y } => {
+ FilterGraphOpKey::SVGFEGaussianBlur{
+ std_deviation_x: Au::from_f32_px(std_deviation_x),
+ std_deviation_y: Au::from_f32_px(std_deviation_y),
+ }
+ }
+ FilterGraphOp::SVGFEIdentity => FilterGraphOpKey::SVGFEIdentity,
+ FilterGraphOp::SVGFEImage { sampling_filter, matrix } => {
+ let mut values: [Au; 6] = [Au(0); 6];
+ for (value, result) in matrix.iter().zip(values.iter_mut()) {
+ *result = Au::from_f32_px(*value)
+ }
+ FilterGraphOpKey::SVGFEImage{
+ sampling_filter,
+ matrix: values,
+ }
+ }
+ FilterGraphOp::SVGFEMorphologyDilate { radius_x, radius_y } => {
+ FilterGraphOpKey::SVGFEMorphologyDilate{
+ radius_x: Au::from_f32_px(radius_x),
+ radius_y: Au::from_f32_px(radius_y),
+ }
+ }
+ FilterGraphOp::SVGFEMorphologyErode { radius_x, radius_y } => {
+ FilterGraphOpKey::SVGFEMorphologyErode{
+ radius_x: Au::from_f32_px(radius_x),
+ radius_y: Au::from_f32_px(radius_y),
+ }
+ }
+ FilterGraphOp::SVGFEOpacity{valuebinding: binding, value: _} => {
+ match binding {
+ PropertyBinding::Value(value) => {
+ FilterGraphOpKey::SVGFEOpacity{value: Au::from_f32_px(value)}
+ }
+ PropertyBinding::Binding(key, default) => {
+ FilterGraphOpKey::SVGFEOpacityBinding{valuebindingid: key.id, value: Au::from_f32_px(default)}
+ }
+ }
+ }
+ FilterGraphOp::SVGFESourceAlpha => FilterGraphOpKey::SVGFESourceAlpha,
+ FilterGraphOp::SVGFESourceGraphic => FilterGraphOpKey::SVGFESourceGraphic,
+ FilterGraphOp::SVGFESpecularLightingDistant { surface_scale, specular_constant, specular_exponent, kernel_unit_length_x, kernel_unit_length_y, azimuth, elevation } => {
+ FilterGraphOpKey::SVGFESpecularLightingDistant{
+ surface_scale: Au::from_f32_px(surface_scale),
+ specular_constant: Au::from_f32_px(specular_constant),
+ specular_exponent: Au::from_f32_px(specular_exponent),
+ kernel_unit_length_x: Au::from_f32_px(kernel_unit_length_x),
+ kernel_unit_length_y: Au::from_f32_px(kernel_unit_length_y),
+ azimuth: Au::from_f32_px(azimuth),
+ elevation: Au::from_f32_px(elevation),
+ }
+ }
+ FilterGraphOp::SVGFESpecularLightingPoint { surface_scale, specular_constant, specular_exponent, kernel_unit_length_x, kernel_unit_length_y, x, y, z } => {
+ FilterGraphOpKey::SVGFESpecularLightingPoint{
+ surface_scale: Au::from_f32_px(surface_scale),
+ specular_constant: Au::from_f32_px(specular_constant),
+ specular_exponent: Au::from_f32_px(specular_exponent),
+ kernel_unit_length_x: Au::from_f32_px(kernel_unit_length_x),
+ kernel_unit_length_y: Au::from_f32_px(kernel_unit_length_y),
+ x: Au::from_f32_px(x),
+ y: Au::from_f32_px(y),
+ z: Au::from_f32_px(z),
+ }
+ }
+ FilterGraphOp::SVGFESpecularLightingSpot { surface_scale, specular_constant, specular_exponent, kernel_unit_length_x, kernel_unit_length_y, x, y, z, points_at_x, points_at_y, points_at_z, cone_exponent, limiting_cone_angle } => {
+ FilterGraphOpKey::SVGFESpecularLightingSpot{
+ surface_scale: Au::from_f32_px(surface_scale),
+ specular_constant: Au::from_f32_px(specular_constant),
+ specular_exponent: Au::from_f32_px(specular_exponent),
+ kernel_unit_length_x: Au::from_f32_px(kernel_unit_length_x),
+ kernel_unit_length_y: Au::from_f32_px(kernel_unit_length_y),
+ x: Au::from_f32_px(x),
+ y: Au::from_f32_px(y),
+ z: Au::from_f32_px(z),
+ points_at_x: Au::from_f32_px(points_at_x),
+ points_at_y: Au::from_f32_px(points_at_y),
+ points_at_z: Au::from_f32_px(points_at_z),
+ cone_exponent: Au::from_f32_px(cone_exponent),
+ limiting_cone_angle: Au::from_f32_px(limiting_cone_angle),
+ }
+ }
+ FilterGraphOp::SVGFETile => FilterGraphOpKey::SVGFETile,
+ FilterGraphOp::SVGFEToAlpha => FilterGraphOpKey::SVGFEToAlpha,
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching { base_frequency_x, base_frequency_y, num_octaves, seed } => {
+ FilterGraphOpKey::SVGFETurbulenceWithFractalNoiseWithNoStitching {
+ base_frequency_x: Au::from_f32_px(base_frequency_x),
+ base_frequency_y: Au::from_f32_px(base_frequency_y),
+ num_octaves,
+ seed,
+ }
+ }
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching { base_frequency_x, base_frequency_y, num_octaves, seed } => {
+ FilterGraphOpKey::SVGFETurbulenceWithFractalNoiseWithStitching {
+ base_frequency_x: Au::from_f32_px(base_frequency_x),
+ base_frequency_y: Au::from_f32_px(base_frequency_y),
+ num_octaves,
+ seed,
+ }
+ }
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching { base_frequency_x, base_frequency_y, num_octaves, seed } => {
+ FilterGraphOpKey::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching {
+ base_frequency_x: Au::from_f32_px(base_frequency_x),
+ base_frequency_y: Au::from_f32_px(base_frequency_y),
+ num_octaves,
+ seed,
+ }
+ }
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching { base_frequency_x, base_frequency_y, num_octaves, seed } => {
+ FilterGraphOpKey::SVGFETurbulenceWithTurbulenceNoiseWithStitching {
+ base_frequency_x: Au::from_f32_px(base_frequency_x),
+ base_frequency_y: Au::from_f32_px(base_frequency_y),
+ num_octaves,
+ seed,
+ }
+ }
+ }
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)]
+pub struct FilterGraphNodeKey {
+ /// Indicates this graph node was marked as unnecessary by the DAG optimizer
+ /// (for example SVGFEOffset can often be folded into downstream nodes)
+ pub kept_by_optimizer: bool,
+ /// True if color_interpolation_filter == LinearRgb; shader will convert
+ /// sRGB texture pixel colors on load and convert back on store, for correct
+ /// interpolation
+ pub linear: bool,
+ /// padding for output rect if we need a border to get correct clamping, or
+ /// to account for larger final subregion than source rect (see bug 1869672)
+ pub inflate: i16,
+ /// virtualized picture input binding 1 (i.e. texture source), typically
+ /// this is used, but certain filters do not use it
+ pub inputs: Vec<FilterGraphPictureReferenceKey>,
+ /// rect this node will render into, in filter space, does not account for
+ /// inflate or device_pixel_scale
+ pub subregion: [Au; 4],
+}
+
+impl From<FilterGraphNode> for FilterGraphNodeKey {
+ fn from(node: FilterGraphNode) -> Self {
+ FilterGraphNodeKey{
+ kept_by_optimizer: node.kept_by_optimizer,
+ linear: node.linear,
+ inflate: node.inflate,
+ inputs: node.inputs.into_iter().map(|node| {node.into()}).collect(),
+ subregion: [
+ Au::from_f32_px(node.subregion.min.x),
+ Au::from_f32_px(node.subregion.min.y),
+ Au::from_f32_px(node.subregion.max.x),
+ Au::from_f32_px(node.subregion.max.y),
+ ],
+ }
+ }
+}
+
/// Represents a hashable description of how a picture primitive
/// will be composited into its parent.
#[cfg_attr(feature = "capture", derive(Serialize))]
@@ -96,6 +850,7 @@ pub enum PictureCompositeKey {
ComponentTransfer(ItemUid),
Flood(ColorU),
SvgFilter(Vec<FilterPrimitiveKey>),
+ SVGFEGraph(Vec<(FilterGraphNodeKey, FilterGraphOpKey)>),
// MixBlendMode
Multiply,
@@ -180,6 +935,7 @@ impl From<Option<PictureCompositeMode>> for PictureCompositeKey {
}
Filter::ComponentTransfer => unreachable!(),
Filter::Flood(color) => PictureCompositeKey::Flood(color.into()),
+ Filter::SVGGraphNode(_node, _op) => unreachable!(),
}
}
Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
@@ -222,6 +978,12 @@ impl From<Option<PictureCompositeMode>> for PictureCompositeKey {
}
}).collect())
}
+ Some(PictureCompositeMode::SVGFEGraph(filter_nodes)) => {
+ PictureCompositeKey::SVGFEGraph(
+ filter_nodes.into_iter().map(|(node, op)| {
+ (node.into(), op.into())
+ }).collect())
+ }
Some(PictureCompositeMode::Blit(_)) |
Some(PictureCompositeMode::TileCache { .. }) |
Some(PictureCompositeMode::IntermediateSurface) |
diff --git a/gfx/wr/webrender/src/quad.rs b/gfx/wr/webrender/src/quad.rs
index 5455611f3f..4e83b0c425 100644
--- a/gfx/wr/webrender/src/quad.rs
+++ b/gfx/wr/webrender/src/quad.rs
@@ -88,6 +88,7 @@ pub fn push_quad(
frame_state.clip_store,
interned_clips,
prim_is_2d_scale_translation,
+ pattern,
frame_context.spatial_tree,
);
@@ -161,10 +162,10 @@ pub fn push_quad(
match strategy {
QuadRenderStrategy::Direct => {}
QuadRenderStrategy::Indirect => {
- let segment = add_segment(
+ let task_id = add_render_task_with_mask(
pattern,
- &clipped_surface_rect,
- true,
+ clipped_surface_rect.size(),
+ clipped_surface_rect.min.to_f32(),
clip_chain,
prim_spatial_node_index,
pic_context.raster_spatial_node_index,
@@ -177,21 +178,23 @@ pub fn push_quad(
frame_state,
);
+ let rect = clipped_surface_rect.to_f32().cast_unit();
+ let is_masked = true;
add_composite_prim(
pattern,
+ is_masked,
prim_instance_index,
- segment.rect,
- quad_flags,
+ rect,
frame_state,
targets,
- &[segment],
+ &[QuadSegment { rect, task_id }],
);
}
QuadRenderStrategy::Tiled { x_tiles, y_tiles } => {
let unclipped_surface_rect = surface
.map_to_device_rect(&clip_chain.pic_coverage_rect, frame_context.spatial_tree);
- scratch.quad_segments.clear();
+ scratch.quad_indirect_segments.clear();
let mut x_coords = vec![clipped_surface_rect.min.x];
let mut y_coords = vec![clipped_surface_rect.min.y];
@@ -225,16 +228,17 @@ pub fn push_quad(
continue;
}
- let create_task = true;
- let rect = DeviceIntRect {
+ let int_rect = DeviceIntRect {
min: point2(x0, y0),
max: point2(x1, y1),
};
- let segment = add_segment(
+ let rect = int_rect.to_f32();
+
+ let task_id = add_render_task_with_mask(
pattern,
- &rect,
- create_task,
+ int_rect.size(),
+ rect.min,
clip_chain,
prim_spatial_node_index,
pic_context.raster_spatial_node_index,
@@ -246,18 +250,20 @@ pub fn push_quad(
needs_scissor,
frame_state,
);
- scratch.quad_segments.push(segment);
+
+ scratch.quad_indirect_segments.push(QuadSegment { rect: rect.cast_unit(), task_id });
}
}
+ let is_masked = true;
add_composite_prim(
pattern,
+ is_masked,
prim_instance_index,
unclipped_surface_rect.cast_unit(),
- quad_flags,
frame_state,
targets,
- &scratch.quad_segments,
+ &scratch.quad_indirect_segments,
);
}
QuadRenderStrategy::NinePatch { clip_rect, radius } => {
@@ -298,7 +304,21 @@ pub fn push_quad(
x_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
y_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
- scratch.quad_segments.clear();
+ scratch.quad_direct_segments.clear();
+ scratch.quad_indirect_segments.clear();
+
+ // TODO: re-land clip-out mode.
+ let mode = ClipMode::Clip;
+
+ fn should_create_task(mode: ClipMode, x: usize, y: usize) -> bool {
+ match mode {
+ // Only create render tasks for the corners.
+ ClipMode::Clip => x != 1 && y != 1,
+ // Create render tasks for all segments (the
+ // center will be skipped).
+ ClipMode::ClipOut => true,
+ }
+ }
for y in 0 .. y_coords.len()-1 {
let y0 = y_coords[y];
@@ -309,6 +329,10 @@ pub fn push_quad(
}
for x in 0 .. x_coords.len()-1 {
+ if mode == ClipMode::ClipOut && x == 1 && y == 1 {
+ continue;
+ }
+
let x0 = x_coords[x];
let x1 = x_coords[x+1];
@@ -316,46 +340,68 @@ pub fn push_quad(
continue;
}
- // Only create render tasks for the corners.
- let create_task = x != 1 && y != 1;
-
let rect = DeviceIntRect::new(point2(x0, y0), point2(x1, y1));
- let rect = match rect.intersection(&clipped_surface_rect) {
+ let device_rect = match rect.intersection(&clipped_surface_rect) {
Some(rect) => rect,
None => {
continue;
}
};
- let segment = add_segment(
- pattern,
- &rect,
- create_task,
- clip_chain,
- prim_spatial_node_index,
- pic_context.raster_spatial_node_index,
- main_prim_address,
- transform_id,
- aa_flags,
- quad_flags,
- device_pixel_scale,
- false,
- frame_state,
- );
- scratch.quad_segments.push(segment);
+ if should_create_task(mode, x, y) {
+ let task_id = add_render_task_with_mask(
+ pattern,
+ device_rect.size(),
+ device_rect.min.to_f32(),
+ clip_chain,
+ prim_spatial_node_index,
+ pic_context.raster_spatial_node_index,
+ main_prim_address,
+ transform_id,
+ aa_flags,
+ quad_flags,
+ device_pixel_scale,
+ false,
+ frame_state,
+ );
+ scratch.quad_indirect_segments.push(QuadSegment {
+ rect: device_rect.to_f32().cast_unit(),
+ task_id,
+ });
+ } else {
+ scratch.quad_direct_segments.push(QuadSegment {
+ rect: device_rect.to_f32().cast_unit(),
+ task_id: RenderTaskId::INVALID,
+ });
+ };
}
}
- add_composite_prim(
- pattern,
- prim_instance_index,
- unclipped_surface_rect.cast_unit(),
- quad_flags,
- frame_state,
- targets,
- &scratch.quad_segments,
- );
+ if !scratch.quad_direct_segments.is_empty() {
+ add_pattern_prim(
+ pattern,
+ prim_instance_index,
+ unclipped_surface_rect.cast_unit(),
+ pattern.is_opaque,
+ frame_state,
+ targets,
+ &scratch.quad_direct_segments,
+ );
+ }
+
+ if !scratch.quad_indirect_segments.is_empty() {
+ let is_masked = true;
+ add_composite_prim(
+ pattern,
+ is_masked,
+ prim_instance_index,
+ unclipped_surface_rect.cast_unit(),
+ frame_state,
+ targets,
+ &scratch.quad_indirect_segments,
+ );
+ }
}
}
}
@@ -366,6 +412,7 @@ fn get_prim_render_strategy(
clip_store: &ClipStore,
interned_clips: &DataStore<ClipIntern>,
can_use_nine_patch: bool,
+ pattern: &Pattern,
spatial_tree: &SpatialTree,
) -> QuadRenderStrategy {
if !clip_chain.needs_mask {
@@ -385,6 +432,10 @@ fn get_prim_render_strategy(
return QuadRenderStrategy::Indirect;
}
+ if !pattern.supports_segmented_rendering() {
+ return QuadRenderStrategy::Indirect;
+ }
+
if can_use_nine_patch && clip_chain.clips_range.count == 1 {
let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, 0);
let clip_node = &interned_clips[clip_instance.handle];
@@ -432,10 +483,10 @@ fn get_prim_render_strategy(
}
}
-fn add_segment(
+fn add_render_task_with_mask(
pattern: &Pattern,
- rect: &DeviceIntRect,
- create_task: bool,
+ task_size: DeviceIntSize,
+ content_origin: DevicePoint,
clip_chain: &ClipChainInstance,
prim_spatial_node_index: SpatialNodeIndex,
raster_spatial_node_index: SpatialNodeIndex,
@@ -446,56 +497,86 @@ fn add_segment(
device_pixel_scale: DevicePixelScale,
needs_scissor_rect: bool,
frame_state: &mut FrameBuildingState,
-) -> QuadSegment {
- let task_size = rect.size();
- let rect = rect.to_f32();
- let content_origin = rect.min;
-
- let task_id = if create_task {
- let task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
- task_size,
- RenderTaskKind::new_prim(
- pattern.kind,
- pattern.shader_input,
- prim_spatial_node_index,
- raster_spatial_node_index,
- device_pixel_scale,
- content_origin,
- prim_address_f,
- transform_id,
- aa_flags,
- quad_flags,
- clip_chain.clips_range,
- needs_scissor_rect,
- ),
- ));
-
- let masks = MaskSubPass {
- clip_node_range: clip_chain.clips_range,
- prim_spatial_node_index,
+) -> RenderTaskId {
+ let task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
+ task_size,
+ RenderTaskKind::new_prim(
+ pattern.kind,
+ pattern.shader_input,
+ raster_spatial_node_index,
+ device_pixel_scale,
+ content_origin,
prim_address_f,
- };
+ transform_id,
+ aa_flags,
+ quad_flags,
+ clip_chain.clips_range,
+ needs_scissor_rect,
+ ),
+ ));
- let task = frame_state.rg_builder.get_task_mut(task_id);
- task.add_sub_pass(SubPass::Masks { masks });
+ let masks = MaskSubPass {
+ clip_node_range: clip_chain.clips_range,
+ prim_spatial_node_index,
+ prim_address_f,
+ };
- frame_state
- .surface_builder
- .add_child_render_task(task_id, frame_state.rg_builder);
+ let task = frame_state.rg_builder.get_task_mut(task_id);
+ task.add_sub_pass(SubPass::Masks { masks });
- task_id
- } else {
- RenderTaskId::INVALID
- };
+ frame_state
+ .surface_builder
+ .add_child_render_task(task_id, frame_state.rg_builder);
- QuadSegment { rect: rect.cast_unit(), task_id }
+ task_id
+}
+
+fn add_pattern_prim(
+ pattern: &Pattern,
+ prim_instance_index: PrimitiveInstanceIndex,
+ rect: LayoutRect,
+ is_opaque: bool,
+ frame_state: &mut FrameBuildingState,
+ targets: &[CommandBufferIndex],
+ segments: &[QuadSegment],
+) {
+ let prim_address = write_prim_blocks(
+ &mut frame_state.frame_gpu_data.f32,
+ rect,
+ rect,
+ pattern.base_color,
+ segments,
+ );
+
+ frame_state.set_segments(segments, targets);
+
+ let mut quad_flags = QuadFlags::IGNORE_DEVICE_PIXEL_SCALE
+ | QuadFlags::APPLY_DEVICE_CLIP;
+
+ if is_opaque {
+ quad_flags |= QuadFlags::IS_OPAQUE;
+ }
+
+ frame_state.push_cmd(
+ &PrimitiveCommand::quad(
+ pattern.kind,
+ pattern.shader_input,
+ prim_instance_index,
+ prim_address,
+ TransformPaletteId::IDENTITY,
+ quad_flags,
+ // TODO(gw): No AA on composite, unless we use it to apply 2d clips
+ EdgeAaSegmentMask::empty(),
+ ),
+ targets,
+ );
}
fn add_composite_prim(
pattern: &Pattern,
+ is_masked: bool,
prim_instance_index: PrimitiveInstanceIndex,
rect: LayoutRect,
- quad_flags: QuadFlags,
frame_state: &mut FrameBuildingState,
targets: &[CommandBufferIndex],
segments: &[QuadSegment],
@@ -504,16 +585,17 @@ fn add_composite_prim(
&mut frame_state.frame_gpu_data.f32,
rect,
rect,
- pattern.base_color,
+ PremultipliedColorF::WHITE,
segments,
);
frame_state.set_segments(segments, targets);
- let mut composite_quad_flags =
- QuadFlags::IGNORE_DEVICE_PIXEL_SCALE | QuadFlags::APPLY_DEVICE_CLIP;
- if quad_flags.contains(QuadFlags::IS_OPAQUE) {
- composite_quad_flags |= QuadFlags::IS_OPAQUE;
+ let mut quad_flags = QuadFlags::IGNORE_DEVICE_PIXEL_SCALE
+ | QuadFlags::APPLY_DEVICE_CLIP;
+
+ if pattern.is_opaque && !is_masked {
+ quad_flags |= QuadFlags::IS_OPAQUE;
}
frame_state.push_cmd(
@@ -523,7 +605,7 @@ fn add_composite_prim(
prim_instance_index,
composite_prim_address,
TransformPaletteId::IDENTITY,
- composite_quad_flags,
+ quad_flags,
// TODO(gw): No AA on composite, unless we use it to apply 2d clips
EdgeAaSegmentMask::empty(),
),
@@ -562,13 +644,13 @@ pub fn write_prim_blocks(
pub fn add_to_batch<F>(
kind: PatternKind,
pattern_input: PatternShaderInput,
- render_task_address: RenderTaskAddress,
+ dst_task_address: RenderTaskAddress,
transform_id: TransformPaletteId,
prim_address_f: GpuBufferAddress,
quad_flags: QuadFlags,
edge_flags: EdgeAaSegmentMask,
segment_index: u8,
- task_id: RenderTaskId,
+ src_task_id: RenderTaskId,
z_id: ZBufferId,
render_tasks: &RenderTaskGraph,
gpu_buffer_builder: &mut GpuBufferBuilder,
@@ -596,13 +678,11 @@ pub fn add_to_batch<F>(
]);
let prim_address_i = writer.finish();
- let texture = match task_id {
- RenderTaskId::INVALID => {
- TextureSource::Invalid
- }
+ let texture = match src_task_id {
+ RenderTaskId::INVALID => TextureSource::Invalid,
_ => {
let texture = render_tasks
- .resolve_texture(task_id)
+ .resolve_texture(src_task_id)
.expect("bug: valid task id must be resolvable");
texture
@@ -614,7 +694,7 @@ pub fn add_to_batch<F>(
TextureSource::Invalid,
);
- let default_blend_mode = if quad_flags.contains(QuadFlags::IS_OPAQUE) && task_id == RenderTaskId::INVALID {
+ let default_blend_mode = if quad_flags.contains(QuadFlags::IS_OPAQUE) {
BlendMode::None
} else {
BlendMode::PremultipliedAlpha
@@ -635,7 +715,7 @@ pub fn add_to_batch<F>(
};
let mut instance = QuadInstance {
- render_task_address,
+ dst_task_address,
prim_address_i,
prim_address_f,
z_id,
diff --git a/gfx/wr/webrender/src/render_target.rs b/gfx/wr/webrender/src/render_target.rs
index f53b5dd4f8..c50a1b2303 100644
--- a/gfx/wr/webrender/src/render_target.rs
+++ b/gfx/wr/webrender/src/render_target.rs
@@ -14,10 +14,10 @@ use crate::spatial_tree::SpatialTree;
use crate::clip::{ClipStore, ClipItemKind};
use crate::frame_builder::{FrameGlobalResources};
use crate::gpu_cache::{GpuCache, GpuCacheAddress};
-use crate::gpu_types::{BorderInstance, SvgFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
+use crate::gpu_types::{BorderInstance, SvgFilterInstance, SVGFEFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
use crate::gpu_types::{TransformPalette, ZBufferIdGenerator, MaskInstance, ClipSpace};
use crate::gpu_types::{ZBufferId, QuadSegment, PrimitiveInstanceData, TransformPaletteId};
-use crate::internal_types::{FastHashMap, TextureSource, CacheTextureId};
+use crate::internal_types::{FastHashMap, TextureSource, CacheTextureId, FilterGraphOp};
use crate::picture::{SliceId, SurfaceInfo, ResolvedSurfaceTexture, TileCacheInstance};
use crate::quad;
use crate::prim_store::{PrimitiveInstance, PrimitiveStore, PrimitiveScratchBuffer};
@@ -28,7 +28,7 @@ use crate::prim_store::gradient::{
use crate::renderer::{GpuBufferAddress, GpuBufferBuilder};
use crate::render_backend::DataStores;
use crate::render_task::{RenderTaskKind, RenderTaskAddress, SubPass};
-use crate::render_task::{RenderTask, ScalingTask, SvgFilterInfo, MaskSubPass};
+use crate::render_task::{RenderTask, ScalingTask, SvgFilterInfo, MaskSubPass, SVGFEFilterTask};
use crate::render_task_graph::{RenderTaskGraph, RenderTaskId};
use crate::resource_cache::ResourceCache;
use crate::spatial_tree::{SpatialNodeIndex};
@@ -226,6 +226,7 @@ pub struct ColorRenderTarget {
pub horizontal_blurs: FastHashMap<TextureSource, Vec<BlurInstance>>,
pub scalings: FastHashMap<TextureSource, Vec<ScalingInstance>>,
pub svg_filters: Vec<(BatchTextures, Vec<SvgFilterInstance>)>,
+ pub svg_nodes: Vec<(BatchTextures, Vec<SVGFEFilterInstance>)>,
pub blits: Vec<BlitJob>,
alpha_tasks: Vec<RenderTaskId>,
screen_size: DeviceIntSize,
@@ -256,6 +257,7 @@ impl RenderTarget for ColorRenderTarget {
horizontal_blurs: FastHashMap::default(),
scalings: FastHashMap::default(),
svg_filters: Vec::new(),
+ svg_nodes: Vec::new(),
blits: Vec::new(),
alpha_tasks: Vec::new(),
screen_size,
@@ -263,7 +265,7 @@ impl RenderTarget for ColorRenderTarget {
used_rect,
resolve_ops: Vec::new(),
clear_color: Some(ColorF::TRANSPARENT),
- prim_instances: [Vec::new(), Vec::new()],
+ prim_instances: [Vec::new(), Vec::new(), Vec::new(), Vec::new()],
prim_instances_with_scissor: FastHashMap::default(),
clip_masks: ClipMaskInstanceList::new(),
}
@@ -438,6 +440,17 @@ impl RenderTarget for ColorRenderTarget {
task_info.extra_gpu_cache_handle.map(|handle| gpu_cache.get_address(&handle)),
)
}
+ RenderTaskKind::SVGFENode(ref task_info) => {
+ add_svg_filter_node_instances(
+ &mut self.svg_nodes,
+ render_tasks,
+ &task_info,
+ task,
+ task.children.get(0).cloned(),
+ task.children.get(1).cloned(),
+ task_info.extra_gpu_cache_handle.map(|handle| gpu_cache.get_address(&handle)),
+ )
+ }
RenderTaskKind::Image(..) |
RenderTaskKind::Cached(..) |
RenderTaskKind::ClipRegion(..) |
@@ -559,7 +572,8 @@ impl RenderTarget for AlphaRenderTarget {
RenderTaskKind::ConicGradient(..) |
RenderTaskKind::TileComposite(..) |
RenderTaskKind::Prim(..) |
- RenderTaskKind::SvgFilter(..) => {
+ RenderTaskKind::SvgFilter(..) |
+ RenderTaskKind::SVGFENode(..) => {
panic!("BUG: should not be added to alpha target!");
}
RenderTaskKind::Empty(..) => {
@@ -799,7 +813,8 @@ impl TextureCacheRenderTarget {
RenderTaskKind::Scaling(..) |
RenderTaskKind::TileComposite(..) |
RenderTaskKind::Empty(..) |
- RenderTaskKind::SvgFilter(..) => {
+ RenderTaskKind::SvgFilter(..) |
+ RenderTaskKind::SVGFENode(..) => {
panic!("BUG: unexpected task kind for texture cache target");
}
#[cfg(test)]
@@ -945,6 +960,175 @@ fn add_svg_filter_instances(
instances.push((textures, vec![instance]));
}
+/// Generates SVGFEFilterInstances from a single SVGFEFilterTask, this is what
+/// prepares vertex data for the shader, and adds it to the appropriate batch.
+///
+/// The interesting parts of the handling of SVG filters are:
+/// * scene_building.rs : wrap_prim_with_filters
+/// * picture.rs : get_coverage_svgfe
+/// * render_task.rs : new_svg_filter_graph
+/// * render_target.rs : add_svg_filter_node_instances (you are here)
+fn add_svg_filter_node_instances(
+ instances: &mut Vec<(BatchTextures, Vec<SVGFEFilterInstance>)>,
+ render_tasks: &RenderTaskGraph,
+ task_info: &SVGFEFilterTask,
+ target_task: &RenderTask,
+ input_1_task: Option<RenderTaskId>,
+ input_2_task: Option<RenderTaskId>,
+ extra_data_address: Option<GpuCacheAddress>,
+) {
+ let node = &task_info.node;
+ let op = &task_info.op;
+ let mut textures = BatchTextures::empty();
+
+ // We have to undo the inflate here as the inflated target rect is meant to
+ // have a blank border
+ let target_rect = target_task
+ .get_target_rect()
+ .inner_box(DeviceIntSideOffsets::new(node.inflate as i32, node.inflate as i32, node.inflate as i32, node.inflate as i32))
+ .to_f32();
+
+ let mut instance = SVGFEFilterInstance {
+ target_rect,
+ input_1_content_scale_and_offset: [0.0; 4],
+ input_2_content_scale_and_offset: [0.0; 4],
+ input_1_task_address: RenderTaskId::INVALID.into(),
+ input_2_task_address: RenderTaskId::INVALID.into(),
+ kind: 0,
+ input_count: node.inputs.len() as u16,
+ extra_data_address: extra_data_address.unwrap_or(GpuCacheAddress::INVALID),
+ };
+
+ // Must match FILTER_* in cs_svg_filter_node.glsl
+ instance.kind = match op {
+ // Identity does not modify color, no linear case
+ FilterGraphOp::SVGFEIdentity => 0,
+ // SourceGraphic does not have its own shader mode, it uses Identity.
+ FilterGraphOp::SVGFESourceGraphic => 0,
+ // SourceAlpha does not have its own shader mode, it uses ToAlpha.
+ FilterGraphOp::SVGFESourceAlpha => 4,
+ // Opacity scales the entire rgba color, so it does not need a linear
+ // case as the rgb / a ratio does not change (sRGB is a curve on the RGB
+ // before alpha multiply, not after)
+ FilterGraphOp::SVGFEOpacity{..} => 2,
+ FilterGraphOp::SVGFEToAlpha => 4,
+ FilterGraphOp::SVGFEBlendColor => {match node.linear {false => 6, true => 7}},
+ FilterGraphOp::SVGFEBlendColorBurn => {match node.linear {false => 8, true => 9}},
+ FilterGraphOp::SVGFEBlendColorDodge => {match node.linear {false => 10, true => 11}},
+ FilterGraphOp::SVGFEBlendDarken => {match node.linear {false => 12, true => 13}},
+ FilterGraphOp::SVGFEBlendDifference => {match node.linear {false => 14, true => 15}},
+ FilterGraphOp::SVGFEBlendExclusion => {match node.linear {false => 16, true => 17}},
+ FilterGraphOp::SVGFEBlendHardLight => {match node.linear {false => 18, true => 19}},
+ FilterGraphOp::SVGFEBlendHue => {match node.linear {false => 20, true => 21}},
+ FilterGraphOp::SVGFEBlendLighten => {match node.linear {false => 22, true => 23}},
+ FilterGraphOp::SVGFEBlendLuminosity => {match node.linear {false => 24, true => 25}},
+ FilterGraphOp::SVGFEBlendMultiply => {match node.linear {false => 26, true => 27}},
+ FilterGraphOp::SVGFEBlendNormal => {match node.linear {false => 28, true => 29}},
+ FilterGraphOp::SVGFEBlendOverlay => {match node.linear {false => 30, true => 31}},
+ FilterGraphOp::SVGFEBlendSaturation => {match node.linear {false => 32, true => 33}},
+ FilterGraphOp::SVGFEBlendScreen => {match node.linear {false => 34, true => 35}},
+ FilterGraphOp::SVGFEBlendSoftLight => {match node.linear {false => 36, true => 37}},
+ FilterGraphOp::SVGFEColorMatrix{..} => {match node.linear {false => 38, true => 39}},
+ FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
+ FilterGraphOp::SVGFEComponentTransferInterned{..} => {match node.linear {false => 40, true => 41}},
+ FilterGraphOp::SVGFECompositeArithmetic{..} => {match node.linear {false => 42, true => 43}},
+ FilterGraphOp::SVGFECompositeATop => {match node.linear {false => 44, true => 45}},
+ FilterGraphOp::SVGFECompositeIn => {match node.linear {false => 46, true => 47}},
+ FilterGraphOp::SVGFECompositeLighter => {match node.linear {false => 48, true => 49}},
+ FilterGraphOp::SVGFECompositeOut => {match node.linear {false => 50, true => 51}},
+ FilterGraphOp::SVGFECompositeOver => {match node.linear {false => 52, true => 53}},
+ FilterGraphOp::SVGFECompositeXOR => {match node.linear {false => 54, true => 55}},
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => {match node.linear {false => 56, true => 57}},
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => {match node.linear {false => 58, true => 59}},
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => {match node.linear {false => 60, true => 61}},
+ FilterGraphOp::SVGFEDiffuseLightingDistant{..} => {match node.linear {false => 62, true => 63}},
+ FilterGraphOp::SVGFEDiffuseLightingPoint{..} => {match node.linear {false => 64, true => 65}},
+ FilterGraphOp::SVGFEDiffuseLightingSpot{..} => {match node.linear {false => 66, true => 67}},
+ FilterGraphOp::SVGFEDisplacementMap{..} => {match node.linear {false => 68, true => 69}},
+ FilterGraphOp::SVGFEDropShadow{..} => {match node.linear {false => 70, true => 71}},
+ // feFlood takes an sRGB color and does no math on it, no linear case
+ FilterGraphOp::SVGFEFlood{..} => 72,
+ FilterGraphOp::SVGFEGaussianBlur{..} => {match node.linear {false => 74, true => 75}},
+ // feImage does not meaningfully modify the color of its input, though a
+ // case could be made for gamma-correct image scaling, that's a bit out
+ // of scope for now
+ FilterGraphOp::SVGFEImage{..} => 76,
+ FilterGraphOp::SVGFEMorphologyDilate{..} => {match node.linear {false => 80, true => 81}},
+ FilterGraphOp::SVGFEMorphologyErode{..} => {match node.linear {false => 82, true => 83}},
+ FilterGraphOp::SVGFESpecularLightingDistant{..} => {match node.linear {false => 86, true => 87}},
+ FilterGraphOp::SVGFESpecularLightingPoint{..} => {match node.linear {false => 88, true => 89}},
+ FilterGraphOp::SVGFESpecularLightingSpot{..} => {match node.linear {false => 90, true => 91}},
+ // feTile does not modify color, no linear case
+ FilterGraphOp::SVGFETile => 92,
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} => {match node.linear {false => 94, true => 95}},
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} => {match node.linear {false => 96, true => 97}},
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} => {match node.linear {false => 98, true => 99}},
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => {match node.linear {false => 100, true => 101}},
+ };
+
+ // This is a bit of an ugly way to do this, but avoids code duplication.
+ let mut resolve_input = |index: usize, src_task: Option<RenderTaskId>| -> (RenderTaskAddress, [f32; 4]) {
+ let mut src_task_id = RenderTaskId::INVALID;
+ let mut resolved_scale_and_offset: [f32; 4] = [0.0; 4];
+ if let Some(input) = node.inputs.get(index) {
+ src_task_id = src_task.unwrap();
+ let src_task = &render_tasks[src_task_id];
+
+ textures.input.colors[index] = src_task.get_texture_source();
+ let src_task_size = src_task.location.size();
+ let src_scale_x = (src_task_size.width as f32 - input.inflate as f32 * 2.0) / input.subregion.width();
+ let src_scale_y = (src_task_size.height as f32 - input.inflate as f32 * 2.0) / input.subregion.height();
+ let scale_x = src_scale_x * node.subregion.width();
+ let scale_y = src_scale_y * node.subregion.height();
+ let offset_x = src_scale_x * (node.subregion.min.x - input.subregion.min.x) + input.inflate as f32;
+ let offset_y = src_scale_y * (node.subregion.min.y - input.subregion.min.y) + input.inflate as f32;
+ resolved_scale_and_offset = [
+ scale_x,
+ scale_y,
+ offset_x,
+ offset_y];
+ }
+ let address: RenderTaskAddress = src_task_id.into();
+ (address, resolved_scale_and_offset)
+ };
+ (instance.input_1_task_address, instance.input_1_content_scale_and_offset) = resolve_input(0, input_1_task);
+ (instance.input_2_task_address, instance.input_2_content_scale_and_offset) = resolve_input(1, input_2_task);
+
+ // Additional instance modifications for certain filters
+ match op {
+ FilterGraphOp::SVGFEOpacity { valuebinding: _, value } => {
+ // opacity only has one input so we can use the other
+ // components to store the opacity value
+ instance.input_2_content_scale_and_offset = [*value, 0.0, 0.0, 0.0];
+ },
+ FilterGraphOp::SVGFEMorphologyDilate { radius_x, radius_y } |
+ FilterGraphOp::SVGFEMorphologyErode { radius_x, radius_y } => {
+ // morphology filters only use one input, so we use the
+ // second offset coord to store the radius values.
+ instance.input_2_content_scale_and_offset = [*radius_x, *radius_y, 0.0, 0.0];
+ },
+ FilterGraphOp::SVGFEFlood { color } => {
+ // flood filters don't use inputs, so we store color here.
+ // We can't do the same trick on DropShadow because it does have two
+ // inputs.
+ instance.input_2_content_scale_and_offset = [color.r, color.g, color.b, color.a];
+ },
+ _ => {},
+ }
+
+ for (ref mut batch_textures, ref mut batch) in instances.iter_mut() {
+ if let Some(combined_textures) = batch_textures.combine_textures(textures) {
+ batch.push(instance);
+ // Update the batch textures to the newly combined batch textures
+ *batch_textures = combined_textures;
+ // is this really the intended behavior?
+ return;
+ }
+ }
+
+ instances.push((textures, vec![instance]));
+}
+
// Information required to do a blit from a source to a target.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs
index bf9050712c..5106971591 100644
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -3,19 +3,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{CompositeOperator, FilterPrimitive, FilterPrimitiveInput, FilterPrimitiveKind};
-use api::{LineStyle, LineOrientation, ClipMode, MixBlendMode, ColorF, ColorSpace};
+use api::{LineStyle, LineOrientation, ClipMode, MixBlendMode, ColorF, ColorSpace, FilterOpGraphPictureBufferId};
use api::MAX_RENDER_TASK_SIZE;
use api::units::*;
+use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::clip::{ClipDataStore, ClipItemKind, ClipStore, ClipNodeRange};
use crate::command_buffer::{CommandBufferIndex, QuadFlags};
use crate::pattern::{PatternKind, PatternShaderInput};
use crate::spatial_tree::SpatialNodeIndex;
use crate::filterdata::SFilterData;
-use crate::frame_builder::FrameBuilderConfig;
+use crate::frame_builder::{FrameBuilderConfig, FrameBuildingState};
use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
use crate::gpu_types::{BorderInstance, ImageSource, UvRectKind, TransformPaletteId};
-use crate::internal_types::{CacheTextureId, FastHashMap, TextureSource, Swizzle};
-use crate::picture::ResolvedSurfaceTexture;
+use crate::internal_types::{CacheTextureId, FastHashMap, FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, SVGFE_CONVOLVE_VALUES_LIMIT, TextureSource, Swizzle};
+use crate::picture::{ResolvedSurfaceTexture, MAX_SURFACE_SIZE};
use crate::prim_store::ClipData;
use crate::prim_store::gradient::{
FastLinearGradientTask, RadialGradientTask,
@@ -24,6 +25,7 @@ use crate::prim_store::gradient::{
use crate::resource_cache::{ResourceCache, ImageRequest};
use std::{usize, f32, i32, u32};
use crate::renderer::{GpuBufferAddress, GpuBufferBuilderF};
+use crate::render_backend::DataStores;
use crate::render_target::{ResolveOp, RenderTargetKind};
use crate::render_task_graph::{PassId, RenderTaskId, RenderTaskGraphBuilder};
use crate::render_task_cache::{RenderTaskCacheEntryHandle, RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskParent};
@@ -190,7 +192,6 @@ pub struct PrimTask {
pub device_pixel_scale: DevicePixelScale,
pub content_origin: DevicePoint,
pub prim_address_f: GpuBufferAddress,
- pub prim_spatial_node_index: SpatialNodeIndex,
pub raster_spatial_node_index: SpatialNodeIndex,
pub transform_id: TransformPaletteId,
pub edge_flags: EdgeAaSegmentMask,
@@ -335,6 +336,16 @@ pub struct SvgFilterTask {
pub extra_gpu_cache_handle: Option<GpuCacheHandle>,
}
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SVGFEFilterTask {
+ pub node: FilterGraphNode,
+ pub op: FilterGraphOp,
+ pub content_origin: DevicePoint,
+ pub extra_gpu_cache_handle: Option<GpuCacheHandle>,
+}
+
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ReadbackTask {
@@ -372,6 +383,7 @@ pub enum RenderTaskKind {
RadialGradient(RadialGradientTask),
ConicGradient(ConicGradientTask),
SvgFilter(SvgFilterTask),
+ SVGFENode(SVGFEFilterTask),
TileComposite(TileCompositeTask),
Prim(PrimTask),
Empty(EmptyTask),
@@ -423,6 +435,7 @@ impl RenderTaskKind {
RenderTaskKind::RadialGradient(..) => "RadialGradient",
RenderTaskKind::ConicGradient(..) => "ConicGradient",
RenderTaskKind::SvgFilter(..) => "SvgFilter",
+ RenderTaskKind::SVGFENode(..) => "SVGFENode",
RenderTaskKind::TileComposite(..) => "TileComposite",
RenderTaskKind::Prim(..) => "Prim",
RenderTaskKind::Empty(..) => "Empty",
@@ -448,6 +461,9 @@ impl RenderTaskKind {
RenderTaskKind::SvgFilter(..) => {
RenderTargetKind::Color
}
+ RenderTaskKind::SVGFENode(..) => {
+ RenderTargetKind::Color
+ }
RenderTaskKind::ClipRegion(..) |
RenderTaskKind::CacheMask(..) |
@@ -521,7 +537,6 @@ impl RenderTaskKind {
pub fn new_prim(
pattern: PatternKind,
pattern_input: PatternShaderInput,
- prim_spatial_node_index: SpatialNodeIndex,
raster_spatial_node_index: SpatialNodeIndex,
device_pixel_scale: DevicePixelScale,
content_origin: DevicePoint,
@@ -535,7 +550,6 @@ impl RenderTaskKind {
RenderTaskKind::Prim(PrimTask {
pattern,
pattern_input,
- prim_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
content_origin,
@@ -791,6 +805,11 @@ impl RenderTaskKind {
_ => [0.0; 4]
}
}
+ RenderTaskKind::SVGFENode(_task) => {
+ // we don't currently use this for SVGFE filters.
+ // see SVGFEFilterInstance instead
+ [0.0; 4]
+ }
#[cfg(test)]
RenderTaskKind::Test(..) => {
@@ -816,39 +835,138 @@ impl RenderTaskKind {
&mut self,
gpu_cache: &mut GpuCache,
) {
- if let RenderTaskKind::SvgFilter(ref mut filter_task) = self {
- match filter_task.info {
- SvgFilterInfo::ColorMatrix(ref matrix) => {
- let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
- if let Some(mut request) = gpu_cache.request(handle) {
- for i in 0..5 {
- request.push([matrix[i*4], matrix[i*4+1], matrix[i*4+2], matrix[i*4+3]]);
+ match self {
+ RenderTaskKind::SvgFilter(ref mut filter_task) => {
+ match filter_task.info {
+ SvgFilterInfo::ColorMatrix(ref matrix) => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ for i in 0..5 {
+ request.push([matrix[i*4], matrix[i*4+1], matrix[i*4+2], matrix[i*4+3]]);
+ }
}
}
- }
- SvgFilterInfo::DropShadow(color) |
- SvgFilterInfo::Flood(color) => {
- let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
- if let Some(mut request) = gpu_cache.request(handle) {
- request.push(color.to_array());
+ SvgFilterInfo::DropShadow(color) |
+ SvgFilterInfo::Flood(color) => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ request.push(color.to_array());
+ }
}
- }
- SvgFilterInfo::ComponentTransfer(ref data) => {
- let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
- if let Some(request) = gpu_cache.request(handle) {
- data.update(request);
+ SvgFilterInfo::ComponentTransfer(ref data) => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(request) = gpu_cache.request(handle) {
+ data.update(request);
+ }
}
+ SvgFilterInfo::Composite(ref operator) => {
+ if let CompositeOperator::Arithmetic(k_vals) = operator {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ request.push(*k_vals);
+ }
+ }
+ }
+ _ => {},
}
- SvgFilterInfo::Composite(ref operator) => {
- if let CompositeOperator::Arithmetic(k_vals) = operator {
+ }
+ RenderTaskKind::SVGFENode(ref mut filter_task) => {
+ match filter_task.op {
+ FilterGraphOp::SVGFEBlendDarken => {}
+ FilterGraphOp::SVGFEBlendLighten => {}
+ FilterGraphOp::SVGFEBlendMultiply => {}
+ FilterGraphOp::SVGFEBlendNormal => {}
+ FilterGraphOp::SVGFEBlendScreen => {}
+ FilterGraphOp::SVGFEBlendOverlay => {}
+ FilterGraphOp::SVGFEBlendColorDodge => {}
+ FilterGraphOp::SVGFEBlendColorBurn => {}
+ FilterGraphOp::SVGFEBlendHardLight => {}
+ FilterGraphOp::SVGFEBlendSoftLight => {}
+ FilterGraphOp::SVGFEBlendDifference => {}
+ FilterGraphOp::SVGFEBlendExclusion => {}
+ FilterGraphOp::SVGFEBlendHue => {}
+ FilterGraphOp::SVGFEBlendSaturation => {}
+ FilterGraphOp::SVGFEBlendColor => {}
+ FilterGraphOp::SVGFEBlendLuminosity => {}
+ FilterGraphOp::SVGFEColorMatrix{values: matrix} => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ for i in 0..5 {
+ request.push([matrix[i*4], matrix[i*4+1], matrix[i*4+2], matrix[i*4+3]]);
+ }
+ }
+ }
+ FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
+ FilterGraphOp::SVGFEComponentTransferInterned{..} => {}
+ FilterGraphOp::SVGFECompositeArithmetic{k1, k2, k3, k4} => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ request.push([k1, k2, k3, k4]);
+ }
+ }
+ FilterGraphOp::SVGFECompositeATop => {}
+ FilterGraphOp::SVGFECompositeIn => {}
+ FilterGraphOp::SVGFECompositeLighter => {}
+ FilterGraphOp::SVGFECompositeOut => {}
+ FilterGraphOp::SVGFECompositeOver => {}
+ FilterGraphOp::SVGFECompositeXOR => {}
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha} |
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha} |
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{order_x, order_y, kernel, divisor, bias, target_x, target_y, kernel_unit_length_x, kernel_unit_length_y, preserve_alpha} => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ request.push([-target_x as f32, -target_y as f32, order_x as f32, order_y as f32]);
+ request.push([kernel_unit_length_x as f32, kernel_unit_length_y as f32, 1.0 / divisor, bias]);
+ assert!(SVGFE_CONVOLVE_VALUES_LIMIT == 25);
+ request.push([kernel[0], kernel[1], kernel[2], kernel[3]]);
+ request.push([kernel[4], kernel[5], kernel[6], kernel[7]]);
+ request.push([kernel[8], kernel[9], kernel[10], kernel[11]]);
+ request.push([kernel[12], kernel[13], kernel[14], kernel[15]]);
+ request.push([kernel[16], kernel[17], kernel[18], kernel[19]]);
+ request.push([kernel[20], 0.0, 0.0, preserve_alpha as f32]);
+ }
+ }
+ FilterGraphOp::SVGFEDiffuseLightingDistant{..} => {}
+ FilterGraphOp::SVGFEDiffuseLightingPoint{..} => {}
+ FilterGraphOp::SVGFEDiffuseLightingSpot{..} => {}
+ FilterGraphOp::SVGFEDisplacementMap{scale, x_channel_selector, y_channel_selector} => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ request.push([x_channel_selector as f32, y_channel_selector as f32, scale, 0.0]);
+ }
+ }
+ FilterGraphOp::SVGFEDropShadow{color, ..} |
+ FilterGraphOp::SVGFEFlood{color} => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ request.push(color.to_array());
+ }
+ }
+ FilterGraphOp::SVGFEGaussianBlur{..} => {}
+ FilterGraphOp::SVGFEIdentity => {}
+ FilterGraphOp::SVGFEImage{..} => {}
+ FilterGraphOp::SVGFEMorphologyDilate{radius_x, radius_y} |
+ FilterGraphOp::SVGFEMorphologyErode{radius_x, radius_y} => {
let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
if let Some(mut request) = gpu_cache.request(handle) {
- request.push(*k_vals);
+ request.push([radius_x, radius_y, 0.0, 0.0]);
}
}
+ FilterGraphOp::SVGFEOpacity{..} => {}
+ FilterGraphOp::SVGFESourceAlpha => {}
+ FilterGraphOp::SVGFESourceGraphic => {}
+ FilterGraphOp::SVGFESpecularLightingDistant{..} => {}
+ FilterGraphOp::SVGFESpecularLightingPoint{..} => {}
+ FilterGraphOp::SVGFESpecularLightingSpot{..} => {}
+ FilterGraphOp::SVGFETile => {}
+ FilterGraphOp::SVGFEToAlpha{..} => {}
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} => {}
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} => {}
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} => {}
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => {}
}
- _ => {},
}
+ _ => {}
}
}
}
@@ -1510,6 +1628,1115 @@ impl RenderTask {
self.sub_pass = Some(sub_pass);
}
+ /// Creates render tasks from PictureCompositeMode::SVGFEGraph.
+ ///
+ /// The interesting parts of the handling of SVG filters are:
+ /// * scene_building.rs : wrap_prim_with_filters
+ /// * picture.rs : get_coverage_svgfe
+ /// * render_task.rs : new_svg_filter_graph (you are here)
+ /// * render_target.rs : add_svg_filter_node_instances
+ pub fn new_svg_filter_graph(
+ filter_nodes: &[(FilterGraphNode, FilterGraphOp)],
+ frame_state: &mut FrameBuildingState,
+ data_stores: &mut DataStores,
+ uv_rect_kind: UvRectKind,
+ original_task_id: RenderTaskId,
+ _surface_rects_task_size: DeviceIntSize,
+ surface_rects_clipped: DeviceRect,
+ surface_rects_clipped_local: PictureRect,
+ ) -> RenderTaskId {
+ const BUFFER_LIMIT: usize = 256;
+ 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
+ // can just return the original picture
+ let mut output_task_id = original_task_id;
+
+ // By this point we assume the following about the graph:
+ // * BUFFER_LIMIT here should be >= BUFFER_LIMIT in the scene_building.rs code.
+ // * input buffer id < output buffer id
+ // * output buffer id between 0 and BUFFER_LIMIT
+ // * the number of filter_datas matches the number of kept nodes with op
+ // SVGFEComponentTransfer.
+ //
+ // 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
+ // internally as we use subregions, see calculate_uv_rect_kind for how
+ // 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,
+ ),
+ );
+ let scale_x = 1.0 / clipped.width();
+ let scale_y = 1.0 / clipped.height();
+ UvRectKind::Quad{
+ top_left: DeviceHomogeneousVector::new(
+ (unclipped.min.x - clipped.min.x) * scale_x,
+ (unclipped.min.y - clipped.min.y) * scale_y,
+ 0.0, 1.0),
+ top_right: DeviceHomogeneousVector::new(
+ (unclipped.max.x - clipped.min.x) * scale_x,
+ (unclipped.min.y - clipped.min.y) * scale_y,
+ 0.0, 1.0),
+ bottom_left: DeviceHomogeneousVector::new(
+ (unclipped.min.x - clipped.min.x) * scale_x,
+ (unclipped.max.y - clipped.min.y) * scale_y,
+ 0.0, 1.0),
+ bottom_right: DeviceHomogeneousVector::new(
+ (unclipped.max.x - clipped.min.x) * scale_x,
+ (unclipped.max.y - clipped.min.y) * scale_y,
+ 0.0, 1.0),
+ }
+ }
+
+ // Determine the local space to device pixel scaling in the most robust
+ // way, this accounts for local to device transform and
+ // device_pixel_scale (if the task is shrunk in get_surface_rects).
+ //
+ // This has some precision issues because surface_rects_clipped was
+ // rounded already, so it's not exactly the same transform that
+ // get_surface_rects performed, but it is very close, since it is not
+ // quite the same we have to round the offset a certain way to avoid
+ // introducing subpixel offsets caused by the slight deviation.
+ let subregion_to_device_scale_x = surface_rects_clipped.width() / surface_rects_clipped_local.width();
+ let subregion_to_device_scale_y = surface_rects_clipped.height() / surface_rects_clipped_local.height();
+ 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() {
+ let node = &filter_node;
+ let is_output = filter_index == filter_nodes.len() - 1;
+
+ // Note that this is never set on the final output by design.
+ if !node.kept_by_optimizer {
+ continue;
+ }
+
+ // Certain ops have parameters that need to be scaled to device
+ // space.
+ let op = match op {
+ FilterGraphOp::SVGFEBlendColor => op.clone(),
+ FilterGraphOp::SVGFEBlendColorBurn => op.clone(),
+ FilterGraphOp::SVGFEBlendColorDodge => op.clone(),
+ FilterGraphOp::SVGFEBlendDarken => op.clone(),
+ FilterGraphOp::SVGFEBlendDifference => op.clone(),
+ FilterGraphOp::SVGFEBlendExclusion => op.clone(),
+ FilterGraphOp::SVGFEBlendHardLight => op.clone(),
+ FilterGraphOp::SVGFEBlendHue => op.clone(),
+ FilterGraphOp::SVGFEBlendLighten => op.clone(),
+ FilterGraphOp::SVGFEBlendLuminosity => op.clone(),
+ FilterGraphOp::SVGFEBlendMultiply => op.clone(),
+ FilterGraphOp::SVGFEBlendNormal => op.clone(),
+ FilterGraphOp::SVGFEBlendOverlay => op.clone(),
+ FilterGraphOp::SVGFEBlendSaturation => op.clone(),
+ FilterGraphOp::SVGFEBlendScreen => op.clone(),
+ FilterGraphOp::SVGFEBlendSoftLight => op.clone(),
+ FilterGraphOp::SVGFEColorMatrix{..} => op.clone(),
+ FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
+ FilterGraphOp::SVGFEComponentTransferInterned{..} => op.clone(),
+ FilterGraphOp::SVGFECompositeArithmetic{..} => op.clone(),
+ FilterGraphOp::SVGFECompositeATop => op.clone(),
+ FilterGraphOp::SVGFECompositeIn => op.clone(),
+ FilterGraphOp::SVGFECompositeLighter => op.clone(),
+ FilterGraphOp::SVGFECompositeOut => op.clone(),
+ FilterGraphOp::SVGFECompositeOver => op.clone(),
+ FilterGraphOp::SVGFECompositeXOR => op.clone(),
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{
+ kernel_unit_length_x, kernel_unit_length_y, order_x,
+ order_y, kernel, divisor, bias, target_x, target_y,
+ preserve_alpha} => {
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{
+ kernel_unit_length_x:
+ (kernel_unit_length_x * subregion_to_device_scale_x).round(),
+ kernel_unit_length_y:
+ (kernel_unit_length_y * subregion_to_device_scale_y).round(),
+ order_x: *order_x, order_y: *order_y, kernel: *kernel,
+ divisor: *divisor, bias: *bias, target_x: *target_x,
+ target_y: *target_y, preserve_alpha: *preserve_alpha}
+ },
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{
+ kernel_unit_length_x, kernel_unit_length_y, order_x,
+ order_y, kernel, divisor, bias, target_x, target_y,
+ preserve_alpha} => {
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{
+ kernel_unit_length_x:
+ (kernel_unit_length_x * subregion_to_device_scale_x).round(),
+ kernel_unit_length_y:
+ (kernel_unit_length_y * subregion_to_device_scale_y).round(),
+ order_x: *order_x, order_y: *order_y, kernel: *kernel,
+ divisor: *divisor, bias: *bias, target_x: *target_x,
+ target_y: *target_y, preserve_alpha: *preserve_alpha}
+ },
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{
+ kernel_unit_length_x, kernel_unit_length_y, order_x,
+ order_y, kernel, divisor, bias, target_x, target_y,
+ preserve_alpha} => {
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{
+ kernel_unit_length_x:
+ (kernel_unit_length_x * subregion_to_device_scale_x).round(),
+ kernel_unit_length_y:
+ (kernel_unit_length_y * subregion_to_device_scale_y).round(),
+ order_x: *order_x, order_y: *order_y, kernel: *kernel,
+ divisor: *divisor, bias: *bias, target_x: *target_x,
+ target_y: *target_y, preserve_alpha: *preserve_alpha}
+ },
+ FilterGraphOp::SVGFEDiffuseLightingDistant{
+ surface_scale, diffuse_constant, kernel_unit_length_x,
+ kernel_unit_length_y, azimuth, elevation} => {
+ FilterGraphOp::SVGFEDiffuseLightingDistant{
+ surface_scale: *surface_scale,
+ diffuse_constant: *diffuse_constant,
+ kernel_unit_length_x:
+ (kernel_unit_length_x * subregion_to_device_scale_x).round(),
+ kernel_unit_length_y:
+ (kernel_unit_length_y * subregion_to_device_scale_y).round(),
+ azimuth: *azimuth, elevation: *elevation}
+ },
+ FilterGraphOp::SVGFEDiffuseLightingPoint{
+ surface_scale, diffuse_constant, kernel_unit_length_x,
+ kernel_unit_length_y, x, y, z} => {
+ FilterGraphOp::SVGFEDiffuseLightingPoint{
+ surface_scale: *surface_scale,
+ diffuse_constant: *diffuse_constant,
+ kernel_unit_length_x:
+ (kernel_unit_length_x * subregion_to_device_scale_x).round(),
+ kernel_unit_length_y:
+ (kernel_unit_length_y * subregion_to_device_scale_y).round(),
+ x: x * subregion_to_device_scale_x + subregion_to_device_offset_x,
+ y: y * subregion_to_device_scale_y + subregion_to_device_offset_y,
+ z: *z}
+ },
+ FilterGraphOp::SVGFEDiffuseLightingSpot{
+ surface_scale, diffuse_constant, kernel_unit_length_x,
+ kernel_unit_length_y, x, y, z, points_at_x, points_at_y,
+ points_at_z, cone_exponent, limiting_cone_angle} => {
+ FilterGraphOp::SVGFEDiffuseLightingSpot{
+ surface_scale: *surface_scale,
+ diffuse_constant: *diffuse_constant,
+ kernel_unit_length_x:
+ (kernel_unit_length_x * subregion_to_device_scale_x).round(),
+ kernel_unit_length_y:
+ (kernel_unit_length_y * subregion_to_device_scale_y).round(),
+ x: x * subregion_to_device_scale_x + subregion_to_device_offset_x,
+ y: y * subregion_to_device_scale_y + subregion_to_device_offset_y,
+ z: *z,
+ points_at_x: points_at_x * subregion_to_device_scale_x + subregion_to_device_offset_x,
+ points_at_y: points_at_y * subregion_to_device_scale_y + subregion_to_device_offset_y,
+ points_at_z: *points_at_z,
+ cone_exponent: *cone_exponent,
+ limiting_cone_angle: *limiting_cone_angle}
+ },
+ FilterGraphOp::SVGFEFlood{..} => op.clone(),
+ FilterGraphOp::SVGFEDisplacementMap{
+ scale, x_channel_selector, y_channel_selector} => {
+ FilterGraphOp::SVGFEDisplacementMap{
+ scale: scale * subregion_to_device_scale_x,
+ x_channel_selector: *x_channel_selector,
+ y_channel_selector: *y_channel_selector}
+ },
+ FilterGraphOp::SVGFEDropShadow{
+ color, dx, dy, std_deviation_x, std_deviation_y} => {
+ FilterGraphOp::SVGFEDropShadow{
+ color: *color,
+ dx: dx * subregion_to_device_scale_x,
+ dy: dy * subregion_to_device_scale_y,
+ std_deviation_x: std_deviation_x * subregion_to_device_scale_x,
+ std_deviation_y: std_deviation_y * subregion_to_device_scale_y}
+ },
+ FilterGraphOp::SVGFEGaussianBlur{std_deviation_x, std_deviation_y} => {
+ let std_deviation_x = std_deviation_x * subregion_to_device_scale_x;
+ let std_deviation_y = std_deviation_y * subregion_to_device_scale_y;
+ // For blurs that effectively have no radius in display
+ // space, we can convert to identity.
+ if std_deviation_x + std_deviation_y >= 0.125 {
+ FilterGraphOp::SVGFEGaussianBlur{
+ std_deviation_x,
+ std_deviation_y}
+ } else {
+ FilterGraphOp::SVGFEIdentity
+ }
+ },
+ FilterGraphOp::SVGFEIdentity => op.clone(),
+ FilterGraphOp::SVGFEImage{..} => op.clone(),
+ FilterGraphOp::SVGFEMorphologyDilate{radius_x, radius_y} => {
+ FilterGraphOp::SVGFEMorphologyDilate{
+ radius_x: (radius_x * subregion_to_device_scale_x).round(),
+ radius_y: (radius_y * subregion_to_device_scale_y).round()}
+ },
+ FilterGraphOp::SVGFEMorphologyErode{radius_x, radius_y} => {
+ FilterGraphOp::SVGFEMorphologyErode{
+ radius_x: (radius_x * subregion_to_device_scale_x).round(),
+ radius_y: (radius_y * subregion_to_device_scale_y).round()}
+ },
+ FilterGraphOp::SVGFEOpacity{..} => op.clone(),
+ FilterGraphOp::SVGFESourceAlpha => op.clone(),
+ FilterGraphOp::SVGFESourceGraphic => op.clone(),
+ FilterGraphOp::SVGFESpecularLightingDistant{
+ surface_scale, specular_constant, specular_exponent,
+ kernel_unit_length_x, kernel_unit_length_y, azimuth,
+ elevation} => {
+ FilterGraphOp::SVGFESpecularLightingDistant{
+ surface_scale: *surface_scale,
+ specular_constant: *specular_constant,
+ specular_exponent: *specular_exponent,
+ kernel_unit_length_x:
+ (kernel_unit_length_x * subregion_to_device_scale_x).round(),
+ kernel_unit_length_y:
+ (kernel_unit_length_y * subregion_to_device_scale_y).round(),
+ azimuth: *azimuth, elevation: *elevation}
+ },
+ FilterGraphOp::SVGFESpecularLightingPoint{
+ surface_scale, specular_constant, specular_exponent,
+ kernel_unit_length_x, kernel_unit_length_y, x, y, z } => {
+ FilterGraphOp::SVGFESpecularLightingPoint{
+ surface_scale: *surface_scale,
+ specular_constant: *specular_constant,
+ specular_exponent: *specular_exponent,
+ kernel_unit_length_x:
+ (kernel_unit_length_x * subregion_to_device_scale_x).round(),
+ kernel_unit_length_y:
+ (kernel_unit_length_y * subregion_to_device_scale_y).round(),
+ x: x * subregion_to_device_scale_x + subregion_to_device_offset_x,
+ y: y * subregion_to_device_scale_y + subregion_to_device_offset_y,
+ z: *z }
+ },
+ FilterGraphOp::SVGFESpecularLightingSpot{
+ surface_scale, specular_constant, specular_exponent,
+ kernel_unit_length_x, kernel_unit_length_y, x, y, z,
+ points_at_x, points_at_y, points_at_z, cone_exponent,
+ limiting_cone_angle} => {
+ FilterGraphOp::SVGFESpecularLightingSpot{
+ surface_scale: *surface_scale,
+ specular_constant: *specular_constant,
+ specular_exponent: *specular_exponent,
+ kernel_unit_length_x:
+ (kernel_unit_length_x * subregion_to_device_scale_x).round(),
+ kernel_unit_length_y:
+ (kernel_unit_length_y * subregion_to_device_scale_y).round(),
+ x: x * subregion_to_device_scale_x + subregion_to_device_offset_x,
+ y: y * subregion_to_device_scale_y + subregion_to_device_offset_y,
+ z: *z,
+ points_at_x: points_at_x * subregion_to_device_scale_x + subregion_to_device_offset_x,
+ points_at_y: points_at_y * subregion_to_device_scale_y + subregion_to_device_offset_y,
+ points_at_z: *points_at_z,
+ cone_exponent: *cone_exponent,
+ limiting_cone_angle: *limiting_cone_angle}
+ },
+ FilterGraphOp::SVGFETile => op.clone(),
+ FilterGraphOp::SVGFEToAlpha => op.clone(),
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{
+ base_frequency_x, base_frequency_y, num_octaves, seed} => {
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{
+ base_frequency_x:
+ base_frequency_x * subregion_to_device_scale_x,
+ base_frequency_y:
+ base_frequency_y * subregion_to_device_scale_y,
+ num_octaves: *num_octaves, seed: *seed}
+ },
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{
+ base_frequency_x, base_frequency_y, num_octaves, seed} => {
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{
+ base_frequency_x:
+ base_frequency_x * subregion_to_device_scale_x,
+ base_frequency_y:
+ base_frequency_y * subregion_to_device_scale_y,
+ num_octaves: *num_octaves, seed: *seed}
+ },
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{
+ base_frequency_x, base_frequency_y, num_octaves, seed} => {
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{
+ base_frequency_x:
+ base_frequency_x * subregion_to_device_scale_x,
+ base_frequency_y:
+ base_frequency_y * subregion_to_device_scale_y,
+ num_octaves: *num_octaves, seed: *seed}
+ },
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{
+ base_frequency_x, base_frequency_y, num_octaves, seed} => {
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{
+ base_frequency_x:
+ base_frequency_x * subregion_to_device_scale_x,
+ base_frequency_y:
+ base_frequency_y * subregion_to_device_scale_y,
+ num_octaves: *num_octaves, seed: *seed}
+ },
+ };
+
+ // Process the inputs and figure out their new subregion, because
+ // the SourceGraphic subregion is smaller than it was in scene build
+ // now that it reflects the invalidation rect
+ //
+ // Also look up the child tasks while we are here.
+ let mut used_subregion = LayoutRect::zero();
+ let node_inputs: Vec<(FilterGraphPictureReference, RenderTaskId)> = node.inputs.iter().map(|input| {
+ let (subregion, task) =
+ match input.buffer_id {
+ FilterOpGraphPictureBufferId::BufferId(id) => {
+ (subregion_by_buffer_id[id as usize], task_by_buffer_id[id as usize])
+ }
+ FilterOpGraphPictureBufferId::None => {
+ // Task must resolve so we use the SourceGraphic as
+ // a placeholder for these, they don't actually
+ // contribute anything to the output
+ (LayoutRect::zero(), original_task_id)
+ }
+ };
+ // Convert offset to device coordinates.
+ let offset = LayoutVector2D::new(
+ (input.offset.x * subregion_to_device_scale_x).round(),
+ (input.offset.y * subregion_to_device_scale_y).round(),
+ );
+ // To figure out the portion of the node subregion used by this
+ // source image we need to apply the target padding. Note that
+ // this does not affect the subregion of the input, as that
+ // can't be modified as it is used for placement (offset).
+ let target_padding = input.target_padding
+ .scale(subregion_to_device_scale_x, subregion_to_device_scale_y)
+ .round();
+ let target_subregion =
+ LayoutRect::new(
+ LayoutPoint::new(
+ subregion.min.x + target_padding.min.x,
+ subregion.min.y + target_padding.min.y,
+ ),
+ LayoutPoint::new(
+ subregion.max.x + target_padding.max.x,
+ subregion.max.y + target_padding.max.y,
+ ),
+ );
+ used_subregion = used_subregion.union(&target_subregion);
+ (FilterGraphPictureReference{
+ buffer_id: input.buffer_id,
+ // Apply offset to the placement of the input subregion.
+ subregion: subregion.translate(offset),
+ offset: LayoutVector2D::zero(),
+ inflate: input.inflate,
+ // Nothing past this point uses the padding.
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ }, task)
+ }).collect();
+
+ // Convert subregion from PicturePixels to DevicePixels and round.
+ let full_subregion = node.subregion
+ .scale(subregion_to_device_scale_x, subregion_to_device_scale_y)
+ .translate(LayoutVector2D::new(subregion_to_device_offset_x, subregion_to_device_offset_y))
+ .round();
+
+ // Clip the used subregion we calculated from the inputs to fit
+ // within the node's specified subregion.
+ used_subregion = used_subregion
+ .intersection(&full_subregion)
+ .unwrap_or(LayoutRect::zero())
+ .round();
+
+ // Certain filters need to override the used_subregion directly.
+ 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[15] != 1.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 } => {
+ // Optimize certain cases of Arithmetic operator
+ //
+ // See logic for SVG_FECOMPOSITE_OPERATOR_ARITHMETIC
+ // in FilterSupport.cpp for more information.
+ //
+ // Any other case uses the union of input subregions
+ 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 {
+ // Can produce pixels where both exist.
+ used_subregion = full_subregion
+ .intersection(&node_inputs[0].0.subregion)
+ .unwrap_or(LayoutRect::zero())
+ .intersection(&node_inputs[1].0.subregion)
+ .unwrap_or(LayoutRect::zero());
+ }
+ else if k2 != 0.0 && k3 == 0.0 && k4 == 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 {
+ // Can produce pixels where background exists.
+ used_subregion = full_subregion
+ .intersection(&node_inputs[1].0.subregion)
+ .unwrap_or(LayoutRect::zero());
+ }
+ },
+ FilterGraphOp::SVGFECompositeATop => {
+ // Can only produce pixels where background exists.
+ used_subregion = full_subregion
+ .intersection(&node_inputs[1].0.subregion)
+ .unwrap_or(LayoutRect::zero());
+ },
+ FilterGraphOp::SVGFECompositeIn => {
+ // Can only produce pixels where both exist.
+ used_subregion = used_subregion
+ .intersection(&node_inputs[0].0.subregion)
+ .unwrap_or(LayoutRect::zero())
+ .intersection(&node_inputs[1].0.subregion)
+ .unwrap_or(LayoutRect::zero());
+ },
+ FilterGraphOp::SVGFECompositeLighter => {},
+ FilterGraphOp::SVGFECompositeOut => {
+ // Can only produce pixels where source exists.
+ used_subregion = full_subregion
+ .intersection(&node_inputs[0].0.subregion)
+ .unwrap_or(LayoutRect::zero());
+ },
+ 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),
+ // we know at this point that it has no inputs, so the
+ // used_region is empty unless we set it here.
+ if color.a > 0.0 {
+ used_subregion = full_subregion;
+ }
+ },
+ FilterGraphOp::SVGFEIdentity => {},
+ FilterGraphOp::SVGFEImage { sampling_filter: _sampling_filter, matrix: _matrix } => {
+ // TODO: calculate the actual subregion
+ used_subregion = full_subregion;
+ },
+ FilterGraphOp::SVGFEGaussianBlur{..} => {},
+ 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_subregion;
+ },
+ FilterGraphOp::SVGFESpecularLightingDistant{..} => {},
+ FilterGraphOp::SVGFESpecularLightingPoint{..} => {},
+ FilterGraphOp::SVGFESpecularLightingSpot{..} => {},
+ FilterGraphOp::SVGFETile => {
+ if !used_subregion.is_empty() {
+ // This fills the entire target, at least if there are
+ // any input pixels to work with.
+ 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;
+ },
+ }
+
+ // If this is the output node, we have to match the provided filter
+ // subregion as the primitive it is applied to is already placed (it
+ // was calculated in get_surface_rects using get_coverage_svgfe).
+ let node_subregion = match is_output {
+ true => output_subregion,
+ false => used_subregion,
+ };
+
+ // Convert subregion from layout pixels to integer device pixels and
+ // then calculate size afterwards so it reflects the used pixel area
+ //
+ // In case of the output node we preserve the exact filter_subregion
+ // task size.
+ //
+ // 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 =
+ match is_output {
+ true => output_rect,
+ false => node_subregion.to_i32(),
+ };
+
+ // 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.
+ 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;
+ }
+ // Add the inflate border
+ node_task_size.width += node.inflate as i32 * 2;
+ node_task_size.height += node.inflate as i32 * 2;
+
+ // 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);
+
+ // Create task for this node
+ let task_id;
+ match op {
+ FilterGraphOp::SVGFEGaussianBlur { std_deviation_x, std_deviation_y } => {
+ // Note: wrap_prim_with_filters copies the SourceGraphic to
+ // a node to apply the transparent border around the image,
+ // we rely on that behavior here as the Blur filter is a
+ // different shader without awareness of the subregion
+ // rules in the SVG spec.
+
+ // Find the input task id
+ assert!(node_inputs.len() == 1);
+ let blur_input = &node_inputs[0].0;
+ let source_task_id = node_inputs[0].1;
+
+ // 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,
+ );
+ let blur_subregion = blur_input.subregion
+ .inflate(
+ std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
+ std_deviation_y.ceil() * BLUR_SAMPLE_SCALE);
+ let blur_task_size = blur_subregion.size().cast_unit();
+ // Adjust task size to prevent potential sampling errors
+ let mut adjusted_blur_task_size =
+ BlurTask::adjusted_blur_source_size(
+ blur_task_size,
+ adjusted_blur_std_deviation,
+ );
+ // 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,
+ )
+ .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;
+ }
+ }
+
+ let input_subregion_task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
+ adjusted_blur_task_size,
+ RenderTaskKind::SVGFENode(
+ SVGFEFilterTask{
+ node: FilterGraphNode{
+ kept_by_optimizer: true,
+ linear: false,
+ inflate: 0,
+ inputs: [blur_input.clone()].to_vec(),
+ subregion: blur_subregion,
+ },
+ op: FilterGraphOp::SVGFEIdentity,
+ content_origin: DevicePoint::zero(),
+ extra_gpu_cache_handle: None,
+ }
+ ),
+ ).with_uv_rect_kind(UvRectKind::Rect));
+ // Adding the dependencies sets the inputs for this task
+ frame_state.rg_builder.add_dependency(input_subregion_task_id, source_task_id);
+
+ // TODO: We should do this blur in the correct
+ // colorspace, linear=true is the default in SVG and
+ // new_blur does not currently support it. If the nodes
+ // that consume the result only use the alpha channel, it
+ // does not matter, but when they use the RGB it matters.
+ let blur_task_id =
+ RenderTask::new_blur(
+ adjusted_blur_std_deviation,
+ input_subregion_task_id,
+ frame_state.rg_builder,
+ RenderTargetKind::Color,
+ None,
+ adjusted_blur_task_size,
+ );
+
+ task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
+ node_task_size,
+ RenderTaskKind::SVGFENode(
+ SVGFEFilterTask{
+ node: FilterGraphNode{
+ kept_by_optimizer: true,
+ linear: node.linear,
+ inflate: node.inflate,
+ inputs: [
+ FilterGraphPictureReference{
+ buffer_id: blur_input.buffer_id,
+ subregion: blur_subregion,
+ inflate: 0,
+ offset: LayoutVector2D::zero(),
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ }].to_vec(),
+ subregion: node_subregion,
+ },
+ op: FilterGraphOp::SVGFEIdentity,
+ content_origin: DevicePoint::zero(),
+ extra_gpu_cache_handle: None,
+ }
+ ),
+ ).with_uv_rect_kind(node_uv_rect_kind));
+ // Adding the dependencies sets the inputs for this task
+ frame_state.rg_builder.add_dependency(task_id, blur_task_id);
+ }
+ FilterGraphOp::SVGFEDropShadow { color, dx, dy, std_deviation_x, std_deviation_y } => {
+ // Note: wrap_prim_with_filters copies the SourceGraphic to
+ // a node to apply the transparent border around the image,
+ // we rely on that behavior here as the Blur filter is a
+ // different shader without awareness of the subregion
+ // rules in the SVG spec.
+
+ // Find the input task id
+ assert!(node_inputs.len() == 1);
+ let blur_input = &node_inputs[0].0;
+ let source_task_id = node_inputs[0].1;
+
+ // 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,
+ );
+ let blur_subregion = blur_input.subregion
+ .inflate(
+ std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
+ std_deviation_y.ceil() * BLUR_SAMPLE_SCALE);
+ let blur_task_size = blur_subregion.size().cast_unit();
+ // Adjust task size to prevent potential sampling errors
+ let mut adjusted_blur_task_size =
+ BlurTask::adjusted_blur_source_size(
+ blur_task_size,
+ adjusted_blur_std_deviation,
+ );
+ // 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,
+ )
+ .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;
+ }
+ }
+
+ let input_subregion_task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
+ adjusted_blur_task_size,
+ RenderTaskKind::SVGFENode(
+ SVGFEFilterTask{
+ node: FilterGraphNode{
+ kept_by_optimizer: true,
+ linear: false,
+ inputs: [
+ FilterGraphPictureReference{
+ buffer_id: blur_input.buffer_id,
+ subregion: blur_input.subregion,
+ offset: LayoutVector2D::zero(),
+ inflate: blur_input.inflate,
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ }].to_vec(),
+ subregion: blur_subregion,
+ inflate: 0,
+ },
+ op: FilterGraphOp::SVGFEIdentity,
+ content_origin: DevicePoint::zero(),
+ extra_gpu_cache_handle: None,
+ }
+ ),
+ ).with_uv_rect_kind(UvRectKind::Rect));
+ // Adding the dependencies sets the inputs for this task
+ frame_state.rg_builder.add_dependency(input_subregion_task_id, source_task_id);
+
+ // The shadow compositing only cares about alpha channel
+ // which is always linear, so we can blur this in sRGB or
+ // linear color space and the result is the same as we will
+ // be replacing the rgb completely.
+ let blur_task_id =
+ RenderTask::new_blur(
+ adjusted_blur_std_deviation,
+ input_subregion_task_id,
+ frame_state.rg_builder,
+ RenderTargetKind::Color,
+ None,
+ adjusted_blur_task_size,
+ );
+
+ // 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
+ .translate(LayoutVector2D::new(dx, dy));
+ task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
+ node_task_size,
+ RenderTaskKind::SVGFENode(
+ SVGFEFilterTask{
+ node: FilterGraphNode{
+ kept_by_optimizer: true,
+ linear: node.linear,
+ inflate: node.inflate,
+ inputs: [
+ // Original picture
+ *blur_input,
+ // Shadow picture
+ FilterGraphPictureReference{
+ buffer_id: blur_input.buffer_id,
+ subregion: blur_subregion,
+ inflate: 0,
+ offset: LayoutVector2D::zero(),
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ }].to_vec(),
+ subregion: node_subregion,
+ },
+ op: FilterGraphOp::SVGFEDropShadow{
+ color,
+ // These parameters don't matter here
+ dx: 0.0, dy: 0.0,
+ std_deviation_x: 0.0, std_deviation_y: 0.0,
+ },
+ content_origin: DevicePoint::zero(),
+ extra_gpu_cache_handle: None,
+ }
+ ),
+ ).with_uv_rect_kind(node_uv_rect_kind));
+ // Adding the dependencies sets the inputs for this task
+ frame_state.rg_builder.add_dependency(task_id, source_task_id);
+ frame_state.rg_builder.add_dependency(task_id, blur_task_id);
+ }
+ FilterGraphOp::SVGFESourceAlpha |
+ FilterGraphOp::SVGFESourceGraphic => {
+ // These copy from the original task, we have to synthesize
+ // a fake input binding to make the shader do the copy. In
+ // the case of SourceAlpha the shader will zero the RGB but
+ // we don't have to care about that distinction here.
+ task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
+ node_task_size,
+ RenderTaskKind::SVGFENode(
+ SVGFEFilterTask{
+ node: FilterGraphNode{
+ kept_by_optimizer: true,
+ linear: node.linear,
+ inflate: node.inflate,
+ inputs: [
+ 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,
+ offset: LayoutVector2D::zero(),
+ inflate: 0,
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ }
+ ].to_vec(),
+ subregion: node_subregion,
+ },
+ op: op.clone(),
+ content_origin: DevicePoint::zero(),
+ extra_gpu_cache_handle: None,
+ }
+ ),
+ ).with_uv_rect_kind(node_uv_rect_kind));
+ frame_state.rg_builder.add_dependency(task_id, original_task_id);
+ made_dependency_on_source = true;
+ }
+ FilterGraphOp::SVGFEComponentTransferInterned { handle, creates_pixels: _ } => {
+ // FIXME: Doing this in prepare_interned_prim_for_render
+ // doesn't seem to be enough, where should it be done?
+ let filter_data = &mut data_stores.filter_data[handle];
+ filter_data.update(frame_state);
+ // ComponentTransfer has a gpu_cache_handle that we need to
+ // pass along
+ task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
+ node_task_size,
+ RenderTaskKind::SVGFENode(
+ SVGFEFilterTask{
+ node: FilterGraphNode{
+ kept_by_optimizer: true,
+ linear: node.linear,
+ inputs: node_inputs.iter().map(|input| {input.0}).collect(),
+ subregion: node_subregion,
+ inflate: node.inflate,
+ },
+ op: op.clone(),
+ content_origin: DevicePoint::zero(),
+ extra_gpu_cache_handle: Some(filter_data.gpu_cache_handle),
+ }
+ ),
+ ).with_uv_rect_kind(node_uv_rect_kind));
+
+ // Add the dependencies for inputs of this node, which will
+ // be used by add_svg_filter_node_instances later
+ for (_input, input_task) in &node_inputs {
+ if *input_task == original_task_id {
+ made_dependency_on_source = true;
+ }
+ if *input_task != RenderTaskId::INVALID {
+ frame_state.rg_builder.add_dependency(task_id, *input_task);
+ }
+ }
+ }
+ _ => {
+ // This is the usual case - zero, one or two inputs that
+ // reference earlier node results.
+ task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
+ node_task_size,
+ RenderTaskKind::SVGFENode(
+ SVGFEFilterTask{
+ node: FilterGraphNode{
+ kept_by_optimizer: true,
+ linear: node.linear,
+ inputs: node_inputs.iter().map(|input| {input.0}).collect(),
+ subregion: node_subregion,
+ inflate: node.inflate,
+ },
+ op: op.clone(),
+ content_origin: DevicePoint::zero(),
+ extra_gpu_cache_handle: None,
+ }
+ ),
+ ).with_uv_rect_kind(node_uv_rect_kind));
+
+ // Add the dependencies for inputs of this node, which will
+ // be used by add_svg_filter_node_instances later
+ for (_input, input_task) in &node_inputs {
+ if *input_task == original_task_id {
+ made_dependency_on_source = true;
+ }
+ if *input_task != RenderTaskId::INVALID {
+ frame_state.rg_builder.add_dependency(task_id, *input_task);
+ }
+ }
+ }
+ }
+
+ // We track the tasks we created by output buffer id to make it easy
+ // 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] = node_subregion;
+
+ if is_output {
+ output_task_id = task_id;
+ }
+ }
+
+ // If no tasks referenced the SourceGraphic, we actually have to create
+ // a fake dependency so that it does not leak.
+ if !made_dependency_on_source && output_task_id != original_task_id {
+ frame_state.rg_builder.add_dependency(output_task_id, original_task_id);
+ }
+
+ output_task_id
+ }
+
pub fn uv_rect_kind(&self) -> UvRectKind {
self.uv_rect_kind
}
@@ -1580,6 +2807,16 @@ impl RenderTask {
}
}
+ pub fn get_target_size(&self) -> DeviceIntSize {
+ match self.location {
+ RenderTaskLocation::Dynamic { rect, .. } => rect.size(),
+ RenderTaskLocation::Static { rect, .. } => rect.size(),
+ RenderTaskLocation::Existing { size, .. } => size,
+ RenderTaskLocation::CacheRequest { size } => size,
+ RenderTaskLocation::Unallocated { size } => size,
+ }
+ }
+
pub fn target_kind(&self) -> RenderTargetKind {
self.kind.target_kind()
}
diff --git a/gfx/wr/webrender/src/render_task_cache.rs b/gfx/wr/webrender/src/render_task_cache.rs
index 2c81a9824f..621a9afd92 100644
--- a/gfx/wr/webrender/src/render_task_cache.rs
+++ b/gfx/wr/webrender/src/render_task_cache.rs
@@ -11,7 +11,6 @@ use crate::device::TextureFilter;
use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
use crate::gpu_cache::GpuCache;
use crate::internal_types::FastHashMap;
-use crate::picture::SurfaceIndex;
use crate::prim_store::image::ImageCacheKey;
use crate::prim_store::gradient::{
FastLinearGradientCacheKey, LinearGradientCacheKey, RadialGradientCacheKey,
@@ -36,7 +35,7 @@ const MAX_CACHE_TASK_SIZE: f32 = 4096.0;
/// box-shadow input).
pub enum RenderTaskParent {
/// Parent is a surface
- Surface(SurfaceIndex),
+ Surface,
/// Parent is a render task
RenderTask(RenderTaskId),
}
@@ -288,9 +287,7 @@ impl RenderTaskCache {
// an input source.
if let Some(render_task_id) = cache_entry.render_task_id {
match parent {
- // TODO(gw): Remove surface from here as a follow up patch, as it's now implicit
- // due to using SurfaceBuilder
- RenderTaskParent::Surface(_surface_index) => {
+ RenderTaskParent::Surface => {
// If parent is a surface, use helper fn to add this dependency,
// which correctly takes account of the render task configuration
// of the surface.
diff --git a/gfx/wr/webrender/src/render_task_graph.rs b/gfx/wr/webrender/src/render_task_graph.rs
index 6c02de8b65..4422d17e60 100644
--- a/gfx/wr/webrender/src/render_task_graph.rs
+++ b/gfx/wr/webrender/src/render_task_graph.rs
@@ -591,9 +591,12 @@ impl RenderTaskGraphBuilder {
}
}
- // By now, all surfaces that were borrowed from the render target pool must
- // be returned to the resource cache, or we are leaking intermediate surfaces!
- assert!(self.active_surfaces.is_empty());
+ if !self.active_surfaces.is_empty() {
+ graph.print();
+ // By now, all surfaces that were borrowed from the render target pool must
+ // be returned to the resource cache, or we are leaking intermediate surfaces!
+ assert!(self.active_surfaces.is_empty());
+ }
// Each task is now allocated to a surface and target rect. Write that to the
// GPU blocks and task_data. After this point, the graph is returned and is
@@ -656,29 +659,30 @@ impl RenderTaskGraph {
pub fn print(
&self,
) {
- debug!("-- RenderTaskGraph --");
+ print!("-- RenderTaskGraph --\n");
for (i, task) in self.tasks.iter().enumerate() {
- debug!("Task {} [{}]: render_on={} free_after={} children={:?}",
+ print!("Task {} [{}]: render_on={} free_after={} children={:?} target_size={:?}\n",
i,
task.kind.as_str(),
task.render_on.0,
task.free_after.0,
task.children,
+ task.get_target_size(),
);
}
for (p, pass) in self.passes.iter().enumerate() {
- debug!("Pass {}:", p);
+ print!("Pass {}:\n", p);
for (s, sub_pass) in pass.sub_passes.iter().enumerate() {
- debug!("\tSubPass {}: {:?}",
+ print!("\tSubPass {}: {:?}\n",
s,
sub_pass.surface,
);
for task_id in &sub_pass.task_ids {
- debug!("\t\tTask {:?}", task_id.index);
+ print!("\t\tTask {:?}\n", task_id.index);
}
}
}
diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs
index a70d3eca18..ab3eb956b0 100644
--- a/gfx/wr/webrender/src/renderer/mod.rs
+++ b/gfx/wr/webrender/src/renderer/mod.rs
@@ -69,7 +69,7 @@ use crate::frame_builder::Frame;
use glyph_rasterizer::GlyphFormat;
use crate::gpu_cache::{GpuCacheUpdate, GpuCacheUpdateList};
use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd};
-use crate::gpu_types::{ScalingInstance, SvgFilterInstance, CopyInstance, PrimitiveInstanceData};
+use crate::gpu_types::{ScalingInstance, SvgFilterInstance, SVGFEFilterInstance, CopyInstance, PrimitiveInstanceData};
use crate::gpu_types::{BlurInstance, ClearInstance, CompositeInstance, CompositorTransform};
use crate::internal_types::{TextureSource, TextureCacheCategory, FrameId};
#[cfg(any(feature = "capture", feature = "replay"))]
@@ -193,11 +193,11 @@ const GPU_TAG_CACHE_LINEAR_GRADIENT: GpuProfileTag = GpuProfileTag {
label: "C_LinearGradient",
color: debug_colors::BROWN,
};
-const GPU_TAG_CACHE_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag {
+const GPU_TAG_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag {
label: "C_RadialGradient",
color: debug_colors::BROWN,
};
-const GPU_TAG_CACHE_CONIC_GRADIENT: GpuProfileTag = GpuProfileTag {
+const GPU_TAG_CONIC_GRADIENT: GpuProfileTag = GpuProfileTag {
label: "C_ConicGradient",
color: debug_colors::BROWN,
};
@@ -257,6 +257,10 @@ const GPU_TAG_SVG_FILTER: GpuProfileTag = GpuProfileTag {
label: "SvgFilter",
color: debug_colors::LEMONCHIFFON,
};
+const GPU_TAG_SVG_FILTER_NODES: GpuProfileTag = GpuProfileTag {
+ label: "SvgFilterNodes",
+ color: debug_colors::LEMONCHIFFON,
+};
const GPU_TAG_COMPOSITE: GpuProfileTag = GpuProfileTag {
label: "Composite",
color: debug_colors::TOMATO,
@@ -288,6 +292,8 @@ impl BatchKind {
}
BatchKind::TextRun(_) => GPU_TAG_PRIM_TEXT_RUN,
BatchKind::Quad(PatternKind::ColorOrTexture) => GPU_TAG_PRIMITIVE,
+ BatchKind::Quad(PatternKind::RadialGradient) => GPU_TAG_RADIAL_GRADIENT,
+ BatchKind::Quad(PatternKind::ConicGradient) => GPU_TAG_CONIC_GRADIENT,
BatchKind::Quad(PatternKind::Mask) => GPU_TAG_INDIRECT_MASK,
}
}
@@ -2527,6 +2533,35 @@ impl Renderer {
);
}
+ fn handle_svg_nodes(
+ &mut self,
+ textures: &BatchTextures,
+ svg_filters: &[SVGFEFilterInstance],
+ projection: &default::Transform3D<f32>,
+ stats: &mut RendererStats,
+ ) {
+ if svg_filters.is_empty() {
+ return;
+ }
+
+ let _timer = self.gpu_profiler.start_timer(GPU_TAG_SVG_FILTER_NODES);
+
+ self.shaders.borrow_mut().cs_svg_filter_node.bind(
+ &mut self.device,
+ &projection,
+ None,
+ &mut self.renderer_errors,
+ &mut self.profile,
+ );
+
+ self.draw_instanced_batch(
+ &svg_filters,
+ VertexArrayKind::SvgFilterNode,
+ textures,
+ stats,
+ );
+ }
+
fn handle_resolve(
&mut self,
resolve_op: &ResolveOp,
@@ -3576,6 +3611,10 @@ impl Renderer {
);
}
+ for (ref textures, ref filters) in &target.svg_nodes {
+ self.handle_svg_nodes(textures, filters, projection, stats);
+ }
+
for alpha_batch_container in &target.alpha_batch_containers {
self.draw_alpha_batch_container(
alpha_batch_container,
@@ -4069,7 +4108,7 @@ impl Renderer {
// Draw any radial gradients for this target.
if !target.radial_gradients.is_empty() {
- let _timer = self.gpu_profiler.start_timer(GPU_TAG_CACHE_RADIAL_GRADIENT);
+ let _timer = self.gpu_profiler.start_timer(GPU_TAG_RADIAL_GRADIENT);
self.set_blend(false, FramebufferKind::Other);
@@ -4095,7 +4134,7 @@ impl Renderer {
// Draw any conic gradients for this target.
if !target.conic_gradients.is_empty() {
- let _timer = self.gpu_profiler.start_timer(GPU_TAG_CACHE_CONIC_GRADIENT);
+ let _timer = self.gpu_profiler.start_timer(GPU_TAG_CONIC_GRADIENT);
self.set_blend(false, FramebufferKind::Other);
diff --git a/gfx/wr/webrender/src/renderer/shade.rs b/gfx/wr/webrender/src/renderer/shade.rs
index 96e8982aa0..5463e8eb67 100644
--- a/gfx/wr/webrender/src/renderer/shade.rs
+++ b/gfx/wr/webrender/src/renderer/shade.rs
@@ -263,6 +263,7 @@ impl LazilyCompiledShader {
VertexArrayKind::Scale => &desc::SCALE,
VertexArrayKind::Resolve => &desc::RESOLVE,
VertexArrayKind::SvgFilter => &desc::SVG_FILTER,
+ VertexArrayKind::SvgFilterNode => &desc::SVG_FILTER_NODE,
VertexArrayKind::Composite => &desc::COMPOSITE,
VertexArrayKind::Clear => &desc::CLEAR,
VertexArrayKind::Copy => &desc::COPY,
@@ -601,6 +602,7 @@ pub struct Shaders {
pub cs_radial_gradient: LazilyCompiledShader,
pub cs_conic_gradient: LazilyCompiledShader,
pub cs_svg_filter: LazilyCompiledShader,
+ pub cs_svg_filter_node: LazilyCompiledShader,
// Brush shaders
brush_solid: BrushShader,
@@ -632,6 +634,8 @@ pub struct Shaders {
ps_split_composite: LazilyCompiledShader,
pub ps_quad_textured: LazilyCompiledShader,
+ pub ps_quad_radial_gradient: LazilyCompiledShader,
+ pub ps_quad_conic_gradient: LazilyCompiledShader,
pub ps_mask: LazilyCompiledShader,
pub ps_mask_fast: LazilyCompiledShader,
pub ps_clear: LazilyCompiledShader,
@@ -768,6 +772,16 @@ impl Shaders {
profile,
)?;
+ let cs_svg_filter_node = LazilyCompiledShader::new(
+ ShaderKind::Cache(VertexArrayKind::SvgFilterNode),
+ "cs_svg_filter_node",
+ &[],
+ device,
+ options.precache_flags,
+ &shader_list,
+ profile,
+ )?;
+
let ps_mask = LazilyCompiledShader::new(
ShaderKind::Cache(VertexArrayKind::Mask),
"ps_quad_mask",
@@ -888,6 +902,26 @@ impl Shaders {
profile,
)?;
+ let ps_quad_radial_gradient = LazilyCompiledShader::new(
+ ShaderKind::Primitive,
+ "ps_quad_radial_gradient",
+ &[],
+ device,
+ options.precache_flags,
+ &shader_list,
+ profile,
+ )?;
+
+ let ps_quad_conic_gradient = LazilyCompiledShader::new(
+ ShaderKind::Primitive,
+ "ps_quad_conic_gradient",
+ &[],
+ device,
+ options.precache_flags,
+ &shader_list,
+ profile,
+ )?;
+
let ps_split_composite = LazilyCompiledShader::new(
ShaderKind::Primitive,
"ps_split_composite",
@@ -1107,6 +1141,7 @@ impl Shaders {
cs_border_solid,
cs_scale,
cs_svg_filter,
+ cs_svg_filter_node,
brush_solid,
brush_image,
brush_fast_image,
@@ -1122,6 +1157,8 @@ impl Shaders {
ps_text_run,
ps_text_run_dual_source,
ps_quad_textured,
+ ps_quad_radial_gradient,
+ ps_quad_conic_gradient,
ps_mask,
ps_mask_fast,
ps_split_composite,
@@ -1160,6 +1197,8 @@ impl Shaders {
) -> &mut LazilyCompiledShader {
match pattern {
PatternKind::ColorOrTexture => &mut self.ps_quad_textured,
+ PatternKind::RadialGradient => &mut self.ps_quad_radial_gradient,
+ PatternKind::ConicGradient => &mut self.ps_quad_conic_gradient,
PatternKind::Mask => unreachable!(),
}
}
@@ -1175,6 +1214,12 @@ impl Shaders {
BatchKind::Quad(PatternKind::ColorOrTexture) => {
&mut self.ps_quad_textured
}
+ BatchKind::Quad(PatternKind::RadialGradient) => {
+ &mut self.ps_quad_radial_gradient
+ }
+ BatchKind::Quad(PatternKind::ConicGradient) => {
+ &mut self.ps_quad_conic_gradient
+ }
BatchKind::Quad(PatternKind::Mask) => {
unreachable!();
}
@@ -1268,6 +1313,7 @@ impl Shaders {
self.cs_blur_a8.deinit(device);
self.cs_blur_rgba8.deinit(device);
self.cs_svg_filter.deinit(device);
+ self.cs_svg_filter_node.deinit(device);
self.brush_solid.deinit(device);
self.brush_blend.deinit(device);
self.brush_mix_blend.deinit(device);
@@ -1305,6 +1351,8 @@ impl Shaders {
self.cs_border_segment.deinit(device);
self.ps_split_composite.deinit(device);
self.ps_quad_textured.deinit(device);
+ self.ps_quad_radial_gradient.deinit(device);
+ self.ps_quad_conic_gradient.deinit(device);
self.ps_mask.deinit(device);
self.ps_mask_fast.deinit(device);
self.ps_clear.deinit(device);
diff --git a/gfx/wr/webrender/src/renderer/vertex.rs b/gfx/wr/webrender/src/renderer/vertex.rs
index cd73975ddd..6ee162ae38 100644
--- a/gfx/wr/webrender/src/renderer/vertex.rs
+++ b/gfx/wr/webrender/src/renderer/vertex.rs
@@ -567,6 +567,56 @@ pub mod desc {
],
};
+ pub const SVG_FILTER_NODE: VertexDescriptor = VertexDescriptor {
+ vertex_attributes: &[VertexAttribute {
+ name: "aPosition",
+ count: 2,
+ kind: VertexAttributeKind::U8Norm,
+ }],
+ instance_attributes: &[
+ VertexAttribute {
+ name: "aFilterTargetRect",
+ count: 4,
+ kind: VertexAttributeKind::F32,
+ },
+ VertexAttribute {
+ name: "aFilterInput1ContentScaleAndOffset",
+ count: 4,
+ kind: VertexAttributeKind::F32,
+ },
+ VertexAttribute {
+ name: "aFilterInput2ContentScaleAndOffset",
+ count: 4,
+ kind: VertexAttributeKind::F32,
+ },
+ VertexAttribute {
+ name: "aFilterInput1TaskAddress",
+ count: 1,
+ kind: VertexAttributeKind::I32,
+ },
+ VertexAttribute {
+ name: "aFilterInput2TaskAddress",
+ count: 1,
+ kind: VertexAttributeKind::I32,
+ },
+ VertexAttribute {
+ name: "aFilterKind",
+ count: 1,
+ kind: VertexAttributeKind::U16,
+ },
+ VertexAttribute {
+ name: "aFilterInputCount",
+ count: 1,
+ kind: VertexAttributeKind::U16,
+ },
+ VertexAttribute {
+ name: "aFilterExtraDataAddress",
+ count: 2,
+ kind: VertexAttributeKind::U16,
+ },
+ ],
+ };
+
pub const MASK: VertexDescriptor = VertexDescriptor {
vertex_attributes: &[VertexAttribute {
name: "aPosition",
@@ -780,6 +830,7 @@ pub enum VertexArrayKind {
ConicGradient,
Resolve,
SvgFilter,
+ SvgFilterNode,
Composite,
Clear,
Copy,
@@ -1004,6 +1055,7 @@ pub struct RendererVAOs {
conic_gradient_vao: VAO,
resolve_vao: VAO,
svg_filter_vao: VAO,
+ svg_filter_node_vao: VAO,
composite_vao: VAO,
clear_vao: VAO,
copy_vao: VAO,
@@ -1051,6 +1103,7 @@ impl RendererVAOs {
conic_gradient_vao: device.create_vao_with_new_instances(&desc::CONIC_GRADIENT, &prim_vao),
resolve_vao: device.create_vao_with_new_instances(&desc::RESOLVE, &prim_vao),
svg_filter_vao: device.create_vao_with_new_instances(&desc::SVG_FILTER, &prim_vao),
+ svg_filter_node_vao: device.create_vao_with_new_instances(&desc::SVG_FILTER_NODE, &prim_vao),
composite_vao: device.create_vao_with_new_instances(&desc::COMPOSITE, &prim_vao),
clear_vao: device.create_vao_with_new_instances(&desc::CLEAR, &prim_vao),
copy_vao: device.create_vao_with_new_instances(&desc::COPY, &prim_vao),
@@ -1073,6 +1126,7 @@ impl RendererVAOs {
device.delete_vao(self.border_vao);
device.delete_vao(self.scale_vao);
device.delete_vao(self.svg_filter_vao);
+ device.delete_vao(self.svg_filter_node_vao);
device.delete_vao(self.composite_vao);
device.delete_vao(self.clear_vao);
device.delete_vao(self.copy_vao);
@@ -1098,6 +1152,7 @@ impl ops::Index<VertexArrayKind> for RendererVAOs {
VertexArrayKind::ConicGradient => &self.conic_gradient_vao,
VertexArrayKind::Resolve => &self.resolve_vao,
VertexArrayKind::SvgFilter => &self.svg_filter_vao,
+ VertexArrayKind::SvgFilterNode => &self.svg_filter_node_vao,
VertexArrayKind::Composite => &self.composite_vao,
VertexArrayKind::Clear => &self.clear_vao,
VertexArrayKind::Copy => &self.copy_vao,
diff --git a/gfx/wr/webrender/src/scene_building.rs b/gfx/wr/webrender/src/scene_building.rs
index 00f29f2ce2..4c76c9522e 100644
--- a/gfx/wr/webrender/src/scene_building.rs
+++ b/gfx/wr/webrender/src/scene_building.rs
@@ -45,8 +45,10 @@ use api::{PropertyBinding, ReferenceFrameKind, ScrollFrameDescriptor, ReferenceF
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::units::*;
use crate::image_tiling::simplify_repeated_primitive;
+use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::clip::{ClipItemKey, ClipStore, ClipItemKeyKind, ClipIntern};
use crate::clip::{ClipInternData, ClipNodeId, ClipLeafId};
use crate::clip::{PolygonDataHandle, ClipTreeBuilder};
@@ -56,7 +58,7 @@ use crate::frame_builder::{FrameBuilderConfig};
use glyph_rasterizer::{FontInstance, SharedFontResources};
use crate::hit_test::HitTestingScene;
use crate::intern::Interner;
-use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter, PlaneSplitterIndex, PipelineInstanceId};
+use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter, FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, PlaneSplitterIndex, PipelineInstanceId};
use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList, SurfaceInfo, PictureFlags};
use crate::picture_graph::PictureGraph;
@@ -90,6 +92,7 @@ use std::collections::vec_deque::VecDeque;
use std::sync::Arc;
use crate::util::{VecHelper, MaxRect};
use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
+use log::Level;
/// Offsets primitives (and clips) by the external scroll offset
/// supplied to scroll nodes.
@@ -192,6 +195,7 @@ impl CompositeOps {
return true;
}
}
+ Filter::SVGGraphNode(..) => {return true;}
_ => {
if filter.is_noop() {
continue;
@@ -724,6 +728,7 @@ impl<'a> SceneBuilder<'a> {
Some(PictureCompositeMode::Filter(Filter::Blur { .. })) => true,
Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) => true,
Some(PictureCompositeMode::SvgFilter( .. )) => true,
+ Some(PictureCompositeMode::SVGFEGraph( .. )) => true,
_ => false,
};
@@ -899,7 +904,11 @@ impl<'a> SceneBuilder<'a> {
let spatial_node_index = self.get_space(info.spatial_id);
let mut subtraversal = item.sub_iter();
// Avoid doing unnecessary work for empty stacking contexts.
- if subtraversal.current_stacking_context_empty() {
+ // We still have to process it if it has filters, they
+ // may be things like SVGFEFlood or various specific
+ // ways to use ComponentTransfer, ColorMatrix, Composite
+ // which are still visible on an empty stacking context
+ if subtraversal.current_stacking_context_empty() && item.filters().is_empty() {
subtraversal.skip_current_stacking_context();
traversal = subtraversal;
continue;
@@ -982,8 +991,8 @@ impl<'a> SceneBuilder<'a> {
match bc.kind {
ContextKind::Root => {}
ContextKind::StackingContext { sc_info } => {
- self.rf_mapper.pop_offset();
self.pop_stacking_context(sc_info);
+ self.rf_mapper.pop_offset();
}
ContextKind::ReferenceFrame => {
self.rf_mapper.pop_scope();
@@ -1041,6 +1050,7 @@ impl<'a> SceneBuilder<'a> {
info.vertical_offset_bounds,
info.horizontal_offset_bounds,
info.previously_applied_offset,
+ info.transform,
);
let index = self.spatial_tree.add_sticky_frame(
@@ -2526,6 +2536,7 @@ impl<'a> SceneBuilder<'a> {
let has_filters = stacking_context.composite_ops.has_valid_filters();
+ let spatial_node_context_offset = self.current_offset(stacking_context.spatial_node_index);
source = self.wrap_prim_with_filters(
source,
stacking_context.clip_node_id,
@@ -2533,6 +2544,7 @@ impl<'a> SceneBuilder<'a> {
stacking_context.composite_ops.filter_primitives,
stacking_context.composite_ops.filter_datas,
None,
+ spatial_node_context_offset,
);
// Same for mix-blend-mode, except we can skip if this primitive is the first in the parent
@@ -3669,6 +3681,7 @@ impl<'a> SceneBuilder<'a> {
filter_primitives,
filter_datas,
Some(false),
+ LayoutVector2D::zero(),
);
// If all the filters were no-ops (e.g. opacity(0)) then we don't get a picture here
@@ -3767,6 +3780,7 @@ impl<'a> SceneBuilder<'a> {
mut filter_primitives: Vec<FilterPrimitive>,
filter_datas: Vec<FilterData>,
should_inflate_override: Option<bool>,
+ context_offset: LayoutVector2D,
) -> PictureChainBuilder {
// TODO(cbrewster): Currently CSS and SVG filters live side by side in WebRender, but unexpected results will
// happen if they are used simulataneously. Gecko only provides either filter ops or filter primitives.
@@ -3776,6 +3790,495 @@ impl<'a> SceneBuilder<'a> {
// For each filter, create a new image with that composite mode.
let mut current_filter_data_index = 0;
+ // Check if the filter chain is actually an SVGFE filter graph DAG
+ if let Some(Filter::SVGGraphNode(..)) = filter_ops.first() {
+ // The interesting parts of the handling of SVG filters are:
+ // * scene_building.rs : wrap_prim_with_filters (you are here)
+ // * picture.rs : get_coverage_svgfe
+ // * render_task.rs : new_svg_filter_graph
+ // * render_target.rs : add_svg_filter_node_instances
+
+ // 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;
+ // Easily tunable for debugging proper handling of inflated rects,
+ // this should normally be 1
+ const SVGFE_INFLATE: i16 = 1;
+ // Easily tunable for debugging proper handling of inflated rects,
+ // this should normally be 0
+ const SVGFE_INFLATE_OUTPUT: i16 = 0;
+
+ // Validate inputs to all filters.
+ //
+ // Several assumptions can be made about the DAG:
+ // * All filters take a specific number of inputs (feMerge is not
+ // supported, the code that built the display items had to convert
+ // any feMerge ops to SVGFECompositeOver already).
+ // * All input buffer ids are < the output buffer id of the node.
+ // * If SourceGraphic or SourceAlpha are used, they are standalone
+ // nodes with no inputs.
+ // * Whenever subregion of a node is smaller than the subregion
+ // of the inputs, it is a deliberate clip of those inputs to the
+ // new rect, this can occur before/after blur and dropshadow for
+ // example, so we must explicitly handle subregion correctly, but
+ // we do not have to allocate the unused pixels as the transparent
+ // black has no efect on any of the filters, only certain filters
+ // like feFlood can generate something from nothing.
+ // * Coordinate basis of the graph has to be adjusted by
+ // context_offset to put the subregions in the same space that the
+ // primitives are in, as they do that offset as well.
+ let mut reference_for_buffer_id: [FilterGraphPictureReference; BUFFER_LIMIT] = [
+ FilterGraphPictureReference{
+ // This value is deliberately invalid, but not a magic
+ // number, it's just this way to guarantee an assertion
+ // failure if something goes wrong.
+ buffer_id: FilterOpGraphPictureBufferId::BufferId(-1),
+ subregion: LayoutRect::zero(), // Always overridden
+ offset: LayoutVector2D::zero(),
+ inflate: 0,
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ }; BUFFER_LIMIT];
+ let mut filters: Vec<(FilterGraphNode, FilterGraphOp)> = Vec::new();
+ filters.reserve(BUFFER_LIMIT);
+ for (original_id, parsefilter) in filter_ops.iter().enumerate() {
+ match parsefilter {
+ Filter::SVGGraphNode(parsenode, op) => {
+ if filters.len() >= BUFFER_LIMIT {
+ // If the DAG is too large we drop it entirely, the spec
+ // allows this.
+ return source;
+ }
+
+ // We need to offset the subregion by the stacking context
+ // offset or we'd be in the wrong coordinate system, prims
+ // are already offset by this same amount.
+ let clip_region = parsenode.subregion
+ .translate(context_offset);
+
+ let mut newnode = FilterGraphNode {
+ kept_by_optimizer: false,
+ linear: parsenode.linear,
+ inflate: SVGFE_INFLATE,
+ inputs: Vec::new(),
+ subregion: clip_region,
+ };
+
+ // Initialize remapped versions of the inputs, this is
+ // done here to share code between the enum variants.
+ let mut remapped_inputs: Vec<FilterGraphPictureReference> = Vec::new();
+ remapped_inputs.reserve_exact(parsenode.inputs.len());
+ for input in &parsenode.inputs {
+ match input.buffer_id {
+ FilterOpGraphPictureBufferId::BufferId(buffer_id) => {
+ // Reference to earlier node output, if this
+ // is None, it's a bug
+ let pic = *reference_for_buffer_id
+ .get(buffer_id as usize)
+ .expect("BufferId not valid?");
+ // We have to adjust the subregion and
+ // padding based on the input offset for
+ // feOffset ops, the padding may be inflated
+ // further by other ops such as blurs below.
+ let offset = input.offset;
+ let subregion = pic.subregion
+ .translate(offset);
+ let source_padding = LayoutRect::zero()
+ .translate(-offset);
+ let target_padding = LayoutRect::zero()
+ .translate(offset);
+ remapped_inputs.push(
+ FilterGraphPictureReference {
+ buffer_id: pic.buffer_id,
+ subregion,
+ offset,
+ inflate: pic.inflate,
+ source_padding,
+ target_padding,
+ });
+ }
+ FilterOpGraphPictureBufferId::None => panic!("Unsupported FilterOpGraphPictureBufferId"),
+ }
+ }
+
+ fn union_unchecked(a: LayoutRect, b: LayoutRect) -> LayoutRect {
+ let mut r = a;
+ if r.min.x > b.min.x {r.min.x = b.min.x}
+ if r.min.y > b.min.y {r.min.y = b.min.y}
+ if r.max.x < b.max.x {r.max.x = b.max.x}
+ if r.max.y < b.max.y {r.max.y = b.max.y}
+ r
+ }
+
+ match op {
+ FilterGraphOp::SVGFEFlood{..} |
+ FilterGraphOp::SVGFESourceAlpha |
+ FilterGraphOp::SVGFESourceGraphic |
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} |
+ FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} |
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} |
+ FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => {
+ assert!(remapped_inputs.len() == 0);
+ filters.push((newnode.clone(), op.clone()));
+ }
+ FilterGraphOp::SVGFEColorMatrix{..} |
+ FilterGraphOp::SVGFEIdentity |
+ FilterGraphOp::SVGFEImage{..} |
+ FilterGraphOp::SVGFEOpacity{..} |
+ FilterGraphOp::SVGFEToAlpha => {
+ assert!(remapped_inputs.len() == 1);
+ newnode.inputs = remapped_inputs;
+ filters.push((newnode.clone(), op.clone()));
+ }
+ FilterGraphOp::SVGFEComponentTransfer => {
+ assert!(remapped_inputs.len() == 1);
+ // Convert to SVGFEComponentTransferInterned
+ let filter_data =
+ &filter_datas[current_filter_data_index];
+ let filter_data = filter_data.sanitize();
+ current_filter_data_index = current_filter_data_index + 1;
+
+ // filter data is 4KiB of gamma ramps used
+ // only by SVGFEComponentTransferWithHandle.
+ //
+ // The gamma ramps are interleaved as RGBA32F
+ // pixels (unlike in regular ComponentTransfer,
+ // where the values are not interleaved), so
+ // r_values[3] is the alpha of the first color,
+ // not the 4th red value. This layout makes the
+ // shader more compatible with buggy compilers that
+ // do not like indexing components on a vec4.
+ let creates_pixels =
+ if let Some(a) = filter_data.r_values.get(3) {
+ *a != 0.0
+ } else {
+ false
+ };
+ let filter_data_key = SFilterDataKey {
+ data:
+ SFilterData {
+ r_func: SFilterDataComponent::from_functype_values(
+ filter_data.func_r_type, &filter_data.r_values),
+ g_func: SFilterDataComponent::from_functype_values(
+ filter_data.func_g_type, &filter_data.g_values),
+ b_func: SFilterDataComponent::from_functype_values(
+ filter_data.func_b_type, &filter_data.b_values),
+ a_func: SFilterDataComponent::from_functype_values(
+ filter_data.func_a_type, &filter_data.a_values),
+ },
+ };
+
+ let handle = self.interners
+ .filter_data
+ .intern(&filter_data_key, || ());
+
+ newnode.inputs = remapped_inputs;
+ filters.push((newnode.clone(), FilterGraphOp::SVGFEComponentTransferInterned{handle, creates_pixels}));
+ }
+ FilterGraphOp::SVGFEComponentTransferInterned{..} => unreachable!(),
+ FilterGraphOp::SVGFETile => {
+ assert!(remapped_inputs.len() == 1);
+ // feTile usually uses every pixel of input
+ remapped_inputs[0].source_padding =
+ LayoutRect::max_rect();
+ remapped_inputs[0].target_padding =
+ LayoutRect::max_rect();
+ newnode.inputs = remapped_inputs;
+ filters.push((newnode.clone(), op.clone()));
+ }
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{kernel_unit_length_x, kernel_unit_length_y, ..} |
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{kernel_unit_length_x, kernel_unit_length_y, ..} |
+ FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{kernel_unit_length_x, kernel_unit_length_y, ..} |
+ FilterGraphOp::SVGFEMorphologyDilate{radius_x: kernel_unit_length_x, radius_y: kernel_unit_length_y} => {
+ assert!(remapped_inputs.len() == 1);
+ let padding = LayoutSize::new(
+ kernel_unit_length_x.ceil(),
+ kernel_unit_length_y.ceil(),
+ );
+ // Add source padding to represent the kernel pixels
+ // needed relative to target pixels
+ remapped_inputs[0].source_padding =
+ remapped_inputs[0].source_padding
+ .inflate(padding.width, padding.height);
+ // Add target padding to represent the area affected
+ // by a source pixel
+ remapped_inputs[0].target_padding =
+ remapped_inputs[0].target_padding
+ .inflate(padding.width, padding.height);
+ newnode.inputs = remapped_inputs;
+ filters.push((newnode.clone(), op.clone()));
+ },
+ FilterGraphOp::SVGFEDiffuseLightingDistant{kernel_unit_length_x, kernel_unit_length_y, ..} |
+ FilterGraphOp::SVGFEDiffuseLightingPoint{kernel_unit_length_x, kernel_unit_length_y, ..} |
+ FilterGraphOp::SVGFEDiffuseLightingSpot{kernel_unit_length_x, kernel_unit_length_y, ..} |
+ FilterGraphOp::SVGFESpecularLightingDistant{kernel_unit_length_x, kernel_unit_length_y, ..} |
+ FilterGraphOp::SVGFESpecularLightingPoint{kernel_unit_length_x, kernel_unit_length_y, ..} |
+ FilterGraphOp::SVGFESpecularLightingSpot{kernel_unit_length_x, kernel_unit_length_y, ..} |
+ FilterGraphOp::SVGFEMorphologyErode{radius_x: kernel_unit_length_x, radius_y: kernel_unit_length_y} => {
+ assert!(remapped_inputs.len() == 1);
+ let padding = LayoutSize::new(
+ kernel_unit_length_x.ceil(),
+ kernel_unit_length_y.ceil(),
+ );
+ // Add source padding to represent the kernel pixels
+ // needed relative to target pixels
+ remapped_inputs[0].source_padding =
+ remapped_inputs[0].source_padding
+ .inflate(padding.width, padding.height);
+ // Add target padding to represent the area affected
+ // by a source pixel
+ remapped_inputs[0].target_padding =
+ remapped_inputs[0].target_padding
+ .inflate(padding.width, padding.height);
+ newnode.inputs = remapped_inputs;
+ filters.push((newnode.clone(), op.clone()));
+ },
+ FilterGraphOp::SVGFEDisplacementMap { scale, .. } => {
+ assert!(remapped_inputs.len() == 2);
+ let padding = LayoutSize::new(
+ scale.ceil(),
+ scale.ceil(),
+ );
+ // Add padding to both inputs for source and target
+ // rects, we might be able to skip some of these,
+ // but it's not that important to optimize here, a
+ // loose fit is fine.
+ remapped_inputs[0].source_padding =
+ remapped_inputs[0].source_padding
+ .inflate(padding.width, padding.height);
+ remapped_inputs[1].source_padding =
+ remapped_inputs[1].source_padding
+ .inflate(padding.width, padding.height);
+ remapped_inputs[0].target_padding =
+ remapped_inputs[0].target_padding
+ .inflate(padding.width, padding.height);
+ remapped_inputs[1].target_padding =
+ remapped_inputs[1].target_padding
+ .inflate(padding.width, padding.height);
+ newnode.inputs = remapped_inputs;
+ filters.push((newnode.clone(), op.clone()));
+ },
+ FilterGraphOp::SVGFEDropShadow{ dx, dy, std_deviation_x, std_deviation_y, .. } => {
+ assert!(remapped_inputs.len() == 1);
+ let padding = LayoutSize::new(
+ std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
+ std_deviation_y.ceil() * BLUR_SAMPLE_SCALE,
+ );
+ // Add source padding to represent the shadow
+ remapped_inputs[0].source_padding =
+ union_unchecked(
+ remapped_inputs[0].source_padding,
+ remapped_inputs[0].source_padding
+ .inflate(padding.width, padding.height)
+ .translate(
+ LayoutVector2D::new(-dx, -dy)
+ )
+ );
+ // Add target padding to represent the area needed
+ // to calculate pixels of the shadow
+ remapped_inputs[0].target_padding =
+ union_unchecked(
+ remapped_inputs[0].target_padding,
+ remapped_inputs[0].target_padding
+ .inflate(padding.width, padding.height)
+ .translate(
+ LayoutVector2D::new(*dx, *dy)
+ )
+ );
+ newnode.inputs = remapped_inputs;
+ filters.push((newnode.clone(), op.clone()));
+ },
+ FilterGraphOp::SVGFEGaussianBlur{std_deviation_x, std_deviation_y} => {
+ assert!(remapped_inputs.len() == 1);
+ let padding = LayoutSize::new(
+ std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
+ std_deviation_y.ceil() * BLUR_SAMPLE_SCALE,
+ );
+ // Add source padding to represent the blur
+ remapped_inputs[0].source_padding =
+ remapped_inputs[0].source_padding
+ .inflate(padding.width, padding.height);
+ // Add target padding to represent the blur
+ remapped_inputs[0].target_padding =
+ remapped_inputs[0].target_padding
+ .inflate(padding.width, padding.height);
+ newnode.inputs = remapped_inputs;
+ filters.push((newnode.clone(), op.clone()));
+ }
+ 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::SVGFECompositeArithmetic{..} |
+ FilterGraphOp::SVGFECompositeATop |
+ FilterGraphOp::SVGFECompositeIn |
+ FilterGraphOp::SVGFECompositeLighter |
+ FilterGraphOp::SVGFECompositeOut |
+ FilterGraphOp::SVGFECompositeOver |
+ FilterGraphOp::SVGFECompositeXOR => {
+ assert!(remapped_inputs.len() == 2);
+ newnode.inputs = remapped_inputs;
+ filters.push((newnode.clone(), op.clone()));
+ }
+ }
+
+ // Set the reference remapping for the last (or only) node
+ // that we just pushed
+ let id = (filters.len() - 1) as i16;
+ if let Some(pic) = reference_for_buffer_id.get_mut(original_id as usize) {
+ *pic = FilterGraphPictureReference {
+ buffer_id: FilterOpGraphPictureBufferId::BufferId(id),
+ subregion: newnode.subregion,
+ offset: LayoutVector2D::zero(),
+ inflate: newnode.inflate,
+ source_padding: LayoutRect::zero(),
+ target_padding: LayoutRect::zero(),
+ };
+ }
+ }
+ _ => {
+ panic!("wrap_prim_with_filters: Mixed SVG and CSS filters?")
+ }
+ }
+ }
+
+ // Push a special output node at the end, this will correctly handle
+ // the final subregion, which may not have the same bounds as the
+ // surface it is being blitted into, so it needs to properly handle
+ // the cropping and UvRectKind, it also has no inflate.
+ if filters.len() >= BUFFER_LIMIT {
+ // If the DAG is too large we drop it entirely
+ return source;
+ }
+ let mut outputnode = FilterGraphNode {
+ kept_by_optimizer: true,
+ linear: false,
+ inflate: SVGFE_INFLATE_OUTPUT,
+ inputs: Vec::new(),
+ subregion: LayoutRect::max_rect(),
+ };
+ outputnode.inputs.push(reference_for_buffer_id[filter_ops.len() - 1]);
+ filters.push((
+ outputnode,
+ FilterGraphOp::SVGFEIdentity,
+ ));
+
+ // We want to optimize the filter DAG and then wrap it in a single
+ // picture, we will use a custom RenderTask method to process the
+ // DAG later, there's not really an easy way to keep it as a series
+ // of pictures like CSS filters use.
+ //
+ // The main optimization we can do here is looking for feOffset
+ // filters we can merge away - because all of the node inputs
+ // support offset capability implicitly. We can also remove no-op
+ // filters (identity) if Gecko produced any.
+ //
+ // TODO: optimize the graph here
+
+ // Mark used graph nodes, starting at the last graph node, since
+ // this is a DAG in sorted order we can just iterate backwards and
+ // know we will find children before parents in order.
+ //
+ // Per SVG spec the last node (which is the first we encounter this
+ // way) is the final output, so its dependencies are what we want to
+ // mark as kept_by_optimizer
+ let mut kept_node_by_buffer_id = [false; BUFFER_LIMIT];
+ kept_node_by_buffer_id[filters.len() - 1] = true;
+ for (index, (node, _op)) in filters.iter_mut().enumerate().rev() {
+ let mut keep = false;
+ // Check if this node's output was marked to be kept
+ if let Some(k) = kept_node_by_buffer_id.get(index) {
+ if *k {
+ keep = true;
+ }
+ }
+ if keep {
+ // If this node contributes to the final output we need
+ // to mark its inputs as also contributing when they are
+ // encountered later
+ node.kept_by_optimizer = true;
+ for input in &node.inputs {
+ if let FilterOpGraphPictureBufferId::BufferId(id) = input.buffer_id {
+ if let Some(k) = kept_node_by_buffer_id.get_mut(id as usize) {
+ *k = true;
+ }
+ }
+ }
+ }
+ }
+
+ // Validate the DAG nature of the graph again - if we find anything
+ // wrong here it means the above code is bugged.
+ let mut invalid_dag = false;
+ for (id, (node, _op)) in filters.iter().enumerate() {
+ for input in &node.inputs {
+ if let FilterOpGraphPictureBufferId::BufferId(buffer_id) = input.buffer_id {
+ if buffer_id < 0 || buffer_id as usize >= id {
+ invalid_dag = true;
+ }
+ }
+ }
+ }
+
+ if invalid_dag {
+ log!(Level::Warn, "List of FilterOp::SVGGraphNode filter primitives appears to be invalid!");
+ for (id, (node, op)) in filters.iter().enumerate() {
+ log!(Level::Warn, " node: buffer=BufferId({}) op={} inflate={} subregion {:?} linear={} kept={}",
+ id, op.kind(), node.inflate,
+ node.subregion,
+ node.linear,
+ node.kept_by_optimizer,
+ );
+ for input in &node.inputs {
+ log!(Level::Warn, "input: buffer={} inflate={} subregion {:?} offset {:?} target_padding={:?} source_padding={:?}",
+ match input.buffer_id {
+ FilterOpGraphPictureBufferId::BufferId(id) => format!("BufferId({})", id),
+ FilterOpGraphPictureBufferId::None => "None".into(),
+ },
+ input.inflate,
+ input.subregion,
+ input.offset,
+ input.target_padding,
+ input.source_padding,
+ );
+ }
+ }
+ }
+ if invalid_dag {
+ // if the DAG is invalid, we can't render it
+ return source;
+ }
+
+ let composite_mode = PictureCompositeMode::SVGFEGraph(
+ filters,
+ );
+
+ source = source.add_picture(
+ composite_mode,
+ clip_node_id,
+ Picture3DContext::Out,
+ &mut self.interners,
+ &mut self.prim_store,
+ &mut self.prim_instances,
+ &mut self.clip_tree_builder,
+ );
+
+ return source;
+ }
+
+ // Handle regular CSS filter chains
for filter in &mut filter_ops {
let composite_mode = match filter {
Filter::ComponentTransfer => {
@@ -3806,6 +4309,10 @@ impl<'a> SceneBuilder<'a> {
PictureCompositeMode::ComponentTransferFilter(handle)
}
}
+ Filter::SVGGraphNode(_, _) => {
+ // SVG filter graphs were handled above
+ panic!("SVGGraphNode encountered in regular CSS filter chain?");
+ }
_ => {
if filter.is_noop() {
continue;
diff --git a/gfx/wr/webrender/src/spatial_node.rs b/gfx/wr/webrender/src/spatial_node.rs
index 6bf1313e0d..727ad405ed 100644
--- a/gfx/wr/webrender/src/spatial_node.rs
+++ b/gfx/wr/webrender/src/spatial_node.rs
@@ -518,17 +518,46 @@ impl SpatialNode {
self.viewport_transform = cs_scale_offset;
self.content_transform = cs_scale_offset;
}
- _ => {
- // We calculate this here to avoid a double-borrow later.
- let sticky_offset = self.calculate_sticky_offset(
+ SpatialNodeType::StickyFrame(ref mut info) => {
+ let animated_offset = if let Some(transform_binding) = info.transform {
+ let transform = scene_properties.resolve_layout_transform(&transform_binding);
+ match ScaleOffset::from_transform(&transform) {
+ Some(ref scale_offset) => {
+ debug_assert!(scale_offset.scale == Vector2D::new(1.0, 1.0),
+ "Can only animate a translation on sticky elements");
+ LayoutVector2D::from_untyped(scale_offset.offset)
+ }
+ None => {
+ debug_assert!(false, "Can only animate a translation on sticky elements");
+ LayoutVector2D::zero()
+ }
+ }
+ } else {
+ LayoutVector2D::zero()
+ };
+
+ let sticky_offset = Self::calculate_sticky_offset(
&state.nearest_scrolling_ancestor_offset,
&state.nearest_scrolling_ancestor_viewport,
+ info,
);
// The transformation for the bounds of our viewport is the parent reference frame
// transform, plus any accumulated scroll offset from our parents, plus any offset
// provided by our own sticky positioning.
- let accumulated_offset = state.parent_accumulated_scroll_offset + sticky_offset;
+ let accumulated_offset = state.parent_accumulated_scroll_offset + sticky_offset + animated_offset;
+ self.viewport_transform = state.coordinate_system_relative_scale_offset
+ .offset(snap_offset(accumulated_offset, state.coordinate_system_relative_scale_offset.scale).to_untyped());
+ self.content_transform = self.viewport_transform;
+
+ info.current_offset = sticky_offset + animated_offset;
+
+ self.coordinate_system_id = state.current_coordinate_system_id;
+ }
+ SpatialNodeType::ScrollFrame(_) => {
+ // The transformation for the bounds of our viewport is the parent reference frame
+ // transform, plus any accumulated scroll offset from our parents.
+ let accumulated_offset = state.parent_accumulated_scroll_offset;
self.viewport_transform = state.coordinate_system_relative_scale_offset
.offset(snap_offset(accumulated_offset, state.coordinate_system_relative_scale_offset.scale).to_untyped());
@@ -538,12 +567,8 @@ impl SpatialNode {
self.content_transform = state.coordinate_system_relative_scale_offset
.offset(snap_offset(added_offset, state.coordinate_system_relative_scale_offset.scale).to_untyped());
- if let SpatialNodeType::StickyFrame(ref mut info) = self.node_type {
- info.current_offset = sticky_offset;
- }
-
self.coordinate_system_id = state.current_coordinate_system_id;
- }
+ }
}
//TODO: remove the field entirely?
@@ -555,15 +580,10 @@ impl SpatialNode {
}
fn calculate_sticky_offset(
- &self,
viewport_scroll_offset: &LayoutVector2D,
viewport_rect: &LayoutRect,
+ info: &StickyFrameInfo
) -> LayoutVector2D {
- let info = match self.node_type {
- SpatialNodeType::StickyFrame(ref info) => info,
- _ => return LayoutVector2D::zero(),
- };
-
if info.margins.top.is_none() && info.margins.bottom.is_none() &&
info.margins.left.is_none() && info.margins.right.is_none() {
return LayoutVector2D::zero();
@@ -885,12 +905,13 @@ pub struct ReferenceFrameInfo {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct StickyFrameInfo {
- pub frame_rect: LayoutRect,
- pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
+ pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
+ pub frame_rect: LayoutRect,
pub vertical_offset_bounds: StickyOffsetBounds,
pub horizontal_offset_bounds: StickyOffsetBounds,
pub previously_applied_offset: LayoutVector2D,
pub current_offset: LayoutVector2D,
+ pub transform: Option<PropertyBinding<LayoutTransform>>,
}
impl StickyFrameInfo {
@@ -899,7 +920,8 @@ impl StickyFrameInfo {
margins: SideOffsets2D<Option<f32>, LayoutPixel>,
vertical_offset_bounds: StickyOffsetBounds,
horizontal_offset_bounds: StickyOffsetBounds,
- previously_applied_offset: LayoutVector2D
+ previously_applied_offset: LayoutVector2D,
+ transform: Option<PropertyBinding<LayoutTransform>>,
) -> StickyFrameInfo {
StickyFrameInfo {
frame_rect,
@@ -908,6 +930,7 @@ impl StickyFrameInfo {
horizontal_offset_bounds,
previously_applied_offset,
current_offset: LayoutVector2D::zero(),
+ transform,
}
}
}
diff --git a/gfx/wr/webrender/src/spatial_tree.rs b/gfx/wr/webrender/src/spatial_tree.rs
index 0aa6bb5296..94e934ca53 100644
--- a/gfx/wr/webrender/src/spatial_tree.rs
+++ b/gfx/wr/webrender/src/spatial_tree.rs
@@ -335,9 +335,11 @@ impl SceneSpatialTree {
pub fn find_scroll_root(
&self,
spatial_node_index: SpatialNodeIndex,
+ allow_sticky_frames: bool,
) -> SpatialNodeIndex {
let mut real_scroll_root = self.root_reference_frame_index;
let mut outermost_scroll_root = self.root_reference_frame_index;
+ let mut current_scroll_root_is_sticky = false;
let mut node_index = spatial_node_index;
while node_index != self.root_reference_frame_index {
@@ -354,10 +356,21 @@ impl SceneSpatialTree {
// we have encountered, as they may end up with a non-axis-aligned transform.
real_scroll_root = self.root_reference_frame_index;
outermost_scroll_root = self.root_reference_frame_index;
+ current_scroll_root_is_sticky = false;
}
}
}
- SpatialNodeType::StickyFrame(..) => {}
+ SpatialNodeType::StickyFrame(..) => {
+ // Though not a scroll frame, we optionally treat sticky frames as scroll roots
+ // to ensure they are given a separate picture cache slice.
+ if allow_sticky_frames {
+ outermost_scroll_root = node_index;
+ real_scroll_root = node_index;
+ // Set this true so that we don't select an ancestor scroll frame as the scroll root
+ // on a subsequent iteration.
+ current_scroll_root_is_sticky = true;
+ }
+ }
SpatialNodeType::ScrollFrame(ref info) => {
match info.frame_kind {
ScrollFrameKind::PipelineRoot { is_root_pipeline } => {
@@ -371,24 +384,29 @@ impl SceneSpatialTree {
// later on, even if it's not actually scrollable.
outermost_scroll_root = node_index;
- // If the scroll root has no scrollable area, we don't want to
- // consider it. This helps pages that have a nested scroll root
- // within a redundant scroll root to avoid selecting the wrong
- // reference spatial node for a picture cache.
- if info.scrollable_size.width > MIN_SCROLLABLE_AMOUNT ||
- info.scrollable_size.height > MIN_SCROLLABLE_AMOUNT {
- // Since we are skipping redundant scroll roots, we may end up
- // selecting inner scroll roots that are very small. There is
- // no performance benefit to creating a slice for these roots,
- // as they are cheap to rasterize. The size comparison is in
- // local-space, but makes for a reasonable estimate. The value
- // is arbitrary, but is generally small enough to ignore things
- // like scroll roots around text input elements.
- if info.viewport_rect.width() > MIN_SCROLL_ROOT_SIZE &&
- info.viewport_rect.height() > MIN_SCROLL_ROOT_SIZE {
- // If we've found a root that is scrollable, and a reasonable
- // size, select that as the current root for this node
- real_scroll_root = node_index;
+ // If the previously identified scroll root is sticky then we don't
+ // want to choose an ancestor scroll root, as we want the sticky item
+ // to have its own picture cache slice.
+ if !current_scroll_root_is_sticky {
+ // If the scroll root has no scrollable area, we don't want to
+ // consider it. This helps pages that have a nested scroll root
+ // within a redundant scroll root to avoid selecting the wrong
+ // reference spatial node for a picture cache.
+ if info.scrollable_size.width > MIN_SCROLLABLE_AMOUNT ||
+ info.scrollable_size.height > MIN_SCROLLABLE_AMOUNT {
+ // Since we are skipping redundant scroll roots, we may end up
+ // selecting inner scroll roots that are very small. There is
+ // no performance benefit to creating a slice for these roots,
+ // as they are cheap to rasterize. The size comparison is in
+ // local-space, but makes for a reasonable estimate. The value
+ // is arbitrary, but is generally small enough to ignore things
+ // like scroll roots around text input elements.
+ if info.viewport_rect.width() > MIN_SCROLL_ROOT_SIZE &&
+ info.viewport_rect.height() > MIN_SCROLL_ROOT_SIZE {
+ // If we've found a root that is scrollable, and a reasonable
+ // size, select that as the current root for this node
+ real_scroll_root = node_index;
+ }
}
}
}
@@ -1732,7 +1750,7 @@ fn test_find_scroll_root_simple() {
SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
);
- assert_eq!(st.find_scroll_root(scroll), scroll);
+ assert_eq!(st.find_scroll_root(scroll, true), scroll);
}
/// Tests that we select the root scroll frame rather than the subframe if both are scrollable.
@@ -1781,7 +1799,7 @@ fn test_find_scroll_root_sub_scroll_frame() {
SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid),
);
- assert_eq!(st.find_scroll_root(sub_scroll), root_scroll);
+ assert_eq!(st.find_scroll_root(sub_scroll, true), root_scroll);
}
/// Tests that we select the sub scroll frame when the root scroll frame is not scrollable.
@@ -1830,7 +1848,7 @@ fn test_find_scroll_root_not_scrollable() {
SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid),
);
- assert_eq!(st.find_scroll_root(sub_scroll), sub_scroll);
+ assert_eq!(st.find_scroll_root(sub_scroll, true), sub_scroll);
}
/// Tests that we select the sub scroll frame when the root scroll frame is too small.
@@ -1879,7 +1897,7 @@ fn test_find_scroll_root_too_small() {
SpatialNodeUid::external(SpatialTreeItemKey::new(0, 2), PipelineId::dummy(), pid),
);
- assert_eq!(st.find_scroll_root(sub_scroll), sub_scroll);
+ assert_eq!(st.find_scroll_root(sub_scroll, true), sub_scroll);
}
/// Tests that we select the root scroll node, even if it is not scrollable,
@@ -1941,7 +1959,7 @@ fn test_find_scroll_root_perspective() {
SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy(), pid),
);
- assert_eq!(st.find_scroll_root(sub_scroll), root_scroll);
+ assert_eq!(st.find_scroll_root(sub_scroll, true), root_scroll);
}
/// Tests that encountering a 2D scale or translation transform does not prevent
@@ -2005,7 +2023,61 @@ fn test_find_scroll_root_2d_scale() {
SpatialNodeUid::external(SpatialTreeItemKey::new(0, 3), PipelineId::dummy(), pid),
);
- assert_eq!(st.find_scroll_root(sub_scroll), sub_scroll);
+ assert_eq!(st.find_scroll_root(sub_scroll, true), sub_scroll);
+}
+
+/// Tests that a sticky spatial node is chosen as the scroll root rather than
+/// its parent scroll frame
+#[test]
+fn test_find_scroll_root_sticky() {
+ let mut st = SceneSpatialTree::new();
+ let pid = PipelineInstanceId::new(0);
+
+ let root = st.add_reference_frame(
+ st.root_reference_frame_index(),
+ TransformStyle::Flat,
+ PropertyBinding::Value(LayoutTransform::identity()),
+ ReferenceFrameKind::Transform {
+ is_2d_scale_translation: true,
+ should_snap: true,
+ paired_with_perspective: false,
+ },
+ LayoutVector2D::new(0.0, 0.0),
+ PipelineId::dummy(),
+ SpatialNodeUid::external(SpatialTreeItemKey::new(0, 0), PipelineId::dummy(), pid),
+ );
+
+ let scroll = st.add_scroll_frame(
+ root,
+ ExternalScrollId(1, PipelineId::dummy()),
+ PipelineId::dummy(),
+ &LayoutRect::from_size(LayoutSize::new(400.0, 400.0)),
+ &LayoutSize::new(400.0, 800.0),
+ ScrollFrameKind::Explicit,
+ LayoutVector2D::new(0.0, 0.0),
+ APZScrollGeneration::default(),
+ HasScrollLinkedEffect::No,
+ SpatialNodeUid::external(SpatialTreeItemKey::new(0, 1), PipelineId::dummy(), pid),
+ );
+
+ let sticky = st.add_sticky_frame(
+ scroll,
+ StickyFrameInfo {
+ frame_rect: LayoutRect::from_size(LayoutSize::new(400.0, 100.0)),
+ margins: euclid::SideOffsets2D::new(Some(0.0), None, None, None),
+ vertical_offset_bounds: api::StickyOffsetBounds::new(0.0, 0.0),
+ horizontal_offset_bounds: api::StickyOffsetBounds::new(0.0, 0.0),
+ previously_applied_offset: LayoutVector2D::zero(),
+ current_offset: LayoutVector2D::zero(),
+ transform: None
+ },
+ PipelineId::dummy(),
+ SpatialTreeItemKey::new(0, 2),
+ pid,
+ );
+
+ assert_eq!(st.find_scroll_root(sticky, true), sticky);
+ assert_eq!(st.find_scroll_root(sticky, false), scroll);
}
#[test]
diff --git a/gfx/wr/webrender/src/tile_cache.rs b/gfx/wr/webrender/src/tile_cache.rs
index 89f42cfe21..a3c1ad233a 100644
--- a/gfx/wr/webrender/src/tile_cache.rs
+++ b/gfx/wr/webrender/src/tile_cache.rs
@@ -226,6 +226,7 @@ impl TileCacheBuilder {
cluster.spatial_node_index,
&mut self.prev_scroll_root_cache,
spatial_tree,
+ true,
);
*scroll_root_occurrences.entry(scroll_root).or_insert(0) += 1;
@@ -324,6 +325,9 @@ impl TileCacheBuilder {
spatial_node_index,
&mut self.prev_scroll_root_cache,
spatial_tree,
+ // Allow sticky frames as scroll roots, unless our quality settings prefer
+ // subpixel AA over performance.
+ !quality_settings.force_subpixel_aa_where_possible,
);
let current_scroll_root = secondary_slices
@@ -369,6 +373,7 @@ impl TileCacheBuilder {
clip_node_data.key.spatial_node_index,
&mut self.prev_scroll_root_cache,
spatial_tree,
+ true,
);
if spatial_root != self.root_spatial_node_index {
@@ -509,12 +514,13 @@ fn find_scroll_root(
spatial_node_index: SpatialNodeIndex,
prev_scroll_root_cache: &mut (SpatialNodeIndex, SpatialNodeIndex),
spatial_tree: &SceneSpatialTree,
+ allow_sticky_frames: bool,
) -> SpatialNodeIndex {
if prev_scroll_root_cache.0 == spatial_node_index {
return prev_scroll_root_cache.1;
}
- let scroll_root = spatial_tree.find_scroll_root(spatial_node_index);
+ let scroll_root = spatial_tree.find_scroll_root(spatial_node_index, allow_sticky_frames);
*prev_scroll_root_cache = (spatial_node_index, scroll_root);
scroll_root