| 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(COORDINATED_GRAPHICS) |
| 24 | #include "GraphicsContext.h" |
| 25 | #include "TiledBackingStoreClient.h" |
| 26 | #include <wtf/CheckedArithmetic.h> |
| 27 | #include <wtf/MemoryPressureHandler.h> |
| 28 | |
| 29 | namespace WebCore { |
| 30 | |
| 31 | static const int defaultTileDimension = 512; |
| 32 | |
| 33 | static IntPoint innerBottomRight(const IntRect& rect) |
| 34 | { |
| 35 | // Actually, the rect does not contain rect.maxX(). Refer to IntRect::contain. |
| 36 | return IntPoint(rect.maxX() - 1, rect.maxY() - 1); |
| 37 | } |
| 38 | |
| 39 | TiledBackingStore::TiledBackingStore(TiledBackingStoreClient& client, float contentsScale) |
| 40 | : m_client(client) |
| 41 | , m_tileSize(defaultTileDimension, defaultTileDimension) |
| 42 | , m_coverAreaMultiplier(2.0f) |
| 43 | , m_contentsScale(contentsScale) |
| 44 | , m_pendingTileCreation(false) |
| 45 | { |
| 46 | } |
| 47 | |
| 48 | TiledBackingStore::~TiledBackingStore() = default; |
| 49 | |
| 50 | void TiledBackingStore::setTrajectoryVector(const FloatPoint& trajectoryVector) |
| 51 | { |
| 52 | m_pendingTrajectoryVector = trajectoryVector; |
| 53 | m_pendingTrajectoryVector.normalize(); |
| 54 | } |
| 55 | |
| 56 | void TiledBackingStore::createTilesIfNeeded(const IntRect& unscaledVisibleRect, const IntRect& contentsRect) |
| 57 | { |
| 58 | IntRect scaledContentsRect = mapFromContents(contentsRect); |
| 59 | IntRect visibleRect = mapFromContents(unscaledVisibleRect); |
| 60 | float coverAreaMultiplier = MemoryPressureHandler::singleton().isUnderMemoryPressure() ? 1.0f : 2.0f; |
| 61 | |
| 62 | bool didChange = m_trajectoryVector != m_pendingTrajectoryVector || m_visibleRect != visibleRect || m_rect != scaledContentsRect || m_coverAreaMultiplier != coverAreaMultiplier; |
| 63 | if (didChange || m_pendingTileCreation) |
| 64 | createTiles(visibleRect, scaledContentsRect, coverAreaMultiplier); |
| 65 | } |
| 66 | |
| 67 | void TiledBackingStore::invalidate(const IntRect& contentsDirtyRect) |
| 68 | { |
| 69 | IntRect dirtyRect(mapFromContents(contentsDirtyRect)); |
| 70 | IntRect keepRectFitToTileSize = tileRectForCoordinate(tileCoordinateForPoint(m_keepRect.location())); |
| 71 | keepRectFitToTileSize.unite(tileRectForCoordinate(tileCoordinateForPoint(innerBottomRight(m_keepRect)))); |
| 72 | |
| 73 | // Only iterate on the part of the rect that we know we might have tiles. |
| 74 | IntRect coveredDirtyRect = intersection(dirtyRect, keepRectFitToTileSize); |
| 75 | Tile::Coordinate topLeft = tileCoordinateForPoint(coveredDirtyRect.location()); |
| 76 | Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(coveredDirtyRect)); |
| 77 | |
| 78 | for (int yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) { |
| 79 | for (int xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) { |
| 80 | Tile* currentTile = m_tiles.get(Tile::Coordinate(xCoordinate, yCoordinate)); |
| 81 | if (!currentTile) |
| 82 | continue; |
| 83 | // Pass the full rect to each tile as coveredDirtyRect might not |
| 84 | // contain them completely and we don't want partial tile redraws. |
| 85 | currentTile->invalidate(dirtyRect); |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | Vector<std::reference_wrapper<Tile>> TiledBackingStore::dirtyTiles() |
| 91 | { |
| 92 | Vector<std::reference_wrapper<Tile>> tiles; |
| 93 | for (auto& tile : m_tiles.values()) { |
| 94 | if (tile->isDirty()) |
| 95 | tiles.append(*tile); |
| 96 | } |
| 97 | |
| 98 | return tiles; |
| 99 | } |
| 100 | |
| 101 | double TiledBackingStore::tileDistance(const IntRect& viewport, const Tile::Coordinate& tileCoordinate) const |
| 102 | { |
| 103 | if (viewport.intersects(tileRectForCoordinate(tileCoordinate))) |
| 104 | return 0; |
| 105 | |
| 106 | IntPoint viewCenter = viewport.location() + IntSize(viewport.width() / 2, viewport.height() / 2); |
| 107 | Tile::Coordinate centerCoordinate = tileCoordinateForPoint(viewCenter); |
| 108 | |
| 109 | return std::max(abs(centerCoordinate.y() - tileCoordinate.y()), abs(centerCoordinate.x() - tileCoordinate.x())); |
| 110 | } |
| 111 | |
| 112 | // Returns a ratio between 0.0f and 1.0f of the surface covered by rendered tiles. |
| 113 | float TiledBackingStore::coverageRatio(const WebCore::IntRect& dirtyRect) const |
| 114 | { |
| 115 | float rectArea = dirtyRect.width() * dirtyRect.height(); |
| 116 | float coverArea = 0.0f; |
| 117 | |
| 118 | Tile::Coordinate topLeft = tileCoordinateForPoint(dirtyRect.location()); |
| 119 | Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(dirtyRect)); |
| 120 | |
| 121 | for (int yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) { |
| 122 | for (int xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) { |
| 123 | Tile::Coordinate currentCoordinate(xCoordinate, yCoordinate); |
| 124 | Tile* currentTile = m_tiles.get(currentCoordinate); |
| 125 | if (currentTile && currentTile->isReadyToPaint()) { |
| 126 | IntRect coverRect = intersection(dirtyRect, currentTile->rect()); |
| 127 | coverArea += coverRect.width() * coverRect.height(); |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | return coverArea / rectArea; |
| 132 | } |
| 133 | |
| 134 | bool TiledBackingStore::visibleAreaIsCovered() const |
| 135 | { |
| 136 | return coverageRatio(intersection(m_visibleRect, m_rect)) == 1.0f; |
| 137 | } |
| 138 | |
| 139 | void TiledBackingStore::createTiles(const IntRect& visibleRect, const IntRect& scaledContentsRect, float coverAreaMultiplier) |
| 140 | { |
| 141 | // Update our backing store geometry. |
| 142 | m_rect = scaledContentsRect; |
| 143 | m_trajectoryVector = m_pendingTrajectoryVector; |
| 144 | m_visibleRect = visibleRect; |
| 145 | m_coverAreaMultiplier = coverAreaMultiplier; |
| 146 | |
| 147 | if (m_rect.isEmpty()) { |
| 148 | setCoverRect(IntRect()); |
| 149 | setKeepRect(IntRect()); |
| 150 | return; |
| 151 | } |
| 152 | |
| 153 | /* We must compute cover and keep rects using the visibleRect, instead of the rect intersecting the visibleRect with m_rect, |
| 154 | * because TBS can be used as a backing store of GraphicsLayer and the visible rect usually does not intersect with m_rect. |
| 155 | * In the below case, the intersecting rect is an empty. |
| 156 | * |
| 157 | * +---------------+ |
| 158 | * | | |
| 159 | * | m_rect | |
| 160 | * | +-------|-----------------------+ |
| 161 | * | | HERE | cover or keep | |
| 162 | * +---------------+ rect | |
| 163 | * | +---------+ | |
| 164 | * | | visible | | |
| 165 | * | | rect | | |
| 166 | * | +---------+ | |
| 167 | * | | |
| 168 | * | | |
| 169 | * +-------------------------------+ |
| 170 | * |
| 171 | * We must create or keep the tiles in the HERE region. |
| 172 | */ |
| 173 | |
| 174 | IntRect coverRect; |
| 175 | IntRect keepRect; |
| 176 | computeCoverAndKeepRect(m_visibleRect, coverRect, keepRect); |
| 177 | |
| 178 | setCoverRect(coverRect); |
| 179 | setKeepRect(keepRect); |
| 180 | |
| 181 | if (coverRect.isEmpty()) |
| 182 | return; |
| 183 | |
| 184 | // Resize tiles at the edge in case the contents size has changed, but only do so |
| 185 | // after having dropped tiles outside the keep rect. |
| 186 | if (m_previousRect != m_rect) { |
| 187 | m_previousRect = m_rect; |
| 188 | resizeEdgeTiles(); |
| 189 | } |
| 190 | |
| 191 | // Search for the tile position closest to the viewport center that does not yet contain a tile. |
| 192 | // Which position is considered the closest depends on the tileDistance function. |
| 193 | double shortestDistance = std::numeric_limits<double>::infinity(); |
| 194 | Vector<Tile::Coordinate> tilesToCreate; |
| 195 | unsigned requiredTileCount = 0; |
| 196 | |
| 197 | // Cover areas (in tiles) with minimum distance from the visible rect. If the visible rect is |
| 198 | // not covered already it will be covered first in one go, due to the distance being 0 for tiles |
| 199 | // inside the visible rect. |
| 200 | Tile::Coordinate topLeft = tileCoordinateForPoint(coverRect.location()); |
| 201 | Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(coverRect)); |
| 202 | for (int yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) { |
| 203 | for (int xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) { |
| 204 | Tile::Coordinate currentCoordinate(xCoordinate, yCoordinate); |
| 205 | if (m_tiles.contains(currentCoordinate)) |
| 206 | continue; |
| 207 | ++requiredTileCount; |
| 208 | double distance = tileDistance(m_visibleRect, currentCoordinate); |
| 209 | if (distance > shortestDistance) |
| 210 | continue; |
| 211 | if (distance < shortestDistance) { |
| 212 | tilesToCreate.clear(); |
| 213 | shortestDistance = distance; |
| 214 | } |
| 215 | tilesToCreate.append(currentCoordinate); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | // Now construct the tile(s) within the shortest distance. |
| 220 | unsigned tilesToCreateCount = tilesToCreate.size(); |
| 221 | for (unsigned n = 0; n < tilesToCreateCount; ++n) { |
| 222 | Tile::Coordinate coordinate = tilesToCreate[n]; |
| 223 | m_tiles.add(coordinate, std::make_unique<Tile>(*this, coordinate)); |
| 224 | } |
| 225 | requiredTileCount -= tilesToCreateCount; |
| 226 | |
| 227 | // Re-call createTiles on a timer to cover the visible area with the newest shortest distance. |
| 228 | m_pendingTileCreation = requiredTileCount; |
| 229 | if (m_pendingTileCreation) |
| 230 | m_client.tiledBackingStoreHasPendingTileCreation(); |
| 231 | } |
| 232 | |
| 233 | void TiledBackingStore::adjustForContentsRect(IntRect& rect) const |
| 234 | { |
| 235 | IntRect bounds = m_rect; |
| 236 | IntSize candidateSize = rect.size(); |
| 237 | |
| 238 | rect.intersect(bounds); |
| 239 | |
| 240 | if (rect.size() == candidateSize) |
| 241 | return; |
| 242 | |
| 243 | /* |
| 244 | * In the following case, there is no intersection of the contents rect and the cover rect. |
| 245 | * Thus the latter should not be inflated. |
| 246 | * |
| 247 | * +---------------+ |
| 248 | * | m_rect | |
| 249 | * +---------------+ |
| 250 | * |
| 251 | * +-------------------------------+ |
| 252 | * | cover rect | |
| 253 | * | +---------+ | |
| 254 | * | | visible | | |
| 255 | * | | rect | | |
| 256 | * | +---------+ | |
| 257 | * +-------------------------------+ |
| 258 | */ |
| 259 | if (rect.isEmpty()) |
| 260 | return; |
| 261 | |
| 262 | // Try to create a cover rect of the same size as the candidate, but within content bounds. |
| 263 | int pixelsCovered = 0; |
| 264 | if (!WTF::safeMultiply(candidateSize.width(), candidateSize.height(), pixelsCovered)) |
| 265 | pixelsCovered = std::numeric_limits<int>::max(); |
| 266 | |
| 267 | if (rect.width() < candidateSize.width()) |
| 268 | rect.inflateY(((pixelsCovered / rect.width()) - rect.height()) / 2); |
| 269 | if (rect.height() < candidateSize.height()) |
| 270 | rect.inflateX(((pixelsCovered / rect.height()) - rect.width()) / 2); |
| 271 | |
| 272 | rect.intersect(bounds); |
| 273 | } |
| 274 | |
| 275 | void TiledBackingStore::computeCoverAndKeepRect(const IntRect& visibleRect, IntRect& coverRect, IntRect& keepRect) const |
| 276 | { |
| 277 | coverRect = visibleRect; |
| 278 | keepRect = visibleRect; |
| 279 | |
| 280 | // If we cover more that the actual viewport we can be smart about which tiles we choose to render. |
| 281 | if (m_coverAreaMultiplier > 1) { |
| 282 | // The initial cover area covers equally in each direction, according to the coverAreaMultiplier. |
| 283 | coverRect.inflateX(visibleRect.width() * (m_coverAreaMultiplier - 1) / 2); |
| 284 | coverRect.inflateY(visibleRect.height() * (m_coverAreaMultiplier - 1) / 2); |
| 285 | keepRect = coverRect; |
| 286 | |
| 287 | if (m_trajectoryVector != FloatPoint::zero()) { |
| 288 | // A null trajectory vector (no motion) means that tiles for the coverArea will be created. |
| 289 | // A non-null trajectory vector will shrink the covered rect to visibleRect plus its expansion from its |
| 290 | // center toward the cover area edges in the direction of the given vector. |
| 291 | |
| 292 | // E.g. if visibleRect == (10,10)5x5 and coverAreaMultiplier == 3.0: |
| 293 | // a (0,0) trajectory vector will create tiles intersecting (5,5)15x15, |
| 294 | // a (1,0) trajectory vector will create tiles intersecting (10,10)10x5, |
| 295 | // and a (1,1) trajectory vector will create tiles intersecting (10,10)10x10. |
| 296 | |
| 297 | // Multiply the vector by the distance to the edge of the cover area. |
| 298 | float trajectoryVectorMultiplier = (m_coverAreaMultiplier - 1) / 2; |
| 299 | |
| 300 | // Unite the visible rect with a "ghost" of the visible rect moved in the direction of the trajectory vector. |
| 301 | coverRect = visibleRect; |
| 302 | coverRect.move(coverRect.width() * m_trajectoryVector.x() * trajectoryVectorMultiplier, coverRect.height() * m_trajectoryVector.y() * trajectoryVectorMultiplier); |
| 303 | |
| 304 | coverRect.unite(visibleRect); |
| 305 | } |
| 306 | ASSERT(keepRect.contains(coverRect)); |
| 307 | } |
| 308 | |
| 309 | adjustForContentsRect(coverRect); |
| 310 | |
| 311 | // The keep rect is an inflated version of the cover rect, inflated in tile dimensions. |
| 312 | keepRect.unite(coverRect); |
| 313 | keepRect.inflateX(m_tileSize.width() / 2); |
| 314 | keepRect.inflateY(m_tileSize.height() / 2); |
| 315 | keepRect.intersect(m_rect); |
| 316 | |
| 317 | ASSERT(coverRect.isEmpty() || keepRect.contains(coverRect)); |
| 318 | } |
| 319 | |
| 320 | void TiledBackingStore::resizeEdgeTiles() |
| 321 | { |
| 322 | Vector<Tile::Coordinate> tilesToRemove; |
| 323 | for (auto& tile : m_tiles.values()) { |
| 324 | Tile::Coordinate tileCoordinate = tile->coordinate(); |
| 325 | IntRect tileRect = tile->rect(); |
| 326 | IntRect expectedTileRect = tileRectForCoordinate(tileCoordinate); |
| 327 | if (expectedTileRect.isEmpty()) |
| 328 | tilesToRemove.append(tileCoordinate); |
| 329 | else if (expectedTileRect != tileRect) |
| 330 | tile->resize(expectedTileRect.size()); |
| 331 | } |
| 332 | |
| 333 | for (auto& coordinateToRemove : tilesToRemove) |
| 334 | m_tiles.remove(coordinateToRemove); |
| 335 | } |
| 336 | |
| 337 | void TiledBackingStore::setKeepRect(const IntRect& keepRect) |
| 338 | { |
| 339 | // Drop tiles outside the new keepRect. |
| 340 | |
| 341 | FloatRect keepRectF = keepRect; |
| 342 | |
| 343 | Vector<Tile::Coordinate> toRemove; |
| 344 | for (auto& tile : m_tiles.values()) { |
| 345 | Tile::Coordinate coordinate = tile->coordinate(); |
| 346 | FloatRect tileRect = tile->rect(); |
| 347 | if (!tileRect.intersects(keepRectF)) |
| 348 | toRemove.append(coordinate); |
| 349 | } |
| 350 | |
| 351 | for (auto& coordinateToRemove : toRemove) |
| 352 | m_tiles.remove(coordinateToRemove); |
| 353 | |
| 354 | m_keepRect = keepRect; |
| 355 | } |
| 356 | |
| 357 | void TiledBackingStore::removeAllNonVisibleTiles(const IntRect& unscaledVisibleRect, const IntRect& contentsRect) |
| 358 | { |
| 359 | IntRect boundedVisibleRect = mapFromContents(intersection(unscaledVisibleRect, contentsRect)); |
| 360 | setKeepRect(boundedVisibleRect); |
| 361 | } |
| 362 | |
| 363 | IntRect TiledBackingStore::mapToContents(const IntRect& rect) const |
| 364 | { |
| 365 | return enclosingIntRect(FloatRect(rect.x() / m_contentsScale, |
| 366 | rect.y() / m_contentsScale, |
| 367 | rect.width() / m_contentsScale, |
| 368 | rect.height() / m_contentsScale)); |
| 369 | } |
| 370 | |
| 371 | IntRect TiledBackingStore::mapFromContents(const IntRect& rect) const |
| 372 | { |
| 373 | return enclosingIntRect(FloatRect(rect.x() * m_contentsScale, |
| 374 | rect.y() * m_contentsScale, |
| 375 | rect.width() * m_contentsScale, |
| 376 | rect.height() * m_contentsScale)); |
| 377 | } |
| 378 | |
| 379 | IntRect TiledBackingStore::tileRectForCoordinate(const Tile::Coordinate& coordinate) const |
| 380 | { |
| 381 | IntRect rect(coordinate.x() * m_tileSize.width(), |
| 382 | coordinate.y() * m_tileSize.height(), |
| 383 | m_tileSize.width(), |
| 384 | m_tileSize.height()); |
| 385 | |
| 386 | rect.intersect(m_rect); |
| 387 | return rect; |
| 388 | } |
| 389 | |
| 390 | Tile::Coordinate TiledBackingStore::tileCoordinateForPoint(const IntPoint& point) const |
| 391 | { |
| 392 | int x = point.x() / m_tileSize.width(); |
| 393 | int y = point.y() / m_tileSize.height(); |
| 394 | return Tile::Coordinate(std::max(x, 0), std::max(y, 0)); |
| 395 | } |
| 396 | |
| 397 | } |
| 398 | |
| 399 | #endif |
| 400 | |