mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 20:17:37 +00:00
387 lines
16 KiB
C++
387 lines
16 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "ReusableTileStoreOGL.h"
|
|
|
|
#include "GLContext.h"
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
ReusableTileStoreOGL::~ReusableTileStoreOGL()
|
|
{
|
|
if (mTiles.Length() == 0)
|
|
return;
|
|
|
|
mContext->MakeCurrent();
|
|
for (uint32_t i = 0; i < mTiles.Length(); i++)
|
|
mContext->fDeleteTextures(1, &mTiles[i]->mTexture.mTextureHandle);
|
|
mTiles.Clear();
|
|
}
|
|
|
|
void
|
|
ReusableTileStoreOGL::InvalidateTiles(TiledThebesLayerOGL* aLayer,
|
|
const nsIntRegion& aValidRegion,
|
|
const gfxSize& aResolution)
|
|
{
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
printf_stderr("Invalidating reused tiles\n");
|
|
#endif
|
|
|
|
// XXX We use GetTransform instead of GetEffectiveTransform in this function
|
|
// as we want the transform of the shadowable layers and not that of the
|
|
// shadow layers, which may have been modified due to async scrolling/
|
|
// zooming.
|
|
gfx3DMatrix transform = aLayer->GetTransform();
|
|
|
|
// Find out the area of the nearest display-port to invalidate retained
|
|
// tiles.
|
|
gfxRect displayPort;
|
|
gfxSize parentResolution = aResolution;
|
|
for (ContainerLayer* parent = aLayer->GetParent(); parent; parent = parent->GetParent()) {
|
|
const FrameMetrics& metrics = parent->GetFrameMetrics();
|
|
if (displayPort.IsEmpty()) {
|
|
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.
|
|
displayPort = gfxRect(metrics.mDisplayPort.x,
|
|
metrics.mDisplayPort.y,
|
|
metrics.mDisplayPort.width,
|
|
metrics.mDisplayPort.height);
|
|
displayPort.ScaleRoundOut(parentResolution.width, parentResolution.height);
|
|
}
|
|
parentResolution.width /= metrics.mResolution.width;
|
|
parentResolution.height /= metrics.mResolution.height;
|
|
}
|
|
if (parent->UseIntermediateSurface()) {
|
|
transform.PreMultiply(parent->GetTransform());
|
|
}
|
|
}
|
|
|
|
// If no display port was found, use the widget size from the layer manager.
|
|
if (displayPort.IsEmpty()) {
|
|
LayerManagerOGL* manager = static_cast<LayerManagerOGL*>(aLayer->Manager());
|
|
const nsIntSize& widgetSize = manager->GetWidgetSize();
|
|
displayPort.width = widgetSize.width;
|
|
displayPort.height = widgetSize.height;
|
|
}
|
|
|
|
// Transform the display port into layer space.
|
|
displayPort = transform.Inverse().TransformBounds(displayPort);
|
|
|
|
// Iterate over existing harvested tiles and release any that are contained
|
|
// 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();
|
|
const nsIntRegion& visibleRegion = aLayer->GetEffectiveVisibleRegion();
|
|
for (uint32_t i = 0; i < mTiles.Length();) {
|
|
ReusableTiledTextureOGL* tile = mTiles[i];
|
|
|
|
nsIntRegion tileRegion = tile->mTileRegion;
|
|
if (tile->mResolution != aResolution) {
|
|
tileRegion.ScaleRoundOut(tile->mResolution.width / aResolution.width,
|
|
tile->mResolution.height / aResolution.height);
|
|
}
|
|
|
|
// Check if the tile region is contained within the new valid region.
|
|
nsIntRect tileRect;
|
|
bool release = false;
|
|
bool forceKeep = false;
|
|
if (aValidRegion.Contains(tile->mTileRegion)) {
|
|
release = true;
|
|
} else if (visibleRegion.Contains(tile->mTileRegion)) {
|
|
forceKeep = true;
|
|
} else {
|
|
tileRect = tile->mTileRegion.GetBounds();
|
|
}
|
|
|
|
// Keep tiles that are within the visible region but outside of the valid
|
|
// region, this signifies area that is in a progressive update and will
|
|
// shortly be refreshed.
|
|
if (forceKeep) {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
// If the tile region wasn't contained within the valid region, check if
|
|
// it intersects with the currently rendered region.
|
|
if (!release) {
|
|
if (displayPort.Contains(tileRegion.GetBounds())) {
|
|
release = true;
|
|
}
|
|
}
|
|
|
|
if (release) {
|
|
#if GFX_TILEDLAYER_PREF_WARNINGS
|
|
nsIntRect tileBounds = tile->mTileRegion.GetBounds();
|
|
printf_stderr("Releasing obsolete reused tile at %d,%d, x%f\n",
|
|
tileBounds.x, tileBounds.y, tile->mResolution.width);
|
|
#endif
|
|
mContext->fDeleteTextures(1, &tile->mTexture.mTextureHandle);
|
|
mTiles.RemoveElementAt(i);
|
|
continue;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void
|
|
ReusableTileStoreOGL::HarvestTiles(TiledThebesLayerOGL* aLayer,
|
|
TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
|
|
const nsIntRegion& aOldValidRegion,
|
|
const nsIntRegion& aNewValidRegion,
|
|
const gfxSize& aOldResolution,
|
|
const gfxSize& aNewResolution)
|
|
{
|
|
NS_ASSERTION(aVideoMemoryTiledBuffer->GetResolution() == 1.0f,
|
|
"ReusableTileStoreOGL cannot harvest scaled tiles!");
|
|
|
|
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 visible region, or
|
|
// any tile that is partially outside of the visible region and whose
|
|
// resolution has changed.
|
|
// XXX Tile iteration needs to be abstracted, or have some utility functions
|
|
// to make it simpler.
|
|
uint16_t tileSize = aVideoMemoryTiledBuffer->GetTileLength();
|
|
nsIntRect validBounds = aOldValidRegion.GetBounds();
|
|
nsIntRegion visibleRegion = aLayer->GetEffectiveVisibleRegion();
|
|
for (int x = validBounds.x; x < validBounds.XMost();) {
|
|
int w = tileSize - aVideoMemoryTiledBuffer->GetTileStart(x);
|
|
if (x + w > validBounds.x + validBounds.width)
|
|
w = validBounds.x + validBounds.width - x;
|
|
|
|
for (int y = validBounds.y; y < validBounds.YMost();) {
|
|
int h = tileSize - aVideoMemoryTiledBuffer->GetTileStart(y);
|
|
if (y + h > validBounds.y + validBounds.height)
|
|
h = validBounds.y + validBounds.height - y;
|
|
|
|
// If the new valid region doesn't contain this tile region,
|
|
// harvest the tile.
|
|
nsIntRegion tileRegion;
|
|
tileRegion.And(aOldValidRegion, nsIntRect(x, y, w, h));
|
|
|
|
nsIntRegion intersectingRegion;
|
|
bool retainTile = false;
|
|
if (fabs(aNewResolution.width - aOldResolution.width) > 1e-6) {
|
|
// Reconcile resolution changes.
|
|
// If the resolution changes, we know the backing layer will have been
|
|
// invalidated, so retain tiles that are partially encompassed by the
|
|
// new valid area, instead of just tiles that don't intersect at all.
|
|
nsIntRegion transformedTileRegion(tileRegion);
|
|
transformedTileRegion.ScaleRoundOut(scaleFactor.width, scaleFactor.height);
|
|
if (!visibleRegion.Contains(transformedTileRegion))
|
|
retainTile = true;
|
|
} else if (intersectingRegion.And(tileRegion, visibleRegion).IsEmpty()) {
|
|
retainTile = true;
|
|
}
|
|
|
|
if (retainTile) {
|
|
TiledTexture removedTile;
|
|
if (aVideoMemoryTiledBuffer->RemoveTile(nsIntPoint(x, y), removedTile)) {
|
|
ReusableTiledTextureOGL* reusedTile =
|
|
new ReusableTiledTextureOGL(removedTile, nsIntPoint(x, y), tileRegion,
|
|
tileSize, aOldResolution);
|
|
mTiles.AppendElement(reusedTile);
|
|
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
bool replacedATile = false;
|
|
#endif
|
|
// Remove any tile that is superseded by this new tile.
|
|
// (same resolution, same area)
|
|
for (uint32_t i = 0; i < mTiles.Length() - 1; i++) {
|
|
// XXX Perhaps we should check the region instead of the origin
|
|
// so a partial tile doesn't replace a full older tile?
|
|
if (aVideoMemoryTiledBuffer->RoundDownToTileEdge(mTiles[i]->mTileOrigin.x) == aVideoMemoryTiledBuffer->RoundDownToTileEdge(x) &&
|
|
aVideoMemoryTiledBuffer->RoundDownToTileEdge(mTiles[i]->mTileOrigin.y) == aVideoMemoryTiledBuffer->RoundDownToTileEdge(y) &&
|
|
abs(mTiles[i]->mResolution.width - aOldResolution.width) < 1e-5) {
|
|
mContext->fDeleteTextures(1, &mTiles[i]->mTexture.mTextureHandle);
|
|
mTiles.RemoveElementAt(i);
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
replacedATile = true;
|
|
#endif
|
|
// There should only be one similar tile
|
|
break;
|
|
}
|
|
}
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
if (replacedATile) {
|
|
printf_stderr("Replaced tile at %d,%d, x%f for reuse\n", x, y, aOldResolution.width);
|
|
} else {
|
|
printf_stderr("New tile at %d,%d, x%f for reuse\n", x, y, aOldResolution.width);
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef GFX_TILEDLAYER_PREF_WARNINGS
|
|
else
|
|
printf_stderr("Failed to retain tile for reuse\n");
|
|
#endif
|
|
}
|
|
|
|
y += h;
|
|
}
|
|
|
|
x += w;
|
|
}
|
|
|
|
// Make sure we don't hold onto tiles that may cause visible rendering glitches
|
|
InvalidateTiles(aLayer, aNewValidRegion, aNewResolution);
|
|
|
|
// Calculate the maximum number of tiles we should have. We base this on the
|
|
// number of tiles it would take to cover the visible region.
|
|
uint32_t maxTiles = 0;
|
|
while (!visibleRegion.IsEmpty()) {
|
|
nsIntRegionRectIterator it(visibleRegion);
|
|
const nsIntRect* rect = it.Next();
|
|
nsIntRect tileRect;
|
|
tileRect.x = aVideoMemoryTiledBuffer->RoundDownToTileEdge(rect->x);
|
|
tileRect.y = aVideoMemoryTiledBuffer->RoundDownToTileEdge(rect->y);
|
|
tileRect.width = aVideoMemoryTiledBuffer->RoundDownToTileEdge(rect->XMost() + tileSize - 1) - tileRect.x;
|
|
tileRect.height = aVideoMemoryTiledBuffer->RoundDownToTileEdge(rect->YMost() + tileSize - 1) - tileRect.y;
|
|
visibleRegion.Sub(visibleRegion, tileRect);
|
|
maxTiles += (tileRect.width / tileSize) * (tileRect.height / tileSize);
|
|
}
|
|
maxTiles *= mSizeLimit;
|
|
|
|
// Now prune our reused tile store of its oldest tiles if it gets too large.
|
|
while (mTiles.Length() > maxTiles) {
|
|
#if GFX_TILEDLAYER_PREF_WARNINGS
|
|
nsIntRect tileBounds = mTiles[0]->mTileRegion.GetBounds();
|
|
printf_stderr("Releasing old reused tile at %d,%d, x%f\n",
|
|
tileBounds.x, tileBounds.y, mTiles[0]->mResolution.width);
|
|
#endif
|
|
mContext->fDeleteTextures(1, &mTiles[0]->mTexture.mTextureHandle);
|
|
mTiles.RemoveElementAt(0);
|
|
}
|
|
|
|
#if GFX_TILEDLAYER_PREF_WARNINGS
|
|
printf_stderr("Retained %d tiles\n", mTiles.Length());
|
|
#endif
|
|
}
|
|
|
|
void
|
|
ReusableTileStoreOGL::DrawTiles(TiledThebesLayerOGL* aLayer,
|
|
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 compositionBounds;
|
|
ContainerLayer* scrollableLayer = nullptr;
|
|
for (ContainerLayer* parent = aLayer->GetParent(); parent; parent = parent->GetParent()) {
|
|
const FrameMetrics& parentMetrics = parent->GetFrameMetrics();
|
|
if (parentMetrics.IsScrollable())
|
|
scrollableLayer = parent;
|
|
if (!parentMetrics.mDisplayPort.IsEmpty() && scrollableLayer) {
|
|
// Get the composition bounds, so as not to waste rendering time.
|
|
compositionBounds = gfxRect(parentMetrics.mCompositionBounds);
|
|
|
|
// Calculate the scale transform applied to the root layer to determine
|
|
// the content resolution.
|
|
Layer* rootLayer = aLayer->Manager()->GetRoot();
|
|
const gfx3DMatrix& rootTransform = rootLayer->GetTransform();
|
|
float scaleX = rootTransform.GetXScale();
|
|
float scaleY = rootTransform.GetYScale();
|
|
|
|
// Get the content document bounds, in screen-space.
|
|
const FrameMetrics& metrics = scrollableLayer->GetFrameMetrics();
|
|
const nsIntSize& contentSize = metrics.mContentRect.Size();
|
|
gfx::Point scrollOffset =
|
|
gfx::Point((metrics.mScrollOffset.x * metrics.LayersPixelsPerCSSPixel().width) / scaleX,
|
|
(metrics.mScrollOffset.y * metrics.LayersPixelsPerCSSPixel().height) / scaleY);
|
|
const nsIntPoint& contentOrigin = metrics.mContentRect.TopLeft() -
|
|
nsIntPoint(NS_lround(scrollOffset.x), NS_lround(scrollOffset.y));
|
|
gfxRect contentRect = gfxRect(contentOrigin.x, contentOrigin.y,
|
|
contentSize.width, contentSize.height);
|
|
gfxRect contentBounds = scrollableLayer->GetEffectiveTransform().
|
|
TransformBounds(contentRect);
|
|
|
|
// Clip the composition bounds to the content bounds
|
|
compositionBounds.IntersectRect(compositionBounds, contentBounds);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Render old tiles to fill in gaps we haven't had the time to render yet.
|
|
// Simultaneously reorder tiles in LRU order.
|
|
nsTArray< nsAutoPtr<ReusableTiledTextureOGL> > reorderedTiles(mTiles.Length());
|
|
for (uint32_t i = 0, lastOldTile = 0; i < mTiles.Length(); i++) {
|
|
ReusableTiledTextureOGL* tile = mTiles[i];
|
|
|
|
// Work out the scaling factor in case of resolution differences.
|
|
gfxSize scaleFactor = gfxSize(aResolution.width / tile->mResolution.width,
|
|
aResolution.height / tile->mResolution.height);
|
|
|
|
// Reconcile the resolution difference by adjusting the transform.
|
|
gfx3DMatrix transform = aTransform;
|
|
if (aResolution != tile->mResolution)
|
|
transform.Scale(scaleFactor.width, scaleFactor.height, 1);
|
|
gfx3DMatrix inverseTransform = transform.Inverse();
|
|
|
|
// 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);
|
|
|
|
// Intersect the tile region with the composition bounds.
|
|
if (!compositionBounds.IsEmpty()) {
|
|
// Transform the composition bounds from screen space to layer space.
|
|
gfxRect transformedCompositionBounds = inverseTransform.TransformBounds(compositionBounds);
|
|
tileRegion.And(tileRegion, nsIntRect(transformedCompositionBounds.x,
|
|
transformedCompositionBounds.y,
|
|
transformedCompositionBounds.width,
|
|
transformedCompositionBounds.height));
|
|
}
|
|
|
|
// If the tile region is empty, skip drawing.
|
|
if (tileRegion.IsEmpty()) {
|
|
reorderedTiles.InsertElementAt(lastOldTile++, mTiles[i].forget());
|
|
continue;
|
|
}
|
|
reorderedTiles.AppendElement(mTiles[i].forget());
|
|
|
|
// XXX If we have multiple tiles covering the same area, we will
|
|
// end up with rendering artifacts if the aLayer isn't opaque.
|
|
int32_t tileStartX;
|
|
int32_t tileStartY;
|
|
if (tile->mTileOrigin.x >= 0) {
|
|
tileStartX = tile->mTileOrigin.x % tile->mTileSize;
|
|
} else {
|
|
tileStartX = (tile->mTileSize - (-tile->mTileOrigin.x % tile->mTileSize)) % tile->mTileSize;
|
|
}
|
|
if (tile->mTileOrigin.y >= 0) {
|
|
tileStartY = tile->mTileOrigin.y % tile->mTileSize;
|
|
} else {
|
|
tileStartY = (tile->mTileSize - (-tile->mTileOrigin.y % tile->mTileSize)) % tile->mTileSize;
|
|
}
|
|
nsIntPoint tileOffset(tile->mTileOrigin.x - tileStartX, tile->mTileOrigin.y - tileStartY);
|
|
nsIntSize textureSize(tile->mTileSize, tile->mTileSize);
|
|
aLayer->RenderTile(tile->mTexture, transform, aRenderOffset, tileRegion, tileOffset, textureSize, aMaskLayer);
|
|
}
|
|
|
|
mTiles.SwapElements(reorderedTiles);
|
|
}
|
|
|
|
} // mozilla
|
|
} // layers
|