1/* 2 Copyright (C) 2010-2012 Nokia Corporation and/or its subsidiary(-ies) 3 4 This library is free software; you can redistribute it and/or 5 modify it under the terms of the GNU Library General Public 6 License as published by the Free Software Foundation; either 7 version 2 of the License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Library General Public License for more details. 13 14 You should have received a copy of the GNU Library General Public License 15 along with this library; see the file COPYING.LIB. If not, write to 16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 Boston, MA 02110-1301, USA. 18 */ 19 20#include "config.h" 21#include "TiledBackingStore.h" 22 23#if USE(TILED_BACKING_STORE) 24 25#include "GraphicsContext.h" 26#include "TiledBackingStoreClient.h" 27 28namespace WebCore { 29 30static const int defaultTileDimension = 512; 31 32static IntPoint innerBottomRight(const IntRect& rect) 33{ 34 // Actually, the rect does not contain rect.maxX(). Refer to IntRect::contain. 35 return IntPoint(rect.maxX() - 1, rect.maxY() - 1); 36} 37 38TiledBackingStore::TiledBackingStore(TiledBackingStoreClient* client, PassOwnPtr<TiledBackingStoreBackend> backend) 39 : m_client(client) 40 , m_backend(backend) 41 , m_tileBufferUpdateTimer(this, &TiledBackingStore::tileBufferUpdateTimerFired) 42 , m_backingStoreUpdateTimer(this, &TiledBackingStore::backingStoreUpdateTimerFired) 43 , m_tileSize(defaultTileDimension, defaultTileDimension) 44 , m_coverAreaMultiplier(2.0f) 45 , m_contentsScale(1.f) 46 , m_pendingScale(0) 47 , m_commitTileUpdatesOnIdleEventLoop(false) 48 , m_contentsFrozen(false) 49 , m_supportsAlpha(false) 50 , m_pendingTileCreation(false) 51{ 52} 53 54TiledBackingStore::~TiledBackingStore() 55{ 56} 57 58void TiledBackingStore::setTileSize(const IntSize& size) 59{ 60 m_tileSize = size; 61 m_tiles.clear(); 62 startBackingStoreUpdateTimer(); 63} 64 65void TiledBackingStore::setTrajectoryVector(const FloatPoint& trajectoryVector) 66{ 67 m_pendingTrajectoryVector = trajectoryVector; 68 m_pendingTrajectoryVector.normalize(); 69} 70 71void TiledBackingStore::coverWithTilesIfNeeded() 72{ 73 IntRect visibleRect = this->visibleRect(); 74 IntRect rect = mapFromContents(m_client->tiledBackingStoreContentsRect()); 75 76 bool didChange = m_trajectoryVector != m_pendingTrajectoryVector || m_visibleRect != visibleRect || m_rect != rect; 77 if (didChange || m_pendingTileCreation) 78 createTiles(); 79} 80 81void TiledBackingStore::invalidate(const IntRect& contentsDirtyRect) 82{ 83 IntRect dirtyRect(mapFromContents(contentsDirtyRect)); 84 IntRect keepRectFitToTileSize = tileRectForCoordinate(tileCoordinateForPoint(m_keepRect.location())); 85 keepRectFitToTileSize.unite(tileRectForCoordinate(tileCoordinateForPoint(innerBottomRight(m_keepRect)))); 86 87 // Only iterate on the part of the rect that we know we might have tiles. 88 IntRect coveredDirtyRect = intersection(dirtyRect, keepRectFitToTileSize); 89 Tile::Coordinate topLeft = tileCoordinateForPoint(coveredDirtyRect.location()); 90 Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(coveredDirtyRect)); 91 92 for (int yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) { 93 for (int xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) { 94 RefPtr<Tile> currentTile = tileAt(Tile::Coordinate(xCoordinate, yCoordinate)); 95 if (!currentTile) 96 continue; 97 // Pass the full rect to each tile as coveredDirtyRect might not 98 // contain them completely and we don't want partial tile redraws. 99 currentTile->invalidate(dirtyRect); 100 } 101 } 102 103 startTileBufferUpdateTimer(); 104} 105 106void TiledBackingStore::updateTileBuffers() 107{ 108 if (m_contentsFrozen) 109 return; 110 111 m_client->tiledBackingStorePaintBegin(); 112 113 Vector<IntRect> paintedArea; 114 Vector<RefPtr<Tile> > dirtyTiles; 115 TileMap::iterator end = m_tiles.end(); 116 for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) { 117 if (!it->value->isDirty()) 118 continue; 119 dirtyTiles.append(it->value); 120 } 121 122 if (dirtyTiles.isEmpty()) { 123 m_client->tiledBackingStorePaintEnd(paintedArea); 124 return; 125 } 126 127 // FIXME: In single threaded case, tile back buffers could be updated asynchronously 128 // one by one and then swapped to front in one go. This would minimize the time spent 129 // blocking on tile updates. 130 unsigned size = dirtyTiles.size(); 131 for (unsigned n = 0; n < size; ++n) { 132 Vector<IntRect> paintedRects = dirtyTiles[n]->updateBackBuffer(); 133 paintedArea.appendVector(paintedRects); 134 dirtyTiles[n]->swapBackBufferToFront(); 135 } 136 137 m_client->tiledBackingStorePaintEnd(paintedArea); 138} 139 140void TiledBackingStore::paint(GraphicsContext* context, const IntRect& rect) 141{ 142 context->save(); 143 144 // Assumes the backing store is painted with the scale transform applied. 145 // Since tile content is already scaled, first revert the scaling from the painter. 146 context->scale(FloatSize(1.f / m_contentsScale, 1.f / m_contentsScale)); 147 148 IntRect dirtyRect = mapFromContents(rect); 149 150 Tile::Coordinate topLeft = tileCoordinateForPoint(dirtyRect.location()); 151 Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(dirtyRect)); 152 153 for (int yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) { 154 for (int xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) { 155 Tile::Coordinate currentCoordinate(xCoordinate, yCoordinate); 156 RefPtr<Tile> currentTile = tileAt(currentCoordinate); 157 if (currentTile && currentTile->isReadyToPaint()) 158 currentTile->paint(context, dirtyRect); 159 else { 160 IntRect tileRect = tileRectForCoordinate(currentCoordinate); 161 IntRect target = intersection(tileRect, dirtyRect); 162 if (target.isEmpty()) 163 continue; 164 m_backend->paintCheckerPattern(context, FloatRect(target)); 165 } 166 } 167 } 168 context->restore(); 169} 170 171IntRect TiledBackingStore::visibleRect() const 172{ 173 return mapFromContents(m_client->tiledBackingStoreVisibleRect()); 174} 175 176void TiledBackingStore::setContentsScale(float scale) 177{ 178 if (m_pendingScale == m_contentsScale) { 179 m_pendingScale = 0; 180 return; 181 } 182 m_pendingScale = scale; 183 if (m_contentsFrozen) 184 return; 185 commitScaleChange(); 186} 187 188void TiledBackingStore::commitScaleChange() 189{ 190 m_contentsScale = m_pendingScale; 191 m_pendingScale = 0; 192 m_tiles.clear(); 193 coverWithTilesIfNeeded(); 194} 195 196double TiledBackingStore::tileDistance(const IntRect& viewport, const Tile::Coordinate& tileCoordinate) const 197{ 198 if (viewport.intersects(tileRectForCoordinate(tileCoordinate))) 199 return 0; 200 201 IntPoint viewCenter = viewport.location() + IntSize(viewport.width() / 2, viewport.height() / 2); 202 Tile::Coordinate centerCoordinate = tileCoordinateForPoint(viewCenter); 203 204 return std::max(abs(centerCoordinate.y() - tileCoordinate.y()), abs(centerCoordinate.x() - tileCoordinate.x())); 205} 206 207// Returns a ratio between 0.0f and 1.0f of the surface of contentsRect covered by rendered tiles. 208float TiledBackingStore::coverageRatio(const WebCore::IntRect& contentsRect) const 209{ 210 IntRect dirtyRect = mapFromContents(contentsRect); 211 float rectArea = dirtyRect.width() * dirtyRect.height(); 212 float coverArea = 0.0f; 213 214 Tile::Coordinate topLeft = tileCoordinateForPoint(dirtyRect.location()); 215 Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(dirtyRect)); 216 217 for (int yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) { 218 for (int xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) { 219 Tile::Coordinate currentCoordinate(xCoordinate, yCoordinate); 220 RefPtr<Tile> currentTile = tileAt(currentCoordinate); 221 if (currentTile && currentTile->isReadyToPaint()) { 222 IntRect coverRect = intersection(dirtyRect, currentTile->rect()); 223 coverArea += coverRect.width() * coverRect.height(); 224 } 225 } 226 } 227 return coverArea / rectArea; 228} 229 230bool TiledBackingStore::visibleAreaIsCovered() const 231{ 232 IntRect boundedVisibleContentsRect = intersection(m_client->tiledBackingStoreVisibleRect(), m_client->tiledBackingStoreContentsRect()); 233 return coverageRatio(boundedVisibleContentsRect) == 1.0f; 234} 235 236void TiledBackingStore::createTiles() 237{ 238 // Guard here as as these can change before the timer fires. 239 if (isBackingStoreUpdatesSuspended()) 240 return; 241 242 // Update our backing store geometry. 243 const IntRect previousRect = m_rect; 244 m_rect = mapFromContents(m_client->tiledBackingStoreContentsRect()); 245 m_trajectoryVector = m_pendingTrajectoryVector; 246 m_visibleRect = visibleRect(); 247 248 if (m_rect.isEmpty()) { 249 setCoverRect(IntRect()); 250 setKeepRect(IntRect()); 251 return; 252 } 253 254 /* We must compute cover and keep rects using the visibleRect, instead of the rect intersecting the visibleRect with m_rect, 255 * because TBS can be used as a backing store of GraphicsLayer and the visible rect usually does not intersect with m_rect. 256 * In the below case, the intersecting rect is an empty. 257 * 258 * +---------------+ 259 * | | 260 * | m_rect | 261 * | +-------|-----------------------+ 262 * | | HERE | cover or keep | 263 * +---------------+ rect | 264 * | +---------+ | 265 * | | visible | | 266 * | | rect | | 267 * | +---------+ | 268 * | | 269 * | | 270 * +-------------------------------+ 271 * 272 * We must create or keep the tiles in the HERE region. 273 */ 274 275 IntRect coverRect; 276 IntRect keepRect; 277 computeCoverAndKeepRect(m_visibleRect, coverRect, keepRect); 278 279 setCoverRect(coverRect); 280 setKeepRect(keepRect); 281 282 if (coverRect.isEmpty()) 283 return; 284 285 // Resize tiles at the edge in case the contents size has changed, but only do so 286 // after having dropped tiles outside the keep rect. 287 bool didResizeTiles = false; 288 if (previousRect != m_rect) 289 didResizeTiles = resizeEdgeTiles(); 290 291 // Search for the tile position closest to the viewport center that does not yet contain a tile. 292 // Which position is considered the closest depends on the tileDistance function. 293 double shortestDistance = std::numeric_limits<double>::infinity(); 294 Vector<Tile::Coordinate> tilesToCreate; 295 unsigned requiredTileCount = 0; 296 297 // Cover areas (in tiles) with minimum distance from the visible rect. If the visible rect is 298 // not covered already it will be covered first in one go, due to the distance being 0 for tiles 299 // inside the visible rect. 300 Tile::Coordinate topLeft = tileCoordinateForPoint(coverRect.location()); 301 Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(coverRect)); 302 for (int yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) { 303 for (int xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) { 304 Tile::Coordinate currentCoordinate(xCoordinate, yCoordinate); 305 if (tileAt(currentCoordinate)) 306 continue; 307 ++requiredTileCount; 308 double distance = tileDistance(m_visibleRect, currentCoordinate); 309 if (distance > shortestDistance) 310 continue; 311 if (distance < shortestDistance) { 312 tilesToCreate.clear(); 313 shortestDistance = distance; 314 } 315 tilesToCreate.append(currentCoordinate); 316 } 317 } 318 319 // Now construct the tile(s) within the shortest distance. 320 unsigned tilesToCreateCount = tilesToCreate.size(); 321 for (unsigned n = 0; n < tilesToCreateCount; ++n) { 322 Tile::Coordinate coordinate = tilesToCreate[n]; 323 setTile(coordinate, m_backend->createTile(this, coordinate)); 324 } 325 requiredTileCount -= tilesToCreateCount; 326 327 // Paint the content of the newly created tiles or resized tiles. 328 if (tilesToCreateCount || didResizeTiles) 329 updateTileBuffers(); 330 331 // Re-call createTiles on a timer to cover the visible area with the newest shortest distance. 332 m_pendingTileCreation = requiredTileCount; 333 if (m_pendingTileCreation) { 334 if (!m_commitTileUpdatesOnIdleEventLoop) { 335 m_client->tiledBackingStoreHasPendingTileCreation(); 336 return; 337 } 338 339 static const double tileCreationDelay = 0.01; 340 startBackingStoreUpdateTimer(tileCreationDelay); 341 } 342} 343 344void TiledBackingStore::adjustForContentsRect(IntRect& rect) const 345{ 346 IntRect bounds = m_rect; 347 IntSize candidateSize = rect.size(); 348 349 rect.intersect(bounds); 350 351 if (rect.size() == candidateSize) 352 return; 353 354 /* 355 * In the following case, there is no intersection of the contents rect and the cover rect. 356 * Thus the latter should not be inflated. 357 * 358 * +---------------+ 359 * | m_rect | 360 * +---------------+ 361 * 362 * +-------------------------------+ 363 * | cover rect | 364 * | +---------+ | 365 * | | visible | | 366 * | | rect | | 367 * | +---------+ | 368 * +-------------------------------+ 369 */ 370 if (rect.isEmpty()) 371 return; 372 373 // Try to create a cover rect of the same size as the candidate, but within content bounds. 374 int pixelsCovered = candidateSize.width() * candidateSize.height(); 375 376 if (rect.width() < candidateSize.width()) 377 rect.inflateY(((pixelsCovered / rect.width()) - rect.height()) / 2); 378 if (rect.height() < candidateSize.height()) 379 rect.inflateX(((pixelsCovered / rect.height()) - rect.width()) / 2); 380 381 rect.intersect(bounds); 382} 383 384void TiledBackingStore::computeCoverAndKeepRect(const IntRect& visibleRect, IntRect& coverRect, IntRect& keepRect) const 385{ 386 coverRect = visibleRect; 387 keepRect = visibleRect; 388 389 // If we cover more that the actual viewport we can be smart about which tiles we choose to render. 390 if (m_coverAreaMultiplier > 1) { 391 // The initial cover area covers equally in each direction, according to the coverAreaMultiplier. 392 coverRect.inflateX(visibleRect.width() * (m_coverAreaMultiplier - 1) / 2); 393 coverRect.inflateY(visibleRect.height() * (m_coverAreaMultiplier - 1) / 2); 394 keepRect = coverRect; 395 396 if (m_trajectoryVector != FloatPoint::zero()) { 397 // A null trajectory vector (no motion) means that tiles for the coverArea will be created. 398 // A non-null trajectory vector will shrink the covered rect to visibleRect plus its expansion from its 399 // center toward the cover area edges in the direction of the given vector. 400 401 // E.g. if visibleRect == (10,10)5x5 and coverAreaMultiplier == 3.0: 402 // a (0,0) trajectory vector will create tiles intersecting (5,5)15x15, 403 // a (1,0) trajectory vector will create tiles intersecting (10,10)10x5, 404 // and a (1,1) trajectory vector will create tiles intersecting (10,10)10x10. 405 406 // Multiply the vector by the distance to the edge of the cover area. 407 float trajectoryVectorMultiplier = (m_coverAreaMultiplier - 1) / 2; 408 409 // Unite the visible rect with a "ghost" of the visible rect moved in the direction of the trajectory vector. 410 coverRect = visibleRect; 411 coverRect.move(coverRect.width() * m_trajectoryVector.x() * trajectoryVectorMultiplier, 412 coverRect.height() * m_trajectoryVector.y() * trajectoryVectorMultiplier); 413 414 coverRect.unite(visibleRect); 415 } 416 ASSERT(keepRect.contains(coverRect)); 417 } 418 419 adjustForContentsRect(coverRect); 420 421 // The keep rect is an inflated version of the cover rect, inflated in tile dimensions. 422 keepRect.unite(coverRect); 423 keepRect.inflateX(m_tileSize.width() / 2); 424 keepRect.inflateY(m_tileSize.height() / 2); 425 keepRect.intersect(m_rect); 426 427 ASSERT(coverRect.isEmpty() || keepRect.contains(coverRect)); 428} 429 430bool TiledBackingStore::isBackingStoreUpdatesSuspended() const 431{ 432 return m_contentsFrozen; 433} 434 435bool TiledBackingStore::isTileBufferUpdatesSuspended() const 436{ 437 return m_contentsFrozen; 438} 439 440bool TiledBackingStore::resizeEdgeTiles() 441{ 442 bool wasResized = false; 443 Vector<Tile::Coordinate> tilesToRemove; 444 TileMap::iterator end = m_tiles.end(); 445 for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) { 446 Tile::Coordinate tileCoordinate = it->value->coordinate(); 447 IntRect tileRect = it->value->rect(); 448 IntRect expectedTileRect = tileRectForCoordinate(tileCoordinate); 449 if (expectedTileRect.isEmpty()) 450 tilesToRemove.append(tileCoordinate); 451 else if (expectedTileRect != tileRect) { 452 it->value->resize(expectedTileRect.size()); 453 wasResized = true; 454 } 455 } 456 unsigned removeCount = tilesToRemove.size(); 457 for (unsigned n = 0; n < removeCount; ++n) 458 removeTile(tilesToRemove[n]); 459 return wasResized; 460} 461 462void TiledBackingStore::setKeepRect(const IntRect& keepRect) 463{ 464 // Drop tiles outside the new keepRect. 465 466 FloatRect keepRectF = keepRect; 467 468 Vector<Tile::Coordinate> toRemove; 469 TileMap::iterator end = m_tiles.end(); 470 for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) { 471 Tile::Coordinate coordinate = it->value->coordinate(); 472 FloatRect tileRect = it->value->rect(); 473 if (!tileRect.intersects(keepRectF)) 474 toRemove.append(coordinate); 475 } 476 unsigned removeCount = toRemove.size(); 477 for (unsigned n = 0; n < removeCount; ++n) 478 removeTile(toRemove[n]); 479 480 m_keepRect = keepRect; 481} 482 483void TiledBackingStore::removeAllNonVisibleTiles() 484{ 485 IntRect boundedVisibleRect = mapFromContents(intersection(m_client->tiledBackingStoreVisibleRect(), m_client->tiledBackingStoreContentsRect())); 486 setKeepRect(boundedVisibleRect); 487} 488 489PassRefPtr<Tile> TiledBackingStore::tileAt(const Tile::Coordinate& coordinate) const 490{ 491 return m_tiles.get(coordinate); 492} 493 494void TiledBackingStore::setTile(const Tile::Coordinate& coordinate, PassRefPtr<Tile> tile) 495{ 496 m_tiles.set(coordinate, tile); 497} 498 499void TiledBackingStore::removeTile(const Tile::Coordinate& coordinate) 500{ 501 m_tiles.remove(coordinate); 502} 503 504IntRect TiledBackingStore::mapToContents(const IntRect& rect) const 505{ 506 return enclosingIntRect(FloatRect(rect.x() / m_contentsScale, 507 rect.y() / m_contentsScale, 508 rect.width() / m_contentsScale, 509 rect.height() / m_contentsScale)); 510} 511 512IntRect TiledBackingStore::mapFromContents(const IntRect& rect) const 513{ 514 return enclosingIntRect(FloatRect(rect.x() * m_contentsScale, 515 rect.y() * m_contentsScale, 516 rect.width() * m_contentsScale, 517 rect.height() * m_contentsScale)); 518} 519 520IntRect TiledBackingStore::tileRectForCoordinate(const Tile::Coordinate& coordinate) const 521{ 522 IntRect rect(coordinate.x() * m_tileSize.width(), 523 coordinate.y() * m_tileSize.height(), 524 m_tileSize.width(), 525 m_tileSize.height()); 526 527 rect.intersect(m_rect); 528 return rect; 529} 530 531Tile::Coordinate TiledBackingStore::tileCoordinateForPoint(const IntPoint& point) const 532{ 533 int x = point.x() / m_tileSize.width(); 534 int y = point.y() / m_tileSize.height(); 535 return Tile::Coordinate(std::max(x, 0), std::max(y, 0)); 536} 537 538void TiledBackingStore::startTileBufferUpdateTimer() 539{ 540 if (!m_commitTileUpdatesOnIdleEventLoop) 541 return; 542 543 if (m_tileBufferUpdateTimer.isActive() || isTileBufferUpdatesSuspended()) 544 return; 545 m_tileBufferUpdateTimer.startOneShot(0); 546} 547 548void TiledBackingStore::tileBufferUpdateTimerFired(Timer<TiledBackingStore>*) 549{ 550 ASSERT(m_commitTileUpdatesOnIdleEventLoop); 551 updateTileBuffers(); 552} 553 554void TiledBackingStore::startBackingStoreUpdateTimer(double interval) 555{ 556 if (!m_commitTileUpdatesOnIdleEventLoop) 557 return; 558 559 if (m_backingStoreUpdateTimer.isActive() || isBackingStoreUpdatesSuspended()) 560 return; 561 m_backingStoreUpdateTimer.startOneShot(interval); 562} 563 564void TiledBackingStore::backingStoreUpdateTimerFired(Timer<TiledBackingStore>*) 565{ 566 ASSERT(m_commitTileUpdatesOnIdleEventLoop); 567 createTiles(); 568} 569 570void TiledBackingStore::setContentsFrozen(bool freeze) 571{ 572 if (m_contentsFrozen == freeze) 573 return; 574 575 m_contentsFrozen = freeze; 576 577 // Restart the timers. There might be pending invalidations that 578 // were not painted or created because tiles are not created or 579 // painted when in frozen state. 580 if (m_contentsFrozen) 581 return; 582 if (m_pendingScale) 583 commitScaleChange(); 584 else { 585 startBackingStoreUpdateTimer(); 586 startTileBufferUpdateTimer(); 587 } 588} 589 590void TiledBackingStore::setSupportsAlpha(bool a) 591{ 592 if (a == m_supportsAlpha) 593 return; 594 m_supportsAlpha = a; 595 invalidate(m_rect); 596} 597 598} 599 600#endif 601