Title: [137387] trunk/Source/WebCore
Revision
137387
Author
[email protected]
Date
2012-12-11 16:47:24 -0800 (Tue, 11 Dec 2012)

Log Message

Enhance the TileCache to cache more tiles
https://bugs.webkit.org/show_bug.cgi?id=104612

Reviewed by Tim Horton.

Add smarts to the TileCache so that it keeps some tiles around
for longer and can prepopulate tiles in to-be-scrolled-to regions.

The new tile management is as follows:
* As before, we expand the visible rect to create a "tile coverage rect"
which is twice the width, and three times the height of the visible rect.
* At the edges, this coverage rect is constrained by them, rather than being
truncated as before (leading to larger initial tile coverage).
* When tiles leave the coverage rect, we keep them around but track
them in cohorts (where a cohort is a set of tiles that left the coverage rect
at the same time).
* If a repaint comes in for a "secondary" tile, we don't paint it, but mark
the tile as being stale. It will get repainted (in full) if it moves back inside
the coverage rect.
* Cohorts are kept around for 2 seconds after they were created. A repeating
1-second timer clears tiles in old cohorts.
* Secondary tiles are pruned if the visible rect changes, or if zooming happens.
* TiledBacking exposes a way to prepopulate a rect with tiles; this is called
when scrolling (e.g. via Home/End keys). Tiles created this way (if outside
the coverage rect) go into a cohort.

* page/FrameView.cpp:
(WebCore::FrameView::requestScrollPositionUpdate): Tell the TiledBacking
to prepopulate some tiles.
* platform/graphics/TiledBacking.h:
(TiledBacking): Add prepopulateRect().
* platform/graphics/ca/mac/TileCache.h:
(TileInfo): The TileMap contains TileInfos, which contain a pointer
to the layer, their cohort, and a 'stale content' flag.
(WebCore::TileCache::TileInfo::TileInfo):
(TileCache):
(TileCohortInfo):
(WebCore::TileCache::TileCohortInfo::TileCohortInfo):
* platform/graphics/ca/mac/TileCache.mm:
(WebCore::TileCache::TileCache):
(WebCore::TileCache::~TileCache):
(WebCore::TileCache::setNeedsDisplay):
(WebCore::TileCache::setNeedsDisplayInRect):
(WebCore::TileCache::setTileNeedsDisplayInRect):
(WebCore::TileCache::setScale):
(WebCore::TileCache::setAcceleratesDrawing):
(WebCore::TileCache::setTilesOpaque):
(WebCore::TileCache::prepopulateRect):
(WebCore::TileCache::setTileDebugBorderWidth):
(WebCore::TileCache::setTileDebugBorderColor):
(WebCore::TileCache::computeTileCoverageRect):
(WebCore::TileCache::tileSizeForCoverageRect):
(WebCore::TileCache::tileRevalidationTimerFired):
(WebCore::TileCache::blankPixelCount):
(WebCore::queueTileForRemoval):
(WebCore::TileCache::removeAllTiles):
(WebCore::TileCache::removeAllSecondaryTiles):
(WebCore::TileCache::removeTilesInCohort):
(WebCore::TileCache::revalidateTiles):
(WebCore::TileCache::nextTileCohort):
(WebCore::TileCache::startedNewCohort):
(WebCore::TileCache::newestTileCohort):
(WebCore::TileCache::oldestTileCohort):
(WebCore::TileCache::scheduleCohortRemoval):
(WebCore::TileCache::cohortRemovalTimerFired):
(WebCore::TileCache::ensureTilesForRect):
(WebCore::TileCache::tileGridExtent):
(WebCore::TileCache::tileCoverageRect):
(WebCore::TileCache::tileLayerAtIndex):
(WebCore::TileCache::drawTileMapContents):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (137386 => 137387)


--- trunk/Source/WebCore/ChangeLog	2012-12-12 00:47:19 UTC (rev 137386)
+++ trunk/Source/WebCore/ChangeLog	2012-12-12 00:47:24 UTC (rev 137387)
@@ -1,5 +1,78 @@
 2012-12-11  Simon Fraser  <[email protected]>
 
