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
diff --git a/src/pipe/utils/SamplePipeControllers.cpp b/src/pipe/utils/SamplePipeControllers.cpp
index 437a372..59f612b 100644
--- a/src/pipe/utils/SamplePipeControllers.cpp
+++ b/src/pipe/utils/SamplePipeControllers.cpp
@@ -103,7 +103,7 @@
     fBytesWritten += bytes;
 }
 
-void ThreadSafePipeController::playback(SkCanvas* target) {
+void ThreadSafePipeController::draw(SkCanvas* target) {
     SkGPipeReader reader(target);
     for (int currentBlock = 0; currentBlock < fBlockList.count(); currentBlock++ ) {
         reader.playback(fBlockList[currentBlock].fBlock, fBlockList[currentBlock].fBytes);
diff --git a/src/pipe/utils/SamplePipeControllers.h b/src/pipe/utils/SamplePipeControllers.h
index 81b9120..5efd6f0 100644
--- a/src/pipe/utils/SamplePipeControllers.h
+++ b/src/pipe/utils/SamplePipeControllers.h
@@ -64,7 +64,7 @@
      * used the flag SkGPipeWriter::kSimultaneousReaders_Flag, this can be called from different
      * threads simultaneously.
      */
-    void playback(SkCanvas*);
+    void draw(SkCanvas*);
 private:
     enum {
         kMinBlockSize = 4096
diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp
index cea7b5b..96649de 100644
--- a/tools/PictureRenderer.cpp
+++ b/tools/PictureRenderer.cpp
@@ -157,13 +157,16 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 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 @@
     } 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::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<class T>
+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<SkRect>* 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<SkRect>* 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<SkRect>* 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<TileData*>(data);
-    tileData->fController->playback(tileData->fCanvas);
-    tileData->fCanvas->flush();
-}
 
-TileData::TileData(SkCanvas* canvas, ThreadSafePipeController* controller)
-: fCanvas(canvas)
-, fController(controller)
-, fThread(&DrawTile, static_cast<void*>(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<SkRect>* 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<CloneData*>(data);
-    cloneData->fCanvas->drawPicture(*cloneData->fClone);
-    cloneData->fCanvas->flush();
+
+    const SkRect* tileRect;
+    while ((tileRect = cloneData->nextTile()) != NULL) {
+        DrawTileToCanvas(cloneData->fCanvas, *tileRect, cloneData->fClone);
+    }
+    SkDELETE(cloneData);
 }
 
-CloneData::CloneData(SkCanvas* target, SkPicture* clone)
-: fCanvas(target)
-, fClone(clone)
-, fThread(&DrawClonedTile, static_cast<void*>(this)) {}
-
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
+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 @@
 
     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<TileData*> 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);
-                }
+    if (this->multiThreaded()) {
+        SkASSERT(fCanvasPool.count() == fNumThreads);
+        SkTDArray<SkThread*> 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));
             }
-            for (int i = 0; i < fTiles.count(); i++) {
-                tileData[i]->fThread.join();
-                SkDELETE(tileData[i]);
+            SkThread* thread = SkNEW_ARGS(SkThread, (proc, data));
+            if (!thread->start()) {
+                SkDebugf("Could not start %s thread %i.\n", (fUsePipe ? "pipe" : "picture"), i);
             }
-        } else {
-            SkPicture* clones = SkNEW_ARRAY(SkPicture, fTiles.count());
-            SkAutoTDeleteArray<SkPicture> autodelete(clones);
-            fPicture->clone(clones, fTiles.count());
-            CloneData** cloneData = SkNEW_ARRAY(CloneData*, fTiles.count());
-            SkAutoTDeleteArray<CloneData*> 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]);
-            }
+            *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 @@
     }
 }
 
+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 5ae3f29..845aa9f 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 @@
 
 protected:
     SkCanvas* setupCanvas();
-    SkCanvas* setupCanvas(int width, int height);
+    virtual SkCanvas* setupCanvas(int width, int height);
 
     SkAutoTUnref<SkCanvas> fCanvas;
     SkPicture* fPicture;
@@ -146,6 +147,7 @@
     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 @@
         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 @@
     ~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<SkRect> fTileRects;
 
+    // These are only used for multithreaded rendering
+    int32_t                   fTileCounter;
+    int                       fNumThreads;
+    SkTDArray<SkCanvas*>      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 626ee52..6cbb991 100644
--- a/tools/bench_pictures_main.cpp
+++ b/tools/bench_pictures_main.cpp
@@ -26,9 +26,10 @@
 "     %s <inputDir>...\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 @@
     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 @@
 "                                                 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 @@
     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 @@
                 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 @@
                     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 @@
         }
     }
 
+    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 @@
                 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 @@
             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 @@
             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 @@
             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 @@
             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) {