• 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 #ifndef PictureRenderer_DEFINED
9 #define PictureRenderer_DEFINED
10 
11 #include "SkCanvas.h"
12 #include "SkDrawFilter.h"
13 #include "SkJSONCPP.h"
14 #include "SkMath.h"
15 #include "SkPaint.h"
16 #include "SkPicture.h"
17 #include "SkPictureRecorder.h"
18 #include "SkRect.h"
19 #include "SkRefCnt.h"
20 #include "SkString.h"
21 #include "SkTDArray.h"
22 #include "SkTypes.h"
23 
24 #if SK_SUPPORT_GPU
25 #include "GrContextFactory.h"
26 #include "GrContext.h"
27 #endif
28 
29 #include "image_expectations.h"
30 
31 class SkBitmap;
32 class SkCanvas;
33 class SkGLContext;
34 class SkThread;
35 
36 namespace sk_tools {
37 
38 class TiledPictureRenderer;
39 
40 class PictureRenderer : public SkRefCnt {
41 
42 public:
43     enum SkDeviceTypes {
44 #if SK_ANGLE
45         kAngle_DeviceType,
46 #endif
47 #if SK_MESA
48         kMesa_DeviceType,
49 #endif
50         kBitmap_DeviceType,
51 #if SK_SUPPORT_GPU
52         kGPU_DeviceType,
53         kNVPR_DeviceType,
54 #endif
55     };
56 
57     enum BBoxHierarchyType {
58         kNone_BBoxHierarchyType = 0,
59         kRTree_BBoxHierarchyType,
60 
61         kLast_BBoxHierarchyType = kRTree_BBoxHierarchyType,
62     };
63 
64     // this uses SkPaint::Flags as a base and adds additional flags
65     enum DrawFilterFlags {
66         kNone_DrawFilterFlag = 0,
67         kHinting_DrawFilterFlag = 0x10000, // toggles between no hinting and normal hinting
68         kSlightHinting_DrawFilterFlag = 0x20000, // toggles between slight and normal hinting
69         kAAClip_DrawFilterFlag = 0x40000, // toggles between soft and hard clip
70         kMaskFilter_DrawFilterFlag = 0x80000, // toggles on/off mask filters (e.g., blurs)
71     };
72 
73     SK_COMPILE_ASSERT(!(kMaskFilter_DrawFilterFlag & SkPaint::kAllFlags), maskfilter_flag_must_be_greater);
74     SK_COMPILE_ASSERT(!(kHinting_DrawFilterFlag & SkPaint::kAllFlags),
75             hinting_flag_must_be_greater);
76     SK_COMPILE_ASSERT(!(kSlightHinting_DrawFilterFlag & SkPaint::kAllFlags),
77             slight_hinting_flag_must_be_greater);
78 
79     /**
80      * Called with each new SkPicture to render.
81      *
82      * @param pict The SkPicture to render.
83      * @param writePath The output directory within which this renderer should write all images,
84      *     or NULL if this renderer should not write all images.
85      * @param mismatchPath The output directory within which this renderer should write any images
86      *     which do not match expectations, or NULL if this renderer should not write mismatches.
87      * @param inputFilename The name of the input file we are rendering.
88      * @param useChecksumBasedFilenames Whether to use checksum-based filenames when writing
89      *     bitmap images to disk.
90      * @param useMultiPictureDraw true if MultiPictureDraw should be used for rendering
91      */
92     virtual void init(const SkPicture* pict,
93                       const SkString* writePath,
94                       const SkString* mismatchPath,
95                       const SkString* inputFilename,
96                       bool useChecksumBasedFilenames,
97                       bool useMultiPictureDraw);
98 
99     /**
100      * TODO(epoger): Temporary hack, while we work on http://skbug.com/2584 ('bench_pictures is
101      * timing reading pixels and writing json files'), such that:
102      * - render_pictures can call this method and continue to work
103      * - any other callers (bench_pictures) will skip calls to write() by default
104      */
enableWrites()105     void enableWrites() { fEnableWrites = true; }
106 
107     /**
108      *  Set the viewport so that only the portion listed gets drawn.
109      */
setViewport(SkISize size)110     void setViewport(SkISize size) { fViewport = size; }
111 
112     /**
113      *  Set the scale factor at which draw the picture.
114      */
setScaleFactor(SkScalar scale)115     void setScaleFactor(SkScalar scale) { fScaleFactor = scale; }
116 
117     /**
118      * Perform any setup that should done prior to each iteration of render() which should not be
119      * timed.
120      */
setup()121     virtual void setup() {}
122 
123     /**
124      * Perform the work.  If this is being called within the context of bench_pictures,
125      * this is the step that will be timed.
126      *
127      * Typically "the work" is rendering an SkPicture into a bitmap, but in some subclasses
128      * it is recording the source SkPicture into another SkPicture.
129      *
130      * If fWritePath has been specified, the result of the work will be written to that dir.
131      * If fMismatchPath has been specified, and the actual image result differs from its
132      * expectation, the result of the work will be written to that dir.
133      *
134      * @param out If non-null, the implementing subclass MAY allocate an SkBitmap, copy the
135      *            output image into it, and return it here.  (Some subclasses ignore this parameter)
136      * @return bool True if rendering succeeded and, if fWritePath had been specified, the output
137      *              was successfully written to a file.
138      */
139     virtual bool render(SkBitmap** out = NULL) = 0;
140 
141     /**
142      * Called once finished with a particular SkPicture, before calling init again, and before
143      * being done with this Renderer.
144      */
145     virtual void end();
146 
147     /**
148      * If this PictureRenderer is actually a TiledPictureRender, return a pointer to this as a
149      * TiledPictureRender so its methods can be called.
150      */
getTiledRenderer()151     virtual TiledPictureRenderer* getTiledRenderer() { return NULL; }
152 
153     /**
154      * Resets the GPU's state. Does nothing if the backing is raster. For a GPU renderer, calls
155      * flush, swapBuffers and, if callFinish is true, finish.
156      * @param callFinish Whether to call finish.
157      */
158     void resetState(bool callFinish);
159 
160     /**
161      * Remove all decoded textures from the CPU caches and all uploaded textures
162      * from the GPU.
163      */
164     void purgeTextures();
165 
166     /**
167      * Set the backend type. Returns true on success and false on failure.
168      */
169 #if SK_SUPPORT_GPU
170     bool setDeviceType(SkDeviceTypes deviceType, GrGLStandard gpuAPI = kNone_GrGLStandard) {
171 #else
172     bool setDeviceType(SkDeviceTypes deviceType) {
173 #endif
174         fDeviceType = deviceType;
175 #if SK_SUPPORT_GPU
176         // In case this function is called more than once
177         SkSafeUnref(fGrContext);
178         fGrContext = NULL;
179         // Set to Native so it will have an initial value.
180         GrContextFactory::GLContextType glContextType = GrContextFactory::kNative_GLContextType;
181 #endif
182         switch(deviceType) {
183             case kBitmap_DeviceType:
184                 return true;
185 #if SK_SUPPORT_GPU
186             case kGPU_DeviceType:
187                 // Already set to GrContextFactory::kNative_GLContextType, above.
188                 break;
189             case kNVPR_DeviceType:
190                 glContextType = GrContextFactory::kNVPR_GLContextType;
191                 break;
192 #if SK_ANGLE
193             case kAngle_DeviceType:
194                 glContextType = GrContextFactory::kANGLE_GLContextType;
195                 break;
196 #endif
197 #if SK_MESA
198             case kMesa_DeviceType:
199                 glContextType = GrContextFactory::kMESA_GLContextType;
200                 break;
201 #endif
202 #endif
203             default:
204                 // Invalid device type.
205                 return false;
206         }
207 #if SK_SUPPORT_GPU
208         fGrContext = fGrContextFactory.get(glContextType, gpuAPI);
209         if (NULL == fGrContext) {
210             return false;
211         } else {
212             fGrContext->ref();
213             return true;
214         }
215 #endif
216     }
217 
218 #if SK_SUPPORT_GPU
setSampleCount(int sampleCount)219     void setSampleCount(int sampleCount) {
220         fSampleCount = sampleCount;
221     }
222 
setUseDFText(bool useDFText)223     void setUseDFText(bool useDFText) {
224         fUseDFText = useDFText;
225     }
226 #endif
227 
setDrawFilters(DrawFilterFlags const * const filters,const SkString & configName)228     void setDrawFilters(DrawFilterFlags const * const filters, const SkString& configName) {
229         memcpy(fDrawFilters, filters, sizeof(fDrawFilters));
230         fDrawFiltersConfig = configName;
231     }
232 
setBBoxHierarchyType(BBoxHierarchyType bbhType)233     void setBBoxHierarchyType(BBoxHierarchyType bbhType) {
234         fBBoxHierarchyType = bbhType;
235     }
236 
getBBoxHierarchyType()237     BBoxHierarchyType getBBoxHierarchyType() { return fBBoxHierarchyType; }
238 
setJsonSummaryPtr(ImageResultsAndExpectations * jsonSummaryPtr)239     void setJsonSummaryPtr(ImageResultsAndExpectations* jsonSummaryPtr) {
240         fJsonSummaryPtr = jsonSummaryPtr;
241     }
242 
isUsingBitmapDevice()243     bool isUsingBitmapDevice() {
244         return kBitmap_DeviceType == fDeviceType;
245     }
246 
getPerIterTimeFormat()247     virtual SkString getPerIterTimeFormat() { return SkString("%.2f"); }
248 
getNormalTimeFormat()249     virtual SkString getNormalTimeFormat() { return SkString("%6.2f"); }
250 
251     /**
252      * Reports the configuration of this PictureRenderer.
253      */
getConfigName()254     SkString getConfigName() {
255         SkString config = this->getConfigNameInternal();
256         if (!fViewport.isEmpty()) {
257             config.appendf("_viewport_%ix%i", fViewport.width(), fViewport.height());
258         }
259         if (fScaleFactor != SK_Scalar1) {
260             config.appendf("_scalar_%f", SkScalarToFloat(fScaleFactor));
261         }
262         if (kRTree_BBoxHierarchyType == fBBoxHierarchyType) {
263             config.append("_rtree");
264         }
265 #if SK_SUPPORT_GPU
266         switch (fDeviceType) {
267             case kGPU_DeviceType:
268                 if (fSampleCount) {
269                     config.appendf("_msaa%d", fSampleCount);
270                 } else if (fUseDFText) {
271                     config.append("_gpudft");
272                 } else {
273                     config.append("_gpu");
274                 }
275                 break;
276             case kNVPR_DeviceType:
277                 config.appendf("_nvprmsaa%d", fSampleCount);
278                 break;
279 #if SK_ANGLE
280             case kAngle_DeviceType:
281                 config.append("_angle");
282                 break;
283 #endif
284 #if SK_MESA
285             case kMesa_DeviceType:
286                 config.append("_mesa");
287                 break;
288 #endif
289             default:
290                 // Assume that no extra info means bitmap.
291                 break;
292         }
293 #endif
294         config.append(fDrawFiltersConfig.c_str());
295         return config;
296     }
297 
getJSONConfig()298     Json::Value getJSONConfig() {
299         Json::Value result;
300 
301         result["mode"] = this->getConfigNameInternal().c_str();
302         result["scale"] = 1.0f;
303         if (SK_Scalar1 != fScaleFactor) {
304             result["scale"] = SkScalarToFloat(fScaleFactor);
305         }
306         if (kRTree_BBoxHierarchyType == fBBoxHierarchyType) {
307             result["bbh"] = "rtree";
308         }
309 #if SK_SUPPORT_GPU
310         SkString tmp;
311         switch (fDeviceType) {
312             case kGPU_DeviceType:
313                 if (0 != fSampleCount) {
314                     tmp = "msaa";
315                     tmp.appendS32(fSampleCount);
316                     result["config"] = tmp.c_str();
317                 } else if (fUseDFText) {
318                     result["config"] = "gpudft";
319                 } else {
320                     result["config"] = "gpu";
321                 }
322                 break;
323             case kNVPR_DeviceType:
324                 tmp = "nvprmsaa";
325                 tmp.appendS32(fSampleCount);
326                 result["config"] = tmp.c_str();
327                 break;
328 #if SK_ANGLE
329             case kAngle_DeviceType:
330                 result["config"] = "angle";
331                 break;
332 #endif
333 #if SK_MESA
334             case kMesa_DeviceType:
335                 result["config"] = "mesa";
336                 break;
337 #endif
338             default:
339                 // Assume that no extra info means bitmap.
340                 break;
341         }
342 #endif
343         return result;
344     }
345 
346 #if SK_SUPPORT_GPU
isUsingGpuDevice()347     bool isUsingGpuDevice() {
348         switch (fDeviceType) {
349             case kGPU_DeviceType:
350             case kNVPR_DeviceType:
351                 // fall through
352 #if SK_ANGLE
353             case kAngle_DeviceType:
354                 // fall through
355 #endif
356 #if SK_MESA
357             case kMesa_DeviceType:
358 #endif
359                 return true;
360             default:
361                 return false;
362         }
363     }
364 
getGLContext()365     SkGLContext* getGLContext() {
366         GrContextFactory::GLContextType glContextType
367                 = GrContextFactory::kNull_GLContextType;
368         switch(fDeviceType) {
369             case kGPU_DeviceType:
370                 glContextType = GrContextFactory::kNative_GLContextType;
371                 break;
372             case kNVPR_DeviceType:
373                 glContextType = GrContextFactory::kNVPR_GLContextType;
374                 break;
375 #if SK_ANGLE
376             case kAngle_DeviceType:
377                 glContextType = GrContextFactory::kANGLE_GLContextType;
378                 break;
379 #endif
380 #if SK_MESA
381             case kMesa_DeviceType:
382                 glContextType = GrContextFactory::kMESA_GLContextType;
383                 break;
384 #endif
385             default:
386                 return NULL;
387         }
388         return fGrContextFactory.getGLContext(glContextType);
389     }
390 
getGrContext()391     GrContext* getGrContext() {
392         return fGrContext;
393     }
394 
getGrContextOptions()395     const GrContext::Options& getGrContextOptions() {
396         return fGrContextFactory.getGlobalOptions();
397     }
398 #endif
399 
getCanvas()400     SkCanvas* getCanvas() {
401         return fCanvas;
402     }
403 
getPicture()404     const SkPicture* getPicture() {
405         return fPicture;
406     }
407 
408 #if SK_SUPPORT_GPU
PictureRenderer(const GrContext::Options & opts)409     explicit PictureRenderer(const GrContext::Options &opts)
410 #else
411     PictureRenderer()
412 #endif
413         : fJsonSummaryPtr(NULL)
414         , fDeviceType(kBitmap_DeviceType)
415         , fEnableWrites(false)
416         , fBBoxHierarchyType(kNone_BBoxHierarchyType)
417         , fScaleFactor(SK_Scalar1)
418 #if SK_SUPPORT_GPU
419         , fGrContextFactory(opts)
420         , fGrContext(NULL)
421         , fSampleCount(0)
422         , fUseDFText(false)
423 #endif
424         {
425             sk_bzero(fDrawFilters, sizeof(fDrawFilters));
426             fViewport.set(0, 0);
427         }
428 
429 #if SK_SUPPORT_GPU
~PictureRenderer()430     virtual ~PictureRenderer() {
431         SkSafeUnref(fGrContext);
432     }
433 #endif
434 
435 protected:
436     SkAutoTUnref<SkCanvas> fCanvas;
437     SkAutoTUnref<const SkPicture> fPicture;
438     bool                   fUseChecksumBasedFilenames;
439     bool                   fUseMultiPictureDraw;
440     ImageResultsAndExpectations*   fJsonSummaryPtr;
441     SkDeviceTypes          fDeviceType;
442     bool                   fEnableWrites;
443     BBoxHierarchyType      fBBoxHierarchyType;
444     DrawFilterFlags        fDrawFilters[SkDrawFilter::kTypeCount];
445     SkString               fDrawFiltersConfig;
446     SkString               fWritePath;
447     SkString               fMismatchPath;
448     SkString               fInputFilename;
449 
450     void buildBBoxHierarchy();
451 
452     /**
453      * Return the total width that should be drawn. If the viewport width has been set greater than
454      * 0, this will be the minimum of the current SkPicture's width and the viewport's width.
455      */
456     int getViewWidth();
457 
458     /**
459      * Return the total height that should be drawn. If the viewport height has been set greater
460      * than 0, this will be the minimum of the current SkPicture's height and the viewport's height.
461      */
462     int getViewHeight();
463 
464     /**
465      * Scales the provided canvas to the scale factor set by setScaleFactor.
466      */
467     void scaleToScaleFactor(SkCanvas*);
468 
469     SkBBHFactory* getFactory();
recordFlags()470     uint32_t recordFlags() const { return 0; }
471     SkCanvas* setupCanvas();
472     virtual SkCanvas* setupCanvas(int width, int height);
473 
474     /**
475      * Copy src to dest; if src==NULL, set dest to empty string.
476      */
477     static void CopyString(SkString* dest, const SkString* src);
478 
479 private:
480     SkISize                fViewport;
481     SkScalar               fScaleFactor;
482 #if SK_SUPPORT_GPU
483     GrContextFactory       fGrContextFactory;
484     GrContext*             fGrContext;
485     int                    fSampleCount;
486     bool                   fUseDFText;
487 #endif
488 
489     virtual SkString getConfigNameInternal() = 0;
490 
491     typedef SkRefCnt INHERITED;
492 };
493 
494 /**
495  * This class does not do any rendering, but its render function executes recording, which we want
496  * to time.
497  */
498 class RecordPictureRenderer : public PictureRenderer {
499 public:
500 #if SK_SUPPORT_GPU
RecordPictureRenderer(const GrContext::Options & opts)501     RecordPictureRenderer(const GrContext::Options &opts) : INHERITED(opts) { }
502 #endif
503 
504     bool render(SkBitmap** out = NULL) override;
505 
getPerIterTimeFormat()506     SkString getPerIterTimeFormat() override { return SkString("%.4f"); }
507 
getNormalTimeFormat()508     SkString getNormalTimeFormat() override { return SkString("%6.4f"); }
509 
510 protected:
511     SkCanvas* setupCanvas(int width, int height) override;
512 
513 private:
514     SkString getConfigNameInternal() override;
515 
516     typedef PictureRenderer INHERITED;
517 };
518 
519 class PipePictureRenderer : public PictureRenderer {
520 public:
521 #if SK_SUPPORT_GPU
PipePictureRenderer(const GrContext::Options & opts)522     PipePictureRenderer(const GrContext::Options &opts) : INHERITED(opts) { }
523 #endif
524 
525     bool render(SkBitmap** out = NULL) override;
526 
527 private:
528     SkString getConfigNameInternal() override;
529 
530     typedef PictureRenderer INHERITED;
531 };
532 
533 class SimplePictureRenderer : public PictureRenderer {
534 public:
535 #if SK_SUPPORT_GPU
SimplePictureRenderer(const GrContext::Options & opts)536     SimplePictureRenderer(const GrContext::Options &opts) : INHERITED(opts) { }
537 #endif
538 
539     virtual void init(const SkPicture* pict,
540                       const SkString* writePath,
541                       const SkString* mismatchPath,
542                       const SkString* inputFilename,
543                       bool useChecksumBasedFilenames,
544                       bool useMultiPictureDraw) override;
545 
546     bool render(SkBitmap** out = NULL) override;
547 
548 private:
549     SkString getConfigNameInternal() override;
550 
551     typedef PictureRenderer INHERITED;
552 };
553 
554 class TiledPictureRenderer : public PictureRenderer {
555 public:
556 #if SK_SUPPORT_GPU
557     TiledPictureRenderer(const GrContext::Options &opts);
558 #else
559     TiledPictureRenderer();
560 #endif
561 
562     virtual void init(const SkPicture* pict,
563                       const SkString* writePath,
564                       const SkString* mismatchPath,
565                       const SkString* inputFilename,
566                       bool useChecksumBasedFilenames,
567                       bool useMultiPictureDraw) override;
568 
569     /**
570      * Renders to tiles, rather than a single canvas.
571      * If fWritePath was provided, a separate file is
572      * created for each tile, named "path0.png", "path1.png", etc.
573      */
574     bool render(SkBitmap** out = NULL) override;
575 
576     void end() override;
577 
setTileWidth(int width)578     void setTileWidth(int width) {
579         fTileWidth = width;
580     }
581 
getTileWidth()582     int getTileWidth() const {
583         return fTileWidth;
584     }
585 
setTileHeight(int height)586     void setTileHeight(int height) {
587         fTileHeight = height;
588     }
589 
getTileHeight()590     int getTileHeight() const {
591         return fTileHeight;
592     }
593 
setTileWidthPercentage(double percentage)594     void setTileWidthPercentage(double percentage) {
595         fTileWidthPercentage = percentage;
596     }
597 
getTileWidthPercentage()598     double getTileWidthPercentage() const {
599         return fTileWidthPercentage;
600     }
601 
setTileHeightPercentage(double percentage)602     void setTileHeightPercentage(double percentage) {
603         fTileHeightPercentage = percentage;
604     }
605 
getTileHeightPercentage()606     double getTileHeightPercentage() const {
607         return fTileHeightPercentage;
608     }
609 
setTileMinPowerOf2Width(int width)610     void setTileMinPowerOf2Width(int width) {
611         SkASSERT(SkIsPow2(width) && width > 0);
612         if (!SkIsPow2(width) || width <= 0) {
613             return;
614         }
615 
616         fTileMinPowerOf2Width = width;
617     }
618 
getTileMinPowerOf2Width()619     int getTileMinPowerOf2Width() const {
620         return fTileMinPowerOf2Width;
621     }
622 
getTiledRenderer()623     TiledPictureRenderer* getTiledRenderer() override { return this; }
624 
supportsTimingIndividualTiles()625     virtual bool supportsTimingIndividualTiles() { return true; }
626 
627     /**
628      * Report the number of tiles in the x and y directions. Must not be called before init.
629      * @param x Output parameter identifying the number of tiles in the x direction.
630      * @param y Output parameter identifying the number of tiles in the y direction.
631      * @return True if the tiles have been set up, and x and y are meaningful. If false, x and y are
632      *         unmodified.
633      */
634     bool tileDimensions(int& x, int&y);
635 
636     /**
637      * Move to the next tile and return its indices. Must be called before calling drawCurrentTile
638      * for the first time.
639      * @param i Output parameter identifying the column of the next tile to be drawn on the next
640      *          call to drawNextTile.
641      * @param j Output parameter identifying the row  of the next tile to be drawn on the next call
642      *          to drawNextTile.
643      * @param True if the tiles have been created and the next tile to be drawn by drawCurrentTile
644      *        is within the range of tiles. If false, i and j are unmodified.
645      */
646     bool nextTile(int& i, int& j);
647 
648     /**
649      * Render one tile. This will draw the same tile each time it is called until nextTile is
650      * called. The tile rendered will depend on how many calls have been made to nextTile.
651      * It is an error to call this without first calling nextTile, or if nextTile returns false.
652      */
653     void drawCurrentTile();
654 
655 protected:
656     SkTDArray<SkIRect> fTileRects;
657 
658     SkCanvas* setupCanvas(int width, int height) override;
659     SkString getConfigNameInternal() override;
660 
661 private:
662     int    fTileWidth;
663     int    fTileHeight;
664     double fTileWidthPercentage;
665     double fTileHeightPercentage;
666     int    fTileMinPowerOf2Width;
667 
668     // These variables are only used for timing individual tiles.
669     // Next tile to draw in fTileRects.
670     int    fCurrentTileOffset;
671     // Number of tiles in the x direction.
672     int    fTilesX;
673     // Number of tiles in the y direction.
674     int    fTilesY;
675 
676     void setupTiles();
677     void setupPowerOf2Tiles();
678     bool postRender(SkCanvas*, const SkIRect& tileRect,
679                     SkBitmap* tempBM, SkBitmap** out,
680                     int tileNumber);
681 
682     typedef PictureRenderer INHERITED;
683 };
684 
685 /**
686  * This class does not do any rendering, but its render function executes turning an SkPictureRecord
687  * into an SkPicturePlayback, which we want to time.
688  */
689 class PlaybackCreationRenderer : public PictureRenderer {
690 public:
691 #if SK_SUPPORT_GPU
PlaybackCreationRenderer(const GrContext::Options & opts)692     PlaybackCreationRenderer(const GrContext::Options &opts) : INHERITED(opts) { }
693 #endif
694 
695     void setup() override;
696 
697     bool render(SkBitmap** out = NULL) override;
698 
getPerIterTimeFormat()699     SkString getPerIterTimeFormat() override { return SkString("%.4f"); }
700 
getNormalTimeFormat()701     SkString getNormalTimeFormat() override { return SkString("%6.4f"); }
702 
703 private:
704     SkAutoTDelete<SkPictureRecorder> fRecorder;
705 
706     SkString getConfigNameInternal() override;
707 
708     typedef PictureRenderer INHERITED;
709 };
710 
711 #if SK_SUPPORT_GPU
712 extern PictureRenderer* CreateGatherPixelRefsRenderer(const GrContext::Options& opts);
713 #else
714 extern PictureRenderer* CreateGatherPixelRefsRenderer();
715 #endif
716 
717 }
718 
719 #endif  // PictureRenderer_DEFINED
720