+        Enhance the TileCache to cache more tiles
+        https://bugs.webkit.org/show_bug.cgi?id=104612
+
+        Reviewed by Tim Horton.
+
+        Add smarts to the TileCache so that it keeps some tiles around
+        for longer and can prepopulate tiles in to-be-scrolled-to regions.
+
+        The new tile management is as follows:
+        * As before, we expand the visible rect to create a "tile coverage rect"
+        which is twice the width, and three times the height of the visible rect.
+        * At the edges, this coverage rect is constrained by them, rather than being
+        truncated as before (leading to larger initial tile coverage).
+        * When tiles leave the coverage rect, we keep them around but track
+        them in cohorts (where a cohort is a set of tiles that left the coverage rect
+        at the same time).
+        * If a repaint comes in for a "secondary" tile, we don't paint it, but mark
+        the tile as being stale. It will get repainted (in full) if it moves back inside
+        the coverage rect.
+        * Cohorts are kept around for 2 seconds after they were created. A repeating
+        1-second timer clears tiles in old cohorts.
+        * Secondary tiles are pruned if the visible rect changes, or if zooming happens.
+        * TiledBacking exposes a way to prepopulate a rect with tiles; this is called
+        when scrolling (e.g. via Home/End keys). Tiles created this way (if outside
+        the coverage rect) go into a cohort.
+
+        * page/FrameView.cpp:
+        (WebCore::FrameView::requestScrollPositionUpdate): Tell the TiledBacking
+        to prepopulate some tiles.
+        * platform/graphics/TiledBacking.h:
+        (TiledBacking): Add prepopulateRect().
+        * platform/graphics/ca/mac/TileCache.h:
+        (TileInfo): The TileMap contains TileInfos, which contain a pointer
+        to the layer, their cohort, and a 'stale content' flag.
+        (WebCore::TileCache::TileInfo::TileInfo):
+        (TileCache):
+        (TileCohortInfo):
+        (WebCore::TileCache::TileCohortInfo::TileCohortInfo):
+        * platform/graphics/ca/mac/TileCache.mm:
+        (WebCore::TileCache::TileCache):
+        (WebCore::TileCache::~TileCache):
+        (WebCore::TileCache::setNeedsDisplay):
+        (WebCore::TileCache::setNeedsDisplayInRect):
+        (WebCore::TileCache::setTileNeedsDisplayInRect):
+        (WebCore::TileCache::setScale):
+        (WebCore::TileCache::setAcceleratesDrawing):
+        (WebCore::TileCache::setTilesOpaque):
+        (WebCore::TileCache::prepopulateRect):
+        (WebCore::TileCache::setTileDebugBorderWidth):
+        (WebCore::TileCache::setTileDebugBorderColor):
+        (WebCore::TileCache::computeTileCoverageRect):
+        (WebCore::TileCache::tileSizeForCoverageRect):
+        (WebCore::TileCache::tileRevalidationTimerFired):
+        (WebCore::TileCache::blankPixelCount):
+        (WebCore::queueTileForRemoval):
+        (WebCore::TileCache::removeAllTiles):
+        (WebCore::TileCache::removeAllSecondaryTiles):
+        (WebCore::TileCache::removeTilesInCohort):
+        (WebCore::TileCache::revalidateTiles):
+        (WebCore::TileCache::nextTileCohort):
+        (WebCore::TileCache::startedNewCohort):
+        (WebCore::TileCache::newestTileCohort):
+        (WebCore::TileCache::oldestTileCohort):
+        (WebCore::TileCache::scheduleCohortRemoval):
+        (WebCore::TileCache::cohortRemovalTimerFired):
+        (WebCore::TileCache::ensureTilesForRect):
+        (WebCore::TileCache::tileGridExtent):
+        (WebCore::TileCache::tileCoverageRect):
+        (WebCore::TileCache::tileLayerAtIndex):
+        (WebCore::TileCache::drawTileMapContents):
+
+2012-12-11  Simon Fraser  <[email protected]>
+
         Remove redundant AffineTransform in RenderSVGShape::fillAndStrokeShape()
         https://bugs.webkit.org/show_bug.cgi?id=104736
 

Modified: trunk/Source/WebCore/page/FrameView.cpp (137386 => 137387)


