PictureRenderer.cpp revision f5e315ccf1ae2941f7cf53fa53e5c8c4bb665fe1
1/* 2 * Copyright 2012 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "PictureRenderer.h" 9#include "picture_utils.h" 10#include "SamplePipeControllers.h" 11#include "SkBitmapHasher.h" 12#include "SkCanvas.h" 13#include "SkData.h" 14#include "SkDevice.h" 15#include "SkDiscardableMemoryPool.h" 16#include "SkGPipe.h" 17#if SK_SUPPORT_GPU 18#include "gl/GrGLDefines.h" 19#include "SkGpuDevice.h" 20#endif 21#include "SkGraphics.h" 22#include "SkImageEncoder.h" 23#include "SkMaskFilter.h" 24#include "SkMatrix.h" 25#include "SkPicture.h" 26#include "SkPictureUtils.h" 27#include "SkPixelRef.h" 28#include "SkQuadTree.h" 29#include "SkQuadTreePicture.h" 30#include "SkRTree.h" 31#include "SkScalar.h" 32#include "SkStream.h" 33#include "SkString.h" 34#include "SkTemplates.h" 35#include "SkTileGridPicture.h" 36#include "SkTDArray.h" 37#include "SkThreadUtils.h" 38#include "SkTypes.h" 39 40static inline SkScalar scalar_log2(SkScalar x) { 41 static const SkScalar log2_conversion_factor = SkScalarDiv(1, SkScalarLog(2)); 42 43 return SkScalarLog(x) * log2_conversion_factor; 44} 45 46namespace sk_tools { 47 48enum { 49 kDefaultTileWidth = 256, 50 kDefaultTileHeight = 256 51}; 52 53/* TODO(epoger): These constants are already maintained in 2 other places: 54 * gm/gm_json.py and gm/gm_expectations.cpp. We shouldn't add yet a third place. 55 * Figure out a way to share the definitions instead. 56 */ 57const static char kJsonKey_ActualResults[] = "actual-results"; 58const static char kJsonKey_ActualResults_NoComparison[] = "no-comparison"; 59const static char kJsonKey_Hashtype_Bitmap_64bitMD5[] = "bitmap-64bitMD5"; 60 61void ImageResultsSummary::add(const char *testName, uint64_t hash) { 62 Json::Value jsonTypeValuePair; 63 jsonTypeValuePair.append(Json::Value(kJsonKey_Hashtype_Bitmap_64bitMD5)); 64 jsonTypeValuePair.append(Json::UInt64(hash)); 65 fActualResultsNoComparison[testName] = jsonTypeValuePair; 66} 67 68void ImageResultsSummary::add(const char *testName, const SkBitmap& bitmap) { 69 uint64_t hash; 70 SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash)); 71 this->add(testName, hash); 72} 73 74void ImageResultsSummary::writeToFile(const char *filename) { 75 Json::Value actualResults; 76 actualResults[kJsonKey_ActualResults_NoComparison] = fActualResultsNoComparison; 77 Json::Value root; 78 root[kJsonKey_ActualResults] = actualResults; 79 std::string jsonStdString = root.toStyledString(); 80 SkFILEWStream stream(filename); 81 stream.write(jsonStdString.c_str(), jsonStdString.length()); 82} 83 84void PictureRenderer::init(SkPicture* pict, const SkString* outputDir, 85 const SkString* inputFilename, bool useChecksumBasedFilenames) { 86 this->CopyString(&fOutputDir, outputDir); 87 this->CopyString(&fInputFilename, inputFilename); 88 fUseChecksumBasedFilenames = useChecksumBasedFilenames; 89 90 SkASSERT(NULL == fPicture); 91 SkASSERT(NULL == fCanvas.get()); 92 if (fPicture != NULL || NULL != fCanvas.get()) { 93 return; 94 } 95 96 SkASSERT(pict != NULL); 97 if (NULL == pict) { 98 return; 99 } 100 101 fPicture = pict; 102 fPicture->ref(); 103 fCanvas.reset(this->setupCanvas()); 104} 105 106void PictureRenderer::CopyString(SkString* dest, const SkString* src) { 107 if (NULL != src) { 108 dest->set(*src); 109 } else { 110 dest->reset(); 111 } 112} 113 114class FlagsDrawFilter : public SkDrawFilter { 115public: 116 FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) : 117 fFlags(flags) {} 118 119 virtual bool filter(SkPaint* paint, Type t) { 120 paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags); 121 if (PictureRenderer::kMaskFilter_DrawFilterFlag & fFlags[t]) { 122 SkMaskFilter* maskFilter = paint->getMaskFilter(); 123 if (NULL != maskFilter) { 124 paint->setMaskFilter(NULL); 125 } 126 } 127 if (PictureRenderer::kHinting_DrawFilterFlag & fFlags[t]) { 128 paint->setHinting(SkPaint::kNo_Hinting); 129 } else if (PictureRenderer::kSlightHinting_DrawFilterFlag & fFlags[t]) { 130 paint->setHinting(SkPaint::kSlight_Hinting); 131 } 132 return true; 133 } 134 135private: 136 PictureRenderer::DrawFilterFlags* fFlags; 137}; 138 139static void setUpFilter(SkCanvas* canvas, PictureRenderer::DrawFilterFlags* drawFilters) { 140 if (drawFilters && !canvas->getDrawFilter()) { 141 canvas->setDrawFilter(SkNEW_ARGS(FlagsDrawFilter, (drawFilters)))->unref(); 142 if (drawFilters[0] & PictureRenderer::kAAClip_DrawFilterFlag) { 143 canvas->setAllowSoftClip(false); 144 } 145 } 146} 147 148SkCanvas* PictureRenderer::setupCanvas() { 149 const int width = this->getViewWidth(); 150 const int height = this->getViewHeight(); 151 return this->setupCanvas(width, height); 152} 153 154SkCanvas* PictureRenderer::setupCanvas(int width, int height) { 155 SkCanvas* canvas; 156 switch(fDeviceType) { 157 case kBitmap_DeviceType: { 158 SkBitmap bitmap; 159 sk_tools::setup_bitmap(&bitmap, width, height); 160 canvas = SkNEW_ARGS(SkCanvas, (bitmap)); 161 } 162 break; 163#if SK_SUPPORT_GPU 164#if SK_ANGLE 165 case kAngle_DeviceType: 166 // fall through 167#endif 168#if SK_MESA 169 case kMesa_DeviceType: 170 // fall through 171#endif 172 case kGPU_DeviceType: 173 case kNVPR_DeviceType: { 174 SkAutoTUnref<GrSurface> target; 175 if (fGrContext) { 176 // create a render target to back the device 177 GrTextureDesc desc; 178 desc.fConfig = kSkia8888_GrPixelConfig; 179 desc.fFlags = kRenderTarget_GrTextureFlagBit; 180 desc.fWidth = width; 181 desc.fHeight = height; 182 desc.fSampleCnt = fSampleCount; 183 target.reset(fGrContext->createUncachedTexture(desc, NULL, 0)); 184 } 185 if (NULL == target.get()) { 186 SkASSERT(0); 187 return NULL; 188 } 189 190 SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(target)); 191 canvas = SkNEW_ARGS(SkCanvas, (device.get())); 192 break; 193 } 194#endif 195 default: 196 SkASSERT(0); 197 return NULL; 198 } 199 setUpFilter(canvas, fDrawFilters); 200 this->scaleToScaleFactor(canvas); 201 202 // Pictures often lie about their extent (i.e., claim to be 100x100 but 203 // only ever draw to 90x100). Clear here so the undrawn portion will have 204 // a consistent color 205 canvas->clear(SK_ColorTRANSPARENT); 206 return canvas; 207} 208 209void PictureRenderer::scaleToScaleFactor(SkCanvas* canvas) { 210 SkASSERT(canvas != NULL); 211 if (fScaleFactor != SK_Scalar1) { 212 canvas->scale(fScaleFactor, fScaleFactor); 213 } 214} 215 216void PictureRenderer::end() { 217 this->resetState(true); 218 SkSafeUnref(fPicture); 219 fPicture = NULL; 220 fCanvas.reset(NULL); 221} 222 223int PictureRenderer::getViewWidth() { 224 SkASSERT(fPicture != NULL); 225 int width = SkScalarCeilToInt(fPicture->width() * fScaleFactor); 226 if (fViewport.width() > 0) { 227 width = SkMin32(width, fViewport.width()); 228 } 229 return width; 230} 231 232int PictureRenderer::getViewHeight() { 233 SkASSERT(fPicture != NULL); 234 int height = SkScalarCeilToInt(fPicture->height() * fScaleFactor); 235 if (fViewport.height() > 0) { 236 height = SkMin32(height, fViewport.height()); 237 } 238 return height; 239} 240 241/** Converts fPicture to a picture that uses a BBoxHierarchy. 242 * PictureRenderer subclasses that are used to test picture playback 243 * should call this method during init. 244 */ 245void PictureRenderer::buildBBoxHierarchy() { 246 SkASSERT(NULL != fPicture); 247 if (kNone_BBoxHierarchyType != fBBoxHierarchyType && NULL != fPicture) { 248 SkPicture* newPicture = this->createPicture(); 249 SkCanvas* recorder = newPicture->beginRecording(fPicture->width(), fPicture->height(), 250 this->recordFlags()); 251 fPicture->draw(recorder); 252 newPicture->endRecording(); 253 fPicture->unref(); 254 fPicture = newPicture; 255 } 256} 257 258void PictureRenderer::resetState(bool callFinish) { 259#if SK_SUPPORT_GPU 260 SkGLContextHelper* glContext = this->getGLContext(); 261 if (NULL == glContext) { 262 SkASSERT(kBitmap_DeviceType == fDeviceType); 263 return; 264 } 265 266 fGrContext->flush(); 267 glContext->swapBuffers(); 268 if (callFinish) { 269 SK_GL(*glContext, Finish()); 270 } 271#endif 272} 273 274void PictureRenderer::purgeTextures() { 275 SkDiscardableMemoryPool* pool = SkGetGlobalDiscardableMemoryPool(); 276 277 pool->dumpPool(); 278 279#if SK_SUPPORT_GPU 280 SkGLContextHelper* glContext = this->getGLContext(); 281 if (NULL == glContext) { 282 SkASSERT(kBitmap_DeviceType == fDeviceType); 283 return; 284 } 285 286 // resetState should've already done this 287 fGrContext->flush(); 288 289 fGrContext->purgeAllUnlockedResources(); 290#endif 291} 292 293uint32_t PictureRenderer::recordFlags() { 294 return ((kNone_BBoxHierarchyType == fBBoxHierarchyType) ? 0 : 295 SkPicture::kOptimizeForClippedPlayback_RecordingFlag) | 296 SkPicture::kUsePathBoundsForClip_RecordingFlag; 297} 298 299/** 300 * Write the canvas to the specified path. 301 * 302 * @param canvas Must be non-null. Canvas to be written to a file. 303 * @param outputDir If nonempty, write the binary image to a file within this directory. 304 * @param inputFilename If we are writing out a binary image, use this to build its filename. 305 * @param jsonSummaryPtr If not null, add image results to this summary. 306 * @param useChecksumBasedFilenames If true, use checksum-based filenames when writing to disk. 307 * @param numberToAppend If not null, append this number to the filename. 308 * @return bool True if the Canvas is written to a file. 309 * 310 * TODO(epoger): Right now, all canvases must pass through this function in order to be appended 311 * to the ImageResultsSummary. We need some way to add bitmaps to the ImageResultsSummary 312 * even if --writePath has not been specified (and thus this function is not called). 313 * 314 * One fix would be to pass in these path elements separately, and allow this function to be 315 * called even if --writePath was not specified... 316 * const char *outputDir // NULL if we don't want to write image files to disk 317 * const char *filename // name we use within JSON summary, and as the filename within outputDir 318 * 319 * UPDATE: Now that outputDir and inputFilename are passed separately, we should be able to do that. 320 */ 321static bool write(SkCanvas* canvas, const SkString& outputDir, const SkString& inputFilename, 322 ImageResultsSummary *jsonSummaryPtr, bool useChecksumBasedFilenames, 323 const int* numberToAppend=NULL) { 324 SkASSERT(canvas != NULL); 325 if (NULL == canvas) { 326 return false; 327 } 328 329 SkBitmap bitmap; 330 SkISize size = canvas->getDeviceSize(); 331 sk_tools::setup_bitmap(&bitmap, size.width(), size.height()); 332 333 // Make sure we only compute the bitmap hash once (at most). 334 uint64_t hash; 335 bool generatedHash = false; 336 337 canvas->readPixels(&bitmap, 0, 0); 338 sk_tools::force_all_opaque(bitmap); 339 340 SkString outputFilename(inputFilename); 341 outputFilename.remove(outputFilename.size() - 4, 4); 342 if (NULL != numberToAppend) { 343 outputFilename.appendf("%i", *numberToAppend); 344 } 345 outputFilename.append(".png"); 346 // TODO(epoger): what about including the config type within outputFilename? That way, 347 // we could combine results of different config types without conflicting filenames. 348 349 if (NULL != jsonSummaryPtr) { 350 if (!generatedHash) { 351 SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash)); 352 generatedHash = true; 353 } 354 jsonSummaryPtr->add(outputFilename.c_str(), hash); 355 } 356 357 // Update outputFilename AFTER adding to JSON summary, but BEFORE writing out the image file. 358 if (useChecksumBasedFilenames) { 359 if (!generatedHash) { 360 SkAssertResult(SkBitmapHasher::ComputeDigest(bitmap, &hash)); 361 generatedHash = true; 362 } 363 outputFilename.set(kJsonKey_Hashtype_Bitmap_64bitMD5); 364 outputFilename.append("_"); 365 outputFilename.appendU64(hash); 366 outputFilename.append(".png"); 367 } 368 369 SkASSERT(!outputDir.isEmpty()); // TODO(epoger): we want to remove this constraint, 370 // as noted above 371 SkString fullPathname; 372 make_filepath(&fullPathname, outputDir, outputFilename); 373 return SkImageEncoder::EncodeFile(fullPathname.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100); 374} 375 376/////////////////////////////////////////////////////////////////////////////////////////////// 377 378SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) { 379 // defer the canvas setup until the render step 380 return NULL; 381} 382 383// the size_t* parameter is deprecated, so we ignore it 384static SkData* encode_bitmap_to_data(size_t*, const SkBitmap& bm) { 385 return SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, 100); 386} 387 388bool RecordPictureRenderer::render(SkBitmap** out) { 389 SkAutoTUnref<SkPicture> replayer(this->createPicture()); 390 SkCanvas* recorder = replayer->beginRecording(this->getViewWidth(), this->getViewHeight(), 391 this->recordFlags()); 392 this->scaleToScaleFactor(recorder); 393 fPicture->draw(recorder); 394 replayer->endRecording(); 395 if (!fOutputDir.isEmpty()) { 396 // Record the new picture as a new SKP with PNG encoded bitmaps. 397 SkString skpPath; 398 make_filepath(&skpPath, fOutputDir, fInputFilename); 399 SkFILEWStream stream(skpPath.c_str()); 400 replayer->serialize(&stream, &encode_bitmap_to_data); 401 return true; 402 } 403 return false; 404} 405 406SkString RecordPictureRenderer::getConfigNameInternal() { 407 return SkString("record"); 408} 409 410/////////////////////////////////////////////////////////////////////////////////////////////// 411 412bool PipePictureRenderer::render(SkBitmap** out) { 413 SkASSERT(fCanvas.get() != NULL); 414 SkASSERT(fPicture != NULL); 415 if (NULL == fCanvas.get() || NULL == fPicture) { 416 return false; 417 } 418 419 PipeController pipeController(fCanvas.get()); 420 SkGPipeWriter writer; 421 SkCanvas* pipeCanvas = writer.startRecording(&pipeController); 422 pipeCanvas->drawPicture(*fPicture); 423 writer.endRecording(); 424 fCanvas->flush(); 425 if (!fOutputDir.isEmpty()) { 426 return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, 427 fUseChecksumBasedFilenames); 428 } 429 if (NULL != out) { 430 *out = SkNEW(SkBitmap); 431 setup_bitmap(*out, fPicture->width(), fPicture->height()); 432 fCanvas->readPixels(*out, 0, 0); 433 } 434 return true; 435} 436 437SkString PipePictureRenderer::getConfigNameInternal() { 438 return SkString("pipe"); 439} 440 441/////////////////////////////////////////////////////////////////////////////////////////////// 442 443void SimplePictureRenderer::init(SkPicture* picture, const SkString* outputDir, 444 const SkString* inputFilename, bool useChecksumBasedFilenames) { 445 INHERITED::init(picture, outputDir, inputFilename, useChecksumBasedFilenames); 446 this->buildBBoxHierarchy(); 447} 448 449bool SimplePictureRenderer::render(SkBitmap** out) { 450 SkASSERT(fCanvas.get() != NULL); 451 SkASSERT(fPicture != NULL); 452 if (NULL == fCanvas.get() || NULL == fPicture) { 453 return false; 454 } 455 456 fCanvas->drawPicture(*fPicture); 457 fCanvas->flush(); 458 if (!fOutputDir.isEmpty()) { 459 return write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, 460 fUseChecksumBasedFilenames); 461 } 462 463 if (NULL != out) { 464 *out = SkNEW(SkBitmap); 465 setup_bitmap(*out, fPicture->width(), fPicture->height()); 466 fCanvas->readPixels(*out, 0, 0); 467 } 468 469 return true; 470} 471 472SkString SimplePictureRenderer::getConfigNameInternal() { 473 return SkString("simple"); 474} 475 476/////////////////////////////////////////////////////////////////////////////////////////////// 477 478TiledPictureRenderer::TiledPictureRenderer() 479 : fTileWidth(kDefaultTileWidth) 480 , fTileHeight(kDefaultTileHeight) 481 , fTileWidthPercentage(0.0) 482 , fTileHeightPercentage(0.0) 483 , fTileMinPowerOf2Width(0) 484 , fCurrentTileOffset(-1) 485 , fTilesX(0) 486 , fTilesY(0) { } 487 488void TiledPictureRenderer::init(SkPicture* pict, const SkString* outputDir, 489 const SkString* inputFilename, bool useChecksumBasedFilenames) { 490 SkASSERT(pict != NULL); 491 SkASSERT(0 == fTileRects.count()); 492 if (NULL == pict || fTileRects.count() != 0) { 493 return; 494 } 495 496 // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not 497 // used by bench_pictures. 498 fPicture = pict; 499 this->CopyString(&fOutputDir, outputDir); 500 this->CopyString(&fInputFilename, inputFilename); 501 fUseChecksumBasedFilenames = useChecksumBasedFilenames; 502 fPicture->ref(); 503 this->buildBBoxHierarchy(); 504 505 if (fTileWidthPercentage > 0) { 506 fTileWidth = sk_float_ceil2int(float(fTileWidthPercentage * fPicture->width() / 100)); 507 } 508 if (fTileHeightPercentage > 0) { 509 fTileHeight = sk_float_ceil2int(float(fTileHeightPercentage * fPicture->height() / 100)); 510 } 511 512 if (fTileMinPowerOf2Width > 0) { 513 this->setupPowerOf2Tiles(); 514 } else { 515 this->setupTiles(); 516 } 517 fCanvas.reset(this->setupCanvas(fTileWidth, fTileHeight)); 518 // Initialize to -1 so that the first call to nextTile will set this up to draw tile 0 on the 519 // first call to drawCurrentTile. 520 fCurrentTileOffset = -1; 521} 522 523void TiledPictureRenderer::end() { 524 fTileRects.reset(); 525 this->INHERITED::end(); 526} 527 528void TiledPictureRenderer::setupTiles() { 529 // Only use enough tiles to cover the viewport 530 const int width = this->getViewWidth(); 531 const int height = this->getViewHeight(); 532 533 fTilesX = fTilesY = 0; 534 for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) { 535 fTilesY++; 536 for (int tile_x_start = 0; tile_x_start < width; tile_x_start += fTileWidth) { 537 if (0 == tile_y_start) { 538 // Only count tiles in the X direction on the first pass. 539 fTilesX++; 540 } 541 *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start), 542 SkIntToScalar(tile_y_start), 543 SkIntToScalar(fTileWidth), 544 SkIntToScalar(fTileHeight)); 545 } 546 } 547} 548 549bool TiledPictureRenderer::tileDimensions(int &x, int &y) { 550 if (fTileRects.count() == 0 || NULL == fPicture) { 551 return false; 552 } 553 x = fTilesX; 554 y = fTilesY; 555 return true; 556} 557 558// The goal of the powers of two tiles is to minimize the amount of wasted tile 559// space in the width-wise direction and then minimize the number of tiles. The 560// constraints are that every tile must have a pixel width that is a power of 561// two and also be of some minimal width (that is also a power of two). 562// 563// This is solved by first taking our picture size and rounding it up to the 564// multiple of the minimal width. The binary representation of this rounded 565// value gives us the tiles we need: a bit of value one means we need a tile of 566// that size. 567void TiledPictureRenderer::setupPowerOf2Tiles() { 568 // Only use enough tiles to cover the viewport 569 const int width = this->getViewWidth(); 570 const int height = this->getViewHeight(); 571 572 int rounded_value = width; 573 if (width % fTileMinPowerOf2Width != 0) { 574 rounded_value = width - (width % fTileMinPowerOf2Width) + fTileMinPowerOf2Width; 575 } 576 577 int num_bits = SkScalarCeilToInt(scalar_log2(SkIntToScalar(width))); 578 int largest_possible_tile_size = 1 << num_bits; 579 580 fTilesX = fTilesY = 0; 581 // The tile height is constant for a particular picture. 582 for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) { 583 fTilesY++; 584 int tile_x_start = 0; 585 int current_width = largest_possible_tile_size; 586 // Set fTileWidth to be the width of the widest tile, so that each canvas is large enough 587 // to draw each tile. 588 fTileWidth = current_width; 589 590 while (current_width >= fTileMinPowerOf2Width) { 591 // It is very important this is a bitwise AND. 592 if (current_width & rounded_value) { 593 if (0 == tile_y_start) { 594 // Only count tiles in the X direction on the first pass. 595 fTilesX++; 596 } 597 *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start), 598 SkIntToScalar(tile_y_start), 599 SkIntToScalar(current_width), 600 SkIntToScalar(fTileHeight)); 601 tile_x_start += current_width; 602 } 603 604 current_width >>= 1; 605 } 606 } 607} 608 609/** 610 * Draw the specified picture to the canvas translated to rectangle provided, so that this mini 611 * canvas represents the rectangle's portion of the overall picture. 612 * Saves and restores so that the initial clip and matrix return to their state before this function 613 * is called. 614 */ 615static void draw_tile_to_canvas(SkCanvas* canvas, const SkRect& tileRect, SkPicture* picture) { 616 int saveCount = canvas->save(); 617 // Translate so that we draw the correct portion of the picture. 618 // Perform a postTranslate so that the scaleFactor does not interfere with the positioning. 619 SkMatrix mat(canvas->getTotalMatrix()); 620 mat.postTranslate(-tileRect.fLeft, -tileRect.fTop); 621 canvas->setMatrix(mat); 622 canvas->drawPicture(*picture); 623 canvas->restoreToCount(saveCount); 624 canvas->flush(); 625} 626 627/////////////////////////////////////////////////////////////////////////////////////////////// 628 629/** 630 * Copies the entirety of the src bitmap (typically a tile) into a portion of the dst bitmap. 631 * If the src bitmap is too large to fit within the dst bitmap after the x and y 632 * offsets have been applied, any excess will be ignored (so only the top-left portion of the 633 * src bitmap will be copied). 634 * 635 * @param src source bitmap 636 * @param dst destination bitmap 637 * @param xOffset x-offset within destination bitmap 638 * @param yOffset y-offset within destination bitmap 639 */ 640static void bitmapCopyAtOffset(const SkBitmap& src, SkBitmap* dst, 641 int xOffset, int yOffset) { 642 for (int y = 0; y <src.height() && y + yOffset < dst->height() ; y++) { 643 for (int x = 0; x < src.width() && x + xOffset < dst->width() ; x++) { 644 *dst->getAddr32(xOffset + x, yOffset + y) = *src.getAddr32(x, y); 645 } 646 } 647} 648 649bool TiledPictureRenderer::nextTile(int &i, int &j) { 650 if (++fCurrentTileOffset < fTileRects.count()) { 651 i = fCurrentTileOffset % fTilesX; 652 j = fCurrentTileOffset / fTilesX; 653 return true; 654 } 655 return false; 656} 657 658void TiledPictureRenderer::drawCurrentTile() { 659 SkASSERT(fCurrentTileOffset >= 0 && fCurrentTileOffset < fTileRects.count()); 660 draw_tile_to_canvas(fCanvas, fTileRects[fCurrentTileOffset], fPicture); 661} 662 663bool TiledPictureRenderer::render(SkBitmap** out) { 664 SkASSERT(fPicture != NULL); 665 if (NULL == fPicture) { 666 return false; 667 } 668 669 SkBitmap bitmap; 670 if (out){ 671 *out = SkNEW(SkBitmap); 672 setup_bitmap(*out, fPicture->width(), fPicture->height()); 673 setup_bitmap(&bitmap, fTileWidth, fTileHeight); 674 } 675 bool success = true; 676 for (int i = 0; i < fTileRects.count(); ++i) { 677 draw_tile_to_canvas(fCanvas, fTileRects[i], fPicture); 678 if (!fOutputDir.isEmpty()) { 679 success &= write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, 680 fUseChecksumBasedFilenames, &i); 681 } 682 if (NULL != out) { 683 if (fCanvas->readPixels(&bitmap, 0, 0)) { 684 // Add this tile to the entire bitmap. 685 bitmapCopyAtOffset(bitmap, *out, SkScalarFloorToInt(fTileRects[i].left()), 686 SkScalarFloorToInt(fTileRects[i].top())); 687 } else { 688 success = false; 689 } 690 } 691 } 692 return success; 693} 694 695SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) { 696 SkCanvas* canvas = this->INHERITED::setupCanvas(width, height); 697 SkASSERT(fPicture != NULL); 698 // Clip the tile to an area that is completely inside both the SkPicture and the viewport. This 699 // is mostly important for tiles on the right and bottom edges as they may go over this area and 700 // the picture may have some commands that draw outside of this area and so should not actually 701 // be written. 702 // Uses a clipRegion so that it will be unaffected by the scale factor, which may have been set 703 // by INHERITED::setupCanvas. 704 SkRegion clipRegion; 705 clipRegion.setRect(0, 0, this->getViewWidth(), this->getViewHeight()); 706 canvas->clipRegion(clipRegion); 707 return canvas; 708} 709 710SkString TiledPictureRenderer::getConfigNameInternal() { 711 SkString name; 712 if (fTileMinPowerOf2Width > 0) { 713 name.append("pow2tile_"); 714 name.appendf("%i", fTileMinPowerOf2Width); 715 } else { 716 name.append("tile_"); 717 if (fTileWidthPercentage > 0) { 718 name.appendf("%.f%%", fTileWidthPercentage); 719 } else { 720 name.appendf("%i", fTileWidth); 721 } 722 } 723 name.append("x"); 724 if (fTileHeightPercentage > 0) { 725 name.appendf("%.f%%", fTileHeightPercentage); 726 } else { 727 name.appendf("%i", fTileHeight); 728 } 729 return name; 730} 731 732/////////////////////////////////////////////////////////////////////////////////////////////// 733 734// Holds all of the information needed to draw a set of tiles. 735class CloneData : public SkRunnable { 736 737public: 738 CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int start, int end, 739 SkRunnable* done, ImageResultsSummary* jsonSummaryPtr, bool useChecksumBasedFilenames) 740 : fClone(clone) 741 , fCanvas(canvas) 742 , fRects(rects) 743 , fStart(start) 744 , fEnd(end) 745 , fSuccess(NULL) 746 , fDone(done) 747 , fJsonSummaryPtr(jsonSummaryPtr) 748 , fUseChecksumBasedFilenames(useChecksumBasedFilenames) { 749 SkASSERT(fDone != NULL); 750 } 751 752 virtual void run() SK_OVERRIDE { 753 SkGraphics::SetTLSFontCacheLimit(1024 * 1024); 754 755 SkBitmap bitmap; 756 if (fBitmap != NULL) { 757 // All tiles are the same size. 758 setup_bitmap(&bitmap, SkScalarFloorToInt(fRects[0].width()), SkScalarFloorToInt(fRects[0].height())); 759 } 760 761 for (int i = fStart; i < fEnd; i++) { 762 draw_tile_to_canvas(fCanvas, fRects[i], fClone); 763 if ((!fOutputDir.isEmpty()) 764 && !write(fCanvas, fOutputDir, fInputFilename, fJsonSummaryPtr, 765 fUseChecksumBasedFilenames, &i) 766 && fSuccess != NULL) { 767 *fSuccess = false; 768 // If one tile fails to write to a file, do not continue drawing the rest. 769 break; 770 } 771 if (fBitmap != NULL) { 772 if (fCanvas->readPixels(&bitmap, 0, 0)) { 773 SkAutoLockPixels alp(*fBitmap); 774 bitmapCopyAtOffset(bitmap, fBitmap, SkScalarFloorToInt(fRects[i].left()), 775 SkScalarFloorToInt(fRects[i].top())); 776 } else { 777 *fSuccess = false; 778 // If one tile fails to read pixels, do not continue drawing the rest. 779 break; 780 } 781 } 782 } 783 fDone->run(); 784 } 785 786 void setPathsAndSuccess(const SkString& outputDir, const SkString& inputFilename, 787 bool* success) { 788 fOutputDir.set(outputDir); 789 fInputFilename.set(inputFilename); 790 fSuccess = success; 791 } 792 793 void setBitmap(SkBitmap* bitmap) { 794 fBitmap = bitmap; 795 } 796 797private: 798 // All pointers unowned. 799 SkPicture* fClone; // Picture to draw from. Each CloneData has a unique one which 800 // is threadsafe. 801 SkCanvas* fCanvas; // Canvas to draw to. Reused for each tile. 802 SkString fOutputDir; // If not empty, write results into this directory. 803 SkString fInputFilename; // Filename of input SkPicture file. 804 SkTDArray<SkRect>& fRects; // All tiles of the picture. 805 const int fStart; // Range of tiles drawn by this thread. 806 const int fEnd; 807 bool* fSuccess; // Only meaningful if path is non-null. Shared by all threads, 808 // and only set to false upon failure to write to a PNG. 809 SkRunnable* fDone; 810 SkBitmap* fBitmap; 811 ImageResultsSummary* fJsonSummaryPtr; 812 bool fUseChecksumBasedFilenames; 813}; 814 815MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount) 816: fNumThreads(threadCount) 817, fThreadPool(threadCount) 818, fCountdown(threadCount) { 819 // Only need to create fNumThreads - 1 clones, since one thread will use the base 820 // picture. 821 fPictureClones = SkNEW_ARRAY(SkPicture, fNumThreads - 1); 822 fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads); 823} 824 825void MultiCorePictureRenderer::init(SkPicture *pict, const SkString* outputDir, 826 const SkString* inputFilename, bool useChecksumBasedFilenames) { 827 // Set fPicture and the tiles. 828 this->INHERITED::init(pict, outputDir, inputFilename, useChecksumBasedFilenames); 829 for (int i = 0; i < fNumThreads; ++i) { 830 *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight()); 831 } 832 // Only need to create fNumThreads - 1 clones, since one thread will use the base picture. 833 fPicture->clone(fPictureClones, fNumThreads - 1); 834 // Populate each thread with the appropriate data. 835 // Group the tiles into nearly equal size chunks, rounding up so we're sure to cover them all. 836 const int chunkSize = (fTileRects.count() + fNumThreads - 1) / fNumThreads; 837 838 for (int i = 0; i < fNumThreads; i++) { 839 SkPicture* pic; 840 if (i == fNumThreads-1) { 841 // The last set will use the original SkPicture. 842 pic = fPicture; 843 } else { 844 pic = &fPictureClones[i]; 845 } 846 const int start = i * chunkSize; 847 const int end = SkMin32(start + chunkSize, fTileRects.count()); 848 fCloneData[i] = SkNEW_ARGS(CloneData, 849 (pic, fCanvasPool[i], fTileRects, start, end, &fCountdown, 850 fJsonSummaryPtr, useChecksumBasedFilenames)); 851 } 852} 853 854bool MultiCorePictureRenderer::render(SkBitmap** out) { 855 bool success = true; 856 if (!fOutputDir.isEmpty()) { 857 for (int i = 0; i < fNumThreads-1; i++) { 858 fCloneData[i]->setPathsAndSuccess(fOutputDir, fInputFilename, &success); 859 } 860 } 861 862 if (NULL != out) { 863 *out = SkNEW(SkBitmap); 864 setup_bitmap(*out, fPicture->width(), fPicture->height()); 865 for (int i = 0; i < fNumThreads; i++) { 866 fCloneData[i]->setBitmap(*out); 867 } 868 } else { 869 for (int i = 0; i < fNumThreads; i++) { 870 fCloneData[i]->setBitmap(NULL); 871 } 872 } 873 874 fCountdown.reset(fNumThreads); 875 for (int i = 0; i < fNumThreads; i++) { 876 fThreadPool.add(fCloneData[i]); 877 } 878 fCountdown.wait(); 879 880 return success; 881} 882 883void MultiCorePictureRenderer::end() { 884 for (int i = 0; i < fNumThreads - 1; i++) { 885 SkDELETE(fCloneData[i]); 886 fCloneData[i] = NULL; 887 } 888 889 fCanvasPool.unrefAll(); 890 891 this->INHERITED::end(); 892} 893 894MultiCorePictureRenderer::~MultiCorePictureRenderer() { 895 // Each individual CloneData was deleted in end. 896 SkDELETE_ARRAY(fCloneData); 897 SkDELETE_ARRAY(fPictureClones); 898} 899 900SkString MultiCorePictureRenderer::getConfigNameInternal() { 901 SkString name = this->INHERITED::getConfigNameInternal(); 902 name.appendf("_multi_%i_threads", fNumThreads); 903 return name; 904} 905 906/////////////////////////////////////////////////////////////////////////////////////////////// 907 908void PlaybackCreationRenderer::setup() { 909 fReplayer.reset(this->createPicture()); 910 SkCanvas* recorder = fReplayer->beginRecording(this->getViewWidth(), this->getViewHeight(), 911 this->recordFlags()); 912 this->scaleToScaleFactor(recorder); 913 fPicture->draw(recorder); 914} 915 916bool PlaybackCreationRenderer::render(SkBitmap** out) { 917 fReplayer->endRecording(); 918 // Since this class does not actually render, return false. 919 return false; 920} 921 922SkString PlaybackCreationRenderer::getConfigNameInternal() { 923 return SkString("playback_creation"); 924} 925 926/////////////////////////////////////////////////////////////////////////////////////////////// 927// SkPicture variants for each BBoxHierarchy type 928 929class RTreePicture : public SkPicture { 930public: 931 virtual SkBBoxHierarchy* createBBoxHierarchy() const SK_OVERRIDE{ 932 static const int kRTreeMinChildren = 6; 933 static const int kRTreeMaxChildren = 11; 934 SkScalar aspectRatio = SkScalarDiv(SkIntToScalar(fWidth), 935 SkIntToScalar(fHeight)); 936 bool sortDraws = false; 937 return SkRTree::Create(kRTreeMinChildren, kRTreeMaxChildren, 938 aspectRatio, sortDraws); 939 } 940}; 941 942SkPicture* PictureRenderer::createPicture() { 943 switch (fBBoxHierarchyType) { 944 case kNone_BBoxHierarchyType: 945 return SkNEW(SkPicture); 946 case kQuadTree_BBoxHierarchyType: 947 return SkNEW_ARGS(SkQuadTreePicture, (SkIRect::MakeWH(fPicture->width(), 948 fPicture->height()))); 949 case kRTree_BBoxHierarchyType: 950 return SkNEW(RTreePicture); 951 case kTileGrid_BBoxHierarchyType: 952 return SkNEW_ARGS(SkTileGridPicture, (fPicture->width(), 953 fPicture->height(), fGridInfo)); 954 } 955 SkASSERT(0); // invalid bbhType 956 return NULL; 957} 958 959/////////////////////////////////////////////////////////////////////////////// 960 961class GatherRenderer : public PictureRenderer { 962public: 963 virtual bool render(SkBitmap** out = NULL) 964 SK_OVERRIDE { 965 SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->width()), 966 SkIntToScalar(fPicture->height())); 967 SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds); 968 SkSafeUnref(data); 969 970 return (fOutputDir.isEmpty()); // we don't have anything to write 971 } 972 973private: 974 virtual SkString getConfigNameInternal() SK_OVERRIDE { 975 return SkString("gather_pixelrefs"); 976 } 977}; 978 979PictureRenderer* CreateGatherPixelRefsRenderer() { 980 return SkNEW(GatherRenderer); 981} 982 983/////////////////////////////////////////////////////////////////////////////// 984 985class PictureCloneRenderer : public PictureRenderer { 986public: 987 virtual bool render(SkBitmap** out = NULL) 988 SK_OVERRIDE { 989 for (int i = 0; i < 100; ++i) { 990 SkPicture* clone = fPicture->clone(); 991 SkSafeUnref(clone); 992 } 993 994 return (fOutputDir.isEmpty()); // we don't have anything to write 995 } 996 997private: 998 virtual SkString getConfigNameInternal() SK_OVERRIDE { 999 return SkString("picture_clone"); 1000 } 1001}; 1002 1003PictureRenderer* CreatePictureCloneRenderer() { 1004 return SkNEW(PictureCloneRenderer); 1005} 1006 1007} // namespace sk_tools 1008