/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "PictureRenderingFlags.h" #include "CopyTilesRenderer.h" #include "PictureRenderer.h" #include "picture_utils.h" #include "SkBitmapFactory.h" #include "SkCommandLineFlags.h" #include "SkData.h" #include "SkImage.h" #include "SkImageDecoder.h" #include "SkLruImageCache.h" #include "SkPurgeableImageCache.h" #include "SkString.h" // Alphabetized list of flags used by this file or bench_ and render_pictures. DEFINE_string(bbh, "none", "bbhType [width height]: Set the bounding box hierarchy type to " "be used. Accepted values are: none, rtree, grid. " "Not compatible with --pipe. With value " "'grid', width and height must be specified. 'grid' can " "only be used with modes tile, record, and " "playbackCreation."); // Although this config does not support all the same options as gm, the names should be kept // consistent. #if SK_ANGLE // ANGLE assumes GPU DEFINE_string(config, "8888", "[8888|gpu|msaa4|msaa16|angle]: Use the corresponding config."); #elif SK_SUPPORT_GPU DEFINE_string(config, "8888", "[8888|gpu|msaa4|msaa16]: Use the corresponding config."); #else DEFINE_string(config, "8888", "[8888]: Use the corresponding config."); #endif DEFINE_bool(deferImageDecoding, false, "Defer decoding until drawing images. " "Has no effect if the provided skp does not have its images encoded."); DEFINE_string(mode, "simple", "Run in the corresponding mode:\n" "simple: Simple rendering.\n" "tile width height: Use tiles with the given dimensions or percentages.\n" "pow2tile minWidth height: Use tiles with widths that are all a power\n" "\tof two such that they minimize the amount of wasted tile space.\n" "\tminWidth must be a power of two.\n" "copyTile width height: Draw the picture, then copy into tiles. If the\n" "\tpicture is large enough, it is broken into larger tiles to avoid\n" "\tcreating a large canvas.\n" // TODO: If bench_pictures and render_pictures were two separate targets, we could use build flags // to determine which modes to display. "record: (Only in bench_pictures) Time recording from a picture to a new\n" "\tpicture.\n" "playbackCreation: (Only in bench_pictures) Time creation of the \n" "\tSkPicturePlayback.\n" "rerecord: (Only in render_pictures) Record the picture as a new skp,\n" "\twith the bitmaps PNG encoded.\n"); DEFINE_int32(multi, 1, "Set the number of threads for multi threaded drawing. " "If > 1, requires tiled rendering."); DEFINE_bool(pipe, false, "Use SkGPipe rendering. Currently incompatible with \"mode\"."); DEFINE_string2(readPath, r, "", "skp files or directories of skp files to process."); DEFINE_double(scale, 1, "Set the scale factor."); DEFINE_string(tiles, "", "Used with --mode copyTile to specify number of tiles per larger tile " "in the x and y directions."); DEFINE_bool(useVolatileCache, false, "Use a volatile cache for deferred image decoding pixels. " "Only meaningful if --deferImageDecoding is set to true and the platform has an " "implementation."); DEFINE_string(viewport, "", "width height: Set the viewport."); sk_tools::PictureRenderer* parseRenderer(SkString& error, PictureTool tool) { error.reset(); if (FLAGS_multi <= 0) { error.printf("--multi must be > 0, was %i", FLAGS_multi); return NULL; } bool useTiles = false; const char* widthString = NULL; const char* heightString = NULL; bool isPowerOf2Mode = false; bool isCopyMode = false; const char* mode = NULL; bool gridSupported = false; SkAutoTUnref renderer; if (FLAGS_mode.count() >= 1) { mode = FLAGS_mode[0]; if (0 == strcmp(mode, "record")) { renderer.reset(SkNEW(sk_tools::RecordPictureRenderer)); gridSupported = true; // undocumented } else if (0 == strcmp(mode, "clone")) { renderer.reset(sk_tools::CreatePictureCloneRenderer()); } else if (0 == strcmp(mode, "tile") || 0 == strcmp(mode, "pow2tile") || 0 == strcmp(mode, "copyTile")) { useTiles = true; if (0 == strcmp(mode, "pow2tile")) { isPowerOf2Mode = true; } else if (0 == strcmp(mode, "copyTile")) { isCopyMode = true; } else { gridSupported = true; } if (FLAGS_mode.count() < 2) { error.printf("Missing width for --mode %s\n", mode); return NULL; } widthString = FLAGS_mode[1]; if (FLAGS_mode.count() < 3) { error.printf("Missing height for --mode %s\n", mode); return NULL; } heightString = FLAGS_mode[2]; } else if (0 == strcmp(mode, "playbackCreation") && kBench_PictureTool == tool) { renderer.reset(SkNEW(sk_tools::PlaybackCreationRenderer)); gridSupported = true; // undocumented } else if (0 == strcmp(mode, "gatherPixelRefs") && kBench_PictureTool == tool) { renderer.reset(sk_tools::CreateGatherPixelRefsRenderer()); } else if (0 == strcmp(mode, "rerecord") && kRender_PictureTool == tool) { renderer.reset(SkNEW(sk_tools::RecordPictureRenderer)); // Allow 'mode' to be set to 'simple', but do not create a renderer, so we can // ensure that pipe does not override a mode besides simple. The renderer will // be created below. } else if (0 != strcmp(mode, "simple")) { error.printf("%s is not a valid mode for --mode\n", mode); return NULL; } } if (useTiles) { SkASSERT(NULL == renderer); SkAutoTUnref tiledRenderer; if (isCopyMode) { int xTiles = -1; int yTiles = -1; if (FLAGS_tiles.count() > 0) { if (FLAGS_tiles.count() != 2) { error.printf("--tiles requires an x value and a y value.\n"); return NULL; } xTiles = atoi(FLAGS_tiles[0]); yTiles = atoi(FLAGS_tiles[1]); } int x, y; if (xTiles != -1 && yTiles != -1) { x = xTiles; y = yTiles; if (x <= 0 || y <= 0) { error.printf("--tiles must be given values > 0\n"); return NULL; } } else { x = y = 4; } tiledRenderer.reset(SkNEW_ARGS(sk_tools::CopyTilesRenderer, (x, y))); } else if (FLAGS_multi > 1) { tiledRenderer.reset(SkNEW_ARGS(sk_tools::MultiCorePictureRenderer, (FLAGS_multi))); } else { tiledRenderer.reset(SkNEW(sk_tools::TiledPictureRenderer)); } if (isPowerOf2Mode) { int minWidth = atoi(widthString); if (!SkIsPow2(minWidth) || minWidth < 0) { SkString err; error.printf("-mode %s must be given a width" " value that is a power of two\n", mode); return NULL; } tiledRenderer->setTileMinPowerOf2Width(minWidth); } else if (sk_tools::is_percentage(widthString)) { if (isCopyMode) { error.printf("--mode %s does not support percentages.\n", mode); return NULL; } tiledRenderer->setTileWidthPercentage(atof(widthString)); if (!(tiledRenderer->getTileWidthPercentage() > 0)) { error.printf("--mode %s must be given a width percentage > 0\n", mode); return NULL; } } else { tiledRenderer->setTileWidth(atoi(widthString)); if (!(tiledRenderer->getTileWidth() > 0)) { error.printf("--mode %s must be given a width > 0\n", mode); return NULL; } } if (sk_tools::is_percentage(heightString)) { if (isCopyMode) { error.printf("--mode %s does not support percentages.\n", mode); return NULL; } tiledRenderer->setTileHeightPercentage(atof(heightString)); if (!(tiledRenderer->getTileHeightPercentage() > 0)) { error.printf("--mode %s must be given a height percentage > 0\n", mode); return NULL; } } else { tiledRenderer->setTileHeight(atoi(heightString)); if (!(tiledRenderer->getTileHeight() > 0)) { SkString err; error.printf("--mode %s must be given a height > 0\n", mode); return NULL; } } renderer.reset(tiledRenderer.detach()); if (FLAGS_pipe) { error.printf("Pipe rendering is currently not compatible with tiling.\n" "Turning off pipe.\n"); } } else { // useTiles if (FLAGS_multi > 1) { error.printf("Multithreaded drawing requires tiled rendering.\n"); return NULL; } if (FLAGS_pipe) { if (renderer != NULL) { error.printf("Pipe is incompatible with other modes.\n"); return NULL; } renderer.reset(SkNEW(sk_tools::PipePictureRenderer)); } } if (NULL == renderer) { renderer.reset(SkNEW(sk_tools::SimplePictureRenderer)); } if (FLAGS_viewport.count() > 0) { if (FLAGS_viewport.count() != 2) { error.printf("--viewport requires a width and a height.\n"); return NULL; } SkISize viewport; viewport.fWidth = atoi(FLAGS_viewport[0]); viewport.fHeight = atoi(FLAGS_viewport[1]); renderer->setViewport(viewport); } sk_tools::PictureRenderer::SkDeviceTypes deviceType = sk_tools::PictureRenderer::kBitmap_DeviceType; #if SK_SUPPORT_GPU int sampleCount = 0; #endif if (FLAGS_config.count() > 0) { if (0 == strcmp(FLAGS_config[0], "8888")) { deviceType = sk_tools::PictureRenderer::kBitmap_DeviceType; } #if SK_SUPPORT_GPU else if (0 == strcmp(FLAGS_config[0], "gpu")) { deviceType = sk_tools::PictureRenderer::kGPU_DeviceType; if (FLAGS_multi > 1) { error.printf("GPU not compatible with multithreaded tiling.\n"); return NULL; } } else if (0 == strcmp(FLAGS_config[0], "msaa4")) { deviceType = sk_tools::PictureRenderer::kGPU_DeviceType; if (FLAGS_multi > 1) { error.printf("GPU not compatible with multithreaded tiling.\n"); return NULL; } sampleCount = 4; } else if (0 == strcmp(FLAGS_config[0], "msaa16")) { deviceType = sk_tools::PictureRenderer::kGPU_DeviceType; if (FLAGS_multi > 1) { error.printf("GPU not compatible with multithreaded tiling.\n"); return NULL; } sampleCount = 16; } #if SK_ANGLE else if (0 == strcmp(FLAGS_config[0], "angle")) { deviceType = sk_tools::PictureRenderer::kAngle_DeviceType; if (FLAGS_multi > 1) { error.printf("Angle not compatible with multithreaded tiling.\n"); return NULL; } } #endif #endif else { error.printf("%s is not a valid mode for --config\n", FLAGS_config[0]); return NULL; } renderer->setDeviceType(deviceType); #if SK_SUPPORT_GPU renderer->setSampleCount(sampleCount); #endif } sk_tools::PictureRenderer::BBoxHierarchyType bbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType; if (FLAGS_bbh.count() > 0) { const char* type = FLAGS_bbh[0]; if (0 == strcmp(type, "none")) { bbhType = sk_tools::PictureRenderer::kNone_BBoxHierarchyType; } else if (0 == strcmp(type, "rtree")) { bbhType = sk_tools::PictureRenderer::kRTree_BBoxHierarchyType; } else if (0 == strcmp(type, "grid")) { if (!gridSupported) { error.printf("'--bbh grid' is not compatible with --mode=%s.\n", mode); return NULL; } bbhType = sk_tools::PictureRenderer::kTileGrid_BBoxHierarchyType; if (FLAGS_bbh.count() != 3) { error.printf("--bbh grid requires a width and a height.\n"); return NULL; } int gridWidth = atoi(FLAGS_bbh[1]); int gridHeight = atoi(FLAGS_bbh[2]); renderer->setGridSize(gridWidth, gridHeight); } else { error.printf("%s is not a valid value for --bbhType\n", type); return NULL; } if (FLAGS_pipe && sk_tools::PictureRenderer::kNone_BBoxHierarchyType != bbhType) { error.printf("--pipe and --bbh cannot be used together\n"); return NULL; } } renderer->setBBoxHierarchyType(bbhType); renderer->setScaleFactor(SkDoubleToScalar(FLAGS_scale)); return renderer.detach(); } SkLruImageCache gLruImageCache(1024*1024); // Simple cache selector to choose between a purgeable cache for large images and the standard one // for smaller images. class MyCacheSelector : public SkBitmapFactory::CacheSelector { public: MyCacheSelector() { fPurgeableImageCache = SkPurgeableImageCache::Create(); } ~MyCacheSelector() { SkSafeUnref(fPurgeableImageCache); } virtual SkImageCache* selectCache(const SkImage::Info& info) SK_OVERRIDE { if (info.fWidth * info.fHeight > 32 * 1024 && fPurgeableImageCache != NULL) { return fPurgeableImageCache; } return &gLruImageCache; } private: SkImageCache* fPurgeableImageCache; }; static MyCacheSelector gCacheSelector; static SkBitmapFactory gFactory(&SkImageDecoder::DecodeMemoryToTarget); bool lazy_decode_bitmap(const void* buffer, size_t size, SkBitmap* bitmap); bool lazy_decode_bitmap(const void* buffer, size_t size, SkBitmap* bitmap) { void* copiedBuffer = sk_malloc_throw(size); memcpy(copiedBuffer, buffer, size); SkAutoDataUnref data(SkData::NewFromMalloc(copiedBuffer, size)); static bool gOnce; if (!gOnce) { // Only use the cache selector if there is a purgeable image cache to use for large // images. if (FLAGS_useVolatileCache && SkAutoTUnref( SkPurgeableImageCache::Create()).get() != NULL) { gFactory.setCacheSelector(&gCacheSelector); } else { gFactory.setImageCache(&gLruImageCache); } gOnce = true; } return gFactory.installPixelRef(data, bitmap); }