--- trunk/Source/WebCore/page/FrameView.cpp	2012-12-12 00:47:19 UTC (rev 137386)
+++ trunk/Source/WebCore/page/FrameView.cpp	2012-12-12 00:47:24 UTC (rev 137387)
@@ -1893,6 +1893,12 @@
 bool FrameView::requestScrollPositionUpdate(const IntPoint& position)
 {
 #if ENABLE(THREADED_SCROLLING)
+    if (TiledBacking* tiledBacking = this->tiledBacking()) {
+        IntRect visibleRect = visibleContentRect();
+        visibleRect.setLocation(position);
+        tiledBacking->prepopulateRect(visibleRect);
+    }
+
     if (Page* page = m_frame->page()) {
         if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator())
             return scrollingCoordinator->requestScrollPositionUpdate(this, position);

Modified: trunk/Source/WebCore/platform/graphics/TiledBacking.h (137386 => 137387)


--- trunk/Source/WebCore/platform/graphics/TiledBacking.h	2012-12-12 00:47:19 UTC (rev 137386)
+++ trunk/Source/WebCore/platform/graphics/TiledBacking.h	2012-12-12 00:47:24 UTC (rev 137387)
@@ -47,6 +47,8 @@
     virtual void setVisibleRect(const IntRect&) = 0;
     virtual IntRect visibleRect() const = 0;
 
+    virtual void prepopulateRect(const IntRect&) = 0;
+
     virtual void setIsInWindow(bool) = 0;
 
     enum {

Modified: trunk/Source/WebCore/platform/graphics/ca/mac/TileCache.h (137386 => 137387)


--- trunk/Source/WebCore/platform/graphics/ca/mac/TileCache.h	2012-12-12 00:47:19 UTC (rev 137386)
+++ trunk/Source/WebCore/platform/graphics/ca/mac/TileCache.h	2012-12-12 00:47:24 UTC (rev 137387)
@@ -30,6 +30,7 @@
 #include "IntRect.h"
 #include "TiledBacking.h"
 #include "Timer.h"
+#include <wtf/Deque.h>
 #include <wtf/HashMap.h>
 #include <wtf/Noncopyable.h>
 #include <wtf/PassOwnPtr.h>
@@ -82,12 +83,30 @@
 
     // Only public for the WebTileCacheMapLayer.
     void drawTileMapContents(CGContextRef, CGRect);
-    
+
+public:
+    // Only public for inline methods in the implementation file.
+    typedef IntPoint TileIndex;
+    typedef unsigned TileCohort;
+    static const TileCohort VisibleTileCohort = UINT_MAX;
+
+    struct TileInfo {
+        RetainPtr<WebTileLayer> layer;
+        TileCohort cohort; // VisibleTileCohort is visible.
+        bool hasStaleContent;
+        
+        TileInfo()
+            : cohort(VisibleTileCohort)
+            , hasStaleContent(false)
+        { }
+    };
+
 private:
     TileCache(WebTileCacheLayer*);
 
     // TiledBacking member functions.
     virtual void setVisibleRect(const IntRect&) OVERRIDE;
+    virtual void prepopulateRect(const IntRect&) OVERRIDE;
     virtual void setIsInWindow(bool) OVERRIDE;
     virtual void setTileCoverage(TileCoverage) OVERRIDE;
     virtual TileCoverage tileCoverage() const OVERRIDE { return m_tileCoverage; }
@@ -102,18 +121,38 @@
 
     IntRect bounds() const;
 
-    typedef IntPoint TileIndex;
     IntRect rectForTileIndex(const TileIndex&) const;
     void getTileIndexRangeForRect(const IntRect&, TileIndex& topLeft, TileIndex& bottomRight) const;
 
-    IntRect computeTileCoverageRect() const;
+    IntRect computeTileCoverageRect(const IntRect& previousVisibleRect) const;
     IntSize tileSizeForCoverageRect(const IntRect&) const;
 
     void scheduleTileRevalidation(double interval);
     void tileRevalidationTimerFired(Timer<TileCache>*);
-    void revalidateTiles();
+
+    void scheduleCohortRemoval();
+    void cohortRemovalTimerFired(Timer<TileCache>*);
+    
+    enum TileValidationPolicy {
+        KeepSecondaryTiles,
+        PruneSecondaryTiles
+    };
+    void revalidateTiles(TileValidationPolicy = KeepSecondaryTiles);
+    void ensureTilesForRect(const IntRect&);
     void updateTileCoverageMap();
 
+    void removeAllTiles();
+    void removeAllSecondaryTiles();
+    void removeTilesInCohort(TileCohort);
+
+    TileCohort nextTileCohort() const;
+    void startedNewCohort(TileCohort);
+    
+    TileCohort newestTileCohort() const;
+    TileCohort oldestTileCohort() const;
+
+    void setTileNeedsDisplayInRect(const TileIndex&, TileInfo&, const IntRect& repaintRectInTileCoords, const IntRect& coverageRectInTileCoords);
+
     WebTileLayer* tileLayerAtIndex(const TileIndex&) const;
     RetainPtr<WebTileLayer> createTileLayer(const IntRect&);
 
@@ -126,12 +165,26 @@
 
     IntSize m_tileSize;
     IntRect m_visibleRect;
+    IntRect m_visibleRectAtLastRevalidate;
 
-    typedef HashMap<TileIndex, RetainPtr<WebTileLayer> > TileMap;
+    typedef HashMap<TileIndex, TileInfo> TileMap;
     TileMap m_tiles;
     Timer<TileCache> m_tileRevalidationTimer;
-    IntRect m_tileCoverageRect;
+    Timer<TileCache> m_cohortRemovalTimer;
 
+    struct TileCohortInfo {
+        TileCohort cohort;
+        double creationTime; // in monotonicallyIncreasingTime().
+        TileCohortInfo(TileCohort inCohort, double inTime)
+            : cohort(inCohort)
+            , creationTime(inTime)
+        { }
+    };
+    typedef Deque<TileCohortInfo> TileCohortList;
+    TileCohortList m_cohortList;
+    
+    IntRect m_primaryTileCoverageRect; // In tile coords.
+
     CGFloat m_scale;
     CGFloat m_deviceScaleFactor;
 

Modified: trunk/Source/WebCore/platform/graphics/ca/mac/TileCache.mm (137386 => 137387)


--- trunk/Source/WebCore/platform/graphics/ca/mac/TileCache.mm	2012-12-12 00:47:19 UTC (rev 137386)
+++ trunk/Source/WebCore/platform/graphics/ca/mac/TileCache.mm	2012-12-12 00:47:24 UTC (rev 137387)
@@ -94,6 +94,7 @@
     , m_tileContainerLayer(adoptCF([[CALayer alloc] init]))
     , m_tileSize(defaultTileCacheWidth, defaultTileCacheHeight)
     , m_tileRevalidationTimer(this, &TileCache::tileRevalidationTimerFired)
+    , m_cohortRemovalTimer(this, &TileCache::cohortRemovalTimerFired)
     , m_scale(1)
     , m_deviceScaleFactor(1)
     , m_tileCoverage(CoverageForVisibleArea)
@@ -118,8 +119,8 @@
     ASSERT(isMainThread());
 
     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
-        WebTileLayer* tileLayer = it->value.get();
-        [tileLayer setTileCache:0];
+        const TileInfo& tileInfo = it->value;
+        [tileInfo.layer.get() setTileCache:0];
     }
     
     if (m_tiledScrollingIndicatorLayer)
@@ -140,8 +141,10 @@
 
 void TileCache::setNeedsDisplay()
 {
-    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
-        [it->value.get() setNeedsDisplay];
+    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
+        const TileInfo& tileInfo = it->value;
+        [tileInfo.layer.get() setNeedsDisplay];
+    }
 }
 
 void TileCache::setNeedsDisplayInRect(const IntRect& rect)
