Bug 1520652 - Clip SVG filters to the PrimitiveSubregion when using WebRender. r=mstange

This patch changes a few things:
* Restores clipping to the computed clip, but just for SVG filters.
* Computes the clip just from the primitive subregion, not the bounds of the filtered content.
* Unconditionally clips all SVG filters using the primitive subregion
* Allows clips to be combined, if they will be sharing a coordinate space
* Fixes coordinate space of the clip region.

Differential Revision: https://phabricator.services.mozilla.com/D16941

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Matt Woodrow 2019-01-19 06:35:16 +00:00
parent b911cbf521
commit a79e573d49
8 changed files with 46 additions and 72 deletions

View File

@ -9350,29 +9350,34 @@ bool nsDisplayFilters::CreateWebRenderCommands(
const StackingContextHelper& aSc,
mozilla::layers::RenderRootStateManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) {
bool snap;
float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
nsRect displayBounds = GetBounds(aDisplayListBuilder, &snap);
auto postFilterBounds = LayoutDeviceIntRect::Round(
LayoutDeviceRect::FromAppUnits(displayBounds, auPerDevPixel));
auto preFilterBounds = LayoutDeviceIntRect::Round(
LayoutDeviceRect::FromAppUnits(mBounds, auPerDevPixel));
nsTArray<mozilla::wr::FilterOp> wrFilters;
Maybe<nsRect> filterClip;
if (!CreateWebRenderCSSFilters(wrFilters) &&
!nsSVGIntegrationUtils::BuildWebRenderFilters(
mFrame, preFilterBounds, wrFilters, postFilterBounds)) {
!nsSVGIntegrationUtils::BuildWebRenderFilters(mFrame, wrFilters,
filterClip)) {
return false;
}
wr::WrStackingContextClip clip;
if (filterClip) {
auto devPxRect = LayoutDeviceRect::FromAppUnits(
filterClip.value() + ToReferenceFrame(), auPerDevPixel);
wr::WrClipId clipId =
aBuilder.DefineClip(Nothing(), wr::ToRoundedLayoutRect(devPxRect));
clip = wr::WrStackingContextClip::ClipId(clipId);
} else {
clip = wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
}
float opacity = mFrame->StyleEffects()->mOpacity;
StackingContextHelper sc(
aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, wrFilters,
LayoutDeviceRect(), nullptr, nullptr,
opacity != 1.0f && mHandleOpacity ? &opacity : nullptr, nullptr,
wr::ReferenceFrameKind::Transform, gfx::CompositionOp::OP_OVER, true,
false, Nothing(),
wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()));
false, Nothing(), clip);
nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, sc,
aManager, aDisplayListBuilder);

View File

@ -2,6 +2,6 @@
# e.g. filter: blur(3px) grayscale(0.5) invert(0.2);
# Some platforms render this complex filter chain a little differently, and that's ok.
fuzzy(0-5,0-13638) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)&&layersGPUAccelerated,0-35,0-13638) fuzzy-if(webrender,4-6,12000-18853) == long-chain.html long-chain-ref.html # Win10: Bug 1258241
fuzzy(0-5,0-13638) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)&&layersGPUAccelerated,0-35,0-13638) fuzzy-if(webrender,4-6,12000-18946) == long-chain.html long-chain-ref.html # Win10: Bug 1258241
== moz-element.html moz-element-ref.html
== same-filter.html same-filter-ref.html
fuzzy-if(webrender,13-15,7682-7966) == same-filter.html same-filter-ref.html

View File

@ -6,7 +6,7 @@
== blur-calc.html blur-calc-ref.html
== blur-calc-negative.html blur-calc-negative-ref.html
fuzzy-if(cocoaWidget&&webrender,0-1,0-2) skip-if(d2d) == blur-cap-large-radius-on-software.html blur-cap-large-radius-on-software-ref.html
fails-if(webrender) == blur-clip-rect.html ../feGaussianBlur-4-ref.svg
fuzzy-if(webrender,3-4,5760-6424) == blur-clip-rect.html ../feGaussianBlur-4-ref.svg
== blur-em-radius.html blur-em-radius-ref.html
== blur-invalid-radius.html blur-invalid-radius-ref.html
== blur-rem-radius.html blur-rem-radius-ref.html

View File

@ -3,6 +3,6 @@
# e.g. filter: url(#f1) blur(3px) url(#2) grayscale(0.5);
== clip-input-css-filter.html clip-input-css-filter-ref.html
== css-filter-first.html css-filter-first-ref.html
fuzzy-if(webrender,0-1,0-288) == css-filter-first.html css-filter-first-ref.html
== css-filter-last.html css-filter-last-ref.html
== css-filter-middle.html css-filter-middle-ref.html

View File

