1 /* 2 * Copyright 2010, The Android Open Source Project 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 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * 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 THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "TiledPage.h" 28 29 #if USE(ACCELERATED_COMPOSITING) 30 31 #include "GLUtils.h" 32 #include "IntRect.h" 33 #include "PaintTileOperation.h" 34 #include "SkPaint.h" 35 #include "SkPaintFlagsDrawFilter.h" 36 #include "TilesManager.h" 37 38 #include <cutils/log.h> 39 #include <wtf/CurrentTime.h> 40 #include <wtf/text/CString.h> 41 42 #undef XLOGC 43 #define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "TiledPage", __VA_ARGS__) 44 45 #ifdef DEBUG 46 47 #undef XLOG 48 #define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "TiledPage", __VA_ARGS__) 49 50 #else 51 52 #undef XLOG 53 #define XLOG(...) 54 55 #endif // DEBUG 56 57 namespace WebCore { 58 59 using namespace android; 60 61 TiledPage::TiledPage(int id, GLWebViewState* state) 62 : m_baseTiles(0) 63 , m_baseTileSize(0) 64 , m_id(id) 65 , m_scale(1) 66 , m_invScale(1) 67 , m_glWebViewState(state) 68 , m_latestPictureInval(0) 69 , m_prepare(false) 70 , m_isPrefetchPage(false) 71 , m_willDraw(false) 72 { 73 m_baseTiles = new BaseTile[TilesManager::getMaxTextureAllocation() + 1]; 74 #ifdef DEBUG_COUNT 75 ClassTracker::instance()->increment("TiledPage"); 76 #endif 77 } 78 79 void TiledPage::updateBaseTileSize() 80 { 81 // This value must be at least 1 greater than the max number of allowed 82 // textures. This is because prepare() asks for a tile before it reserves 83 // a texture for that tile. If all textures are currently in use by the 84 // page then there will be no available tile and having the extra tile 85 // ensures that this does not happen. After claiming the extra tile the call 86 // to reserveTexture() will cause some other tile in the page to lose it's 87 // texture and become available, thus ensuring that we always have at least 88 // one tile that is available. 89 int baseTileSize = TilesManager::instance()->maxTextureCount() + 1; 90 if (baseTileSize > m_baseTileSize) 91 m_baseTileSize = baseTileSize; 92 } 93 94 TiledPage::~TiledPage() 95 { 96 // In order to delete the page we must ensure that none of its BaseTiles are 97 // currently painting or scheduled to be painted by the TextureGenerator 98 TilesManager::instance()->removeOperationsForPage(this); 99 delete[] m_baseTiles; 100 #ifdef DEBUG_COUNT 101 ClassTracker::instance()->decrement("TiledPage"); 102 #endif 103 } 104 105 BaseTile* TiledPage::getBaseTile(int x, int y) const 106 { 107 // TODO: replace loop over array with HashMap indexing 108 for (int j = 0; j < m_baseTileSize; j++) { 109 BaseTile& tile = m_baseTiles[j]; 110 if (tile.x() == x && tile.y() == y) 111 return &tile; 112 } 113 return 0; 114 } 115 116 void TiledPage::discardTextures() 117 { 118 for (int j = 0; j < m_baseTileSize; j++) { 119 BaseTile& tile = m_baseTiles[j]; 120 tile.discardTextures(); 121 } 122 return; 123 } 124 125 void TiledPage::invalidateRect(const IntRect& inval, const unsigned int pictureCount) 126 { 127 // Given the current scale level we need to mark the appropriate tiles as dirty 128 const float invTileContentWidth = m_scale / TilesManager::tileWidth(); 129 const float invTileContentHeight = m_scale / TilesManager::tileHeight(); 130 131 const int firstDirtyTileX = static_cast<int>(floorf(inval.x() * invTileContentWidth)); 132 const int firstDirtyTileY = static_cast<int>(floorf(inval.y() * invTileContentHeight)); 133 const int lastDirtyTileX = static_cast<int>(ceilf(inval.maxX() * invTileContentWidth)); 134 const int lastDirtyTileY = static_cast<int>(ceilf(inval.maxY() * invTileContentHeight)); 135 136 XLOG("Marking X %d-%d and Y %d-%d dirty", firstDirtyTileX, lastDirtyTileX, firstDirtyTileY, lastDirtyTileY); 137 // We defer marking the tile as dirty until the next time we need to prepare 138 // to draw. 139 m_invalRegion.op(firstDirtyTileX, firstDirtyTileY, lastDirtyTileX, lastDirtyTileY, SkRegion::kUnion_Op); 140 m_invalTilesRegion.op(inval.x(), inval.y(), inval.maxX(), inval.maxY(), SkRegion::kUnion_Op); 141 m_latestPictureInval = pictureCount; 142 } 143 144 void TiledPage::prepareRow(bool goingLeft, int tilesInRow, int firstTileX, int y, const SkIRect& tileBounds) 145 { 146 for (int i = 0; i < tilesInRow; i++) { 147 int x = firstTileX; 148 149 // If we are goingLeft, we want to schedule the tiles starting from the 150 // right (and to the left if not). This is because tiles are appended to 151 // the list and the texture uploader goes through the set front to back. 152 if (goingLeft) 153 x += (tilesInRow - 1) - i; 154 else 155 x += i; 156 157 BaseTile* currentTile = 0; 158 BaseTile* availableTile = 0; 159 for (int j = 0; j < m_baseTileSize; j++) { 160 BaseTile& tile = m_baseTiles[j]; 161 if (tile.x() == x && tile.y() == y) { 162 currentTile = &tile; 163 break; 164 } 165 166 if (!availableTile || (tile.drawCount() < availableTile->drawCount())) 167 availableTile = &tile; 168 } 169 170 if (!currentTile && availableTile) { 171 XLOG("STEALING tile %d, %d (draw count %llu) for tile %d, %d", 172 availableTile->x(), availableTile->y(), availableTile->drawCount(), x, y); 173 availableTile->discardTextures(); // don't wait for textures to be stolen 174 currentTile = availableTile; 175 } 176 177 if (!currentTile) { 178 XLOG("ERROR: No tile available for tile %d %d", x, y); 179 } 180 181 if (currentTile) { 182 currentTile->setGLWebViewState(m_glWebViewState); 183 currentTile->setPage(this); 184 185 currentTile->setContents(this, x, y, m_scale); 186 187 // TODO: move below (which is largely the same for layers / tiled 188 // page) into prepare() function 189 190 // ensure there is a texture associated with the tile and then check to 191 // see if the texture is dirty and in need of repainting 192 if (currentTile->isDirty() || !currentTile->frontTexture()) 193 currentTile->reserveTexture(); 194 if (currentTile->backTexture() 195 && currentTile->isDirty() 196 && !currentTile->isRepaintPending()) { 197 PaintTileOperation *operation = new PaintTileOperation(currentTile); 198 TilesManager::instance()->scheduleOperation(operation); 199 } 200 } 201 } 202 } 203 204 bool TiledPage::updateTileDirtiness(const SkIRect& tileBounds) 205 { 206 if (!m_glWebViewState || tileBounds.isEmpty()) { 207 m_invalRegion.setEmpty(); 208 m_invalTilesRegion.setEmpty(); 209 return false; 210 } 211 212 bool visibleTileIsDirty = false; 213 for (int x = 0; x < m_baseTileSize; x++) { 214 215 BaseTile& tile = m_baseTiles[x]; 216 217 // if the tile is in the dirty region then we must invalidate it 218 if (m_invalRegion.contains(tile.x(), tile.y())) { 219 tile.markAsDirty(m_latestPictureInval, m_invalTilesRegion); 220 if (tileBounds.contains(tile.x(), tile.y())) 221 visibleTileIsDirty = true; 222 } 223 } 224 225 // clear the invalidated region as all tiles within that region have now 226 // been marked as dirty. 227 m_invalRegion.setEmpty(); 228 m_invalTilesRegion.setEmpty(); 229 return visibleTileIsDirty; 230 } 231 232 void TiledPage::prepare(bool goingDown, bool goingLeft, const SkIRect& tileBounds, PrepareBounds bounds) 233 { 234 if (!m_glWebViewState) 235 return; 236 237 TilesManager::instance()->gatherTextures(); 238 m_scrollingDown = goingDown; 239 240 int firstTileX = tileBounds.fLeft; 241 int firstTileY = tileBounds.fTop; 242 int nbTilesWidth = tileBounds.width(); 243 int nbTilesHeight = tileBounds.height(); 244 245 // Expand number of tiles to allow tiles outside of viewport to be prepared for 246 // smoother scrolling. 247 int nTilesToPrepare = nbTilesWidth * nbTilesHeight; 248 int nMaxTilesPerPage = m_baseTileSize / 2; 249 250 if (bounds == ExpandedBounds) { 251 // prepare tiles outside of the visible bounds 252 int expandX = m_glWebViewState->expandedTileBoundsX(); 253 int expandY = m_glWebViewState->expandedTileBoundsY(); 254 255 firstTileX -= expandX; 256 nbTilesWidth += expandX * 2; 257 258 firstTileY -= expandY; 259 nbTilesHeight += expandY * 2; 260 } 261 262 // crop the tile bounds in each dimension to the larger of the base layer or viewport 263 float maxBaseX = m_glWebViewState->baseContentWidth() * m_scale / TilesManager::tileWidth(); 264 float maxBaseY = m_glWebViewState->baseContentHeight() * m_scale / TilesManager::tileHeight(); 265 int maxX = std::max(static_cast<int>(ceilf(maxBaseX)), 266 m_glWebViewState->viewportTileBounds().width()); 267 int maxY = std::max(static_cast<int>(ceilf(maxBaseY)), 268 m_glWebViewState->viewportTileBounds().height()); 269 270 // adjust perimeter to not go outside cropped region 271 if (firstTileX < 0) { 272 nbTilesWidth += firstTileX; 273 firstTileX = 0; 274 } 275 if (firstTileY < 0) { 276 nbTilesHeight += firstTileY; 277 firstTileY = 0; 278 } 279 nbTilesWidth = std::min(nbTilesWidth, maxX - firstTileX); 280 nbTilesHeight = std::min(nbTilesHeight, maxY - firstTileY); 281 282 // check against corrupted scale values giving bad height/width (use float to avoid overflow) 283 float numTiles = static_cast<float>(nbTilesHeight) * static_cast<float>(nbTilesWidth); 284 if (numTiles > TilesManager::getMaxTextureAllocation() || nbTilesHeight < 1 || nbTilesWidth < 1) 285 { 286 XLOGC("ERROR: We don't have enough tiles for this page!" 287 " nbTilesHeight %d nbTilesWidth %d", nbTilesHeight, nbTilesWidth); 288 return; 289 } 290 for (int i = 0; i < nbTilesHeight; i++) 291 prepareRow(goingLeft, nbTilesWidth, firstTileX, firstTileY + i, tileBounds); 292 293 m_prepare = true; 294 } 295 296 bool TiledPage::hasMissingContent(const SkIRect& tileBounds) 297 { 298 int neededTiles = tileBounds.width() * tileBounds.height(); 299 for (int j = 0; j < m_baseTileSize; j++) { 300 BaseTile& tile = m_baseTiles[j]; 301 if (tileBounds.contains(tile.x(), tile.y())) { 302 if (tile.frontTexture()) 303 neededTiles--; 304 } 305 } 306 return neededTiles > 0; 307 } 308 309 bool TiledPage::isReady(const SkIRect& tileBounds) 310 { 311 int neededTiles = tileBounds.width() * tileBounds.height(); 312 XLOG("tiled page %p needs %d ready tiles", this, neededTiles); 313 for (int j = 0; j < m_baseTileSize; j++) { 314 BaseTile& tile = m_baseTiles[j]; 315 if (tileBounds.contains(tile.x(), tile.y())) { 316 if (tile.isTileReady()) 317 neededTiles--; 318 } 319 } 320 XLOG("tiled page %p still needs %d ready tiles", this, neededTiles); 321 return neededTiles == 0; 322 } 323 324 bool TiledPage::swapBuffersIfReady(const SkIRect& tileBounds, float scale) 325 { 326 if (!m_glWebViewState) 327 return false; 328 329 if (!m_invalRegion.isEmpty() && !m_prepare) 330 return false; 331 332 if (m_scale != scale) 333 return false; 334 335 int swaps = 0; 336 bool fullSwap = true; 337 for (int x = tileBounds.fLeft; x < tileBounds.fRight; x++) { 338 for (int y = tileBounds.fTop; y < tileBounds.fBottom; y++) { 339 BaseTile* t = getBaseTile(x, y); 340 if (!t || !t->isTileReady()) 341 fullSwap = false; 342 } 343 } 344 345 // swap every tile on page (even if off screen) 346 for (int j = 0; j < m_baseTileSize; j++) { 347 BaseTile& tile = m_baseTiles[j]; 348 if (tile.swapTexturesIfNeeded()) 349 swaps++; 350 } 351 352 XLOG("%p greedy swapped %d textures, returning true", this, swaps); 353 return fullSwap; 354 } 355 356 void TiledPage::prepareForDrawGL(float transparency, const SkIRect& tileBounds) 357 { 358 m_willDraw = true; 359 m_transparency = transparency; 360 m_tileBounds = tileBounds; 361 } 362 363 void TiledPage::drawGL() 364 { 365 if (!m_glWebViewState || m_transparency == 0 || !m_willDraw) 366 return; 367 368 const float tileWidth = TilesManager::tileWidth() * m_invScale; 369 const float tileHeight = TilesManager::tileHeight() * m_invScale; 370 371 for (int j = 0; j < m_baseTileSize; j++) { 372 BaseTile& tile = m_baseTiles[j]; 373 bool tileInView = m_tileBounds.contains(tile.x(), tile.y()); 374 if (tileInView) { 375 SkRect rect; 376 rect.fLeft = tile.x() * tileWidth; 377 rect.fTop = tile.y() * tileHeight; 378 rect.fRight = rect.fLeft + tileWidth; 379 rect.fBottom = rect.fTop + tileHeight; 380 381 tile.draw(m_transparency, rect, m_scale); 382 } 383 384 TilesManager::instance()->getProfiler()->nextTile(tile, m_invScale, tileInView); 385 } 386 m_willDraw = false; // don't redraw until re-prepared 387 } 388 389 bool TiledPage::paint(BaseTile* tile, SkCanvas* canvas, unsigned int* pictureUsed) 390 { 391 static SkPaintFlagsDrawFilter prefetchFilter(SkPaint::kAllFlags, 392 SkPaint::kAntiAlias_Flag); 393 394 if (!m_glWebViewState) 395 return false; 396 397 if (isPrefetchPage()) 398 canvas->setDrawFilter(&prefetchFilter); 399 400 *pictureUsed = m_glWebViewState->paintBaseLayerContent(canvas); 401 return true; 402 } 403 404 TiledPage* TiledPage::sibling() 405 { 406 if (!m_glWebViewState) 407 return 0; 408 return m_glWebViewState->sibling(this); 409 } 410 411 } // namespace WebCore 412 413 #endif // USE(ACCELERATED_COMPOSITING) 414