Skip to content

Commit cb885be

Browse files
[LBSE] Apply filters specified on the outermost <svg>
https://bugs.webkit.org/show_bug.cgi?id=321427 Reviewed by Alejandro G. Castro. A filter on an outermost <svg> was either dropped entirely or set up with the wrong geometry under the layer-based SVG engine. RenderLayer::shouldPaintWithFilters() bailed out for any SVG root referencing a <filter>. That is correct for the legacy engine, where LegacyRenderSVGRoot applies the referenced filter itself through SVGRenderingContext::prepareToRenderSVGContent() and the layer must not apply it a second time. The layer-based engine has no such path, RenderSVGRoot relies on its layer, exactly like an HTML box with 'filter: url(...)' - so the filter was silently never applied. Fix that behavior for LBSE. RenderLayerFilters::beginFilterEffect() then treated RenderSVGRoot as a renderer whose filter lives in SVG user space, seeding the filter region and the reference box from objectBoundingBox() and scaling by the accumulated SVG ancestor transform. The outermost <svg> is a replaced element in the CSS box tree: its filter resolves against its border box, in its container's coordinate system, not against the SVG user space its children live in - fix that too. Fixes svg/filters/filter-specified-on-svg-root.html and svg/filters/filter-image-ref-root.html. * LayoutTests/platform/mac-tahoe-wk2-lbse-text/TestExpectations: * Source/WebCore/rendering/RenderLayer.cpp: (WebCore::RenderLayer::shouldPaintWithFilters const): (WebCore::RenderLayer::paintLayerContents): * Source/WebCore/rendering/RenderLayerFilters.cpp: (WebCore::RenderLayerFilters::beginFilterEffect): (WebCore::RenderLayerFilters::applyFilterEffect): Canonical link: https://commits.webkit.org/319121@main
1 parent 4af3bba commit cb885be

3 files changed

Lines changed: 22 additions & 31 deletions

File tree

‎LayoutTests/platform/mac-tahoe-wk2-lbse-text/TestExpectations‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,10 @@ svg/filters/feImage-filterUnits-userSpaceOnUse-primitiveUnits-objectBoundingBox.
4949
svg/filters/feImage-filterUnits-userSpaceOnUse-primitiveUnits-userSpaceOnUse.svg [ ImageOnlyFailure ]
5050
svg/filters/feImage-late-indirect-update.svg [ ImageOnlyFailure ]
5151
svg/filters/feImage-reference-svg-primitive.svg [ ImageOnlyFailure ]
52-
svg/filters/filter-image-ref-root.html [ ImageOnlyFailure ]
5352
svg/filters/filter-on-root-tile-boundary.html [ ImageOnlyFailure ]
5453
svg/filters/filter-on-tspan.svg [ ImageOnlyFailure ]
5554
svg/filters/filter-placement-issue.svg [ ImageOnlyFailure ]
5655
svg/filters/filter-source-position.svg [ ImageOnlyFailure ]
57-
svg/filters/filter-specified-on-svg-root.html [ ImageOnlyFailure ]
5856

5957
# Regressed by conditional layer creation (bugs.webkit.org/b/308565). Filter and resource-buffer
6058
# painting for non-layered SVG renderers is corrected by a follow-up PR, which restores these.

‎Source/WebCore/rendering/RenderLayer.cpp‎

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,7 @@ bool RenderLayer::shouldPaintWithFilters(OptionSet<PaintBehavior> paintBehavior)
10551055
if (filter.isNone())
10561056
return false;
10571057

1058-
if (renderer().isRenderOrLegacyRenderSVGRoot() && filter.isReferenceFilter())
1058+
if (renderer().isLegacyRenderSVGRoot() && filter.isReferenceFilter())
10591059
return false;
10601060

