PictureRenderer.cpp revision 3cb834bd27a16cc60ff30adae96659558c2dc91f
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 "SkCanvas.h"
12#include "SkDevice.h"
13#include "SkGPipe.h"
14#if SK_SUPPORT_GPU
15#include "SkGpuDevice.h"
16#endif
17#include "SkGraphics.h"
18#include "SkImageEncoder.h"
19#include "SkMaskFilter.h"
20#include "SkMatrix.h"
21#include "SkPicture.h"
22#include "SkRTree.h"
23#include "SkScalar.h"
24#include "SkStream.h"
25#include "SkString.h"
26#include "SkTemplates.h"
27#include "SkTileGridPicture.h"
28#include "SkTDArray.h"
29#include "SkThreadUtils.h"
30#include "SkTypes.h"
31#include "SkData.h"
32#include "SkPictureUtils.h"
33
34namespace sk_tools {
35
36enum {
37    kDefaultTileWidth = 256,
38    kDefaultTileHeight = 256
39};
40
41void PictureRenderer::init(SkPicture* pict) {
42    SkASSERT(NULL == fPicture);
43    SkASSERT(NULL == fCanvas.get());
44    if (fPicture != NULL || NULL != fCanvas.get()) {
45        return;
46    }
47
48    SkASSERT(pict != NULL);
49    if (NULL == pict) {
50        return;
51    }
52
53    fPicture = pict;
54    fPicture->ref();
55    fCanvas.reset(this->setupCanvas());
56}
57
58class FlagsDrawFilter : public SkDrawFilter {
59public:
60    FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) :
61        fFlags(flags) {}
62
63    virtual bool filter(SkPaint* paint, Type t) {
64        paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags);
65        if ((PictureRenderer::kBlur_DrawFilterFlag | PictureRenderer::kLowBlur_DrawFilterFlag)
66                & fFlags[t]) {
67            SkMaskFilter* maskFilter = paint->getMaskFilter();
68            SkMaskFilter::BlurInfo blurInfo;
69            if (maskFilter && maskFilter->asABlur(&blurInfo)) {
70                if (PictureRenderer::kBlur_DrawFilterFlag & fFlags[t]) {
71                    paint->setMaskFilter(NULL);
72                } else {
73                    blurInfo.fHighQuality = false;
74                    maskFilter->setAsABlur(blurInfo);
75                }
76            }
77        }
78        if (PictureRenderer::kHinting_DrawFilterFlag & fFlags[t]) {
79            paint->setHinting(SkPaint::kNo_Hinting);
80        } else if (PictureRenderer::kSlightHinting_DrawFilterFlag & fFlags[t]) {
81            paint->setHinting(SkPaint::kSlight_Hinting);
82        }
83        return true;
84    }
85
86private:
87    PictureRenderer::DrawFilterFlags* fFlags;
88};
89
90static SkCanvas* setUpFilter(SkCanvas* canvas, PictureRenderer::DrawFilterFlags* drawFilters) {
91    if (drawFilters && !canvas->getDrawFilter()) {
92        canvas->setDrawFilter(SkNEW_ARGS(FlagsDrawFilter, (drawFilters)))->unref();
93        if (drawFilters[0] & PictureRenderer::kAAClip_DrawFilterFlag) {
94            canvas->setAllowSoftClip(false);
95        }
96    }
97    return canvas;
98}
99
100SkCanvas* PictureRenderer::setupCanvas() {
101    return this->setupCanvas(fPicture->width(), fPicture->height());
102}
103
104SkCanvas* PictureRenderer::setupCanvas(int width, int height) {
105    SkCanvas* canvas;
106    switch(fDeviceType) {
107        case kBitmap_DeviceType: {
108            SkBitmap bitmap;
109            sk_tools::setup_bitmap(&bitmap, width, height);
110            canvas = SkNEW_ARGS(SkCanvas, (bitmap));
111            return setUpFilter(canvas, fDrawFilters);
112        }
113#if SK_SUPPORT_GPU
114        case kGPU_DeviceType: {
115            SkAutoTUnref<SkGpuDevice> device(SkNEW_ARGS(SkGpuDevice,
116                                                    (fGrContext, SkBitmap::kARGB_8888_Config,
117                                                    width, height)));
118            canvas = SkNEW_ARGS(SkCanvas, (device.get()));
119            return setUpFilter(canvas, fDrawFilters);
120        }
121#endif
122        default:
123            SkASSERT(0);
124    }
125
126    return NULL;
127}
128
129void PictureRenderer::end() {
130    this->resetState();
131    SkSafeUnref(fPicture);
132    fPicture = NULL;
133    fCanvas.reset(NULL);
134}
135
136/** Converts fPicture to a picture that uses a BBoxHierarchy.
137 *  PictureRenderer subclasses that are used to test picture playback
138 *  should call this method during init.
139 */
140void PictureRenderer::buildBBoxHierarchy() {
141    SkASSERT(NULL != fPicture);
142    if (kNone_BBoxHierarchyType != fBBoxHierarchyType && NULL != fPicture) {
143        SkPicture* newPicture = this->createPicture();
144        SkCanvas* recorder = newPicture->beginRecording(fPicture->width(), fPicture->height(),
145                                                        this->recordFlags());
146        fPicture->draw(recorder);
147        newPicture->endRecording();
148        fPicture->unref();
149        fPicture = newPicture;
150    }
151}
152
153void PictureRenderer::resetState() {
154#if SK_SUPPORT_GPU
155    if (this->isUsingGpuDevice()) {
156        SkGLContext* glContext = fGrContextFactory.getGLContext(
157            GrContextFactory::kNative_GLContextType);
158
159        SkASSERT(glContext != NULL);
160        if (NULL == glContext) {
161            return;
162        }
163
164        fGrContext->flush();
165        SK_GL(*glContext, Finish());
166    }
167#endif
168}
169
170uint32_t PictureRenderer::recordFlags() {
171    return kNone_BBoxHierarchyType == fBBoxHierarchyType ? 0 :
172        SkPicture::kOptimizeForClippedPlayback_RecordingFlag;
173}
174
175/**
176 * Write the canvas to the specified path.
177 * @param canvas Must be non-null. Canvas to be written to a file.
178 * @param path Path for the file to be written. Should have no extension; write() will append
179 *             an appropriate one. Passed in by value so it can be modified.
180 * @return bool True if the Canvas is written to a file.
181 */
182static bool write(SkCanvas* canvas, SkString path) {
183    SkASSERT(canvas != NULL);
184    if (NULL == canvas) {
185        return false;
186    }
187
188    SkBitmap bitmap;
189    SkISize size = canvas->getDeviceSize();
190    sk_tools::setup_bitmap(&bitmap, size.width(), size.height());
191
192    canvas->readPixels(&bitmap, 0, 0);
193    sk_tools::force_all_opaque(bitmap);
194
195    // Since path is passed in by value, it is okay to modify it.
196    path.append(".png");
197    return SkImageEncoder::EncodeFile(path.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
198}
199
200/**
201 * If path is non NULL, append number to it, and call write(SkCanvas*, SkString) to write the
202 * provided canvas to a file. Returns true if path is NULL or if write() succeeds.
203 */
204static bool writeAppendNumber(SkCanvas* canvas, const SkString* path, int number) {
205    if (NULL == path) {
206        return true;
207    }
208    SkString pathWithNumber(*path);
209    pathWithNumber.appendf("%i", number);
210    return write(canvas, pathWithNumber);
211}
212
213///////////////////////////////////////////////////////////////////////////////////////////////
214
215SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) {
216    // defer the canvas setup until the render step
217    return NULL;
218}
219
220static bool PNGEncodeBitmapToStream(SkWStream* wStream, const SkBitmap& bm) {
221    return SkImageEncoder::EncodeStream(wStream, bm, SkImageEncoder::kPNG_Type, 100);
222}
223
224bool RecordPictureRenderer::render(const SkString* path) {
225    SkAutoTUnref<SkPicture> replayer(this->createPicture());
226    SkCanvas* recorder = replayer->beginRecording(fPicture->width(), fPicture->height(),
227                                                  this->recordFlags());
228    fPicture->draw(recorder);
229    replayer->endRecording();
230    if (path != NULL) {
231        // Record the new picture as a new SKP with PNG encoded bitmaps.
232        SkString skpPath(*path);
233        // ".skp" was removed from 'path' before being passed in here.
234        skpPath.append(".skp");
235        SkFILEWStream stream(skpPath.c_str());
236        replayer->serialize(&stream, &PNGEncodeBitmapToStream);
237        return true;
238    }
239    return false;
240}
241
242SkString RecordPictureRenderer::getConfigNameInternal() {
243    return SkString("record");
244}
245
246///////////////////////////////////////////////////////////////////////////////////////////////
247
248bool PipePictureRenderer::render(const SkString* path) {
249    SkASSERT(fCanvas.get() != NULL);
250    SkASSERT(fPicture != NULL);
251    if (NULL == fCanvas.get() || NULL == fPicture) {
252        return false;
253    }
254
255    PipeController pipeController(fCanvas.get());
256    SkGPipeWriter writer;
257    SkCanvas* pipeCanvas = writer.startRecording(&pipeController);
258    pipeCanvas->drawPicture(*fPicture);
259    writer.endRecording();
260    fCanvas->flush();
261    if (NULL != path) {
262        return write(fCanvas, *path);
263    }
264    return true;
265}
266
267SkString PipePictureRenderer::getConfigNameInternal() {
268    return SkString("pipe");
269}
270
271///////////////////////////////////////////////////////////////////////////////////////////////
272
273void SimplePictureRenderer::init(SkPicture* picture) {
274    INHERITED::init(picture);
275    this->buildBBoxHierarchy();
276}
277
278bool SimplePictureRenderer::render(const SkString* path) {
279    SkASSERT(fCanvas.get() != NULL);
280    SkASSERT(fPicture != NULL);
281    if (NULL == fCanvas.get() || NULL == fPicture) {
282        return false;
283    }
284
285    fCanvas->drawPicture(*fPicture);
286    fCanvas->flush();
287    if (NULL != path) {
288        return write(fCanvas, *path);
289    }
290    return true;
291}
292
293SkString SimplePictureRenderer::getConfigNameInternal() {
294    return SkString("simple");
295}
296
297///////////////////////////////////////////////////////////////////////////////////////////////
298
299TiledPictureRenderer::TiledPictureRenderer()
300    : fTileWidth(kDefaultTileWidth)
301    , fTileHeight(kDefaultTileHeight)
302    , fTileWidthPercentage(0.0)
303    , fTileHeightPercentage(0.0)
304    , fTileMinPowerOf2Width(0) { }
305
306void TiledPictureRenderer::init(SkPicture* pict) {
307    SkASSERT(pict != NULL);
308    SkASSERT(0 == fTileRects.count());
309    if (NULL == pict || fTileRects.count() != 0) {
310        return;
311    }
312
313    // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not
314    // used by bench_pictures.
315    fPicture = pict;
316    fPicture->ref();
317    this->buildBBoxHierarchy();
318
319    if (fTileWidthPercentage > 0) {
320        fTileWidth = sk_float_ceil2int(float(fTileWidthPercentage * fPicture->width() / 100));
321    }
322    if (fTileHeightPercentage > 0) {
323        fTileHeight = sk_float_ceil2int(float(fTileHeightPercentage * fPicture->height() / 100));
324    }
325
326    if (fTileMinPowerOf2Width > 0) {
327        this->setupPowerOf2Tiles();
328    } else {
329        this->setupTiles();
330    }
331}
332
333void TiledPictureRenderer::end() {
334    fTileRects.reset();
335    this->INHERITED::end();
336}
337
338void TiledPictureRenderer::setupTiles() {
339    for (int tile_y_start = 0; tile_y_start < fPicture->height(); tile_y_start += fTileHeight) {
340        for (int tile_x_start = 0; tile_x_start < fPicture->width(); tile_x_start += fTileWidth) {
341            *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
342                                                    SkIntToScalar(tile_y_start),
343                                                    SkIntToScalar(fTileWidth),
344                                                    SkIntToScalar(fTileHeight));
345        }
346    }
347}
348
349// The goal of the powers of two tiles is to minimize the amount of wasted tile
350// space in the width-wise direction and then minimize the number of tiles. The
351// constraints are that every tile must have a pixel width that is a power of
352// two and also be of some minimal width (that is also a power of two).
353//
354// This is solved by first taking our picture size and rounding it up to the
355// multiple of the minimal width. The binary representation of this rounded
356// value gives us the tiles we need: a bit of value one means we need a tile of
357// that size.
358void TiledPictureRenderer::setupPowerOf2Tiles() {
359    int rounded_value = fPicture->width();
360    if (fPicture->width() % fTileMinPowerOf2Width != 0) {
361        rounded_value = fPicture->width() - (fPicture->width() % fTileMinPowerOf2Width)
362            + fTileMinPowerOf2Width;
363    }
364
365    int num_bits = SkScalarCeilToInt(SkScalarLog2(SkIntToScalar(fPicture->width())));
366    int largest_possible_tile_size = 1 << num_bits;
367
368    // The tile height is constant for a particular picture.
369    for (int tile_y_start = 0; tile_y_start < fPicture->height(); tile_y_start += fTileHeight) {
370        int tile_x_start = 0;
371        int current_width = largest_possible_tile_size;
372        // Set fTileWidth to be the width of the widest tile, so that each canvas is large enough
373        // to draw each tile.
374        fTileWidth = current_width;
375
376        while (current_width >= fTileMinPowerOf2Width) {
377            // It is very important this is a bitwise AND.
378            if (current_width & rounded_value) {
379                *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
380                                                        SkIntToScalar(tile_y_start),
381                                                        SkIntToScalar(current_width),
382                                                        SkIntToScalar(fTileHeight));
383                tile_x_start += current_width;
384            }
385
386            current_width >>= 1;
387        }
388    }
389}
390
391/**
392 * Draw the specified playback to the canvas translated to rectangle provided, so that this mini
393 * canvas represents the rectangle's portion of the overall picture.
394 * Saves and restores so that the initial clip and matrix return to their state before this function
395 * is called.
396 */
397template<class T>
398static void DrawTileToCanvas(SkCanvas* canvas, const SkRect& tileRect, T* playback) {
399    int saveCount = canvas->save();
400    // Translate so that we draw the correct portion of the picture
401    canvas->translate(-tileRect.fLeft, -tileRect.fTop);
402    playback->draw(canvas);
403    canvas->restoreToCount(saveCount);
404    canvas->flush();
405}
406
407///////////////////////////////////////////////////////////////////////////////////////////////
408
409bool TiledPictureRenderer::render(const SkString* path) {
410    SkASSERT(fPicture != NULL);
411    if (NULL == fPicture) {
412        return false;
413    }
414
415    // Reuse one canvas for all tiles.
416    SkCanvas* canvas = this->setupCanvas(fTileWidth, fTileHeight);
417    SkAutoUnref aur(canvas);
418
419    bool success = true;
420    for (int i = 0; i < fTileRects.count(); ++i) {
421        DrawTileToCanvas(canvas, fTileRects[i], fPicture);
422        if (NULL != path) {
423            success &= writeAppendNumber(canvas, path, i);
424        }
425    }
426    return success;
427}
428
429SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) {
430    SkCanvas* canvas = this->INHERITED::setupCanvas(width, height);
431    SkASSERT(fPicture != NULL);
432    // Clip the tile to an area that is completely in what the SkPicture says is the
433    // drawn-to area. This is mostly important for tiles on the right and bottom edges
434    // as they may go over this area and the picture may have some commands that
435    // draw outside of this area and so should not actually be written.
436    SkRect clip = SkRect::MakeWH(SkIntToScalar(fPicture->width()),
437                                 SkIntToScalar(fPicture->height()));
438    canvas->clipRect(clip);
439    return canvas;
440}
441
442SkString TiledPictureRenderer::getConfigNameInternal() {
443    SkString name;
444    if (fTileMinPowerOf2Width > 0) {
445        name.append("pow2tile_");
446        name.appendf("%i", fTileMinPowerOf2Width);
447    } else {
448        name.append("tile_");
449        if (fTileWidthPercentage > 0) {
450            name.appendf("%.f%%", fTileWidthPercentage);
451        } else {
452            name.appendf("%i", fTileWidth);
453        }
454    }
455    name.append("x");
456    if (fTileHeightPercentage > 0) {
457        name.appendf("%.f%%", fTileHeightPercentage);
458    } else {
459        name.appendf("%i", fTileHeight);
460    }
461    return name;
462}
463
464///////////////////////////////////////////////////////////////////////////////////////////////
465
466// Holds all of the information needed to draw a set of tiles.
467class CloneData : public SkRunnable {
468
469public:
470    CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int start, int end,
471              SkRunnable* done)
472        : fClone(clone)
473        , fCanvas(canvas)
474        , fPath(NULL)
475        , fRects(rects)
476        , fStart(start)
477        , fEnd(end)
478        , fSuccess(NULL)
479        , fDone(done) {
480        SkASSERT(fDone != NULL);
481    }
482
483    virtual void run() SK_OVERRIDE {
484        SkGraphics::SetTLSFontCacheLimit(1024 * 1024);
485        for (int i = fStart; i < fEnd; i++) {
486            DrawTileToCanvas(fCanvas, fRects[i], fClone);
487            if (fPath != NULL && !writeAppendNumber(fCanvas, fPath, i)
488                && fSuccess != NULL) {
489                *fSuccess = false;
490                // If one tile fails to write to a file, do not continue drawing the rest.
491                break;
492            }
493        }
494        fDone->run();
495    }
496
497    void setPathAndSuccess(const SkString* path, bool* success) {
498        fPath = path;
499        fSuccess = success;
500    }
501
502private:
503    // All pointers unowned.
504    SkPicture*         fClone;      // Picture to draw from. Each CloneData has a unique one which
505                                    // is threadsafe.
506    SkCanvas*          fCanvas;     // Canvas to draw to. Reused for each tile.
507    const SkString*    fPath;       // If non-null, path to write the result to as a PNG.
508    SkTDArray<SkRect>& fRects;      // All tiles of the picture.
509    const int          fStart;      // Range of tiles drawn by this thread.
510    const int          fEnd;
511    bool*              fSuccess;    // Only meaningful if path is non-null. Shared by all threads,
512                                    // and only set to false upon failure to write to a PNG.
513    SkRunnable*        fDone;
514};
515
516MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount)
517: fNumThreads(threadCount)
518, fThreadPool(threadCount)
519, fCountdown(threadCount) {
520    // Only need to create fNumThreads - 1 clones, since one thread will use the base
521    // picture.
522    fPictureClones = SkNEW_ARRAY(SkPicture, fNumThreads - 1);
523    fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads);
524}
525
526void MultiCorePictureRenderer::init(SkPicture *pict) {
527    // Set fPicture and the tiles.
528    this->INHERITED::init(pict);
529    for (int i = 0; i < fNumThreads; ++i) {
530        *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight());
531    }
532    // Only need to create fNumThreads - 1 clones, since one thread will use the base picture.
533    fPicture->clone(fPictureClones, fNumThreads - 1);
534    // Populate each thread with the appropriate data.
535    // Group the tiles into nearly equal size chunks, rounding up so we're sure to cover them all.
536    const int chunkSize = (fTileRects.count() + fNumThreads - 1) / fNumThreads;
537
538    for (int i = 0; i < fNumThreads; i++) {
539        SkPicture* pic;
540        if (i == fNumThreads-1) {
541            // The last set will use the original SkPicture.
542            pic = fPicture;
543        } else {
544            pic = &fPictureClones[i];
545        }
546        const int start = i * chunkSize;
547        const int end = SkMin32(start + chunkSize, fTileRects.count());
548        fCloneData[i] = SkNEW_ARGS(CloneData,
549                                   (pic, fCanvasPool[i], fTileRects, start, end, &fCountdown));
550    }
551}
552
553bool MultiCorePictureRenderer::render(const SkString *path) {
554    bool success = true;
555    if (path != NULL) {
556        for (int i = 0; i < fNumThreads-1; i++) {
557            fCloneData[i]->setPathAndSuccess(path, &success);
558        }
559    }
560
561    fCountdown.reset(fNumThreads);
562    for (int i = 0; i < fNumThreads; i++) {
563        fThreadPool.add(fCloneData[i]);
564    }
565    fCountdown.wait();
566
567    return success;
568}
569
570void MultiCorePictureRenderer::end() {
571    for (int i = 0; i < fNumThreads - 1; i++) {
572        SkDELETE(fCloneData[i]);
573        fCloneData[i] = NULL;
574    }
575
576    fCanvasPool.unrefAll();
577
578    this->INHERITED::end();
579}
580
581MultiCorePictureRenderer::~MultiCorePictureRenderer() {
582    // Each individual CloneData was deleted in end.
583    SkDELETE_ARRAY(fCloneData);
584    SkDELETE_ARRAY(fPictureClones);
585}
586
587SkString MultiCorePictureRenderer::getConfigNameInternal() {
588    SkString name = this->INHERITED::getConfigNameInternal();
589    name.appendf("_multi_%i_threads", fNumThreads);
590    return name;
591}
592
593///////////////////////////////////////////////////////////////////////////////////////////////
594
595void PlaybackCreationRenderer::setup() {
596    fReplayer.reset(this->createPicture());
597    SkCanvas* recorder = fReplayer->beginRecording(fPicture->width(), fPicture->height(),
598                                                   this->recordFlags());
599    fPicture->draw(recorder);
600}
601
602bool PlaybackCreationRenderer::render(const SkString*) {
603    fReplayer->endRecording();
604    // Since this class does not actually render, return false.
605    return false;
606}
607
608SkString PlaybackCreationRenderer::getConfigNameInternal() {
609    return SkString("playback_creation");
610}
611
612///////////////////////////////////////////////////////////////////////////////////////////////
613// SkPicture variants for each BBoxHierarchy type
614
615class RTreePicture : public SkPicture {
616public:
617    virtual SkBBoxHierarchy* createBBoxHierarchy() const SK_OVERRIDE{
618        static const int kRTreeMinChildren = 6;
619        static const int kRTreeMaxChildren = 11;
620        SkScalar aspectRatio = SkScalarDiv(SkIntToScalar(fWidth),
621                                           SkIntToScalar(fHeight));
622        return SkRTree::Create(kRTreeMinChildren, kRTreeMaxChildren,
623                               aspectRatio);
624    }
625};
626
627SkPicture* PictureRenderer::createPicture() {
628    switch (fBBoxHierarchyType) {
629        case kNone_BBoxHierarchyType:
630            return SkNEW(SkPicture);
631        case kRTree_BBoxHierarchyType:
632            return SkNEW(RTreePicture);
633        case kTileGrid_BBoxHierarchyType:
634            return SkNEW_ARGS(SkTileGridPicture, (fGridWidth, fGridHeight, fPicture->width(),
635                fPicture->height()));
636    }
637    SkASSERT(0); // invalid bbhType
638    return NULL;
639}
640
641///////////////////////////////////////////////////////////////////////////////
642
643class GatherRenderer : public PictureRenderer {
644public:
645    virtual bool render(const SkString* path) SK_OVERRIDE {
646        SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->width()),
647                                       SkIntToScalar(fPicture->height()));
648        SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds);
649        SkSafeUnref(data);
650
651        return NULL == path;    // we don't have anything to write
652    }
653
654private:
655    virtual SkString getConfigNameInternal() SK_OVERRIDE {
656        return SkString("gather_pixelrefs");
657    }
658};
659
660PictureRenderer* CreateGatherPixelRefsRenderer() {
661    return SkNEW(GatherRenderer);
662}
663
664///////////////////////////////////////////////////////////////////////////////
665
666class PictureCloneRenderer : public PictureRenderer {
667public:
668    virtual bool render(const SkString* path) SK_OVERRIDE {
669        for (int i = 0; i < 100; ++i) {
670            SkPicture* clone = fPicture->clone();
671            SkSafeUnref(clone);
672        }
673
674        return NULL == path;    // we don't have anything to write
675    }
676
677private:
678    virtual SkString getConfigNameInternal() SK_OVERRIDE {
679        return SkString("picture_clone");
680    }
681};
682
683PictureRenderer* CreatePictureCloneRenderer() {
684    return SkNEW(PictureCloneRenderer);
685}
686
687} // namespace sk_tools
688