@@ -151,37 +154,57 @@
 
     FloatRect scaledRect(rect);
     scaledRect.scale(m_scale);
+    IntRect repaintRectInTileCoords(enclosingIntRect(scaledRect));
 
-    // Find the tiles that need to be invalidated.
-    IntRect coveredRect = intersection(enclosingIntRect(scaledRect), m_tileCoverageRect);
-    if (coveredRect.isEmpty())
+    // For small invalidations, lookup the covered tiles.
+    if (repaintRectInTileCoords.height() < 2 * m_tileSize.height() && repaintRectInTileCoords.width() < 2 * m_tileSize.width()) {
+        TileIndex topLeft;
+        TileIndex bottomRight;
+        getTileIndexRangeForRect(repaintRectInTileCoords, topLeft, bottomRight);
+
+        for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
+            for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
+                TileIndex tileIndex(x, y);
+                
+                TileMap::iterator it = m_tiles.find(tileIndex);
+                if (it != m_tiles.end())
+                    setTileNeedsDisplayInRect(tileIndex, it->value, repaintRectInTileCoords, m_primaryTileCoverageRect);
+            }
+        }
         return;
+    }
 
-    TileIndex topLeft;
-    TileIndex bottomRight;
-    getTileIndexRangeForRect(coveredRect, topLeft, bottomRight);
+    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
+        setTileNeedsDisplayInRect(it->key, it->value, repaintRectInTileCoords, m_primaryTileCoverageRect);
+}
 
-    for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
-        for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
-            WebTileLayer* tileLayer = tileLayerAtIndex(TileIndex(x, y));
-            if (!tileLayer)
-                continue;
+void TileCache::setTileNeedsDisplayInRect(const TileIndex& tileIndex, TileInfo& tileInfo, const IntRect& repaintRectInTileCoords, const IntRect& coverageRectInTileCoords)
+{
+    WebTileLayer* tileLayer = tileInfo.layer.get();
 
-            CGRect tileRect = [m_tileCacheLayer convertRect:rect toLayer:tileLayer];
-            if (CGRectIsEmpty(tileRect))
-                continue;
+    IntRect tileRect = rectForTileIndex(tileIndex);
+    IntRect tileRepaintRect = tileRect;
+    tileRepaintRect.intersect(repaintRectInTileCoords);
+    if (tileRepaintRect.isEmpty())
+        return;
 
-            [tileLayer setNeedsDisplayInRect:tileRect];
+    tileRepaintRect.moveBy(-tileRect.location());
+    
+    // We could test for intersection with the visible rect. This would reduce painting yet more,
+    // but may make scrolling stale tiles into view more frequent.
+    if (tileRect.intersects(coverageRectInTileCoords)) {
+        [tileLayer setNeedsDisplayInRect:tileRepaintRect];
 
-            if (shouldShowRepaintCounters()) {
-                CGRect bounds = [tileLayer bounds];
-                CGRect indicatorRect = CGRectMake(bounds.origin.x, bounds.origin.y, 52, 27);
-                [tileLayer setNeedsDisplayInRect:indicatorRect];
-            }
+        if (shouldShowRepaintCounters()) {
+            CGRect bounds = [tileLayer bounds];
+            CGRect indicatorRect = CGRectMake(bounds.origin.x, bounds.origin.y, 52, 27);
+            [tileLayer setNeedsDisplayInRect:indicatorRect];
         }
-    }
+    } else
+        tileInfo.hasStaleContent = true;
 }
 
