/* 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(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 > 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