Bug 1287492 - Part 3. (Main) Shrink mTargetBBoxInFilterSpace
Clip mTargetBBoxInFilterSpace by the bounds of parent SVG frame. Fix this bug and
good for both rendering performance and memory consumption.
The root cause of this bug
<svg width="100" height="100" style="filter: opacity(100%);">
<g transform="matrix(200,0,0,200,-20000,-20000)">
<rect width="200" height="200" style="fill:lime">
</g>
</svg>
In this example, <rect> is going to be a huge graphic object because of the scale
transform in <g>. The bounding-box of <svg> is an union of all descedants, so the
size of mTargetBBoxInFilterSpace is huage too. We are not able to create such a
huge surface because of the limitation at
nsFilterInstance::OutputFilterSpaceBounds[1].
[1] https://hg.mozilla.org/mozilla-central/file/f4f6790e3926/layout/svg/nsFilterInstance.cpp#l556
MozReview-Commit-ID: 4Fdj5mgcE0V
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -181,30 +181,25 @@ nsFilterInstance::nsFilterInstance(nsIFr
if (aOverrideBBox) {
mTargetBBox = *aOverrideBBox;
} else {
MOZ_ASSERT(mTargetFrame, "Need to supply a frame when there's no aOverrideBBox");
mTargetBBox = nsSVGUtils::GetBBox(mTargetFrame);
}
// Compute user space to filter space transforms.
- nsresult rv = ComputeUserSpaceToFilterSpaceScale();
- if (NS_FAILED(rv)) {
+ if (!ComputeUserSpaceToFilterSpaceScale()) {
return;
}
- gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
- targetBBoxInFilterSpace.RoundOut();
- if (!gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace, &mTargetBBoxInFilterSpace)) {
- // The target's bbox is way too big if there is float->int overflow.
+ if (!ComputeTargetBBoxInFilterSpace()) {
return;
}
// Get various transforms:
-
gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f,
0.0f, mFilterSpaceToUserSpaceScale.height,
0.0f, 0.0f);
mFilterSpaceToFrameSpaceInCSSPxTransform =
filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
// mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
mFrameSpaceInCSSPxToFilterSpaceTransform =
@@ -217,47 +212,79 @@ nsFilterInstance::nsFilterInstance(nsIFr
FrameSpaceToFilterSpace(aPreFilterVisualOverflowRectOverride);
} else if (mTargetFrame) {
nsRect preFilterVOR = mTargetFrame->GetPreEffectsVisualOverflowRect();
targetBounds = FrameSpaceToFilterSpace(&preFilterVOR);
}
mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
// Build the filter graph.
- rv = BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted);
- if (NS_FAILED(rv)) {
+ if (NS_FAILED(BuildPrimitives(aFilterChain, aTargetFrame,
+ aFilterInputIsTainted))) {
return;
}
// Convert the passed in rects from frame space to filter space:
mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
mInitialized = true;
}
-nsresult
+bool
+nsFilterInstance::ComputeTargetBBoxInFilterSpace()
+{
+ gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
+ targetBBoxInFilterSpace.RoundOut();
+ if (!gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace,
+ &mTargetBBoxInFilterSpace)) {
+ // The target's bbox is way too big if there is float->int overflow.
+ return false;
+ }
+
+ if (!mTargetFrame || !mTargetFrame->IsFrameOfType(nsIFrame::eSVG)) {
+ return true;
+ }
+
+ // SVG graphic elements will always be clipped by svg::svg element, so we
+ // should clip mTargetBBoxInFilterSpace by the bounded parent SVG frame
+ // anyway to shrink the size of surface that we are going to create later in
+ // BuildSourcePaint and BuildSourceImage.
+ MOZ_ASSERT(mTargetFrame->IsFrameOfType(nsIFrame::eSVG));
+ nsIFrame* svgFrame = nsSVGUtils::GetNearestSVGParent(mTargetFrame);
+ if (svgFrame) {
+ nscoord A2D = svgFrame->PresContext()->AppUnitsPerCSSPixel();
+ nsIntRect bounds =
+ svgFrame->GetVisualOverflowRect().ToOutsidePixels(A2D);
+
+ mTargetBBoxInFilterSpace = mTargetBBoxInFilterSpace.Intersect(bounds);
+ }
+
+ return true;
+}
+
+bool
nsFilterInstance::ComputeUserSpaceToFilterSpaceScale()
{
gfxMatrix canvasTransform;
if (mTargetFrame) {
mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(true);
if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
mUserSpaceToFilterSpaceScale.height <= 0.0f) {
// Nothing should be rendered.
- return NS_ERROR_FAILURE;
+ return false;
}
} else {
mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
}
mFilterSpaceToUserSpaceScale =
gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
1.0f / mUserSpaceToFilterSpaceScale.height);
- return NS_OK;
+ return true;
}
gfxRect
nsFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const
{
gfxRect filterSpaceRect = aUserSpaceRect;
filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
mUserSpaceToFilterSpaceScale.height);
--- a/layout/svg/nsFilterInstance.h
+++ b/layout/svg/nsFilterInstance.h
@@ -265,17 +265,17 @@ private:
/**
* Returns the output bounds of the final FilterPrimitiveDescription.
*/
nsIntRect OutputFilterSpaceBounds() const;
/**
* Compute the scale factors between user space and filter space.
*/
- nsresult ComputeUserSpaceToFilterSpaceScale();
+ bool ComputeUserSpaceToFilterSpaceScale();
/**
* Transform a rect between user space and filter space.
*/
gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpace) const;
gfxRect FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const;
/**
@@ -298,16 +298,18 @@ private:
/**
* Returns the transform from frame space to the coordinate space that
* GetCanvasTM transforms to. "Frame space" is the origin of a frame, aka the
* top-left corner of its border box, aka the top left corner of its mRect.
*/
gfxMatrix GetUserSpaceToFrameSpaceInCSSPxTransform() const;
+ bool ComputeTargetBBoxInFilterSpace();
+
/**
* The frame for the element that is currently being filtered.
*/
nsIFrame* mTargetFrame;
/**
* The filtered element.
*/