Bug 738192 - Get rid of the old horrendous "invalidate everything" hack in nsSVGUtils::FindFilterInvalidation(). r=longsonr.

--HG--
extra : rebase_source : 5c26b1320e1a0613c1b71b7c12c86db83316dcdd
This commit is contained in:
Jonathan Watt 2012-06-24 13:59:26 +01:00
parent 54f5a211be
commit 25d4e26503
8 changed files with 116 additions and 137 deletions

View File

@ -15,6 +15,7 @@
#include "nsSVGFilterElement.h"
#include "nsSVGFilterInstance.h"
#include "nsSVGFilterPaintCallback.h"
#include "nsSVGIntegrationUtils.h"
#include "nsSVGUtils.h"
nsIFrame*
@ -25,6 +26,10 @@ NS_NewSVGFilterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
NS_IMPL_FRAMEARENA_HELPERS(nsSVGFilterFrame)
/**
* Returns the entire filter size if aDeviceRect is null, or if
* the result is too large to be stored in an nsIntRect.
*/
static nsIntRect
MapDeviceRectToFilterSpace(const gfxMatrix& aMatrix,
const gfxIntSize& aFilterSize,
@ -404,6 +409,10 @@ nsSVGFilterFrame::PaintFilteredFrame(nsRenderingContext *aContext,
return rv;
}
/**
* Returns NS_ERROR_FAILURE if the result is too large to be stored
* in an nsIntRect.
*/
static nsresult
TransformFilterSpaceToDeviceSpace(nsSVGFilterInstance *aInstance,
nsIntRect *aRect)
@ -423,8 +432,21 @@ nsIntRect
nsSVGFilterFrame::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
const nsIntRect& aPreFilterDirtyRect)
{
bool overrideCTM = false;
gfxMatrix ctm;
if (aFilteredFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
// In the case of this method we want to input a rect that is relative to
// aFilteredFrame and get back a rect that is relative to aFilteredFrame.
// To do that we need to provide an override canvanTM to prevent the filter
// code from calling GetCanvasTM and using that TM as normal.
overrideCTM = true;
ctm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
}
nsAutoFilterInstance instance(aFilteredFrame, this, nsnull, nsnull,
&aPreFilterDirtyRect, nsnull);
&aPreFilterDirtyRect, nsnull,
overrideCTM ? &ctm : nsnull);
if (!instance.get())
return nsIntRect();
@ -478,11 +500,7 @@ nsSVGFilterFrame::GetPostFilterBounds(nsIFrame *aFilteredFrame,
// rect relative to aTarget itself. For that we need to prevent the filter
// code using GetCanvasTM().
overrideCTM = true;
PRInt32 appUnitsPerDevPixel =
aFilteredFrame->PresContext()->AppUnitsPerDevPixel();
float devPxPerCSSPx =
1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
ctm.Scale(devPxPerCSSPx, devPxPerCSSPx);
ctm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
}
nsAutoFilterInstance instance(aFilteredFrame, this, nsnull, nsnull,

View File

@ -175,7 +175,7 @@ nsSVGForeignObjectFrame::InvalidateInternal(const nsRect& aDamageRect,
if (!mInReflow) {
// We can't collect dirty areas, since we don't have a place to reliably
// call FlushDirtyRegion before we paint, so we have to invalidate now.
InvalidateDirtyRect(nsSVGUtils::GetOuterSVGFrame(this), aDamageRect + nsPoint(aX, aY), aFlags);
InvalidateDirtyRect(aDamageRect + nsPoint(aX, aY), aFlags, false);
return;
}
@ -578,13 +578,16 @@ nsSVGForeignObjectFrame::DoReflow()
mInReflow = false;
if (!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
FlushDirtyRegion(0);
// Since we're a reflow root and can be reflowed independently of our
// outer-<svg>, we can't just blindly pass 'true' here.
FlushDirtyRegion(0, nsSVGUtils::OuterSVGIsCallingUpdateBounds(this));
}
}
void
nsSVGForeignObjectFrame::InvalidateDirtyRect(nsSVGOuterSVGFrame* aOuter,
const nsRect& aRect, PRUint32 aFlags)
nsSVGForeignObjectFrame::InvalidateDirtyRect(const nsRect& aRect,
PRUint32 aFlags,
bool aDuringReflowSVG)
{
if (aRect.IsEmpty())
return;
@ -594,19 +597,12 @@ nsSVGForeignObjectFrame::InvalidateDirtyRect(nsSVGOuterSVGFrame* aOuter,
if (rect.IsEmpty())
return;
// The areas dirtied by children are in app units, relative to this frame.
// We need to convert the rect from app units in our userspace to app units
// relative to our nsSVGOuterSVGFrame's content rect.
gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel());
rect = ToCanvasBounds(r, GetCanvasTM(), PresContext());
rect = nsSVGUtils::FindFilterInvalidation(this, rect);
aOuter->InvalidateWithFlags(rect, aFlags);
nsSVGUtils::InvalidateBounds(this, aDuringReflowSVG, &rect, aFlags);
}
void
nsSVGForeignObjectFrame::FlushDirtyRegion(PRUint32 aFlags)
nsSVGForeignObjectFrame::FlushDirtyRegion(PRUint32 aFlags,
bool aDuringReflowSVG)
{
NS_ABORT_IF_FALSE(!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD),
"Should not have been called");
@ -618,15 +614,12 @@ nsSVGForeignObjectFrame::FlushDirtyRegion(PRUint32 aFlags)
return;
}
nsSVGOuterSVGFrame *outerSVGFrame = nsSVGUtils::GetOuterSVGFrame(this);
if (!outerSVGFrame) {
NS_ERROR("null outerSVGFrame");
return;
}
InvalidateDirtyRect(outerSVGFrame, mSameDocDirtyRegion.GetBounds(), aFlags);
InvalidateDirtyRect(outerSVGFrame, mSubDocDirtyRegion.GetBounds(),
aFlags | INVALIDATE_CROSS_DOC);
InvalidateDirtyRect(mSameDocDirtyRegion.GetBounds(),
aFlags,
aDuringReflowSVG);
InvalidateDirtyRect(mSubDocDirtyRegion.GetBounds(),
aFlags | INVALIDATE_CROSS_DOC,
aDuringReflowSVG);
mSameDocDirtyRegion.SetEmpty();
mSubDocDirtyRegion.SetEmpty();

