1/*
2 * Copyright (C) 2011-2014 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#include "config.h"
27#include "TileGrid.h"
28
29#include "GraphicsContext.h"
30#include "LayerPool.h"
31#include "PlatformCALayer.h"
32#include "TileController.h"
33#include <wtf/MainThread.h>
34
35#if PLATFORM(IOS)
36#include "TileControllerMemoryHandlerIOS.h"
37#endif
38
39namespace WebCore {
40
41TileGrid::TileGrid(TileController& controller)
42    : m_controller(controller)
43    , m_containerLayer(*controller.rootLayer().createCompatibleLayer(PlatformCALayer::LayerTypeLayer, nullptr))
44    , m_scale(1)
45    , m_cohortRemovalTimer(this, &TileGrid::cohortRemovalTimerFired)
46{
47#ifndef NDEBUG
48    m_containerLayer.get().setName("TileGrid Container Layer");
49#endif
50}
51
52TileGrid::~TileGrid()
53{
54    ASSERT(isMainThread());
55
56    for (auto& tile : m_tiles.values())
57        tile.layer->setOwner(nullptr);
58}
59
60void TileGrid::setScale(float scale)
61{
62    m_scale = scale;
63
64    TransformationMatrix transform;
65    transform.scale(1 / m_scale);
66    m_containerLayer->setTransform(transform);
67
68    // FIXME: we may revalidateTiles twice in this commit.
69    revalidateTiles(PruneSecondaryTiles);
70
71    for (auto& tile : m_tiles.values())
72        tile.layer->setContentsScale(m_controller.deviceScaleFactor());
73}
74
75void TileGrid::setNeedsDisplay()
76{
77    for (auto& entry : m_tiles) {
78        TileInfo& tileInfo = entry.value;
79        IntRect tileRect = rectForTileIndex(entry.key);
80
81        if (tileRect.intersects(m_primaryTileCoverageRect) && tileInfo.layer->superlayer())
82            tileInfo.layer->setNeedsDisplay();
83        else
84            tileInfo.hasStaleContent = true;
85    }
86}
87
88void TileGrid::setNeedsDisplayInRect(const IntRect& rect)
89{
90    if (m_tiles.isEmpty())
91        return;
92
93    FloatRect scaledRect(rect);
94    scaledRect.scale(m_scale);
95    IntRect repaintRectInTileCoords(enclosingIntRect(scaledRect));
96
97    IntSize tileSize = m_controller.tileSize();
98
99    // For small invalidations, lookup the covered tiles.
100    if (repaintRectInTileCoords.height() < 2 * tileSize.height() && repaintRectInTileCoords.width() < 2 * tileSize.width()) {
101        TileIndex topLeft;
102        TileIndex bottomRight;
103        getTileIndexRangeForRect(repaintRectInTileCoords, topLeft, bottomRight);
104
105        for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
106            for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
107                TileIndex tileIndex(x, y);
108
109                TileMap::iterator it = m_tiles.find(tileIndex);
110                if (it != m_tiles.end())
111                    setTileNeedsDisplayInRect(tileIndex, it->value, repaintRectInTileCoords, m_primaryTileCoverageRect);
112            }
113        }
114        return;
115    }
116
117    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
118        setTileNeedsDisplayInRect(it->key, it->value, repaintRectInTileCoords, m_primaryTileCoverageRect);
119}
120
121void TileGrid::dropTilesInRect(const IntRect& rect)
122{
123    if (m_tiles.isEmpty())
124        return;
125
126    FloatRect scaledRect(rect);
127    scaledRect.scale(m_scale);
128    IntRect dropRectInTileCoords(enclosingIntRect(scaledRect));
129
130    Vector<TileIndex> tilesToRemove;
131
132    for (auto& index : m_tiles.keys()) {
133        if (rectForTileIndex(index).intersects(dropRectInTileCoords))
134            tilesToRemove.append(index);
135    }
136
137    removeTiles(tilesToRemove);
138}
139
140void TileGrid::setTileNeedsDisplayInRect(const TileIndex& tileIndex, TileInfo& tileInfo, const IntRect& repaintRectInTileCoords, const IntRect& coverageRectInTileCoords)
141{
142    PlatformCALayer* tileLayer = tileInfo.layer.get();
143
144    IntRect tileRect = rectForTileIndex(tileIndex);
145    FloatRect tileRepaintRect = tileRect;
146    tileRepaintRect.intersect(repaintRectInTileCoords);
147    if (tileRepaintRect.isEmpty())
148        return;
149
150    tileRepaintRect.moveBy(-tileRect.location());
151
152    // We could test for intersection with the visible rect. This would reduce painting yet more,
153    // but may make scrolling stale tiles into view more frequent.
154    if (tileRect.intersects(coverageRectInTileCoords) && tileLayer->superlayer()) {
155        tileLayer->setNeedsDisplay(&tileRepaintRect);
156
157        if (m_controller.rootLayer().owner()->platformCALayerShowRepaintCounter(0)) {
158            FloatRect indicatorRect(0, 0, 52, 27);
159            tileLayer->setNeedsDisplay(&indicatorRect);
160        }
161    } else
162        tileInfo.hasStaleContent = true;
163}
164
165void TileGrid::updateTileLayerProperties()
166{
167    bool acceleratesDrawing = m_controller.acceleratesDrawing();
168    bool opaque = m_controller.tilesAreOpaque();
169    Color tileDebugBorderColor = m_controller.tileDebugBorderColor();
170    float tileDebugBorderWidth = m_controller.tileDebugBorderWidth();
171
172    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
173        const TileInfo& tileInfo = it->value;
174        tileInfo.layer->setAcceleratesDrawing(acceleratesDrawing);
175        tileInfo.layer->setOpaque(opaque);
176        tileInfo.layer->setBorderColor(tileDebugBorderColor);
177        tileInfo.layer->setBorderWidth(tileDebugBorderWidth);
178    }
179}
180
181bool TileGrid::tilesWouldChangeForVisibleRect(const FloatRect& newVisibleRect, const FloatRect& oldVisibleRect) const
182{
183    FloatRect visibleRect = newVisibleRect;
184
185    if (visibleRect.isEmpty())
186        return false;
187
188    FloatRect currentTileCoverageRect = m_controller.computeTileCoverageRect(oldVisibleRect, newVisibleRect);
189    FloatRect scaledRect(currentTileCoverageRect);
190    scaledRect.scale(m_scale);
191    IntRect currentCoverageRectInTileCoords(enclosingIntRect(scaledRect));
192
193    TileIndex topLeft;
194    TileIndex bottomRight;
195    getTileIndexRangeForRect(currentCoverageRectInTileCoords, topLeft, bottomRight);
196
197    IntRect coverageRect = rectForTileIndex(topLeft);
198    coverageRect.unite(rectForTileIndex(bottomRight));
199    return coverageRect != m_primaryTileCoverageRect;
200}
201
202bool TileGrid::prepopulateRect(const FloatRect& rect)
203{
204    IntRect enclosingCoverageRect = enclosingIntRect(rect);
205    if (m_primaryTileCoverageRect.contains(enclosingCoverageRect))
206        return false;
207
208    m_secondaryTileCoverageRects.append(enclosingCoverageRect);
209    return true;
210}
211
212IntRect TileGrid::rectForTileIndex(const TileIndex& tileIndex) const
213{
214    // FIXME: calculating the scaled size here should match with the rest of calculated sizes where we use the combination of
215    // enclosingIntRect, expandedIntSize (floor vs ceil).
216    // However enclosing this size could reveal gap on root layer's background. see RenderView::backgroundRect()
217    IntSize tileSize = m_controller.tileSize();
218    IntRect rect(tileIndex.x() * tileSize.width(), tileIndex.y() * tileSize.height(), tileSize.width(), tileSize.height());
219    IntRect scaledBounds(m_controller.bounds());
220    scaledBounds.scale(m_scale);
221    rect.intersect(scaledBounds);
222    return rect;
223}
224
225void TileGrid::getTileIndexRangeForRect(const IntRect& rect, TileIndex& topLeft, TileIndex& bottomRight) const
226{
227    IntRect clampedRect = m_controller.bounds();
228    clampedRect.scale(m_scale);
229    clampedRect.intersect(rect);
230
231    auto tileSize = m_controller.tileSize();
232    if (clampedRect.x() >= 0)
233        topLeft.setX(clampedRect.x() / tileSize.width());
234    else
235        topLeft.setX(floorf((float)clampedRect.x() / tileSize.width()));
236
237    if (clampedRect.y() >= 0)
238        topLeft.setY(clampedRect.y() / tileSize.height());
239    else
240        topLeft.setY(floorf((float)clampedRect.y() / tileSize.height()));
241
242    int bottomXRatio = ceil((float)clampedRect.maxX() / tileSize.width());
243    bottomRight.setX(std::max(bottomXRatio - 1, 0));
244
245    int bottomYRatio = ceil((float)clampedRect.maxY() / tileSize.height());
246    bottomRight.setY(std::max(bottomYRatio - 1, 0));
247}
248
249unsigned TileGrid::blankPixelCount() const
250{
251    PlatformLayerList tiles(m_tiles.size());
252
253    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
254        if (PlatformLayer *layer = it->value.layer->platformLayer())
255            tiles.append(layer);
256    }
257
258    return TileController::blankPixelCountForTiles(tiles, m_controller.visibleRect(), IntPoint(0, 0));
259}
260
261void TileGrid::removeTiles(Vector<TileGrid::TileIndex>& toRemove)
262{
263    for (size_t i = 0; i < toRemove.size(); ++i) {
264        TileInfo tileInfo = m_tiles.take(toRemove[i]);
265        tileInfo.layer->removeFromSuperlayer();
266        m_tileRepaintCounts.remove(tileInfo.layer.get());
267        tileInfo.layer->moveToLayerPool();
268    }
269}
270
271void TileGrid::removeAllSecondaryTiles()
272{
273    Vector<TileIndex> tilesToRemove;
274
275    for (auto& entry : m_tiles) {
276        const TileInfo& tileInfo = entry.value;
277        if (tileInfo.cohort == VisibleTileCohort)
278            continue;
279        tilesToRemove.append(entry.key);
280    }
281
282    removeTiles(tilesToRemove);
283}
284
285void TileGrid::removeTilesInCohort(TileCohort cohort)
286{
287    ASSERT(cohort != VisibleTileCohort);
288    Vector<TileIndex> tilesToRemove;
289
290    for (auto& entry : m_tiles) {
291        const TileInfo& tileInfo = entry.value;
292        if (tileInfo.cohort != cohort)
293            continue;
294        tilesToRemove.append(entry.key);
295    }
296
297    removeTiles(tilesToRemove);
298}
299
300void TileGrid::revalidateTiles(unsigned validationPolicy)
301{
302    FloatRect visibleRect = m_controller.visibleRect();
303    IntRect bounds = m_controller.bounds();
304
305    if (visibleRect.isEmpty() || bounds.isEmpty())
306        return;
307
308    FloatRect tileCoverageRect = m_controller.computeTileCoverageRect(m_controller.visibleRectAtLastRevalidate(), visibleRect);
309    FloatRect scaledRect(tileCoverageRect);
310    scaledRect.scale(m_scale);
311    IntRect coverageRectInTileCoords(enclosingIntRect(scaledRect));
312
313    TileCohort currCohort = nextTileCohort();
314    unsigned tilesInCohort = 0;
315
316    double minimumRevalidationTimerDuration = std::numeric_limits<double>::max();
317    bool needsTileRevalidation = false;
318
319    // Move tiles newly outside the coverage rect into the cohort map.
320    for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
321        TileInfo& tileInfo = it->value;
322        TileIndex tileIndex = it->key;
323
324        PlatformCALayer* tileLayer = tileInfo.layer.get();
325        IntRect tileRect = rectForTileIndex(tileIndex);
326        if (tileRect.intersects(coverageRectInTileCoords)) {
327            tileInfo.cohort = VisibleTileCohort;
328            if (tileInfo.hasStaleContent) {
329                // FIXME: store a dirty region per layer?
330                tileLayer->setNeedsDisplay();
331                tileInfo.hasStaleContent = false;
332            }
333        } else {
334            // Add to the currentCohort if not already in one.
335            if (tileInfo.cohort == VisibleTileCohort) {
336                tileInfo.cohort = currCohort;
337                ++tilesInCohort;
338
339                if (m_controller.unparentsOffscreenTiles())
340                    tileLayer->removeFromSuperlayer();
341            } else if (m_controller.unparentsOffscreenTiles() && m_controller.shouldAggressivelyRetainTiles() && tileLayer->superlayer()) {
342                // Aggressive tile retention means we'll never remove cohorts, but we need to make sure they're unparented.
343                // We can't immediately unparent cohorts comprised of secondary tiles that never touch the primary coverage rect,
344                // because that would defeat the usefulness of prepopulateRect(); instead, age prepopulated tiles out as if they were being removed.
345                for (auto& cohort : m_cohortList) {
346                    if (cohort.cohort != tileInfo.cohort)
347                        continue;
348                    double timeUntilCohortExpires = cohort.timeUntilExpiration();
349                    if (timeUntilCohortExpires > 0) {
350                        minimumRevalidationTimerDuration = std::min(minimumRevalidationTimerDuration, timeUntilCohortExpires);
351                        needsTileRevalidation = true;
352                    } else
353                        tileLayer->removeFromSuperlayer();
354                    break;
355                }
356            }
357        }
358    }
359
360    if (needsTileRevalidation)
361        m_controller.scheduleTileRevalidation(minimumRevalidationTimerDuration);
362
363    if (tilesInCohort)
364        startedNewCohort(currCohort);
365
366    if (!m_controller.shouldAggressivelyRetainTiles()) {
367        if (m_controller.shouldTemporarilyRetainTileCohorts())
368            scheduleCohortRemoval();
369        else if (tilesInCohort)
370            removeTilesInCohort(currCohort);
371    }
372
373    // Ensure primary tile coverage tiles.
374    m_primaryTileCoverageRect = ensureTilesForRect(tileCoverageRect, CoverageType::PrimaryTiles);
375
376    if (validationPolicy & PruneSecondaryTiles) {
377        removeAllSecondaryTiles();
378        m_cohortList.clear();
379    } else {
380        for (auto& secondaryCoverageRect : m_secondaryTileCoverageRects) {
381            FloatRect secondaryRectInLayerCoordinates(secondaryCoverageRect);
382            secondaryRectInLayerCoordinates.scale(1 / m_scale);
383            ensureTilesForRect(secondaryRectInLayerCoordinates, CoverageType::SecondaryTiles);
384        }
385        m_secondaryTileCoverageRects.clear();
386    }
387
388    if (m_controller.unparentsOffscreenTiles() && (validationPolicy & UnparentAllTiles)) {
389        for (TileMap::iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it)
390            it->value.layer->removeFromSuperlayer();
391    }
392
393    auto boundsAtLastRevalidate = m_controller.boundsAtLastRevalidate();
394    if (boundsAtLastRevalidate != bounds) {
395        // If there are margin tiles and the bounds have grown taller or wider, then the tiles that used to
396        // be bottom or right margin tiles need to be invalidated.
397        if (m_controller.hasMargins()) {
398            if (bounds.width() > boundsAtLastRevalidate.width() || bounds.height() > boundsAtLastRevalidate.height()) {
399                IntRect boundsWithoutMargin = m_controller.boundsWithoutMargin();
400                IntRect oldBoundsWithoutMargin = m_controller.boundsAtLastRevalidateWithoutMargin();
401
402                if (bounds.height() > boundsAtLastRevalidate.height()) {
403                    IntRect formerBottomMarginRect = IntRect(oldBoundsWithoutMargin.x(), oldBoundsWithoutMargin.height(),
404                        oldBoundsWithoutMargin.width(), boundsWithoutMargin.height() - oldBoundsWithoutMargin.height());
405                    setNeedsDisplayInRect(formerBottomMarginRect);
406                }
407
408                if (bounds.width() > boundsAtLastRevalidate.width()) {
409                    IntRect formerRightMarginRect = IntRect(oldBoundsWithoutMargin.width(), oldBoundsWithoutMargin.y(),
410                        boundsWithoutMargin.width() - oldBoundsWithoutMargin.width(), oldBoundsWithoutMargin.height());
411                    setNeedsDisplayInRect(formerRightMarginRect);
412                }
413            }
414        }
415
416        FloatRect scaledBounds(bounds);
417        scaledBounds.scale(m_scale);
418        IntRect boundsInTileCoords(enclosingIntRect(scaledBounds));
419
420        TileIndex topLeftForBounds;
421        TileIndex bottomRightForBounds;
422        getTileIndexRangeForRect(boundsInTileCoords, topLeftForBounds, bottomRightForBounds);
423
424        Vector<TileIndex> tilesToRemove;
425        for (auto& index : m_tiles.keys()) {
426            if (index.y() < topLeftForBounds.y() || index.y() > bottomRightForBounds.y() || index.x() < topLeftForBounds.x() || index.x() > bottomRightForBounds.x())
427                tilesToRemove.append(index);
428        }
429        removeTiles(tilesToRemove);
430    }
431
432    m_controller.didRevalidateTiles();
433}
434
435TileGrid::TileCohort TileGrid::nextTileCohort() const
436{
437    if (!m_cohortList.isEmpty())
438        return m_cohortList.last().cohort + 1;
439
440    return 1;
441}
442
443void TileGrid::startedNewCohort(TileCohort cohort)
444{
445    m_cohortList.append(TileCohortInfo(cohort, monotonicallyIncreasingTime()));
446#if PLATFORM(IOS)
447    if (!m_controller.isInWindow())
448        tileControllerMemoryHandler().tileControllerGainedUnparentedTiles(&m_controller);
449#endif
450}
451
452TileGrid::TileCohort TileGrid::newestTileCohort() const
453{
454    return m_cohortList.isEmpty() ? 0 : m_cohortList.last().cohort;
455}
456
457TileGrid::TileCohort TileGrid::oldestTileCohort() const
458{
459    return m_cohortList.isEmpty() ? 0 : m_cohortList.first().cohort;
460}
461
462void TileGrid::scheduleCohortRemoval()
463{
464    const double cohortRemovalTimerSeconds = 1;
465
466    // Start the timer, or reschedule the timer from now if it's already active.
467    if (!m_cohortRemovalTimer.isActive())
468        m_cohortRemovalTimer.startRepeating(cohortRemovalTimerSeconds);
469}
470
471double TileGrid::TileCohortInfo::timeUntilExpiration()
472{
473    double cohortLifeTimeSeconds = 2;
474    double timeThreshold = monotonicallyIncreasingTime() - cohortLifeTimeSeconds;
475    return creationTime - timeThreshold;
476}
477
478void TileGrid::cohortRemovalTimerFired(Timer<TileGrid>*)
479{
480    if (m_cohortList.isEmpty()) {
481        m_cohortRemovalTimer.stop();
482        return;
483    }
484
485    while (!m_cohortList.isEmpty() && m_cohortList.first().timeUntilExpiration() < 0) {
486        TileCohortInfo firstCohort = m_cohortList.takeFirst();
487        removeTilesInCohort(firstCohort.cohort);
488    }
489
490    m_controller.updateTileCoverageMap();
491}
492
493IntRect TileGrid::ensureTilesForRect(const FloatRect& rect, CoverageType newTileType)
494{
495    if (m_controller.unparentsOffscreenTiles() && !m_controller.isInWindow())
496        return IntRect();
497
498    FloatRect scaledRect(rect);
499    scaledRect.scale(m_scale);
500    IntRect rectInTileCoords(enclosingIntRect(scaledRect));
501
502    TileIndex topLeft;
503    TileIndex bottomRight;
504    getTileIndexRangeForRect(rectInTileCoords, topLeft, bottomRight);
505
506    TileCohort currCohort = nextTileCohort();
507    unsigned tilesInCohort = 0;
508
509    IntRect coverageRect;
510
511    for (int y = topLeft.y(); y <= bottomRight.y(); ++y) {
512        for (int x = topLeft.x(); x <= bottomRight.x(); ++x) {
513            TileIndex tileIndex(x, y);
514
515            IntRect tileRect = rectForTileIndex(tileIndex);
516            TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value;
517
518            coverageRect.unite(tileRect);
519
520            bool shouldChangeTileLayerFrame = false;
521
522            if (!tileInfo.layer) {
523                tileInfo.layer = m_controller.createTileLayer(tileRect, *this);
524                ASSERT(!m_tileRepaintCounts.contains(tileInfo.layer.get()));
525            } else {
526                // We already have a layer for this tile. Ensure that its size is correct.
527                FloatSize tileLayerSize(tileInfo.layer->bounds().size());
528                shouldChangeTileLayerFrame = tileLayerSize != FloatSize(tileRect.size());
529
530                if (shouldChangeTileLayerFrame) {
531                    tileInfo.layer->setBounds(FloatRect(FloatPoint(), tileRect.size()));
532                    tileInfo.layer->setPosition(tileRect.location());
533                    tileInfo.layer->setNeedsDisplay();
534                }
535            }
536
537            if (newTileType == CoverageType::SecondaryTiles && !tileRect.intersects(m_primaryTileCoverageRect)) {
538                tileInfo.cohort = currCohort;
539                ++tilesInCohort;
540            }
541
542            bool shouldParentTileLayer = (!m_controller.unparentsOffscreenTiles() || m_controller.isInWindow()) && !tileInfo.layer->superlayer();
543
544            if (shouldParentTileLayer)
545                m_containerLayer.get().appendSublayer(tileInfo.layer.get());
546        }
547    }
548
549    if (tilesInCohort)
550        startedNewCohort(currCohort);
551
552    return coverageRect;
553}
554
555IntRect TileGrid::extent() const
556{
557    TileIndex topLeft;
558    TileIndex bottomRight;
559    getTileIndexRangeForRect(m_primaryTileCoverageRect, topLeft, bottomRight);
560
561    // Return index of top, left tile and the number of tiles across and down.
562    return IntRect(topLeft.x(), topLeft.y(), bottomRight.x() - topLeft.x() + 1, bottomRight.y() - topLeft.y() + 1);
563}
564
565double TileGrid::retainedTileBackingStoreMemory() const
566{
567    double totalBytes = 0;
568
569    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
570        const TileInfo& tileInfo = it->value;
571        if (tileInfo.layer->superlayer()) {
572            FloatRect bounds = tileInfo.layer->bounds();
573            double contentsScale = tileInfo.layer->contentsScale();
574            totalBytes += 4 * bounds.width() * contentsScale * bounds.height() * contentsScale;
575        }
576    }
577
578    return totalBytes;
579}
580
581// Return the rect in layer coords, not tile coords.
582IntRect TileGrid::tileCoverageRect() const
583{
584    IntRect coverageRectInLayerCoords(m_primaryTileCoverageRect);
585    coverageRectInLayerCoords.scale(1 / m_scale);
586    return coverageRectInLayerCoords;
587}
588
589void TileGrid::drawTileMapContents(CGContextRef context, CGRect layerBounds) const
590{
591    CGContextSetRGBFillColor(context, 0.3, 0.3, 0.3, 1);
592    CGContextFillRect(context, layerBounds);
593
594    CGFloat scaleFactor = layerBounds.size.width / m_controller.bounds().width();
595
596    CGFloat contextScale = scaleFactor / m_scale;
597    CGContextScaleCTM(context, contextScale, contextScale);
598
599    for (TileMap::const_iterator it = m_tiles.begin(), end = m_tiles.end(); it != end; ++it) {
600        const TileInfo& tileInfo = it->value;
601        PlatformCALayer* tileLayer = tileInfo.layer.get();
602
603        CGFloat red = 1;
604        CGFloat green = 1;
605        CGFloat blue = 1;
606        CGFloat alpha = 1;
607        if (tileInfo.hasStaleContent) {
608            red = 0.25;
609            green = 0.125;
610            blue = 0;
611        } else if (m_controller.shouldAggressivelyRetainTiles() && tileInfo.cohort != VisibleTileCohort) {
612            red = 0.8;
613            green = 0.8;
614            blue = 0.8;
615        }
616
617        TileCohort newestCohort = newestTileCohort();
618        TileCohort oldestCohort = oldestTileCohort();
619
620        if (!m_controller.shouldAggressivelyRetainTiles() && tileInfo.cohort != VisibleTileCohort && newestCohort > oldestCohort)
621            alpha = 1 - (static_cast<float>((newestCohort - tileInfo.cohort)) / (newestCohort - oldestCohort));
622
623        CGContextSetRGBFillColor(context, red, green, blue, alpha);
624
625        if (tileLayer->superlayer()) {
626            CGContextSetLineWidth(context, 0.5 / contextScale);
627            CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
628        } else {
629            CGContextSetLineWidth(context, 1 / contextScale);
630            CGContextSetRGBStrokeColor(context, 0.2, 0.1, 0.9, 1);
631        }
632
633        CGRect frame = CGRectMake(tileLayer->position().x(), tileLayer->position().y(), tileLayer->bounds().size().width(), tileLayer->bounds().size().height());
634        CGContextFillRect(context, frame);
635        CGContextStrokeRect(context, frame);
636    }
637}
638
639void TileGrid::platformCALayerPaintContents(PlatformCALayer* platformCALayer, GraphicsContext& context, const FloatRect&)
640{
641#if PLATFORM(IOS)
642    if (pthread_main_np())
643        WebThreadLock();
644#endif
645
646    {
647        GraphicsContextStateSaver stateSaver(context);
648
649        FloatPoint3D layerOrigin = platformCALayer->position();
650        context.translate(-layerOrigin.x(), -layerOrigin.y());
651        context.scale(FloatSize(m_scale, m_scale));
652
653        PlatformCALayer::RepaintRectList dirtyRects = PlatformCALayer::collectRectsToPaint(context.platformContext(), platformCALayer);
654        PlatformCALayer::drawLayerContents(context.platformContext(), &m_controller.rootLayer(), dirtyRects);
655    }
656
657    int repaintCount = platformCALayerIncrementRepaintCount(platformCALayer);
658    if (m_controller.rootLayer().owner()->platformCALayerShowRepaintCounter(0))
659        PlatformCALayer::drawRepaintIndicator(context.platformContext(), platformCALayer, repaintCount, cachedCGColor(m_controller.tileDebugBorderColor(), ColorSpaceDeviceRGB));
660
661    if (m_controller.scrollingPerformanceLoggingEnabled()) {
662        FloatRect visiblePart(platformCALayer->position().x(), platformCALayer->position().y(), platformCALayer->bounds().size().width(), platformCALayer->bounds().size().height());
663        visiblePart.intersect(m_controller.visibleRect());
664
665        if (repaintCount == 1 && !visiblePart.isEmpty())
666            WTFLogAlways("SCROLLING: Filled visible fresh tile. Time: %f Unfilled Pixels: %u\n", WTF::monotonicallyIncreasingTime(), blankPixelCount());
667    }
668}
669
670float TileGrid::platformCALayerDeviceScaleFactor() const
671{
672    return m_controller.rootLayer().owner()->platformCALayerDeviceScaleFactor();
673}
674
675bool TileGrid::platformCALayerShowDebugBorders() const
676{
677    return m_controller.rootLayer().owner()->platformCALayerShowDebugBorders();
678}
679
680bool TileGrid::platformCALayerShowRepaintCounter(PlatformCALayer*) const
681{
682    return m_controller.rootLayer().owner()->platformCALayerShowRepaintCounter(0);
683}
684
685bool TileGrid::platformCALayerContentsOpaque() const
686{
687    return m_controller.tilesAreOpaque();
688}
689
690int TileGrid::platformCALayerIncrementRepaintCount(PlatformCALayer* platformCALayer)
691{
692    int repaintCount = 0;
693
694    if (m_tileRepaintCounts.contains(platformCALayer))
695        repaintCount = m_tileRepaintCounts.get(platformCALayer);
696
697    m_tileRepaintCounts.set(platformCALayer, ++repaintCount);
698
699    return repaintCount;
700}
701
702#if PLATFORM(IOS)
703void TileGrid::removeUnparentedTilesNow()
704{
705    while (!m_cohortList.isEmpty()) {
706        TileCohortInfo firstCohort = m_cohortList.takeFirst();
707        removeTilesInCohort(firstCohort.cohort);
708    }
709}
710#endif
711
712} // namespace WebCore
713