In bench_pictures --multi, maintain thread local caches.

Builds on https://codereview.appspot.com/6718046/ by mtklein.

Previously, each iteration of drawing a picture started new threads to draw the picture. Since each thread is using thread local storage for the font cache, this means that each iteration had to start with an empty font cache.

The newly added MultiCorePictureRenderer, separated from TiledPictureRenderer, now starts the drawing threads at the beginning of the test using an SkThreadPool, and keeps them alive through all iterations, so the font cache can be reused.

For now, I have removed the pipe version of the threaded renderer.

Updated bench_pictures_main and render_pictures_main to use the new
renderer, and to unref a renderer before early exit.

Review URL: https://codereview.appspot.com/6777063

git-svn-id: http://skia.googlecode.com/svn/trunk@6285 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp
index 064557b..745bec0 100644
--- a/tools/PictureRenderer.cpp
+++ b/tools/PictureRenderer.cpp
@@ -220,16 +220,11 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 TiledPictureRenderer::TiledPictureRenderer()
-    : fUsePipe(false)
-    , fTileWidth(kDefaultTileWidth)
+    : fTileWidth(kDefaultTileWidth)
     , fTileHeight(kDefaultTileHeight)
     , fTileWidthPercentage(0.0)
     , fTileHeightPercentage(0.0)
-    , fTileMinPowerOf2Width(0)
-    , fTileCounter(0)
-    , fNumThreads(1)
-    , fPictureClones(NULL)
-    , fPipeController(NULL) { }
+    , fTileMinPowerOf2Width(0) { }
 
 void TiledPictureRenderer::init(SkPicture* pict) {
     SkASSERT(pict != NULL);
@@ -242,9 +237,7 @@
     // used by bench_pictures.
     fPicture = pict;
     fPicture->ref();
-    if (!fUsePipe) {
-        this->buildBBoxHierarchy();
-    }
+    this->buildBBoxHierarchy();
 
     if (fTileWidthPercentage > 0) {
         fTileWidth = sk_float_ceil2int(float(fTileWidthPercentage * fPicture->width() / 100));
@@ -258,42 +251,13 @@
     } 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);
-            fPicture->clone(fPictureClones, numberOfClones);
-        }
-    }
 }
 
 void TiledPictureRenderer::end() {
     fTileRects.reset();
-    SkDELETE_ARRAY(fPictureClones);
-    fPictureClones = NULL;
-    fCanvasPool.unrefAll();
-    if (fPipeController != NULL) {
-        SkASSERT(fUsePipe);
-        SkDELETE(fPipeController);
-        fPipeController = NULL;
-    }
     this->INHERITED::end();
 }
 
