https://bugs.webkit.org/show_bug.cgi?id=100169
[WebKit-https.git] / Source / WebCore / platform / graphics / ca / mac / TileCache.mm
1 /*
2  * Copyright (C) 2011 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 "TileCache.h"
28
29 #import "IntRect.h"
30 #import "PlatformCALayer.h"
31 #import "Region.h"
32 #import "WebLayer.h"
33 #import "WebTileCacheLayer.h"
34 #import "WebTileLayer.h"
35 #import <wtf/MainThread.h>
36 #import <utility>
37
38 using namespace std;
39
40 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
41 @interface CALayer (WebCALayerDetails)
42 - (void)setAcceleratesDrawing:(BOOL)flag;
43 @end
44 #endif
45
46 namespace WebCore {
47
48 static const int defaultTileCacheWidth = 512;
49 static const int defaultTileCacheHeight = 512;
50
51 PassOwnPtr<TileCache> TileCache::create(WebTileCacheLayer* tileCacheLayer)
52 {
53     return adoptPtr(new TileCache(tileCacheLayer));
54 }
55
56 TileCache::TileCache(WebTileCacheLayer* tileCacheLayer)
57     : m_tileCacheLayer(tileCacheLayer)
58     , m_tileContainerLayer(adoptCF([[CALayer alloc] init]))
59     , m_tileSize(defaultTileCacheWidth, defaultTileCacheHeight)
60     , m_tileRevalidationTimer(this, &TileCache::tileRevalidationTimerFired)
61     , m_scale(1)
62     , m_deviceScaleFactor(1)
63     , m_tileCoverage(CoverageForVisibleArea)
64     , m_isInWindow(false)
65     , m_scrollingPerformanceLoggingEnabled(false)
66     , m_acceleratesDrawing(false)
67     , m_tilesAreOpaque(false)
68     , m_tileDebugBorderWidth(0)
69 {
70     [CATransaction begin];
71     [CATransaction setDisableActions:YES];
72     [m_tileCacheLayer addSublayer:m_tileContainerLayer.get()];
73 #ifndef NDEBUG
74     [m_tileContainerLayer.get() setName:@"TileCache Container Layer"];
75 #endif
76     [CATransaction commit];
77 }
78
79 TileCache::~TileCache()
80 {
81     ASSERT(isMainThread());
82
83     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
84         WebTileLayer* tileLayer = it->value.get();
85         [tileLayer setTileCache:0];
86     }
87 }
88
89 void TileCache::tileCacheLayerBoundsChanged()
90 {
91     if (m_tiles.isEmpty()) {
92         // We must revalidate immediately instead of using a timer when there are
93         // no tiles to avoid a flash when transitioning from one page to another.
94         revalidateTiles();
95         return;
96     }
97
98     scheduleTileRevalidation(0);
99 }
100
101 void TileCache::setNeedsDisplay()
102 {
103     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
104         [it->value.get() setNeedsDisplay];
105 }
106
107 void TileCache::setNeedsDisplayInRect(const IntRect& rect)
108 {
109     if (m_tiles.isEmpty())
110         return;
111
112     FloatRect scaledRect(rect);
113     scaledRect.scale(m_scale);
114
115     // Find the tiles that need to be invalidated.
116     IntRect coveredRect = intersection(enclosingIntRect(scaledRect), m_tileCoverageRect);
117     if (coveredRect.isEmpty())
118         return;
119
120     TileIndex topLeft;
121     TileIndex bottomRight;
122     getTileIndexRangeForRect(coveredRect, topLeft, bottomRight);
123
124     for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
125         for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
126             WebTileLayer* tileLayer = tileLayerAtIndex(TileIndex(x, y));
127             if (!tileLayer)
128                 continue;
129
130             CGRect tileRect = [m_tileCacheLayer convertRect:rect toLayer:tileLayer];
131             if (CGRectIsEmpty(tileRect))
132                 continue;
133
134             [tileLayer setNeedsDisplayInRect:tileRect];
135
136             if (shouldShowRepaintCounters()) {
137                 CGRect bounds = [tileLayer bounds];
138                 CGRect indicatorRect = CGRectMake(bounds.origin.x, bounds.origin.y, 52, 27);
139                 [tileLayer setNeedsDisplayInRect:indicatorRect];
140             }
141         }
142     }
143 }
144
145 void TileCache::drawLayer(WebTileLayer *layer, CGContextRef context)
146 {
147     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
148     if (!platformLayer)
149         return;
150
151     CGContextSaveGState(context);
152
153     CGPoint layerOrigin = [layer frame].origin;
154     CGContextTranslateCTM(context, -layerOrigin.x, -layerOrigin.y);
155     CGContextScaleCTM(context, m_scale, m_scale);
156     drawLayerContents(context, layer, platformLayer);
157
158     CGContextRestoreGState(context);
159
160     drawRepaintCounter(layer, context);
161 }
162
163 void TileCache::setScale(CGFloat scale)
164 {
165     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
166     float deviceScaleFactor = platformLayer->owner()->platformCALayerDeviceScaleFactor();
167
168     // The scale we get is the produce of the page scale factor and device scale factor.
169     // Divide by the device scale factor so we'll get the page scale factor.
170     scale /= deviceScaleFactor;
171
172     if (m_scale == scale && m_deviceScaleFactor == deviceScaleFactor)
173         return;
174
175 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
176     Vector<FloatRect> dirtyRects;
177
178     m_deviceScaleFactor = deviceScaleFactor;
179     m_scale = scale;
180     [m_tileContainerLayer.get() setTransform:CATransform3DMakeScale(1 / m_scale, 1 / m_scale, 1)];
181
182     revalidateTiles();
183
184     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
185         [it->value.get() setContentsScale:deviceScaleFactor];
186
187         IntRect tileRect = rectForTileIndex(it->key);
188         FloatRect scaledTileRect = tileRect;
189
190         scaledTileRect.scale(1 / m_scale);
191         dirtyRects.append(scaledTileRect);
192     }
193
194     platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
195 #endif
196 }
197
198 void TileCache::setAcceleratesDrawing(bool acceleratesDrawing)
199 {
200 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
201     if (m_acceleratesDrawing == acceleratesDrawing)
202         return;
203
204     m_acceleratesDrawing = acceleratesDrawing;
205
206     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
207         [it->value.get() setAcceleratesDrawing:m_acceleratesDrawing];
208 #else
209     UNUSED_PARAM(acceleratesDrawing);
210 #endif
211 }
212
213 void TileCache::setTilesOpaque(bool opaque)
214 {
215     if (opaque == m_tilesAreOpaque)
216         return;
217
218     m_tilesAreOpaque = opaque;
219
220     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
221         WebTileLayer* tileLayer = it->value.get();
222         [tileLayer setOpaque:opaque];
223     }
224 }
225
226 void TileCache::setVisibleRect(const IntRect& visibleRect)
227 {
228     if (m_visibleRect == visibleRect)
229         return;
230
231     m_visibleRect = visibleRect;
232     revalidateTiles();
233 }
234
235 void TileCache::setIsInWindow(bool isInWindow)
236 {
237     if (m_isInWindow == isInWindow)
238         return;
239
240     m_isInWindow = isInWindow;
241
242     if (!m_isInWindow) {
243         const double tileRevalidationTimeout = 4;
244         scheduleTileRevalidation(tileRevalidationTimeout);
245     }
246 }
247
248 void TileCache::setTileCoverage(TileCoverage coverage)
249 {
250     if (coverage == m_tileCoverage)
251         return;
252
253     m_tileCoverage = coverage;
254     scheduleTileRevalidation(0);
255 }
256
257 void TileCache::forceRepaint()
258 {
259     setNeedsDisplay();
260 }
261
262 void TileCache::setTileDebugBorderWidth(float borderWidth)
263 {
264     if (m_tileDebugBorderWidth == borderWidth)
265         return;
266
267     m_tileDebugBorderWidth = borderWidth;
268     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
269         [it->value.get() setBorderWidth:m_tileDebugBorderWidth];
270 }
271
272 void TileCache::setTileDebugBorderColor(CGColorRef borderColor)
273 {
274     if (m_tileDebugBorderColor == borderColor)
275         return;
276
277     m_tileDebugBorderColor = borderColor;
278     for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
279         [it->value.get() setBorderColor:m_tileDebugBorderColor.get()];
280 }
281
282 IntRect TileCache::bounds() const
283 {
284     return IntRect(IntPoint(), IntSize([m_tileCacheLayer bounds].size));
285 }
286
287 IntRect TileCache::rectForTileIndex(const TileIndex& tileIndex) const
288 {
289     IntRect rect(tileIndex.x() * m_tileSize.width(), tileIndex.y() * m_tileSize.height(), m_tileSize.width(), m_tileSize.height());
290     IntRect scaledBounds(bounds());
291     scaledBounds.scale(m_scale);
292
293     rect.intersect(scaledBounds);
294
295     return rect;
296 }
297
298 void TileCache::getTileIndexRangeForRect(const IntRect& rect, TileIndex& topLeft, TileIndex& bottomRight)
299 {
300     IntRect clampedRect = bounds();
301     clampedRect.scale(m_scale);
302     clampedRect.intersect(rect);
303
304     topLeft.setX(max(clampedRect.x() / m_tileSize.width(), 0));
305     topLeft.setY(max(clampedRect.y() / m_tileSize.height(), 0));
306
307     int bottomXRatio = ceil((float)clampedRect.maxX() / m_tileSize.width());
308     bottomRight.setX(max(bottomXRatio - 1, 0));
309
310     int bottomYRatio = ceil((float)clampedRect.maxY() / m_tileSize.height());
311     bottomRight.setY(max(bottomYRatio - 1, 0));
312 }
313
314 IntRect TileCache::computeTileCoverageRect() const
315 {
316     IntRect tileCoverageRect = m_visibleRect;
317
318     // 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.
319     // Furthermore, if the page can't have scrollbars (for example if its body element has overflow:hidden) it's very unlikely that the
320     // page will ever be scrolled so we limit the tile coverage rect as well.
321     if (m_isInWindow) {
322         // Inflate the coverage rect so that it covers 2x of the visible width and 3x of the visible height.
323         // These values were chosen because it's more common to have tall pages and to scroll vertically,
324         // so we keep more tiles above and below the current area.
325         if (m_tileCoverage & CoverageForHorizontalScrolling)
326             tileCoverageRect.inflateX(tileCoverageRect.width() / 2);
327
328         if (m_tileCoverage & CoverageForVerticalScrolling)
329             tileCoverageRect.inflateY(tileCoverageRect.height());
330     }
331
332     return tileCoverageRect;
333 }
334
335 IntSize TileCache::tileSizeForCoverageRect(const IntRect& coverageRect) const
336 {
337     if (m_tileCoverage == CoverageForVisibleArea)
338         return coverageRect.size();
339     return IntSize(defaultTileCacheWidth, defaultTileCacheHeight);
340 }
341
342 void TileCache::scheduleTileRevalidation(double interval)
343 {
344     if (m_tileRevalidationTimer.isActive() && m_tileRevalidationTimer.nextFireInterval() < interval)
345         return;
346
347     m_tileRevalidationTimer.startOneShot(interval);
348 }
349
350 void TileCache::tileRevalidationTimerFired(Timer<TileCache>*)
351 {
352     revalidateTiles();
353 }
354
355 unsigned TileCache::blankPixelCount() const
356 {
357     WebTileLayerList tiles(m_tiles.size());
358     tiles.appendRange(m_tiles.begin().values(), m_tiles.end().values());
359
360     return blankPixelCountForTiles(tiles, m_visibleRect, IntPoint(0,0));
361 }
362
363 unsigned TileCache::blankPixelCountForTiles(const WebTileLayerList& tiles, IntRect visibleRect, IntPoint tileTranslation)
364 {
365     Region paintedVisibleTiles;
366
367     for (WebTileLayerList::const_iterator it = tiles.begin(), end = tiles.end(); it != end; ++it) {
368         const WebTileLayer* tileLayer = it->get();
369
370         IntRect visiblePart(CGRectOffset([tileLayer frame], tileTranslation.x(), tileTranslation.y()));
371         visiblePart.intersect(visibleRect);
372
373         if (!visiblePart.isEmpty() && [tileLayer repaintCount])
374             paintedVisibleTiles.unite(visiblePart);
375     }
376
377     Region uncoveredRegion(visibleRect);
378     uncoveredRegion.subtract(paintedVisibleTiles);
379
380     return uncoveredRegion.totalArea();
381 }
382
383 void TileCache::revalidateTiles()
384 {
385     // If the underlying PlatformLayer has been destroyed, but the WebTileCacheLayer hasn't
386     // platformLayer will be null here.
387     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
388     if (!platformLayer)
389         return;
390
391     if (m_visibleRect.isEmpty() || bounds().isEmpty())
392         return;
393
394     IntRect tileCoverageRect = computeTileCoverageRect();
395
396     IntSize oldTileSize = m_tileSize;
397     m_tileSize = tileSizeForCoverageRect(tileCoverageRect);
398     bool tileSizeChanged = m_tileSize != oldTileSize;
399
400     Vector<TileIndex> tilesToRemove;
401
402     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
403         const TileIndex& tileIndex = it->key;
404
405         WebTileLayer* tileLayer = it->value.get();
406
407         if (!rectForTileIndex(tileIndex).intersects(tileCoverageRect) || tileSizeChanged) {
408             // Remove this layer.
409             [tileLayer removeFromSuperlayer];
410             [tileLayer setTileCache:0];
411
412             tilesToRemove.append(tileIndex);
413         }
414     }
415
416     // FIXME: Be more clever about which tiles to remove. We might not want to remove all
417     // the tiles that are outside the coverage rect. When we know that we're going to be scrolling up,
418     // we might want to remove the ones below the coverage rect but keep the ones above.
419     for (size_t i = 0; i < tilesToRemove.size(); ++i)
420         m_tiles.remove(tilesToRemove[i]);
421
422     TileIndex topLeft;
423     TileIndex bottomRight;
424     getTileIndexRangeForRect(tileCoverageRect, topLeft, bottomRight);
425
426     Vector<FloatRect> dirtyRects;
427
428     for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
429         for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
430             TileIndex tileIndex(x, y);
431
432             IntRect tileRect = rectForTileIndex(tileIndex);
433             RetainPtr<WebTileLayer>& tileLayer = m_tiles.add(tileIndex, 0).iterator->value;
434             if (!tileLayer) {
435                 tileLayer = createTileLayer(tileRect);
436                 [m_tileContainerLayer.get() addSublayer:tileLayer.get()];
437             } else {
438                 // We already have a layer for this tile. Ensure that its size is correct.
439                 CGSize tileLayerSize = [tileLayer.get() frame].size;
440                 if (tileLayerSize.width >= tileRect.width() && tileLayerSize.height >= tileRect.height())
441                     continue;
442                 [tileLayer.get() setFrame:tileRect];
443             }
444
445             FloatRect scaledTileRect = tileRect;
446             scaledTileRect.scale(1 / m_scale);
447             dirtyRects.append(scaledTileRect);
448         }
449     }
450
451     m_tileCoverageRect = IntRect();
452     for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
453         const TileIndex& tileIndex = it->key;
454
455         m_tileCoverageRect.unite(rectForTileIndex(tileIndex));
456     }
457
458     if (dirtyRects.isEmpty())
459         return;
460     platformLayer->owner()->platformCALayerDidCreateTiles(dirtyRects);
461 }
462
463 WebTileLayer* TileCache::tileLayerAtIndex(const TileIndex& index) const
464 {
465     return m_tiles.get(index).get();
466 }
467
468 RetainPtr<WebTileLayer> TileCache::createTileLayer(const IntRect& tileRect)
469 {
470     RetainPtr<WebTileLayer> layer = adoptNS([[WebTileLayer alloc] init]);
471     [layer.get() setAnchorPoint:CGPointZero];
472     [layer.get() setFrame:tileRect];
473     [layer.get() setTileCache:this];
474     [layer.get() setBorderColor:m_tileDebugBorderColor.get()];
475     [layer.get() setBorderWidth:m_tileDebugBorderWidth];
476     [layer.get() setEdgeAntialiasingMask:0];
477     [layer.get() setOpaque:m_tilesAreOpaque];
478 #ifndef NDEBUG
479     [layer.get() setName:@"Tile"];
480 #endif
481
482 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
483     [layer.get() setContentsScale:m_deviceScaleFactor];
484     [layer.get() setAcceleratesDrawing:m_acceleratesDrawing];
485 #endif
486
487     return layer;
488 }
489
490 bool TileCache::shouldShowRepaintCounters() const
491 {
492     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
493     if (!platformLayer)
494         return false;
495
496     WebCore::PlatformCALayerClient* layerContents = platformLayer->owner();
497     ASSERT(layerContents);
498     if (!layerContents)
499         return false;
500
501     return layerContents->platformCALayerShowRepaintCounter(0);
502 }
503
504 void TileCache::drawRepaintCounter(WebTileLayer *layer, CGContextRef context)
505 {
506     unsigned repaintCount = [layer incrementRepaintCount];
507     if (!shouldShowRepaintCounters())
508         return;
509
510     // FIXME: Some of this code could be shared with WebLayer.
511     char text[16]; // that's a lot of repaints
512     snprintf(text, sizeof(text), "%d", repaintCount);
513
514     CGRect indicatorBox = [layer bounds];
515     indicatorBox.size.width = 12 + 10 * strlen(text);
516     indicatorBox.size.height = 27;
517     CGContextSaveGState(context);
518
519     CGContextSetAlpha(context, 0.5f);
520     CGContextBeginTransparencyLayerWithRect(context, indicatorBox, 0);
521
522     CGContextSetFillColorWithColor(context, m_tileDebugBorderColor.get());
523     CGContextFillRect(context, indicatorBox);
524
525     PlatformCALayer* platformLayer = PlatformCALayer::platformCALayer(m_tileCacheLayer);
526
527     if (platformLayer->acceleratesDrawing())
528         CGContextSetRGBFillColor(context, 1, 0, 0, 1);
529     else
530         CGContextSetRGBFillColor(context, 1, 1, 1, 1);
531
532     CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1, -1));
533     CGContextSelectFont(context, "Helvetica", 22, kCGEncodingMacRoman);
534     CGContextShowTextAtPoint(context, indicatorBox.origin.x + 5, indicatorBox.origin.y + 22, text, strlen(text));
535
536     CGContextEndTransparencyLayer(context);
537     CGContextRestoreGState(context);
538 }
539
540 } // namespace WebCore