Tile cache doesn't have an upper limit
authorandersca@apple.com <andersca@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 1 Feb 2012 19:59:47 +0000 (19:59 +0000)
committerandersca@apple.com <andersca@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 1 Feb 2012 19:59:47 +0000 (19:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=77564
<rdar://problem/10710744>

Reviewed by Darin Adler.

Cache enough tiles to cover 3x the visible height and 2x the visible width of the page,
and drop tiles that are outside that area.

* platform/graphics/ca/GraphicsLayerCA.cpp:
(WebCore::GraphicsLayerCA::platformCALayerDidCreateTiles):
Call GraphicsLayerClient::notifySyncRequired here, which will schedule a layer flush and ensure that
the page layout is up to date before the new tiles are painted.

* platform/graphics/ca/PlatformCALayerClient.h:
Add platformCALayerDidCreateTiles member function.

* platform/graphics/ca/mac/TileCache.h:
Update for new/removed member functions and member variables.

* platform/graphics/ca/mac/TileCache.mm:
(WebCore::TileCache::TileCache):
Initialize the tile revalidation timer.

(WebCore::TileCache::tileCacheLayerBoundsChanged):
If we don't have any tiles at all right now, revalidate the tiles immediately. Otherwise,
schedule the revalidation timer.

(WebCore::TileCache::setNeedsDisplayInRect):
Return early if we have no tiles.

(WebCore::TileCache::visibleRectChanged):
Schedule tile revalidation.

(WebCore::TileCache::rectForTileIndex):
New helper function that returns the bounds rect of a tile given its tile index.

(WebCore::TileCache::getTileIndexRangeForRect):
Clamp the rect to the bounds of the tile cache layer.

(WebCore::TileCache::scheduleTileRevalidation):
Schedule the revalidation timer if it hasn't already been scheduled.

(WebCore::TileCache::tileRevalidationTimerFired):
Call revalidateTiles.

(WebCore::TileCache::revalidateTiles):
Compute the tile coverage rect and remove all tiles that are outside. Create new tiles for any
parts of the tile coverage rect that don't have tiles already.

(WebCore::TileCache::tileLayerAtIndex):
Remove invalid assertions.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@106482 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp
Source/WebCore/platform/graphics/ca/GraphicsLayerCA.h
Source/WebCore/platform/graphics/ca/PlatformCALayerClient.h
Source/WebCore/platform/graphics/ca/mac/TileCache.h
Source/WebCore/platform/graphics/ca/mac/TileCache.mm

index df71f3d..6b98ca7 100644 (file)
@@ -1,3 +1,58 @@
+2012-02-01  Anders Carlsson  <andersca@apple.com>
+
+        Tile cache doesn't have an upper limit
+        https://bugs.webkit.org/show_bug.cgi?id=77564
+        <rdar://problem/10710744>
+
+        Reviewed by Darin Adler.
+
+        Cache enough tiles to cover 3x the visible height and 2x the visible width of the page,
+        and drop tiles that are outside that area.
+
+        * platform/graphics/ca/GraphicsLayerCA.cpp:
+        (WebCore::GraphicsLayerCA::platformCALayerDidCreateTiles):
+        Call GraphicsLayerClient::notifySyncRequired here, which will schedule a layer flush and ensure that
+        the page layout is up to date before the new tiles are painted.
+
+        * platform/graphics/ca/PlatformCALayerClient.h:
+        Add platformCALayerDidCreateTiles member function.
+
+        * platform/graphics/ca/mac/TileCache.h:
+        Update for new/removed member functions and member variables.
+
+        * platform/graphics/ca/mac/TileCache.mm:
+        (WebCore::TileCache::TileCache):
+        Initialize the tile revalidation timer.
+
+        (WebCore::TileCache::tileCacheLayerBoundsChanged):
+        If we don't have any tiles at all right now, revalidate the tiles immediately. Otherwise,
+        schedule the revalidation timer.
+
+        (WebCore::TileCache::setNeedsDisplayInRect):
+        Return early if we have no tiles.
+
+        (WebCore::TileCache::visibleRectChanged):
+        Schedule tile revalidation.
+
+        (WebCore::TileCache::rectForTileIndex):
+        New helper function that returns the bounds rect of a tile given its tile index.
+
+        (WebCore::TileCache::getTileIndexRangeForRect):
+        Clamp the rect to the bounds of the tile cache layer.
+
+        (WebCore::TileCache::scheduleTileRevalidation):
+        Schedule the revalidation timer if it hasn't already been scheduled.
+
+        (WebCore::TileCache::tileRevalidationTimerFired):
+        Call revalidateTiles.
+
+        (WebCore::TileCache::revalidateTiles):
+        Compute the tile coverage rect and remove all tiles that are outside. Create new tiles for any
+        parts of the tile coverage rect that don't have tiles already.
+
+        (WebCore::TileCache::tileLayerAtIndex):
+        Remove invalid assertions.
+
 2012-02-01  Max Vujovic  <mvujovic@adobe.com>
 
         Add support for fixed and percent min-width on the table element for table-layout: auto to
index 4960e38..ad3ac04 100644 (file)
@@ -933,6 +933,15 @@ void GraphicsLayerCA::platformCALayerPaintContents(GraphicsContext& context, con
     paintGraphicsLayerContents(context, clip);
 }
 
+void GraphicsLayerCA::platformCALayerDidCreateTiles()
+{
+    ASSERT(m_layer->layerType() == PlatformCALayer::LayerTypeTileCacheLayer);
+
+    // Ensure that the layout is up to date before any individual tiles are painted by telling the client
+    // that it needs to sync its layer state, which will end up scheduling the layer flusher.
+    client()->notifySyncRequired(this);
+}
+
 void GraphicsLayerCA::commitLayerChangesBeforeSublayers(float pageScaleFactor, const FloatPoint& positionRelativeToBase)
 {
     if (!m_uncommittedChanges)
index e5fbdf9..e9dcda1 100644 (file)
@@ -157,6 +157,7 @@ private:
     virtual bool platformCALayerContentsOpaque() const { return contentsOpaque(); }
     virtual bool platformCALayerDrawsContent() const { return drawsContent(); }
     virtual void platformCALayerLayerDidDisplay(PlatformLayer* layer) { return layerDidDisplay(layer); }
+    virtual void platformCALayerDidCreateTiles() override;
 
     void updateOpacityOnLayer();
     
index ae1815c..be8e459 100644 (file)
@@ -59,6 +59,8 @@ public:
     virtual bool platformCALayerDrawsContent() const = 0;
     virtual void platformCALayerLayerDidDisplay(PlatformLayer*) = 0;
 
+    virtual void platformCALayerDidCreateTiles() = 0;
+
 protected:
     virtual ~PlatformCALayerClient() {}
 };
index 6bd6bbb..2c290e5 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "IntPointHash.h"
 #include "IntSize.h"
+#include "Timer.h"
 #include <wtf/HashMap.h>
 #include <wtf/Noncopyable.h>
 #include <wtf/PassOwnPtr.h>
@@ -73,12 +74,14 @@ private:
     TileCache(WebTileCacheLayer*, const IntSize& tileSize);
 
     FloatRect visibleRect() const;
-
     IntRect bounds() const;
+
+    IntRect rectForTileIndex(const TileIndex&) const;
     void getTileIndexRangeForRect(const IntRect&, TileIndex& topLeft, TileIndex& bottomRight);
 
-    IntSize numTilesForGridSize(const IntSize&) const;
-    void resizeTileGrid(const IntSize& numTiles);
+    void scheduleTileRevalidation();
+    void tileRevalidationTimerFired(Timer<TileCache>*);
+    void revalidateTiles();
 
     WebTileLayer* tileLayerAtIndex(const TileIndex&) const;
     RetainPtr<WebTileLayer> createTileLayer();
@@ -89,11 +92,9 @@ private:
     RetainPtr<CALayer> m_tileContainerLayer;
     const IntSize m_tileSize;
 
-    // Number of tiles in each dimension.
-    IntSize m_numTilesInGrid;
-
     typedef HashMap<TileIndex, RetainPtr<WebTileLayer> > TileMap;
     TileMap m_tiles;
+    Timer<TileCache> m_tileRevalidationTimer;
 
     bool m_acceleratesDrawing;
 
index 08d9115..672746f 100644 (file)
@@ -52,6 +52,7 @@ TileCache::TileCache(WebTileCacheLayer* tileCacheLayer, const IntSize& tileSize)
     : m_tileCacheLayer(tileCacheLayer)
     , m_tileContainerLayer(adoptCF([[CALayer alloc] init]))
     , m_tileSize(tileSize)
+    , m_tileRevalidationTimer(this, &TileCache::tileRevalidationTimerFired)
     , m_acceleratesDrawing(false)
     , m_tileDebugBorderWidth(0)
 {
@@ -63,11 +64,14 @@ TileCache::TileCache(WebTileCacheLayer* tileCacheLayer, const IntSize& tileSize)
 
 void TileCache::tileCacheLayerBoundsChanged()
 {
-    IntSize numTilesInGrid = numTilesForGridSize(bounds().size());
-    if (numTilesInGrid == m_numTilesInGrid)
+    if (m_tiles.isEmpty()) {
+        // We must revalidate immediately instead of using a timer when there are
+        // no tiles to avoid a flash when transitioning from one page to another.
+        revalidateTiles();
         return;
+    }
 
-    resizeTileGrid(numTilesInGrid);
+    scheduleTileRevalidation();
 }
 
 void TileCache::setNeedsDisplay()
@@ -77,7 +81,7 @@ void TileCache::setNeedsDisplay()
 
 void TileCache::setNeedsDisplayInRect(const IntRect& rect)
 {
-    if (m_numTilesInGrid.isZero())
+    if (m_tiles.isEmpty())
         return;
 
     // Find the tiles that need to be invalidated.
@@ -169,7 +173,7 @@ void TileCache::setAcceleratesDrawing(bool acceleratesDrawing)
 
 void TileCache::visibleRectChanged()
 {
-    // FIXME: Implement.
+    scheduleTileRevalidation();
 }
 
 void TileCache::setTileDebugBorderWidth(float borderWidth)
@@ -219,60 +223,104 @@ IntRect TileCache::bounds() const
     return IntRect(IntPoint(), IntSize([m_tileCacheLayer bounds].size));
 }
 
+IntRect TileCache::rectForTileIndex(const TileIndex& tileIndex) const
+{
+    return IntRect(IntPoint(tileIndex.x() * m_tileSize.width(), tileIndex.y() * m_tileSize.height()), m_tileSize);
+}
+
 void TileCache::getTileIndexRangeForRect(const IntRect& rect, TileIndex& topLeft, TileIndex& bottomRight)
 {
-    topLeft.setX(max(rect.x() / m_tileSize.width(), 0));
-    topLeft.setY(max(rect.y() / m_tileSize.height(), 0));
-    bottomRight.setX(min(rect.maxX() / m_tileSize.width(), m_numTilesInGrid.width() - 1));
-    bottomRight.setY(min(rect.maxY() / m_tileSize.height(), m_numTilesInGrid.height() - 1));
+    IntRect clampedRect = intersection(rect, bounds());
+
+    topLeft.setX(max(clampedRect.x() / m_tileSize.width(), 0));
+    topLeft.setY(max(clampedRect.y() / m_tileSize.height(), 0));
+    bottomRight.setX(max(clampedRect.maxX() / m_tileSize.width(), 0));
+    bottomRight.setY(max(clampedRect.maxY() / m_tileSize.height(), 0));
 }
 
-IntSize TileCache::numTilesForGridSize(const IntSize& gridSize) const
+void TileCache::scheduleTileRevalidation()
 {
-    int numXTiles = ceil(static_cast<double>(gridSize.width()) / m_tileSize.width());
-    int numYTiles = ceil(static_cast<double>(gridSize.height()) / m_tileSize.height());
+    if (m_tileRevalidationTimer.isActive())
+        return;
 
-    return IntSize(numXTiles, numYTiles);
+    m_tileRevalidationTimer.startOneShot(0);
 }
 
-void TileCache::resizeTileGrid(const IntSize& numTilesInGrid)
+void TileCache::tileRevalidationTimerFired(Timer<TileCache>*)
 {
-    [CATransaction begin];
-    [CATransaction setDisableActions:YES];
+    revalidateTiles();
+}
 
-    RetainPtr<NSMutableArray> newSublayers = adoptNS([[NSMutableArray alloc] initWithCapacity:numTilesInGrid.width() * numTilesInGrid.height()]);
+void TileCache::revalidateTiles()
+{
+    IntRect tileCoverageRect = enclosingIntRect(visibleRect());
+    if (tileCoverageRect.isEmpty())
+        return;
+
+    // Inflate the coverage rect so that it covers 2x of the visible width and 3x of the visible height.
+    // These values were chosen because it's more common to have tall pages and to scroll vertically,
+    // so we keep more tiles above and below the current area.
+    tileCoverageRect.inflateX(tileCoverageRect.width() / 2);
+    tileCoverageRect.inflateY(tileCoverageRect.height());
+
+    Vector<TileIndex> tilesToRemove;
 
-    for (int y = 0; y < numTilesInGrid.height(); ++y) {
-        for (int x = 0; x < numTilesInGrid.width(); ++x) {
-            RetainPtr<WebTileLayer> tileLayer;
+    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
+        const TileIndex& tileIndex = it->first;
 
-            if (x < m_numTilesInGrid.width() && y < m_numTilesInGrid.height()) {
-                // We can reuse the tile layer at this index.
-                tileLayer = tileLayerAtIndex(TileIndex(x, y));
-            } else {
-                tileLayer = createTileLayer();
-                m_tiles.set(TileIndex(x, y), tileLayer.get());
+        WebTileLayer* tileLayer = it->second.get();
+
+        if (!rectForTileIndex(tileIndex).intersects(tileCoverageRect)) {
+            // Remove this layer.
+            [tileLayer removeFromSuperlayer];
+            [tileLayer setTileCache:0];
+
+            tilesToRemove.append(tileIndex);
+        }
+    }
+
+    // FIXME: Be more clever about which tiles to remove. We might not want to remove all
+    // the tiles that are outside the coverage rect. When we know that we're going to be scrolling up,
+    // we might want to remove the ones below the coverage rect but keep the ones above.
+    for (size_t i = 0; i < tilesToRemove.size(); ++i)
+        m_tiles.remove(tilesToRemove[i]);
+
+    TileIndex topLeft;
+    TileIndex bottomRight;
+    getTileIndexRangeForRect(tileCoverageRect, topLeft, bottomRight);
+
+    bool didCreateNewTiles = false;
+
+    for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
+        for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
+            TileIndex tileIndex(x, y);
+
+            pair<TileMap::iterator, bool> result = m_tiles.add(tileIndex, 0);
+            if (result.first->second) {
+                // We already have a layer for this tile.
+                continue;
             }
 
+            didCreateNewTiles = true;
+
+            RetainPtr<WebTileLayer> tileLayer = createTileLayer();
+            result.first->second = tileLayer.get();
+
+            [tileLayer.get() setNeedsDisplay];
             [tileLayer.get() setPosition:CGPointMake(x * m_tileSize.width(), y * m_tileSize.height())];
-            [newSublayers.get() addObject:tileLayer.get()];
+            [m_tileContainerLayer.get() addSublayer:tileLayer.get()];
         }
     }
 
-    // FIXME: Make sure to call setTileCache:0 on the layers that get thrown away here.
-    [m_tileContainerLayer.get() setSublayers:newSublayers.get()];
-    m_numTilesInGrid = numTilesInGrid;
+    if (!didCreateNewTiles)
+        return;
 
-    [CATransaction commit];
+    PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
+    platformLayer->owner()->platformCALayerDidCreateTiles();
 }
 
 WebTileLayer* TileCache::tileLayerAtIndex(const TileIndex& index) const
 {
-    ASSERT(index.x() >= 0);
-    ASSERT(index.x() <= m_numTilesInGrid.width());
-    ASSERT(index.y() >= 0);
-    ASSERT(index.y() <= m_numTilesInGrid.height());
-
     return m_tiles.get(index).get();
 }