| /* |
| * Copyright (C) 2011-2014 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| |
| #if USE(CG) |
| |
| #include "TileGrid.h" |
| |
| #include "GraphicsContext.h" |
| #include "LayerPool.h" |
| #include "Logging.h" |
| #include "PlatformCALayer.h" |
| #include "TileController.h" |
| #include <wtf/MainThread.h> |
| #include <wtf/text/CString.h> |
| #include <wtf/text/TextStream.h> |
| |
| #if PLATFORM(IOS_FAMILY) |
| #include "TileControllerMemoryHandlerIOS.h" |
| #endif |
| |
| namespace WebCore { |
| |
| #if !LOG_DISABLED |
| |
| static String validationPolicyAsString(OptionSet<TileGrid::ValidationPolicyFlag> policy) |
| { |
| return makeString('[', |
| policy.contains(TileGrid::PruneSecondaryTiles) ? "prune secondary" : "", |
| policy.containsAll({ TileGrid::PruneSecondaryTiles, TileGrid::UnparentAllTiles }) ? ", " : "", |
| policy.contains(TileGrid::PruneSecondaryTiles) ? "unparent all" : "", |
| ']'); |
| } |
| |
| #endif |
| |
| TileGrid::TileGrid(TileController& controller) |
| : m_controller(controller) |
| , m_containerLayer(controller.rootLayer().createCompatibleLayer(PlatformCALayer::LayerTypeLayer, nullptr)) |
| , m_cohortRemovalTimer(*this, &TileGrid::cohortRemovalTimerFired) |
| , m_tileSize(kDefaultTileSize, kDefaultTileSize) |
| { |
| m_containerLayer.get().setName(TileController::tileGridContainerLayerName()); |
| m_containerLayer.get().setContentsScale(m_controller.deviceScaleFactor()); |
| } |
| |
| TileGrid::~TileGrid() |
| { |
| ASSERT(isMainThread()); |
| |
| for (auto& tile : m_tiles.values()) |
| tile.layer->setOwner(nullptr); |
| } |
| |
| void TileGrid::setIsZoomedOutTileGrid(bool isZoomedOutGrid) |
| { |
| if (isZoomedOutGrid) |
| m_containerLayer.get().setName(TileController::zoomedOutTileGridContainerLayerName()); |
| else |
| m_containerLayer.get().setName(TileController::tileGridContainerLayerName()); |
| } |
| |
| void TileGrid::setScale(float scale) |
| { |
| m_scale = scale; |
| |
| TransformationMatrix transform; |
| transform.scale(1 / m_scale); |
| m_containerLayer->setTransform(transform); |
| |
| m_controller.setNeedsRevalidateTiles(); |
| |
| m_containerLayer.get().setContentsScale(m_controller.deviceScaleFactor()); |
| |
| for (auto& tile : m_tiles.values()) |
| tile.layer->setContentsScale(m_controller.deviceScaleFactor()); |
| } |
| |
| void TileGrid::setNeedsDisplay() |
| { |
| for (auto& entry : m_tiles) { |
| TileInfo& tileInfo = entry.value; |
| IntRect tileRect = rectForTileIndex(entry.key); |
| |
| if (tileRect.intersects(m_primaryTileCoverageRect) && tileInfo.layer->superlayer()) |
| tileInfo.layer->setNeedsDisplay(); |
| else |
| tileInfo.hasStaleContent = true; |
| } |
| } |
| |
| void TileGrid::setNeedsDisplayInRect(const IntRect& rect) |
| { |
| if (m_tiles.isEmpty()) |
| return; |
| |
| FloatRect scaledRect(rect); |
| scaledRect.scale(m_scale); |
| IntRect repaintRectInTileCoords(enclosingIntRectPreservingEmptyRects(scaledRect)); |
| |
| IntSize tileSize = m_tileSize; |
| |
| // For small invalidations, lookup the covered tiles. |
| if (repaintRectInTileCoords.height() < 2 * tileSize.height() && repaintRectInTileCoords.width() < 2 * tileSize.width()) { |
| TileIndex topLeft; |
| TileIndex bottomRight; |
| if (getTileIndexRangeForRect(repaintRectInTileCoords, topLeft, bottomRight)) { |
| for (int y = topLeft.y(); y <= bottomRight.y(); ++y) { |
| for (int x = topLeft.x(); x <= bottomRight.x(); ++x) { |
| TileIndex tileIndex(x, y); |
| auto it = m_tiles.find(tileIndex); |
| if (it != m_tiles.end()) |
| setTileNeedsDisplayInRect(tileIndex, it->value, repaintRectInTileCoords, m_primaryTileCoverageRect); |
| } |
| } |
| } |
| return; |
| } |
| |
| for (auto& entry : m_tiles) |
| setTileNeedsDisplayInRect(entry.key, entry.value, repaintRectInTileCoords, m_primaryTileCoverageRect); |
| } |
| |
| void TileGrid::dropTilesInRect(const IntRect& rect) |
| { |
| if (m_tiles.isEmpty()) |
| return; |
| |
| FloatRect scaledRect(rect); |
| scaledRect.scale(m_scale); |
| IntRect dropRectInTileCoords(enclosingIntRectPreservingEmptyRects(scaledRect)); |
| |
| Vector<TileIndex> tilesToRemove; |
| |
| for (auto& index : m_tiles.keys()) { |
| if (rectForTileIndex(index).intersects(dropRectInTileCoords)) |
| tilesToRemove.append(index); |
| } |
| |
| removeTiles(tilesToRemove); |
| } |
| |
| void TileGrid::setTileNeedsDisplayInRect(const TileIndex& tileIndex, TileInfo& tileInfo, const IntRect& repaintRectInTileCoords, const IntRect& coverageRectInTileCoords) |
| { |
| PlatformCALayer* tileLayer = tileInfo.layer.get(); |
| |
| IntRect tileRect = rectForTileIndex(tileIndex); |
| FloatRect tileRepaintRect = tileRect; |
| tileRepaintRect.intersect(repaintRectInTileCoords); |
| if (tileRepaintRect.isEmpty()) |
| return; |
| |
| tileRepaintRect.moveBy(-tileRect.location()); |
| |
| // We could test for intersection with the visible rect. This would reduce painting yet more, |
| // but may make scrolling stale tiles into view more frequent. |
| if (tileRect.intersects(coverageRectInTileCoords) && tileLayer->superlayer()) { |
| tileLayer->setNeedsDisplayInRect(tileRepaintRect); |
| |
| if (m_controller.rootLayer().owner()->platformCALayerShowRepaintCounter(0)) { |
| FloatRect indicatorRect(0, 0, 52, 27); |
| tileLayer->setNeedsDisplayInRect(indicatorRect); |
| } |
| } else |
| tileInfo.hasStaleContent = true; |
| } |
| |
| void TileGrid::updateTileLayerProperties() |
| { |
| bool acceleratesDrawing = m_controller.acceleratesDrawing(); |
| bool deepColor = m_controller.wantsDeepColorBackingStore(); |
| bool subpixelAntialiasedText = m_controller.supportsSubpixelAntialiasedText(); |
| bool opaque = m_controller.tilesAreOpaque(); |
| Color tileDebugBorderColor = m_controller.tileDebugBorderColor(); |
| float tileDebugBorderWidth = m_controller.tileDebugBorderWidth(); |
| for (auto& tileInfo : m_tiles.values()) { |
| tileInfo.layer->setAcceleratesDrawing(acceleratesDrawing); |
| tileInfo.layer->setWantsDeepColorBackingStore(deepColor); |
| tileInfo.layer->setSupportsSubpixelAntialiasedText(subpixelAntialiasedText); |
| tileInfo.layer->setOpaque(opaque); |
| tileInfo.layer->setBorderColor(tileDebugBorderColor); |
| tileInfo.layer->setBorderWidth(tileDebugBorderWidth); |
| } |
| } |
| |
| bool TileGrid::tilesWouldChangeForCoverageRect(const FloatRect& coverageRect) const |
| { |
| if (coverageRect.isEmpty()) |
| return false; |
| |
| FloatRect scaledRect(coverageRect); |
| scaledRect.scale(m_scale); |
| IntRect currentCoverageRectInTileCoords(enclosingIntRectPreservingEmptyRects(scaledRect)); |
| |
| IntRect tileCoverageRect; |
| TileIndex topLeft; |
| TileIndex bottomRight; |
| if (getTileIndexRangeForRect(currentCoverageRectInTileCoords, topLeft, bottomRight)) { |
| tileCoverageRect = rectForTileIndex(topLeft); |
| tileCoverageRect.unite(rectForTileIndex(bottomRight)); |
| } |
| |
| return tileCoverageRect != m_primaryTileCoverageRect; |
| } |
| |
| bool TileGrid::prepopulateRect(const FloatRect& rect) |
| { |
| LOG_WITH_STREAM(Tiling, stream << "TileGrid " << this << " prepopulateRect: " << rect); |
| |
| IntRect enclosingCoverageRect = enclosingIntRectPreservingEmptyRects(rect); |
| if (m_primaryTileCoverageRect.contains(enclosingCoverageRect)) |
| return false; |
| |
| m_secondaryTileCoverageRects.append(enclosingCoverageRect); |
| return true; |
| } |
| |
| IntRect TileGrid::rectForTileIndex(const TileIndex& tileIndex) const |
| { |
| // FIXME: calculating the scaled size here should match with the rest of calculated sizes where we use the combination of |
| // enclosingIntRect, expandedIntSize (floor vs ceil). |
| // However enclosing this size could reveal gap on root layer's background. see RenderView::backgroundRect() |
| IntSize tileSize = m_tileSize; |
| IntRect rect(tileIndex.x() * tileSize.width(), tileIndex.y() * tileSize.height(), tileSize.width(), tileSize.height()); |
| IntRect scaledBounds(m_controller.bounds()); |
| scaledBounds.scale(m_scale); |
| rect.intersect(scaledBounds); |
| return rect; |
| } |
| |
| bool TileGrid::getTileIndexRangeForRect(const IntRect& rect, TileIndex& topLeft, TileIndex& bottomRight) const |
| { |
| IntRect clampedRect = m_controller.bounds(); |
| clampedRect.scale(m_scale); |
| clampedRect.intersect(rect); |
| |
| if (clampedRect.isEmpty()) |
| return false; |
| |
| auto tileSize = m_tileSize; |
| if (clampedRect.x() >= 0) |
| topLeft.setX(clampedRect.x() / tileSize.width()); |
| else |
| topLeft.setX(floorf((float)clampedRect.x() / tileSize.width())); |
| |
| if (clampedRect.y() >= 0) |
| topLeft.setY(clampedRect.y() / tileSize.height()); |
| else |
| topLeft.setY(floorf((float)clampedRect.y() / tileSize.height())); |
| |
| int bottomXRatio = ceil((float)clampedRect.maxX() / tileSize.width()); |
| bottomRight.setX(std::max(bottomXRatio - 1, 0)); |
| |
| int bottomYRatio = ceil((float)clampedRect.maxY() / tileSize.height()); |
| bottomRight.setY(std::max(bottomYRatio - 1, 0)); |
| |
| return true; |
| } |
| |
| unsigned TileGrid::blankPixelCount() const |
| { |
| PlatformLayerList tiles(m_tiles.size()); |
| for (auto& tile : m_tiles.values()) { |
| if (auto layer = tile.layer->platformLayer()) |
| tiles.append(layer); |
| } |
| return TileController::blankPixelCountForTiles(tiles, m_controller.visibleRect(), IntPoint(0, 0)); |
| } |
| |
| void TileGrid::removeTiles(const Vector<TileGrid::TileIndex>& toRemove) |
| { |
| for (size_t i = 0; i < toRemove.size(); ++i) { |
| TileInfo tileInfo = m_tiles.take(toRemove[i]); |
| tileInfo.layer->removeFromSuperlayer(); |
| m_tileRepaintCounts.removeAll(tileInfo.layer.get()); |
| tileInfo.layer->moveToLayerPool(); |
| } |
| } |
| |
| void TileGrid::removeAllTiles() |
| { |
| removeTiles(copyToVector(m_tiles.keys())); |
| } |
| |
| void TileGrid::removeAllSecondaryTiles() |
| { |
| Vector<TileIndex> tilesToRemove; |
| |
| for (auto& entry : m_tiles) { |
| const TileInfo& tileInfo = entry.value; |
| if (tileInfo.cohort == visibleTileCohort) |
| continue; |
| tilesToRemove.append(entry.key); |
| } |
| |
| removeTiles(tilesToRemove); |
| } |
| |
| void TileGrid::removeTilesInCohort(TileCohort cohort) |
| { |
| ASSERT(cohort != visibleTileCohort); |
| Vector<TileIndex> tilesToRemove; |
| |
| for (auto& entry : m_tiles) { |
| const TileInfo& tileInfo = entry.value; |
| if (tileInfo.cohort != cohort) |
| continue; |
| tilesToRemove.append(entry.key); |
| } |
| |
| removeTiles(tilesToRemove); |
| } |
| |
| void TileGrid::revalidateTiles(OptionSet<ValidationPolicyFlag> validationPolicy) |
| { |
| FloatRect coverageRect = m_controller.coverageRect(); |
| IntRect bounds = m_controller.bounds(); |
| |
| LOG_WITH_STREAM(Tiling, stream << "TileGrid " << this << " (controller " << &m_controller << ") revalidateTiles: bounds " << bounds << " coverageRect" << coverageRect << " validation: " << validationPolicyAsString(validationPolicy)); |
| |
| FloatRect scaledRect(coverageRect); |
| scaledRect.scale(m_scale); |
| IntRect coverageRectInTileCoords(enclosingIntRectPreservingEmptyRects(scaledRect)); |
| |
| TileCohort currCohort = nextTileCohort(); |
| unsigned tilesInCohort = 0; |
| |
| Seconds minimumRevalidationTimerDuration = Seconds::infinity(); |
| bool needsTileRevalidation = false; |
| |
| auto tileSize = m_controller.computeTileSize(); |
| if (tileSize != m_tileSize) { |
| removeAllTiles(); |
| m_tileSize = tileSize; |
| } |
| |
| // Move tiles newly outside the coverage rect into the cohort map. |
| for (auto& entry : m_tiles) { |
| TileInfo& tileInfo = entry.value; |
| TileIndex tileIndex = entry.key; |
| |
| PlatformCALayer* tileLayer = tileInfo.layer.get(); |
| IntRect tileRect = rectForTileIndex(tileIndex); |
| |
| if (tileRect.intersects(coverageRectInTileCoords)) { |
| tileInfo.cohort = visibleTileCohort; |
| if (tileInfo.hasStaleContent) { |
| // FIXME: store a dirty region per layer? |
| tileLayer->setNeedsDisplay(); |
| tileInfo.hasStaleContent = false; |
| } |
| } else { |
| // Add to the currentCohort if not already in one. |
| if (tileInfo.cohort == visibleTileCohort) { |
| tileInfo.cohort = currCohort; |
| ++tilesInCohort; |
| tileLayer->removeFromSuperlayer(); |
| } else if (m_controller.shouldAggressivelyRetainTiles() && tileLayer->superlayer()) { |
| // Aggressive tile retention means we'll never remove cohorts, but we need to make sure they're unparented. |
| // We can't immediately unparent cohorts comprised of secondary tiles that never touch the primary coverage rect, |
| // because that would defeat the usefulness of prepopulateRect(); instead, age prepopulated tiles out as if they were being removed. |
| for (auto& cohort : m_cohortList) { |
| if (cohort.cohort != tileInfo.cohort) |
| continue; |
| Seconds timeUntilCohortExpires = cohort.timeUntilExpiration(); |
| if (timeUntilCohortExpires > 0_s) { |
| minimumRevalidationTimerDuration = std::min(minimumRevalidationTimerDuration, timeUntilCohortExpires); |
| needsTileRevalidation = true; |
| } else |
| tileLayer->removeFromSuperlayer(); |
| break; |
| } |
| } |
| } |
| } |
| |
| if (needsTileRevalidation) |
| m_controller.scheduleTileRevalidation(minimumRevalidationTimerDuration); |
| |
| if (!m_controller.shouldAggressivelyRetainTiles()) { |
| if (m_controller.shouldTemporarilyRetainTileCohorts()) |
| scheduleCohortRemoval(); |
| else if (tilesInCohort) { |
| removeTilesInCohort(currCohort); |
| tilesInCohort = 0; |
| } |
| } |
| |
| if (tilesInCohort) |
| startedNewCohort(currCohort); |
| |
| if (validationPolicy & PruneSecondaryTiles) { |
| removeAllSecondaryTiles(); |
| m_cohortList.clear(); |
| } |
| |
| if (validationPolicy & UnparentAllTiles) { |
| for (auto& tile : m_tiles.values()) |
| tile.layer->removeFromSuperlayer(); |
| } |
| |
| auto boundsAtLastRevalidate = m_controller.boundsAtLastRevalidate(); |
| if (boundsAtLastRevalidate != bounds) { |
| // If there are margin tiles and the bounds have grown taller or wider, then the tiles that used to |
| // be bottom or right margin tiles need to be invalidated. |
| if (m_controller.hasMargins()) { |
| if (bounds.width() > boundsAtLastRevalidate.width() || bounds.height() > boundsAtLastRevalidate.height()) { |
| IntRect boundsWithoutMargin = m_controller.boundsWithoutMargin(); |
| IntRect oldBoundsWithoutMargin = m_controller.boundsAtLastRevalidateWithoutMargin(); |
| |
| if (bounds.height() > boundsAtLastRevalidate.height()) { |
| IntRect formerBottomMarginRect = IntRect(oldBoundsWithoutMargin.x(), oldBoundsWithoutMargin.height(), |
| oldBoundsWithoutMargin.width(), boundsWithoutMargin.height() - oldBoundsWithoutMargin.height()); |
| setNeedsDisplayInRect(formerBottomMarginRect); |
| } |
| |
| if (bounds.width() > boundsAtLastRevalidate.width()) { |
| IntRect formerRightMarginRect = IntRect(oldBoundsWithoutMargin.width(), oldBoundsWithoutMargin.y(), |
| boundsWithoutMargin.width() - oldBoundsWithoutMargin.width(), oldBoundsWithoutMargin.height()); |
| setNeedsDisplayInRect(formerRightMarginRect); |
| } |
| } |
| } |
| |
| FloatRect scaledBounds(bounds); |
| scaledBounds.scale(m_scale); |
| IntRect boundsInTileCoords(enclosingIntRectPreservingEmptyRects(scaledBounds)); |
| |
| TileIndex topLeftForBounds; |
| TileIndex bottomRightForBounds; |
| if (getTileIndexRangeForRect(boundsInTileCoords, topLeftForBounds, bottomRightForBounds)) { |
| Vector<TileIndex> tilesToRemove; |
| for (auto& index : m_tiles.keys()) { |
| if (index.y() < topLeftForBounds.y() || index.y() > bottomRightForBounds.y() || index.x() < topLeftForBounds.x() || index.x() > bottomRightForBounds.x()) |
| tilesToRemove.append(index); |
| } |
| removeTiles(tilesToRemove); |
| } |
| } |
| |
| // Ensure primary tile coverage tiles. |
| m_primaryTileCoverageRect = ensureTilesForRect(coverageRect, CoverageType::PrimaryTiles); |
| |
| // Ensure secondary tiles (requested via prepopulateRect). |
| if (!(validationPolicy & PruneSecondaryTiles)) { |
| for (auto& secondaryCoverageRect : m_secondaryTileCoverageRects) { |
| FloatRect secondaryRectInLayerCoordinates(secondaryCoverageRect); |
| secondaryRectInLayerCoordinates.scale(1 / m_scale); |
| ensureTilesForRect(secondaryRectInLayerCoordinates, CoverageType::SecondaryTiles); |
| } |
| m_secondaryTileCoverageRects.clear(); |
| } |
| |
| m_controller.didRevalidateTiles(); |
| } |
| |
| TileGrid::TileCohort TileGrid::nextTileCohort() const |
| { |
| if (!m_cohortList.isEmpty()) |
| return m_cohortList.last().cohort + 1; |
| |
| return 1; |
| } |
| |
| void TileGrid::startedNewCohort(TileCohort cohort) |
| { |
| m_cohortList.append(TileCohortInfo(cohort, MonotonicTime::now())); |
| #if PLATFORM(IOS_FAMILY) |
| if (!m_controller.isInWindow()) |
| tileControllerMemoryHandler().tileControllerGainedUnparentedTiles(&m_controller); |
| #endif |
| } |
| |
| TileGrid::TileCohort TileGrid::newestTileCohort() const |
| { |
| return m_cohortList.isEmpty() ? 0 : m_cohortList.last().cohort; |
| } |
| |
| TileGrid::TileCohort TileGrid::oldestTileCohort() const |
| { |
| return m_cohortList.isEmpty() ? 0 : m_cohortList.first().cohort; |
| } |
| |
| void TileGrid::scheduleCohortRemoval() |
| { |
| const Seconds cohortRemovalTimerSeconds { 1_s }; |
| |
| // Start the timer, or reschedule the timer from now if it's already active. |
| if (!m_cohortRemovalTimer.isActive()) |
| m_cohortRemovalTimer.startRepeating(cohortRemovalTimerSeconds); |
| } |
| |
| Seconds TileGrid::TileCohortInfo::timeUntilExpiration() |
| { |
| Seconds cohortLifeTimeSeconds = 2_s; |
| MonotonicTime timeThreshold = MonotonicTime::now() - cohortLifeTimeSeconds; |
| return creationTime - timeThreshold; |
| } |
| |
| void TileGrid::cohortRemovalTimerFired() |
| { |
| if (m_cohortList.isEmpty()) { |
| m_cohortRemovalTimer.stop(); |
| return; |
| } |
| |
| while (!m_cohortList.isEmpty() && m_cohortList.first().timeUntilExpiration() < 0_s) { |
| TileCohortInfo firstCohort = m_cohortList.takeFirst(); |
| removeTilesInCohort(firstCohort.cohort); |
| } |
| |
| m_controller.updateTileCoverageMap(); |
| } |
| |
| IntRect TileGrid::ensureTilesForRect(const FloatRect& rect, CoverageType newTileType) |
| { |
| if (!m_controller.isInWindow()) |
| return IntRect(); |
| |
| FloatRect scaledRect(rect); |
| scaledRect.scale(m_scale); |
| IntRect rectInTileCoords(enclosingIntRectPreservingEmptyRects(scaledRect)); |
| |
| TileIndex topLeft; |
| TileIndex bottomRight; |
| if (!getTileIndexRangeForRect(rectInTileCoords, topLeft, bottomRight)) |
| return IntRect(); |
| |
| TileCohort currCohort = nextTileCohort(); |
| unsigned tilesInCohort = 0; |
| |
| IntRect coverageRect; |
| |
| for (int y = topLeft.y(); y <= bottomRight.y(); ++y) { |
| for (int x = topLeft.x(); x <= bottomRight.x(); ++x) { |
| TileIndex tileIndex(x, y); |
| |
| IntRect tileRect = rectForTileIndex(tileIndex); |
| TileInfo& tileInfo = m_tiles.add(tileIndex, TileInfo()).iterator->value; |
| |
| coverageRect.unite(tileRect); |
| |
| bool shouldChangeTileLayerFrame = false; |
| |
| if (!tileInfo.layer) { |
| tileInfo.layer = m_controller.createTileLayer(tileRect, *this); |
| ASSERT(!m_tileRepaintCounts.contains(tileInfo.layer.get())); |
| } else { |
| // We already have a layer for this tile. Ensure that its size is correct. |
| FloatSize tileLayerSize(tileInfo.layer->bounds().size()); |
| shouldChangeTileLayerFrame = tileLayerSize != FloatSize(tileRect.size()); |
| |
| if (shouldChangeTileLayerFrame) { |
| tileInfo.layer->setBounds(FloatRect(FloatPoint(), tileRect.size())); |
| tileInfo.layer->setPosition(tileRect.location()); |
| tileInfo.layer->setNeedsDisplay(); |
| } |
| } |
| |
| if (newTileType == CoverageType::SecondaryTiles && !tileRect.intersects(m_primaryTileCoverageRect)) { |
| tileInfo.cohort = currCohort; |
| ++tilesInCohort; |
| } |
| |
| if (!tileInfo.layer->superlayer()) |
| m_containerLayer.get().appendSublayer(*tileInfo.layer); |
| } |
| } |
| |
| if (tilesInCohort) |
| startedNewCohort(currCohort); |
| |
| LOG_WITH_STREAM(Tiling, stream << "TileGrid " << this << " (bounds " << m_controller.bounds() << ") ensureTilesForRect: " << rect << " covered " << coverageRect); |
| |
| return coverageRect; |
| } |
| |
| IntRect TileGrid::extent() const |
| { |
| TileIndex topLeft; |
| TileIndex bottomRight; |
| if (getTileIndexRangeForRect(m_primaryTileCoverageRect, topLeft, bottomRight)) { |
| // Return index of top, left tile and the number of tiles across and down. |
| return IntRect(topLeft.x(), topLeft.y(), bottomRight.x() - topLeft.x() + 1, bottomRight.y() - topLeft.y() + 1); |
| } |
| |
| return IntRect(); |
| } |
| |
| double TileGrid::retainedTileBackingStoreMemory() const |
| { |
| double totalBytes = 0; |
| for (auto& tileInfo : m_tiles.values()) { |
| if (tileInfo.layer->superlayer()) { |
| FloatRect bounds = tileInfo.layer->bounds(); |
| double contentsScale = tileInfo.layer->contentsScale(); |
| totalBytes += 4 * bounds.width() * contentsScale * bounds.height() * contentsScale; |
| } |
| } |
| return totalBytes; |
| } |
| |
| // Return the rect in layer coords, not tile coords. |
| IntRect TileGrid::tileCoverageRect() const |
| { |
| IntRect coverageRectInLayerCoords(m_primaryTileCoverageRect); |
| coverageRectInLayerCoords.scale(1 / m_scale); |
| return coverageRectInLayerCoords; |
| } |
| |
| void TileGrid::drawTileMapContents(CGContextRef context, CGRect layerBounds) const |
| { |
| CGContextSetRGBFillColor(context, 0.3, 0.3, 0.3, 1); |
| CGContextFillRect(context, layerBounds); |
| |
| CGFloat scaleFactor = layerBounds.size.width / m_controller.bounds().width(); |
| |
| CGFloat contextScale = scaleFactor / m_scale; |
| CGContextScaleCTM(context, contextScale, contextScale); |
| |
| for (auto& tileInfo : m_tiles.values()) { |
| PlatformCALayer* tileLayer = tileInfo.layer.get(); |
| |
| CGFloat red = 1; |
| CGFloat green = 1; |
| CGFloat blue = 1; |
| CGFloat alpha = 1; |
| if (tileInfo.hasStaleContent) { |
| red = 0.25; |
| green = 0.125; |
| blue = 0; |
| } else if (m_controller.shouldAggressivelyRetainTiles() && tileInfo.cohort != visibleTileCohort) { |
| red = 0.8; |
| green = 0.8; |
| blue = 0.8; |
| } |
| |
| TileCohort newestCohort = newestTileCohort(); |
| TileCohort oldestCohort = oldestTileCohort(); |
| |
| if (!m_controller.shouldAggressivelyRetainTiles() && tileInfo.cohort != visibleTileCohort && newestCohort > oldestCohort) |
| alpha = 1 - (static_cast<float>((newestCohort - tileInfo.cohort)) / (newestCohort - oldestCohort)); |
| |
| CGContextSetRGBFillColor(context, red, green, blue, alpha); |
| |
| if (tileLayer->superlayer()) { |
| CGContextSetLineWidth(context, 0.5 / contextScale); |
| CGContextSetRGBStrokeColor(context, 0, 0, 0, 1); |
| } else { |
| CGContextSetLineWidth(context, 1 / contextScale); |
| CGContextSetRGBStrokeColor(context, 0.2, 0.1, 0.9, 1); |
| } |
| |
| CGRect frame = CGRectMake(tileLayer->position().x(), tileLayer->position().y(), tileLayer->bounds().size().width(), tileLayer->bounds().size().height()); |
| CGContextFillRect(context, frame); |
| CGContextStrokeRect(context, frame); |
| |
| CGContextSetRGBFillColor(context, 0, 0, 0, 0.5); |
| |
| CGContextSaveGState(context); |
| |
| auto repaintCount = m_tileRepaintCounts.count(tileLayer); |
| char repaintCountCharacters[lengthOfIntegerAsString(std::numeric_limits<decltype(repaintCount)>::max())]; |
| writeIntegerToBuffer(repaintCount, repaintCountCharacters); |
| tileLayer->drawTextAtPoint(context, frame.origin.x + 64, frame.origin.y + 192, CGSizeMake(3, -3), 58, |
| repaintCountCharacters, lengthOfIntegerAsString(repaintCount)); |
| |
| CGContextRestoreGState(context); |
| } |
| } |
| |
| void TileGrid::platformCALayerPaintContents(PlatformCALayer* platformCALayer, GraphicsContext& context, const FloatRect&, GraphicsLayerPaintBehavior layerPaintBehavior) |
| { |
| #if PLATFORM(IOS_FAMILY) |
| if (pthread_main_np()) |
| WebThreadLock(); |
| #endif |
| |
| if (!platformCALayerRepaintCount(platformCALayer)) |
| layerPaintBehavior |= GraphicsLayerPaintFirstTilePaint; |
| |
| { |
| GraphicsContextStateSaver stateSaver(context); |
| |
| FloatPoint3D layerOrigin = platformCALayer->position(); |
| context.translate(-layerOrigin.x(), -layerOrigin.y()); |
| context.scale(m_scale); |
| |
| PlatformCALayer::RepaintRectList dirtyRects = PlatformCALayer::collectRectsToPaint(context, platformCALayer); |
| PlatformCALayer::drawLayerContents(context, &m_controller.rootLayer(), dirtyRects, layerPaintBehavior); |
| } |
| |
| int repaintCount = platformCALayerIncrementRepaintCount(platformCALayer); |
| if (m_controller.rootLayer().owner()->platformCALayerShowRepaintCounter(0)) |
| PlatformCALayer::drawRepaintIndicator(context, platformCALayer, repaintCount, m_controller.tileDebugBorderColor()); |
| |
| if (m_controller.scrollingPerformanceTestingEnabled()) { |
| FloatRect visiblePart(platformCALayer->position().x(), platformCALayer->position().y(), platformCALayer->bounds().size().width(), platformCALayer->bounds().size().height()); |
| visiblePart.intersect(m_controller.visibleRect()); |
| |
| if (repaintCount == 1 && !visiblePart.isEmpty()) |
| m_controller.logFilledVisibleFreshTile(blankPixelCount()); |
| } |
| } |
| |
| float TileGrid::platformCALayerDeviceScaleFactor() const |
| { |
| if (auto* layerOwner = m_controller.rootLayer().owner()) |
| return layerOwner->platformCALayerDeviceScaleFactor(); |
| return 1.0f; |
| } |
| |
| bool TileGrid::platformCALayerShowDebugBorders() const |
| { |
| if (auto* layerOwner = m_controller.rootLayer().owner()) |
| return layerOwner->platformCALayerShowDebugBorders(); |
| return false; |
| } |
| |
| bool TileGrid::platformCALayerShowRepaintCounter(PlatformCALayer*) const |
| { |
| if (auto* layerOwner = m_controller.rootLayer().owner()) |
| return layerOwner->platformCALayerShowRepaintCounter(nullptr); |
| return false; |
| } |
| |
| bool TileGrid::isUsingDisplayListDrawing(PlatformCALayer*) const |
| { |
| if (auto* layerOwner = m_controller.rootLayer().owner()) |
| return layerOwner->isUsingDisplayListDrawing(nullptr); |
| return false; |
| } |
| |
| bool TileGrid::platformCALayerContentsOpaque() const |
| { |
| return m_controller.tilesAreOpaque(); |
| } |
| |
| int TileGrid::platformCALayerRepaintCount(PlatformCALayer* platformCALayer) const |
| { |
| return m_tileRepaintCounts.count(platformCALayer); |
| } |
| |
| int TileGrid::platformCALayerIncrementRepaintCount(PlatformCALayer* platformCALayer) |
| { |
| return m_tileRepaintCounts.add(platformCALayer).iterator->value; |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| void TileGrid::removeUnparentedTilesNow() |
| { |
| while (!m_cohortList.isEmpty()) |
| removeTilesInCohort(m_cohortList.takeFirst().cohort); |
| } |
| #endif |
| |
| } // namespace WebCore |
| |
| #endif |