blob: b277232708e048baca2cd6e564df5969e0018602 [file] [log] [blame]
keyar@chromium.orgb3fb7c12012-08-20 21:02:49 +00001/*
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
keyar@chromium.org451bb9f2012-07-26 17:27:57 +00008#include "PictureRenderer.h"
scroggo@google.com58b4ead2012-08-31 16:15:22 +00009#include "picture_utils.h"
keyar@chromium.org451bb9f2012-07-26 17:27:57 +000010#include "SamplePipeControllers.h"
11#include "SkCanvas.h"
12#include "SkDevice.h"
13#include "SkGPipe.h"
scroggo@google.com58b4ead2012-08-31 16:15:22 +000014#if SK_SUPPORT_GPU
15#include "SkGpuDevice.h"
16#endif
17#include "SkGraphics.h"
18#include "SkImageEncoder.h"
caryclark@google.coma3622372012-11-06 21:26:13 +000019#include "SkMaskFilter.h"
keyar@chromium.orgea826952012-08-23 15:24:13 +000020#include "SkMatrix.h"
keyar@chromium.org451bb9f2012-07-26 17:27:57 +000021#include "SkPicture.h"
junov@chromium.org9313ca42012-11-02 18:11:49 +000022#include "SkRTree.h"
keyar@chromium.orgea826952012-08-23 15:24:13 +000023#include "SkScalar.h"
scroggo@google.coma9e3a362012-11-07 17:52:48 +000024#include "SkStream.h"
keyar@chromium.org9299ede2012-08-21 19:05:08 +000025#include "SkString.h"
scroggo@google.com58b4ead2012-08-31 16:15:22 +000026#include "SkTemplates.h"
junov@chromium.org3cb834b2012-12-13 16:39:53 +000027#include "SkTileGridPicture.h"
keyar@chromium.org451bb9f2012-07-26 17:27:57 +000028#include "SkTDArray.h"
scroggo@google.com58b4ead2012-08-31 16:15:22 +000029#include "SkThreadUtils.h"
keyar@chromium.org451bb9f2012-07-26 17:27:57 +000030#include "SkTypes.h"
reed@google.comfe7b1ed2012-11-29 21:00:39 +000031#include "SkData.h"
32#include "SkPictureUtils.h"
keyar@chromium.org4ea96c52012-08-20 15:03:29 +000033
keyar@chromium.org451bb9f2012-07-26 17:27:57 +000034namespace sk_tools {
35
36enum {
37 kDefaultTileWidth = 256,
38 kDefaultTileHeight = 256
39};
40
keyar@chromium.org9d696c02012-08-07 17:11:33 +000041void PictureRenderer::init(SkPicture* pict) {
keyar@chromium.org78a35c52012-08-20 15:03:44 +000042 SkASSERT(NULL == fPicture);
43 SkASSERT(NULL == fCanvas.get());
44 if (fPicture != NULL || NULL != fCanvas.get()) {
keyar@chromium.org9d696c02012-08-07 17:11:33 +000045 return;
46 }
47
48 SkASSERT(pict != NULL);
keyar@chromium.org78a35c52012-08-20 15:03:44 +000049 if (NULL == pict) {
keyar@chromium.org9d696c02012-08-07 17:11:33 +000050 return;
51 }
52
53 fPicture = pict;
junov@chromium.org9313ca42012-11-02 18:11:49 +000054 fPicture->ref();
keyar@chromium.orga474ce32012-08-20 15:03:57 +000055 fCanvas.reset(this->setupCanvas());
56}
57
caryclark@google.coma3622372012-11-06 21:26:13 +000058class FlagsDrawFilter : public SkDrawFilter {
59public:
60 FlagsDrawFilter(PictureRenderer::DrawFilterFlags* flags) :
61 fFlags(flags) {}
62
reed@google.com971aca72012-11-26 20:26:54 +000063 virtual bool filter(SkPaint* paint, Type t) {
caryclark@google.coma3622372012-11-06 21:26:13 +000064 paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags);
reed@google.com457d8a72012-12-18 18:20:44 +000065 if (PictureRenderer::kBlur_DrawFilterFlag & fFlags[t]) {
caryclark@google.coma3622372012-11-06 21:26:13 +000066 SkMaskFilter* maskFilter = paint->getMaskFilter();
67 SkMaskFilter::BlurInfo blurInfo;
68 if (maskFilter && maskFilter->asABlur(&blurInfo)) {
reed@google.com457d8a72012-12-18 18:20:44 +000069 paint->setMaskFilter(NULL);
caryclark@google.coma3622372012-11-06 21:26:13 +000070 }
71 }
72 if (PictureRenderer::kHinting_DrawFilterFlag & fFlags[t]) {
73 paint->setHinting(SkPaint::kNo_Hinting);
74 } else if (PictureRenderer::kSlightHinting_DrawFilterFlag & fFlags[t]) {
75 paint->setHinting(SkPaint::kSlight_Hinting);
76 }
reed@google.com971aca72012-11-26 20:26:54 +000077 return true;
caryclark@google.coma3622372012-11-06 21:26:13 +000078 }
79
80private:
81 PictureRenderer::DrawFilterFlags* fFlags;
82};
83
scroggo@google.com82ec0b02012-12-17 19:25:54 +000084static void setUpFilter(SkCanvas* canvas, PictureRenderer::DrawFilterFlags* drawFilters) {
caryclark@google.coma3622372012-11-06 21:26:13 +000085 if (drawFilters && !canvas->getDrawFilter()) {
86 canvas->setDrawFilter(SkNEW_ARGS(FlagsDrawFilter, (drawFilters)))->unref();
caryclark@google.come3e940c2012-11-07 16:42:17 +000087 if (drawFilters[0] & PictureRenderer::kAAClip_DrawFilterFlag) {
88 canvas->setAllowSoftClip(false);
89 }
caryclark@google.coma3622372012-11-06 21:26:13 +000090 }
caryclark@google.coma3622372012-11-06 21:26:13 +000091}
92
keyar@chromium.orga474ce32012-08-20 15:03:57 +000093SkCanvas* PictureRenderer::setupCanvas() {
scroggo@google.comc0d5e542012-12-13 21:40:48 +000094 const int width = this->getViewWidth();
95 const int height = this->getViewHeight();
96 return this->setupCanvas(width, height);
keyar@chromium.orga474ce32012-08-20 15:03:57 +000097}
98
99SkCanvas* PictureRenderer::setupCanvas(int width, int height) {
caryclark@google.coma3622372012-11-06 21:26:13 +0000100 SkCanvas* canvas;
keyar@chromium.org4ea96c52012-08-20 15:03:29 +0000101 switch(fDeviceType) {
102 case kBitmap_DeviceType: {
103 SkBitmap bitmap;
keyar@chromium.orga474ce32012-08-20 15:03:57 +0000104 sk_tools::setup_bitmap(&bitmap, width, height);
caryclark@google.coma3622372012-11-06 21:26:13 +0000105 canvas = SkNEW_ARGS(SkCanvas, (bitmap));
keyar@chromium.org4ea96c52012-08-20 15:03:29 +0000106 }
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000107 break;
keyar@chromium.org4ea96c52012-08-20 15:03:29 +0000108#if SK_SUPPORT_GPU
109 case kGPU_DeviceType: {
keyar@chromium.org4ea96c52012-08-20 15:03:29 +0000110 SkAutoTUnref<SkGpuDevice> device(SkNEW_ARGS(SkGpuDevice,
keyar@chromium.org06125642012-08-20 15:03:33 +0000111 (fGrContext, SkBitmap::kARGB_8888_Config,
keyar@chromium.orga474ce32012-08-20 15:03:57 +0000112 width, height)));
caryclark@google.coma3622372012-11-06 21:26:13 +0000113 canvas = SkNEW_ARGS(SkCanvas, (device.get()));
keyar@chromium.org4ea96c52012-08-20 15:03:29 +0000114 }
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000115 break;
keyar@chromium.org4ea96c52012-08-20 15:03:29 +0000116#endif
117 default:
118 SkASSERT(0);
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000119 return NULL;
keyar@chromium.org4ea96c52012-08-20 15:03:29 +0000120 }
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000121 setUpFilter(canvas, fDrawFilters);
122 this->scaleToScaleFactor(canvas);
123 return canvas;
124}
keyar@chromium.orga474ce32012-08-20 15:03:57 +0000125
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000126void PictureRenderer::scaleToScaleFactor(SkCanvas* canvas) {
127 SkASSERT(canvas != NULL);
128 if (fScaleFactor != SK_Scalar1) {
129 canvas->scale(fScaleFactor, fScaleFactor);
130 }
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000131}
132
133void PictureRenderer::end() {
scroggo@google.com08085f82013-01-28 20:40:24 +0000134 this->resetState(true);
junov@chromium.org9313ca42012-11-02 18:11:49 +0000135 SkSafeUnref(fPicture);
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000136 fPicture = NULL;
137 fCanvas.reset(NULL);
138}
139
scroggo@google.comc0d5e542012-12-13 21:40:48 +0000140int PictureRenderer::getViewWidth() {
141 SkASSERT(fPicture != NULL);
robertphillips@google.com8ac811e2013-02-07 00:13:34 +0000142 int width = SkScalarCeilToInt(fPicture->width() * fScaleFactor);
scroggo@google.comc0d5e542012-12-13 21:40:48 +0000143 if (fViewport.width() > 0) {
144 width = SkMin32(width, fViewport.width());
145 }
146 return width;
147}
148
149int PictureRenderer::getViewHeight() {
150 SkASSERT(fPicture != NULL);
robertphillips@google.com8ac811e2013-02-07 00:13:34 +0000151 int height = SkScalarCeilToInt(fPicture->height() * fScaleFactor);
scroggo@google.comc0d5e542012-12-13 21:40:48 +0000152 if (fViewport.height() > 0) {
153 height = SkMin32(height, fViewport.height());
154 }
155 return height;
156}
157
junov@chromium.org9313ca42012-11-02 18:11:49 +0000158/** Converts fPicture to a picture that uses a BBoxHierarchy.
159 * PictureRenderer subclasses that are used to test picture playback
160 * should call this method during init.
161 */
162void PictureRenderer::buildBBoxHierarchy() {
163 SkASSERT(NULL != fPicture);
164 if (kNone_BBoxHierarchyType != fBBoxHierarchyType && NULL != fPicture) {
165 SkPicture* newPicture = this->createPicture();
166 SkCanvas* recorder = newPicture->beginRecording(fPicture->width(), fPicture->height(),
167 this->recordFlags());
168 fPicture->draw(recorder);
169 newPicture->endRecording();
170 fPicture->unref();
171 fPicture = newPicture;
172 }
173}
174
scroggo@google.com08085f82013-01-28 20:40:24 +0000175void PictureRenderer::resetState(bool callFinish) {
keyar@chromium.org28136b32012-08-20 15:04:15 +0000176#if SK_SUPPORT_GPU
177 if (this->isUsingGpuDevice()) {
178 SkGLContext* glContext = fGrContextFactory.getGLContext(
179 GrContextFactory::kNative_GLContextType);
keyar@chromium.org28136b32012-08-20 15:04:15 +0000180
181 SkASSERT(glContext != NULL);
182 if (NULL == glContext) {
183 return;
184 }
185
scroggo@google.com9a412522012-09-07 15:21:18 +0000186 fGrContext->flush();
scroggo@google.com08085f82013-01-28 20:40:24 +0000187 if (callFinish) {
188 SK_GL(*glContext, Finish());
189 }
keyar@chromium.org77a55222012-08-20 15:03:47 +0000190 }
keyar@chromium.orga40c20d2012-08-20 15:04:12 +0000191#endif
keyar@chromium.org77a55222012-08-20 15:03:47 +0000192}
193
junov@chromium.org9313ca42012-11-02 18:11:49 +0000194uint32_t PictureRenderer::recordFlags() {
junov@chromium.org100b1c52013-01-16 20:12:22 +0000195 return ((kNone_BBoxHierarchyType == fBBoxHierarchyType) ? 0 :
196 SkPicture::kOptimizeForClippedPlayback_RecordingFlag) |
197 SkPicture::kUsePathBoundsForClip_RecordingFlag;
junov@chromium.org9313ca42012-11-02 18:11:49 +0000198}
199
scroggo@google.comb6e806b2012-10-03 17:32:33 +0000200/**
201 * Write the canvas to the specified path.
202 * @param canvas Must be non-null. Canvas to be written to a file.
203 * @param path Path for the file to be written. Should have no extension; write() will append
204 * an appropriate one. Passed in by value so it can be modified.
205 * @return bool True if the Canvas is written to a file.
206 */
207static bool write(SkCanvas* canvas, SkString path) {
scroggo@google.com81f9d2e2012-09-20 14:54:21 +0000208 SkASSERT(canvas != NULL);
scroggo@google.comb6e806b2012-10-03 17:32:33 +0000209 if (NULL == canvas) {
keyar@chromium.org9299ede2012-08-21 19:05:08 +0000210 return false;
211 }
212
213 SkBitmap bitmap;
scroggo@google.com81f9d2e2012-09-20 14:54:21 +0000214 SkISize size = canvas->getDeviceSize();
215 sk_tools::setup_bitmap(&bitmap, size.width(), size.height());
keyar@chromium.org9299ede2012-08-21 19:05:08 +0000216
scroggo@google.com81f9d2e2012-09-20 14:54:21 +0000217 canvas->readPixels(&bitmap, 0, 0);
keyar@chromium.org9299ede2012-08-21 19:05:08 +0000218 sk_tools::force_all_opaque(bitmap);
219
scroggo@google.com81f9d2e2012-09-20 14:54:21 +0000220 // Since path is passed in by value, it is okay to modify it.
221 path.append(".png");
keyar@chromium.org9299ede2012-08-21 19:05:08 +0000222 return SkImageEncoder::EncodeFile(path.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
223}
224
scroggo@google.comb6e806b2012-10-03 17:32:33 +0000225/**
226 * If path is non NULL, append number to it, and call write(SkCanvas*, SkString) to write the
227 * provided canvas to a file. Returns true if path is NULL or if write() succeeds.
228 */
229static bool writeAppendNumber(SkCanvas* canvas, const SkString* path, int number) {
230 if (NULL == path) {
231 return true;
232 }
233 SkString pathWithNumber(*path);
234 pathWithNumber.appendf("%i", number);
235 return write(canvas, pathWithNumber);
236}
237
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000238///////////////////////////////////////////////////////////////////////////////////////////////
239
djsollen@google.comfd9720c2012-11-06 16:54:40 +0000240SkCanvas* RecordPictureRenderer::setupCanvas(int width, int height) {
241 // defer the canvas setup until the render step
242 return NULL;
243}
244
scroggo@google.coma9e3a362012-11-07 17:52:48 +0000245static bool PNGEncodeBitmapToStream(SkWStream* wStream, const SkBitmap& bm) {
246 return SkImageEncoder::EncodeStream(wStream, bm, SkImageEncoder::kPNG_Type, 100);
247}
248
edisonn@google.com84f548c2012-12-18 22:24:03 +0000249bool RecordPictureRenderer::render(const SkString* path, SkBitmap** out) {
junov@chromium.org9313ca42012-11-02 18:11:49 +0000250 SkAutoTUnref<SkPicture> replayer(this->createPicture());
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000251 SkCanvas* recorder = replayer->beginRecording(this->getViewWidth(), this->getViewHeight(),
junov@chromium.org9313ca42012-11-02 18:11:49 +0000252 this->recordFlags());
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000253 this->scaleToScaleFactor(recorder);
scroggo@google.com9a412522012-09-07 15:21:18 +0000254 fPicture->draw(recorder);
junov@chromium.org9313ca42012-11-02 18:11:49 +0000255 replayer->endRecording();
scroggo@google.coma9e3a362012-11-07 17:52:48 +0000256 if (path != NULL) {
257 // Record the new picture as a new SKP with PNG encoded bitmaps.
258 SkString skpPath(*path);
259 // ".skp" was removed from 'path' before being passed in here.
260 skpPath.append(".skp");
261 SkFILEWStream stream(skpPath.c_str());
262 replayer->serialize(&stream, &PNGEncodeBitmapToStream);
263 return true;
264 }
scroggo@google.com81f9d2e2012-09-20 14:54:21 +0000265 return false;
scroggo@google.com9a412522012-09-07 15:21:18 +0000266}
267
scroggo@google.com0a049b82012-11-02 22:01:26 +0000268SkString RecordPictureRenderer::getConfigNameInternal() {
269 return SkString("record");
270}
271
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000272///////////////////////////////////////////////////////////////////////////////////////////////
273
edisonn@google.com84f548c2012-12-18 22:24:03 +0000274bool PipePictureRenderer::render(const SkString* path, SkBitmap** out) {
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000275 SkASSERT(fCanvas.get() != NULL);
276 SkASSERT(fPicture != NULL);
keyar@chromium.org78a35c52012-08-20 15:03:44 +0000277 if (NULL == fCanvas.get() || NULL == fPicture) {
scroggo@google.com81f9d2e2012-09-20 14:54:21 +0000278 return false;
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000279 }
280
281 PipeController pipeController(fCanvas.get());
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000282 SkGPipeWriter writer;
283 SkCanvas* pipeCanvas = writer.startRecording(&pipeController);
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000284 pipeCanvas->drawPicture(*fPicture);
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000285 writer.endRecording();
scroggo@google.com9a412522012-09-07 15:21:18 +0000286 fCanvas->flush();
borenet@google.com070d3542012-10-26 13:26:55 +0000287 if (NULL != path) {
288 return write(fCanvas, *path);
289 }
edisonn@google.com84f548c2012-12-18 22:24:03 +0000290 if (NULL != out) {
291 *out = SkNEW(SkBitmap);
292 setup_bitmap(*out, fPicture->width(), fPicture->height());
293 fCanvas->readPixels(*out, 0, 0);
skia.committer@gmail.coma7d8e3e2012-12-19 02:01:38 +0000294 }
borenet@google.com070d3542012-10-26 13:26:55 +0000295 return true;
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000296}
297
scroggo@google.com0a049b82012-11-02 22:01:26 +0000298SkString PipePictureRenderer::getConfigNameInternal() {
299 return SkString("pipe");
300}
301
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000302///////////////////////////////////////////////////////////////////////////////////////////////
303
junov@chromium.org9313ca42012-11-02 18:11:49 +0000304void SimplePictureRenderer::init(SkPicture* picture) {
305 INHERITED::init(picture);
306 this->buildBBoxHierarchy();
307}
308
edisonn@google.com84f548c2012-12-18 22:24:03 +0000309bool SimplePictureRenderer::render(const SkString* path, SkBitmap** out) {
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000310 SkASSERT(fCanvas.get() != NULL);
311 SkASSERT(fPicture != NULL);
keyar@chromium.org78a35c52012-08-20 15:03:44 +0000312 if (NULL == fCanvas.get() || NULL == fPicture) {
scroggo@google.com81f9d2e2012-09-20 14:54:21 +0000313 return false;
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000314 }
315
316 fCanvas->drawPicture(*fPicture);
scroggo@google.com9a412522012-09-07 15:21:18 +0000317 fCanvas->flush();
borenet@google.com070d3542012-10-26 13:26:55 +0000318 if (NULL != path) {
319 return write(fCanvas, *path);
320 }
skia.committer@gmail.coma7d8e3e2012-12-19 02:01:38 +0000321
edisonn@google.com84f548c2012-12-18 22:24:03 +0000322 if (NULL != out) {
323 *out = SkNEW(SkBitmap);
324 setup_bitmap(*out, fPicture->width(), fPicture->height());
325 fCanvas->readPixels(*out, 0, 0);
326 }
327
borenet@google.com070d3542012-10-26 13:26:55 +0000328 return true;
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000329}
330
scroggo@google.com0a049b82012-11-02 22:01:26 +0000331SkString SimplePictureRenderer::getConfigNameInternal() {
332 return SkString("simple");
333}
334
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000335///////////////////////////////////////////////////////////////////////////////////////////////
336
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000337TiledPictureRenderer::TiledPictureRenderer()
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000338 : fTileWidth(kDefaultTileWidth)
rileya@google.comb947b912012-08-29 17:35:07 +0000339 , fTileHeight(kDefaultTileHeight)
rileya@google.coma04dc022012-09-10 19:01:38 +0000340 , fTileWidthPercentage(0.0)
rileya@google.comb947b912012-08-29 17:35:07 +0000341 , fTileHeightPercentage(0.0)
scroggo@google.comcbcef702012-12-13 22:09:28 +0000342 , fTileMinPowerOf2Width(0)
343 , fCurrentTileOffset(-1)
344 , fTilesX(0)
345 , fTilesY(0) { }
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000346
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000347void TiledPictureRenderer::init(SkPicture* pict) {
348 SkASSERT(pict != NULL);
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000349 SkASSERT(0 == fTileRects.count());
350 if (NULL == pict || fTileRects.count() != 0) {
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000351 return;
352 }
353
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000354 // Do not call INHERITED::init(), which would create a (potentially large) canvas which is not
355 // used by bench_pictures.
356 fPicture = pict;
junov@chromium.org9313ca42012-11-02 18:11:49 +0000357 fPicture->ref();
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000358 this->buildBBoxHierarchy();
keyar@chromium.orgcc6e5ef2012-07-27 20:09:26 +0000359
360 if (fTileWidthPercentage > 0) {
robertphillips@google.com5d8d1862012-08-15 14:36:41 +0000361 fTileWidth = sk_float_ceil2int(float(fTileWidthPercentage * fPicture->width() / 100));
keyar@chromium.orgcc6e5ef2012-07-27 20:09:26 +0000362 }
363 if (fTileHeightPercentage > 0) {
robertphillips@google.com5d8d1862012-08-15 14:36:41 +0000364 fTileHeight = sk_float_ceil2int(float(fTileHeightPercentage * fPicture->height() / 100));
keyar@chromium.orgcc6e5ef2012-07-27 20:09:26 +0000365 }
366
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000367 if (fTileMinPowerOf2Width > 0) {
368 this->setupPowerOf2Tiles();
369 } else {
370 this->setupTiles();
371 }
scroggo@google.comcbcef702012-12-13 22:09:28 +0000372 fCanvas.reset(this->setupCanvas(fTileWidth, fTileHeight));
373 // Initialize to -1 so that the first call to nextTile will set this up to draw tile 0 on the
374 // first call to drawCurrentTile.
375 fCurrentTileOffset = -1;
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000376}
377
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000378void TiledPictureRenderer::end() {
scroggo@google.combcdf2ec2012-09-20 14:42:33 +0000379 fTileRects.reset();
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000380 this->INHERITED::end();
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000381}
382
keyar@chromium.org9d696c02012-08-07 17:11:33 +0000383void TiledPictureRenderer::setupTiles() {
scroggo@google.comc0d5e542012-12-13 21:40:48 +0000384 // Only use enough tiles to cover the viewport
385 const int width = this->getViewWidth();
386 const int height = this->getViewHeight();
387
scroggo@google.comcbcef702012-12-13 22:09:28 +0000388 fTilesX = fTilesY = 0;
scroggo@google.comc0d5e542012-12-13 21:40:48 +0000389 for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) {
scroggo@google.comcbcef702012-12-13 22:09:28 +0000390 fTilesY++;
scroggo@google.comc0d5e542012-12-13 21:40:48 +0000391 for (int tile_x_start = 0; tile_x_start < width; tile_x_start += fTileWidth) {
scroggo@google.comcbcef702012-12-13 22:09:28 +0000392 if (0 == tile_y_start) {
393 // Only count tiles in the X direction on the first pass.
394 fTilesX++;
395 }
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000396 *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
397 SkIntToScalar(tile_y_start),
398 SkIntToScalar(fTileWidth),
399 SkIntToScalar(fTileHeight));
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000400 }
401 }
402}
403
scroggo@google.comcbcef702012-12-13 22:09:28 +0000404bool TiledPictureRenderer::tileDimensions(int &x, int &y) {
405 if (fTileRects.count() == 0 || NULL == fPicture) {
406 return false;
407 }
408 x = fTilesX;
409 y = fTilesY;
410 return true;
411}
412
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000413// The goal of the powers of two tiles is to minimize the amount of wasted tile
414// space in the width-wise direction and then minimize the number of tiles. The
415// constraints are that every tile must have a pixel width that is a power of
416// two and also be of some minimal width (that is also a power of two).
417//
scroggo@google.com58b4ead2012-08-31 16:15:22 +0000418// This is solved by first taking our picture size and rounding it up to the
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000419// multiple of the minimal width. The binary representation of this rounded
420// value gives us the tiles we need: a bit of value one means we need a tile of
421// that size.
422void TiledPictureRenderer::setupPowerOf2Tiles() {
scroggo@google.comc0d5e542012-12-13 21:40:48 +0000423 // Only use enough tiles to cover the viewport
424 const int width = this->getViewWidth();
425 const int height = this->getViewHeight();
426
427 int rounded_value = width;
428 if (width % fTileMinPowerOf2Width != 0) {
429 rounded_value = width - (width % fTileMinPowerOf2Width) + fTileMinPowerOf2Width;
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000430 }
431
scroggo@google.comc0d5e542012-12-13 21:40:48 +0000432 int num_bits = SkScalarCeilToInt(SkScalarLog2(SkIntToScalar(width)));
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000433 int largest_possible_tile_size = 1 << num_bits;
434
scroggo@google.comcbcef702012-12-13 22:09:28 +0000435 fTilesX = fTilesY = 0;
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000436 // The tile height is constant for a particular picture.
scroggo@google.comc0d5e542012-12-13 21:40:48 +0000437 for (int tile_y_start = 0; tile_y_start < height; tile_y_start += fTileHeight) {
scroggo@google.comcbcef702012-12-13 22:09:28 +0000438 fTilesY++;
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000439 int tile_x_start = 0;
440 int current_width = largest_possible_tile_size;
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000441 // Set fTileWidth to be the width of the widest tile, so that each canvas is large enough
442 // to draw each tile.
443 fTileWidth = current_width;
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000444
445 while (current_width >= fTileMinPowerOf2Width) {
446 // It is very important this is a bitwise AND.
447 if (current_width & rounded_value) {
scroggo@google.comcbcef702012-12-13 22:09:28 +0000448 if (0 == tile_y_start) {
449 // Only count tiles in the X direction on the first pass.
450 fTilesX++;
451 }
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000452 *fTileRects.append() = SkRect::MakeXYWH(SkIntToScalar(tile_x_start),
453 SkIntToScalar(tile_y_start),
454 SkIntToScalar(current_width),
455 SkIntToScalar(fTileHeight));
keyar@chromium.orgf4959ab2012-08-23 20:53:25 +0000456 tile_x_start += current_width;
457 }
458
459 current_width >>= 1;
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000460 }
461 }
462}
463
scroggo@google.combcdf2ec2012-09-20 14:42:33 +0000464/**
465 * Draw the specified playback to the canvas translated to rectangle provided, so that this mini
466 * canvas represents the rectangle's portion of the overall picture.
467 * Saves and restores so that the initial clip and matrix return to their state before this function
468 * is called.
469 */
470template<class T>
471static void DrawTileToCanvas(SkCanvas* canvas, const SkRect& tileRect, T* playback) {
472 int saveCount = canvas->save();
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000473 // Translate so that we draw the correct portion of the picture.
474 // Perform a postTranslate so that the scaleFactor does not interfere with the positioning.
475 SkMatrix mat(canvas->getTotalMatrix());
476 mat.postTranslate(-tileRect.fLeft, -tileRect.fTop);
477 canvas->setMatrix(mat);
scroggo@google.combcdf2ec2012-09-20 14:42:33 +0000478 playback->draw(canvas);
479 canvas->restoreToCount(saveCount);
480 canvas->flush();
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000481}
482
scroggo@google.com58b4ead2012-08-31 16:15:22 +0000483///////////////////////////////////////////////////////////////////////////////////////////////
scroggo@google.combcdf2ec2012-09-20 14:42:33 +0000484
edisonn@google.com84f548c2012-12-18 22:24:03 +0000485static void bitmapCopySubset(const SkBitmap& src, SkBitmap* dst, int xDst,
486 int yDst) {
487 for (int y = 0; y <src.height() && y + yDst < dst->height() ; y++) {
488 for (int x = 0; x < src.width() && x + xDst < dst->width() ; x++) {
489 *dst->getAddr32(xDst + x, yDst + y) = *src.getAddr32(x, y);
490 }
491 }
492}
493
scroggo@google.comcbcef702012-12-13 22:09:28 +0000494bool TiledPictureRenderer::nextTile(int &i, int &j) {
495 if (++fCurrentTileOffset < fTileRects.count()) {
496 i = fCurrentTileOffset % fTilesX;
497 j = fCurrentTileOffset / fTilesX;
498 return true;
499 }
500 return false;
501}
502
503void TiledPictureRenderer::drawCurrentTile() {
504 SkASSERT(fCurrentTileOffset >= 0 && fCurrentTileOffset < fTileRects.count());
505 DrawTileToCanvas(fCanvas, fTileRects[fCurrentTileOffset], fPicture);
506}
507
edisonn@google.com84f548c2012-12-18 22:24:03 +0000508bool TiledPictureRenderer::render(const SkString* path, SkBitmap** out) {
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000509 SkASSERT(fPicture != NULL);
510 if (NULL == fPicture) {
scroggo@google.com81f9d2e2012-09-20 14:54:21 +0000511 return false;
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000512 }
513
edisonn@google.com84f548c2012-12-18 22:24:03 +0000514 SkBitmap bitmap;
515 if (out){
516 *out = SkNEW(SkBitmap);
517 setup_bitmap(*out, fPicture->width(), fPicture->height());
518 setup_bitmap(&bitmap, fTileWidth, fTileHeight);
519 }
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000520 bool success = true;
521 for (int i = 0; i < fTileRects.count(); ++i) {
scroggo@google.comcbcef702012-12-13 22:09:28 +0000522 DrawTileToCanvas(fCanvas, fTileRects[i], fPicture);
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000523 if (NULL != path) {
scroggo@google.comcbcef702012-12-13 22:09:28 +0000524 success &= writeAppendNumber(fCanvas, path, i);
scroggo@google.com58b4ead2012-08-31 16:15:22 +0000525 }
edisonn@google.com84f548c2012-12-18 22:24:03 +0000526 if (NULL != out) {
527 if (fCanvas->readPixels(&bitmap, 0, 0)) {
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000528 bitmapCopySubset(bitmap, *out, SkScalarFloorToInt(fTileRects[i].left()),
529 SkScalarFloorToInt(fTileRects[i].top()));
edisonn@google.com84f548c2012-12-18 22:24:03 +0000530 } else {
531 success = false;
532 }
533 }
keyar@chromium.org163b5672012-08-01 17:53:29 +0000534 }
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000535 return success;
keyar@chromium.org163b5672012-08-01 17:53:29 +0000536}
537
scroggo@google.combcdf2ec2012-09-20 14:42:33 +0000538SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) {
539 SkCanvas* canvas = this->INHERITED::setupCanvas(width, height);
540 SkASSERT(fPicture != NULL);
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000541 // Clip the tile to an area that is completely inside both the SkPicture and the viewport. This
542 // is mostly important for tiles on the right and bottom edges as they may go over this area and
543 // the picture may have some commands that draw outside of this area and so should not actually
544 // be written.
545 // Uses a clipRegion so that it will be unaffected by the scale factor, which may have been set
546 // by INHERITED::setupCanvas.
547 SkRegion clipRegion;
548 clipRegion.setRect(0, 0, this->getViewWidth(), this->getViewHeight());
549 canvas->clipRegion(clipRegion);
scroggo@google.combcdf2ec2012-09-20 14:42:33 +0000550 return canvas;
551}
scroggo@google.com0a049b82012-11-02 22:01:26 +0000552
553SkString TiledPictureRenderer::getConfigNameInternal() {
554 SkString name;
555 if (fTileMinPowerOf2Width > 0) {
556 name.append("pow2tile_");
557 name.appendf("%i", fTileMinPowerOf2Width);
558 } else {
559 name.append("tile_");
560 if (fTileWidthPercentage > 0) {
561 name.appendf("%.f%%", fTileWidthPercentage);
562 } else {
563 name.appendf("%i", fTileWidth);
564 }
565 }
566 name.append("x");
567 if (fTileHeightPercentage > 0) {
568 name.appendf("%.f%%", fTileHeightPercentage);
569 } else {
570 name.appendf("%i", fTileHeight);
571 }
572 return name;
573}
574
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000575///////////////////////////////////////////////////////////////////////////////////////////////
576
577// Holds all of the information needed to draw a set of tiles.
578class CloneData : public SkRunnable {
579
580public:
581 CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int start, int end,
582 SkRunnable* done)
583 : fClone(clone)
584 , fCanvas(canvas)
585 , fPath(NULL)
586 , fRects(rects)
587 , fStart(start)
588 , fEnd(end)
589 , fSuccess(NULL)
590 , fDone(done) {
591 SkASSERT(fDone != NULL);
592 }
593
594 virtual void run() SK_OVERRIDE {
595 SkGraphics::SetTLSFontCacheLimit(1024 * 1024);
edisonn@google.com84f548c2012-12-18 22:24:03 +0000596
597 SkBitmap bitmap;
598 if (fBitmap != NULL) {
599 // All tiles are the same size.
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000600 setup_bitmap(&bitmap, SkScalarFloorToInt(fRects[0].width()), SkScalarFloorToInt(fRects[0].height()));
edisonn@google.com84f548c2012-12-18 22:24:03 +0000601 }
skia.committer@gmail.coma7d8e3e2012-12-19 02:01:38 +0000602
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000603 for (int i = fStart; i < fEnd; i++) {
604 DrawTileToCanvas(fCanvas, fRects[i], fClone);
605 if (fPath != NULL && !writeAppendNumber(fCanvas, fPath, i)
606 && fSuccess != NULL) {
607 *fSuccess = false;
608 // If one tile fails to write to a file, do not continue drawing the rest.
609 break;
610 }
edisonn@google.com84f548c2012-12-18 22:24:03 +0000611 if (fBitmap != NULL) {
612 if (fCanvas->readPixels(&bitmap, 0, 0)) {
613 SkAutoLockPixels alp(*fBitmap);
jvanverth@google.com9c4e5ac2013-01-07 18:41:28 +0000614 bitmapCopySubset(bitmap, fBitmap, SkScalarFloorToInt(fRects[i].left()),
615 SkScalarFloorToInt(fRects[i].top()));
edisonn@google.com84f548c2012-12-18 22:24:03 +0000616 } else {
617 *fSuccess = false;
618 // If one tile fails to read pixels, do not continue drawing the rest.
619 break;
620 }
621 }
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000622 }
623 fDone->run();
624 }
625
626 void setPathAndSuccess(const SkString* path, bool* success) {
627 fPath = path;
628 fSuccess = success;
629 }
630
edisonn@google.com84f548c2012-12-18 22:24:03 +0000631 void setBitmap(SkBitmap* bitmap) {
632 fBitmap = bitmap;
633 }
634
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000635private:
636 // All pointers unowned.
637 SkPicture* fClone; // Picture to draw from. Each CloneData has a unique one which
638 // is threadsafe.
639 SkCanvas* fCanvas; // Canvas to draw to. Reused for each tile.
640 const SkString* fPath; // If non-null, path to write the result to as a PNG.
641 SkTDArray<SkRect>& fRects; // All tiles of the picture.
642 const int fStart; // Range of tiles drawn by this thread.
643 const int fEnd;
644 bool* fSuccess; // Only meaningful if path is non-null. Shared by all threads,
645 // and only set to false upon failure to write to a PNG.
646 SkRunnable* fDone;
edisonn@google.com84f548c2012-12-18 22:24:03 +0000647 SkBitmap* fBitmap;
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000648};
649
650MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount)
651: fNumThreads(threadCount)
652, fThreadPool(threadCount)
653, fCountdown(threadCount) {
654 // Only need to create fNumThreads - 1 clones, since one thread will use the base
655 // picture.
656 fPictureClones = SkNEW_ARRAY(SkPicture, fNumThreads - 1);
657 fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads);
658}
659
660void MultiCorePictureRenderer::init(SkPicture *pict) {
661 // Set fPicture and the tiles.
662 this->INHERITED::init(pict);
663 for (int i = 0; i < fNumThreads; ++i) {
664 *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight());
665 }
666 // Only need to create fNumThreads - 1 clones, since one thread will use the base picture.
667 fPicture->clone(fPictureClones, fNumThreads - 1);
668 // Populate each thread with the appropriate data.
669 // Group the tiles into nearly equal size chunks, rounding up so we're sure to cover them all.
670 const int chunkSize = (fTileRects.count() + fNumThreads - 1) / fNumThreads;
671
672 for (int i = 0; i < fNumThreads; i++) {
673 SkPicture* pic;
674 if (i == fNumThreads-1) {
675 // The last set will use the original SkPicture.
676 pic = fPicture;
677 } else {
678 pic = &fPictureClones[i];
679 }
680 const int start = i * chunkSize;
681 const int end = SkMin32(start + chunkSize, fTileRects.count());
682 fCloneData[i] = SkNEW_ARGS(CloneData,
683 (pic, fCanvasPool[i], fTileRects, start, end, &fCountdown));
684 }
685}
686
edisonn@google.com84f548c2012-12-18 22:24:03 +0000687bool MultiCorePictureRenderer::render(const SkString *path, SkBitmap** out) {
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000688 bool success = true;
689 if (path != NULL) {
690 for (int i = 0; i < fNumThreads-1; i++) {
691 fCloneData[i]->setPathAndSuccess(path, &success);
692 }
693 }
694
edisonn@google.com84f548c2012-12-18 22:24:03 +0000695 if (NULL != out) {
696 *out = SkNEW(SkBitmap);
697 setup_bitmap(*out, fPicture->width(), fPicture->height());
698 for (int i = 0; i < fNumThreads; i++) {
699 fCloneData[i]->setBitmap(*out);
700 }
701 } else {
702 for (int i = 0; i < fNumThreads; i++) {
703 fCloneData[i]->setBitmap(NULL);
704 }
705 }
706
scroggo@google.coma62da2f2012-11-02 21:28:12 +0000707 fCountdown.reset(fNumThreads);
708 for (int i = 0; i < fNumThreads; i++) {
709 fThreadPool.add(fCloneData[i]);
710 }
711 fCountdown.wait();
712
713 return success;
714}
715
716void MultiCorePictureRenderer::end() {
717 for (int i = 0; i < fNumThreads - 1; i++) {
718 SkDELETE(fCloneData[i]);
719 fCloneData[i] = NULL;
720 }
721
722 fCanvasPool.unrefAll();
723
724 this->INHERITED::end();
725}
726
727MultiCorePictureRenderer::~MultiCorePictureRenderer() {
728 // Each individual CloneData was deleted in end.
729 SkDELETE_ARRAY(fCloneData);
730 SkDELETE_ARRAY(fPictureClones);
731}
scroggo@google.combcdf2ec2012-09-20 14:42:33 +0000732
scroggo@google.com0a049b82012-11-02 22:01:26 +0000733SkString MultiCorePictureRenderer::getConfigNameInternal() {
734 SkString name = this->INHERITED::getConfigNameInternal();
735 name.appendf("_multi_%i_threads", fNumThreads);
736 return name;
737}
738
scroggo@google.comacfb30e2012-09-18 14:32:35 +0000739///////////////////////////////////////////////////////////////////////////////////////////////
scroggo@google.com9a412522012-09-07 15:21:18 +0000740
741void PlaybackCreationRenderer::setup() {
junov@chromium.org9313ca42012-11-02 18:11:49 +0000742 fReplayer.reset(this->createPicture());
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000743 SkCanvas* recorder = fReplayer->beginRecording(this->getViewWidth(), this->getViewHeight(),
junov@chromium.org9313ca42012-11-02 18:11:49 +0000744 this->recordFlags());
scroggo@google.com82ec0b02012-12-17 19:25:54 +0000745 this->scaleToScaleFactor(recorder);
scroggo@google.com9a412522012-09-07 15:21:18 +0000746 fPicture->draw(recorder);
747}
748
edisonn@google.com84f548c2012-12-18 22:24:03 +0000749bool PlaybackCreationRenderer::render(const SkString*, SkBitmap** out) {
junov@chromium.org9313ca42012-11-02 18:11:49 +0000750 fReplayer->endRecording();
scroggo@google.com81f9d2e2012-09-20 14:54:21 +0000751 // Since this class does not actually render, return false.
752 return false;
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000753}
754
scroggo@google.com0a049b82012-11-02 22:01:26 +0000755SkString PlaybackCreationRenderer::getConfigNameInternal() {
756 return SkString("playback_creation");
757}
758
junov@chromium.org9313ca42012-11-02 18:11:49 +0000759///////////////////////////////////////////////////////////////////////////////////////////////
760// SkPicture variants for each BBoxHierarchy type
761
762class RTreePicture : public SkPicture {
763public:
764 virtual SkBBoxHierarchy* createBBoxHierarchy() const SK_OVERRIDE{
765 static const int kRTreeMinChildren = 6;
766 static const int kRTreeMaxChildren = 11;
767 SkScalar aspectRatio = SkScalarDiv(SkIntToScalar(fWidth),
768 SkIntToScalar(fHeight));
769 return SkRTree::Create(kRTreeMinChildren, kRTreeMaxChildren,
770 aspectRatio);
771 }
772};
773
774SkPicture* PictureRenderer::createPicture() {
775 switch (fBBoxHierarchyType) {
776 case kNone_BBoxHierarchyType:
777 return SkNEW(SkPicture);
778 case kRTree_BBoxHierarchyType:
779 return SkNEW(RTreePicture);
junov@chromium.org7b537062012-11-06 18:58:43 +0000780 case kTileGrid_BBoxHierarchyType:
junov@chromium.org3cb834b2012-12-13 16:39:53 +0000781 return SkNEW_ARGS(SkTileGridPicture, (fGridWidth, fGridHeight, fPicture->width(),
782 fPicture->height()));
junov@chromium.org9313ca42012-11-02 18:11:49 +0000783 }
784 SkASSERT(0); // invalid bbhType
785 return NULL;
keyar@chromium.org451bb9f2012-07-26 17:27:57 +0000786}
junov@chromium.org9313ca42012-11-02 18:11:49 +0000787
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000788///////////////////////////////////////////////////////////////////////////////
789
790class GatherRenderer : public PictureRenderer {
791public:
edisonn@google.com84f548c2012-12-18 22:24:03 +0000792 virtual bool render(const SkString* path, SkBitmap** out = NULL)
793 SK_OVERRIDE {
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000794 SkRect bounds = SkRect::MakeWH(SkIntToScalar(fPicture->width()),
795 SkIntToScalar(fPicture->height()));
796 SkData* data = SkPictureUtils::GatherPixelRefs(fPicture, bounds);
797 SkSafeUnref(data);
skia.committer@gmail.comc7b4be72012-12-11 02:01:20 +0000798
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000799 return NULL == path; // we don't have anything to write
800 }
skia.committer@gmail.comc7b4be72012-12-11 02:01:20 +0000801
reed@google.comfe7b1ed2012-11-29 21:00:39 +0000802private:
803 virtual SkString getConfigNameInternal() SK_OVERRIDE {
804 return SkString("gather_pixelrefs");
805 }
806};
807
808PictureRenderer* CreateGatherPixelRefsRenderer() {
809 return SkNEW(GatherRenderer);
810}
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000811
reed@google.com5a34fd32012-12-10 16:05:09 +0000812///////////////////////////////////////////////////////////////////////////////
813
814class PictureCloneRenderer : public PictureRenderer {
815public:
edisonn@google.com84f548c2012-12-18 22:24:03 +0000816 virtual bool render(const SkString* path, SkBitmap** out = NULL)
817 SK_OVERRIDE {
reed@google.com5a34fd32012-12-10 16:05:09 +0000818 for (int i = 0; i < 100; ++i) {
819 SkPicture* clone = fPicture->clone();
820 SkSafeUnref(clone);
821 }
skia.committer@gmail.comc7b4be72012-12-11 02:01:20 +0000822
reed@google.com5a34fd32012-12-10 16:05:09 +0000823 return NULL == path; // we don't have anything to write
824 }
skia.committer@gmail.comc7b4be72012-12-11 02:01:20 +0000825
reed@google.com5a34fd32012-12-10 16:05:09 +0000826private:
827 virtual SkString getConfigNameInternal() SK_OVERRIDE {
828 return SkString("picture_clone");
829 }
830};
831
832PictureRenderer* CreatePictureCloneRenderer() {
833 return SkNEW(PictureCloneRenderer);
834}
835
junov@chromium.org9313ca42012-11-02 18:11:49 +0000836} // namespace sk_tools