Bug 750535 - Fix invalidation of retained tiles. r=ajuma a=blocking-fennec

When the layer size changed, reused tiles that then fell out of the layer area
would be incorrectly rendered. Fix this by deriving the rendered content area
from set display-ports and correctly invalidating when harvesting tiles, and
clipping when drawing them.

--HG--
extra : rebase_source : 6427be89b9cfb1e54feb0582fb64fa97d7d42bde
This commit is contained in:
Chris Lord 2012-05-03 20:55:08 -04:00
parent 253473034f
commit cf4f8d7c5f
3 changed files with 129 additions and 48 deletions

View File

@ -19,47 +19,70 @@ ReusableTileStoreOGL::~ReusableTileStoreOGL()
}
void
ReusableTileStoreOGL::HarvestTiles(TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
const nsIntSize& aContentSize,
const nsIntRegion& aOldValidRegion,
const nsIntRegion& aNewValidRegion,
const gfxSize& aOldResolution,
const gfxSize& aNewResolution)
ReusableTileStoreOGL::InvalidateTiles(TiledThebesLayerOGL* aLayer,
const nsIntRegion& aValidRegion,
const gfxSize& aResolution)
{
gfxSize scaleFactor = gfxSize(aNewResolution.width / aOldResolution.width,
aNewResolution.height / aOldResolution.height);
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
printf_stderr("Seeing if there are any tiles we can reuse\n");
printf_stderr("Invalidating reused tiles\n");
#endif
// Find out the area of the nearest display-port to invalidate retained
// tiles.
gfxRect renderBounds;
for (ContainerLayer* parent = aLayer->GetParent(); parent; parent = parent->GetParent()) {
const FrameMetrics& metrics = parent->GetFrameMetrics();
if (!metrics.mDisplayPort.IsEmpty()) {
// We use the bounds to cut down on complication/computation time.
// This will be incorrect when the transform involves rotation, but
// it'd be quite hard to retain invalid tiles correctly in this
// situation anyway.
renderBounds = parent->GetEffectiveTransform().TransformBounds(gfxRect(metrics.mDisplayPort));
break;
}
}
// If no display port was found, use the widget size from the layer manager.
if (renderBounds.IsEmpty()) {
LayerManagerOGL* manager = static_cast<LayerManagerOGL*>(aLayer->Manager());
const nsIntSize& widgetSize = manager->GetWidgetSize();
renderBounds.width = widgetSize.width;
renderBounds.height = widgetSize.height;
}
// Iterate over existing harvested tiles and release any that are contained
// within the new valid region, or that fall outside of the layer.
// within the new valid region, the display-port or the widget area. The
// assumption is that anything within this area should be valid, so there's
// no need to keep invalid tiles there.
mContext->MakeCurrent();
for (PRUint32 i = 0; i < mTiles.Length();) {
ReusableTiledTextureOGL* tile = mTiles[i];
// Check if the tile region is contained within the new valid region.
nsIntRect tileRect;
bool release = false;
if (tile->mResolution == aNewResolution) {
if (aNewValidRegion.Contains(tile->mTileRegion)) {
if (tile->mResolution == aResolution) {
if (aValidRegion.Contains(tile->mTileRegion)) {
release = true;
} else {
tileRect = tile->mTileRegion.GetBounds();
}
} else {
nsIntRegion transformedTileRegion(tile->mTileRegion);
transformedTileRegion.ScaleRoundOut(tile->mResolution.width / aNewResolution.width,
tile->mResolution.height / aNewResolution.height);
if (aNewValidRegion.Contains(transformedTileRegion))
transformedTileRegion.ScaleRoundOut(tile->mResolution.width / aResolution.width,
tile->mResolution.height / aResolution.height);
if (aValidRegion.Contains(transformedTileRegion))
release = true;
else
tileRect = transformedTileRegion.GetBounds();
}
// If the tile region wasn't contained within the valid region, check if
// it intersects with the currently rendered region.
if (!release) {
if (tileRect.width > aContentSize.width ||
tileRect.height > aContentSize.height)
// Transform the tile region to see if it falls inside the rendered bounds
gfxRect tileBounds = aLayer->GetEffectiveTransform().TransformBounds(gfxRect(tileRect));
if (renderBounds.Contains(tileBounds))
release = true;
}
@ -76,6 +99,22 @@ ReusableTileStoreOGL::HarvestTiles(TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
i++;
}
}
void
ReusableTileStoreOGL::HarvestTiles(TiledThebesLayerOGL* aLayer,
TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
const nsIntRegion& aOldValidRegion,
const nsIntRegion& aNewValidRegion,
const gfxSize& aOldResolution,
const gfxSize& aNewResolution)
{
gfxSize scaleFactor = gfxSize(aNewResolution.width / aOldResolution.width,
aNewResolution.height / aOldResolution.height);
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
printf_stderr("Seeing if there are any tiles we can reuse\n");
#endif
// Iterate over the tiles and decide which ones we're going to harvest.
// We harvest any tile that is entirely outside of the new valid region, or
@ -138,6 +177,9 @@ ReusableTileStoreOGL::HarvestTiles(TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
x += w;
}
// Make sure we don't hold onto tiles that may cause visible rendering glitches
InvalidateTiles(aLayer, aNewValidRegion, aNewResolution);
// Now prune our reused tile store of its oldest tiles if it gets too large.
while (mTiles.Length() > aVideoMemoryTiledBuffer->GetTileCount() * mSizeLimit) {
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
@ -156,13 +198,36 @@ ReusableTileStoreOGL::HarvestTiles(TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
void
ReusableTileStoreOGL::DrawTiles(TiledThebesLayerOGL* aLayer,
const nsIntSize& aContentSize,
const nsIntRegion& aValidRegion,
const gfxSize& aResolution,
const gfx3DMatrix& aTransform,
const nsIntPoint& aRenderOffset,
Layer* aMaskLayer)
{
// Walk up the tree, looking for a display-port - if we find one, we know
// that this layer represents a content node and we can use its first
// scrollable child, in conjunction with its content area and viewport offset
// to establish the screen coordinates to which the content area will be
// rendered.
gfxRect contentBounds, displayPort;
ContainerLayer* scrollableLayer = nsnull;
for (ContainerLayer* parent = aLayer->GetParent(); parent; parent = parent->GetParent()) {
const FrameMetrics& parentMetrics = parent->GetFrameMetrics();
if (parentMetrics.IsScrollable())
scrollableLayer = parent;
if (!parentMetrics.mDisplayPort.IsEmpty() && scrollableLayer) {
displayPort = parent->GetEffectiveTransform().
TransformBounds(gfxRect(parentMetrics.mDisplayPort));
const FrameMetrics& metrics = scrollableLayer->GetFrameMetrics();
const nsIntSize& contentSize = metrics.mContentSize;
const nsIntPoint& contentOrigin = metrics.mViewportScrollOffset;
gfxRect contentRect = gfxRect(-contentOrigin.x, -contentOrigin.y,
contentSize.width, contentSize.height);
contentBounds = scrollableLayer->GetEffectiveTransform().TransformBounds(contentRect);
break;
}
}
// Render old tiles to fill in gaps we haven't had the time to render yet.
for (PRUint32 i = 0; i < mTiles.Length(); i++) {
ReusableTiledTextureOGL* tile = mTiles[i];
@ -171,37 +236,48 @@ ReusableTileStoreOGL::DrawTiles(TiledThebesLayerOGL* aLayer,
gfxSize scaleFactor = gfxSize(aResolution.width / tile->mResolution.width,
aResolution.height / tile->mResolution.height);
// Get the valid tile region, in the given coordinate space.
nsIntRegion transformedTileRegion(tile->mTileRegion);
if (aResolution != tile->mResolution)
transformedTileRegion.ScaleRoundOut(scaleFactor.width, scaleFactor.height);
// Skip drawing tiles that will be completely drawn over.
if (aValidRegion.Contains(transformedTileRegion))
continue;
// Skip drawing tiles that have fallen outside of the layer area (these
// will be discarded next time tiles are harvested).
nsIntRect transformedTileRect = transformedTileRegion.GetBounds();
if (transformedTileRect.XMost() > aContentSize.width ||
transformedTileRect.YMost() > aContentSize.height)
continue;
// Reconcile the resolution difference by adjusting the transform.
gfx3DMatrix transform = aTransform;
if (aResolution != tile->mResolution)
transform.Scale(scaleFactor.width, scaleFactor.height, 1);
// XXX We should clip here to make sure we don't overlap with the valid
// region, otherwise we may end up with rendering artifacts on
// semi-transparent layers.
// Similarly, if we have multiple tiles covering the same area, we will
// Subtract the layer's valid region from the tile region.
nsIntRegion transformedValidRegion(aValidRegion);
if (aResolution != tile->mResolution)
transformedValidRegion.ScaleRoundOut(1.0f/scaleFactor.width,
1.0f/scaleFactor.height);
nsIntRegion tileRegion;
tileRegion.Sub(tile->mTileRegion, transformedValidRegion);
// Subtract the display-port from the tile region.
if (!displayPort.IsEmpty()) {
gfxRect transformedRenderBounds = transform.Inverse().TransformBounds(displayPort);
tileRegion.Sub(tileRegion, nsIntRect(transformedRenderBounds.x,
transformedRenderBounds.y,
transformedRenderBounds.width,
transformedRenderBounds.height));
}
// Intersect the tile region with the content area.
if (!contentBounds.IsEmpty()) {
gfxRect transformedRenderBounds = transform.Inverse().TransformBounds(contentBounds);
tileRegion.And(tileRegion, nsIntRect(transformedRenderBounds.x,
transformedRenderBounds.y,
transformedRenderBounds.width,
transformedRenderBounds.height));
}
// If the tile region is empty, skip drawing.
if (tileRegion.IsEmpty())
continue;
// XXX If we have multiple tiles covering the same area, we will
// end up with rendering artifacts if the aLayer isn't opaque.
uint16_t tileStartX = tile->mTileOrigin.x % tile->mTileSize;
uint16_t tileStartY = tile->mTileOrigin.y % tile->mTileSize;
nsIntPoint tileOffset(tile->mTileOrigin.x - tileStartX, tile->mTileOrigin.y - tileStartY);
nsIntSize textureSize(tile->mTileSize, tile->mTileSize);
aLayer->RenderTile(tile->mTexture, transform, aRenderOffset, tile->mTileRegion, tileOffset, textureSize, aMaskLayer);
aLayer->RenderTile(tile->mTexture, transform, aRenderOffset, tileRegion, tileOffset, textureSize, aMaskLayer);
}
}

View File

@ -64,8 +64,8 @@ public:
// and resolution of the data currently in aVideoMemoryTiledBuffer, and
// aNewValidRegion and aNewResolution should be the valid region and
// resolution of the data that is about to update aVideoMemoryTiledBuffer.
void HarvestTiles(TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
const nsIntSize& aContentSize,
void HarvestTiles(TiledThebesLayerOGL* aLayer,
TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
const nsIntRegion& aOldValidRegion,
const nsIntRegion& aNewValidRegion,
const gfxSize& aOldResolution,
@ -75,13 +75,20 @@ public:
// Differences in resolution will be reconciled via altering the given
// transformation.
void DrawTiles(TiledThebesLayerOGL* aLayer,
const nsIntSize& aContentSize,
const nsIntRegion& aValidRegion,
const gfxSize& aResolution,
const gfx3DMatrix& aTransform,
const nsIntPoint& aRenderOffset,
Layer* aMaskLayer);
protected:
// Invalidates tiles contained within the valid region, or intersecting with
// the currently rendered region (discovered by looking for a display-port,
// or failing that, looking at the widget size).
void InvalidateTiles(TiledThebesLayerOGL* aLayer,
const nsIntRegion& aValidRegion,
const gfxSize& aResolution);
private:
// This GLContext should correspond to the one used in any TiledLayerBufferOGL
// that is passed into HarvestTiles and DrawTiles.

View File

@ -155,9 +155,8 @@ TiledThebesLayerOGL::ProcessUploadQueue()
resolution.height *= metrics.mResolution.height;
}
const FrameMetrics& metrics = GetParent()->GetFrameMetrics();
mReusableTileStore->HarvestTiles(&mVideoMemoryTiledBuffer,
metrics.mContentSize,
mReusableTileStore->HarvestTiles(this,
&mVideoMemoryTiledBuffer,
mVideoMemoryTiledBuffer.GetValidRegion(),
mMainMemoryTiledBuffer.GetValidRegion(),
mVideoMemoryTiledBuffer.GetResolution(),
@ -225,8 +224,7 @@ TiledThebesLayerOGL::RenderLayer(int aPreviousFrameBuffer, const nsIntPoint& aOf
// Render old tiles to fill in gaps we haven't had the time to render yet.
if (mReusableTileStore) {
const FrameMetrics& metrics = GetParent()->GetFrameMetrics();
mReusableTileStore->DrawTiles(this, metrics.mContentSize,
mReusableTileStore->DrawTiles(this,
mVideoMemoryTiledBuffer.GetValidRegion(),
mVideoMemoryTiledBuffer.GetResolution(),
GetEffectiveTransform(), aOffset, maskLayer);