From bcdf2ec50dfd170959cc2db67c49f6dac084be03 Mon Sep 17 00:00:00 2001 From: "scroggo@google.com" Date: Thu, 20 Sep 2012 14:42:33 +0000 Subject: In bench_pictures, use a pool of tiles for multicore drawing. Also includes some code cleanup and code sharing. Allow setting the number of threads on the command line. Rename ThreadSafePipeController::playback to ::draw, to be the same as SkPicture so DrawTileToCanvas can take a template parameter. Disallow multithreading with GPU turned on. Display help information with improper tiled arguments. BUG=https://code.google.com/p/skia/issues/detail?id=871 Review URL: https://codereview.appspot.com/6536050 git-svn-id: http://skia.googlecode.com/svn/trunk@5602 2bbb7eff-a529-9590-31e7-b0007b416f81 --- tools/PictureRenderer.cpp | 250 ++++++++++++++++++++++++++++-------------- tools/PictureRenderer.h | 35 ++++-- tools/bench_pictures_main.cpp | 64 +++++++---- 3 files changed, 234 insertions(+), 115 deletions(-) (limited to 'tools') diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp index cea7b5bff5..96649de3b8 100644 --- a/tools/PictureRenderer.cpp +++ b/tools/PictureRenderer.cpp @@ -157,13 +157,16 @@ void SimplePictureRenderer::render(bool doExtraWorkToDrawToBaseCanvas) { /////////////////////////////////////////////////////////////////////////////////////////////// TiledPictureRenderer::TiledPictureRenderer() - : fMultiThreaded(false) - , fUsePipe(false) + : fUsePipe(false) , fTileWidth(kDefaultTileWidth) , fTileHeight(kDefaultTileHeight) , fTileWidthPercentage(0.0) , fTileHeightPercentage(0.0) - , fTileMinPowerOf2Width(0) { } + , fTileMinPowerOf2Width(0) + , fTileCounter(0) + , fNumThreads(1) + , fPictureClones(NULL) + , fPipeController(NULL) { } void TiledPictureRenderer::init(SkPicture* pict) { SkASSERT(pict != NULL); @@ -188,15 +191,40 @@ void TiledPictureRenderer::init(SkPicture* pict) { } else { this->setupTiles(); } + + if (this->multiThreaded()) { + for (int i = 0; i < fNumThreads; ++i) { + *fCanvasPool.append() = this->setupCanvas(fTileWidth, fTileHeight); + } + if (!fUsePipe) { + SkASSERT(NULL == fPictureClones); + // Only need to create fNumThreads - 1 clones, since one thread will use the base + // picture. + int numberOfClones = fNumThreads - 1; + // This will be deleted in end(). + fPictureClones = SkNEW_ARRAY(SkPicture, numberOfClones); + fPictureClones->clone(fPictureClones, numberOfClones); + } + } } void TiledPictureRenderer::end() { - this->deleteTiles(); + fTileRects.reset(); + SkDELETE_ARRAY(fPictureClones); + fPictureClones = NULL; + fCanvasPool.unrefAll(); + if (fPipeController != NULL) { + SkASSERT(fUsePipe); + SkDELETE(fPipeController); + fPipeController = NULL; + } this->INHERITED::end(); } TiledPictureRenderer::~TiledPictureRenderer() { - this->deleteTiles(); + // end() must be called to delete fPictureClones and fPipeController + SkASSERT(NULL == fPictureClones); + SkASSERT(NULL == fPipeController); } void TiledPictureRenderer::setupTiles() { @@ -252,56 +280,124 @@ void TiledPictureRenderer::setupPowerOf2Tiles() { } } -void TiledPictureRenderer::deleteTiles() { - fTileRects.reset(); +/** + * Draw the specified playback to the canvas translated to rectangle provided, so that this mini + * canvas represents the rectangle's portion of the overall picture. + * Saves and restores so that the initial clip and matrix return to their state before this function + * is called. + */ +template +static void DrawTileToCanvas(SkCanvas* canvas, const SkRect& tileRect, T* playback) { + int saveCount = canvas->save(); + // Translate so that we draw the correct portion of the picture + canvas->translate(-tileRect.fLeft, -tileRect.fTop); + playback->draw(canvas); + canvas->restoreToCount(saveCount); + canvas->flush(); } +/////////////////////////////////////////////////////////////////////////////////////////////// +// Base class for data used both by pipe and clone picture multi threaded drawing. + +struct ThreadData { + ThreadData(SkCanvas* target, int* tileCounter, SkTDArray* tileRects) + : fCanvas(target) + , fTileCounter(tileCounter) + , fTileRects(tileRects) { + SkASSERT(target != NULL && tileCounter != NULL && tileRects != NULL); + } + + const SkRect* nextTile() { + if (int32_t i = sk_atomic_inc(fTileCounter) < fTileRects->count()) { + return &fTileRects->operator[](i); + } + return NULL; + } + + // All of these are pointers to objects owned elsewhere + SkCanvas* fCanvas; +private: + // Shared by all threads, this states which is the next tile to be drawn. + int32_t* fTileCounter; + // Points to the array of rectangles. The array is already created before any threads are + // started and then it is unmodified, so there is no danger of race conditions. + const SkTDArray* fTileRects; +}; + /////////////////////////////////////////////////////////////////////////////////////////////// // Draw using Pipe -struct TileData { - TileData(SkCanvas* canvas, ThreadSafePipeController* controller); - SkCanvas* fCanvas; +struct TileData : public ThreadData { + TileData(ThreadSafePipeController* controller, SkCanvas* canvas, int* tileCounter, + SkTDArray* tileRects) + : INHERITED(canvas, tileCounter, tileRects) + , fController(controller) {} + ThreadSafePipeController* fController; - SkThread fThread; + + typedef ThreadData INHERITED; }; static void DrawTile(void* data) { SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024); TileData* tileData = static_cast(data); - tileData->fController->playback(tileData->fCanvas); - tileData->fCanvas->flush(); -} -TileData::TileData(SkCanvas* canvas, ThreadSafePipeController* controller) -: fCanvas(canvas) -, fController(controller) -, fThread(&DrawTile, static_cast(this)) {} + const SkRect* tileRect; + while ((tileRect = tileData->nextTile()) != NULL) { + DrawTileToCanvas(tileData->fCanvas, *tileRect, tileData->fController); + } + SkDELETE(tileData); +} /////////////////////////////////////////////////////////////////////////////////////////////// // Draw using Picture -struct CloneData { - CloneData(SkCanvas* target, SkPicture* original); - SkCanvas* fCanvas; +struct CloneData : public ThreadData { + CloneData(SkPicture* clone, SkCanvas* target, int* tileCounter, SkTDArray* tileRects) + : INHERITED(target, tileCounter, tileRects) + , fClone(clone) {} + SkPicture* fClone; - SkThread fThread; + + typedef ThreadData INHERITED; }; -static void DrawClonedTile(void* data) { +static void DrawClonedTiles(void* data) { SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024); CloneData* cloneData = static_cast(data); - cloneData->fCanvas->drawPicture(*cloneData->fClone); - cloneData->fCanvas->flush(); -} -CloneData::CloneData(SkCanvas* target, SkPicture* clone) -: fCanvas(target) -, fClone(clone) -, fThread(&DrawClonedTile, static_cast(this)) {} + const SkRect* tileRect; + while ((tileRect = cloneData->nextTile()) != NULL) { + DrawTileToCanvas(cloneData->fCanvas, *tileRect, cloneData->fClone); + } + SkDELETE(cloneData); +} /////////////////////////////////////////////////////////////////////////////////////////////// +void TiledPictureRenderer::setup() { + if (this->multiThreaded()) { + // Reset to zero so we start with the first tile. + fTileCounter = 0; + if (fUsePipe) { + // Record the picture into the pipe controller. It is done here because unlike + // SkPicture, the pipe is modified (bitmaps can be removed) by drawing. + // fPipeController is deleted here after each call to render() except the last one and + // in end() for the last one. + if (fPipeController != NULL) { + SkDELETE(fPipeController); + } + fPipeController = SkNEW_ARGS(ThreadSafePipeController, (fTileRects.count())); + SkGPipeWriter writer; + SkCanvas* pipeCanvas = writer.startRecording(fPipeController, + SkGPipeWriter::kSimultaneousReaders_Flag); + SkASSERT(fPicture != NULL); + fPicture->draw(pipeCanvas); + writer.endRecording(); + } + } +} + void TiledPictureRenderer::render(bool doExtraWorkToDrawToBaseCanvas) { SkASSERT(fPicture != NULL); if (NULL == fPicture) { @@ -310,71 +406,44 @@ void TiledPictureRenderer::render(bool doExtraWorkToDrawToBaseCanvas) { if (doExtraWorkToDrawToBaseCanvas) { if (NULL == fCanvas.get()) { - fCanvas.reset(this->setupCanvas()); + fCanvas.reset(this->INHERITED::setupCanvas()); } } - if (fMultiThreaded) { - // FIXME: Turning off multi threading while we transition to using a pool of tiles -/* - if (fUsePipe) { - // First, draw into a pipe controller - SkGPipeWriter writer; - ThreadSafePipeController controller(fTiles.count()); - SkCanvas* pipeCanvas = writer.startRecording(&controller, - SkGPipeWriter::kSimultaneousReaders_Flag); - pipeCanvas->drawPicture(*(fPicture)); - writer.endRecording(); - - // Create and start the threads. - TileData** tileData = SkNEW_ARRAY(TileData*, fTiles.count()); - SkAutoTDeleteArray deleteTileData(tileData); - for (int i = 0; i < fTiles.count(); i++) { - tileData[i] = SkNEW_ARGS(TileData, (fTiles[i], &controller)); - if (!tileData[i]->fThread.start()) { - SkDebugf("could not start thread %i\n", i); - } - } - for (int i = 0; i < fTiles.count(); i++) { - tileData[i]->fThread.join(); - SkDELETE(tileData[i]); + if (this->multiThreaded()) { + SkASSERT(fCanvasPool.count() == fNumThreads); + SkTDArray threads; + SkThread::entryPointProc proc = fUsePipe ? DrawTile : DrawClonedTiles; + for (int i = 0; i < fNumThreads; ++i) { + // data will be deleted by the entryPointProc. + ThreadData* data; + if (fUsePipe) { + data = SkNEW_ARGS(TileData, + (fPipeController, fCanvasPool[i], &fTileCounter, &fTileRects)); + } else { + SkPicture* pic = (0 == i) ? fPicture : &fPictureClones[i-1]; + data = SkNEW_ARGS(CloneData, (pic, fCanvasPool[i], &fTileCounter, &fTileRects)); } - } else { - SkPicture* clones = SkNEW_ARRAY(SkPicture, fTiles.count()); - SkAutoTDeleteArray autodelete(clones); - fPicture->clone(clones, fTiles.count()); - CloneData** cloneData = SkNEW_ARRAY(CloneData*, fTiles.count()); - SkAutoTDeleteArray deleteCloneData(cloneData); - for (int i = 0; i < fTiles.count(); i++) { - cloneData[i] = SkNEW_ARGS(CloneData, (fTiles[i], &clones[i])); - if (!cloneData[i]->fThread.start()) { - SkDebugf("Could not start picture thread %i\n", i); - } - } - for (int i = 0; i < fTiles.count(); i++) { - cloneData[i]->fThread.join(); - SkDELETE(cloneData[i]); + SkThread* thread = SkNEW_ARGS(SkThread, (proc, data)); + if (!thread->start()) { + SkDebugf("Could not start %s thread %i.\n", (fUsePipe ? "pipe" : "picture"), i); } + *threads.append() = thread; } - */ + SkASSERT(threads.count() == fNumThreads); + for (int i = 0; i < fNumThreads; ++i) { + SkThread* thread = threads[i]; + thread->join(); + SkDELETE(thread); + } + threads.reset(); } else { - // For single thread, we really only need one canvas total + // For single thread, we really only need one canvas total. SkCanvas* canvas = this->setupCanvas(fTileWidth, fTileHeight); SkAutoUnref aur(canvas); - // Clip the tile to an area that is completely in what the SkPicture says is the - // drawn-to area. This is mostly important for tiles on the right and bottom edges - // as they may go over this area and the picture may have some commands that - // draw outside of this area and so should not actually be written. - SkRect clip = SkRect::MakeWH(SkIntToScalar(fPicture->width()), - SkIntToScalar(fPicture->height())); for (int i = 0; i < fTileRects.count(); ++i) { - canvas->resetMatrix(); - canvas->clipRect(clip); - // Translate so that we draw the correct portion of the picture - canvas->translate(-fTileRects[i].fLeft, -fTileRects[i].fTop); - canvas->drawPicture(*(fPicture)); - canvas->flush(); + DrawTileToCanvas(canvas, fTileRects[i], fPicture); if (doExtraWorkToDrawToBaseCanvas) { SkASSERT(fCanvas.get() != NULL); SkBitmap source = canvas->getDevice()->accessBitmap(false); @@ -385,6 +454,19 @@ void TiledPictureRenderer::render(bool doExtraWorkToDrawToBaseCanvas) { } } +SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) { + SkCanvas* canvas = this->INHERITED::setupCanvas(width, height); + SkASSERT(fPicture != NULL); + // Clip the tile to an area that is completely in what the SkPicture says is the + // drawn-to area. This is mostly important for tiles on the right and bottom edges + // as they may go over this area and the picture may have some commands that + // draw outside of this area and so should not actually be written. + SkRect clip = SkRect::MakeWH(SkIntToScalar(fPicture->width()), + SkIntToScalar(fPicture->height())); + canvas->clipRect(clip); + return canvas; +} + /////////////////////////////////////////////////////////////////////////////////////////////// void PlaybackCreationRenderer::setup() { diff --git a/tools/PictureRenderer.h b/tools/PictureRenderer.h index 5ae3f29443..845aa9f73e 100644 --- a/tools/PictureRenderer.h +++ b/tools/PictureRenderer.h @@ -23,6 +23,7 @@ class SkBitmap; class SkCanvas; class SkGLContext; +class ThreadSafePipeController; namespace sk_tools { @@ -98,7 +99,7 @@ public: protected: SkCanvas* setupCanvas(); - SkCanvas* setupCanvas(int width, int height); + virtual SkCanvas* setupCanvas(int width, int height); SkAutoTUnref fCanvas; SkPicture* fPicture; @@ -146,6 +147,7 @@ public: TiledPictureRenderer(); virtual void init(SkPicture* pict) SK_OVERRIDE; + virtual void setup() SK_OVERRIDE; virtual void render(bool doExtraWorkToDrawToBaseCanvas) SK_OVERRIDE; virtual void end() SK_OVERRIDE; @@ -194,8 +196,11 @@ public: return fTileMinPowerOf2Width; } - void setMultiThreaded(bool multi) { - fMultiThreaded = multi; + /** + * Set the number of threads to use for drawing. Non-positive numbers will set it to 1. + */ + void setNumberOfThreads(int num) { + fNumThreads = SkMax32(num, 1); } void setUsePipe(bool usePipe) { @@ -205,19 +210,25 @@ public: ~TiledPictureRenderer(); private: - bool fMultiThreaded; - bool fUsePipe; - int fTileWidth; - int fTileHeight; - double fTileWidthPercentage; - double fTileHeightPercentage; - int fTileMinPowerOf2Width; - + bool fUsePipe; + int fTileWidth; + int fTileHeight; + double fTileWidthPercentage; + double fTileHeightPercentage; + int fTileMinPowerOf2Width; SkTDArray fTileRects; + // These are only used for multithreaded rendering + int32_t fTileCounter; + int fNumThreads; + SkTDArray fCanvasPool; + SkPicture* fPictureClones; + ThreadSafePipeController* fPipeController; + void setupTiles(); void setupPowerOf2Tiles(); - void deleteTiles(); + virtual SkCanvas* setupCanvas(int width, int height) SK_OVERRIDE; + bool multiThreaded() { return fNumThreads > 1; } typedef PictureRenderer INHERITED; }; diff --git a/tools/bench_pictures_main.cpp b/tools/bench_pictures_main.cpp index 626ee52658..6cbb991e73 100644 --- a/tools/bench_pictures_main.cpp +++ b/tools/bench_pictures_main.cpp @@ -26,9 +26,10 @@ static void usage(const char* argv0) { " %s ...\n" " [--logFile filename][--timers [wcgWC]*][--logPerIter 1|0][--min]\n" " [--repeat] \n" -" [--mode pow2tile minWidth height[] (multi) | record | simple\n" -" | tile width[] height[] (multi) | playbackCreation]\n" +" [--mode pow2tile minWidth height[] | record | simple\n" +" | tile width[] height[] | playbackCreation]\n" " [--pipe]\n" +" [--multi numThreads]\n" " [--device bitmap" #if SK_SUPPORT_GPU " | gpu" @@ -46,8 +47,8 @@ static void usage(const char* argv0) { SkDebugf(" --timers [wcgWC]* : " "Display wall, cpu, gpu, truncated wall or truncated cpu time for each picture.\n"); SkDebugf( -" --mode pow2tile minWidht height[] (multi) | record | simple\n" -" | tile width[] height[] (multi) | playbackCreation:\n" +" --mode pow2tile minWidht height[] | record | simple\n" +" | tile width[] height[] | playbackCreation:\n" " Run in the corresponding mode.\n" " Default is simple.\n"); SkDebugf( @@ -59,22 +60,20 @@ static void usage(const char* argv0) { " of these tiles and must be a\n" " power of two. Simple\n" " rendering using these tiles\n" -" is benchmarked.\n" -" Append \"multi\" for multithreaded\n" -" drawing.\n"); +" is benchmarked.\n"); SkDebugf( " record, Benchmark picture to picture recording.\n"); SkDebugf( " simple, Benchmark a simple rendering.\n"); SkDebugf( " tile width[] height[], Benchmark simple rendering using\n" -" tiles with the given dimensions.\n" -" Append \"multi\" for multithreaded\n" -" drawing.\n"); +" tiles with the given dimensions.\n"); SkDebugf( " playbackCreation, Benchmark creation of the SkPicturePlayback.\n"); SkDebugf("\n"); SkDebugf( +" --multi numThreads : Set the number of threads for multi threaded drawing. Must be greater\n" +" than 1. Only works with tiled rendering.\n" " --pipe: Benchmark SkGPipe rendering. Compatible with tiled, multithreaded rendering.\n"); SkDebugf( " --device bitmap" @@ -154,7 +153,7 @@ static void parse_commandline(int argc, char* const argv[], SkTArray* commandLine.append("\n"); bool usePipe = false; - bool multiThreaded = false; + int numThreads = 1; bool useTiles = false; const char* widthString = NULL; const char* heightString = NULL; @@ -194,6 +193,19 @@ static void parse_commandline(int argc, char* const argv[], SkTArray* usage(argv0); exit(-1); } + } else if (0 == strcmp(*argv, "--multi")) { + ++argv; + if (argv >= stop) { + gLogger.logError("Missing arg for --multi\n"); + usage(argv0); + exit(-1); + } + numThreads = atoi(*argv); + if (numThreads < 2) { + gLogger.logError("Number of threads must be at least 2.\n"); + usage(argv0); + exit(-1); + } } else if (0 == strcmp(*argv, "--mode")) { ++argv; @@ -232,13 +244,6 @@ static void parse_commandline(int argc, char* const argv[], SkTArray* exit(-1); } heightString = *argv; - - ++argv; - if (argv < stop && 0 == strcmp(*argv, "multi")) { - multiThreaded = true; - } else { - --argv; - } } else if (0 == strcmp(*argv, "playbackCreation")) { renderer = SkNEW(sk_tools::PlaybackCreationRenderer); } else { @@ -328,6 +333,12 @@ static void parse_commandline(int argc, char* const argv[], SkTArray* } } + if (numThreads > 1 && !useTiles) { + gLogger.logError("Multithreaded drawing requires tiled rendering.\n"); + usage(argv0); + exit(-1); + } + if (useTiles) { SkASSERT(NULL == renderer); sk_tools::TiledPictureRenderer* tiledRenderer = SkNEW(sk_tools::TiledPictureRenderer); @@ -339,6 +350,7 @@ static void parse_commandline(int argc, char* const argv[], SkTArray* err.printf("-mode %s must be given a width" " value that is a power of two\n", mode); gLogger.logError(err); + usage(argv0); exit(-1); } tiledRenderer->setTileMinPowerOf2Width(minWidth); @@ -347,6 +359,7 @@ static void parse_commandline(int argc, char* const argv[], SkTArray* if (!(tiledRenderer->getTileWidthPercentage() > 0)) { tiledRenderer->unref(); gLogger.logError("--mode tile must be given a width percentage > 0\n"); + usage(argv0); exit(-1); } } else { @@ -354,6 +367,7 @@ static void parse_commandline(int argc, char* const argv[], SkTArray* if (!(tiledRenderer->getTileWidth() > 0)) { tiledRenderer->unref(); gLogger.logError("--mode tile must be given a width > 0\n"); + usage(argv0); exit(-1); } } @@ -363,6 +377,7 @@ static void parse_commandline(int argc, char* const argv[], SkTArray* if (!(tiledRenderer->getTileHeightPercentage() > 0)) { tiledRenderer->unref(); gLogger.logError("--mode tile must be given a height percentage > 0\n"); + usage(argv0); exit(-1); } } else { @@ -370,10 +385,21 @@ static void parse_commandline(int argc, char* const argv[], SkTArray* if (!(tiledRenderer->getTileHeight() > 0)) { tiledRenderer->unref(); gLogger.logError("--mode tile must be given a height > 0\n"); + usage(argv0); exit(-1); } } - tiledRenderer->setMultiThreaded(multiThreaded); + if (numThreads > 1) { +#if SK_SUPPORT_GPU + if (sk_tools::PictureRenderer::kGPU_DeviceType == deviceType) { + tiledRenderer->unref(); + gLogger.logError("GPU not compatible with multithreaded tiling.\n"); + usage(argv0); + exit(-1); + } +#endif + tiledRenderer->setNumberOfThreads(numThreads); + } tiledRenderer->setUsePipe(usePipe); renderer = tiledRenderer; } else if (usePipe) { -- cgit v1.2.3