10611061
if (RenderLayerFilters::isIdentity(renderer()))
@@ -3771,6 +3771,11 @@ void RenderLayer::paintLayerContents(GraphicsContext& context, const LayerPainti
37713771

37723772
LayerPaintingInfo localPaintingInfo(paintingInfo);
37733773

3774+
// The outermost <svg> is a replaced element in the CSS box tree, so its filter is set up in
3775+
// CSS box coordinates (see RenderLayerFilters::beginFilterEffect) and none of the SVG user
3776+
// space corrections in this scope apply to it.
3777+
bool filtersInSVGUserSpace = renderer().isSVGLayerAwareRenderer() && !renderer().isRenderSVGRoot();
3778+
37743779
// Position the filter buffer and composite its result at the element's
37753780
// nominalSVGLayoutLocation, independent of intermediate force-layers that perturb offsetFromRoot.
37763781
auto svgFilterOffset = columnAwareOffsetFromRoot;
@@ -3783,29 +3788,16 @@ void RenderLayer::paintLayerContents(GraphicsContext& context, const LayerPainti
37833788

37843789
auto* filterContext = setupFilters(context, localPaintingInfo, localPaintFlags, svgFilterOffset, backgroundRect);
37853790

3786-
// Non-layer content drawn into the buffer uses a distinct offset. The buffer coordinate
3787-
// system is the filter region (transform-aware objectBoundingBox), whereas svgFilterOffset is
3788-
// based on objectBoundingBoxWithoutTransformations. The two differ by the child-transform delta
3789-
// only when the filtered element has a transformed child (e.g. a layered <use> with x/y), and
3790-
// are equal otherwise. This positions non-layer fragment collection only. A layer child already
3791-
// carries its transform in its CTM, so it uses svgFilterOffset (the buffer origin). Adding the
3792-
// delta there would double-count the transform (svg/filters/filter-refresh.svg).
37933791
auto svgFilterContentOffset = svgFilterOffset;
3794-
if (filterContext && renderer().isSVGLayerAwareRenderer())
3792+
if (filterContext && filtersInSVGUserSpace)
37953793
svgFilterContentOffset += renderer().objectBoundingBoxLocation() - renderer().nominalSVGLayoutLocation();
37963794

3797-
// Layer children reset their CTM via offsetFromAncestor(rootLayer) and miss the buffer origin
3798-
// that non-layer children pick up through containerBaseOffset, so translate them by
3799-
// svgFilterOffset to match. Needed when rootLayer == this, and when this filter is nested
3800-
// inside another filter whose buffer was coordinate-origin shifted (our buffer inherits the
3801-
// shift but the layer child does not, svg/filters/filter-refresh.svg). The nested case is read
3802-
// from rootLayer directly: a descendant painting inside a shifted buffer always has that
3803-
// shifting ancestor (transformed, hence rootLayer-establishing, and filtered) as its rootLayer.
38043795
// A failed filter does not paint its descendants, so a rootLayer with hasFilter() set up a buffer.
38053796
CheckedPtr currentRootLayer = localPaintingInfo.rootLayer;
38063797
bool rootLayerShiftedFilterBuffer = currentRootLayer && currentRootLayer != this
3807-
&& currentRootLayer->hasFilter() && currentRootLayer->renderer().isSVGLayerAwareRenderer();
3808-
bool appliesFilterChildCorrection = filterContext && renderer().isSVGLayerAwareRenderer()
3798+
&& currentRootLayer->hasFilter() && currentRootLayer->renderer().isSVGLayerAwareRenderer()
3799+
&& !currentRootLayer->renderer().isRenderSVGRoot();
3800+
bool appliesFilterChildCorrection = filterContext && filtersInSVGUserSpace
38093801
&& (currentRootLayer == this || rootLayerShiftedFilterBuffer);
38103802

38113803
// This applies to this layer's immediate child layers only, and a nested filter re-derives its own.
@@ -3933,7 +3925,7 @@ void RenderLayer::paintLayerContents(GraphicsContext& context, const LayerPainti
39333925
// element is its own rootLayer, or a force-created ancestor layer baked it in). In the plain
39343926
// non-layer case offsetFromRoot equals the nominal position and isZero() skips.
39353927
GraphicsContextStateSaver filterCompensationSaver(context, false);
3936-
if (renderer().isSVGLayerAwareRenderer()) {
3928+
if (filtersInSVGUserSpace) {
39373929
auto svgFilterCompensation = columnAwareOffsetFromRoot - svgFilterOffset;
39383930
if (!svgFilterCompensation.isZero()) {
39393931
filterCompensationSaver.save();

‎Source/WebCore/rendering/RenderLayerFilters.cpp‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@ GraphicsContext* RenderLayerFilters::beginFilterEffect(RenderElement& renderer,
169169
// LBSE seeds the filter region with objectBoundingBox, expandFilterRegionForSVGReferences()
170170
// grows it to cover <filter> reference regions. The caller passes the nominal SVG position
171171
// as the offset, so the buffer shares the SVG coordinate space and needs no shift compensation.
172-
if (renderer.isSVGLayerAwareRenderer()) {
172+
bool usesSVGUserSpace = renderer.isSVGLayerAwareRenderer() && !renderer.isRenderSVGRoot();
173+
if (usesSVGUserSpace) {
173174
auto boundingBox = renderer.objectBoundingBox();
174175
filterRegion = dirtyFilterRegion = enclosingLayoutRect(boundingBox);
175176
} else {
@@ -189,7 +190,7 @@ GraphicsContext* RenderLayerFilters::beginFilterEffect(RenderElement& renderer,
189190
filterRegion.expand(toLayoutBoxExtent(outsets));
190191
}
191192

192-
if (filterRegion.isEmpty() && !renderer.isSVGLayerAwareRenderer())
193+
if (filterRegion.isEmpty() && !usesSVGUserSpace)
193194
return nullptr;
194195

195196
auto geometryReferenceGeometryChanged = [](auto& existingGeometry, auto& newGeometry) {
@@ -199,11 +200,11 @@ GraphicsContext* RenderLayerFilters::beginFilterEffect(RenderElement& renderer,
199200
// The reference box must stay in the original SVG coordinate system (objectBoundingBox,
200201
// as legacy does) with no shift, otherwise filter region resolution is wrong.
201202
auto referenceBox = filterBoxRect;
202-
if (renderer.isSVGLayerAwareRenderer())
203+
if (usesSVGUserSpace)
203204
referenceBox = enclosingLayoutRect(renderer.objectBoundingBox());
204205

205206
auto filterScale = m_filterScale;
206-
if (renderer.isSVGLayerAwareRenderer())
207+
if (usesSVGUserSpace)
207208
filterScale = m_filterScale * SVGTransformComputation(downcast<RenderLayerModelObject>(renderer)).calculateAccumulatedSVGAncestorTransformScale();
208209

209210
auto geometry = FilterGeometry {
@@ -221,19 +222,19 @@ GraphicsContext* RenderLayerFilters::beginFilterEffect(RenderElement& renderer,
221222
renderingOptions.add(FilterRenderingOption::ShowDebugOverlay);
222223
if (paintBehavior.contains(PaintBehavior::FastAndLowQualityFilters))
223224
renderingOptions.add(FilterRenderingOption::FastAndLowQuality);
224-
if (renderer.isSVGLayerAwareRenderer())
225+
if (usesSVGUserSpace)
225226
renderingOptions.add(FilterRenderingOption::ApplyToSVGRenderer);
226227
m_filter = CSSFilterRenderer::create(renderer, renderer.style().filter(), geometry, preferredFilterRenderingModes, renderingOptions, context);
227228
m_lastUnclampedFilterScale = filterScale;
228229
hasUpdatedBackingStore = true;
229-
} else if (!renderer.isSVGLayerAwareRenderer() && filterRegion != m_filter->filterRegion()) {
230+
} else if (!usesSVGUserSpace && filterRegion != m_filter->filterRegion()) {
230231
m_filter->setFilterRegion(filterRegion);
231232
hasUpdatedBackingStore = true;
232233
}
233234

234235
// Read back the region expandFilterRegionForSVGReferences() produced - it may have
235236
// grown to cover SVG reference regions and clamped the scale.
236-
if (renderer.isSVGLayerAwareRenderer() && m_filter) {
237+
if (usesSVGUserSpace && m_filter) {
237238
filterRegion = enclosingLayoutRect(m_filter->filterRegion());
238239
dirtyFilterRegion = filterRegion;
239240
}
@@ -258,7 +259,7 @@ GraphicsContext* RenderLayerFilters::beginFilterEffect(RenderElement& renderer,
258259

259260
if (!m_targetSwitcher || hasUpdatedBackingStore) {
260261
FloatRect sourceImageRect;
261-
if (renderer.isSVGLayerAwareRenderer()) {
262+
if (usesSVGUserSpace) {
262263
// If the region was clamped to MaxClampedArea, source from the bounding box to
263264
// avoid a huge mostly-transparent buffer. Otherwise source the full region.
264265
bool filterRegionOversized = !referenceBox.isEmpty() && m_filter->filterScale() != m_lastUnclampedFilterScale;
@@ -270,7 +271,7 @@ GraphicsContext* RenderLayerFilters::beginFilterEffect(RenderElement& renderer,
270271
// operations should happen in linear color space. Match legacy SVG filter behavior.
271272
auto colorSpace = DestinationColorSpace::SRGB();
272273
#if !USE(CAIRO)
273-
if (renderer.isSVGLayerAwareRenderer())
274+
if (usesSVGUserSpace)
274275
colorSpace = DestinationColorSpace::LinearSRGB();
275276
#endif
276277

@@ -294,7 +295,7 @@ void RenderLayerFilters::applyFilterEffect(GraphicsContext& destinationContext)
294295
auto colorSpace = DestinationColorSpace::SRGB();
295296
#if !USE(CAIRO)
296297
if (CheckedPtr layer = m_layer.get()) {
297-
if (layer->renderer().isSVGLayerAwareRenderer())
298+
if (layer->renderer().isSVGLayerAwareRenderer() && !layer->renderer().isRenderSVGRoot())
298299
colorSpace = DestinationColorSpace::LinearSRGB();
299300
}
300301
#endif

0 commit comments

Comments
 (0)