+
 void TileCache::drawLayer(WebTileLayer *layer, CGContextRef context)
 {
     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
@@ -219,10 +242,11 @@
     m_scale = scale;
     [m_tileContainerLayer.get() setTransform:CATransform3DMakeScale(1 / m_scale, 1 / m_scale, 1)];
 
-    revalidateTiles();
+    revalidateTiles(PruneSecondaryTiles);
 
     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
-        [it->value.get() setContentsScale:deviceScaleFactor];
+        const TileInfo& tileInfo = it->value;
+        [tileInfo.layer.get() setContentsScale:deviceScaleFactor];
 
         IntRect tileRect = rectForTileIndex(it->key);
         FloatRect scaledTileRect = tileRect;
@@ -243,8 +267,10 @@
 
     m_acceleratesDrawing = acceleratesDrawing;
 
-    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
-        [it->value.get() setAcceleratesDrawing:m_acceleratesDrawing];
+    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
+        const TileInfo& tileInfo = it->value;
+        [tileInfo.layer.get() setAcceleratesDrawing:m_acceleratesDrawing];
+    }
 #else
     UNUSED_PARAM(acceleratesDrawing);
 #endif
@@ -258,8 +284,8 @@
     m_tilesAreOpaque = opaque;
 
     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
-        WebTileLayer* tileLayer = it->value.get();
-        [tileLayer setOpaque:opaque];
+        const TileInfo& tileInfo = it->value;
+        [tileInfo.layer.get() setOpaque:opaque];
     }
 }
 
@@ -272,6 +298,11 @@
     revalidateTiles();
 }
 
+void TileCache::prepopulateRect(const IntRect& rect)
+{
+    ensureTilesForRect(rect);
+}
+
 void TileCache::setIsInWindow(bool isInWindow)
 {
     if (m_isInWindow == isInWindow)
@@ -305,8 +336,10 @@
         return;
 
     m_tileDebugBorderWidth = borderWidth;
-    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
-        [it->value.get() setBorderWidth:m_tileDebugBorderWidth];
+    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
+        const TileInfo& tileInfo = it->value;
+        [tileInfo.layer.get() setBorderWidth:m_tileDebugBorderWidth];
+    }
 }
 
 void TileCache::setTileDebugBorderColor(CGColorRef borderColor)
@@ -315,8 +348,10 @@
         return;
 
     m_tileDebugBorderColor = borderColor;
-    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
-        [it->value.get() setBorderColor:m_tileDebugBorderColor.get()];
+    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
+        const TileInfo& tileInfo = it->value;
+        [tileInfo.layer.get() setBorderColor:m_tileDebugBorderColor.get()];
+    }
 }
 
 IntRect TileCache::bounds() const
@@ -351,31 +386,47 @@
     bottomRight.setY(max(bottomYRatio - 1, 0));
 }
 
-IntRect TileCache::computeTileCoverageRect() const
+IntRect TileCache::computeTileCoverageRect(const IntRect& previousVisibleRect) const
 {
-    IntRect tileCoverageRect = m_visibleRect;
-
     // If the page is not in a window (for example if it's in a background tab), we limit the tile coverage rect to the visible rect.
     // Furthermore, if the page can't have scrollbars (for example if its body element has overflow:hidden) it's very unlikely that the
     // page will ever be scrolled so we limit the tile coverage rect as well.
-    if (m_isInWindow && !(m_tileCoverage & CoverageForSlowScrolling)) {
-        // 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.
-        if (m_tileCoverage & CoverageForHorizontalScrolling)
-            tileCoverageRect.inflateX(tileCoverageRect.width() / 2);
+    if (!m_isInWindow || m_tileCoverage & CoverageForSlowScrolling)
+        return m_visibleRect;
 
-        if (m_tileCoverage & CoverageForVerticalScrolling)
-            tileCoverageRect.inflateY(tileCoverageRect.height());
-    }
+    bool largeVisibleRectChange = !previousVisibleRect.isEmpty() && !m_visibleRect.intersects(previousVisibleRect);
+    
+    // FIXME: look at how far the document can scroll in each dimension.
+    int coverageHorizontalSize = m_visibleRect.width();
+    int coverageVerticalSize = m_visibleRect.height();
+    
+    // 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.
+    if (m_tileCoverage & CoverageForHorizontalScrolling && !largeVisibleRectChange)
+        coverageHorizontalSize *= 2;
 
-    return tileCoverageRect;
+    if (m_tileCoverage & CoverageForVerticalScrolling && !largeVisibleRectChange)
+        coverageVerticalSize *= 3;
+
+    // Don't extend coverage before 0 or after the end.
+    IntRect coverageBounds = bounds();
+    int coverageLeft = m_visibleRect.x() - (coverageHorizontalSize - m_visibleRect.width()) / 2;
+    coverageLeft = min(coverageLeft, coverageBounds.maxX() - coverageHorizontalSize);
+    coverageLeft = max(coverageLeft, coverageBounds.x());
+
+    int coverageTop = m_visibleRect.y() - (coverageVerticalSize - m_visibleRect.height()) / 2;
+    coverageTop = min(coverageTop, coverageBounds.maxY() - coverageVerticalSize);
+    coverageTop = max(coverageTop, coverageBounds.y());
+
+    return IntRect(coverageLeft, coverageTop, coverageHorizontalSize, coverageVerticalSize);
 }
 
 IntSize TileCache::tileSizeForCoverageRect(const IntRect& coverageRect) const
 {
     if (m_tileCoverage & CoverageForSlowScrolling)
         return coverageRect.size();
+
     return IntSize(defaultTileCacheWidth, defaultTileCacheHeight);
 }
 
