/* * Copyright 2012 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef PictureRenderer_DEFINED #define PictureRenderer_DEFINED #include "SkCanvas.h" #include "SkDrawFilter.h" #include "SkJSONCPP.h" #include "SkMath.h" #include "SkPaint.h" #include "SkPicture.h" #include "SkPictureRecorder.h" #include "SkRect.h" #include "SkRefCnt.h" #include "SkString.h" #include "SkTDArray.h" #include "SkTypes.h" #if SK_SUPPORT_GPU #include "GrContextFactory.h" #include "GrContext.h" #endif #include "image_expectations.h" class SkBitmap; class SkCanvas; class SkGLContextHelper; class SkThread; namespace sk_tools { class TiledPictureRenderer; class PictureRenderer : public SkRefCnt { public: enum SkDeviceTypes { #if SK_ANGLE kAngle_DeviceType, #endif #if SK_MESA kMesa_DeviceType, #endif kBitmap_DeviceType, #if SK_SUPPORT_GPU kGPU_DeviceType, kNVPR_DeviceType, #endif }; enum BBoxHierarchyType { kNone_BBoxHierarchyType = 0, kRTree_BBoxHierarchyType, kTileGrid_BBoxHierarchyType, kLast_BBoxHierarchyType = kTileGrid_BBoxHierarchyType, }; // this uses SkPaint::Flags as a base and adds additional flags enum DrawFilterFlags { kNone_DrawFilterFlag = 0, kHinting_DrawFilterFlag = 0x10000, // toggles between no hinting and normal hinting kSlightHinting_DrawFilterFlag = 0x20000, // toggles between slight and normal hinting kAAClip_DrawFilterFlag = 0x40000, // toggles between soft and hard clip kMaskFilter_DrawFilterFlag = 0x80000, // toggles on/off mask filters (e.g., blurs) }; SK_COMPILE_ASSERT(!(kMaskFilter_DrawFilterFlag & SkPaint::kAllFlags), maskfilter_flag_must_be_greater); SK_COMPILE_ASSERT(!(kHinting_DrawFilterFlag & SkPaint::kAllFlags), hinting_flag_must_be_greater); SK_COMPILE_ASSERT(!(kSlightHinting_DrawFilterFlag & SkPaint::kAllFlags), slight_hinting_flag_must_be_greater); /** * Called with each new SkPicture to render. * * @param pict The SkPicture to render. * @param writePath The output directory within which this renderer should write all images, * or NULL if this renderer should not write all images. * @param mismatchPath The output directory within which this renderer should write any images * which do not match expectations, or NULL if this renderer should not write mismatches. * @param inputFilename The name of the input file we are rendering. * @param useChecksumBasedFilenames Whether to use checksum-based filenames when writing * bitmap images to disk. */ virtual void init(const SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, const SkString* inputFilename, bool useChecksumBasedFilenames); /** * TODO(epoger): Temporary hack, while we work on http://skbug.com/2584 ('bench_pictures is * timing reading pixels and writing json files'), such that: * - render_pictures can call this method and continue to work * - any other callers (bench_pictures) will skip calls to write() by default */ void enableWrites() { fEnableWrites = true; } /** * Set the viewport so that only the portion listed gets drawn. */ void setViewport(SkISize size) { fViewport = size; } /** * Set the scale factor at which draw the picture. */ void setScaleFactor(SkScalar scale) { fScaleFactor = scale; } /** * Perform any setup that should done prior to each iteration of render() which should not be * timed. */ virtual void setup() {} /** * Perform the work. If this is being called within the context of bench_pictures, * this is the step that will be timed. * * Typically "the work" is rendering an SkPicture into a bitmap, but in some subclasses * it is recording the source SkPicture into another SkPicture. * * If fWritePath has been specified, the result of the work will be written to that dir. * If fMismatchPath has been specified, and the actual image result differs from its * expectation, the result of the work will be written to that dir. * * @param out If non-null, the implementing subclass MAY allocate an SkBitmap, copy the * output image into it, and return it here. (Some subclasses ignore this parameter) * @return bool True if rendering succeeded and, if fWritePath had been specified, the output * was successfully written to a file. */ virtual bool render(SkBitmap** out = NULL) = 0; /** * Called once finished with a particular SkPicture, before calling init again, and before * being done with this Renderer. */ virtual void end(); /** * If this PictureRenderer is actually a TiledPictureRender, return a pointer to this as a * TiledPictureRender so its methods can be called. */ virtual TiledPictureRenderer* getTiledRenderer() { return NULL; } /** * Resets the GPU's state. Does nothing if the backing is raster. For a GPU renderer, calls * flush, swapBuffers and, if callFinish is true, finish. * @param callFinish Whether to call finish. */ void resetState(bool callFinish); /** * Remove all decoded textures from the CPU caches and all uploaded textures * from the GPU. */ void purgeTextures(); /** * Set the backend type. Returns true on success and false on failure. */ #if SK_SUPPORT_GPU bool setDeviceType(SkDeviceTypes deviceType, GrGLStandard gpuAPI = kNone_GrGLStandard) { #else bool setDeviceType(SkDeviceTypes deviceType) { #endif fDeviceType = deviceType; #if SK_SUPPORT_GPU // In case this function is called more than once SkSafeUnref(fGrContext); fGrContext = NULL; // Set to Native so it will have an initial value. GrContextFactory::GLContextType glContextType = GrContextFactory::kNative_GLContextType; #endif switch(deviceType) { case kBitmap_DeviceType: return true; #if SK_SUPPORT_GPU case kGPU_DeviceType: // Already set to GrContextFactory::kNative_GLContextType, above. break; case kNVPR_DeviceType: glContextType = GrContextFactory::kNVPR_GLContextType; break; #if SK_ANGLE case kAngle_DeviceType: glContextType = GrContextFactory::kANGLE_GLContextType; break; #endif #if SK_MESA case kMesa_DeviceType: glContextType = GrContextFactory::kMESA_GLContextType; break; #endif #endif default: // Invalid device type. return false; } #if SK_SUPPORT_GPU fGrContext = fGrContextFactory.get(glContextType, gpuAPI); if (NULL == fGrContext) { return false; } else { fGrContext->ref(); return true; } #endif } #if SK_SUPPORT_GPU void setSampleCount(int sampleCount) { fSampleCount = sampleCount; } #endif void setDrawFilters(DrawFilterFlags const * const filters, const SkString& configName) { memcpy(fDrawFilters, filters, sizeof(fDrawFilters)); fDrawFiltersConfig = configName; } void setBBoxHierarchyType(BBoxHierarchyType bbhType) { fBBoxHierarchyType = bbhType; } BBoxHierarchyType getBBoxHierarchyType() { return fBBoxHierarchyType; } void setGridSize(int width, int height) { fGridInfo.fTileInterval.set(width, height); } void setJsonSummaryPtr(ImageResultsAndExpectations* jsonSummaryPtr) { fJsonSummaryPtr = jsonSummaryPtr; } bool isUsingBitmapDevice() { return kBitmap_DeviceType == fDeviceType; } virtual SkString getPerIterTimeFormat() { return SkString("%.2f"); } virtual SkString getNormalTimeFormat() { return SkString("%6.2f"); } /** * Reports the configuration of this PictureRenderer. */ SkString getConfigName() { SkString config = this->getConfigNameInternal(); if (!fViewport.isEmpty()) { config.appendf("_viewport_%ix%i", fViewport.width(), fViewport.height()); } if (fScaleFactor != SK_Scalar1) { config.appendf("_scalar_%f", SkScalarToFloat(fScaleFactor)); } if (kRTree_BBoxHierarchyType == fBBoxHierarchyType) { config.append("_rtree"); } else if (kTileGrid_BBoxHierarchyType == fBBoxHierarchyType) { config.append("_grid"); config.append("_"); config.appendS32(fGridInfo.fTileInterval.width()); config.append("x"); config.appendS32(fGridInfo.fTileInterval.height()); } #if SK_SUPPORT_GPU switch (fDeviceType) { case kGPU_DeviceType: if (fSampleCount) { config.appendf("_msaa%d", fSampleCount); } else { config.append("_gpu"); } break; case kNVPR_DeviceType: config.appendf("_nvprmsaa%d", fSampleCount); break; #if SK_ANGLE case kAngle_DeviceType: config.append("_angle"); break; #endif #if SK_MESA case kMesa_DeviceType: config.append("_mesa"); break; #endif default: // Assume that no extra info means bitmap. break; } #endif config.append(fDrawFiltersConfig.c_str()); return config; } Json::Value getJSONConfig() { Json::Value result; result["mode"] = this->getConfigNameInternal().c_str(); result["scale"] = 1.0f; if (SK_Scalar1 != fScaleFactor) { result["scale"] = SkScalarToFloat(fScaleFactor); } if (kRTree_BBoxHierarchyType == fBBoxHierarchyType) { result["bbh"] = "rtree"; } else if (kTileGrid_BBoxHierarchyType == fBBoxHierarchyType) { SkString tmp("grid_"); tmp.appendS32(fGridInfo.fTileInterval.width()); tmp.append("x"); tmp.appendS32(fGridInfo.fTileInterval.height()); result["bbh"] = tmp.c_str(); } #if SK_SUPPORT_GPU SkString tmp; switch (fDeviceType) { case kGPU_DeviceType: if (0 != fSampleCount) { tmp = "msaa"; tmp.appendS32(fSampleCount); result["config"] = tmp.c_str(); } else { result["config"] = "gpu"; } break; case kNVPR_DeviceType: tmp = "nvprmsaa"; tmp.appendS32(fSampleCount); result["config"] = tmp.c_str(); break; #if SK_ANGLE case kAngle_DeviceType: result["config"] = "angle"; break; #endif #if SK_MESA case kMesa_DeviceType: result["config"] = "mesa"; break; #endif default: // Assume that no extra info means bitmap. break; } #endif return result; } #if SK_SUPPORT_GPU bool isUsingGpuDevice() { switch (fDeviceType) { case kGPU_DeviceType: case kNVPR_DeviceType: // fall through #if SK_ANGLE case kAngle_DeviceType: // fall through #endif #if SK_MESA case kMesa_DeviceType: #endif return true; default: return false; } } SkGLContextHelper* getGLContext() { GrContextFactory::GLContextType glContextType = GrContextFactory::kNull_GLContextType; switch(fDeviceType) { case kGPU_DeviceType: glContextType = GrContextFactory::kNative_GLContextType; break; case kNVPR_DeviceType: glContextType = GrContextFactory::kNVPR_GLContextType; break; #if SK_ANGLE case kAngle_DeviceType: glContextType = GrContextFactory::kANGLE_GLContextType; break; #endif #if SK_MESA case kMesa_DeviceType: glContextType = GrContextFactory::kMESA_GLContextType; break; #endif default: return NULL; } return fGrContextFactory.getGLContext(glContextType); } GrContext* getGrContext() { return fGrContext; } const GrContext::Options& getGrContextOptions() { return fGrContextFactory.getGlobalOptions(); } #endif SkCanvas* getCanvas() { return fCanvas; } const SkPicture* getPicture() { return fPicture; } #if SK_SUPPORT_GPU explicit PictureRenderer(const GrContext::Options &opts) #else PictureRenderer() #endif : fJsonSummaryPtr(NULL) , fDeviceType(kBitmap_DeviceType) , fEnableWrites(false) , fBBoxHierarchyType(kNone_BBoxHierarchyType) , fScaleFactor(SK_Scalar1) #if SK_SUPPORT_GPU , fGrContextFactory(opts) , fGrContext(NULL) , fSampleCount(0) #endif { fGridInfo.fMargin.setEmpty(); fGridInfo.fOffset.setZero(); fGridInfo.fTileInterval.set(1, 1); sk_bzero(fDrawFilters, sizeof(fDrawFilters)); fViewport.set(0, 0); } #if SK_SUPPORT_GPU virtual ~PictureRenderer() { SkSafeUnref(fGrContext); } #endif protected: SkAutoTUnref fCanvas; SkAutoTUnref fPicture; bool fUseChecksumBasedFilenames; ImageResultsAndExpectations* fJsonSummaryPtr; SkDeviceTypes fDeviceType; bool fEnableWrites; BBoxHierarchyType fBBoxHierarchyType; DrawFilterFlags fDrawFilters[SkDrawFilter::kTypeCount]; SkString fDrawFiltersConfig; SkString fWritePath; SkString fMismatchPath; SkString fInputFilename; SkTileGridFactory::TileGridInfo fGridInfo; // used when fBBoxHierarchyType is TileGrid void buildBBoxHierarchy(); /** * Return the total width that should be drawn. If the viewport width has been set greater than * 0, this will be the minimum of the current SkPicture's width and the viewport's width. */ int getViewWidth(); /** * Return the total height that should be drawn. If the viewport height has been set greater * than 0, this will be the minimum of the current SkPicture's height and the viewport's height. */ int getViewHeight(); /** * Scales the provided canvas to the scale factor set by setScaleFactor. */ void scaleToScaleFactor(SkCanvas*); SkBBHFactory* getFactory(); uint32_t recordFlags() const { return 0; } SkCanvas* setupCanvas(); virtual SkCanvas* setupCanvas(int width, int height); /** * Copy src to dest; if src==NULL, set dest to empty string. */ static void CopyString(SkString* dest, const SkString* src); private: SkISize fViewport; SkScalar fScaleFactor; #if SK_SUPPORT_GPU GrContextFactory fGrContextFactory; GrContext* fGrContext; int fSampleCount; #endif virtual SkString getConfigNameInternal() = 0; typedef SkRefCnt INHERITED; }; /** * This class does not do any rendering, but its render function executes recording, which we want * to time. */ class RecordPictureRenderer : public PictureRenderer { public: #if SK_SUPPORT_GPU RecordPictureRenderer(const GrContext::Options &opts) : INHERITED(opts) { } #endif virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; virtual SkString getPerIterTimeFormat() SK_OVERRIDE { return SkString("%.4f"); } virtual SkString getNormalTimeFormat() SK_OVERRIDE { return SkString("%6.4f"); } protected: virtual SkCanvas* setupCanvas(int width, int height) SK_OVERRIDE; private: virtual SkString getConfigNameInternal() SK_OVERRIDE; typedef PictureRenderer INHERITED; }; class PipePictureRenderer : public PictureRenderer { public: #if SK_SUPPORT_GPU PipePictureRenderer(const GrContext::Options &opts) : INHERITED(opts) { } #endif virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; private: virtual SkString getConfigNameInternal() SK_OVERRIDE; typedef PictureRenderer INHERITED; }; class SimplePictureRenderer : public PictureRenderer { public: #if SK_SUPPORT_GPU SimplePictureRenderer(const GrContext::Options &opts) : INHERITED(opts) { } #endif virtual void init(const SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE; virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; private: virtual SkString getConfigNameInternal() SK_OVERRIDE; typedef PictureRenderer INHERITED; }; class TiledPictureRenderer : public PictureRenderer { public: #if SK_SUPPORT_GPU TiledPictureRenderer(const GrContext::Options &opts); #else TiledPictureRenderer(); #endif virtual void init(const SkPicture* pict, const SkString* writePath, const SkString* mismatchPath, const SkString* inputFilename, bool useChecksumBasedFilenames) SK_OVERRIDE; /** * Renders to tiles, rather than a single canvas. * If fWritePath was provided, a separate file is * created for each tile, named "path0.png", "path1.png", etc. */ virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; virtual void end() SK_OVERRIDE; void setTileWidth(int width) { fTileWidth = width; } int getTileWidth() const { return fTileWidth; } void setTileHeight(int height) { fTileHeight = height; } int getTileHeight() const { return fTileHeight; } void setTileWidthPercentage(double percentage) { fTileWidthPercentage = percentage; } double getTileWidthPercentage() const { return fTileWidthPercentage; } void setTileHeightPercentage(double percentage) { fTileHeightPercentage = percentage; } double getTileHeightPercentage() const { return fTileHeightPercentage; } void setTileMinPowerOf2Width(int width) { SkASSERT(SkIsPow2(width) && width > 0); if (!SkIsPow2(width) || width <= 0) { return; } fTileMinPowerOf2Width = width; } int getTileMinPowerOf2Width() const { return fTileMinPowerOf2Width; } virtual TiledPictureRenderer* getTiledRenderer() SK_OVERRIDE { return this; } virtual bool supportsTimingIndividualTiles() { return true; } /** * Report the number of tiles in the x and y directions. Must not be called before init. * @param x Output parameter identifying the number of tiles in the x direction. * @param y Output parameter identifying the number of tiles in the y direction. * @return True if the tiles have been set up, and x and y are meaningful. If false, x and y are * unmodified. */ bool tileDimensions(int& x, int&y); /** * Move to the next tile and return its indices. Must be called before calling drawCurrentTile * for the first time. * @param i Output parameter identifying the column of the next tile to be drawn on the next * call to drawNextTile. * @param j Output parameter identifying the row of the next tile to be drawn on the next call * to drawNextTile. * @param True if the tiles have been created and the next tile to be drawn by drawCurrentTile * is within the range of tiles. If false, i and j are unmodified. */ bool nextTile(int& i, int& j); /** * Render one tile. This will draw the same tile each time it is called until nextTile is * called. The tile rendered will depend on how many calls have been made to nextTile. * It is an error to call this without first calling nextTile, or if nextTile returns false. */ void drawCurrentTile(); protected: SkTDArray fTileRects; virtual SkCanvas* setupCanvas(int width, int height) SK_OVERRIDE; virtual SkString getConfigNameInternal() SK_OVERRIDE; private: int fTileWidth; int fTileHeight; double fTileWidthPercentage; double fTileHeightPercentage; int fTileMinPowerOf2Width; // These variables are only used for timing individual tiles. // Next tile to draw in fTileRects. int fCurrentTileOffset; // Number of tiles in the x direction. int fTilesX; // Number of tiles in the y direction. int fTilesY; void setupTiles(); void setupPowerOf2Tiles(); typedef PictureRenderer INHERITED; }; /** * This class does not do any rendering, but its render function executes turning an SkPictureRecord * into an SkPicturePlayback, which we want to time. */ class PlaybackCreationRenderer : public PictureRenderer { public: #if SK_SUPPORT_GPU PlaybackCreationRenderer(const GrContext::Options &opts) : INHERITED(opts) { } #endif virtual void setup() SK_OVERRIDE; virtual bool render(SkBitmap** out = NULL) SK_OVERRIDE; virtual SkString getPerIterTimeFormat() SK_OVERRIDE { return SkString("%.4f"); } virtual SkString getNormalTimeFormat() SK_OVERRIDE { return SkString("%6.4f"); } private: SkAutoTDelete fRecorder; virtual SkString getConfigNameInternal() SK_OVERRIDE; typedef PictureRenderer INHERITED; }; #if SK_SUPPORT_GPU extern PictureRenderer* CreateGatherPixelRefsRenderer(const GrContext::Options& opts); #else extern PictureRenderer* CreateGatherPixelRefsRenderer(); #endif } #endif // PictureRenderer_DEFINED