-TiledPictureRenderer::~TiledPictureRenderer() {
-    // end() must be called to delete fPictureClones and fPipeController
-    SkASSERT(NULL == fPictureClones);
-    SkASSERT(NULL == fPipeController);
-}
-
 void TiledPictureRenderer::setupTiles() {
     for (int tile_y_start = 0; tile_y_start < fPicture->height(); tile_y_start += fTileHeight) {
         for (int tile_x_start = 0; tile_x_start < fPicture->width(); tile_x_start += fTileWidth) {
@@ -364,129 +328,6 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
-// Base class for data used both by pipe and clone picture multi threaded drawing.
-
-struct ThreadData {
-    ThreadData(SkCanvas* target, int* tileCounter, SkTDArray<SkRect>* tileRects,
-               const SkString* path, bool* success)
-    : fCanvas(target)
-    , fPath(path)
-    , fSuccess(success)
-    , fTileCounter(tileCounter)
-    , fTileRects(tileRects) {
-        SkASSERT(target != NULL && tileCounter != NULL && tileRects != NULL);
-        // Success must start off true, and it will be set to false upon failure.
-        SkASSERT(success != NULL && *success);
-    }
-
-    int32_t nextTile(SkRect* rect) {
-        int32_t i = sk_atomic_inc(fTileCounter);
-        if (i < fTileRects->count()) {
-            SkASSERT(rect != NULL);
-            *rect = fTileRects->operator[](i);
-            return i;
-        }
-        return -1;
-    }
-
-    // All of these are pointers to objects owned elsewhere
-    SkCanvas*                fCanvas;
-    const SkString*          fPath;
-    bool*                    fSuccess;
-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<SkRect>* fTileRects;
-};
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-// Draw using Pipe
-
-struct TileData : public ThreadData {
-    TileData(ThreadSafePipeController* controller, SkCanvas* canvas, int* tileCounter,
-             SkTDArray<SkRect>* tileRects, const SkString* path, bool* success)
-    : INHERITED(canvas, tileCounter, tileRects, path, success)
-    , fController(controller) {}
-
-    ThreadSafePipeController* fController;
-
-    typedef ThreadData INHERITED;
-};
-
-static void DrawTile(void* data) {
-    SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
-    TileData* tileData = static_cast<TileData*>(data);
-
-    SkRect tileRect;
-    int32_t i;
-    while ((i = tileData->nextTile(&tileRect)) != -1) {
-        DrawTileToCanvas(tileData->fCanvas, tileRect, tileData->fController);
-        if (NULL != tileData->fPath &&
-            !writeAppendNumber(tileData->fCanvas, tileData->fPath, i)) {
-            *tileData->fSuccess = false;
-            break;
-        }
-    }
-    SkDELETE(tileData);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-// Draw using Picture
-
-struct CloneData : public ThreadData {
-    CloneData(SkPicture* clone, SkCanvas* target, int* tileCounter, SkTDArray<SkRect>* tileRects,
-              const SkString* path, bool* success)
-    : INHERITED(target, tileCounter, tileRects, path, success)
-    , fClone(clone) {}
-
-    SkPicture* fClone;
-
-    typedef ThreadData INHERITED;
-};
-
-static void DrawClonedTiles(void* data) {
-    SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
-    CloneData* cloneData = static_cast<CloneData*>(data);
-
-    SkRect tileRect;
-    int32_t i;
-    while ((i = cloneData->nextTile(&tileRect)) != -1) {
-        DrawTileToCanvas(cloneData->fCanvas, tileRect, cloneData->fClone);
-        if (NULL != cloneData->fPath &&
-            !writeAppendNumber(cloneData->fCanvas, cloneData->fPath, i)) {
-            *cloneData->fSuccess = false;
-            break;
-        }
-    }
-    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();
-        }
-    }
-}
 
 bool TiledPictureRenderer::render(const SkString* path) {
     SkASSERT(fPicture != NULL);
@@ -494,50 +335,18 @@
         return false;
     }
 
-    if (this->multiThreaded()) {
-        SkASSERT(fCanvasPool.count() == fNumThreads);
-        SkTDArray<SkThread*> threads;
-        SkThread::entryPointProc proc = fUsePipe ? DrawTile : DrawClonedTiles;
-        bool success = true;
-        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, path, &success));
-            } else {
-                SkPicture* pic = (0 == i) ? fPicture : &fPictureClones[i-1];
-                data = SkNEW_ARGS(CloneData, (pic, fCanvasPool[i], &fTileCounter, &fTileRects, path,
-                                              &success));
-            }
-            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();
-        return success;
-    } else {
-        // For single thread, we really only need one canvas total.
-        SkCanvas* canvas = this->setupCanvas(fTileWidth, fTileHeight);
-        SkAutoUnref aur(canvas);
+    // Reuse one canvas for all tiles.
+    SkCanvas* canvas = this->setupCanvas(fTileWidth, fTileHeight);
+    SkAutoUnref aur(canvas);
 
-        bool success = true;
-        for (int i = 0; i < fTileRects.count(); ++i) {
-            DrawTileToCanvas(canvas, fTileRects[i], fPicture);
-            if (NULL != path) {
-                success &= writeAppendNumber(canvas, path, i);
-            }
+    bool success = true;
+    for (int i = 0; i < fTileRects.count(); ++i) {
+        DrawTileToCanvas(canvas, fTileRects[i], fPicture);
+        if (NULL != path) {
+            success &= writeAppendNumber(canvas, path, i);
         }
-        return success;
     }
+    return success;
 }
 
 SkCanvas* TiledPictureRenderer::setupCanvas(int width, int height) {
@@ -552,6 +361,128 @@
     canvas->clipRect(clip);
     return canvas;
 }
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+// Holds all of the information needed to draw a set of tiles.
+class CloneData : public SkRunnable {
+
+public:
+    CloneData(SkPicture* clone, SkCanvas* canvas, SkTDArray<SkRect>& rects, int start, int end,
+              SkRunnable* done)
+        : fClone(clone)
+        , fCanvas(canvas)
+        , fPath(NULL)
+        , fRects(rects)
+        , fStart(start)
+        , fEnd(end)
+        , fSuccess(NULL)
+        , fDone(done) {
+        SkASSERT(fDone != NULL);
+    }
+
+    virtual void run() SK_OVERRIDE {
+        SkGraphics::SetTLSFontCacheLimit(1024 * 1024);
+        for (int i = fStart; i < fEnd; i++) {
+            DrawTileToCanvas(fCanvas, fRects[i], fClone);
+            if (fPath != NULL && !writeAppendNumber(fCanvas, fPath, i)
+                && fSuccess != NULL) {
+                *fSuccess = false;
+                // If one tile fails to write to a file, do not continue drawing the rest.
+                break;
+            }
+        }
+        fDone->run();
+    }
+
+    void setPathAndSuccess(const SkString* path, bool* success) {
+        fPath = path;
+        fSuccess = success;
+    }
+
+private:
+    // All pointers unowned.
+    SkPicture*         fClone;      // Picture to draw from. Each CloneData has a unique one which
+                                    // is threadsafe.
+    SkCanvas*          fCanvas;     // Canvas to draw to. Reused for each tile.
+    const SkString*    fPath;       // If non-null, path to write the result to as a PNG.
+    SkTDArray<SkRect>& fRects;      // All tiles of the picture.
+    const int          fStart;      // Range of tiles drawn by this thread.
+    const int          fEnd;
+    bool*              fSuccess;    // Only meaningful if path is non-null. Shared by all threads,
+                                    // and only set to false upon failure to write to a PNG.
+    SkRunnable*        fDone;
+};
+
+MultiCorePictureRenderer::MultiCorePictureRenderer(int threadCount)
+: fNumThreads(threadCount)
+, fThreadPool(threadCount)
+, fCountdown(threadCount) {
+    // Only need to create fNumThreads - 1 clones, since one thread will use the base
+    // picture.
+    fPictureClones = SkNEW_ARRAY(SkPicture, fNumThreads - 1);
+    fCloneData = SkNEW_ARRAY(CloneData*, fNumThreads);
+}
+
+void MultiCorePictureRenderer::init(SkPicture *pict) {
+    // Set fPicture and the tiles.
+    this->INHERITED::init(pict);
+    for (int i = 0; i < fNumThreads; ++i) {
+        *fCanvasPool.append() = this->setupCanvas(this->getTileWidth(), this->getTileHeight());
+    }
+    // Only need to create fNumThreads - 1 clones, since one thread will use the base picture.
+    fPicture->clone(fPictureClones, fNumThreads - 1);
+    // Populate each thread with the appropriate data.
+    // Group the tiles into nearly equal size chunks, rounding up so we're sure to cover them all.
+    const int chunkSize = (fTileRects.count() + fNumThreads - 1) / fNumThreads;
+
+    for (int i = 0; i < fNumThreads; i++) {
+        SkPicture* pic;
+        if (i == fNumThreads-1) {
+            // The last set will use the original SkPicture.
+            pic = fPicture;
+        } else {
+            pic = &fPictureClones[i];
+        }
+        const int start = i * chunkSize;
+        const int end = SkMin32(start + chunkSize, fTileRects.count());
+        fCloneData[i] = SkNEW_ARGS(CloneData,
+                                   (pic, fCanvasPool[i], fTileRects, start, end, &fCountdown));
+    }
+}
+
+bool MultiCorePictureRenderer::render(const SkString *path) {
+    bool success = true;
+    if (path != NULL) {
+        for (int i = 0; i < fNumThreads-1; i++) {
+            fCloneData[i]->setPathAndSuccess(path, &success);
+        }
+    }
+
+    fCountdown.reset(fNumThreads);
+    for (int i = 0; i < fNumThreads; i++) {
+        fThreadPool.add(fCloneData[i]);
+    }
+    fCountdown.wait();
+
+    return success;
+}
+
+void MultiCorePictureRenderer::end() {
+    for (int i = 0; i < fNumThreads - 1; i++) {
+        SkDELETE(fCloneData[i]);
+        fCloneData[i] = NULL;
+    }
+
+    fCanvasPool.unrefAll();
+
+    this->INHERITED::end();
+}
+
+MultiCorePictureRenderer::~MultiCorePictureRenderer() {
+    // Each individual CloneData was deleted in end.
+    SkDELETE_ARRAY(fCloneData);
+    SkDELETE_ARRAY(fPictureClones);
+}
 
 ///////////////////////////////////////////////////////////////////////////////////////////////