@@ -389,14 +440,16 @@
 
 void TileCache::tileRevalidationTimerFired(Timer<TileCache>*)
 {
-    revalidateTiles();
+    revalidateTiles(PruneSecondaryTiles);
 }
 
 unsigned TileCache::blankPixelCount() const
 {
     WebTileLayerList tiles(m_tiles.size());
-    tiles.appendRange(m_tiles.begin().values(), m_tiles.end().values());
 
+    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
+        tiles.append(it->value.layer);
+
     return blankPixelCountForTiles(tiles, m_visibleRect, IntPoint(0,0));
 }
 
@@ -420,8 +473,66 @@
     return uncoveredRegion.totalArea();
 }
 
-void TileCache::revalidateTiles()
+static inline void queueTileForRemoval(const TileCache::TileIndex& tileIndex, const TileCache::TileInfo& tileInfo, Vector<TileCache::TileIndex>& tilesToRemove)
 {
+    WebTileLayer* tileLayer = tileInfo.layer.get();
+    [tileLayer removeFromSuperlayer];
+    [tileLayer setTileCache:0];
+    tilesToRemove.append(tileIndex);
+}
+
+void TileCache::removeAllTiles()
+{
+    Vector<TileIndex> tilesToRemove;
+
+    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
+        queueTileForRemoval(it->key, it->value, tilesToRemove);
+
+    for (size_t i = 0; i < tilesToRemove.size(); ++i) {
+        TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
+        LayerPool::sharedPool()->addLayer(tileInfo.layer);
+    }
+}
+
+void TileCache::removeAllSecondaryTiles()
+{
+    Vector<TileIndex> tilesToRemove;
+
+    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
+        const TileInfo& tileInfo = it->value;
+        if (tileInfo.cohort == VisibleTileCohort)
+            continue;
+
+        queueTileForRemoval(it->key, it->value, tilesToRemove);
+    }
+
+    for (size_t i = 0; i < tilesToRemove.size(); ++i) {
+        TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
+        LayerPool::sharedPool()->addLayer(tileInfo.layer);
+    }
+}
+
+void TileCache::removeTilesInCohort(TileCohort cohort)
+{
+    ASSERT(cohort != VisibleTileCohort);
+    Vector<TileIndex> tilesToRemove;
+
+    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
+        const TileInfo& tileInfo = it->value;
+        if (tileInfo.cohort != cohort)
+            continue;
+
+        queueTileForRemoval(it->key, it->value, tilesToRemove);
+    }
+
+    for (size_t i = 0; i < tilesToRemove.size(); ++i) {
+        TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
+        LayerPool::sharedPool()->addLayer(tileInfo.layer);
+    }
+}
+
+void TileCache::revalidateTiles(TileValidationPolicy validationPolicy)
+{
     // If the underlying PlatformLayer has been destroyed, but the WebTileCacheLayer hasn't
     // platformLayer will be null here.
     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
@@ -431,58 +542,87 @@
     if (m_visibleRect.isEmpty() || bounds().isEmpty())
         return;
 
-    IntRect tileCoverageRect = computeTileCoverageRect();
-    IntRect coverageRectInTileCoords(tileCoverageRect);
-    coverageRectInTileCoords.scale(m_scale);
+    // If the visible rect size changed, drop secondary tiles to avoid lots of repainting.
+    // FIXME: when scaled, this changes due to rounding. We should use FloatRects or something.
+    // Also changes on rubber banding.
+    if (m_visibleRectAtLastRevalidate.size() != m_visibleRect.size())
+        validationPolicy = PruneSecondaryTiles;
+    
+    IntRect tileCoverageRect = computeTileCoverageRect(m_visibleRectAtLastRevalidate);
+    FloatRect scaledRect(tileCoverageRect);
+    scaledRect.scale(m_scale);
+    IntRect coverageRectInTileCoords(enclosingIntRect(scaledRect));
 
     IntSize oldTileSize = m_tileSize;
     m_tileSize = tileSizeForCoverageRect(tileCoverageRect);
     bool tileSizeChanged = m_tileSize != oldTileSize;
 
-    Vector<TileIndex> tilesToRemove;
+    if (tileSizeChanged) {
+        removeAllTiles();
+        m_cohortList.clear();
+    } else {
+        TileCohort currCohort = nextTileCohort();
+        unsigned tilesInCohort = 0;
+        
+        // Move tiles newly outside the coverage rect into the cohort map.
+        for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
+            TileInfo& tileInfo = it->value;
+            TileIndex tileIndex = it->key;
 
-    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
-        const TileIndex& tileIndex = it->key;
+            WebTileLayer* tileLayer = tileInfo.layer.get();
+            IntRect tileRect = rectForTileIndex(tileIndex);
+            if (tileRect.intersects(coverageRectInTileCoords)) {
+                tileInfo.cohort = VisibleTileCohort;
+                if (tileInfo.hasStaleContent) {
+                    // FIXME: store a dirty region per layer?
+                    [tileLayer setNeedsDisplay];
+                    tileInfo.hasStaleContent = false;
+                }
+            } else {
+                // Add to the currentCohort if not already in one.
+                if (tileInfo.cohort == VisibleTileCohort) {
+                    tileInfo.cohort = currCohort;
+                    ++tilesInCohort;
+                }
+            }
+        }
+        
+        if (tilesInCohort)
+            startedNewCohort(currCohort);
 
-        WebTileLayer* tileLayer = it->value.get();
-
-        if (!rectForTileIndex(tileIndex).intersects(coverageRectInTileCoords) || tileSizeChanged) {
-            // Remove this layer.
-            [tileLayer removeFromSuperlayer];
-            [tileLayer setTileCache:0];
-
-            tilesToRemove.append(tileIndex);
-        }
+        scheduleCohortRemoval();
     }
 
