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