1 /* 2 * Copyright 2008, 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_NDEBUG 0 27 #define LOG_TAG "pictureset" 28 29 //#include <config.h> 30 #include "CachedPrefix.h" 31 #include "android_graphics.h" 32 #include "PictureSet.h" 33 #include "SkBounder.h" 34 #include "SkCanvas.h" 35 #include "SkPicture.h" 36 #include "SkRect.h" 37 #include "SkRegion.h" 38 #include "SkStream.h" 39 #include "TimeCounter.h" 40 41 #define MAX_DRAW_TIME 100 42 #define MIN_SPLITTABLE 400 43 #define MAX_ADDITIONAL_AREA 0.65 44 #define MAX_ADDITIONAL_PICTURES 32 45 46 #define BUCKET_SIZE 1024 47 #define MAX_BUCKET_COUNT_X 16 48 #define MAX_BUCKET_COUNT_Y 64 49 50 #include <wtf/CurrentTime.h> 51 52 #include <cutils/log.h> 53 #include <wtf/text/CString.h> 54 55 #undef XLOGC 56 #define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "PictureSet", __VA_ARGS__) 57 58 #ifdef DEBUG 59 60 #undef XLOG 61 #define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "PictureSet", __VA_ARGS__) 62 63 #else 64 65 #undef XLOG 66 #define XLOG(...) 67 68 #endif // DEBUG 69 70 #if PICTURE_SET_DEBUG 71 class MeasureStream : public SkWStream { 72 public: 73 MeasureStream() : mTotal(0) {} 74 virtual bool write(const void* , size_t size) { 75 mTotal += size; 76 return true; 77 } 78 size_t mTotal; 79 }; 80 #endif 81 82 namespace android { 83 84 PictureSet::PictureSet() : 85 #ifdef FAST_PICTURESET 86 mBucketSizeX(BUCKET_SIZE), mBucketSizeY(BUCKET_SIZE), 87 mBucketCountX(0), mBucketCountY(0), 88 #endif 89 mHeight(0), mWidth(0) 90 { 91 setDimensions(0, 0); 92 mBaseArea = mAdditionalArea = 0; 93 } 94 95 PictureSet::PictureSet(SkPicture* picture) : 96 #ifdef FAST_PICTURESET 97 mBucketSizeX(BUCKET_SIZE), mBucketSizeY(BUCKET_SIZE), 98 mBucketCountX(0), mBucketCountY(0), 99 #endif 100 mHeight(0), mWidth(0) 101 { 102 mBaseArea = mAdditionalArea = 0; 103 if (!picture) { 104 setDimensions(0, 0); 105 return; 106 } 107 setDimensions(picture->width(), picture->height()); 108 mBaseArea = mWidth * mHeight; 109 #ifdef FAST_PICTURESET 110 SkIRect area; 111 area.set(0, 0, mWidth, mHeight); 112 splitAdd(area); 113 WTF::Vector<Bucket*>* buckets = bucketsToUpdate(); 114 for (unsigned int i = 0; i < buckets->size(); i++) { 115 Bucket* bucket = (*buckets)[i]; 116 for (unsigned int j = 0; j < bucket->size(); j++) { 117 BucketPicture& bucketPicture = (*bucket)[j]; 118 const SkIRect& inval = bucketPicture.mRealArea; 119 SkPicture *splitPicture = new SkPicture(); 120 SkCanvas *canvas = splitPicture->beginRecording( 121 inval.width(), inval.height(), 122 SkPicture::kUsePathBoundsForClip_RecordingFlag); 123 canvas->translate(-inval.fLeft, -inval.fTop); 124 picture->draw(canvas); 125 splitPicture->endRecording(); 126 SkSafeUnref(bucketPicture.mPicture); 127 bucketPicture.mPicture = splitPicture; 128 } 129 } 130 buckets->clear(); 131 #else 132 Pictures pictureAndBounds; 133 pictureAndBounds.mPicture = picture; 134 SkSafeRef(pictureAndBounds.mPicture); 135 pictureAndBounds.mEmpty = false; 136 pictureAndBounds.mArea.setRect(0, 0, mWidth, mHeight); 137 pictureAndBounds.mSplit = false; 138 pictureAndBounds.mBase = true; 139 pictureAndBounds.mElapsed = 0; 140 pictureAndBounds.mWroteElapsed = false; 141 mPictures.append(pictureAndBounds); 142 #endif // FAST_PICTURESET 143 } 144 145 PictureSet::~PictureSet() 146 { 147 clear(); 148 } 149 150 #ifdef FAST_PICTURESET 151 #else 152 void PictureSet::add(const Pictures* temp) 153 { 154 Pictures pictureAndBounds = *temp; 155 SkSafeRef(pictureAndBounds.mPicture); 156 pictureAndBounds.mWroteElapsed = false; 157 mPictures.append(pictureAndBounds); 158 } 159 #endif // FAST_PICTURESET 160 161 void PictureSet::add(const SkRegion& area, SkPicture* picture, 162 uint32_t elapsed, bool split) 163 { 164 if (area.isRect()) { 165 #ifdef FAST_PICTURESET 166 splitAdd(area.getBounds()); 167 #else 168 add(area, picture, elapsed, split, false); 169 #endif // FAST_PICTURESET 170 } else { 171 SkRegion::Iterator cliperator(area); 172 while (!cliperator.done()) { 173 SkIRect ir = cliperator.rect(); 174 #ifdef FAST_PICTURESET 175 splitAdd(ir); 176 #else 177 SkRegion newArea; 178 newArea.setRect(ir); 179 add(newArea, picture, elapsed, split, false); 180 #endif // FAST_PICTURESET 181 cliperator.next(); 182 } 183 } 184 } 185 186 #ifdef FAST_PICTURESET 187 188 Bucket* PictureSet::getBucket(int x, int y) 189 { 190 // only create buckets for valid, positive coordinates, ignore and return 191 // NULL otherwise 192 if (x < 0 || y < 0) 193 return 0; 194 195 BucketPosition position(x+1, y+1); 196 if (!mBuckets.contains(position)) { 197 XLOG("PictureSet::getBucket(%d, %d) adding new bucket", x, y); 198 Bucket* bucket = new Bucket(); 199 mBuckets.add(position, bucket); 200 } 201 return mBuckets.get(position); 202 } 203 204 void PictureSet::displayBucket(Bucket* bucket) 205 { 206 BucketPicture* first = bucket->begin(); 207 BucketPicture* last = bucket->end(); 208 for (BucketPicture* current = first; current != last; current++) { 209 XLOGC("- in %x, bucketPicture %d,%d,%d,%d - %dx%d, picture: %x, base: %x", 210 bucket, 211 current->mArea.fLeft, 212 current->mArea.fTop, 213 current->mArea.fRight, 214 current->mArea.fBottom, 215 current->mArea.width(), 216 current->mArea.height(), 217 current->mPicture, 218 current->mBase); 219 } 220 } 221 222 void PictureSet::displayBuckets() 223 { 224 XLOGC("\n\n****** DISPLAY BUCKETS ON PictureSet %x ******", this); 225 for (BucketMap::iterator iter = mBuckets.begin(); iter != mBuckets.end(); ++iter) { 226 XLOGC("\n*** Bucket %x for %d, %d", iter->second, iter->first.first, iter->first.second); 227 displayBucket(iter->second); 228 } 229 XLOGC("\n****** END OF DISPLAY BUCKETS ******\n\n"); 230 } 231 232 // When we receive an inval in a Bucket, we try to see if we intersect with 233 // existing invals/pictures in the Bucket. 234 void PictureSet::addToBucket(Bucket* bucket, int dx, int dy, SkIRect& rect) 235 { 236 bool resetBase = false; 237 238 SkIRect totalArea = rect; 239 BucketPicture* first = bucket->begin(); 240 BucketPicture* last = bucket->end(); 241 242 // If the inval covers a large area of the base inval, let's repaint the 243 // entire bucket. 244 if (rect.width() * rect.height() > MAX_ADDITIONAL_AREA * mBucketSizeX * mBucketSizeY) 245 resetBase = true; 246 247 // let's gather all the BucketPicture intersecting with the new invalidated 248 // area, collect their area and remove their picture 249 for (BucketPicture* current = first; current != last; current++) { 250 bool remove = resetBase; 251 bool intersect = false; 252 253 if (!remove) 254 intersect = SkIRect::Intersects(current->mArea, rect); 255 // If the current picture is not a base, and we intersect, remove it 256 if (!remove && !current->mBase && intersect) 257 remove = true; 258 // If the current picture is a base, check if the new inval completely 259 // contains the base, and if so remove it. 260 if (!remove && current->mBase && rect.contains(current->mArea)) 261 remove = true; 262 // If the current picture is a base and it intersects, 263 // also check that it fully covers the bucket -- otherwise, 264 // let's aggregate it with the new inval. 265 if (!remove && current->mBase && intersect 266 && (current->mArea.width() < mBucketSizeX || current->mArea.height() < mBucketSizeY)) { 267 remove = true; 268 } 269 270 if (remove) { 271 totalArea.join(current->mArea); 272 current->mBase = false; 273 current->mArea.setEmpty(); 274 SkSafeUnref(current->mPicture); 275 current->mPicture = 0; 276 } 277 } 278 279 // Now, let's add the new BucketPicture to the list, with the correct 280 // area that needs to be repainted 281 SkRegion region; 282 SkIRect area = totalArea; 283 area.offset(dx, dy); 284 BucketPicture picture = { 0, totalArea, area, false }; 285 286 bucket->append(picture); 287 288 first = bucket->begin(); 289 last = bucket->end(); 290 291 bool clearUp = false; 292 if (last - first > MAX_ADDITIONAL_PICTURES) { 293 // too many pictures in the bucket, let's collapse 294 clearUp = true; 295 } 296 297 float bucketBaseArea = 0; 298 float bucketAdditionalArea = 0; 299 for (BucketPicture* current = first; current != last; current++) { 300 float area = current->mArea.width() * current->mArea.height(); 301 if (current->mBase) 302 bucketBaseArea += area; 303 else 304 bucketAdditionalArea += area; 305 } 306 307 if (bucketBaseArea > 0 && bucketBaseArea * MAX_ADDITIONAL_AREA <= bucketAdditionalArea) { 308 // additional area too large, not worth maintaining 309 clearUp = true; 310 } 311 312 // To clear things up, we just need to mark the pictures' area as empty 313 // We only keep the base surface. 314 if (clearUp) { 315 for (BucketPicture* current = first; current != last; current++) { 316 if (!current->mBase) 317 current->mArea.setEmpty(); 318 SkSafeUnref(current->mPicture); 319 current->mPicture = 0; 320 } 321 } 322 323 // let's do a pass to collapse out empty areas 324 BucketPicture* writer = first; 325 for (BucketPicture* current = first; current != last; current++) { 326 if (current && current->mArea.isEmpty()) 327 continue; 328 *writer++ = *current; 329 } 330 331 bucket->shrink(writer - first); 332 333 // let's recompute the bases 334 first = bucket->begin(); 335 last = bucket->end(); 336 SkRegion drawn; 337 drawn.setEmpty(); 338 for (BucketPicture* current = first; current != last; current++) { 339 if (drawn.contains(current->mArea) == false) { 340 current->mBase = true; 341 } 342 drawn.op(current->mArea, SkRegion::kUnion_Op); 343 } 344 } 345 346 void PictureSet::gatherBucketsForArea(WTF::Vector<Bucket*>& list, const SkIRect& rect) 347 { 348 XLOG("\n--- gatherBucketsForArea for rect %d, %d, %d, %d (%d x %d)", 349 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, 350 rect.width(), rect.height()); 351 352 if (!mBucketSizeX || !mBucketSizeY) { 353 XLOGC("PictureSet::gatherBucketsForArea() called with bad bucket size: x=%d y=%d", 354 mBucketSizeX, mBucketSizeY); 355 return; 356 } 357 358 int x = rect.fLeft; 359 int y = rect.fTop; 360 int firstTileX = rect.fLeft / mBucketSizeX; 361 int firstTileY = rect.fTop / mBucketSizeY; 362 int lastTileX = rect.fRight / mBucketSizeX; 363 int lastTileY = rect.fBottom / mBucketSizeY; 364 365 for (int i = firstTileX; i <= lastTileX; i++) { 366 for (int j = firstTileY; j <= lastTileY; j++) { 367 Bucket* bucket = getBucket(i, j); 368 XLOG("gather bucket %x for %d, %d", bucket, i+1, j+1); 369 if (bucket) 370 list.append(bucket); 371 } 372 } 373 } 374 375 // When we receive a new inval rect, we first find the Buckets that intersect 376 // with it; then we split the original inval into a serie of invals (one for 377 // each Bucket we intersect with). We then send that inval to the Bucket. 378 void PictureSet::splitAdd(const SkIRect& rect) 379 { 380 XLOG("\n--- splitAdd for rect %d, %d, %d, %d (%d x %d)", 381 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, 382 rect.width(), rect.height()); 383 384 if (!mBucketSizeX || !mBucketSizeY) { 385 XLOGC("PictureSet::splitAdd() called with bad bucket size: x=%d y=%d", 386 mBucketSizeX, mBucketSizeY); 387 return; 388 } 389 390 // TODO: reuse gatherBucketsForArea() (change Bucket to be a class) 391 int x = rect.fLeft; 392 int y = rect.fTop; 393 int firstTileX = rect.fLeft / mBucketSizeX; 394 int firstTileY = rect.fTop / mBucketSizeY; 395 int lastTileX = rect.fRight / mBucketSizeX; 396 int lastTileY = rect.fBottom / mBucketSizeY; 397 398 XLOG("--- firstTile(%d, %d) lastTile(%d, %d)", 399 firstTileX, firstTileY, 400 lastTileX, lastTileY); 401 402 for (int i = firstTileX; i <= lastTileX; i++) { 403 for (int j = firstTileY; j <= lastTileY; j++) { 404 Bucket* bucket = getBucket(i, j); 405 if (!bucket) 406 continue; 407 408 SkIRect newRect; 409 int deltaX = i * mBucketSizeX; 410 int deltaY = j * mBucketSizeY; 411 int left = (i == firstTileX) ? rect.fLeft - deltaX : 0; 412 int top = (j == firstTileY) ? rect.fTop - deltaY : 0; 413 int right = (i == lastTileX) ? rect.fRight % mBucketSizeX : mBucketSizeX; 414 int bottom = (j == lastTileY) ? rect.fBottom % mBucketSizeY : mBucketSizeY; 415 416 newRect.set(left, top, right, bottom); 417 addToBucket(bucket, deltaX, deltaY, newRect); 418 mUpdatedBuckets.append(bucket); 419 } 420 } 421 422 XLOG("--- splitAdd DONE\n"); 423 } 424 425 #endif // FAST_PICTURESET 426 427 // This function is used to maintain the list of Pictures. 428 // Pictures contain an SkPicture covering a specific area; some 429 // Pictures are "base" Pictures -- i.e. there is no Pictures 430 // underneath them. 431 // The idea here is to keep a balance between the number of Pictures 432 // we have (more Pictures slow us down) and the area of Pictures that 433 // need to be repainted (obviously, smaller areas are better). 434 // To do so, we try to not update/repaint the base pictures -- by 435 // construction, they usually cover a large area (the entire page). 436 // We only reset a base picture if the new invalidated area entirely 437 // contains it. 438 // Most of the time we thus work on smaller pictures on top of the 439 // base ones; We compute the total area of all pictures intersecting 440 // with the passed invalidated area (as they would need to be invalidated), 441 // and use that as the basis for the correct area we want to invalidate 442 // (we then can simply delete the pictures we intersect with). 443 // In addition, we do a couple of things to limit the total number of pictures 444 // we keep in the list: 445 // - if the total area of additional textures reach 65% of the base pictures, 446 // we delete the additional pictures and mark the base pictures as 447 // needing a full repaint 448 // - we limit the number of pictures to 32 -- above that, we do the same 449 // things (deleting additional pictures + full repaint of base pictures) 450 #ifdef FAST_PICTURESET 451 #else 452 void PictureSet::add(const SkRegion& area, SkPicture* picture, 453 uint32_t elapsed, bool split, bool empty) 454 { 455 bool checkForNewBases = false; 456 457 Pictures* first = mPictures.begin(); 458 Pictures* last = mPictures.end(); 459 #ifdef DEBUG 460 XLOG("--- before adding the new inval ---"); 461 for (Pictures* working = mPictures.begin(); working != mPictures.end(); working++) { 462 SkIRect currentArea = working->mArea.getBounds(); 463 XLOG("picture %d (%d, %d, %d, %d - %d x %d) (isRect? %c) base: %c", 464 working - first, 465 currentArea.fLeft, currentArea.fTop, currentArea.fRight, currentArea.fBottom, 466 currentArea.width(), currentArea.height(), 467 working->mArea.isRect() ? 'Y' : 'N', 468 working->mBase ? 'Y' : 'N'); 469 } 470 XLOG("----------------------------------"); 471 #endif 472 473 // let's gather all the Pictures intersecting with the new invalidated 474 // area, collect their area and remove their picture 475 SkIRect totalArea = area.getBounds(); 476 for (Pictures* working = first; working != last; working++) { 477 SkIRect inval = area.getBounds(); 478 bool remove = false; 479 if (!working->mBase && working->mArea.intersects(inval)) 480 remove = true; 481 if (working->mBase) { 482 SkIRect baseArea = working->mArea.getBounds(); 483 if (area.contains(baseArea)) { 484 remove = true; 485 checkForNewBases = true; 486 } 487 } 488 489 if (remove) { 490 SkIRect currentArea = working->mArea.getBounds(); 491 if (working->mBase) 492 mBaseArea -= currentArea.width() * currentArea.height(); 493 else 494 mAdditionalArea -= currentArea.width() * currentArea.height(); 495 496 totalArea.join(currentArea); 497 XLOG("picture %d (%d, %d, %d, %d - %d x %d) (isRect? %c) intersects with the new inval area (%d, %d, %d, %d - %d x %d) (isRect? %c, we remove it", 498 working - first, 499 currentArea.fLeft, currentArea.fTop, currentArea.fRight, currentArea.fBottom, 500 currentArea.width(), currentArea.height(), 501 working->mArea.isRect() ? 'Y' : 'N', 502 inval.fLeft, inval.fTop, inval.fRight, inval.fBottom, 503 inval.width(), inval.height(), 504 area.isRect() ? 'Y' : 'N'); 505 working->mArea.setEmpty(); 506 SkSafeUnref(working->mPicture); 507 working->mPicture = 0; 508 } 509 } 510 511 // Now we can add the new Picture to the list, with the correct area 512 // that need to be repainted 513 SkRegion collect; 514 collect.setRect(totalArea); 515 Pictures pictureAndBounds = {collect, 0, collect.getBounds(), 516 elapsed, split, false, false, empty}; 517 518 #ifdef FAST_PICTURESET 519 if (mPictures.size() == 0) 520 checkForNewBases = true; 521 #endif 522 523 mPictures.append(pictureAndBounds); 524 mAdditionalArea += totalArea.width() * totalArea.height(); 525 last = mPictures.end(); 526 first = mPictures.begin(); 527 528 // Then, let's see if we have to clear up the pictures in order to keep 529 // the total number of pictures under our limit 530 bool clearUp = false; 531 if (last - first > MAX_ADDITIONAL_PICTURES) { 532 XLOG("--- too many pictures, only keeping the bases : %d", last - first); 533 clearUp = true; 534 } 535 536 if (!clearUp) { 537 if (mBaseArea > 0 && mBaseArea * MAX_ADDITIONAL_AREA <= mAdditionalArea) { 538 XLOG("+++ the sum of the additional area is > %.2f\% of the base Area (%.2f (%.2f) <= %.2f", 539 MAX_ADDITIONAL_AREA * 100, mBaseArea * 0.65, mBaseArea, mAdditionalArea); 540 clearUp = true; 541 } 542 } 543 544 if (clearUp) { 545 for (Pictures* working = mPictures.begin(); working != mPictures.end(); working++) { 546 if (!working->mBase) 547 working->mArea.setEmpty(); 548 SkSafeUnref(working->mPicture); 549 working->mPicture = 0; 550 } 551 } 552 553 #ifdef DEBUG 554 XLOG("--- after adding the new inval, but before collapsing ---"); 555 for (Pictures* working = mPictures.begin(); working != mPictures.end(); working++) { 556 SkIRect currentArea = working->mArea.getBounds(); 557 XLOG("picture %d (%d, %d, %d, %d - %d x %d) (isRect? %c) base: %c", 558 working - first, 559 currentArea.fLeft, currentArea.fTop, currentArea.fRight, currentArea.fBottom, 560 currentArea.width(), currentArea.height(), 561 working->mArea.isRect() ? 'Y' : 'N', 562 working->mBase ? 'Y' : 'N'); 563 } 564 XLOG("----------------------------------"); 565 XLOG("let's collapse..."); 566 #endif 567 568 // Finally, let's do a pass to collapse out empty regions 569 Pictures* writer = first; 570 for (Pictures* working = first; working != last; working++) { 571 if (working && working->mArea.isEmpty()) 572 continue; 573 *writer++ = *working; 574 } 575 XLOG("shiking of %d elements", writer - first); 576 mPictures.shrink(writer - first); 577 578 #ifdef DEBUG 579 XLOG("--- after adding the new inval ---"); 580 for (Pictures* working = mPictures.begin(); working != mPictures.end(); working++) { 581 SkIRect currentArea = working->mArea.getBounds(); 582 XLOG("picture %d (%d, %d, %d, %d - %d x %d) (isRect? %c) base: %c picture %x", 583 working - first, 584 currentArea.fLeft, currentArea.fTop, currentArea.fRight, currentArea.fBottom, 585 currentArea.width(), currentArea.height(), 586 working->mArea.isRect() ? 'Y' : 'N', 587 working->mBase ? 'Y' : 'N', working->mPicture); 588 } 589 XLOG("----------------------------------"); 590 #endif 591 592 // Base pictures might have been removed/added -- let's recompute them 593 SkRegion drawn; 594 if (checkForNewBases) { 595 drawn.setEmpty(); 596 Pictures* last = mPictures.end(); 597 XLOG("checkForNewBases..."); 598 for (Pictures* working = mPictures.begin(); working != last; working++) { 599 SkRegion& area = working->mArea; 600 const SkIRect& a = area.getBounds(); 601 if (drawn.contains(working->mArea) == false) { 602 working->mBase = true; 603 float area = a.width() * a.height(); 604 mBaseArea += area; 605 mAdditionalArea -= area; 606 } 607 drawn.op(working->mArea, SkRegion::kUnion_Op); 608 } 609 } 610 } 611 #endif // FAST_PICTURESET 612 613 void PictureSet::setDimensions(int width, int height, SkRegion* inval) 614 { 615 // Note that setDimensions() may be called by our ctor and should behave accordingly 616 if (mWidth == width && mHeight == height) 617 return; 618 DBG_SET_LOGD("%p old:(w=%d,h=%d) new:(w=%d,h=%d)", this, 619 mWidth, mHeight, width, height); 620 bool clearCache = false; 621 if (inval) { 622 if (mWidth == width && height > mHeight) { // only grew vertically 623 SkIRect rect; 624 rect.set(0, mHeight, width, height); 625 inval->op(rect, SkRegion::kUnion_Op); 626 } else { 627 clearCache = true; 628 inval->setRect(0, 0, width, height); 629 } 630 } 631 #ifdef FAST_PICTURESET 632 // First figure out how large each bucket would be if we used all of the buckets 633 int tmpSizeX = (width + MAX_BUCKET_COUNT_X - 1) / MAX_BUCKET_COUNT_X; 634 int tmpSizeY = (height + MAX_BUCKET_COUNT_Y - 1) / MAX_BUCKET_COUNT_Y; 635 636 // Then round the bucket size up to the nearest chunk 637 int bucketSizeX = ((tmpSizeX - 1) / BUCKET_SIZE + 1) * BUCKET_SIZE; 638 int bucketSizeY = ((tmpSizeY - 1) / BUCKET_SIZE + 1) * BUCKET_SIZE; 639 640 int bucketCountX = (width + bucketSizeX - 1) / bucketSizeX; 641 int bucketCountY = (height + bucketSizeY - 1) / bucketSizeY; 642 643 // Clear the cache if the horizontal bucket count changed or the vertical 644 // count shrank 645 if (bucketCountX != mBucketCountX || bucketCountY < mBucketCountY) 646 clearCache = true; 647 648 // Or if the bucket size changed 649 if (bucketSizeX != mBucketSizeX || bucketSizeY != mBucketSizeY) 650 clearCache = true; 651 652 XLOG("old width=%d height=%d bucketSizeX=%d bucketSizeY=%d bucketCountX=%d bucketCountY=%d clearCache=%d", 653 mWidth, mHeight, mBucketSizeX, mBucketSizeY, mBucketCountX, mBucketCountY, clearCache); 654 XLOG("new width=%d height=%d bucketSizeX=%d bucketSizeY=%d bucketCountX=%d bucketCountY=%d clearCache=%d", 655 width, height, bucketSizeX, bucketSizeY, bucketCountX, bucketCountY, clearCache); 656 #endif 657 if (clearCache) 658 clear(); 659 mWidth = width; 660 mHeight = height; 661 #ifdef FAST_PICTURESET 662 mBucketSizeX = bucketSizeX; 663 mBucketSizeY = bucketSizeY; 664 mBucketCountX = bucketCountX; 665 mBucketCountY = bucketCountY; 666 #endif 667 } 668 669 void PictureSet::clear() 670 { 671 DBG_SET_LOG(""); 672 #ifdef FAST_PICTURESET 673 for (BucketMap::iterator iter = mBuckets.begin(); iter != mBuckets.end(); ++iter) { 674 Bucket* bucket = iter->second; 675 BucketPicture* first = bucket->begin(); 676 BucketPicture* last = bucket->end(); 677 for (BucketPicture* current = first; current != last; current++) { 678 SkSafeUnref(current->mPicture); 679 current->mPicture = 0; 680 } 681 bucket->clear(); 682 } 683 mBuckets.clear(); 684 mBucketSizeX = mBucketSizeY = BUCKET_SIZE; 685 #else 686 Pictures* last = mPictures.end(); 687 for (Pictures* working = mPictures.begin(); working != last; working++) { 688 working->mArea.setEmpty(); 689 SkSafeUnref(working->mPicture); 690 } 691 mPictures.clear(); 692 #endif // FAST_PICTURESET 693 mWidth = mHeight = 0; 694 } 695 696 bool PictureSet::draw(SkCanvas* canvas) 697 { 698 #ifdef FAST_PICTURESET 699 XLOG("PictureSet %x draw on canvas %x", this, canvas); 700 SkRect bounds; 701 if (canvas->getClipBounds(&bounds) == false) 702 return false; 703 SkIRect irect; 704 bounds.roundOut(&irect); 705 706 WTF::Vector<Bucket*> list; 707 gatherBucketsForArea(list, irect); 708 709 XLOG("PictureSet draw on canvas %x, we have %d buckets", canvas, list.size()); 710 for (unsigned int i = 0; i < list.size(); i++) { 711 Bucket* bucket = list[i]; 712 XLOG("We paint using bucket %x with %d pictures", bucket, bucket->size()); 713 for (unsigned int j = 0; j < bucket->size(); j++) { 714 BucketPicture& picture = bucket->at(j); 715 if (!picture.mPicture) 716 continue; 717 int saved = canvas->save(); 718 SkRect pathBounds; 719 pathBounds.set(picture.mRealArea); 720 XLOG("[%d/%d] draw on canvas with clip %d, %d, %d, %d - %d x %d", 721 j, bucket->size(), 722 picture.mRealArea.fLeft, 723 picture.mRealArea.fTop, 724 picture.mRealArea.fRight, 725 picture.mRealArea.fBottom, 726 picture.mRealArea.width(), 727 picture.mRealArea.height()); 728 canvas->clipRect(pathBounds); 729 canvas->translate(pathBounds.fLeft, pathBounds.fTop); 730 canvas->save(); 731 canvas->drawPicture(*picture.mPicture); 732 canvas->restoreToCount(saved); 733 } 734 } 735 return false; 736 737 #else 738 739 validate(__FUNCTION__); 740 Pictures* first = mPictures.begin(); 741 Pictures* last = mPictures.end(); 742 Pictures* working; 743 SkRect bounds; 744 if (canvas->getClipBounds(&bounds) == false) 745 return false; 746 SkIRect irect; 747 bounds.roundOut(&irect); 748 for (working = last; working != first; ) { 749 --working; 750 if (working->mArea.contains(irect)) { 751 #if PICTURE_SET_DEBUG 752 const SkIRect& b = working->mArea.getBounds(); 753 DBG_SET_LOGD("contains working->mArea={%d,%d,%d,%d}" 754 " irect={%d,%d,%d,%d}", b.fLeft, b.fTop, b.fRight, b.fBottom, 755 irect.fLeft, irect.fTop, irect.fRight, irect.fBottom); 756 #endif 757 first = working; 758 break; 759 } 760 } 761 DBG_SET_LOGD("%p first=%d last=%d", this, first - mPictures.begin(), 762 last - mPictures.begin()); 763 uint32_t maxElapsed = 0; 764 for (working = first; working != last; working++) { 765 const SkRegion& area = working->mArea; 766 if (area.quickReject(irect)) { 767 #if PICTURE_SET_DEBUG 768 const SkIRect& b = area.getBounds(); 769 DBG_SET_LOGD("[%d] %p quickReject working->mArea={%d,%d,%d,%d}" 770 " irect={%d,%d,%d,%d}", working - first, working, 771 b.fLeft, b.fTop, b.fRight, b.fBottom, 772 irect.fLeft, irect.fTop, irect.fRight, irect.fBottom); 773 #endif 774 working->mElapsed = 0; 775 continue; 776 } 777 int saved = canvas->save(); 778 SkRect pathBounds; 779 if (area.isComplex()) { 780 SkPath pathClip; 781 area.getBoundaryPath(&pathClip); 782 canvas->clipPath(pathClip); 783 pathBounds = pathClip.getBounds(); 784 } else { 785 pathBounds.set(area.getBounds()); 786 canvas->clipRect(pathBounds); 787 } 788 canvas->translate(pathBounds.fLeft, pathBounds.fTop); 789 canvas->save(); 790 uint32_t startTime = getThreadMsec(); 791 canvas->drawPicture(*working->mPicture); 792 size_t elapsed = working->mElapsed = getThreadMsec() - startTime; 793 working->mWroteElapsed = true; 794 if (maxElapsed < elapsed && (pathBounds.width() >= MIN_SPLITTABLE || 795 pathBounds.height() >= MIN_SPLITTABLE)) 796 maxElapsed = elapsed; 797 canvas->restoreToCount(saved); 798 #define DRAW_TEST_IMAGE 01 799 #if DRAW_TEST_IMAGE && PICTURE_SET_DEBUG 800 SkColor color = 0x3f000000 | (0xffffff & (unsigned) working); 801 canvas->drawColor(color); 802 SkPaint paint; 803 color ^= 0x00ffffff; 804 paint.setColor(color); 805 char location[256]; 806 for (int x = area.getBounds().fLeft & ~0x3f; 807 x < area.getBounds().fRight; x += 0x40) { 808 for (int y = area.getBounds().fTop & ~0x3f; 809 y < area.getBounds().fBottom; y += 0x40) { 810 int len = snprintf(location, sizeof(location) - 1, "(%d,%d)", x, y); 811 canvas->drawText(location, len, x, y, paint); 812 } 813 } 814 #endif 815 DBG_SET_LOGD("[%d] %p working->mArea={%d,%d,%d,%d} elapsed=%d base=%s", 816 working - first, working, 817 area.getBounds().fLeft, area.getBounds().fTop, 818 area.getBounds().fRight, area.getBounds().fBottom, 819 working->mElapsed, working->mBase ? "true" : "false"); 820 } 821 // dump(__FUNCTION__); 822 return maxElapsed >= MAX_DRAW_TIME; 823 #endif // FAST_PICTURESET 824 } 825 826 void PictureSet::dump(const char* label) const 827 { 828 #if PICTURE_SET_DUMP 829 DBG_SET_LOGD("%p %s (%d) (w=%d,h=%d)", this, label, mPictures.size(), 830 mWidth, mHeight); 831 const Pictures* last = mPictures.end(); 832 for (const Pictures* working = mPictures.begin(); working != last; working++) { 833 const SkIRect& bounds = working->mArea.getBounds(); 834 const SkIRect& unsplit = working->mUnsplit; 835 MeasureStream measure; 836 if (working->mPicture != NULL) 837 working->mPicture->serialize(&measure); 838 LOGD(" [%d]" 839 " mArea.bounds={%d,%d,r=%d,b=%d}" 840 " mPicture=%p" 841 " mUnsplit={%d,%d,r=%d,b=%d}" 842 " mElapsed=%d" 843 " mSplit=%s" 844 " mWroteElapsed=%s" 845 " mBase=%s" 846 " pict-size=%d", 847 working - mPictures.begin(), 848 bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 849 working->mPicture, 850 unsplit.fLeft, unsplit.fTop, unsplit.fRight, unsplit.fBottom, 851 working->mElapsed, working->mSplit ? "true" : "false", 852 working->mWroteElapsed ? "true" : "false", 853 working->mBase ? "true" : "false", 854 measure.mTotal); 855 } 856 #endif 857 } 858 859 class IsEmptyBounder : public SkBounder { 860 virtual bool onIRect(const SkIRect& rect) { 861 return false; 862 } 863 }; 864 865 class IsEmptyCanvas : public SkCanvas { 866 public: 867 IsEmptyCanvas(SkBounder* bounder, SkPicture* picture) : 868 mPicture(picture), mEmpty(true) { 869 setBounder(bounder); 870 } 871 872 void notEmpty() { 873 mEmpty = false; 874 mPicture->abortPlayback(); 875 } 876 877 virtual bool clipPath(const SkPath&, SkRegion::Op) { 878 // this can be expensive to actually do, and doesn't affect the 879 // question of emptiness, so we make it a no-op 880 return true; 881 } 882 883 virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect, 884 const SkMatrix& , const SkPaint& ) { 885 if (bitmap.width() <= 1 || bitmap.height() <= 1) 886 return; 887 DBG_SET_LOGD("abort {%d,%d}", bitmap.width(), bitmap.height()); 888 notEmpty(); 889 } 890 891 virtual void drawPaint(const SkPaint& paint) { 892 } 893 894 virtual void drawPath(const SkPath& , const SkPaint& paint) { 895 DBG_SET_LOG("abort"); 896 notEmpty(); 897 } 898 899 virtual void drawPoints(PointMode , size_t , const SkPoint [], 900 const SkPaint& paint) { 901 } 902 903 virtual void drawRect(const SkRect& , const SkPaint& paint) { 904 // wait for visual content 905 if (paint.getColor() != SK_ColorWHITE) 906 notEmpty(); 907 } 908 909 virtual void drawSprite(const SkBitmap& , int , int , 910 const SkPaint* paint = NULL) { 911 DBG_SET_LOG("abort"); 912 notEmpty(); 913 } 914 915 virtual void drawText(const void* , size_t byteLength, SkScalar , 916 SkScalar , const SkPaint& paint) { 917 DBG_SET_LOGD("abort %d", byteLength); 918 notEmpty(); 919 } 920 921 virtual void drawPosText(const void* , size_t byteLength, 922 const SkPoint [], const SkPaint& paint) { 923 DBG_SET_LOGD("abort %d", byteLength); 924 notEmpty(); 925 } 926 927 virtual void drawPosTextH(const void* , size_t byteLength, 928 const SkScalar [], SkScalar , 929 const SkPaint& paint) { 930 DBG_SET_LOGD("abort %d", byteLength); 931 notEmpty(); 932 } 933 934 virtual void drawTextOnPath(const void* , size_t byteLength, 935 const SkPath& , const SkMatrix* , 936 const SkPaint& paint) { 937 DBG_SET_LOGD("abort %d", byteLength); 938 notEmpty(); 939 } 940 941 virtual void drawPicture(SkPicture& picture) { 942 SkCanvas::drawPicture(picture); 943 } 944 945 SkPicture* mPicture; 946 bool mEmpty; 947 }; 948 949 bool PictureSet::emptyPicture(SkPicture* picture) const 950 { 951 IsEmptyBounder isEmptyBounder; 952 IsEmptyCanvas checker(&isEmptyBounder, picture); 953 SkBitmap bitmap; 954 bitmap.setConfig(SkBitmap::kARGB_8888_Config, mWidth, mHeight); 955 checker.setBitmapDevice(bitmap); 956 checker.drawPicture(*picture); 957 return checker.mEmpty; 958 } 959 960 bool PictureSet::isEmpty() const 961 { 962 #ifdef FAST_PICTURESET 963 // For now, just assume the pictureset is *not* empty 964 // if the hashmap contains something 965 for (BucketMap::const_iterator iter = mBuckets.begin(); iter != mBuckets.end(); ++iter) { 966 if (iter->second->size() > 0) 967 return false; 968 } 969 return true; 970 #else 971 const Pictures* last = mPictures.end(); 972 for (const Pictures* working = mPictures.begin(); working != last; working++) { 973 if (!working->mEmpty) 974 return false; 975 } 976 return true; 977 #endif // FAST_PICTURESET 978 } 979 980 void PictureSet::set(const PictureSet& src) 981 { 982 DBG_SET_LOGD("start %p src=%p", this, &src); 983 clear(); 984 setDimensions(src.mWidth, src.mHeight); 985 #ifdef FAST_PICTURESET 986 XLOG("\n--- set picture ---"); 987 for (BucketMap::const_iterator iter = src.mBuckets.begin(); 988 iter != src.mBuckets.end(); ++iter) { 989 Bucket* sourceBucket = iter->second; 990 Bucket* targetBucket = getBucket(iter->first.first-1, iter->first.second-1); 991 BucketPicture* first = sourceBucket->begin(); 992 BucketPicture* last = sourceBucket->end(); 993 XLOG("set from bucket %x (%d, %d), %d pictures", sourceBucket, 994 iter->first.first, iter->first.second, sourceBucket->size()); 995 for (BucketPicture* current = first; current != last; current++) { 996 XLOG("set picture %x from bucket %x in bucket %x (%d, %d)", 997 current->mPicture, sourceBucket, targetBucket, 998 iter->first.first, iter->first.second); 999 SkSafeRef(current->mPicture); 1000 BucketPicture picture = { current->mPicture, current->mArea, 1001 current->mRealArea, current->mBase }; 1002 targetBucket->append(picture); 1003 } 1004 } 1005 XLOG("--- DONE set picture ---\n"); 1006 #else 1007 const Pictures* last = src.mPictures.end(); 1008 for (const Pictures* working = src.mPictures.begin(); working != last; working++) 1009 add(working); 1010 // dump(__FUNCTION__); 1011 validate(__FUNCTION__); 1012 DBG_SET_LOG("end"); 1013 #endif // FAST_PICTURESET 1014 } 1015 1016 #ifdef FAST_PICTURESET 1017 #else 1018 1019 bool PictureSet::reuseSubdivided(const SkRegion& inval) 1020 { 1021 validate(__FUNCTION__); 1022 1023 if (inval.isComplex()) 1024 return false; 1025 Pictures* working, * last = mPictures.end(); 1026 const SkIRect& invalBounds = inval.getBounds(); 1027 bool steal = false; 1028 for (working = mPictures.begin(); working != last; working++) { 1029 if (working->mSplit && invalBounds == working->mUnsplit) { 1030 steal = true; 1031 continue; 1032 } 1033 if (steal == false) 1034 continue; 1035 SkRegion temp = SkRegion(inval); 1036 temp.op(working->mArea, SkRegion::kIntersect_Op); 1037 if (temp.isEmpty() || temp == working->mArea) 1038 continue; 1039 return false; 1040 } 1041 if (steal == false) 1042 return false; 1043 for (working = mPictures.begin(); working != last; working++) { 1044 if ((working->mSplit == false || invalBounds != working->mUnsplit) && 1045 inval.contains(working->mArea) == false) 1046 continue; 1047 SkSafeUnref(working->mPicture); 1048 working->mPicture = NULL; 1049 } 1050 return true; 1051 } 1052 1053 void PictureSet::setDrawTimes(const PictureSet& src) 1054 { 1055 validate(__FUNCTION__); 1056 if (mWidth != src.mWidth || mHeight != src.mHeight) 1057 return; 1058 Pictures* last = mPictures.end(); 1059 Pictures* working = mPictures.begin(); 1060 if (working == last) 1061 return; 1062 const Pictures* srcLast = src.mPictures.end(); 1063 const Pictures* srcWorking = src.mPictures.begin(); 1064 for (; srcWorking != srcLast; srcWorking++) { 1065 if (srcWorking->mWroteElapsed == false) 1066 continue; 1067 while ((srcWorking->mArea != working->mArea || 1068 srcWorking->mPicture != working->mPicture)) { 1069 if (++working == last) 1070 return; 1071 } 1072 DBG_SET_LOGD("%p [%d] [%d] {%d,%d,r=%d,b=%d} working->mElapsed=%d <- %d", 1073 this, working - mPictures.begin(), srcWorking - src.mPictures.begin(), 1074 working->mArea.getBounds().fLeft, working->mArea.getBounds().fTop, 1075 working->mArea.getBounds().fRight, working->mArea.getBounds().fBottom, 1076 working->mElapsed, srcWorking->mElapsed); 1077 working->mElapsed = srcWorking->mElapsed; 1078 } 1079 } 1080 1081 void PictureSet::setPicture(size_t i, SkPicture* p) 1082 { 1083 SkSafeUnref(mPictures[i].mPicture); 1084 mPictures[i].mPicture = p; 1085 mPictures[i].mEmpty = emptyPicture(p); 1086 } 1087 1088 void PictureSet::split(PictureSet* out) const 1089 { 1090 dump(__FUNCTION__); 1091 DBG_SET_LOGD("%p", this); 1092 SkIRect totalBounds; 1093 out->mWidth = mWidth; 1094 out->mHeight = mHeight; 1095 totalBounds.set(0, 0, mWidth, mHeight); 1096 SkRegion* total = new SkRegion(totalBounds); 1097 const Pictures* last = mPictures.end(); 1098 const Pictures* working; 1099 uint32_t balance = 0; 1100 int multiUnsplitFastPictures = 0; // > 1 has more than 1 1101 for (working = mPictures.begin(); working != last; working++) { 1102 if (working->mElapsed >= MAX_DRAW_TIME || working->mSplit) 1103 continue; 1104 if (++multiUnsplitFastPictures > 1) 1105 break; 1106 } 1107 for (working = mPictures.begin(); working != last; working++) { 1108 uint32_t elapsed = working->mElapsed; 1109 if (elapsed < MAX_DRAW_TIME) { 1110 bool split = working->mSplit; 1111 DBG_SET_LOGD("elapsed=%d working=%p total->getBounds()=" 1112 "{%d,%d,r=%d,b=%d} split=%s", elapsed, working, 1113 total->getBounds().fLeft, total->getBounds().fTop, 1114 total->getBounds().fRight, total->getBounds().fBottom, 1115 split ? "true" : "false"); 1116 if (multiUnsplitFastPictures <= 1 || split) { 1117 total->op(working->mArea, SkRegion::kDifference_Op); 1118 out->add(working->mArea, working->mPicture, elapsed, split, 1119 working->mEmpty); 1120 } else if (balance < elapsed) 1121 balance = elapsed; 1122 continue; 1123 } 1124 total->op(working->mArea, SkRegion::kDifference_Op); 1125 const SkIRect& bounds = working->mArea.getBounds(); 1126 int width = bounds.width(); 1127 int height = bounds.height(); 1128 int across = 1; 1129 int down = 1; 1130 while (height >= MIN_SPLITTABLE || width >= MIN_SPLITTABLE) { 1131 if (height >= width) { 1132 height >>= 1; 1133 down <<= 1; 1134 } else { 1135 width >>= 1; 1136 across <<= 1 ; 1137 } 1138 if ((elapsed >>= 1) < MAX_DRAW_TIME) 1139 break; 1140 } 1141 width = bounds.width(); 1142 height = bounds.height(); 1143 int top = bounds.fTop; 1144 for (int indexY = 0; indexY < down; ) { 1145 int bottom = bounds.fTop + height * ++indexY / down; 1146 int left = bounds.fLeft; 1147 for (int indexX = 0; indexX < across; ) { 1148 int right = bounds.fLeft + width * ++indexX / across; 1149 SkIRect cBounds; 1150 cBounds.set(left, top, right, bottom); 1151 out->add(SkRegion(cBounds), (across | down) != 1 ? NULL : 1152 working->mPicture, elapsed, true, 1153 (across | down) != 1 ? false : working->mEmpty); 1154 left = right; 1155 } 1156 top = bottom; 1157 } 1158 } 1159 DBG_SET_LOGD("%p w=%d h=%d total->isEmpty()=%s multiUnsplitFastPictures=%d", 1160 this, mWidth, mHeight, total->isEmpty() ? "true" : "false", 1161 multiUnsplitFastPictures); 1162 if (!total->isEmpty() && multiUnsplitFastPictures > 1) 1163 out->add(*total, NULL, balance, false, false); 1164 delete total; 1165 validate(__FUNCTION__); 1166 out->dump("split-out"); 1167 } 1168 1169 #endif // FAST_PICTURESET 1170 1171 bool PictureSet::validate(const char* funct) const 1172 { 1173 #ifdef FAST_PICTURESET 1174 return true; 1175 #else 1176 bool valid = true; 1177 #if PICTURE_SET_VALIDATE 1178 SkRegion all; 1179 const Pictures* first = mPictures.begin(); 1180 for (const Pictures* working = mPictures.end(); working != first; ) { 1181 --working; 1182 const SkPicture* pict = working->mPicture; 1183 const SkRegion& area = working->mArea; 1184 const SkIRect& bounds = area.getBounds(); 1185 bool localValid = false; 1186 if (working->mUnsplit.isEmpty()) 1187 LOGD("%s working->mUnsplit.isEmpty()", funct); 1188 else if (working->mUnsplit.contains(bounds) == false) 1189 LOGD("%s working->mUnsplit.contains(bounds) == false", funct); 1190 else if (working->mElapsed >= 1000) 1191 LOGD("%s working->mElapsed >= 1000", funct); 1192 else if ((working->mSplit & 0xfe) != 0) 1193 LOGD("%s (working->mSplit & 0xfe) != 0", funct); 1194 else if ((working->mWroteElapsed & 0xfe) != 0) 1195 LOGD("%s (working->mWroteElapsed & 0xfe) != 0", funct); 1196 else if (pict != NULL) { 1197 int pictWidth = pict->width(); 1198 int pictHeight = pict->height(); 1199 if (pictWidth < bounds.width()) 1200 LOGD("%s pictWidth=%d < bounds.width()=%d", funct, pictWidth, bounds.width()); 1201 else if (pictHeight < bounds.height()) 1202 LOGD("%s pictHeight=%d < bounds.height()=%d", funct, pictHeight, bounds.height()); 1203 else if (working->mArea.isEmpty()) 1204 LOGD("%s working->mArea.isEmpty()", funct); 1205 else 1206 localValid = true; 1207 } else 1208 localValid = true; 1209 working->mArea.validate(); 1210 if (localValid == false) { 1211 if (all.contains(area) == true) 1212 LOGD("%s all.contains(area) == true", funct); 1213 else 1214 localValid = true; 1215 } 1216 valid &= localValid; 1217 all.op(area, SkRegion::kUnion_Op); 1218 } 1219 const SkIRect& allBounds = all.getBounds(); 1220 if (valid) { 1221 valid = false; 1222 if (allBounds.width() != mWidth) 1223 LOGD("%s allBounds.width()=%d != mWidth=%d", funct, allBounds.width(), mWidth); 1224 else if (allBounds.height() != mHeight) 1225 LOGD("%s allBounds.height()=%d != mHeight=%d", funct, allBounds.height(), mHeight); 1226 else 1227 valid = true; 1228 } 1229 while (valid == false) 1230 ; 1231 #endif 1232 return valid; 1233 #endif // FAST_PICTURESET 1234 } 1235 1236 } /* namespace android */ 1237