View File

@ -93,9 +93,9 @@ protected:
// Returns GetCanvasTM followed by a scale from CSS px to Dev px. Used for
// painting, because children expect to paint to device space, not userspace.
gfxMatrix GetCanvasTMForChildren();
void InvalidateDirtyRect(nsSVGOuterSVGFrame* aOuter,
const nsRect& aRect, PRUint32 aFlags);
void FlushDirtyRegion(PRUint32 aFlags);
void InvalidateDirtyRect(const nsRect& aRect, PRUint32 aFlags,
bool aDuringReflowSVG);
void FlushDirtyRegion(PRUint32 aFlags, bool aDuringReflowSVG);
// If width or height is less than or equal to zero we must disable rendering
bool IsDisabled() const { return mRect.width <= 0 || mRect.height <= 0; }

View File

@ -494,8 +494,6 @@ nsSVGIntegrationUtils::PaintFramesWithEffects(nsRenderingContext* aCtx,
gfxMatrix
nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame)
{
NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
"SVG frames should not get here");
PRInt32 appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
float devPxPerCSSPx =
1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);

View File

@ -396,9 +396,7 @@ nsSVGOuterSVGFrame::Reflow(nsPresContext* aPresContext,
// Now that we've marked the necessary children as dirty, call
// UpdateBounds() on them:
#ifdef DEBUG
mCallingUpdateBounds = true;
#endif
if (!(mState & NS_STATE_SVG_NONDISPLAY_CHILD)) {
nsIFrame* kid = mFrames.FirstChild();
@ -411,9 +409,7 @@ nsSVGOuterSVGFrame::Reflow(nsPresContext* aPresContext,
}
}
#ifdef DEBUG
mCallingUpdateBounds = false;
#endif
// Make sure we scroll if we're too big:
// XXX Use the bounding box of our descendants? (See bug 353460 comment 14.)

View File

@ -111,17 +111,13 @@ public:
*/
bool VerticalScrollbarNotNeeded() const;
#ifdef DEBUG
bool IsCallingUpdateBounds() const {
return mCallingUpdateBounds;
}
#endif
protected:
#ifdef DEBUG
bool mCallingUpdateBounds;
#endif
/* Returns true if our content is the document element and our document is
* embedded in an HTML 'object', 'embed' or 'applet' element. Set

View File

@ -19,6 +19,7 @@
#include "nsCSSFrameConstructor.h"
#include "nsComputedDOMStyle.h"
#include "nsContentUtils.h"
#include "nsDisplayList.h"
#include "nsFrameList.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
@ -598,80 +599,15 @@ nsSVGUtils::GetPostFilterVisualOverflowRect(nsIFrame *aFrame,
return r;
}
nsRect
nsSVGUtils::FindFilterInvalidation(nsIFrame *aFrame, const nsRect& aRect)
{
PRInt32 appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
nsIntRect rect = aRect.ToOutsidePixels(appUnitsPerDevPixel);
while (aFrame) {
if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)
break;
nsSVGFilterFrame *filter = nsSVGEffects::GetFilterFrame(aFrame);
if (filter) {
// When we are under AttributeChanged, we can no longer get the old bbox
// by calling GetBBox(), and we need that to set up the filter region
// with the correct position. :-(
//rect = filter->GetInvalidationBBox(aFrame, rect);
// XXX [perf] As a horrible workaround, for now we just invalidate the
// entire area of the nearest viewport establishing frame that doesnt
// have overflow:visible. See bug 463939.
nsSVGDisplayContainerFrame* viewportFrame = GetNearestSVGViewport(aFrame);
while (viewportFrame && !viewportFrame->GetStyleDisplay()->IsScrollableOverflow()) {
viewportFrame = GetNearestSVGViewport(viewportFrame);
}
if (!viewportFrame) {
viewportFrame = GetOuterSVGFrame(aFrame);
}
if (viewportFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) {
nsRect r = viewportFrame->GetVisualOverflowRect();
// GetOverflowRect is relative to our border box, but we need it
// relative to our content box.
r.MoveBy(viewportFrame->GetPosition() - viewportFrame->GetContentRect().TopLeft());
return r;
}
NS_ASSERTION(viewportFrame->GetType() == nsGkAtoms::svgInnerSVGFrame,
"Wrong frame type");
nsSVGInnerSVGFrame* innerSvg = do_QueryFrame(static_cast<nsIFrame*>(viewportFrame));
nsSVGDisplayContainerFrame* innerSvgParent = do_QueryFrame(viewportFrame->GetParent());
float x, y, width, height;
static_cast<nsSVGSVGElement*>(innerSvg->GetContent())->
GetAnimatedLengthValues(&x, &y, &width, &height, nsnull);
gfxRect bounds = GetCanvasTM(innerSvgParent).
TransformBounds(gfxRect(x, y, width, height));
bounds.RoundOut();
nsIntRect r;
if (gfxUtils::GfxRectToIntRect(bounds, &r)) {
rect = r;
} else {
NS_NOTREACHED("Not going to invalidate the correct area");
}
aFrame = viewportFrame;
}
aFrame = aFrame->GetParent();
}
nsRect r = rect.ToAppUnits(appUnitsPerDevPixel);
if (aFrame) {
NS_ASSERTION(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG,
"outer SVG frame expected");
r.MoveBy(aFrame->GetContentRect().TopLeft() - aFrame->GetPosition());
}
return r;
}
#ifdef DEBUG
bool
nsSVGUtils::OuterSVGIsCallingUpdateBounds(nsIFrame *aFrame)
{
return nsSVGUtils::GetOuterSVGFrame(aFrame)->IsCallingUpdateBounds();
}
#endif
void
nsSVGUtils::InvalidateBounds(nsIFrame *aFrame, bool aDuringUpdate)
nsSVGUtils::InvalidateBounds(nsIFrame *aFrame, bool aDuringUpdate,
const nsRect *aBoundsSubArea, PRUint32 aFlags)
{
NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG),
"Passed bad frame!");
@ -707,23 +643,70 @@ nsSVGUtils::InvalidateBounds(nsIFrame *aFrame, bool aDuringUpdate)
return;
}
// XXXSDL we want to reduce the bounds when passing through inner-<svg>
// and <use>, etc.
// Okay, so now we pass the area that needs to be invalidated up our parent
// chain, accounting for filter effects and transforms as we go, until we
// reach our nsSVGOuterSVGFrame where we can invalidate:
nsSVGOuterSVGFrame* outerSVGFrame = GetOuterSVGFrame(aFrame);
NS_ASSERTION(outerSVGFrame, "no outer svg frame");
if (outerSVGFrame) {
nsISVGChildFrame *svgFrame = do_QueryFrame(aFrame);
if (!svgFrame)
return;
// Note that filters can paint even if the element being filtered has empty
// bounds, so we don't return early for that. Specifically, filters can be
// given an explicit size that doesn't depend on the bbox of the element
// being filtered, and then feFlood can be used to fill that area with paint.
nsRect rect = FindFilterInvalidation(aFrame, svgFrame->GetCoveredRegion());
outerSVGFrame->Invalidate(rect);
nsRect invalidArea;
if (aBoundsSubArea) {
invalidArea = *aBoundsSubArea;
} else {
invalidArea = aFrame->GetVisualOverflowRect();
// GetVisualOverflowRect() already includes filter effects and transforms,
// so advance to our parent before the loop below:
invalidArea += aFrame->GetPosition();
aFrame = aFrame->GetParent();
}
PRInt32 appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
PRInt32 appUnitsPerCSSPx = aFrame->PresContext()->AppUnitsPerCSSPixel();
while (aFrame) {
if ((aFrame->GetStateBits() & NS_FRAME_IS_DIRTY)) {
// This ancestor frame has already been invalidated, so nothing to do.
return;
}
if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) {
break;
}
if (aFrame->GetType() == nsGkAtoms::svgInnerSVGFrame &&
aFrame->GetStyleDisplay()->IsScrollableOverflow()) {
// Clip rect to the viewport established by this inner-<svg>:
float x, y, width, height;
static_cast<nsSVGSVGElement*>(aFrame->GetContent())->
GetAnimatedLengthValues(&x, &y, &width, &height, nsnull);
if (width <= 0.0f || height <= 0.0f) {
return; // Nothing to invalidate
}
nsRect viewportRect =
nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(0.0, 0.0, width, height),
appUnitsPerCSSPx);
invalidArea = invalidArea.Intersect(viewportRect);
if (invalidArea.IsEmpty()) {
return; // Nothing to invalidate
}
}
nsSVGFilterFrame *filterFrame = nsSVGEffects::GetFilterFrame(aFrame);
if (filterFrame) {
invalidArea =
filterFrame->GetPostFilterDirtyArea(aFrame,
invalidArea.ToOutsidePixels(appUnitsPerDevPixel)).
ToAppUnits(appUnitsPerDevPixel);
}
if (aFrame->IsTransformed()) {
invalidArea =
nsDisplayTransform::TransformRect(invalidArea, aFrame, nsPoint(0, 0));
}
invalidArea += aFrame->GetPosition();
aFrame = aFrame->GetParent();
}
NS_ASSERTION(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG,
"SVG frames must always have an nsSVGOuterSVGFrame ancestor!");
invalidArea.MoveBy(aFrame->GetContentRect().TopLeft() - aFrame->GetPosition());
static_cast<nsSVGOuterSVGFrame*>(aFrame)->InvalidateWithFlags(invalidArea,
aFlags);
}
void

View File

@ -327,25 +327,20 @@ public:
static nsRect GetPostFilterVisualOverflowRect(nsIFrame *aFrame,
const nsRect &aUnfilteredRect);
/**
* Figures out the worst case invalidation area for a frame, taking
* filters into account.
* Note that the caller is responsible for making sure that any cached
* covered regions in the frame tree rooted at aFrame are up to date.
* @param aRect the area in app units that needs to be invalidated in aFrame
* @return the rect in app units that should be invalidated, taking
* filters into account. Will return aRect when no filters are present.
*/
static nsRect FindFilterInvalidation(nsIFrame *aFrame, const nsRect& aRect);
/**
* Invalidates the area that is painted by the frame without updating its
* bounds.
*
* This is similar to InvalidateOverflowRect(). It will go away when we
* support display list based invalidation of SVG.
*
* @param aBoundsSubArea If non-null, a sub-area of aFrame's pre-filter
* visual overflow rect that should be invalidated instead of aFrame's
* entire visual overflow rect.
*/
static void InvalidateBounds(nsIFrame *aFrame, bool aDuringUpdate = false);
static void InvalidateBounds(nsIFrame *aFrame, bool aDuringUpdate = false,
const nsRect *aBoundsSubArea = nsnull,
PRUint32 aFlags = 0);
/**
* Schedules an update of the frame's bounds (which will in turn invalidate
@ -621,9 +616,9 @@ public:
#ifdef DEBUG
static void
WritePPM(const char *fname, gfxImageSurface *aSurface);
#endif
static bool OuterSVGIsCallingUpdateBounds(nsIFrame *aFrame);
#endif
/*
* Get any additional transforms that apply only to stroking