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