Bug 803013 - Maintain coherency when progressively updating visible areas. r=bgirard

When rendering progressively, make sure that any previously visible area that
is still visible is updated at the same time. This helps maintain visual
coherency on pages that invalidate previously visible areas while scrolling,
and when losing layers between updates.

This supersedes the previous method of only doing progressive updates while
scrolling.
This commit is contained in:
Chris Lord 2012-10-22 20:18:14 +01:00
parent 85cc57baf9
commit 2caf0bb798
6 changed files with 228 additions and 117 deletions

View File

@ -105,10 +105,8 @@ public:
unsigned int GetTileCount() const { return mRetainedTiles.Length(); } unsigned int GetTileCount() const { return mRetainedTiles.Length(); }
const nsIntRegion& GetValidRegion() const { return mValidRegion; } const nsIntRegion& GetValidRegion() const { return mValidRegion; }
const nsIntRegion& GetLastPaintRegion() const { return mLastPaintRegion; } const nsIntRegion& GetPaintedRegion() const { return mPaintedRegion; }
void SetLastPaintRegion(const nsIntRegion& aLastPaintRegion) { void ClearPaintedRegion() { mPaintedRegion.SetEmpty(); }
mLastPaintRegion = aLastPaintRegion;
}
// Given a position i, this function returns the position inside the current tile. // Given a position i, this function returns the position inside the current tile.
int GetTileStart(int i) const { int GetTileStart(int i) const {
@ -127,7 +125,7 @@ protected:
void Update(const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion); void Update(const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion);
nsIntRegion mValidRegion; nsIntRegion mValidRegion;
nsIntRegion mLastPaintRegion; nsIntRegion mPaintedRegion;
/** /**
* mRetainedTiles is a rectangular buffer of mRetainedWidth x mRetainedHeight * mRetainedTiles is a rectangular buffer of mRetainedWidth x mRetainedHeight
@ -403,7 +401,7 @@ TiledLayerBuffer<Derived, Tile>::Update(const nsIntRegion& aNewValidRegion,
mRetainedTiles = newRetainedTiles; mRetainedTiles = newRetainedTiles;
mValidRegion = aNewValidRegion; mValidRegion = aNewValidRegion;
mLastPaintRegion = aPaintRegion; mPaintedRegion.Or(mPaintedRegion, aPaintRegion);
} }
} // layers } // layers

View File

@ -1022,7 +1022,7 @@ BasicLayerManager::CreateReadbackLayer()
BasicShadowLayerManager::BasicShadowLayerManager(nsIWidget* aWidget) : BasicShadowLayerManager::BasicShadowLayerManager(nsIWidget* aWidget) :
BasicLayerManager(aWidget), mTargetRotation(ROTATION_0), BasicLayerManager(aWidget), mTargetRotation(ROTATION_0),
mRepeatTransaction(false) mRepeatTransaction(false), mIsRepeatTransaction(false)
{ {
MOZ_COUNT_CTOR(BasicShadowLayerManager); MOZ_COUNT_CTOR(BasicShadowLayerManager);
} }
@ -1132,8 +1132,10 @@ BasicShadowLayerManager::EndTransaction(DrawThebesLayerCallback aCallback,
if (mRepeatTransaction) { if (mRepeatTransaction) {
mRepeatTransaction = false; mRepeatTransaction = false;
mIsRepeatTransaction = true;
BasicLayerManager::BeginTransaction(); BasicLayerManager::BeginTransaction();
BasicShadowLayerManager::EndTransaction(aCallback, aCallbackData, aFlags); BasicShadowLayerManager::EndTransaction(aCallback, aCallbackData, aFlags);
mIsRepeatTransaction = false;
} else if (mShadowTarget) { } else if (mShadowTarget) {
if (mWidget) { if (mWidget) {
if (CompositorChild* remoteRenderer = mWidget->GetRemoteRenderer()) { if (CompositorChild* remoteRenderer = mWidget->GetRemoteRenderer()) {

View File

@ -272,6 +272,8 @@ public:
void SetRepeatTransaction() { mRepeatTransaction = true; } void SetRepeatTransaction() { mRepeatTransaction = true; }
bool IsRepeatTransaction() { return mIsRepeatTransaction; }
/** /**
* Called for each iteration of a progressive tile update. Fills * Called for each iteration of a progressive tile update. Fills
* aViewport, aScaleX and aScaleY with the current scale and viewport * aViewport, aScaleX and aScaleY with the current scale and viewport
@ -307,6 +309,7 @@ private:
// Used to repeat the transaction right away (to avoid rebuilding // Used to repeat the transaction right away (to avoid rebuilding
// a display list) to support progressive drawing. // a display list) to support progressive drawing.
bool mRepeatTransaction; bool mRepeatTransaction;
bool mIsRepeatTransaction;
}; };
class BasicShadowableThebesLayer; class BasicShadowableThebesLayer;

View File

@ -229,6 +229,138 @@ BasicTiledThebesLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
aAttrs = ThebesLayerAttributes(GetValidRegion()); aAttrs = ThebesLayerAttributes(GetValidRegion());
} }
static nsIntRect
RoundedTransformViewportBounds(const gfx::Rect& aViewport,
const gfx::Point& aScrollOffset,
const gfxSize& aResolution,
float aScaleX,
float aScaleY,
const gfx3DMatrix& aTransform)
{
gfxRect transformedViewport(aViewport.x - (aScrollOffset.x * aResolution.width),
aViewport.y - (aScrollOffset.y * aResolution.height),
aViewport.width, aViewport.height);
transformedViewport.Scale((aScaleX / aResolution.width) / aResolution.width,
(aScaleY / aResolution.height) / aResolution.height);
transformedViewport = aTransform.TransformBounds(transformedViewport);
return nsIntRect((int32_t)floor(transformedViewport.x),
(int32_t)floor(transformedViewport.y),
(int32_t)ceil(transformedViewport.width),
(int32_t)ceil(transformedViewport.height));
}
bool
BasicTiledThebesLayer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion,
const nsIntRegion& aOldValidRegion,
nsIntRegion& aRegionToPaint,
const gfx3DMatrix& aTransform,
const gfx::Point& aScrollOffset,
const gfxSize& aResolution,
bool aIsRepeated)
{
aRegionToPaint = aInvalidRegion;
// Find out if we have any non-stale content to update.
nsIntRegion freshRegion;
if (!mFirstPaint) {
freshRegion.And(aInvalidRegion, aOldValidRegion);
freshRegion.Sub(aInvalidRegion, freshRegion);
}
// Find out the current view transform to determine which tiles to draw
// first, and see if we should just abort this paint. Aborting is usually
// caused by there being an incoming, more relevant paint.
gfx::Rect viewport;
float scaleX, scaleY;
if (BasicManager()->ProgressiveUpdateCallback(!freshRegion.IsEmpty(), viewport, scaleX, scaleY)) {
aRegionToPaint.SetEmpty();
return aIsRepeated;
}
// Transform the screen coordinates into local layer coordinates.
nsIntRect roundedTransformedViewport =
RoundedTransformViewportBounds(viewport, aScrollOffset, aResolution,
scaleX, scaleY, aTransform);
// Paint tiles that have no content before tiles that only have stale content.
bool drawingStale = freshRegion.IsEmpty();
if (!drawingStale) {
aRegionToPaint = freshRegion;
}
// Prioritise tiles that are currently visible on the screen.
bool paintVisible = false;
if (aRegionToPaint.Intersects(roundedTransformedViewport)) {
aRegionToPaint.And(aRegionToPaint, roundedTransformedViewport);
paintVisible = true;
}
// The following code decides what order to draw tiles in, based on the
// current scroll direction of the primary scrollable layer.
NS_ASSERTION(!aRegionToPaint.IsEmpty(), "Unexpectedly empty paint region!");
nsIntRect paintBounds = aRegionToPaint.GetBounds();
int startX, incX, startY, incY;
if (aScrollOffset.x >= mLastScrollOffset.x) {
startX = mTiledBuffer.RoundDownToTileEdge(paintBounds.x);
incX = mTiledBuffer.GetTileLength();
} else {
startX = mTiledBuffer.RoundDownToTileEdge(paintBounds.XMost() - 1);
incX = -mTiledBuffer.GetTileLength();
}
if (aScrollOffset.y >= mLastScrollOffset.y) {
startY = mTiledBuffer.RoundDownToTileEdge(paintBounds.y);
incY = mTiledBuffer.GetTileLength();
} else {
startY = mTiledBuffer.RoundDownToTileEdge(paintBounds.YMost() - 1);
incY = -mTiledBuffer.GetTileLength();
}
// Find a tile to draw.
nsIntRect tileBounds(startX, startY,
mTiledBuffer.GetTileLength(),
mTiledBuffer.GetTileLength());
int32_t scrollDiffX = aScrollOffset.x - mLastScrollOffset.x;
int32_t scrollDiffY = aScrollOffset.y - mLastScrollOffset.y;
// This loop will always terminate, as there is at least one tile area
// along the first/last row/column intersecting with regionToPaint, or its
// bounds would have been smaller.
while (true) {
aRegionToPaint.And(aInvalidRegion, tileBounds);
if (!aRegionToPaint.IsEmpty()) {
break;
}
if (NS_ABS(scrollDiffY) >= NS_ABS(scrollDiffX)) {
tileBounds.x += incX;
} else {
tileBounds.y += incY;
}
}
bool repeatImmediately = false;
if (!aRegionToPaint.Contains(aInvalidRegion)) {
// The region needed to paint is larger then our progressive chunk size
// therefore update what we want to paint and ask for a new paint transaction.
// If we're drawing stale, visible content, make sure that it happens
// in one go by repeating this work without calling the painted
// callback. The remaining content is then drawn tile-by-tile in
// multiple transactions.
if (paintVisible && drawingStale) {
repeatImmediately = true;
} else {
BasicManager()->SetRepeatTransaction();
}
} else {
// The transaction is completed, store the last scroll offset.
mLastScrollOffset = aScrollOffset;
}
return repeatImmediately;
}
void void
BasicTiledThebesLayer::PaintThebes(gfxContext* aContext, BasicTiledThebesLayer::PaintThebes(gfxContext* aContext,
Layer* aMaskLayer, Layer* aMaskLayer,
@ -254,7 +386,6 @@ BasicTiledThebesLayer::PaintThebes(gfxContext* aContext,
invalidRegion.Sub(invalidRegion, mValidRegion); invalidRegion.Sub(invalidRegion, mValidRegion);
if (invalidRegion.IsEmpty()) if (invalidRegion.IsEmpty())
return; return;
nsIntRegion regionToPaint = invalidRegion;
gfxSize resolution(1, 1); gfxSize resolution(1, 1);
for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) { for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
@ -263,38 +394,10 @@ BasicTiledThebesLayer::PaintThebes(gfxContext* aContext,
resolution.height *= metrics.mResolution.height; resolution.height *= metrics.mResolution.height;
} }
// Calculate the scroll offset since the last transaction. Progressive tile // Only draw progressively when the resolution is unchanged.
// painting is only used when scrolling.
gfx::Point scrollOffset(0, 0);
Layer* primaryScrollable = BasicManager()->GetPrimaryScrollableLayer();
if (primaryScrollable) {
const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics();
scrollOffset = metrics.mScrollOffset;
}
int32_t scrollDiffX = scrollOffset.x - mLastScrollOffset.x;
int32_t scrollDiffY = scrollOffset.y - mLastScrollOffset.y;
// Only draw progressively when we're panning and the resolution is unchanged.
if (gfxPlatform::UseProgressiveTilePainting() && if (gfxPlatform::UseProgressiveTilePainting() &&
mTiledBuffer.GetResolution() == resolution && mTiledBuffer.GetResolution() == resolution) {
(scrollDiffX != 0 || scrollDiffY != 0)) { // Calculate the transform required to convert screen space into layer space
// Find out if we have any non-stale content to update.
nsIntRegion freshRegion = mTiledBuffer.GetValidRegion();
freshRegion.And(freshRegion, invalidRegion);
freshRegion.Sub(invalidRegion, freshRegion);
// Find out the current view transform to determine which tiles to draw
// first, and see if we should just abort this paint. Aborting is usually
// caused by there being an incoming, more relevant paint.
gfx::Rect viewport;
float scaleX, scaleY;
if (BasicManager()->ProgressiveUpdateCallback(!freshRegion.IsEmpty(), viewport, scaleX, scaleY)) {
return;
}
// Prioritise tiles that are currently visible on the screen.
// Get the transform to the current layer.
gfx3DMatrix transform = GetEffectiveTransform(); gfx3DMatrix transform = GetEffectiveTransform();
// XXX Not sure if this code for intermediate surfaces is correct. // XXX Not sure if this code for intermediate surfaces is correct.
// It rarely gets hit though, and shouldn't have terrible consequences // It rarely gets hit though, and shouldn't have terrible consequences
@ -306,91 +409,65 @@ BasicTiledThebesLayer::PaintThebes(gfxContext* aContext,
} }
transform.Invert(); transform.Invert();
// Transform the screen coordinates into local layer coordinates. // Store the old valid region, then clear it before painting.
gfxRect transformedViewport(viewport.x - (scrollOffset.x * resolution.width), nsIntRegion oldValidRegion = mTiledBuffer.GetValidRegion();
viewport.y - (scrollOffset.y * resolution.height), mTiledBuffer.ClearPaintedRegion();
viewport.width, viewport.height);
transformedViewport.Scale((scaleX / resolution.width) / resolution.width,
(scaleY / resolution.height) / resolution.height);
transformedViewport = transform.TransformBounds(transformedViewport);
nsIntRect roundedTransformedViewport((int32_t)floor(transformedViewport.x), // Make sure that tiles that fall outside of the visible region are
(int32_t)floor(transformedViewport.y), // discarded on the first update.
(int32_t)ceil(transformedViewport.width), if (!BasicManager()->IsRepeatTransaction()) {
(int32_t)ceil(transformedViewport.height));
// Paint tiles that have no content before tiles that only have stale content.
if (!freshRegion.IsEmpty()) {
regionToPaint = freshRegion;
}
if (regionToPaint.Intersects(roundedTransformedViewport)) {
regionToPaint.And(regionToPaint, roundedTransformedViewport);
}
// The following code decides what order to draw tiles in, based on the
// current scroll direction of the primary scrollable layer.
NS_ASSERTION(!regionToPaint.IsEmpty(), "Unexpectedly empty paint region!");
nsIntRect paintBounds = regionToPaint.GetBounds();
int startX, incX, startY, incY;
if (scrollOffset.x >= mLastScrollOffset.x) {
startX = mTiledBuffer.RoundDownToTileEdge(paintBounds.x);
incX = mTiledBuffer.GetTileLength();
} else {
startX = mTiledBuffer.RoundDownToTileEdge(paintBounds.XMost() - 1);
incX = -mTiledBuffer.GetTileLength();
}
if (scrollOffset.y >= mLastScrollOffset.y) {
startY = mTiledBuffer.RoundDownToTileEdge(paintBounds.y);
incY = mTiledBuffer.GetTileLength();
} else {
startY = mTiledBuffer.RoundDownToTileEdge(paintBounds.YMost() - 1);
incY = -mTiledBuffer.GetTileLength();
}
// Find a tile to draw.
nsIntRect tileBounds(startX, startY,
mTiledBuffer.GetTileLength(),
mTiledBuffer.GetTileLength());
// This loop will always terminate, as there is at least one tile area
// along the first/last row/column intersecting with regionToPaint, or its
// bounds would have been smaller.
while (true) {
regionToPaint.And(invalidRegion, tileBounds);
if (!regionToPaint.IsEmpty()) {
break;
}
if (NS_ABS(scrollDiffY) >= NS_ABS(scrollDiffX)) {
tileBounds.x += incX;
} else {
tileBounds.y += incY;
}
}
if (!regionToPaint.Contains(invalidRegion)) {
// The region needed to paint is larger then our progressive chunk size
// therefore update what we want to paint and ask for a new paint transaction.
BasicManager()->SetRepeatTransaction();
// Make sure that tiles that fall outside of the visible region are discarded.
mValidRegion.And(mValidRegion, mVisibleRegion); mValidRegion.And(mValidRegion, mVisibleRegion);
} else {
// The transaction is completed, store the last scroll offset.
mLastScrollOffset = scrollOffset;
} }
// Keep track of what we're about to refresh. // Calculate the scroll offset since the last transaction.
mValidRegion.Or(mValidRegion, regionToPaint); gfx::Point scrollOffset(0, 0);
Layer* primaryScrollable = BasicManager()->GetPrimaryScrollableLayer();
if (primaryScrollable) {
const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics();
scrollOffset = metrics.mScrollOffset;
}
bool repeat = false;
do {
// Compute the region that should be updated. Repeat as many times as
// is required.
nsIntRegion regionToPaint;
repeat = ComputeProgressiveUpdateRegion(invalidRegion,
oldValidRegion,
regionToPaint,
transform,
scrollOffset,
resolution,
repeat);
// There's no further work to be done, return if nothing has been
// drawn, or give what has been drawn to the shadow layer to upload.
if (regionToPaint.IsEmpty()) {
if (repeat) {
break;
} else {
return;
}
}
// Keep track of what we're about to refresh.
mValidRegion.Or(mValidRegion, regionToPaint);
// Paint the computed region and subtract it from the invalid region.
mTiledBuffer.PaintThebes(this, mValidRegion, regionToPaint, aCallback, aCallbackData);
invalidRegion.Sub(invalidRegion, regionToPaint);
} while (repeat);
} else { } else {
mTiledBuffer.ClearPaintedRegion();
mTiledBuffer.SetResolution(resolution); mTiledBuffer.SetResolution(resolution);
mValidRegion = mVisibleRegion; mValidRegion = mVisibleRegion;
mTiledBuffer.PaintThebes(this, mValidRegion, invalidRegion, aCallback, aCallbackData);
} }
mTiledBuffer.PaintThebes(this, mValidRegion, regionToPaint, aCallback, aCallbackData);
mTiledBuffer.ReadLock(); mTiledBuffer.ReadLock();
if (aMaskLayer) {
// Only paint the mask layer on the first transaction.
if (aMaskLayer && !BasicManager()->IsRepeatTransaction()) {
static_cast<BasicImplData*>(aMaskLayer->ImplData()) static_cast<BasicImplData*>(aMaskLayer->ImplData())
->Paint(aContext, nullptr); ->Paint(aContext, nullptr);
} }
@ -402,6 +479,7 @@ BasicTiledThebesLayer::PaintThebes(gfxContext* aContext,
BasicTiledLayerBuffer *heapCopy = new BasicTiledLayerBuffer(mTiledBuffer); BasicTiledLayerBuffer *heapCopy = new BasicTiledLayerBuffer(mTiledBuffer);
BasicManager()->PaintedTiledLayerBuffer(BasicManager()->Hold(this), heapCopy); BasicManager()->PaintedTiledLayerBuffer(BasicManager()->Hold(this), heapCopy);
mFirstPaint = false;
} }
} // mozilla } // mozilla

View File

@ -163,6 +163,7 @@ public:
BasicTiledThebesLayer(BasicShadowLayerManager* const aManager) BasicTiledThebesLayer(BasicShadowLayerManager* const aManager)
: ThebesLayer(aManager, static_cast<BasicImplData*>(this)) : ThebesLayer(aManager, static_cast<BasicImplData*>(this))
, mLastScrollOffset(0, 0) , mLastScrollOffset(0, 0)
, mFirstPaint(true)
{ {
MOZ_COUNT_CTOR(BasicTiledThebesLayer); MOZ_COUNT_CTOR(BasicTiledThebesLayer);
} }
@ -212,9 +213,39 @@ private:
void* aCallbackData) void* aCallbackData)
{ NS_RUNTIMEABORT("Not reached."); } { NS_RUNTIMEABORT("Not reached."); }
/**
* Calculates the region to update in a single progressive update transaction.
* This employs some heuristics to update the most 'sensible' region to
* update at this point in time, and how large an update should be performed
* at once to maintain visual coherency.
*
* aInvalidRegion is the current invalid region.
* aOldValidRegion is the valid region of mTiledBuffer at the beginning of the
* current transaction.
* aRegionToPaint will be filled with the region to update. This may be empty,
* which indicates that there is no more work to do.
* aTransform is the transform required to convert from screen-space to
* layer-space.
* aScrollOffset is the current scroll offset of the primary scrollable layer.
* aResolution is the render resolution of the layer.
* aIsRepeated should be true if this function has already been called during
* this transaction.
*
* Returns true if it should be called again, false otherwise. In the case
* that aRegionToPaint is empty, this will return aIsRepeated for convenience.
*/
bool ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion,
const nsIntRegion& aOldValidRegion,
nsIntRegion& aRegionToPaint,
const gfx3DMatrix& aTransform,
const gfx::Point& aScrollOffset,
const gfxSize& aResolution,
bool aIsRepeated);
// Members // Members
BasicTiledLayerBuffer mTiledBuffer; BasicTiledLayerBuffer mTiledBuffer;
gfx::Point mLastScrollOffset; gfx::Point mLastScrollOffset;
bool mFirstPaint;
}; };
} // layers } // layers

View File

@ -134,8 +134,8 @@ TiledThebesLayerOGL::PaintedTiledLayerBuffer(const BasicTiledLayerBuffer* mTiled
mMainMemoryTiledBuffer = *mTiledBuffer; mMainMemoryTiledBuffer = *mTiledBuffer;
// TODO: Remove me once Bug 747811 lands. // TODO: Remove me once Bug 747811 lands.
delete mTiledBuffer; delete mTiledBuffer;
mRegionToUpload.Or(mRegionToUpload, mMainMemoryTiledBuffer.GetLastPaintRegion()); mRegionToUpload.Or(mRegionToUpload, mMainMemoryTiledBuffer.GetPaintedRegion());
mMainMemoryTiledBuffer.ClearPaintedRegion();
} }
void void
@ -284,7 +284,6 @@ TiledThebesLayerOGL::RenderLayer(int aPreviousFrameBuffer, const nsIntPoint& aOf
tileX++; tileX++;
x += w; x += w;
} }
} }
} // mozilla } // mozilla