• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "SkMultiPictureDraw.h"
26 #include "SkOSFile.h"
27 #include "SkPicture.h"
28 #include "SkPictureRecorder.h"
29 #include "SkPictureUtils.h"
30 #include "SkPixelRef.h"
31 #include "SkPixelSerializer.h"
32 #include "SkScalar.h"
33 #include "SkStream.h"
34 #include "SkString.h"
35 #include "SkSurface.h"
36 #include "SkTemplates.h"
37 #include "SkTDArray.h"
38 #include "SkThreadUtils.h"
39 #include "SkTypes.h"
40 #include "sk_tool_utils.h"
41 
scalar_log2(SkScalar x)42 static inline SkScalar scalar_log2(SkScalar x) {
43     static const SkScalar log2_conversion_factor = SkScalarInvert(SkScalarLog(2));
44 
45     return SkScalarLog(x) * log2_conversion_factor;
46 }
47 
48 namespace sk_tools {
49 
50 enum {
51     kDefaultTileWidth = 256,
52     kDefaultTileHeight = 256
53 };
54 
init(const SkPicture * pict,const SkString * writePath,const SkString * mismatchPath,const SkString * inputFilename,bool useChecksumBasedFilenames,bool useMultiPictureDraw)55 void PictureRenderer::init(const SkPicture* pict,
56                            const SkString* writePath,
57                            const SkString* mismatchPath,
58                            const SkString* inputFilename,
59                            bool useChecksumBasedFilenames,
60                            bool useMultiPictureDraw) {
61     this->CopyString(&fWritePath, writePath);
62     this->CopyString(&fMismatchPath, mismatchPath);
63     this->CopyString(&fInputFilename, inputFilename);
64     fUseChecksumBasedFilenames = useChecksumBasedFilenames;
65     fUseMultiPictureDraw = useMultiPictureDraw;
66 
67     SkASSERT(NULL == fPicture);
68     SkASSERT(NULL == fCanvas.get());
69     if (fPicture || fCanvas.get()) {
70         return;
71     }
72 
73     SkASSERT(pict != NULL);
74     if (NULL == pict) {
75         return;
76     }
77 
78     fPicture.reset(pict)->ref();
79     fCanvas.reset(this->setupCanvas());
80 }
81 
CopyString(SkString * dest,const SkString * src)82 void PictureRenderer::CopyString(SkString* dest, const SkString* src) {
83     if (src) {
84         dest->set(*src);
85     } else {
86         dest->reset();
87     }
88 }
89 
90 class FlagsDrawFilter : public SkDrawFilter {
91 public:
FlagsDrawFilter(PictureRenderer::DrawFilterFlags * flags)92     FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) :
93         fFlags(flags) {}
94 
filter(SkPaint * paint,Type t)95     virtual bool filter(SkPaint* paint, Type t) {
96         paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags);
97         if (PictureRenderer::kMaskFilter_DrawFilterFlag & fFlags[t]) {
98             SkMaskFilter* maskFilter = paint->getMaskFilter();
99             if (maskFilter) {
100                 paint->setMaskFilter(NULL);
101             }
102         }
103         if (PictureRenderer::kHinting_DrawFilterFlag & fFlags[t]) {
104             paint->setHinting(SkPaint::kNo_Hinting);
105         } else if (PictureRenderer::kSlightHinting_DrawFilterFlag & fFlags[t]) {
106             paint->setHinting(SkPaint::kSlight_Hinting);
107         }
108         return true;
109     }
110 
111 private:
112     PictureRenderer::DrawFilterFlags* fFlags;
113 };
114 
setUpFilter(SkCanvas * canvas,PictureRenderer::DrawFilterFlags * drawFilters)115 static void setUpFilter(SkCanvas* canvas, PictureRenderer::DrawFilterFlags* drawFilters) {
116     if (drawFilters && !canvas->getDrawFilter()) {
117         canvas->setDrawFilter(SkNEW_ARGS(FlagsDrawFilter, (drawFilters)))->unref();
118         if (drawFilters[0] & PictureRenderer::kAAClip_DrawFilterFlag) {
119             canvas->setAllowSoftClip(false);
120         }
121     }
122 }
123 
setupCanvas()124 SkCanvas* PictureRenderer::setupCanvas() {
125     const int width = this->getViewWidth();
126     const int height = this->getViewHeight();
127     return this->setupCanvas(width, height);
128 }
129 
setupCanvas(int width,int height)130 SkCanvas* PictureRenderer::setupCanvas(int width, int height) {
131     SkCanvas* canvas;
132     switch(fDeviceType) {
133         case kBitmap_DeviceType: {
134             SkBitmap bitmap;
135             sk_tools::setup_bitmap(&bitmap, width, height);
136             canvas = SkNEW_ARGS(SkCanvas, (bitmap));
137         }
138         break;
139 #if SK_SUPPORT_GPU
140 #if SK_ANGLE
141         case kAngle_DeviceType:
142             // fall through
143 #endif
144 #if SK_MESA
145         case kMesa_DeviceType:
146             // fall through
147 #endif
148         case kGPU_DeviceType:
149         case kNVPR_DeviceType: {
150             SkAutoTUnref<GrSurface> target;
151             if (fGrContext) {
152                 // create a render target to back the device
153                 GrSurfaceDesc desc;
154                 desc.fConfig = kSkia8888_GrPixelConfig;
155                 desc.fFlags = kRenderTarget_GrSurfaceFlag;
156                 desc.fWidth = width;
157                 desc.fHeight = height;
158                 desc.fSampleCnt = fSampleCount;
159                 target.reset(fGrContext->textureProvider()->createTexture(desc, false, NULL, 0));
160             }
161 
162             uint32_t flags = fUseDFText ? SkSurfaceProps::kUseDistanceFieldFonts_Flag : 0;
163             SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
164             SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(target->asRenderTarget(), &props));
165             if (!device) {
166                 return NULL;
167             }
168             canvas = SkNEW_ARGS(SkCanvas, (device));
169             break;
170         }
171 #endif
172         default:
173             SkASSERT(0);
174             return NULL;
175     }
176     setUpFilter(canvas, fDrawFilters);
177     this->scaleToScaleFactor(canvas);
178 
179     // Pictures often lie about their extent (i.e., claim to be 100x100 but
180     // only ever draw to 90x100). Clear here so the undrawn portion will have
181     // a consistent color
182     canvas->clear(SK_ColorTRANSPARENT);
183     return canvas;
184 }
185 
scaleToScaleFactor(SkCanvas * canvas)186 void PictureRenderer::scaleToScaleFactor(SkCanvas* canvas) {
187     SkASSERT(canvas != NULL);
188     if (fScaleFactor != SK_Scalar1) {
189         canvas->scale(fScaleFactor, fScaleFactor);
190     }
191 }
192 
end()193 void PictureRenderer::end() {
194     this->resetState(true);
195     fPicture.reset(NULL);
196     fCanvas.reset(NULL);
197 }
198 
getViewWidth()199 int PictureRenderer::getViewWidth() {
200     SkASSERT(fPicture != NULL);
201     int width = SkScalarCeilToInt(fPicture->cullRect().width() * fScaleFactor);
202     if (fViewport.width() > 0) {
203         width = SkMin32(width, fViewport.width());
204     }
205     return width;
206 }
207 
getViewHeight()208 int PictureRenderer::getViewHeight() {
209     SkASSERT(fPicture != NULL);
210     int height = SkScalarCeilToInt(fPicture->cullRect().height() * fScaleFactor);
211     if (fViewport.height() > 0) {
212         height = SkMin32(height, fViewport.height());
213     }
214     return height;
215 }
216 
217 /** Converts fPicture to a picture that uses a BBoxHierarchy.
218  *  PictureRenderer subclasses that are used to test picture playback
219  *  should call this method during init.
220  */
buildBBoxHierarchy()221 void PictureRenderer::buildBBoxHierarchy() {
222     SkASSERT(fPicture);
223     if (kNone_BBoxHierarchyType != fBBoxHierarchyType && fPicture) {
224         SkAutoTDelete<SkBBHFactory> factory(this->getFactory());
225         SkPictureRecorder recorder;
226         uint32_t flags = this->recordFlags();
227         if (fUseMultiPictureDraw) {
228             flags |= SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag;
229         }
230         SkCanvas* canvas = recorder.beginRecording(fPicture->cullRect().width(),
231                                                    fPicture->cullRect().height(),
232                                                    factory.get(),
233                                                    flags);
234         fPicture->playback(canvas);
235         fPicture.reset(recorder.endRecording());
236     }
237 }
238 
resetState(bool callFinish)239 void PictureRenderer::resetState(bool callFinish) {
240 #if SK_SUPPORT_GPU
241     SkGLContext* glContext = this->getGLContext();
242     if (NULL == glContext) {
243         SkASSERT(kBitmap_DeviceType == fDeviceType);
244         return;
245     }
246 
247     fGrContext->flush();
248     glContext->swapBuffers();
249     if (callFinish) {
250         SK_GL(*glContext, Finish());
251     }
252 #endif
253 }
254 
purgeTextures()255 void PictureRenderer::purgeTextures() {
256     SkDiscardableMemoryPool* pool = SkGetGlobalDiscardableMemoryPool();
257 
258     pool->dumpPool();
259 
260 #if SK_SUPPORT_GPU
261     SkGLContext* glContext = this->getGLContext();
262     if (NULL == glContext) {
263         SkASSERT(kBitmap_DeviceType == fDeviceType);
264         return;
265     }
266 
267     // resetState should've already done this
268     fGrContext->flush();
269 
270     fGrContext->purgeAllUnlockedResources();
271 #endif
272 }
273 
274 /**
275  * Write the canvas to an image file and/or JSON summary.
276  *
277  * @param canvas Must be non-null. Canvas to be written to a file.
278  * @param writePath If nonempty, write the binary image to a file within this directory.
279  * @param mismatchPath If nonempty, write the binary image to a file within this directory,
280  *     but only if the image does not match expectations.
281  * @param inputFilename If we are writing out a binary image, use this to build its filename.
282  * @param jsonSummaryPtr If not null, add image results (checksum) to this summary.
283  * @param useChecksumBasedFilenames If true, use checksum-based filenames when writing to disk.
284  * @param tileNumberPtr If not null, which tile number this image contains.
285  *
286  * @return bool True if the operation completed successfully.
287  */
write(SkCanvas * canvas,const SkString & writePath,const SkString & mismatchPath,const SkString & inputFilename,ImageResultsAndExpectations * jsonSummaryPtr,bool useChecksumBasedFilenames,const int * tileNumberPtr=NULL)288 static bool write(SkCanvas* canvas, const SkString& writePath, const SkString& mismatchPath,
289                   const SkString& inputFilename, ImageResultsAndExpectations *jsonSummaryPtr,
290                   bool useChecksumBasedFilenames, const int* tileNumberPtr=NULL) {
291     SkASSERT(canvas != NULL);
292     if (NULL == canvas) {
293         return false;
294     }
295 
296     SkBitmap bitmap;
297     SkISize size = canvas->getDeviceSize();
298     setup_bitmap(&bitmap, size.width(), size.height());
299 
300     canvas->readPixels(&bitmap, 0, 0);
301     force_all_opaque(bitmap);
302     BitmapAndDigest bitmapAndDigest(bitmap);
303 
304     SkString escapedInputFilename(inputFilename);
305     replace_char(&escapedInputFilename, '.', '_');
306 
307     // TODO(epoger): what about including the config type within outputFilename?  That way,
308     // we could combine results of different config types without conflicting filenames.
309     SkString outputFilename;
310     const char *outputSubdirPtr = NULL;
311     if (useChecksumBasedFilenames) {
312         ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr();
313         outputSubdirPtr = escapedInputFilename.c_str();
314         outputFilename.set(imageDigestPtr->getHashType());
315         outputFilename.append("_");
316         outputFilename.appendU64(imageDigestPtr->getHashValue());
317     } else {
318         outputFilename.set(escapedInputFilename);
319         if (tileNumberPtr) {
320             outputFilename.append("-tile");
321             outputFilename.appendS32(*tileNumberPtr);
322         }
323     }
324     outputFilename.append(".png");
325 
326     if (jsonSummaryPtr) {
327         ImageDigest *imageDigestPtr = bitmapAndDigest.getImageDigestPtr();
328         SkString outputRelativePath;
329         if (outputSubdirPtr) {
330             outputRelativePath.set(outputSubdirPtr);
331             outputRelativePath.append("/");  // always use "/", even on Windows
332             outputRelativePath.append(outputFilename);
333         } else {
334             outputRelativePath.set(outputFilename);
335         }
336 
337         jsonSummaryPtr->add(inputFilename.c_str(), outputRelativePath.c_str(),
338                             *imageDigestPtr, tileNumberPtr);
339         if (!mismatchPath.isEmpty() &&
340             !jsonSummaryPtr->getExpectation(inputFilename.c_str(),
341                                             tileNumberPtr).matches(*imageDigestPtr)) {
342             if (!write_bitmap_to_disk(bitmap, mismatchPath, outputSubdirPtr, outputFilename)) {
343                 return false;
344             }
345         }
346     }
347 
348     if (writePath.isEmpty()) {
349         return true;
350     } else {
351         return write_bitmap_to_disk(bitmap, writePath, outputSubdirPtr, outputFilename);
352     }
353 }
354 
355 ///////////////////////////////////////////////////////////////////////////////////////////////
356 
setupCanvas(int width,int height)357 SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) {
358     // defer the canvas setup until the render step
359     return NULL;
360 }
361 
render(SkBitmap ** out)362 bool RecordPictureRenderer::render(SkBitmap** out) {
363     SkAutoTDelete<SkBBHFactory> factory(this->getFactory());
364     SkPictureRecorder recorder;
365     SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(this->getViewWidth()),
366                                                SkIntToScalar(this->getViewHeight()),
367                                                factory.get(),
368                                                this->recordFlags());
369     this->scaleToScaleFactor(canvas);
370     fPicture->playback(canvas);
371     SkAutoTUnref<SkPicture> picture(recorder.endRecording());
372     if (!fWritePath.isEmpty()) {
373         // Record the new picture as a new SKP with PNG encoded bitmaps.
374         SkString skpPath = SkOSPath::Join(fWritePath.c_str(), fInputFilename.c_str());
375         SkFILEWStream stream(skpPath.c_str());
376         sk_tool_utils::PngPixelSerializer serializer;
377         picture->serialize(&stream, &serializer);
378         return true;
379     }
380     return false;
381 }
382 
getConfigNameInternal()383 SkString RecordPictureRenderer::getConfigNameInternal() {
384     return SkString("record");
385 }
386 
387 ///////////////////////////////////////////////////////////////////////////////////////////////
388 
render(SkBitmap ** out)389 bool PipePictureRenderer::render(SkBitmap** out) {
390     SkASSERT(fCanvas.get() != NULL);
391     SkASSERT(fPicture != NULL);
392     if (NULL == fCanvas.get() || NULL == fPicture) {
393         return false;
394     }
395 
396     PipeController pipeController(fCanvas.get());
397     SkGPipeWriter writer;
398     SkCanvas* pipeCanvas = writer.startRecording(&pipeController);
399     pipeCanvas->drawPicture(fPicture);
400     writer.endRecording();
401     fCanvas->flush();
402     if (out) {
403         *out = SkNEW(SkBitmap);
404         setup_bitmap(*out, SkScalarCeilToInt(fPicture->cullRect().width()),
405                            SkScalarCeilToInt(fPicture->cullRect().height()));
406         fCanvas->readPixels(*out, 0, 0);
407     }
408     if (fEnableWrites) {
409         return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
410                      fUseChecksumBasedFilenames);
411     } else {
412         return true;
413     }
414 }
415 
getConfigNameInternal()416 SkString PipePictureRenderer::getConfigNameInternal() {
417     return SkString("pipe");
418 }
419 
420 ///////////////////////////////////////////////////////////////////////////////////////////////
421 
init(const SkPicture * picture,const SkString * writePath,const SkString * mismatchPath,const SkString * inputFilename,bool useChecksumBasedFilenames,bool useMultiPictureDraw)422 void SimplePictureRenderer::init(const SkPicture* picture, const SkString* writePath,
423                                  const SkString* mismatchPath, const SkString* inputFilename,
424                                  bool useChecksumBasedFilenames, bool useMultiPictureDraw) {
425     INHERITED::init(picture, writePath, mismatchPath, inputFilename,
426                     useChecksumBasedFilenames, useMultiPictureDraw);
427     this->buildBBoxHierarchy();
428 }
429 
render(SkBitmap ** out)430 bool SimplePictureRenderer::render(SkBitmap** out) {
431     SkASSERT(fCanvas.get() != NULL);
432     SkASSERT(fPicture);
433     if (NULL == fCanvas.get() || NULL == fPicture) {
434         return false;
435     }
436 
437     if (fUseMultiPictureDraw) {
438         SkMultiPictureDraw mpd;
439 
440         mpd.add(fCanvas, fPicture);
441 
442         mpd.draw();
443     } else {
444         fCanvas->drawPicture(fPicture);
445     }
446     fCanvas->flush();
447     if (out) {
448         *out = SkNEW(SkBitmap);
449         setup_bitmap(*out, SkScalarCeilToInt(fPicture->cullRect().width()),
450                            SkScalarCeilToInt(fPicture->cullRect().height()));
451         fCanvas->readPixels(*out, 0, 0);
452     }
453     if (fEnableWrites) {
454         return write(fCanvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
455                      fUseChecksumBasedFilenames);
456     } else {
457         return true;
458     }
459 }
460 
getConfigNameInternal()461 SkString SimplePictureRenderer::getConfigNameInternal() {
462     return SkString("simple");
463 }
464 
465 ///////////////////////////////////////////////////////////////////////////////////////////////
466 
467 #if SK_SUPPORT_GPU
TiledPictureRenderer(const GrContext::Options & opts)468 TiledPictureRenderer::TiledPictureRenderer(const GrContext::Options& opts)
469     : INHERITED(opts)
470     , fTileWidth(kDefaultTileWidth)
471 #else
472 TiledPictureRenderer::TiledPictureRenderer()
473     : fTileWidth(kDefaultTileWidth)
474 #endif
475     , fTileHeight(kDefaultTileHeight)
476     , fTileWidthPercentage(0.0)
477     , fTileHeightPercentage(0.0)
478     , fTileMinPowerOf2Width(0)
479     , fCurrentTileOffset(-1)
480     , fTilesX(0)
481     , fTilesY(0) { }
482 
init(const SkPicture * pict,const SkString * writePath,const SkString * mismatchPath,const SkString * inputFilename,bool useChecksumBasedFilenames,bool useMultiPictureDraw)483 void TiledPictureRenderer::init(const SkPicture* pict, const SkString* writePath,
484                                 const SkString* mismatchPath, const SkString* inputFilename,
485                                 bool useChecksumBasedFilenames, bool useMultiPictureDraw) {
486     SkASSERT(pict);
487     SkASSERT(0 == fTileRects.count());
488     if (NULL == pict || fTileRects.count() != 0) {
489         return;
490     }
491 
492     // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not
493     // used by bench_pictures.
494     fPicture.reset(pict)->ref();
495     this->CopyString(&fWritePath, writePath);
496     this->CopyString(&fMismatchPath, mismatchPath);
497     this->CopyString(&fInputFilename, inputFilename);
498     fUseChecksumBasedFilenames = useChecksumBasedFilenames;
499     fUseMultiPictureDraw = useMultiPictureDraw;
500     this->buildBBoxHierarchy();
501 
502     if (fTileWidthPercentage > 0) {
503         fTileWidth = SkScalarCeilToInt(float(fTileWidthPercentage * fPicture->cullRect().width() / 100));
504     }
505     if (fTileHeightPercentage > 0) {
506         fTileHeight = SkScalarCeilToInt(float(fTileHeightPercentage * fPicture->cullRect().height() / 100));
507     }
508 
509     if (fTileMinPowerOf2Width > 0) {
510         this->setupPowerOf2Tiles();
511     } else {
512         this->setupTiles();
513     }
514     fCanvas.reset(this->setupCanvas(fTileWidth, fTileHeight));
515     // Initialize to -1 so that the first call to nextTile will set this up to draw tile 0 on the
516     // first call to drawCurrentTile.
517     fCurrentTileOffset = -1;
518 }
519 
end()520 void TiledPictureRenderer::end() {
521     fTileRects.reset();
522     this->INHERITED::end();
523 }
524 
setupTiles()525 void TiledPictureRenderer::setupTiles() {
526     // Only use enough tiles to cover the viewport
527     const int width = this->getViewWidth();
528     const int height = this->getViewHeight();
529 
530     fTilesX = fTilesY = 0;
531     for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) {
532         fTilesY++;
533         for (int tile_x_start = 0; tile_x_start < width; tile_x_start += fTileWidth) {
534             if (0 == tile_y_start) {
535                 // Only count tiles in the X direction on the first pass.
536                 fTilesX++;
537             }
538             *fTileRects.append() = SkIRect::MakeXYWH(tile_x_start, tile_y_start,
539                                                      fTileWidth, fTileHeight);
540         }
541     }
542 }
543 
tileDimensions(int & x,int & y)544 bool TiledPictureRenderer::tileDimensions(int &x, int &y) {
545     if (fTileRects.count() == 0 || NULL == fPicture) {
546         return false;
547     }
548     x = fTilesX;
549     y = fTilesY;
550     return true;
551 }
552 
553 // The goal of the powers of two tiles is to minimize the amount of wasted tile
554 // space in the width-wise direction and then minimize the number of tiles. The
555 // constraints are that every tile must have a pixel width that is a power of
556 // two and also be of some minimal width (that is also a power of two).
557 //
558 // This is solved by first taking our picture size and rounding it up to the
559 // multiple of the minimal width. The binary representation of this rounded
560 // value gives us the tiles we need: a bit of value one means we need a tile of
561 // that size.
setupPowerOf2Tiles()562 void TiledPictureRenderer::setupPowerOf2Tiles() {
563     // Only use enough tiles to cover the viewport
564     const int width = this->getViewWidth();
565     const int height = this->getViewHeight();
566 
567     int rounded_value = width;
568     if (width % fTileMinPowerOf2Width != 0) {
569         rounded_value = width - (width % fTileMinPowerOf2Width) + fTileMinPowerOf2Width;
570     }
571 
572     int num_bits = SkScalarCeilToInt(scalar_log2(SkIntToScalar(width)));
573     int largest_possible_tile_size = 1 << num_bits;
574 
575     fTilesX = fTilesY = 0;
576     // The tile height is constant for a particular picture.
577     for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) {
578         fTilesY++;
579         int tile_x_start = 0;
580         int current_width = largest_possible_tile_size;
581         // Set fTileWidth to be the width of the widest tile, so that each canvas is large enough
582         // to draw each tile.
583         fTileWidth = current_width;
584 
585         while (current_width >= fTileMinPowerOf2Width) {
586             // It is very important this is a bitwise AND.
587             if (current_width & rounded_value) {
588                 if (0 == tile_y_start) {
589                     // Only count tiles in the X direction on the first pass.
590                     fTilesX++;
591                 }
592                 *fTileRects.append() = SkIRect::MakeXYWH(tile_x_start, tile_y_start,
593                                                          current_width, fTileHeight);
594                 tile_x_start += current_width;
595             }
596 
597             current_width >>= 1;
598         }
599     }
600 }
601 
602 /**
603  * Draw the specified picture to the canvas translated to rectangle provided, so that this mini
604  * canvas represents the rectangle's portion of the overall picture.
605  * Saves and restores so that the initial clip and matrix return to their state before this function
606  * is called.
607  */
draw_tile_to_canvas(SkCanvas * canvas,const SkIRect & tileRect,const SkPicture * picture)608 static void draw_tile_to_canvas(SkCanvas* canvas,
609                                 const SkIRect& tileRect,
610                                 const SkPicture* picture) {
611     int saveCount = canvas->save();
612     // Translate so that we draw the correct portion of the picture.
613     // Perform a postTranslate so that the scaleFactor does not interfere with the positioning.
614     SkMatrix mat(canvas->getTotalMatrix());
615     mat.postTranslate(-SkIntToScalar(tileRect.fLeft), -SkIntToScalar(tileRect.fTop));
616     canvas->setMatrix(mat);
617     canvas->clipRect(SkRect::Make(tileRect));
618     canvas->clear(SK_ColorTRANSPARENT); // Not every picture covers the entirety of every tile
619     canvas->drawPicture(picture);
620     canvas->restoreToCount(saveCount);
621     canvas->flush();
622 }
623 
624 ///////////////////////////////////////////////////////////////////////////////////////////////
625 
626 /**
627  * Copies the entirety of the src bitmap (typically a tile) into a portion of the dst bitmap.
628  * If the src bitmap is too large to fit within the dst bitmap after the x and y
629  * offsets have been applied, any excess will be ignored (so only the top-left portion of the
630  * src bitmap will be copied).
631  *
632  * @param src source bitmap
633  * @param dst destination bitmap
634  * @param xOffset x-offset within destination bitmap
635  * @param yOffset y-offset within destination bitmap
636  */
bitmapCopyAtOffset(const SkBitmap & src,SkBitmap * dst,int xOffset,int yOffset)637 static void bitmapCopyAtOffset(const SkBitmap& src, SkBitmap* dst,
638                                int xOffset, int yOffset) {
639     for (int y = 0; y <src.height() && y + yOffset < dst->height() ; y++) {
640         for (int x = 0; x < src.width() && x + xOffset < dst->width() ; x++) {
641             *dst->getAddr32(xOffset + x, yOffset + y) = *src.getAddr32(x, y);
642         }
643     }
644 }
645 
nextTile(int & i,int & j)646 bool TiledPictureRenderer::nextTile(int &i, int &j) {
647     if (++fCurrentTileOffset < fTileRects.count()) {
648         i = fCurrentTileOffset % fTilesX;
649         j = fCurrentTileOffset / fTilesX;
650         return true;
651     }
652     return false;
653 }
654 
drawCurrentTile()655 void TiledPictureRenderer::drawCurrentTile() {
656     SkASSERT(fCurrentTileOffset >= 0 && fCurrentTileOffset < fTileRects.count());
657     draw_tile_to_canvas(fCanvas, fTileRects[fCurrentTileOffset], fPicture);
658 }
659 
postRender(SkCanvas * canvas,const SkIRect & tileRect,SkBitmap * tempBM,SkBitmap ** out,int tileNumber)660 bool TiledPictureRenderer::postRender(SkCanvas* canvas, const SkIRect& tileRect,
661                                       SkBitmap* tempBM, SkBitmap** out,
662                                       int tileNumber) {
663     bool success = true;
664 
665     if (fEnableWrites) {
666         success &= write(canvas, fWritePath, fMismatchPath, fInputFilename, fJsonSummaryPtr,
667                          fUseChecksumBasedFilenames, &tileNumber);
668     }
669     if (out) {
670         if (canvas->readPixels(tempBM, 0, 0)) {
671             // Add this tile to the entire bitmap.
672             bitmapCopyAtOffset(*tempBM, *out, tileRect.left(), tileRect.top());
673         } else {
674             success = false;
675         }
676     }
677 
678     return success;
679 }
680 
render(SkBitmap ** out)681 bool TiledPictureRenderer::render(SkBitmap** out) {
682     SkASSERT(fPicture != NULL);
683     if (NULL == fPicture) {
684         return false;
685     }
686 
687     SkBitmap bitmap;
688     if (out) {
689         *out = SkNEW(SkBitmap);
690         setup_bitmap(*out, SkScalarCeilToInt(fPicture->cullRect().width()),
691                            SkScalarCeilToInt(fPicture->cullRect().height()));
692         setup_bitmap(&bitmap, fTileWidth, fTileHeight);
693     }
694     bool success = true;
695 
696     if (fUseMultiPictureDraw) {
697         SkMultiPictureDraw mpd;
698         SkTDArray<SkSurface*> surfaces;
699         surfaces.setReserve(fTileRects.count());
700 
701         // Create a separate SkSurface/SkCanvas for each tile along with a
702         // translated version of the skp (to mimic Chrome's behavior) and
703         // feed all such pairs to the MultiPictureDraw.
704         for (int i = 0; i < fTileRects.count(); ++i) {
705             SkImageInfo ii = fCanvas->imageInfo().makeWH(fTileRects[i].width(),
706                                                          fTileRects[i].height());
707             *surfaces.append() = fCanvas->newSurface(ii);
708             surfaces[i]->getCanvas()->setMatrix(fCanvas->getTotalMatrix());
709 
710             SkPictureRecorder recorder;
711             SkRTreeFactory bbhFactory;
712 
713             SkCanvas* c = recorder.beginRecording(SkIntToScalar(fTileRects[i].width()),
714                                                   SkIntToScalar(fTileRects[i].height()),
715                                                   &bbhFactory,
716                                                   SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag);
717             c->save();
718             SkMatrix mat;
719             mat.setTranslate(-SkIntToScalar(fTileRects[i].fLeft),
720                              -SkIntToScalar(fTileRects[i].fTop));
721             c->setMatrix(mat);
722             c->drawPicture(fPicture);
723             c->restore();
724 
725             SkAutoTUnref<SkPicture> xlatedPicture(recorder.endRecording());
726 
727             mpd.add(surfaces[i]->getCanvas(), xlatedPicture);
728         }
729 
730         // Render all the buffered SkCanvases/SkPictures
731         mpd.draw();
732 
733         // Sort out the results and cleanup the allocated surfaces
734         for (int i = 0; i < fTileRects.count(); ++i) {
735             success &= this->postRender(surfaces[i]->getCanvas(), fTileRects[i], &bitmap, out, i);
736             surfaces[i]->unref();
737         }
738     } else {
739         for (int i = 0; i < fTileRects.count(); ++i) {
740             draw_tile_to_canvas(fCanvas, fTileRects[i], fPicture);
741             success &= this->postRender(fCanvas, fTileRects[i], &bitmap, out, i);
742         }
743     }
744 
745     return success;
746 }
747 
setupCanvas(int width,int height)748 SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) {
749     SkCanvas* canvas = this->INHERITED::setupCanvas(width, height);
750     SkASSERT(fPicture);
751     // Clip the tile to an area that is completely inside both the SkPicture and the viewport. This
752     // is mostly important for tiles on the right and bottom edges as they may go over this area and
753     // the picture may have some commands that draw outside of this area and so should not actually
754     // be written.
755     // Uses a clipRegion so that it will be unaffected by the scale factor, which may have been set
756     // by INHERITED::setupCanvas.
757     SkRegion clipRegion;
758     clipRegion.setRect(0, 0, this->getViewWidth(), this->getViewHeight());
759     canvas->clipRegion(clipRegion);
760     return canvas;
761 }
762 
getConfigNameInternal()763 SkString TiledPictureRenderer::getConfigNameInternal() {
764     SkString name;
765     if (fTileMinPowerOf2Width > 0) {
766         name.append("pow2tile_");
767         name.appendf("%i", fTileMinPowerOf2Width);
768     } else {
769         name.append("tile_");
770         if (fTileWidthPercentage > 0) {
771             name.appendf("%.f%%", fTileWidthPercentage);
772         } else {
773             name.appendf("%i", fTileWidth);
774         }
775     }
776     name.append("x");
777     if (fTileHeightPercentage > 0) {
778         name.appendf("%.f%%", fTileHeightPercentage);
779     } else {
780         name.appendf("%i", fTileHeight);
781     }
782     return name;
783 }
784 
785 ///////////////////////////////////////////////////////////////////////////////////////////////
786 
setup()787 void PlaybackCreationRenderer::setup() {
788     SkAutoTDelete<SkBBHFactory> factory(this->getFactory());
789     fRecorder.reset(SkNEW(SkPictureRecorder));
790     SkCanvas* canvas = fRecorder->beginRecording(SkIntToScalar(this->getViewWidth()),
791                                                  SkIntToScalar(this->getViewHeight()),
792                                                  factory.get(),
793                                                  this->recordFlags());
794     this->scaleToScaleFactor(canvas);
795     canvas->drawPicture(fPicture);
796 }
797 
render(SkBitmap ** out)798 bool PlaybackCreationRenderer::render(SkBitmap** out) {
799     fPicture.reset(fRecorder->endRecording());
800     // Since this class does not actually render, return false.
801     return false;
802 }
803 
getConfigNameInternal()804 SkString PlaybackCreationRenderer::getConfigNameInternal() {
805     return SkString("playback_creation");
806 }
807 
808 ///////////////////////////////////////////////////////////////////////////////////////////////
809 // SkPicture variants for each BBoxHierarchy type
810 
getFactory()811 SkBBHFactory* PictureRenderer::getFactory() {
812     switch (fBBoxHierarchyType) {
813         case kNone_BBoxHierarchyType:
814             return NULL;
815         case kRTree_BBoxHierarchyType:
816             return SkNEW(SkRTreeFactory);
817     }
818     SkASSERT(0); // invalid bbhType
819     return NULL;
820 }
821 
822 } // namespace sk_tools
823