Bug 243726. Make Invalidate/UpdateView *much* cheaper by delaying propagating damage over the widget tree until EndUpdateViewBatch. Also adds SimplifyInward/SimplifyOutward methods to nsRegion to avoid region complexity getting out of hand and killing our performance. r=Dainis_Jonitis,bzbarsky, sr=bzbarsky

This commit is contained in:
roc+%cs.cmu.edu 2004-11-03 02:37:21 +00:00
parent e3a829ac3e
commit 767022e55b
8 changed files with 154 additions and 103 deletions

View File

@ -44,7 +44,7 @@
// Rectangles in this list do not overlap and are sorted by (y, x) coordinates.
#include "nsRect.h"
#include "nsPoint.h"
class NS_GFX nsRegion
{
@ -155,7 +155,11 @@ public:
}
void MoveBy (PRInt32 aXOffset, PRInt32 aYOffset);
void MoveBy (PRInt32 aXOffset, PRInt32 aYOffset)
{
MoveBy (nsPoint (aXOffset, aYOffset));
}
void MoveBy (nsPoint aPt);
void SetEmpty ()
{
SetToElements (0);
@ -168,6 +172,19 @@ public:
PRUint32 GetNumRects () const { return mRectCount; }
const nsRect& GetBounds () const { return mBoundRect; }
/**
* Make sure the region has at most aMaxRects by adding area to it
* if necessary. The simplified region will be a superset of the
* original region. The simplified region's bounding box will be
* the same as for the current region.
*/
void SimplifyOutward (PRUint32 aMaxRects);
/**
* Make sure the region has at most aMaxRects by removing area from
* it if necessary. The simplified region will be a subset of the
* original region.
*/
void SimplifyInward (PRUint32 aMaxRects);
private:
PRUint32 mRectCount;

View File

@ -1223,18 +1223,38 @@ PRBool nsRegion::IsEqual (const nsRegion& aRegion) const
}
void nsRegion::MoveBy (PRInt32 aXOffset, PRInt32 aYOffset)
void nsRegion::MoveBy (nsPoint aPt)
{
if (aXOffset || aYOffset)
if (aPt.x || aPt.y)
{
RgnRect* pRect = mRectListHead.next;
while (pRect != &mRectListHead)
{
pRect->MoveBy (aXOffset, aYOffset);
pRect->MoveBy (aPt.x, aPt.y);
pRect = pRect->next;
}
mBoundRect.MoveBy (aXOffset, aYOffset);
mBoundRect.MoveBy (aPt.x, aPt.y);
}
}
void nsRegion::SimplifyOutward (PRUint32 aMaxRects)
{
NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count");
if (mRectCount <= aMaxRects)
return;
*this = GetBounds();
}
void nsRegion::SimplifyInward (PRUint32 aMaxRects)
{
NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count");
if (mRectCount <= aMaxRects)
return;
SetEmpty();
}

View File