@ -93,10 +93,9 @@ void nsFilterInstance::PaintFilteredFrame(
}
}
bool nsFilterInstance::BuildWebRenderFilters(
nsIFrame* aFilteredFrame, const LayoutDeviceIntRect& aPreFilterBounds,
nsTArray<wr::FilterOp>& aWrFilters,
LayoutDeviceIntRect& aPostFilterBounds) {
bool nsFilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
nsTArray<wr::FilterOp>& aWrFilters,
Maybe<nsRect>& aPostFilterClip) {
aWrFilters.Clear();
auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
@ -121,7 +120,7 @@ bool nsFilterInstance::BuildWebRenderFilters(
return false;
}
Maybe<LayoutDeviceIntRect> finalClip;
Maybe<IntRect> finalClip;
bool srgb = true;
// We currently apply the clip on the stacking context after applying filters,
// but primitive subregions imply clipping after each filter and not just the
@ -132,17 +131,7 @@ bool nsFilterInstance::BuildWebRenderFilters(
// We can lift this restriction once we have added support for primitive
// subregions to WebRender's filters.
// During the loop this tracks whether any of the previous filters in the
// chain affected by the primitive subregion.
bool chainIsAffectedByPrimSubregion = false;
// During the loop this tracks whether the current filter is affected by the
// primitive subregion.
bool filterIsAffectedByPrimSubregion = false;
for (const auto& primitive : instance.mFilterDescription.mPrimitives) {
chainIsAffectedByPrimSubregion |= filterIsAffectedByPrimSubregion;
filterIsAffectedByPrimSubregion = false;
bool primIsSrgb = primitive.OutputColorSpace() == gfx::ColorSpace::SRGB;
if (srgb && !primIsSrgb) {
aWrFilters.AppendElement(wr::FilterOp::SrgbToLinear());
@ -153,26 +142,6 @@ bool nsFilterInstance::BuildWebRenderFilters(
}
const PrimitiveAttributes& attr = primitive.Attributes();
auto subregion = LayoutDeviceIntRect::FromUnknownRect(
primitive.PrimitiveSubregion() +
aPreFilterBounds.TopLeft().ToUnknownPoint());
if (!subregion.Contains(aPreFilterBounds)) {
if (!aPostFilterBounds.Contains(subregion)) {
filterIsAffectedByPrimSubregion = true;
}
subregion = subregion.Intersect(aPostFilterBounds);
if (finalClip.isNothing()) {
finalClip = Some(subregion);
} else if (!subregion.IsEqualEdges(finalClip.value())) {
// We don't currently support rendering a chain of filters with
// different primitive subregions in WebRender so bail out in that
// situation.
return false;
}
}
bool filterIsNoop = false;
@ -213,7 +182,7 @@ bool nsFilterInstance::BuildWebRenderFilters(
aWrFilters.AppendElement(wr::FilterOp::ColorMatrix(matrix));
} else if (attr.is<GaussianBlurAttributes>()) {
if (chainIsAffectedByPrimSubregion) {
if (finalClip) {
// There's a clip that needs to apply before the blur filter, but
// WebRender only lets us apply the clip at the end of the filter
// chain. Clipping after a blur is not equivalent to clipping before
@ -235,7 +204,7 @@ bool nsFilterInstance::BuildWebRenderFilters(
filterIsNoop = true;
}
} else if (attr.is<DropShadowAttributes>()) {
if (chainIsAffectedByPrimSubregion) {
if (finalClip) {
// We have to bail out for the same reason we would with a blur filter.
return false;
}
@ -271,18 +240,24 @@ bool nsFilterInstance::BuildWebRenderFilters(
Unused << aWrFilters.PopLastElement();
srgb = !srgb;
}
if (!filterIsNoop) {
if (finalClip.isNothing()) {
finalClip = Some(primitive.PrimitiveSubregion());
} else {
finalClip =
Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
}
}
}
if (!srgb) {
aWrFilters.AppendElement(wr::FilterOp::LinearToSrgb());
}
// Only adjust the post filter clip if we are able to render this without
// fallback.
if (finalClip.isSome()) {
aPostFilterBounds = finalClip.value();
if (finalClip) {
aPostFilterClip = Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
}
return true;
}

View File

@ -122,11 +122,9 @@ class nsFilterInstance {
* Try to build WebRender filters for a frame if the filters applied to it are
* supported.
*/
static bool BuildWebRenderFilters(
nsIFrame* aFilteredFrame,
const mozilla::LayoutDeviceIntRect& aPreFilterBounds,
nsTArray<mozilla::wr::FilterOp>& aWrFilters,
mozilla::LayoutDeviceIntRect& aPostFilterBounds);
static bool BuildWebRenderFilters(nsIFrame* aFilteredFrame,
nsTArray<mozilla::wr::FilterOp>& aWrFilters,
mozilla::Maybe<nsRect>& aPostFilterClip);
private:
/**

View File

@ -1086,12 +1086,10 @@ void nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams) {
}
bool nsSVGIntegrationUtils::BuildWebRenderFilters(
nsIFrame* aFilteredFrame,
const mozilla::LayoutDeviceIntRect& aPreFilterBounds,
nsTArray<mozilla::wr::FilterOp>& aWrFilters,
mozilla::LayoutDeviceIntRect& aPostFilterBounds) {
return nsFilterInstance::BuildWebRenderFilters(
aFilteredFrame, aPreFilterBounds, aWrFilters, aPostFilterBounds);
nsIFrame* aFilteredFrame, nsTArray<mozilla::wr::FilterOp>& aWrFilters,
Maybe<nsRect>& aPostFilterClip) {
return nsFilterInstance::BuildWebRenderFilters(aFilteredFrame, aWrFilters,
aPostFilterClip);
}
class PaintFrameCallback : public gfxDrawingCallback {

View File

@ -195,11 +195,9 @@ class nsSVGIntegrationUtils final {
* Try to build WebRender filters for a frame if the filters applied to it are
* supported.
*/
static bool BuildWebRenderFilters(
nsIFrame* aFilteredFrame,
const mozilla::LayoutDeviceIntRect& aPreFilterBounds,
nsTArray<mozilla::wr::FilterOp>& aWrFilters,
mozilla::LayoutDeviceIntRect& aPostFilterBounds);
static bool BuildWebRenderFilters(nsIFrame* aFilteredFrame,
nsTArray<mozilla::wr::FilterOp>& aWrFilters,
mozilla::Maybe<nsRect>& aPostFilterClip);
/**
* @param aRenderingContext the target rendering context in which the paint