-    // 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)
-        LayerPool::sharedPool()->addLayer(m_tiles.take(tilesToRemove[i]));
-
     TileIndex topLeft;
     TileIndex bottomRight;
     getTileIndexRangeForRect(coverageRectInTileCoords, topLeft, bottomRight);
 
     Vector<FloatRect> dirtyRects;
-
+    
+    // Ensure primary tile coverage tiles.
+    m_primaryTileCoverageRect = IntRect();
+    int primaryLayerCount = 0;
+    
     for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
         for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
             TileIndex tileIndex(x, y);
 
             IntRect tileRect = rectForTileIndex(tileIndex);
-            RetainPtr<WebTileLayer>& tileLayer = m_tiles.add(tileIndex, 0).iterator->value;
-            if (!tileLayer) {
-                tileLayer = createTileLayer(tileRect);
-                [m_tileContainerLayer.get() addSublayer:tileLayer.get()];
+            m_primaryTileCoverageRect.unite(tileRect);
+
+            TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value;
+            if (!tileInfo.layer) {
+                tileInfo.layer = createTileLayer(tileRect);
+                [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
             } else {
                 // We already have a layer for this tile. Ensure that its size is correct.
-                CGSize tileLayerSize = [tileLayer.get() frame].size;
+                CGSize tileLayerSize = [tileInfo.layer.get() frame].size;
                 if (tileLayerSize.width >= tileRect.width() && tileLayerSize.height >= tileRect.height())
                     continue;
-                [tileLayer.get() setFrame:tileRect];
+                [tileInfo.layer.get() setFrame:tileRect];
             }
+            
+            ++primaryLayerCount;
 
             FloatRect scaledTileRect = tileRect;
             scaledTileRect.scale(1 / m_scale);
@@ -490,21 +630,131 @@
         }
     }
 
-    m_tileCoverageRect = IntRect();
-    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
-        const TileIndex& tileIndex = it->key;
-
-        m_tileCoverageRect.unite(rectForTileIndex(tileIndex));
+    if (validationPolicy == PruneSecondaryTiles) {
+        removeAllSecondaryTiles();
+        m_cohortList.clear();
     }
-
+    
     if (m_tiledScrollingIndicatorLayer)
         updateTileCoverageMap();
 
+    m_visibleRectAtLastRevalidate = m_visibleRect;
+
     if (dirtyRects.isEmpty())
         return;
     platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
 }
 