@ -473,15 +473,19 @@ PRBool nsScrollPortView::CannotBitBlt(nsView* aScrolledView)
}
void nsScrollPortView::Scroll(nsView *aScrolledView, PRInt32 aDx, PRInt32 aDy, float scale, PRUint32 aUpdateFlags)
void nsScrollPortView::Scroll(nsView *aScrolledView, nsPoint aTwipsDelta, nsPoint aPixDelta,
float aT2P)
{
if ((aDx != 0) || (aDy != 0))
if (aTwipsDelta.x != 0 || aTwipsDelta.y != 0)
{
// Since we keep track of the dirty region as absolute screen coordintes,
// Since we keep track of the dirty region as absolute coordinates,
// we need to offset it by the amount we scrolled.
nsCOMPtr<nsIRegion> dirtyRegion;
GetDirtyRegion(*getter_AddRefs(dirtyRegion));
dirtyRegion->Offset(aDx, aDy);
if (HasNonEmptyDirtyRegion()) {
nsRegion* rgn = GetDirtyRegion();
if (rgn) {
rgn->MoveBy(aTwipsDelta);
}
}
nsIWidget *scrollWidget = GetWidget();
@ -494,7 +498,7 @@ void nsScrollPortView::Scroll(nsView *aScrolledView, PRInt32 aDx, PRInt32 aDy, f
// may include area that's not supposed to be scrolled. We need
// to invalidate to ensure that any such area is properly
// repainted back to the right rendering.
AdjustChildWidgets(aScrolledView, offsetToWidget, scale, PR_TRUE);
AdjustChildWidgets(aScrolledView, offsetToWidget, aT2P, PR_TRUE);
// If we don't have a scroll widget then we must just update.
// We should call this after fixing up the widget positions to be
// consistent with the view hierarchy.
@ -506,15 +510,15 @@ void nsScrollPortView::Scroll(nsView *aScrolledView, PRInt32 aDx, PRInt32 aDy, f
nsRect bounds(GetBounds());
nsPoint topLeft(bounds.x, bounds.y);
AdjustChildWidgets(aScrolledView,
GetPosition() - topLeft, scale, PR_FALSE);
GetPosition() - topLeft, aT2P, PR_FALSE);
// We should call this after fixing up the widget positions to be
// consistent with the view hierarchy.
mViewManager->UpdateView(this, 0);
} else { // if we can blit and have a scrollwidget then scroll.
// Scroll the contents of the widget by the specfied amount, and scroll
// the child widgets
scrollWidget->Scroll(aDx, aDy, nsnull);
mViewManager->UpdateViewAfterScroll(this, aDx, aDy);
scrollWidget->Scroll(aPixDelta.x, aPixDelta.y, nsnull);
mViewManager->UpdateViewAfterScroll(this, aTwipsDelta.x, aTwipsDelta.y);
}
}
}
@ -618,11 +622,13 @@ NS_IMETHODIMP nsScrollPortView::ScrollToImpl(nscoord aX, nscoord aY, PRUint32 aU
mOffsetXpx = aXpx;
mOffsetYpx = aYpx;
nsPoint twipsDelta(aX - mOffsetX, aY - mOffsetY);
// store the new position
mOffsetX = aX;
mOffsetY = aY;
Scroll(scrolledView, dxPx, dyPx, t2p, 0);
Scroll(scrolledView, twipsDelta, nsPoint(dxPx, dyPx), t2p);
mViewManager->SynthesizeMouseMove(PR_TRUE);

View File

@ -106,7 +106,7 @@ protected:
virtual ~nsScrollPortView();
//private
void Scroll(nsView *aScrolledView, PRInt32 aDx, PRInt32 aDy, float scale, PRUint32 aUpdateFlags);
void Scroll(nsView *aScrolledView, nsPoint aTwipsDelta, nsPoint aPixDelta, float aT2P);
PRBool CannotBitBlt(nsView* aScrolledView);
nscoord mOffsetX, mOffsetY;

View File

@ -258,8 +258,7 @@ nsView::~nsView()
mWindow->Destroy();
NS_RELEASE(mWindow);
}
NS_IF_RELEASE(mDirtyRegion);
delete mDirtyRegion;
delete mClipRect;
}
@ -787,19 +786,6 @@ nsIWidget* nsIView::GetNearestWidget(nsPoint* aOffset)
return v->GetWidget();
}
nsresult nsView::GetDirtyRegion(nsIRegion*& aRegion)
{
if (nsnull == mDirtyRegion) {
nsresult rv = GetViewManager()->CreateRegion(&mDirtyRegion);
if (NS_FAILED(rv))
return rv;
}
aRegion = mDirtyRegion;
NS_ADDREF(aRegion);
return NS_OK;
}
PRBool nsIView::IsRoot() const
{
NS_ASSERTION(mViewManager != nsnull," View manager is null in nsView::IsRoot()");

View File

@ -40,6 +40,7 @@
#include "nsIView.h"
#include "nsIWidget.h"
#include "nsRegion.h"
#include "nsRect.h"
#include "nsCRT.h"
#include "nsIFactory.h"
@ -210,11 +211,6 @@ public:
* @param aTransparent PR_TRUE if there are transparent areas, PR_FALSE otherwise.
*/
NS_IMETHOD SetContentTransparency(PRBool aTransparent);
/**
* Gets the dirty region associated with this view. Used by the view
* manager.
*/
nsresult GetDirtyRegion(nsIRegion*& aRegion);
/**
* Set the widget associated with this view.
* @param aWidget widget to associate with view. It is an error
@ -256,6 +252,17 @@ public:
// These are defined exactly the same in nsIView, but for now they have to be redeclared
// here because of stupid C++ method hiding rules
PRBool HasNonEmptyDirtyRegion() {
return mDirtyRegion && !mDirtyRegion->IsEmpty();
}
nsRegion* GetDirtyRegion() {
if (!mDirtyRegion) {
mDirtyRegion = new nsRegion();
NS_ASSERTION(mDirtyRegion, "Out of memory!");
}
return mDirtyRegion;
}
void InsertChild(nsView *aChild, nsView *aSibling);
void RemoveChild(nsView *aChild);
@ -284,13 +291,12 @@ public:
virtual ~nsView();
protected:
nsZPlaceholderView*mZParent;
nsZPlaceholderView* mZParent;
// mClipRect is relative to the view's origin.
nsRect* mClipRect;
nsIRegion* mDirtyRegion;
PRPackedBool mChildRemoved;
nsRect* mClipRect;
nsRegion* mDirtyRegion;
PRPackedBool mChildRemoved;
};
#endif

View File

@ -1564,13 +1564,6 @@ void nsViewManager::ProcessPendingUpdates(nsView* aView)
if (aView->HasWidget()) {
aView->ResetWidgetBounds(PR_FALSE, PR_FALSE, PR_TRUE);
nsCOMPtr<nsIRegion> dirtyRegion;
aView->GetDirtyRegion(*getter_AddRefs(dirtyRegion));
if (dirtyRegion && !dirtyRegion->IsEmpty()) {
aView->GetWidget()->InvalidateRegion(dirtyRegion, PR_FALSE);
dirtyRegion->Init();
}
}
// process pending updates in child view.
@ -1578,6 +1571,17 @@ void nsViewManager::ProcessPendingUpdates(nsView* aView)
childView = childView->GetNextSibling()) {
ProcessPendingUpdates(childView);
}
if (aView->HasNonEmptyDirtyRegion()) {
// Push out updates after we've processed the children; ensures that
// damage is applied based on the final widget geometry
NS_ASSERTION(mRefreshEnabled, "Cannot process pending updates with refresh disabled");
nsRegion* dirtyRegion = aView->GetDirtyRegion();
if (dirtyRegion) {
UpdateWidgetArea(aView, *dirtyRegion, nsnull);
dirtyRegion->SetEmpty();
}
}
}
NS_IMETHODIMP nsViewManager::Composite()
@ -1626,24 +1630,52 @@ nsViewManager::UpdateViewAfterScroll(nsIView *aView, PRInt32 aDX, PRInt32 aDY)
return;
}
UpdateWidgetArea(RootViewManager()->GetRootView(), damageRect, view);
UpdateWidgetArea(RootViewManager()->GetRootView(), nsRegion(damageRect), view);
Composite();
}
// Returns true if this view's widget(s) completely cover the rectangle
// The specified rectangle, relative to aWidgetView, is invalidated in every widget child of aWidgetView,
// plus aWidgetView's own widget
// If non-null, the aIgnoreWidgetView's widget and its children are not updated.
PRBool nsViewManager::UpdateWidgetArea(nsView *aWidgetView, const nsRect &aDamagedRect, nsView* aIgnoreWidgetView)
/**
* @param aDamagedRegion this region, relative to aWidgetView, is invalidated in
* every widget child of aWidgetView, plus aWidgetView's own widget
* @param aIgnoreWidgetView if non-null, the aIgnoreWidgetView's widget and its
* children are not updated.
* @param aCoveredRegion if non-null, is set to PR_TRUE whenever the aWidgetView's
* widget completely covers the region. Must be null when refresh is disabled.
*/
void
nsViewManager::UpdateWidgetArea(nsView *aWidgetView, const nsRegion &aDamagedRegion,
nsView* aIgnoreWidgetView, PRBool* aCoveredRegion)
{
// If the bounds don't overlap at all, there's nothing to do
nsRect bounds;
aWidgetView->GetDimensions(bounds);
if (!IsRefreshEnabled()) {
NS_ASSERTION(!aCoveredRegion, "aCoveredRegion is not computed when refresh is disabled");
PRBool overlap = bounds.IntersectRect(bounds, aDamagedRect);
if (!overlap) {
return PR_FALSE;
// accumulate this rectangle in the view's dirty region, so we can
// process it later.
nsRegion* dirtyRegion = aWidgetView->GetDirtyRegion();
if (!dirtyRegion) return;
dirtyRegion->Or(*dirtyRegion, aDamagedRegion);
// Don't let dirtyRegion grow beyond 8 rects
dirtyRegion->SimplifyOutward(8);
nsViewManager* rootVM = RootViewManager();
rootVM->mHasPendingInvalidates = PR_TRUE;
rootVM->IncrementUpdateCount();
return;
// this should only happen at the top level, and this result
// should not be consumed by top-level callers, so it doesn't
// really matter what we return
}
if (aCoveredRegion) {
*aCoveredRegion = PR_FALSE;
}
// If the bounds don't overlap at all, there's nothing to do
nsRegion intersection;
intersection.And(aWidgetView->GetDimensions(), aDamagedRegion);
if (intersection.IsEmpty()) {
return;
}
// If the widget is hidden, it don't cover nothing
@ -1657,14 +1689,16 @@ PRBool nsViewManager::UpdateWidgetArea(nsView *aWidgetView, const nsRect &aDamag
NS_ASSERTION(!visible, "View is hidden but widget is visible!");
}
#endif
return PR_FALSE;
return;
}
PRBool noCropping = bounds == aDamagedRect;
if (aWidgetView == aIgnoreWidgetView) {
// the widget for aIgnoreWidgetView (and its children) should be treated as already updated.
// We still need to report whether this widget covers the rectangle.
return noCropping;
if (aCoveredRegion) {
*aCoveredRegion = intersection.IsEqual(aDamagedRegion);
}
return;
}
nsIWidget* widget = aWidgetView->GetNearestWidget(nsnull);
@ -1672,7 +1706,11 @@ PRBool nsViewManager::UpdateWidgetArea(nsView *aWidgetView, const nsRect &aDamag
// The root view or a scrolling view might not have a widget
// (for example, during printing). We get here when we scroll
// during printing to show selected options in a listbox, for example.
return PR_FALSE;
return;
}
if (aCoveredRegion) {
*aCoveredRegion = intersection.IsEqual(aDamagedRegion);
}
PRBool childCovers = PR_FALSE;
@ -1681,15 +1719,19 @@ PRBool nsViewManager::UpdateWidgetArea(nsView *aWidgetView, const nsRect &aDamag
childWidget = childWidget->GetNextSibling()) {
nsView* view = nsView::GetViewFor(childWidget);
if (nsnull != view) {
nsRect damage = bounds;
nsView* vp = view;
nsPoint offset(0, 0);
while (vp != aWidgetView && nsnull != vp) {
vp->ConvertFromParentCoords(&damage.x, &damage.y);
offset -= vp->GetPosition();
vp = vp->GetParent();
}
if (nsnull != vp) { // vp == nsnull means it's in a different hierarchy so we ignore it
if (UpdateWidgetArea(view, damage, aIgnoreWidgetView)) {
nsRegion damage = intersection;
damage.MoveBy(offset);
PRBool covers;
UpdateWidgetArea(view, damage, aIgnoreWidgetView, &covers);
if (covers) {
childCovers = PR_TRUE;
}
}
@ -1697,21 +1739,15 @@ PRBool nsViewManager::UpdateWidgetArea(nsView *aWidgetView, const nsRect &aDamag
}
if (!childCovers) {
nsViewManager* vm = aWidgetView->GetViewManager();
nsViewManager* rootVM = RootViewManager();
rootVM->IncrementUpdateCount();
NS_ASSERTION(IsRefreshEnabled(), "Can only get here with refresh enabled, I hope");
if (!IsRefreshEnabled()) {
// accumulate this rectangle in the view's dirty region, so we can process it later.
vm->AddRectToDirtyRegion(aWidgetView, bounds);
rootVM->mHasPendingInvalidates = PR_TRUE;
} else {
const nsRect* r;
for (nsRegionRectIterator iter(intersection); (r = iter.Next());) {
nsRect bounds = *r;
ViewToWidget(aWidgetView, aWidgetView, bounds);
widget->Invalidate(bounds, PR_FALSE);
}
}
return noCropping;
}
NS_IMETHODIMP nsViewManager::UpdateView(nsIView *aView, const nsRect &aRect, PRUint32 aUpdateFlags)
@ -1752,7 +1788,7 @@ NS_IMETHODIMP nsViewManager::UpdateView(nsIView *aView, const nsRect &aRect, PRU
widgetParent = widgetParent->GetParent();
}
UpdateWidgetArea(widgetParent, damagedRect, nsnull);
UpdateWidgetArea(widgetParent, nsRegion(damagedRect), nsnull);
} else {
// Propagate the update to the root widget of the root view manager, since
// iframes, for example, can overlap each other and be translucent. So we
@ -1760,7 +1796,7 @@ NS_IMETHODIMP nsViewManager::UpdateView(nsIView *aView, const nsRect &aRect, PRU
// lying about.
damagedRect.MoveBy(ComputeViewOffset(view));
UpdateWidgetArea(RootViewManager()->GetRootView(), damagedRect, nsnull);
UpdateWidgetArea(RootViewManager()->GetRootView(), nsRegion(damagedRect), nsnull);
}
RootViewManager()->IncrementUpdateCount();
@ -3206,25 +3242,6 @@ nsViewManager::CreateRenderingContext(nsView &aView)
return cx;
}
void nsViewManager::AddRectToDirtyRegion(nsView* aView, const nsRect &aRect) const
{
// Find a view with an associated widget. We'll transform this rect from the
// current view's coordinate system to a "heavyweight" parent view, then convert
// the rect to pixel coordinates, and accumulate the rect into that view's dirty region.
nsView* widgetView = GetWidgetView(aView);
if (widgetView != nsnull) {
nsRect widgetRect = aRect;
ViewToWidget(aView, widgetView, widgetRect);
// Get the dirty region associated with the widget view
nsCOMPtr<nsIRegion> dirtyRegion;
if (NS_SUCCEEDED(widgetView->GetDirtyRegion(*getter_AddRefs(dirtyRegion)))) {
// add this rect to the widget view's dirty region.
dirtyRegion->Union(widgetRect.x, widgetRect.y, widgetRect.width, widgetRect.height);
}
}
}
NS_IMETHODIMP nsViewManager::DisableRefresh(void)
{
if (!IsRootVM()) {

View File

@ -267,9 +267,8 @@ private:
void ReparentChildWidgets(nsIView* aView, nsIWidget *aNewWidget);
void ReparentWidgets(nsIView* aView, nsIView *aParent);
already_AddRefed<nsIRenderingContext> CreateRenderingContext(nsView &aView);
void AddRectToDirtyRegion(nsView* aView, const nsRect &aRect) const;
PRBool UpdateWidgetArea(nsView *aWidgetView, const nsRect &aDamagedRect, nsView* aIgnoreWidgetView);
void UpdateWidgetArea(nsView *aWidgetView, const nsRegion &aDamagedRegion,
nsView* aIgnoreWidgetView, PRBool* aCoveredRegion = nsnull);
void UpdateViews(nsView *aView, PRUint32 aUpdateFlags);