Bug 507334, part 2: Blit multiple rectangles when scrolling rather than blitting only the largest single rectangle, and avoid repainting opaque content that covers the scrolling content. r=dbaron

This commit is contained in:
Robert O'Callahan 2009-08-13 19:09:51 -07:00
parent 14c9b93519
commit 9f2ae1a715
19 changed files with 432 additions and 184 deletions

View File

@ -65,6 +65,7 @@ nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
PRBool aIsForEvents, PRBool aBuildCaret)
: mReferenceFrame(aReferenceFrame),
mMovingFrame(nsnull),
mSaveVisibleRegionOfMovingContent(nsnull),
mIgnoreScrollFrame(nsnull),
mCurrentTableItem(nsnull),
mBuildCaret(aBuildCaret),
@ -155,6 +156,16 @@ nsDisplayListBuilder::~nsDisplayListBuilder() {
PL_FinishArenaPool(&mPool);
}
void
nsDisplayListBuilder::SubtractFromVisibleRegion(nsRegion* aVisibleRegion,
const nsRegion& aRegion)
{
aVisibleRegion->Sub(*aVisibleRegion, aRegion);
if (!GetAccurateVisibleRegions()) {
aVisibleRegion->SimplifyOutward(15);
}
}
PRBool
nsDisplayListBuilder::IsMovingFrame(nsIFrame* aFrame)
{
@ -236,6 +247,26 @@ nsDisplayListBuilder::Allocate(size_t aSize) {
return tmp;
}
void
nsDisplayListBuilder::AccumulateVisibleRegionOfMovingContent(const nsRegion& aMovingContent,
const nsRegion& aVisibleRegion)
{
if (!mSaveVisibleRegionOfMovingContent)
return;
// Grab the union of aMovingContent (after the move) with
// aMovingContent - mMoveDelta (before the move)
nsRegion r = aMovingContent;
r.MoveBy(-mMoveDelta);
r.Or(r, aMovingContent);
// Reduce to the part that's visible after the move
r.And(r, aVisibleRegion);
// Accumulate it into our result
mSaveVisibleRegionOfMovingContent->Or(
*mSaveVisibleRegionOfMovingContent, r);
mSaveVisibleRegionOfMovingContent->SimplifyOutward(15);
}
void nsDisplayListSet::MoveTo(const nsDisplayListSet& aDestination) const
{
aDestination.BorderBackground()->AppendToTop(BorderBackground());
@ -266,11 +297,7 @@ nsDisplayItem::OptimizeVisibility(nsDisplayListBuilder* aBuilder,
// before state and in the after state.
opaqueArea.IntersectRect(bounds - aBuilder->GetMoveDelta(), bounds);
}
if (aBuilder->GetAccurateVisibleRegions()) {
aVisibleRegion->Sub(*aVisibleRegion, opaqueArea);
} else {
aVisibleRegion->SimpleSubtract(opaqueArea);
}
aBuilder->SubtractFromVisibleRegion(aVisibleRegion, nsRegion(opaqueArea));
}
return PR_TRUE;
@ -304,6 +331,12 @@ nsDisplayList::OptimizeVisibility(nsDisplayListBuilder* aBuilder,
nsAutoTArray<nsDisplayItem*, 512> elements;
FlattenTo(&elements);
// Accumulate the bounds of all moving content we find in this list
nsRect movingContentAccumulatedBounds;
// Store an overapproximation of the visible region for the moving
// content in this list
nsRegion movingContentVisibleRegion;
for (PRInt32 i = elements.Length() - 1; i >= 0; --i) {
nsDisplayItem* item = elements[i];
nsDisplayItem* belowItem = i < 1 ? nsnull : elements[i - 1];
@ -313,13 +346,26 @@ nsDisplayList::OptimizeVisibility(nsDisplayListBuilder* aBuilder,
elements.ReplaceElementsAt(i - 1, 1, item);
continue;
}
nsIFrame* f = item->GetUnderlyingFrame();
if (f && aBuilder->IsMovingFrame(f)) {
if (movingContentAccumulatedBounds.IsEmpty()) {
// *aVisibleRegion can only shrink during this loop, so storing
// the first one we see is a sound overapproximation
movingContentVisibleRegion = *aVisibleRegion;
}
movingContentAccumulatedBounds.UnionRect(movingContentAccumulatedBounds,
item->GetBounds(aBuilder));
}
if (item->OptimizeVisibility(aBuilder, aVisibleRegion)) {
AppendToBottom(item);
} else {
item->~nsDisplayItem();
}
}
aBuilder->AccumulateVisibleRegionOfMovingContent(
nsRegion(movingContentAccumulatedBounds), movingContentVisibleRegion);
}
void nsDisplayList::Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx,
@ -835,7 +881,7 @@ void nsDisplayWrapList::Paint(nsDisplayListBuilder* aBuilder,
static nsresult
WrapDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList, nsDisplayWrapper* aWrapper) {
if (!aList->GetTop())
if (!aList->GetTop() && !aBuilder->HasMovingFrames())
return NS_OK;
nsDisplayItem* item = aWrapper->WrapList(aBuilder, aFrame, aList);
if (!item)
@ -1024,15 +1070,25 @@ PRBool nsDisplayClip::OptimizeVisibility(nsDisplayListBuilder* aBuilder,
nsRegion* aVisibleRegion) {
nsRegion clipped;
clipped.And(*aVisibleRegion, mClip);
if (aBuilder->HasMovingFrames() &&
!aBuilder->IsMovingFrame(mClippingFrame)) {
// There may be some clipped moving children that were visible before
// but are clipped out now. Conservatively assume they were there
// and add their possible area to the visible region of moving
// content.
// Compute the after-move region of moving content that could have been
// totally clipped out.
nsRegion r;
r.Sub(mClip + aBuilder->GetMoveDelta(), mClip);
aBuilder->AccumulateVisibleRegionOfMovingContent(r, *aVisibleRegion);
}
nsRegion rNew(clipped);
PRBool anyVisible = nsDisplayWrapList::OptimizeVisibility(aBuilder, &rNew);
nsRegion subtracted;
subtracted.Sub(clipped, rNew);
if (aBuilder->GetAccurateVisibleRegions()) {
aVisibleRegion->Sub(*aVisibleRegion, subtracted);
} else {
aVisibleRegion->SimpleSubtract(subtracted);
}
aBuilder->SubtractFromVisibleRegion(aVisibleRegion, subtracted);
return anyVisible;
}

View File

@ -61,6 +61,7 @@ class nsRegion;
class nsIRenderingContext;
class nsIDeviceContext;
class nsDisplayTableItem;
class nsDisplayItem;
/*
* An nsIFrame can have many different visual parts. For example an image frame
@ -160,10 +161,15 @@ public:
* cover (during OptimizeVisibility) non-moving frames. E.g. when we're
* constructing a display list to see what should be repainted during a
* scroll operation, we specify the scrolled frame as the moving frame.
* @param aSaveVisibleRegionOfMovingContent if non-null,
* this receives a bounding region for the visible moving content
* (considering the moving content both before and after the move)
*/
void SetMovingFrame(nsIFrame* aMovingFrame, const nsPoint& aMoveDelta) {
void SetMovingFrame(nsIFrame* aMovingFrame, const nsPoint& aMoveDelta,
nsRegion* aSaveVisibleRegionOfMovingContent) {
mMovingFrame = aMovingFrame;
mMoveDelta = aMoveDelta;
mSaveVisibleRegionOfMovingContent = aSaveVisibleRegionOfMovingContent;
}
/**
@ -179,6 +185,14 @@ public:
* Only valid when GetRootMovingFrame() returns non-null.
*/
const nsPoint& GetMoveDelta() { return mMoveDelta; }
/**
* Given the bounds of some moving content, and a visible region,
* intersect the bounds with the visible region and add it to the
* recorded region of visible moving content.
*/
void AccumulateVisibleRegionOfMovingContent(const nsRegion& aMovingContent,
const nsRegion& aVisibleRegion);
/**
* @return PR_TRUE if aFrame is, or is a descendant of, the hypothetical
* moving frame
@ -276,6 +290,15 @@ public:
*/
void SetInTransform(PRBool aInTransform) { mInTransform = aInTransform; }
/**
* Subtracts aRegion from *aVisibleRegion. We avoid letting
* aVisibleRegion become overcomplex by simplifying it if necessary ---
* unless mAccurateVisibleRegions is set, in which case we let it
* get arbitrarily complex.
*/
void SubtractFromVisibleRegion(nsRegion* aVisibleRegion,
const nsRegion& aRegion);
/**
* Mark the frames in aFrames to be displayed if they intersect aDirtyRect
* (which is relative to aDirtyFrame). If the frames have placeholders
@ -356,6 +379,7 @@ private:
nsIFrame* mReferenceFrame;
nsIFrame* mMovingFrame;
nsRegion* mSaveVisibleRegionOfMovingContent;
nsIFrame* mIgnoreScrollFrame;
nsPoint mMoveDelta; // only valid when mMovingFrame is non-null
PLArenaPool mPool;

View File

@ -1135,12 +1135,12 @@ nsLayoutUtils::PaintFrame(nsIRenderingContext* aRenderingContext, nsIFrame* aFra
}
static void
AccumulateItemInRegion(nsRegion* aRegion, const nsRect& aAreaRect,
AccumulateItemInRegion(nsRegion* aRegion, const nsRect& aUpdateRect,
const nsRect& aItemRect, const nsRect& aExclude,
nsDisplayItem* aItem)
{
nsRect damageRect;
if (damageRect.IntersectRect(aAreaRect, aItemRect)) {
if (damageRect.IntersectRect(aUpdateRect, aItemRect)) {
nsRegion r;
r.Sub(damageRect, aExclude);
#ifdef DEBUG
@ -1157,7 +1157,7 @@ AccumulateItemInRegion(nsRegion* aRegion, const nsRect& aAreaRect,
static void
AddItemsToRegion(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
const nsRect& aRect, const nsRect& aClipRect, nsPoint aDelta,
const nsRect& aUpdateRect, const nsRect& aClipRect, nsPoint aDelta,
nsRegion* aRegion)
{
for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) {
@ -1186,19 +1186,19 @@ AddItemsToRegion(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
// Invalidate the translation of the source area that was clipped out
nsRegion clippedOutSource;
clippedOutSource.Sub(aRect, clip);
clippedOutSource.Sub(aUpdateRect - aDelta, clip);
clippedOutSource.MoveBy(aDelta);
aRegion->Or(*aRegion, clippedOutSource);
// Invalidate the destination area that is clipped out
nsRegion clippedOutDestination;
clippedOutDestination.Sub(aRect + aDelta, clip);
clippedOutDestination.Sub(aUpdateRect, clip);
aRegion->Or(*aRegion, clippedOutDestination);
}
AddItemsToRegion(aBuilder, sublist, aRect, clip, aDelta, aRegion);
AddItemsToRegion(aBuilder, sublist, aUpdateRect, clip, aDelta, aRegion);
} else {
// opacity, or a generic sublist
AddItemsToRegion(aBuilder, sublist, aRect, aClipRect, aDelta, aRegion);
AddItemsToRegion(aBuilder, sublist, aUpdateRect, aClipRect, aDelta, aRegion);
}
} else {
nsRect r;
@ -1210,7 +1210,7 @@ AddItemsToRegion(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
if (item->IsVaryingRelativeToMovingFrame(aBuilder)) {
// something like background-attachment:fixed that varies
// its drawing when it moves
AccumulateItemInRegion(aRegion, aRect + aDelta, r, exclude, item);
AccumulateItemInRegion(aRegion, aUpdateRect, r, exclude, item);
}
} else {
// not moving.
@ -1220,11 +1220,11 @@ AddItemsToRegion(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
exclude.IntersectRect(r, r + aDelta);
}
// area where a non-moving element is visible must be repainted
AccumulateItemInRegion(aRegion, aRect + aDelta, r, exclude, item);
AccumulateItemInRegion(aRegion, aUpdateRect, r, exclude, item);
// we may have bitblitted an area that was painted by a non-moving
// element. This bitblitted data is invalid and was copied to
// "r + aDelta".
AccumulateItemInRegion(aRegion, aRect + aDelta, r + aDelta,
AccumulateItemInRegion(aRegion, aUpdateRect, r + aDelta,
exclude, item);
}
}
@ -1236,7 +1236,8 @@ nsresult
nsLayoutUtils::ComputeRepaintRegionForCopy(nsIFrame* aRootFrame,
nsIFrame* aMovingFrame,
nsPoint aDelta,
const nsRect& aCopyRect,
const nsRect& aUpdateRect,
nsRegion* aBlitRegion,
nsRegion* aRepaintRegion)
{
NS_ASSERTION(aRootFrame != aMovingFrame,
@ -1257,9 +1258,12 @@ nsLayoutUtils::ComputeRepaintRegionForCopy(nsIFrame* aRootFrame,
// XXX but currently a non-moving clip item can incorrectly clip
// moving items! See bug 428156.
nsRect rect;
rect.UnionRect(aCopyRect, aCopyRect + aDelta);
rect.UnionRect(aUpdateRect, aUpdateRect - aDelta);
nsDisplayListBuilder builder(aRootFrame, PR_FALSE, PR_TRUE);
builder.SetMovingFrame(aMovingFrame, aDelta);
// Retrieve the area of the moving content that's visible. This is the
// only area that needs to be blitted or repainted.
nsRegion visibleRegionOfMovingContent;
builder.SetMovingFrame(aMovingFrame, aDelta, &visibleRegionOfMovingContent);
nsDisplayList list;
builder.EnterPresShell(aRootFrame, rect);
@ -1281,8 +1285,8 @@ nsLayoutUtils::ComputeRepaintRegionForCopy(nsIFrame* aRootFrame,
// Optimize for visibility, but frames under aMovingFrame will not be
// considered opaque, so they don't cover non-moving frames.
nsRegion visibleRegion(aCopyRect);
visibleRegion.Or(visibleRegion, aCopyRect + aDelta);
nsRegion visibleRegion(aUpdateRect);
visibleRegion.Or(visibleRegion, aUpdateRect - aDelta);
list.OptimizeVisibility(&builder, &visibleRegion);
#ifdef DEBUG
@ -1297,14 +1301,21 @@ nsLayoutUtils::ComputeRepaintRegionForCopy(nsIFrame* aRootFrame,
// a) at their current location and b) offset by -aPt (their position in
// the 'before' display list) (unless they're uniform and we can exclude them).
// Also, any visible position-varying display items get added to the
// repaint region. All these areas are confined to aCopyRect+aDelta.
// repaint region. All these areas are confined to aUpdateRect.
// We could do more work here: e.g., do another optimize-visibility pass
// with the moving items taken into account, either on the before-list
// or the after-list, or even both if we cloned the display lists ... but
// it's probably not worth it.
AddItemsToRegion(&builder, &list, aCopyRect, rect, aDelta, aRepaintRegion);
AddItemsToRegion(&builder, &list, aUpdateRect, rect, aDelta, aRepaintRegion);
// Flush the list so we don't trigger the IsEmpty-on-destruction assertion
list.DeleteAll();
// Finalize output regions. The region of moving content that's not
// visible --- hidden by overlaid opaque non-moving content --- need not
// be blitted or repainted.
visibleRegionOfMovingContent.And(visibleRegionOfMovingContent, aUpdateRect);
aRepaintRegion->And(*aRepaintRegion, visibleRegionOfMovingContent);
aBlitRegion->Sub(visibleRegionOfMovingContent, *aRepaintRegion);
return NS_OK;
}

View File

@ -497,20 +497,21 @@ public:
/**
* @param aRootFrame the root frame of the tree to be displayed
* @param aMovingFrame a frame that has moved
* @param aPt the amount by which aMovingFrame has moved and the rect will
* be copied
* @param aCopyRect a rectangle that will be copied, relative to aRootFrame
* @param aRepaintRegion a subregion of aCopyRect+aDelta that must be repainted
* after doing the bitblt
* @param aPt the amount by which aMovingFrame has moved
* @param aUpdateRect a rectangle that bounds the area to be updated,
* relative to aRootFrame
* @param aRepaintRegion output: a subregion of aUpdateRect that must be
* repainted after doing the blit
* @param aBlitRegion output: a subregion of aUpdateRect that should
* be repainted by blitting
*
* Ideally this function would actually have the rect-to-copy as an output
* rather than an input, but for now, scroll bitblitting is limited to
* the whole of a single widget, so we cannot choose the rect.
* If the caller does a bitblt copy of aBlitRegion-aPt to aBlitRegion,
* and then repaints aRepaintRegion, then the area aUpdateRect will be
* correctly up to date. aBlitRegion and aRepaintRegion do not intersect
* and are both contained within aUpdateRect.
*
* This function assumes that the caller will do a bitblt copy of aCopyRect
* to aCopyRect+aPt. It computes a region that must be repainted in order
* for the resulting rendering to be correct. Frame geometry must have
* already been adjusted for the scroll/copy operation.
* Frame geometry must have already been adjusted for the scroll/copy
* operation before this function is called.
*
* Conceptually it works by computing a display list in the before-state
* and a display list in the after-state and analyzing them to find the
@ -519,25 +520,31 @@ public:
* efficient), so we use some unfortunately tricky techniques to get by
* with just the after-list.
*
* The output region consists of:
* We compute the "visible moving area": aUpdateRect minus any opaque
* areas of non-moving content that are above all moving content in
* z-order.
*
* The aRepaintRegion region consists of the visible moving area
* intersected with the union of the following areas:
* a) any visible background-attachment:fixed areas in the after-move display
* list
* b) any visible areas of the before-move display list corresponding to
* frames that will not move (translated by aDelta)
* c) any visible areas of the after-move display list corresponding to
* frames that did not move
* d) except that if the same display list element is visible in b) and c)
* for a frame that did not move and paints a uniform color within its
* bounds, then the intersection of its old and new bounds can be excluded
* when it is processed by b) and c).
*
* We may return a larger region if computing the above region precisely is
* too expensive.
* aBlitRegion is the visible moving area minus aRepaintRegion.
*
* We may return a larger region for aRepaintRegion and/or aBlitRegion
* if computing the above regions precisely is too expensive. (However,
* they will never intersect, since the regions that may be computed
* imprecisely are really the "visible moving area" and aRepaintRegion.)
*/
static nsresult ComputeRepaintRegionForCopy(nsIFrame* aRootFrame,
nsIFrame* aMovingFrame,
nsPoint aDelta,
const nsRect& aCopyRect,
const nsRect& aUpdateRect,
nsRegion* aBlitRegion,
nsRegion* aRepaintRegion);
/**

View File

@ -913,7 +913,8 @@ public:
NS_IMETHOD ComputeRepaintRegionForCopy(nsIView* aRootView,
nsIView* aMovingView,
nsPoint aDelta,
const nsRect& aCopyRect,
const nsRect& aUpdateRect,
nsRegion* aBlitRegion,
nsRegion* aRepaintRegion);
NS_IMETHOD HandleEvent(nsIView* aView,
nsGUIEvent* aEvent,
@ -5224,13 +5225,14 @@ NS_IMETHODIMP
PresShell::ComputeRepaintRegionForCopy(nsIView* aRootView,
nsIView* aMovingView,
nsPoint aDelta,
const nsRect& aCopyRect,
const nsRect& aUpdateRect,
nsRegion* aBlitRegion,
nsRegion* aRepaintRegion)
{
return nsLayoutUtils::ComputeRepaintRegionForCopy(
static_cast<nsIFrame*>(aRootView->GetClientData()),
static_cast<nsIFrame*>(aMovingView->GetClientData()),
aDelta, aCopyRect, aRepaintRegion);
aDelta, aUpdateRect, aBlitRegion, aRepaintRegion);
}
NS_IMETHODIMP

View File

@ -4129,15 +4129,17 @@ nsTreeBodyFrame::ScrollInternal(const ScrollParts& aParts, PRInt32 aRow)
nscoord rowHeightAsPixels =
PresContext()->AppUnitsToDevPixels(mRowHeight);
nsIntPoint deltaPt = nsIntPoint(0, -delta*rowHeightAsPixels);
nsIntRect bounds;
widget->GetBounds(bounds);
bounds.x = bounds.y = 0;
nsIntRect source;
source.IntersectRect(bounds, bounds - deltaPt);
nsTArray<nsIntRect> destRects;
destRects.AppendElement(bounds);
// No plugins have a tree widget as a parent so we don't need
// configurations here.
nsTArray<nsIWidget::Configuration> emptyConfigurations;
widget->Scroll(deltaPt, source, emptyConfigurations);
widget->Scroll(deltaPt, destRects, emptyConfigurations);
nsIntRect invalid = bounds;
if (deltaPt.y < 0) {
invalid.y = bounds.height + deltaPt.y;
@ -4184,15 +4186,17 @@ nsTreeBodyFrame::ScrollHorzInternal(const ScrollParts& aParts, PRInt32 aPosition
nsIWidget* widget = nsLeafBoxFrame::GetView()->GetWidget();
if (widget) {
nsIntPoint deltaPt(PresContext()->AppUnitsToDevPixels(-delta), 0);
nsIntRect bounds;
widget->GetBounds(bounds);
bounds.x = bounds.y = 0;
nsIntRect source;
source.IntersectRect(bounds, bounds - deltaPt);
nsTArray<nsIntRect> destRects;
destRects.AppendElement(bounds);
// No plugins have a tree widget as a parent so we don't need
// configurations here.
nsTArray<nsIWidget::Configuration> emptyConfigurations;
widget->Scroll(deltaPt, source, emptyConfigurations);
widget->Scroll(deltaPt, destRects, emptyConfigurations);
nsIntRect invalid = bounds;
if (deltaPt.x < 0) {
invalid.x = bounds.width + deltaPt.x;

View File

@ -97,7 +97,8 @@ public:
NS_IMETHOD ComputeRepaintRegionForCopy(nsIView* aRootView,
nsIView* aMovingView,
nsPoint aDelta,
const nsRect& aCopyRect,
const nsRect& aUpdateRect,
nsRegion* aBlitRegion,
nsRegion* aRepaintRegion) = 0;
/* called when the observer needs to handle an event

View File

@ -541,6 +541,134 @@ NS_IMETHODIMP nsScrollPortView::CanScroll(PRBool aHorizontal,
return NS_OK;
}
/**
* Given aBlitRegion in appunits, create and return an nsRegion in
* device pixels that represents the device pixels that are wholly
* contained in aBlitRegion. Whatever appunit area was removed in that
* process is added to aRepaintRegion.
*/
static nsRegion
ConvertToInnerPixelRegion(const nsRegion& aBlitRegion,
nscoord aAppUnitsPerPixel,
nsRegion* aRepaintRegion)
{
// Basically we compute the inverse of aBlitRegion,
// expand each of its rectangles out to device pixel boundaries, then
// invert that.
nsIntRect boundingBoxPixels =
aBlitRegion.GetBounds().ToOutsidePixels(aAppUnitsPerPixel);
nsRect boundingBox = boundingBoxPixels.ToAppUnits(aAppUnitsPerPixel);
nsRegion outside;
outside.Sub(boundingBox, aBlitRegion);
nsRegion outsidePixels;
nsRegion outsideAppUnits;
const nsRect* r;
for (nsRegionRectIterator iter(outside); (r = iter.Next());) {
nsIntRect pixRect = r->ToOutsidePixels(aAppUnitsPerPixel);
outsidePixels.Or(outsidePixels,
nsRect(pixRect.x, pixRect.y, pixRect.width, pixRect.height));
outsideAppUnits.Or(outsideAppUnits,
pixRect.ToAppUnits(aAppUnitsPerPixel));
}
nsRegion repaint;
repaint.And(aBlitRegion, outsideAppUnits);
aRepaintRegion->Or(*aRepaintRegion, repaint);
nsRegion result;
result.Sub(nsRect(boundingBoxPixels.x, boundingBoxPixels.y,
boundingBoxPixels.width, boundingBoxPixels.height),
outsidePixels);
return result;
}
/**
* An nsTArray comparator that lets us sort nsIntRects by their right edge.
*/
class RightEdgeComparator {
public:
/** @return True if the elements are equals; false otherwise. */
PRBool Equals(const nsIntRect& aA, const nsIntRect& aB) const
{
return aA.XMost() == aB.XMost();
}
/** @return True if (a < b); false otherwise. */
PRBool LessThan(const nsIntRect& aA, const nsIntRect& aB) const
{
return aA.XMost() < aB.XMost();
}
};
// If aPixDelta has a negative component, flip aRect across the
// axis in that direction. We do this so we can assume all scrolling is
// down and to the right to simplify SortBlitRectsForCopy
static nsIntRect
FlipRect(const nsIntRect& aRect, nsIntPoint aPixDelta)
{
nsIntRect r = aRect;
if (aPixDelta.x < 0) {
r.x = -r.XMost();
}
if (aPixDelta.y < 0) {
r.y = -r.YMost();
}
return r;
}
// Extract the rectangles from aInnerPixRegion, and sort them into aRects
// so that moving rectangle aRects[i] - aPixDelta to aRects[i] will not
// cause the rectangle to overlap any rectangles that haven't moved yet. See
// http://weblogs.mozillazine.org/roc/archives/2009/08/homework_answer.html
static void
SortBlitRectsForCopy(const nsRegion& aInnerPixRegion,
nsIntPoint aPixDelta,
nsTArray<nsIntRect>* aResult)
{
nsTArray<nsIntRect> rects;
const nsRect* r;
for (nsRegionRectIterator iter(aInnerPixRegion); (r = iter.Next());) {
nsIntRect rect =
FlipRect(nsIntRect(r->x, r->y, r->width, r->height), aPixDelta);
rects.AppendElement(rect);
}
rects.Sort(RightEdgeComparator());
// This could probably be improved a bit for some worst-case scenarios.
// But in common cases this should be very fast, and we shouldn't
// make it more complex unless we really need to.
while (!rects.IsEmpty()) {
PRInt32 i = rects.Length() - 1;
PRBool overlappedBelow;
do {
overlappedBelow = PR_FALSE;
const nsIntRect& rectI = rects[i];
// see if any rectangle < i overlaps rectI horizontally and is below
// rectI
for (PRInt32 j = i - 1; j >= 0; --j) {
if (rects[j].XMost() <= rectI.x) {
// No rectangle with index <= j can overlap rectI horizontally
break;
}
// Rectangle j overlaps rectI horizontally.
if (rects[j].y >= rectI.y) {
// Rectangle j is below rectangle i. This is the rightmost such
// rectangle, so set i to this rectangle and continue.
i = j;
overlappedBelow = PR_TRUE;
break;
}
}
} while (overlappedBelow);
// Rectangle i has no rectangles to the right or below.
// Flip it back before saving the result.
aResult->AppendElement(FlipRect(rects[i], aPixDelta));
rects.RemoveElementAt(i);
}
}
void nsScrollPortView::Scroll(nsView *aScrolledView, nsPoint aTwipsDelta,
nsIntPoint aPixDelta, PRInt32 aP2A,
const nsTArray<nsIWidget::Configuration>& aConfigurations)
@ -555,13 +683,8 @@ void nsScrollPortView::Scroll(nsView *aScrolledView, nsPoint aTwipsDelta,
nsPoint nearestWidgetOffset;
nsIWidget *nearestWidget = GetNearestWidget(&nearestWidgetOffset);
nsRegion updateRegion;
PRBool canBitBlit = nearestWidget &&
mViewManager->CanScrollWithBitBlt(aScrolledView, aTwipsDelta, &updateRegion) &&
nearestWidget->GetTransparencyMode() != eTransparencyTransparent;
if (!canBitBlit) {
// We can't blit for some reason.
if (!nearestWidget ||
nearestWidget->GetTransparencyMode() == eTransparencyTransparent) {
// Just update the view and adjust widgets
// Recall that our widget's origin is at our bounds' top-left
if (nearestWidget) {
@ -575,45 +698,27 @@ void nsScrollPortView::Scroll(nsView *aScrolledView, nsPoint aTwipsDelta,
// consistent with the view hierarchy.
mViewManager->UpdateView(this, NS_VMREFRESH_DEFERRED);
} else {
nsRegion blitRegion;
nsRegion repaintRegion;
mViewManager->GetRegionsForBlit(aScrolledView, aTwipsDelta,
&blitRegion, &repaintRegion);
blitRegion.MoveBy(nearestWidgetOffset);
repaintRegion.MoveBy(nearestWidgetOffset);
// We're going to bit-blit. Let the viewmanager know so it can
// adjust dirty regions appropriately.
mViewManager->WillBitBlit(this, aTwipsDelta);
// Compute the region that needs to be updated by the bit-blit scroll
nsRect bounds(nsPoint(0,0), GetBounds().Size());
nsRegion regionToScroll;
regionToScroll.Sub(bounds, updateRegion);
// Only the area corresponding to the widget bounds, translated
// by the scroll amount, will actually be filled by the blit
regionToScroll.And(regionToScroll, bounds - aTwipsDelta);
// Find the largest rectangle in that region
nsRegionRectIterator iter(regionToScroll);
nsRect biggestRect(0,0,0,0);
const nsRect* r;
for (r = iter.Next(); r; r = iter.Next()) {
if (PRInt64(r->width)*PRInt64(r->height) > PRInt64(biggestRect.width)*PRInt64(biggestRect.height)) {
biggestRect = *r;
}
}
// Convert the largest rectangle to widget device pixel coordinates
nsIntRect destScroll = (biggestRect + nearestWidgetOffset).ToInsidePixels(aP2A);
// Convert it back to view-relative appunits, since we shrank it in
// ToInsidePixels
biggestRect = destScroll.ToAppUnits(aP2A) - nearestWidgetOffset;
// Make sure we repaint the area we've decided not to bit-blit to
regionToScroll.Sub(regionToScroll, biggestRect);
updateRegion.Or(updateRegion, regionToScroll);
// innerPixRegion is in device pixels
nsRegion innerPixRegion =
ConvertToInnerPixelRegion(blitRegion, aP2A, &repaintRegion);
nsTArray<nsIntRect> blitRects;
SortBlitRectsForCopy(innerPixRegion, aPixDelta, &blitRects);
// Compute the area that's being exposed by the scroll operation
// and make sure it gets repainted
nsRegion exposedArea;
exposedArea.Sub(bounds, bounds - aTwipsDelta);
updateRegion.Or(updateRegion, exposedArea);
nearestWidget->Scroll(aPixDelta, destScroll - aPixDelta,
aConfigurations);
nearestWidget->Scroll(aPixDelta, blitRects, aConfigurations);
AdjustChildWidgets(aScrolledView, nearestWidgetOffset, aP2A, PR_TRUE);
mViewManager->UpdateViewAfterScroll(this, updateRegion);
repaintRegion.MoveBy(-nearestWidgetOffset);
mViewManager->UpdateViewAfterScroll(this, repaintRegion);
}
}
}

View File

@ -1514,35 +1514,35 @@ NS_IMETHODIMP nsViewManager::ResizeView(nsIView *aView, const nsRect &aRect, PRB
return NS_OK;
}
static double GetArea(const nsRect& aRect)
{
return double(aRect.width)*double(aRect.height);
}
PRBool nsViewManager::CanScrollWithBitBlt(nsView* aView, nsPoint aDelta,
nsRegion* aUpdateRegion)
void nsViewManager::GetRegionsForBlit(nsView* aView, nsPoint aDelta,
nsRegion* aBlitRegion,
nsRegion* aRepaintRegion)
{
NS_ASSERTION(!IsPainting(),
"View manager shouldn't be scrolling during a paint");
if (IsPainting() || !mObserver) {
return PR_FALSE; // do the safe thing
}
nsView* displayRoot = GetDisplayRootFor(aView);
nsPoint displayOffset = aView->GetParent()->GetOffsetTo(displayRoot);
nsRect parentBounds = aView->GetParent()->GetDimensions() + displayOffset;
// The rect we're going to scroll is intersection of the parent bounds with its
// preimage
nsRect toScroll;
toScroll.IntersectRect(parentBounds + aDelta, parentBounds);
nsresult rv =
mObserver->ComputeRepaintRegionForCopy(displayRoot, aView, -aDelta, toScroll,
aUpdateRegion);
NS_ENSURE_SUCCESS(rv, PR_FALSE);
if (IsPainting() || !mObserver) {
// Be simple and safe
aBlitRegion->SetEmpty();
*aRepaintRegion = parentBounds;
} else {
nsresult rv =
mObserver->ComputeRepaintRegionForCopy(displayRoot, aView, -aDelta,
parentBounds,
aBlitRegion,
aRepaintRegion);
if (NS_FAILED(rv)) {
aBlitRegion->SetEmpty();
*aRepaintRegion = nsRegion(parentBounds);
return;
}
}
aUpdateRegion->MoveBy(-displayOffset);
return GetArea(aUpdateRegion->GetBounds()) < GetArea(parentBounds)/2;
aBlitRegion->MoveBy(-displayOffset);
aRepaintRegion->MoveBy(-displayOffset);
}
NS_IMETHODIMP nsViewManager::SetViewFloating(nsIView *aView, PRBool aFloating)

View File

@ -356,11 +356,12 @@ public: // NOT in nsIViewManager, so private to the view module
void UpdateViewAfterScroll(nsView *aView, const nsRegion& aUpdateRegion);
/**
* Asks whether we can scroll a view using bitblt. If we say 'yes', we
* return in aUpdateRegion an area that must be updated (relative to aView
* after it has been scrolled).
* Given that the view aView has being moved by scrolling by aDelta
* (so we want to blit pixels by -aDelta), compute the regions that
* must be blitted and repainted to correctly update the screen.
*/
PRBool CanScrollWithBitBlt(nsView* aView, nsPoint aDelta, nsRegion* aUpdateRegion);
void GetRegionsForBlit(nsView* aView, nsPoint aDelta,
nsRegion* aBlitRegion, nsRegion* aRepaintRegion);
nsresult CreateRegion(nsIRegion* *result);

View File

@ -102,10 +102,9 @@ typedef nsEventStatus (* EVENT_CALLBACK)(nsGUIEvent *event);
#define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102
#endif
// {A16A3387-A529-439C-A127-A5893351FD24}
#define NS_IWIDGET_IID \
{ 0xA16A3387, 0xA529, 0x439C, \
{ 0xA1, 0x27, 0xA5, 0x89, 0x33, 0x51, 0xFD, 0x24 } }
{ 0xb681539f, 0x5dac, 0x45af, \
{ 0x8a, 0x25, 0xdf, 0xd7, 0x14, 0xe0, 0x9f, 0x43 } }
/*
* Window shadow styles
@ -718,7 +717,7 @@ class nsIWidget : public nsISupports {
virtual nsIToolkit* GetToolkit() = 0;
/**
* Scroll a rectangle in this widget and (as simultaneously as
* Scroll a set of rectangles in this widget and (as simultaneously as
* possible) modify the specified child widgets.
*
* This will invalidate areas of the children that have changed, unless
@ -726,16 +725,23 @@ class nsIWidget : public nsISupports {
* invalidate any part of this widget, except where the scroll
* operation fails to blit because part of the window is unavailable
* (e.g. partially offscreen).
*
* The caller guarantees that the rectangles in aDestRects are ordered
* so that copying from aDestRects[i] - aDelta to aDestRects[i] does
* not alter anything in aDestRects[j] - aDelta for j > i. That is,
* it's safe to just copy the rectangles in the order given in
* aDestRects.
*
* @param aDelta amount to scroll (device pixels)
* @param aSource rectangle to copy (device pixels relative to this
* widget)
* @param aDestRects rectangles to copy into
* (device pixels relative to this widget)
* @param aReconfigureChildren commands to set the bounds and clip
* region of a subset of the children of this widget; these should
* be performed simultaneously with the scrolling, as far as possible,
* to avoid visual artifacts.
*/
virtual void Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
virtual void Scroll(const nsIntPoint& aDelta,
const nsTArray<nsIntRect>& aDestRects,
const nsTArray<Configuration>& aReconfigureChildren) = 0;
/**

View File

@ -347,7 +347,7 @@ public:
virtual void* GetNativeData(PRUint32 aDataType);
virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations);
virtual void Scroll(const nsIntPoint& aDelta,
const nsIntRect& aSource,
const nsTArray<nsIntRect>& aDestRects,
const nsTArray<Configuration>& aConfigurations);
virtual nsIntPoint WidgetToScreenOffset();
virtual PRBool ShowsResizeIndicator(nsIntRect* aResizerRect);

View File

@ -1697,7 +1697,8 @@ nsresult nsChildView::ConfigureChildren(const nsTArray<Configuration>& aConfigur
return NS_OK;
}
void nsChildView::Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
void nsChildView::Scroll(const nsIntPoint& aDelta,
const nsTArray<nsIntRect>& aDestRects,
const nsTArray<Configuration>& aConfigurations)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
@ -1710,10 +1711,12 @@ void nsChildView::Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
if (mVisible) {
viewWasDirty = [mView needsDisplay];
NSRect rect;
GeckoRectToNSRect(aSource, rect);
NSSize scrollVector = {aDelta.x, aDelta.y};
[mView scrollRect:rect by:scrollVector];
for (PRUint32 i = 0; i < aDestRects.Length(); ++i) {
NSRect rect;
GeckoRectToNSRect(aDestRects[i] - aDelta, rect);
NSSize scrollVector = {aDelta.x, aDelta.y};
[mView scrollRect:rect by:scrollVector];
}
}
// Don't force invalidation of the child if it's moving by the scroll

View File

@ -227,7 +227,8 @@ public:
NS_IMETHOD Invalidate(PRBool aIsSynchronous);
NS_IMETHOD Update();
virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations);
virtual void Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
virtual void Scroll(const nsIntPoint& aDelta,
const nsTArray<nsIntRect>& aDestRects,
const nsTArray<Configuration>& aConfigurations);
NS_IMETHOD DispatchEvent(nsGUIEvent* event, nsEventStatus & aStatus) ;
NS_IMETHOD CaptureRollupEvents(nsIRollupListener * aListener, PRBool aDoCapture, PRBool aConsumeRollupEvent);

View File

@ -852,11 +852,12 @@ nsCocoaWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
}
void
nsCocoaWindow::Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
nsCocoaWindow::Scroll(const nsIntPoint& aDelta,
const nsTArray<nsIntRect>& aDestRects,
const nsTArray<Configuration>& aConfigurations)
{
if (mPopupContentView) {
mPopupContentView->Scroll(aDelta, aSource, aConfigurations);
mPopupContentView->Scroll(aDelta, aDestRects, aConfigurations);
}
}

View File

@ -1741,7 +1741,8 @@ nsWindow::Update()
}
void
nsWindow::Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
nsWindow::Scroll(const nsIntPoint& aDelta,
const nsTArray<nsIntRect>& aDestRects,
const nsTArray<Configuration>& aConfigurations)
{
if (!mGdkWindow) {
@ -1768,11 +1769,18 @@ nsWindow::Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
}
}
GdkRectangle gdkSource =
{ aSource.x, aSource.y, aSource.width, aSource.height };
GdkRegion* region = gdk_region_rectangle(&gdkSource);
gdk_window_move_region(GDK_WINDOW(mGdkWindow), region, aDelta.x, aDelta.y);
gdk_region_destroy(region);
// gdk_window_move_region, up to GDK 2.16 at least, has a ghastly bug
// where it doesn't restrict blitting to the given region, and blits
// its full bounding box. So we have to work around that by
// blitting one rectangle at a time.
for (PRUint32 i = 0; i < aDestRects.Length(); ++i) {
const nsIntRect& r = aDestRects[i];
GdkRectangle gdkSource =
{ r.x - aDelta.x, r.y - aDelta.y, r.width, r.height };
GdkRegion* region = gdk_region_rectangle(&gdkSource);
gdk_window_move_region(GDK_WINDOW(mGdkWindow), region, aDelta.x, aDelta.y);
gdk_region_destroy(region);
}
ConfigureChildren(aConfigurations);

View File

@ -186,7 +186,8 @@ public:
NS_IMETHOD Invalidate(const nsIntRect &aRect,
PRBool aIsSynchronous);
NS_IMETHOD Update();
virtual void Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
virtual void Scroll(const nsIntPoint& aDelta,
const nsTArray<nsIntRect>& aDestRects,
const nsTArray<Configuration>& aReconfigureChildren);
virtual void* GetNativeData(PRUint32 aDataType);
NS_IMETHOD SetBorderStyle(nsBorderStyle aBorderStyle);

View File

@ -2169,7 +2169,8 @@ ClipRegionContainedInRect(const nsTArray<nsIntRect>& aClipRects,
}
void
nsWindow::Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
nsWindow::Scroll(const nsIntPoint& aDelta,
const nsTArray<nsIntRect>& aDestRects,
const nsTArray<Configuration>& aConfigurations)
{
// We use SW_SCROLLCHILDREN if all the windows that intersect the
@ -2191,43 +2192,58 @@ nsWindow::Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
w->SetWindowClipRegion(configuration.mClipRegion, PR_TRUE);
}
// Now check if any of our children would be affected by
// SW_SCROLLCHILDREN but not supposed to scroll.
nsIntRect affectedRect;
affectedRect.UnionRect(aSource, aSource + aDelta);
// We pass SW_INVALIDATE because areas that get scrolled into view
// from offscreen (but inside the scroll area) need to be repainted.
UINT flags = SW_SCROLLCHILDREN | SW_INVALIDATE;
for (nsWindow* w = static_cast<nsWindow*>(GetFirstChild()); w;
w = static_cast<nsWindow*>(w->GetNextSibling())) {
if (w->mBounds.Intersects(affectedRect) &&
!scrolledWidgets.GetEntry(w)) {
flags &= ~SW_SCROLLCHILDREN;
break;
}
}
if (flags & SW_SCROLLCHILDREN) {
for (PRUint32 i = 0; i < aConfigurations.Length(); ++i) {
const Configuration& configuration = aConfigurations[i];
nsWindow* w = static_cast<nsWindow*>(configuration.mChild);
// Widgets that will be scrolled by SW_SCROLLCHILDREN but which
// will be partly visible outside the scroll area after scrolling
// must be invalidated, because SW_SCROLLCHILDREN doesn't
// update parts of widgets outside the area it scrolled, even
// if it moved them.
if (w->mBounds.Intersects(affectedRect) &&
!ClipRegionContainedInRect(configuration.mClipRegion,
affectedRect - (w->mBounds.TopLeft() + aDelta))) {
w->Invalidate(PR_FALSE);
for (PRUint32 i = 0; i < aDestRects.Length(); ++i) {
nsIntRect affectedRect;
affectedRect.UnionRect(aDestRects[i], aDestRects[i] - aDelta);
// We pass SW_INVALIDATE because areas that get scrolled into view
// from offscreen (but inside the scroll area) need to be repainted.
UINT flags = SW_SCROLLCHILDREN | SW_INVALIDATE;
// Now check if any of our children would be affected by
// SW_SCROLLCHILDREN but not supposed to scroll.
for (nsWindow* w = static_cast<nsWindow*>(GetFirstChild()); w;
w = static_cast<nsWindow*>(w->GetNextSibling())) {
if (w->mBounds.Intersects(affectedRect)) {
// This child will be affected
nsPtrHashKey<nsWindow>* entry = scrolledWidgets.GetEntry(w);
if (entry) {
// It's supposed to be scrolled, so we can still use
// SW_SCROLLCHILDREN. But don't allow SW_SCROLLCHILDREN to be
// used on it again by a later rectangle in aDestRects, we
// don't want it to move twice!
scrolledWidgets.RawRemoveEntry(entry);
} else {
flags &= ~SW_SCROLLCHILDREN;
// We may have removed some children from scrolledWidgets even
// though we decide here to not use SW_SCROLLCHILDREN. That's OK,
// it just means that we might not use SW_SCROLLCHILDREN
// for a later rectangle when we could have.
break;
}
}
}
}
// Note that when SW_SCROLLCHILDREN is used, WM_MOVE messages are sent
// which will update the mBounds of the children.
RECT clip = { affectedRect.x, affectedRect.y, affectedRect.XMost(), affectedRect.YMost() };
::ScrollWindowEx(mWnd, aDelta.x, aDelta.y, &clip, &clip, NULL, NULL, flags);
if (flags & SW_SCROLLCHILDREN) {
for (PRUint32 i = 0; i < aConfigurations.Length(); ++i) {
const Configuration& configuration = aConfigurations[i];
nsWindow* w = static_cast<nsWindow*>(configuration.mChild);
// Widgets that will be scrolled by SW_SCROLLCHILDREN but which
// will be partly visible outside the scroll area after scrolling
// must be invalidated, because SW_SCROLLCHILDREN doesn't
// update parts of widgets outside the area it scrolled, even
// if it moved them.
if (w->mBounds.Intersects(affectedRect) &&
!ClipRegionContainedInRect(configuration.mClipRegion,
affectedRect - (w->mBounds.TopLeft() + aDelta))) {
w->Invalidate(PR_FALSE);
}
}
}
// Note that when SW_SCROLLCHILDREN is used, WM_MOVE messages are sent
// which will update the mBounds of the children.
RECT clip = { affectedRect.x, affectedRect.y, affectedRect.XMost(), affectedRect.YMost() };
::ScrollWindowEx(mWnd, aDelta.x, aDelta.y, &clip, &clip, NULL, NULL, flags);
}
// Now make sure all children actually get positioned, sized and clipped
// correctly. If SW_SCROLLCHILDREN already moved widgets to their correct

View File

@ -148,7 +148,8 @@ public:
NS_IMETHOD Invalidate(PRBool aIsSynchronous);
NS_IMETHOD Invalidate(const nsIntRect & aRect, PRBool aIsSynchronous);
NS_IMETHOD Update();
virtual void Scroll(const nsIntPoint& aDelta, const nsIntRect& aSource,
virtual void Scroll(const nsIntPoint& aDelta,
const nsTArray<nsIntRect>& aDestRects,
const nsTArray<Configuration>& aReconfigureChildren);
virtual void* GetNativeData(PRUint32 aDataType);
virtual void FreeNativeData(void * data, PRUint32 aDataType);