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 #define LOG_TAG "Tile" 27 #define LOG_NDEBUG 1 28 29 #include "config.h" 30 #include "Tile.h" 31 32 #if USE(ACCELERATED_COMPOSITING) 33 34 #include "AndroidLog.h" 35 #include "GLUtils.h" 36 #include "BaseRenderer.h" 37 #include "TextureInfo.h" 38 #include "TileTexture.h" 39 #include "TilesManager.h" 40 41 // If the dirty portion of a tile exceeds this ratio, fully repaint. 42 // Lower values give fewer partial repaints, thus fewer front-to-back 43 // texture copies (cost will vary by device). It's a tradeoff between 44 // the rasterization cost and the FBO texture recopy cost when using 45 // GPU for the transfer queue. 46 #define MAX_INVAL_AREA 0.6 47 48 namespace WebCore { 49 50 Tile::Tile(bool isLayerTile) 51 : m_x(-1) 52 , m_y(-1) 53 , m_frontTexture(0) 54 , m_backTexture(0) 55 , m_lastDrawnTexture(0) 56 , m_scale(1) 57 , m_dirty(true) 58 , m_repaintsPending(0) 59 , m_fullRepaint(true) 60 , m_isLayerTile(isLayerTile) 61 , m_drawCount(0) 62 , m_state(Unpainted) 63 { 64 #ifdef DEBUG_COUNT 65 ClassTracker::instance()->increment("Tile"); 66 #endif 67 } 68 69 Tile::~Tile() 70 { 71 if (m_backTexture) 72 m_backTexture->release(this); 73 if (m_frontTexture) 74 m_frontTexture->release(this); 75 76 #ifdef DEBUG_COUNT 77 ClassTracker::instance()->decrement("Tile"); 78 #endif 79 } 80 81 // All the following functions must be called from the main GL thread. 82 83 void Tile::setContents(int x, int y, float scale, bool isExpandedPrefetchTile) 84 { 85 // TODO: investigate whether below check/discard is necessary 86 if ((m_x != x) 87 || (m_y != y) 88 || (m_scale != scale)) { 89 // neither texture is relevant 90 discardTextures(); 91 } 92 93 android::AutoMutex lock(m_atomicSync); 94 m_x = x; 95 m_y = y; 96 m_scale = scale; 97 m_drawCount = TilesManager::instance()->getDrawGLCount(); 98 if (isExpandedPrefetchTile) 99 m_drawCount--; // deprioritize expanded painting region 100 } 101 102 void Tile::reserveTexture() 103 { 104 TileTexture* texture = TilesManager::instance()->getAvailableTexture(this); 105 106 android::AutoMutex lock(m_atomicSync); 107 if (texture && m_backTexture != texture) { 108 ALOGV("tile %p reserving texture %p, back was %p (front %p)", 109 this, texture, m_backTexture, m_frontTexture); 110 m_state = Unpainted; 111 m_backTexture = texture; 112 } 113 114 if (m_state == UpToDate) { 115 ALOGV("moving tile %p to unpainted, since it reserved while up to date", this); 116 m_dirty = true; 117 m_state = Unpainted; 118 } 119 } 120 121 bool Tile::removeTexture(TileTexture* texture) 122 { 123 ALOGV("%p removeTexture %p, back %p front %p", 124 this, texture, m_backTexture, m_frontTexture); 125 // We update atomically, so paintBitmap() can see the correct value 126 android::AutoMutex lock(m_atomicSync); 127 if (m_frontTexture == texture) { 128 if (m_state == UpToDate) { 129 ALOGV("front texture removed, state was UpToDate, now becoming unpainted, bt is %p", m_backTexture); 130 m_state = Unpainted; 131 } 132 133 m_frontTexture = 0; 134 } 135 if (m_backTexture == texture) { 136 m_state = Unpainted; 137 m_backTexture = 0; 138 } 139 140 // mark dirty regardless of which texture was taken - the back texture may 141 // have been ready to swap 142 m_dirty = true; 143 144 return true; 145 } 146 147 void Tile::markAsDirty() 148 { 149 android::AutoMutex lock(m_atomicSync); 150 m_dirtyArea.setEmpty(); // empty dirty rect prevents fast blit path 151 markAsDirtyInternal(); 152 } 153 154 void Tile::markAsDirty(const SkRegion& dirtyArea) 155 { 156 if (dirtyArea.isEmpty()) 157 return; 158 android::AutoMutex lock(m_atomicSync); 159 m_dirtyArea.op(dirtyArea, SkRegion::kUnion_Op); 160 161 // Check if we actually intersect with the area 162 bool intersect = false; 163 SkRegion::Iterator cliperator(dirtyArea); 164 SkRect realTileRect; 165 SkRect dirtyRect; 166 while (!cliperator.done()) { 167 dirtyRect.set(cliperator.rect()); 168 if (intersectWithRect(m_x, m_y, TilesManager::tileWidth(), TilesManager::tileHeight(), 169 m_scale, dirtyRect, realTileRect)) { 170 intersect = true; 171 break; 172 } 173 cliperator.next(); 174 } 175 176 if (!intersect) 177 return; 178 179 markAsDirtyInternal(); 180 } 181 182 void Tile::markAsDirtyInternal() 183 { 184 // NOTE: callers must hold lock on m_atomicSync 185 186 m_dirty = true; 187 if (m_state == UpToDate) { 188 // We only mark a tile as unpainted in 'markAsDirty' if its status is 189 // UpToDate: marking dirty means we need to repaint, but don't stop the 190 // current paint 191 m_state = Unpainted; 192 } else if (m_state != Unpainted) { 193 // TODO: fix it so that they can paint while deferring the markAsDirty 194 // call (or block updates) 195 ALOGV("Warning: tried to mark tile %p at %d, %d islayertile %d as dirty, state %d", 196 this, m_x, m_y, isLayerTile(), m_state); 197 198 // prefetch tiles can be marked dirty while in the process of painting, 199 // due to not using an update lock. force them to fail validate step. 200 m_state = Unpainted; 201 } 202 } 203 204 bool Tile::isDirty() 205 { 206 android::AutoMutex lock(m_atomicSync); 207 return m_dirty; 208 } 209 210 bool Tile::isRepaintPending() 211 { 212 android::AutoMutex lock(m_atomicSync); 213 return m_repaintsPending != 0; 214 } 215 216 void Tile::setRepaintPending(bool pending) 217 { 218 android::AutoMutex lock(m_atomicSync); 219 m_repaintsPending += pending ? 1 : -1; 220 } 221 222 bool Tile::drawGL(float opacity, const SkRect& rect, float scale, 223 const TransformationMatrix* transform, 224 bool forceBlending, bool usePointSampling, 225 const FloatRect& fillPortion) 226 { 227 if (m_x < 0 || m_y < 0 || m_scale != scale) 228 return false; 229 230 // No need to mutex protect reads of m_backTexture as it is only written to by 231 // the consumer thread. 232 if (!m_frontTexture) 233 return false; 234 235 if (fillPortion.maxX() < 1.0f || fillPortion.maxY() < 1.0f 236 || fillPortion.x() > 0.0f || fillPortion.y() > 0.0f) 237 ALOGV("drawing tile %p (%d, %d with fill portions %f %f->%f, %f", 238 this, m_x, m_y, fillPortion.x(), fillPortion.y(), 239 fillPortion.maxX(), fillPortion.maxY()); 240 241 m_frontTexture->drawGL(isLayerTile(), rect, opacity, transform, 242 forceBlending, usePointSampling, fillPortion); 243 m_lastDrawnTexture = m_frontTexture; 244 return true; 245 } 246 247 bool Tile::isTileReady() 248 { 249 // Return true if the tile's most recently drawn texture is up to date 250 android::AutoMutex lock(m_atomicSync); 251 TileTexture * texture = (m_state == ReadyToSwap) ? m_backTexture : m_frontTexture; 252 253 if (!texture) 254 return false; 255 256 if (texture->owner() != this) 257 return false; 258 259 if (m_dirty) 260 return false; 261 262 if (m_state != ReadyToSwap && m_state != UpToDate) 263 return false; 264 265 return true; 266 } 267 268 bool Tile::intersectWithRect(int x, int y, int tileWidth, int tileHeight, 269 float scale, const SkRect& dirtyRect, 270 SkRect& realTileRect) 271 { 272 // compute the rect to corresponds to pixels 273 realTileRect.fLeft = x * tileWidth; 274 realTileRect.fTop = y * tileHeight; 275 realTileRect.fRight = realTileRect.fLeft + tileWidth; 276 realTileRect.fBottom = realTileRect.fTop + tileHeight; 277 278 // scale the dirtyRect for intersect computation. 279 SkRect realDirtyRect = SkRect::MakeWH(dirtyRect.width() * scale, 280 dirtyRect.height() * scale); 281 realDirtyRect.offset(dirtyRect.fLeft * scale, dirtyRect.fTop * scale); 282 283 if (!realTileRect.intersect(realDirtyRect)) 284 return false; 285 return true; 286 } 287 288 bool Tile::isTileVisible(const IntRect& viewTileBounds) 289 { 290 return (m_x >= viewTileBounds.x() 291 && m_x < viewTileBounds.x() + viewTileBounds.width() 292 && m_y >= viewTileBounds.y() 293 && m_y < viewTileBounds.y() + viewTileBounds.height()); 294 } 295 296 // This is called from the texture generation thread 297 void Tile::paintBitmap(TilePainter* painter, BaseRenderer* renderer) 298 { 299 // We acquire the values below atomically. This ensures that we are reading 300 // values correctly across cores. Further, once we have these values they 301 // can be updated by other threads without consequence. 302 m_atomicSync.lock(); 303 bool dirty = m_dirty; 304 TileTexture* texture = m_backTexture; 305 SkRegion dirtyArea = m_dirtyArea; 306 float scale = m_scale; 307 const int x = m_x; 308 const int y = m_y; 309 310 if (!dirty || !texture) { 311 m_atomicSync.unlock(); 312 return; 313 } 314 if (m_state != Unpainted) { 315 ALOGV("Warning: started painting tile %p, but was at state %d, ft %p bt %p", 316 this, m_state, m_frontTexture, m_backTexture); 317 } 318 m_state = PaintingStarted; 319 TextureInfo* textureInfo = texture->getTextureInfo(); 320 m_atomicSync.unlock(); 321 322 // at this point we can safely check the ownership (if the texture got 323 // transferred to another Tile under us) 324 if (texture->owner() != this) { 325 return; 326 } 327 328 // setup the common renderInfo fields; 329 TileRenderInfo renderInfo; 330 renderInfo.x = x; 331 renderInfo.y = y; 332 renderInfo.scale = scale; 333 renderInfo.tileSize = texture->getSize(); 334 renderInfo.tilePainter = painter; 335 renderInfo.baseTile = this; 336 renderInfo.textureInfo = textureInfo; 337 338 const float tileWidth = renderInfo.tileSize.width(); 339 const float tileHeight = renderInfo.tileSize.height(); 340 341 renderer->renderTiledContent(renderInfo); 342 343 m_atomicSync.lock(); 344 345 if (texture == m_backTexture) { 346 // set the fullrepaint flags 347 m_fullRepaint = false; 348 349 // The various checks to see if we are still dirty... 350 351 m_dirty = false; 352 353 if (m_scale != scale) 354 m_dirty = true; 355 356 m_dirtyArea.setEmpty(); 357 358 ALOGV("painted tile %p (%d, %d), texture %p, dirty=%d", this, x, y, texture, m_dirty); 359 360 validatePaint(); 361 } else { 362 ALOGV("tile %p no longer owns texture %p, m_state %d. ft %p bt %p", 363 this, texture, m_state, m_frontTexture, m_backTexture); 364 } 365 366 m_atomicSync.unlock(); 367 } 368 369 void Tile::discardTextures() { 370 android::AutoMutex lock(m_atomicSync); 371 ALOGV("%p discarding bt %p, ft %p", 372 this, m_backTexture, m_frontTexture); 373 if (m_frontTexture) { 374 m_frontTexture->release(this); 375 m_frontTexture = 0; 376 } 377 if (m_backTexture) { 378 m_backTexture->release(this); 379 m_backTexture = 0; 380 } 381 m_dirtyArea.setEmpty(); 382 m_fullRepaint = true; 383 384 m_dirty = true; 385 m_state = Unpainted; 386 } 387 388 void Tile::discardBackTexture() { 389 android::AutoMutex lock(m_atomicSync); 390 if (m_backTexture) { 391 m_backTexture->release(this); 392 m_backTexture = 0; 393 } 394 m_state = Unpainted; 395 m_dirty = true; 396 } 397 398 bool Tile::swapTexturesIfNeeded() { 399 android::AutoMutex lock(m_atomicSync); 400 if (m_state == ReadyToSwap) { 401 // discard old texture and swap the new one in its place 402 if (m_frontTexture) 403 m_frontTexture->release(this); 404 405 m_frontTexture = m_backTexture; 406 m_backTexture = 0; 407 m_state = UpToDate; 408 ALOGV("display texture for %p at %d, %d front is now %p, back is %p", 409 this, m_x, m_y, m_frontTexture, m_backTexture); 410 411 return true; 412 } 413 return false; 414 } 415 416 void Tile::backTextureTransfer() { 417 android::AutoMutex lock(m_atomicSync); 418 if (m_state == PaintingStarted) 419 m_state = TransferredUnvalidated; 420 else if (m_state == ValidatedUntransferred) 421 m_state = ReadyToSwap; 422 else { 423 // shouldn't have transferred a tile in any other state, log 424 ALOGV("Note: transferred tile %p at %d %d, state wasn't paintingstarted or validated: %d", 425 this, m_x, m_y, m_state); 426 } 427 } 428 429 void Tile::backTextureTransferFail() { 430 // transfer failed for some reason, mark dirty so it will (repaint and) be 431 // retransferred. 432 android::AutoMutex lock(m_atomicSync); 433 m_state = Unpainted; 434 m_dirty = true; 435 // whether validatePaint is called before or after, it won't do anything 436 } 437 438 void Tile::onBlitUpdate() 439 { 440 // The front texture was directly updated with a blit, so mark this as clean 441 android::AutoMutex lock(m_atomicSync); 442 m_dirty = false; 443 m_dirtyArea.setEmpty(); 444 m_state = Tile::UpToDate; 445 } 446 447 void Tile::validatePaint() { 448 // ONLY CALL while m_atomicSync is locked (at the end of paintBitmap()) 449 450 if (!m_dirty) { 451 // since after the paint, the tile isn't dirty, 'validate' it - this 452 // may happed before or after the transfer queue operation. Only 453 // when both have happened, mark as 'ReadyToSwap' 454 if (m_state == PaintingStarted) 455 m_state = ValidatedUntransferred; 456 else if (m_state == TransferredUnvalidated) { 457 // When the backTexture has been marked pureColor, we will skip the 458 // transfer and marked as ReadyToSwap, in this case, we don't want 459 // to reset m_dirty bit to true. 460 m_state = ReadyToSwap; 461 } else { 462 ALOGV("Note: validated tile %p at %d %d, state wasn't paintingstarted or transferred %d", 463 this, m_x, m_y, m_state); 464 // failed transferring, in which case mark dirty (since 465 // paintBitmap() may have cleared m_dirty) 466 m_dirty = true; 467 } 468 } else { 469 ALOGV("Note: paint was unsuccessful."); 470 m_state = Unpainted; 471 } 472 473 } 474 475 } // namespace WebCore 476 477 #endif // USE(ACCELERATED_COMPOSITING) 478