+TileCache::TileCohort TileCache::nextTileCohort() const
+{
+    if (!m_cohortList.isEmpty())
+        return m_cohortList.last().cohort + 1;
+
+    return 1;
+}
+
+void TileCache::startedNewCohort(TileCohort cohort)
+{
+    m_cohortList.append(TileCohortInfo(cohort, monotonicallyIncreasingTime()));
+}
+
+TileCache::TileCohort TileCache::newestTileCohort() const
+{
+    return m_cohortList.isEmpty() ? 0 : m_cohortList.last().cohort;
+}
+
+TileCache::TileCohort TileCache::oldestTileCohort() const
+{
+    return m_cohortList.isEmpty() ? 0 : m_cohortList.first().cohort;
+}
+
+void TileCache::scheduleCohortRemoval()
+{
+    const double cohortRemovalTimerSeconds = 1;
+    
+    // Start the timer, or reschedule the timer from now if it's already active.
+    if (!m_cohortRemovalTimer.isActive())
+        m_cohortRemovalTimer.startRepeating(cohortRemovalTimerSeconds);
+}
+
+void TileCache::cohortRemovalTimerFired(Timer<TileCache>*)
+{
+    if (m_cohortList.isEmpty()) {
+        m_cohortRemovalTimer.stop();
+        return;
+    }
+
+    double cohortLifeTimeSeconds = 2;
+    double timeThreshold = monotonicallyIncreasingTime() - cohortLifeTimeSeconds;
+    
+    while (!m_cohortList.isEmpty() && m_cohortList.first().creationTime < timeThreshold) {
+        TileCohortInfo firstCohort = m_cohortList.takeFirst();
+        removeTilesInCohort(firstCohort.cohort);
+    }
+
+    if (m_tiledScrollingIndicatorLayer)
+        updateTileCoverageMap();
+}
+
+void TileCache::ensureTilesForRect(const IntRect& rect)
+{
+    PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
+    if (!platformLayer)
+        return;
+
+    FloatRect scaledRect(rect);
+    scaledRect.scale(m_scale);
+    IntRect rectInTileCoords(enclosingIntRect(scaledRect));
+
+    if (m_primaryTileCoverageRect.contains(rectInTileCoords))
+        return;
+
+    TileIndex topLeft;
+    TileIndex bottomRight;
+    getTileIndexRangeForRect(rectInTileCoords, topLeft, bottomRight);
+
+    Vector<FloatRect> dirtyRects;
+    TileCohort currCohort = nextTileCohort();
+    unsigned tilesInCohort = 0;
+
+    for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
+        for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
+            TileIndex tileIndex(x, y);
+
+            IntRect tileRect = rectForTileIndex(tileIndex);
+            TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value;
+            if (!tileInfo.layer) {
+                tileInfo.layer = createTileLayer(tileRect);
+                [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
+            } else {
+                // We already have a layer for this tile. Ensure that its size is correct.
+                CGSize tileLayerSize = [tileInfo.layer.get() frame].size;
+                if (tileLayerSize.width >= tileRect.width() && tileLayerSize.height >= tileRect.height())
+                    continue;
+                [tileInfo.layer.get() setFrame:tileRect];
+            }
+
+            if (!tileRect.intersects(m_primaryTileCoverageRect)) {
+                tileInfo.cohort = currCohort;
+                ++tilesInCohort;
+            }
+
+            FloatRect scaledTileRect = tileRect;
+            scaledTileRect.scale(1 / m_scale);
+            dirtyRects.append(scaledTileRect);
+        }
+    }
+    
+    if (tilesInCohort)
+        startedNewCohort(currCohort);
+
+    if (m_tiledScrollingIndicatorLayer)
+        updateTileCoverageMap();
+
+    if (!dirtyRects.isEmpty())
+        platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
+}
+
 void TileCache::updateTileCoverageMap()
 {
     FloatRect containerBounds = bounds();
@@ -553,7 +803,7 @@
 {
     TileIndex topLeft;
     TileIndex bottomRight;
-    getTileIndexRangeForRect(m_tileCoverageRect, topLeft, bottomRight);
+    getTileIndexRangeForRect(m_primaryTileCoverageRect, topLeft, bottomRight);
 
     // Return index of top, left tile and the number of tiles across and down.
     return IntRect(topLeft.x(), topLeft.y(), bottomRight.x() - topLeft.x() + 1, bottomRight.y() - topLeft.y() + 1);
@@ -562,7 +812,7 @@
 // Return the rect in layer coords, not tile coords.
 IntRect TileCache::tileCoverageRect() const
 {
-    IntRect coverageRectInLayerCoords(m_tileCoverageRect);
+    IntRect coverageRectInLayerCoords(m_primaryTileCoverageRect);
     coverageRectInLayerCoords.scale(1 / m_scale);
     return coverageRectInLayerCoords;
 }
@@ -594,7 +844,7 @@
 
 WebTileLayer* TileCache::tileLayerAtIndex(const TileIndex& index) const
 {
-    return m_tiles.get(index).get();
+    return m_tiles.get(index).layer.get();
 }
 
 RetainPtr<WebTileLayer> TileCache::createTileLayer(const IntRect& tileRect)
@@ -681,12 +931,11 @@
 
 void TileCache::drawTileMapContents(CGContextRef context, CGRect layerBounds)
 {
-    CGContextSetRGBFillColor(context, 0.5, 0.5, 0.5, 1);
+    CGContextSetRGBFillColor(context, 0.3, 0.3, 0.3, 1);
     CGContextFillRect(context, layerBounds);
 
     CGFloat scaleFactor = layerBounds.size.width / bounds().width();
     
-    CGContextSetRGBFillColor(context, 1, 1, 1, 1);
     CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
 
     CGFloat contextScale = scaleFactor / scale();
@@ -695,7 +944,27 @@
     CGContextSetLineWidth(context, 0.5 / contextScale);
     
     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
-        WebTileLayer* tileLayer = it->value.get();
+        const TileInfo& tileInfo = it->value;
+        WebTileLayer* tileLayer = tileInfo.layer.get();
+        
+        CGFloat red = 1;
+        CGFloat green = 1;
+        CGFloat blue = 1;
+        if (tileInfo.hasStaleContent) {
+            red = 0.25;
+            green = 0.125;
+            blue = 0;
+        }
+        
+        TileCohort newestCohort = newestTileCohort();
+        TileCohort oldestCohort = oldestTileCohort();
+        
+        if (tileInfo.cohort != VisibleTileCohort && newestCohort > oldestCohort) {
+            float cohortProportion = static_cast<float>((newestCohort - tileInfo.cohort)) / (newestCohort - oldestCohort);
+            CGContextSetRGBFillColor(context, red, green, blue, 1 - cohortProportion);
+        } else
+            CGContextSetRGBFillColor(context, red, green, blue, 1);
+
         CGRect frame = [tileLayer frame];
         CGContextFillRect(context, frame);
         CGContextStrokeRect(context, frame);
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to