Add a way to update GraphicsLayerCA visibleRects without having to do a flush
[WebKit-https.git] / Source / WebCore / platform / graphics / ca / mac / TileController.mm
1 /*
2  * Copyright (C) 2011, 2012, 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "TileController.h"
28
29 #import "IntRect.h"
30 #import "PlatformCALayer.h"
31 #import "Region.h"
32 #import "LayerPool.h"
33 #import "WebLayer.h"
34 #import "WebTiledBackingLayer.h"
35 #import "WebTileLayer.h"
36 #import <QuartzCore/QuartzCore.h>
37 #import <wtf/MainThread.h>
38 #import <WebCore/BlockExceptions.h>
39 #import <utility>
40
41 using namespace std;
42
43 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
44 @interface CALayer (WebCALayerDetails)
45 - (void)setAcceleratesDrawing:(BOOL)flag;
46 @end
47 #endif
48
49 @interface WebTiledScrollingIndicatorLayer : CALayer {
50     WebCore::TileController* _tileController;
51     CALayer *_visibleRectFrameLayer; // Owned by being a sublayer.
52 }
53 @property (assign) WebCore::TileController* tileController;
54 @property (assign) CALayer* visibleRectFrameLayer;
55 @end
56
57 @implementation WebTiledScrollingIndicatorLayer
58 @synthesize tileController = _tileController;
59 @synthesize visibleRectFrameLayer = _visibleRectFrameLayer;
60 - (id)init
61 {
62     if ((self = [super init])) {
63         [self setStyle:[NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNull null], @"bounds", [NSNull null], @"position", [NSNull null], @"contents", nil] forKey:@"actions"]];
64
65         _visibleRectFrameLayer = [CALayer layer];
66         [_visibleRectFrameLayer setStyle:[NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNull null], @"bounds", [NSNull null], @"position", nil] forKey:@"actions"]];
67         [self addSublayer:_visibleRectFrameLayer];
68         [_visibleRectFrameLayer setBorderColor:WebCore::cachedCGColor(WebCore::Color(255, 0, 0), WebCore::ColorSpaceDeviceRGB)];
69         [_visibleRectFrameLayer setBorderWidth:2];
70         return self;
71     }
72     return nil;
73 }
74
75 - (void)drawInContext:(CGContextRef)context
76 {
77     if (_tileController)
78         _tileController->drawTileMapContents(context, [self bounds]);
79 }
80 @end
81
82 namespace WebCore {
83     
84 enum TileValidationPolicyFlag {
85     PruneSecondaryTiles = 1 << 0,
86     UnparentAllTiles = 1 << 1
87 };
88
89 static const int defaultTileWidth = 512;
90 static const int defaultTileHeight = 512;
91
92 PassOwnPtr<TileController> TileController::create(WebTiledBackingLayer* tileCacheLayer)
93 {
94     return adoptPtr(new TileController(tileCacheLayer));
95 }
96
97 TileController::TileController(WebTiledBackingLayer* tileCacheLayer)
98     : m_tileCacheLayer(tileCacheLayer)
99     , m_tileContainerLayer(adoptCF([[CALayer alloc] init]))
100     , m_tileSize(defaultTileWidth, defaultTileHeight)
101     , m_tileRevalidationTimer(this, &TileController::tileRevalidationTimerFired)
102     , m_cohortRemovalTimer(this, &TileController::cohortRemovalTimerFired)
103     , m_scale(1)
104     , m_deviceScaleFactor(1)
105     , m_tileCoverage(CoverageForVisibleArea)
106     , m_isInWindow(false)
107     , m_scrollingPerformanceLoggingEnabled(false)
108     , m_aggressivelyRetainsTiles(false)
109     , m_unparentsOffscreenTiles(false)
110     , m_acceleratesDrawing(false)
111     , m_tilesAreOpaque(false)
112     , m_clipsToExposedRect(false)
113     , m_tileDebugBorderWidth(0)
114     , m_indicatorMode(ThreadedScrollingIndication)
115 {
116     [CATransaction begin];
117     [CATransaction setDisableActions:YES];
118     [m_tileCacheLayer addSublayer:m_tileContainerLayer.get()];
119 #ifndef NDEBUG
120     [m_tileContainerLayer.get() setName:@"TileController Container Layer"];
121 #endif
122     [CATransaction commit];
123 }
124
125 TileController::~TileController()
126 {
127     ASSERT(isMainThread());
128
129     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
130         const TileInfo& tileInfo = it->value;
131         [tileInfo.layer.get() setTileController:0];
132     }
133     
134     if (m_tiledScrollingIndicatorLayer)
135         [m_tiledScrollingIndicatorLayer.get() setTileController:nil];
136 }
137
138 void TileController::tileCacheLayerBoundsChanged()
139 {
140     if (m_tiles.isEmpty()) {
141         // We must revalidate immediately instead of using a timer when there are
142         // no tiles to avoid a flash when transitioning from one page to another.
143         revalidateTiles();
144         return;
145     }
146
147     scheduleTileRevalidation(0);
148 }
149
150 void TileController::setNeedsDisplay()
151 {
152     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
153         const TileInfo& tileInfo = it->value;
154         [tileInfo.layer.get() setNeedsDisplay];
155     }
156 }
157
158 void TileController::setNeedsDisplayInRect(const IntRect& rect)
159 {
160     if (m_tiles.isEmpty())
161         return;
162
163     FloatRect scaledRect(rect);
164     scaledRect.scale(m_scale);
165     IntRect repaintRectInTileCoords(enclosingIntRect(scaledRect));
166
167     // For small invalidations, lookup the covered tiles.
168     if (repaintRectInTileCoords.height() < 2 * m_tileSize.height() && repaintRectInTileCoords.width() < 2 * m_tileSize.width()) {
169         TileIndex topLeft;
170         TileIndex bottomRight;
171         getTileIndexRangeForRect(repaintRectInTileCoords, topLeft, bottomRight);
172
173         for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
174             for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
175                 TileIndex tileIndex(x, y);
176                 
177                 TileMap::iterator it = m_tiles.find(tileIndex);
178                 if (it != m_tiles.end())
179                     setTileNeedsDisplayInRect(tileIndex, it->value, repaintRectInTileCoords, m_primaryTileCoverageRect);
180             }
181         }
182         return;
183     }
184
185     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
186         setTileNeedsDisplayInRect(it->key, it->value, repaintRectInTileCoords, m_primaryTileCoverageRect);
187 }
188
189 void TileController::setTileNeedsDisplayInRect(const TileIndex& tileIndex, TileInfo& tileInfo, const IntRect& repaintRectInTileCoords, const IntRect& coverageRectInTileCoords)
190 {
191     WebTileLayer* tileLayer = tileInfo.layer.get();
192
193     IntRect tileRect = rectForTileIndex(tileIndex);
194     IntRect tileRepaintRect = tileRect;
195     tileRepaintRect.intersect(repaintRectInTileCoords);
196     if (tileRepaintRect.isEmpty())
197         return;
198
199     tileRepaintRect.moveBy(-tileRect.location());
200     
201     // We could test for intersection with the visible rect. This would reduce painting yet more,
202     // but may make scrolling stale tiles into view more frequent.
203     if (tileRect.intersects(coverageRectInTileCoords)) {
204         [tileLayer setNeedsDisplayInRect:tileRepaintRect];
205
206         if (shouldShowRepaintCounters()) {
207             CGRect bounds = [tileLayer bounds];
208             CGRect indicatorRect = CGRectMake(bounds.origin.x, bounds.origin.y, 52, 27);
209             [tileLayer setNeedsDisplayInRect:indicatorRect];
210         }
211     } else
212         tileInfo.hasStaleContent = true;
213 }
214
215
216 void TileController::drawLayer(WebTileLayer *layer, CGContextRef context)
217 {
218     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
219     if (!platformLayer)
220         return;
221
222     CGContextSaveGState(context);
223
224     CGPoint layerOrigin = [layer frame].origin;
225     CGContextTranslateCTM(context, -layerOrigin.x, -layerOrigin.y);
226     CGContextScaleCTM(context, m_scale, m_scale);
227     drawLayerContents(context, layer, platformLayer);
228
229     CGContextRestoreGState(context);
230
231     drawRepaintCounter(layer, context);
232 }
233
234 void TileController::setScale(CGFloat scale)
235 {
236     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
237     float deviceScaleFactor = platformLayer->owner()->platformCALayerDeviceScaleFactor();
238
239     // The scale we get is the produce of the page scale factor and device scale factor.
240     // Divide by the device scale factor so we'll get the page scale factor.
241     scale /= deviceScaleFactor;
242
243     if (m_scale == scale && m_deviceScaleFactor == deviceScaleFactor)
244         return;
245
246 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
247     Vector<FloatRect> dirtyRects;
248
249     m_deviceScaleFactor = deviceScaleFactor;
250     m_scale = scale;
251     [m_tileContainerLayer.get() setTransform:CATransform3DMakeScale(1 / m_scale, 1 / m_scale, 1)];
252
253     revalidateTiles(PruneSecondaryTiles, PruneSecondaryTiles);
254
255     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
256         const TileInfo& tileInfo = it->value;
257         [tileInfo.layer.get() setContentsScale:deviceScaleFactor];
258
259         IntRect tileRect = rectForTileIndex(it->key);
260         FloatRect scaledTileRect = tileRect;
261
262         scaledTileRect.scale(1 / m_scale);
263         dirtyRects.append(scaledTileRect);
264     }
265
266     platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
267 #endif
268 }
269
270 void TileController::setAcceleratesDrawing(bool acceleratesDrawing)
271 {
272 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
273     if (m_acceleratesDrawing == acceleratesDrawing)
274         return;
275
276     m_acceleratesDrawing = acceleratesDrawing;
277
278     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
279         const TileInfo& tileInfo = it->value;
280         [tileInfo.layer.get() setAcceleratesDrawing:m_acceleratesDrawing];
281     }
282 #else
283     UNUSED_PARAM(acceleratesDrawing);
284 #endif
285 }
286
287 void TileController::setTilesOpaque(bool opaque)
288 {
289     if (opaque == m_tilesAreOpaque)
290         return;
291
292     m_tilesAreOpaque = opaque;
293
294     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
295         const TileInfo& tileInfo = it->value;
296         [tileInfo.layer.get() setOpaque:opaque];
297     }
298 }
299
300 void TileController::setVisibleRect(const FloatRect& visibleRect)
301 {
302     if (m_visibleRect == visibleRect)
303         return;
304
305     m_visibleRect = visibleRect;
306     revalidateTiles();
307 }
308
309 bool TileController::tilesWouldChangeForVisibleRect(const FloatRect& newVisibleRect) const
310 {
311     FloatRect visibleRect = newVisibleRect;
312
313     if (m_clipsToExposedRect)
314         visibleRect.intersect(m_exposedRect);
315
316     if (visibleRect.isEmpty() || bounds().isEmpty())
317         return false;
318         
319     FloatRect currentTileCoverageRect = computeTileCoverageRect(m_visibleRect, newVisibleRect);
320     FloatRect scaledRect(currentTileCoverageRect);
321     scaledRect.scale(m_scale);
322     IntRect currentCoverageRectInTileCoords(enclosingIntRect(scaledRect));
323
324     IntSize newTileSize = tileSizeForCoverageRect(currentTileCoverageRect);
325     bool tileSizeChanged = newTileSize != m_tileSize;
326     if (tileSizeChanged)
327         return true;
328
329     TileIndex topLeft;
330     TileIndex bottomRight;
331     getTileIndexRangeForRect(currentCoverageRectInTileCoords, topLeft, bottomRight);
332
333     IntRect coverageRect = rectForTileIndex(topLeft);
334     coverageRect.unite(rectForTileIndex(bottomRight));
335     return coverageRect != m_primaryTileCoverageRect;
336 }
337
338 void TileController::setExposedRect(const FloatRect& exposedRect)
339 {
340     if (m_exposedRect == exposedRect)
341         return;
342
343     m_exposedRect = exposedRect;
344     revalidateTiles();
345 }
346
347 void TileController::setClipsToExposedRect(bool clipsToExposedRect)
348 {
349     if (m_clipsToExposedRect == clipsToExposedRect)
350         return;
351
352     m_clipsToExposedRect = clipsToExposedRect;
353
354     // Going from not clipping to clipping, we don't need to revalidate right away.
355     if (clipsToExposedRect)
356         revalidateTiles();
357 }
358
359 void TileController::prepopulateRect(const FloatRect& rect)
360 {
361     ensureTilesForRect(rect);
362 }
363
364 void TileController::setIsInWindow(bool isInWindow)
365 {
366     if (m_isInWindow == isInWindow)
367         return;
368
369     m_isInWindow = isInWindow;
370
371     if (m_isInWindow)
372         revalidateTiles();
373     else {
374         const double tileRevalidationTimeout = 4;
375         scheduleTileRevalidation(tileRevalidationTimeout);
376     }
377 }
378
379 void TileController::setTileCoverage(TileCoverage coverage)
380 {
381     if (coverage == m_tileCoverage)
382         return;
383
384     m_tileCoverage = coverage;
385     scheduleTileRevalidation(0);
386 }
387
388 void TileController::forceRepaint()
389 {
390     setNeedsDisplay();
391 }
392
393 void TileController::setTileDebugBorderWidth(float borderWidth)
394 {
395     if (m_tileDebugBorderWidth == borderWidth)
396         return;
397
398     m_tileDebugBorderWidth = borderWidth;
399     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
400         const TileInfo& tileInfo = it->value;
401         [tileInfo.layer.get() setBorderWidth:m_tileDebugBorderWidth];
402     }
403 }
404
405 void TileController::setTileDebugBorderColor(CGColorRef borderColor)
406 {
407     if (m_tileDebugBorderColor == borderColor)
408         return;
409
410     m_tileDebugBorderColor = borderColor;
411     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
412         const TileInfo& tileInfo = it->value;
413         [tileInfo.layer.get() setBorderColor:m_tileDebugBorderColor.get()];
414     }
415 }
416
417 IntRect TileController::bounds() const
418 {
419     return IntRect(IntPoint(), IntSize([m_tileCacheLayer bounds].size));
420 }
421
422 IntRect TileController::rectForTileIndex(const TileIndex& tileIndex) const
423 {
424     IntRect rect(tileIndex.x() * m_tileSize.width(), tileIndex.y() * m_tileSize.height(), m_tileSize.width(), m_tileSize.height());
425     IntRect scaledBounds(bounds());
426     scaledBounds.scale(m_scale);
427
428     rect.intersect(scaledBounds);
429
430     return rect;
431 }
432
433 void TileController::getTileIndexRangeForRect(const IntRect& rect, TileIndex& topLeft, TileIndex& bottomRight) const
434 {
435     IntRect clampedRect = bounds();
436     clampedRect.scale(m_scale);
437     clampedRect.intersect(rect);
438
439     topLeft.setX(max(clampedRect.x() / m_tileSize.width(), 0));
440     topLeft.setY(max(clampedRect.y() / m_tileSize.height(), 0));
441
442     int bottomXRatio = ceil((float)clampedRect.maxX() / m_tileSize.width());
443     bottomRight.setX(max(bottomXRatio - 1, 0));
444
445     int bottomYRatio = ceil((float)clampedRect.maxY() / m_tileSize.height());
446     bottomRight.setY(max(bottomYRatio - 1, 0));
447 }
448
449 FloatRect TileController::computeTileCoverageRect(const FloatRect& previousVisibleRect, const FloatRect& currentVisibleRect) const
450 {
451     FloatRect visibleRect = currentVisibleRect;
452
453     if (m_clipsToExposedRect)
454         visibleRect.intersect(m_exposedRect);
455
456     // 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.
457     // Furthermore, if the page can't have scrollbars (for example if its body element has overflow:hidden) it's very unlikely that the
458     // page will ever be scrolled so we limit the tile coverage rect as well.
459     if (!m_isInWindow || m_tileCoverage & CoverageForSlowScrolling)
460         return visibleRect;
461
462     bool largeVisibleRectChange = !previousVisibleRect.isEmpty() && !visibleRect.intersects(previousVisibleRect);
463     
464     // FIXME: look at how far the document can scroll in each dimension.
465     float coverageHorizontalSize = visibleRect.width();
466     float coverageVerticalSize = visibleRect.height();
467     
468     // Inflate the coverage rect so that it covers 2x of the visible width and 3x of the visible height.
469     // These values were chosen because it's more common to have tall pages and to scroll vertically,
470     // so we keep more tiles above and below the current area.
471     if (m_tileCoverage & CoverageForHorizontalScrolling && !largeVisibleRectChange)
472         coverageHorizontalSize *= 2;
473
474     if (m_tileCoverage & CoverageForVerticalScrolling && !largeVisibleRectChange)
475         coverageVerticalSize *= 3;
476
477     // Don't extend coverage before 0 or after the end.
478     FloatRect coverageBounds = bounds();
479     float coverageLeft = visibleRect.x() - (coverageHorizontalSize - visibleRect.width()) / 2;
480     coverageLeft = min(coverageLeft, coverageBounds.maxX() - coverageHorizontalSize);
481     coverageLeft = max(coverageLeft, coverageBounds.x());
482
483     float coverageTop = visibleRect.y() - (coverageVerticalSize - visibleRect.height()) / 2;
484     coverageTop = min(coverageTop, coverageBounds.maxY() - coverageVerticalSize);
485     coverageTop = max(coverageTop, coverageBounds.y());
486
487     return FloatRect(coverageLeft, coverageTop, coverageHorizontalSize, coverageVerticalSize);
488 }
489
490 IntSize TileController::tileSizeForCoverageRect(const FloatRect& coverageRect) const
491 {
492     if (m_tileCoverage & CoverageForSlowScrolling) {
493         FloatSize tileSize = coverageRect.size();
494         tileSize.scale(m_scale);
495         return expandedIntSize(tileSize);
496     }
497
498     return IntSize(defaultTileWidth, defaultTileHeight);
499 }
500
501 void TileController::scheduleTileRevalidation(double interval)
502 {
503     if (m_tileRevalidationTimer.isActive() && m_tileRevalidationTimer.nextFireInterval() < interval)
504         return;
505
506     m_tileRevalidationTimer.startOneShot(interval);
507 }
508
509 void TileController::tileRevalidationTimerFired(Timer<TileController>*)
510 {
511     TileValidationPolicyFlags foregroundValidationPolicy = m_aggressivelyRetainsTiles ? 0 : PruneSecondaryTiles;
512     TileValidationPolicyFlags backgroundValidationPolicy = foregroundValidationPolicy | UnparentAllTiles;
513
514     revalidateTiles(foregroundValidationPolicy, backgroundValidationPolicy);
515 }
516
517 unsigned TileController::blankPixelCount() const
518 {
519     WebTileLayerList tiles(m_tiles.size());
520
521     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
522         tiles.append(it->value.layer);
523
524     return blankPixelCountForTiles(tiles, m_visibleRect, IntPoint(0,0));
525 }
526
527 unsigned TileController::blankPixelCountForTiles(const WebTileLayerList& tiles, const FloatRect& visibleRect, const IntPoint& tileTranslation)
528 {
529     Region paintedVisibleTiles;
530
531     for (WebTileLayerList::const_iterator it = tiles.begin(), end = tiles.end(); it != end; ++it) {
532         const WebTileLayer* tileLayer = it->get();
533
534         FloatRect visiblePart(CGRectOffset([tileLayer frame], tileTranslation.x(), tileTranslation.y()));
535         visiblePart.intersect(visibleRect);
536
537         if (!visiblePart.isEmpty())
538             paintedVisibleTiles.unite(enclosingIntRect(visiblePart));
539     }
540
541     Region uncoveredRegion(enclosingIntRect(visibleRect));
542     uncoveredRegion.subtract(paintedVisibleTiles);
543
544     return uncoveredRegion.totalArea();
545 }
546
547 static inline void queueTileForRemoval(const TileController::TileIndex& tileIndex, const TileController::TileInfo& tileInfo, Vector<TileController::TileIndex>& tilesToRemove)
548 {
549     WebTileLayer* tileLayer = tileInfo.layer.get();
550     [tileLayer removeFromSuperlayer];
551     [tileLayer setTileController:0];
552     tilesToRemove.append(tileIndex);
553 }
554
555 void TileController::removeAllTiles()
556 {
557     Vector<TileIndex> tilesToRemove;
558
559     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
560         queueTileForRemoval(it->key, it->value, tilesToRemove);
561
562     for (size_t i = 0; i < tilesToRemove.size(); ++i) {
563         TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
564         LayerPool::sharedPool()->addLayer(tileInfo.layer);
565     }
566 }
567
568 void TileController::removeAllSecondaryTiles()
569 {
570     Vector<TileIndex> tilesToRemove;
571
572     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
573         const TileInfo& tileInfo = it->value;
574         if (tileInfo.cohort == VisibleTileCohort)
575             continue;
576
577         queueTileForRemoval(it->key, it->value, tilesToRemove);
578     }
579
580     for (size_t i = 0; i < tilesToRemove.size(); ++i) {
581         TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
582         LayerPool::sharedPool()->addLayer(tileInfo.layer);
583     }
584 }
585
586 void TileController::removeTilesInCohort(TileCohort cohort)
587 {
588     ASSERT(cohort != VisibleTileCohort);
589     Vector<TileIndex> tilesToRemove;
590
591     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
592         const TileInfo& tileInfo = it->value;
593         if (tileInfo.cohort != cohort)
594             continue;
595
596         queueTileForRemoval(it->key, it->value, tilesToRemove);
597     }
598
599     for (size_t i = 0; i < tilesToRemove.size(); ++i) {
600         TileInfo tileInfo = m_tiles.take(tilesToRemove[i]);
601         LayerPool::sharedPool()->addLayer(tileInfo.layer);
602     }
603 }
604
605 void TileController::revalidateTiles(TileValidationPolicyFlags foregroundValidationPolicy, TileValidationPolicyFlags backgroundValidationPolicy)
606 {
607     // If the underlying PlatformLayer has been destroyed, but the WebTiledBackingLayer hasn't
608     // platformLayer will be null here.
609     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
610     if (!platformLayer)
611         return;
612
613     FloatRect visibleRect = m_visibleRect;
614
615     if (m_clipsToExposedRect)
616         visibleRect.intersect(m_exposedRect);
617
618     if (visibleRect.isEmpty() || bounds().isEmpty())
619         return;
620     
621     TileValidationPolicyFlags validationPolicy = m_isInWindow ? foregroundValidationPolicy : backgroundValidationPolicy;
622     
623     FloatRect tileCoverageRect = computeTileCoverageRect(m_visibleRectAtLastRevalidate, m_visibleRect);
624     FloatRect scaledRect(tileCoverageRect);
625     scaledRect.scale(m_scale);
626     IntRect coverageRectInTileCoords(enclosingIntRect(scaledRect));
627
628     IntSize oldTileSize = m_tileSize;
629     m_tileSize = tileSizeForCoverageRect(tileCoverageRect);
630     bool tileSizeChanged = m_tileSize != oldTileSize;
631
632     if (tileSizeChanged) {
633         removeAllTiles();
634         m_cohortList.clear();
635     } else {
636         TileCohort currCohort = nextTileCohort();
637         unsigned tilesInCohort = 0;
638         
639         // Move tiles newly outside the coverage rect into the cohort map.
640         for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
641             TileInfo& tileInfo = it->value;
642             TileIndex tileIndex = it->key;
643
644             WebTileLayer* tileLayer = tileInfo.layer.get();
645             IntRect tileRect = rectForTileIndex(tileIndex);
646             if (tileRect.intersects(coverageRectInTileCoords)) {
647                 tileInfo.cohort = VisibleTileCohort;
648                 if (tileInfo.hasStaleContent) {
649                     // FIXME: store a dirty region per layer?
650                     [tileLayer setNeedsDisplay];
651                     tileInfo.hasStaleContent = false;
652                 }
653             } else {
654                 // Add to the currentCohort if not already in one.
655                 if (tileInfo.cohort == VisibleTileCohort) {
656                     tileInfo.cohort = currCohort;
657                     ++tilesInCohort;
658                     
659                     if (m_unparentsOffscreenTiles)
660                         [tileInfo.layer.get() removeFromSuperlayer];
661                 }
662             }
663         }
664         
665         if (tilesInCohort)
666             startedNewCohort(currCohort);
667
668         if (!m_aggressivelyRetainsTiles)
669             scheduleCohortRemoval();
670     }
671
672     TileIndex topLeft;
673     TileIndex bottomRight;
674     getTileIndexRangeForRect(coverageRectInTileCoords, topLeft, bottomRight);
675
676     Vector<FloatRect> dirtyRects;
677     
678     // Ensure primary tile coverage tiles.
679     m_primaryTileCoverageRect = IntRect();
680
681     for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
682         for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
683             TileIndex tileIndex(x, y);
684
685             IntRect tileRect = rectForTileIndex(tileIndex);
686             m_primaryTileCoverageRect.unite(tileRect);
687
688             TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value;
689             if (!tileInfo.layer) {
690                 tileInfo.layer = createTileLayer(tileRect);
691                 if (!m_unparentsOffscreenTiles || m_isInWindow)
692                     [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
693             } else {
694                 if ((!m_unparentsOffscreenTiles || m_isInWindow) && ![tileInfo.layer.get() superlayer])
695                     [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
696
697                 // We already have a layer for this tile. Ensure that its size is correct.
698                 FloatSize tileLayerSize([tileInfo.layer.get() frame].size);
699                 if (tileLayerSize == FloatSize(tileRect.size()))
700                     continue;
701
702                 [tileInfo.layer.get() setFrame:tileRect];
703             }
704             
705             FloatRect scaledTileRect = tileRect;
706             scaledTileRect.scale(1 / m_scale);
707             dirtyRects.append(scaledTileRect);
708         }
709     }
710
711     if (validationPolicy & PruneSecondaryTiles) {
712         removeAllSecondaryTiles();
713         m_cohortList.clear();
714     }
715
716     if (m_unparentsOffscreenTiles && (validationPolicy & UnparentAllTiles)) {
717         for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
718             [it->value.layer.get() removeFromSuperlayer];
719     }
720     
721     if (m_tiledScrollingIndicatorLayer)
722         updateTileCoverageMap();
723
724     m_visibleRectAtLastRevalidate = visibleRect;
725
726     if (dirtyRects.isEmpty())
727         return;
728     platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
729 }
730
731 TileController::TileCohort TileController::nextTileCohort() const
732 {
733     if (!m_cohortList.isEmpty())
734         return m_cohortList.last().cohort + 1;
735
736     return 1;
737 }
738
739 void TileController::startedNewCohort(TileCohort cohort)
740 {
741     m_cohortList.append(TileCohortInfo(cohort, monotonicallyIncreasingTime()));
742 }
743
744 TileController::TileCohort TileController::newestTileCohort() const
745 {
746     return m_cohortList.isEmpty() ? 0 : m_cohortList.last().cohort;
747 }
748
749 TileController::TileCohort TileController::oldestTileCohort() const
750 {
751     return m_cohortList.isEmpty() ? 0 : m_cohortList.first().cohort;
752 }
753
754 void TileController::scheduleCohortRemoval()
755 {
756     const double cohortRemovalTimerSeconds = 1;
757
758     // Start the timer, or reschedule the timer from now if it's already active.
759     if (!m_cohortRemovalTimer.isActive())
760         m_cohortRemovalTimer.startRepeating(cohortRemovalTimerSeconds);
761 }
762
763 void TileController::cohortRemovalTimerFired(Timer<TileController>*)
764 {
765     if (m_cohortList.isEmpty()) {
766         m_cohortRemovalTimer.stop();
767         return;
768     }
769
770     double cohortLifeTimeSeconds = 2;
771     double timeThreshold = monotonicallyIncreasingTime() - cohortLifeTimeSeconds;
772
773     while (!m_cohortList.isEmpty() && m_cohortList.first().creationTime < timeThreshold) {
774         TileCohortInfo firstCohort = m_cohortList.takeFirst();
775         removeTilesInCohort(firstCohort.cohort);
776     }
777
778     if (m_tiledScrollingIndicatorLayer)
779         updateTileCoverageMap();
780 }
781
782 void TileController::ensureTilesForRect(const FloatRect& rect)
783 {
784     if (m_unparentsOffscreenTiles && !m_isInWindow)
785         return;
786
787     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
788     if (!platformLayer)
789         return;
790
791     FloatRect scaledRect(rect);
792     scaledRect.scale(m_scale);
793     IntRect rectInTileCoords(enclosingIntRect(scaledRect));
794
795     if (m_primaryTileCoverageRect.contains(rectInTileCoords))
796         return;
797
798     TileIndex topLeft;
799     TileIndex bottomRight;
800     getTileIndexRangeForRect(rectInTileCoords, topLeft, bottomRight);
801
802     Vector<FloatRect> dirtyRects;
803     TileCohort currCohort = nextTileCohort();
804     unsigned tilesInCohort = 0;
805
806     for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
807         for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
808             TileIndex tileIndex(x, y);
809
810             IntRect tileRect = rectForTileIndex(tileIndex);
811             TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value;
812             if (!tileInfo.layer) {
813                 tileInfo.layer = createTileLayer(tileRect);
814                 [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
815             } else {
816                 if (![tileInfo.layer.get() superlayer])
817                     [m_tileContainerLayer.get() addSublayer:tileInfo.layer.get()];
818
819                 // We already have a layer for this tile. Ensure that its size is correct.
820                 CGSize tileLayerSize = [tileInfo.layer.get() frame].size;
821                 if (tileLayerSize.width >= tileRect.width() && tileLayerSize.height >= tileRect.height())
822                     continue;
823                 [tileInfo.layer.get() setFrame:tileRect];
824             }
825
826             if (!tileRect.intersects(m_primaryTileCoverageRect)) {
827                 tileInfo.cohort = currCohort;
828                 ++tilesInCohort;
829             }
830
831             FloatRect scaledTileRect = tileRect;
832             scaledTileRect.scale(1 / m_scale);
833             dirtyRects.append(scaledTileRect);
834         }
835     }
836     
837     if (tilesInCohort)
838         startedNewCohort(currCohort);
839
840     if (m_tiledScrollingIndicatorLayer)
841         updateTileCoverageMap();
842
843     if (!dirtyRects.isEmpty())
844         platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
845 }
846
847 void TileController::updateTileCoverageMap()
848 {
849     FloatRect containerBounds = bounds();
850     FloatRect visibleRect = this->visibleRect();
851
852     if (m_clipsToExposedRect)
853         visibleRect.intersect(m_exposedRect);
854
855     visibleRect.contract(4, 4); // Layer is positioned 2px from top and left edges.
856
857     float widthScale = 1;
858     float scale = 1;
859     if (!containerBounds.isEmpty()) {
860         widthScale = std::min<float>(visibleRect.width() / containerBounds.width(), 0.1);
861         scale = std::min(widthScale, visibleRect.height() / containerBounds.height());
862     }
863     
864     float indicatorScale = scale * m_scale;
865     FloatRect mapBounds = containerBounds;
866     mapBounds.scale(indicatorScale, indicatorScale);
867     
868     BEGIN_BLOCK_OBJC_EXCEPTIONS
869     
870     if (m_clipsToExposedRect)
871         [m_tiledScrollingIndicatorLayer.get() setPosition:m_exposedRect.location() + FloatPoint(2, 2)];
872     else
873         [m_tiledScrollingIndicatorLayer.get() setPosition:CGPointMake(2, 2)];
874
875     [m_tiledScrollingIndicatorLayer.get() setBounds:mapBounds];
876     [m_tiledScrollingIndicatorLayer.get() setNeedsDisplay];
877
878     visibleRect.scale(indicatorScale, indicatorScale);
879     visibleRect.expand(2, 2);
880     [[m_tiledScrollingIndicatorLayer.get() visibleRectFrameLayer] setFrame:visibleRect];
881
882     Color backgroundColor;
883     switch (m_indicatorMode) {
884     case MainThreadScrollingBecauseOfStyleIndication:
885         backgroundColor = Color(255, 0, 0);
886         break;
887     case MainThreadScrollingBecauseOfEventHandlersIndication:
888         backgroundColor = Color(255, 255, 0);
889         break;
890     case ThreadedScrollingIndication:
891         backgroundColor = Color(0, 200, 0);
892         break;
893     }
894
895     [[m_tiledScrollingIndicatorLayer.get() visibleRectFrameLayer] setBorderColor:cachedCGColor(backgroundColor, ColorSpaceDeviceRGB)];
896
897     END_BLOCK_OBJC_EXCEPTIONS
898 }
899
900 IntRect TileController::tileGridExtent() const
901 {
902     TileIndex topLeft;
903     TileIndex bottomRight;
904     getTileIndexRangeForRect(m_primaryTileCoverageRect, topLeft, bottomRight);
905
906     // Return index of top, left tile and the number of tiles across and down.
907     return IntRect(topLeft.x(), topLeft.y(), bottomRight.x() - topLeft.x() + 1, bottomRight.y() - topLeft.y() + 1);
908 }
909
910 double TileController::retainedTileBackingStoreMemory() const
911 {
912     double totalBytes = 0;
913     
914     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
915         const TileInfo& tileInfo = it->value;
916         if ([tileInfo.layer.get() superlayer]) {
917             CGRect bounds = [tileInfo.layer.get() bounds];
918             double contentsScale = [tileInfo.layer.get() contentsScale];
919             totalBytes += 4 * bounds.size.width * contentsScale * bounds.size.height * contentsScale;
920         }
921     }
922
923     return totalBytes;
924 }
925
926 // Return the rect in layer coords, not tile coords.
927 IntRect TileController::tileCoverageRect() const
928 {
929     IntRect coverageRectInLayerCoords(m_primaryTileCoverageRect);
930     coverageRectInLayerCoords.scale(1 / m_scale);
931     return coverageRectInLayerCoords;
932 }
933
934 CALayer *TileController::tiledScrollingIndicatorLayer()
935 {
936     if (!m_tiledScrollingIndicatorLayer) {
937         m_tiledScrollingIndicatorLayer = [WebTiledScrollingIndicatorLayer layer];
938         [m_tiledScrollingIndicatorLayer.get() setTileController:this];
939         [m_tiledScrollingIndicatorLayer.get() setOpacity:0.75];
940         [m_tiledScrollingIndicatorLayer.get() setAnchorPoint:CGPointZero];
941         [m_tiledScrollingIndicatorLayer.get() setBorderColor:cachedCGColor(Color::black, ColorSpaceDeviceRGB)];
942         [m_tiledScrollingIndicatorLayer.get() setBorderWidth:1];
943         [m_tiledScrollingIndicatorLayer.get() setPosition:CGPointMake(2, 2)];
944         updateTileCoverageMap();
945     }
946
947     return m_tiledScrollingIndicatorLayer.get();
948 }
949
950 void TileController::setScrollingModeIndication(ScrollingModeIndication scrollingMode)
951 {
952     if (scrollingMode == m_indicatorMode)
953         return;
954
955     m_indicatorMode = scrollingMode;
956
957     if (m_tiledScrollingIndicatorLayer)
958         updateTileCoverageMap();
959 }
960
961 WebTileLayer* TileController::tileLayerAtIndex(const TileIndex& index) const
962 {
963     return m_tiles.get(index).layer.get();
964 }
965
966 RetainPtr<WebTileLayer> TileController::createTileLayer(const IntRect& tileRect)
967 {
968     RetainPtr<WebTileLayer> layer = LayerPool::sharedPool()->takeLayerWithSize(tileRect.size());
969     if (layer) {
970         // If we were able to restore a layer from the LayerPool, we should call setNeedsDisplay to
971         // ensure we avoid stale content.
972         [layer resetPaintCount];
973         [layer setNeedsDisplay];
974     } else
975         layer = adoptNS([[WebTileLayer alloc] init]);
976     [layer.get() setAnchorPoint:CGPointZero];
977     [layer.get() setFrame:tileRect];
978     [layer.get() setTileController:this];
979     [layer.get() setBorderColor:m_tileDebugBorderColor.get()];
980     [layer.get() setBorderWidth:m_tileDebugBorderWidth];
981     [layer.get() setEdgeAntialiasingMask:0];
982     [layer.get() setOpaque:m_tilesAreOpaque];
983 #ifndef NDEBUG
984     [layer.get() setName:@"Tile"];
985 #endif
986
987 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
988     [layer.get() setContentsScale:m_deviceScaleFactor];
989     [layer.get() setAcceleratesDrawing:m_acceleratesDrawing];
990 #endif
991
992     return layer;
993 }
994
995 bool TileController::shouldShowRepaintCounters() const
996 {
997     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
998     if (!platformLayer)
999         return false;
1000
1001     WebCore::PlatformCALayerClient* layerContents = platformLayer->owner();
1002     ASSERT(layerContents);
1003     if (!layerContents)
1004         return false;
1005
1006     return layerContents->platformCALayerShowRepaintCounter(0);
1007 }
1008
1009 void TileController::drawRepaintCounter(WebTileLayer *layer, CGContextRef context)
1010 {
1011     unsigned paintCount = [layer incrementPaintCount];
1012     if (!shouldShowRepaintCounters())
1013         return;
1014
1015     // FIXME: Some of this code could be shared with WebLayer.
1016     char text[16]; // that's a lot of repaints
1017     snprintf(text, sizeof(text), "%d", paintCount);
1018
1019     CGRect indicatorBox = [layer bounds];
1020     indicatorBox.size.width = 12 + 10 * strlen(text);
1021     indicatorBox.size.height = 27;
1022     CGContextSaveGState(context);
1023
1024     CGContextSetAlpha(context, 0.5f);
1025     CGContextBeginTransparencyLayerWithRect(context, indicatorBox, 0);
1026
1027     CGContextSetFillColorWithColor(context, m_tileDebugBorderColor.get());
1028     CGContextFillRect(context, indicatorBox);
1029
1030     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
1031
1032     if (platformLayer->acceleratesDrawing())
1033         CGContextSetRGBFillColor(context, 1, 0, 0, 1);
1034     else
1035         CGContextSetRGBFillColor(context, 1, 1, 1, 1);
1036
1037 #pragma clang diagnostic push
1038 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
1039     CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1, -1));
1040     CGContextSelectFont(context, "Helvetica", 22, kCGEncodingMacRoman);
1041     CGContextShowTextAtPoint(context, indicatorBox.origin.x + 5, indicatorBox.origin.y + 22, text, strlen(text));
1042 #pragma clang diagnostic pop
1043
1044     CGContextEndTransparencyLayer(context);
1045     CGContextRestoreGState(context);
1046 }
1047
1048 void TileController::drawTileMapContents(CGContextRef context, CGRect layerBounds)
1049 {
1050     CGContextSetRGBFillColor(context, 0.3, 0.3, 0.3, 1);
1051     CGContextFillRect(context, layerBounds);
1052
1053     CGFloat scaleFactor = layerBounds.size.width / bounds().width();
1054
1055     CGFloat contextScale = scaleFactor / scale();
1056     CGContextScaleCTM(context, contextScale, contextScale);
1057     
1058     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
1059         const TileInfo& tileInfo = it->value;
1060         WebTileLayer* tileLayer = tileInfo.layer.get();
1061
1062         CGFloat red = 1;
1063         CGFloat green = 1;
1064         CGFloat blue = 1;
1065         if (tileInfo.hasStaleContent) {
1066             red = 0.25;
1067             green = 0.125;
1068             blue = 0;
1069         }
1070
1071         TileCohort newestCohort = newestTileCohort();
1072         TileCohort oldestCohort = oldestTileCohort();
1073
1074         if (!m_aggressivelyRetainsTiles && tileInfo.cohort != VisibleTileCohort && newestCohort > oldestCohort) {
1075             float cohortProportion = static_cast<float>((newestCohort - tileInfo.cohort)) / (newestCohort - oldestCohort);
1076             CGContextSetRGBFillColor(context, red, green, blue, 1 - cohortProportion);
1077         } else
1078             CGContextSetRGBFillColor(context, red, green, blue, 1);
1079
1080         if ([tileLayer superlayer]) {
1081             CGContextSetLineWidth(context, 0.5 / contextScale);
1082             CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
1083         } else {
1084             CGContextSetLineWidth(context, 1 / contextScale);
1085             CGContextSetRGBStrokeColor(context, 0.2, 0.1, 0.9, 1);
1086         }
1087
1088         CGRect frame = [tileLayer frame];
1089         CGContextFillRect(context, frame);
1090         CGContextStrokeRect(context, frame);
1091     }
1092 }
1093     
1094
1095 } // namespace WebCore