Split SkPictureReplacementPlayback out of SkPicturePlayback

R=mtklein@google.com, reed@google.com

Author: robertphillips@google.com

Review URL: https://codereview.chromium.org/383733002
diff --git a/gyp/core.gypi b/gyp/core.gypi
index b1c8cc9..f7853f9 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -140,6 +140,8 @@
         '<(skia_src_path)/core/SkPictureRecord.cpp',
         '<(skia_src_path)/core/SkPictureRecord.h',
         '<(skia_src_path)/core/SkPictureRecorder.cpp',
+        '<(skia_src_path)/core/SkPictureReplacementPlayback.cpp',
+        '<(skia_src_path)/core/SkPictureReplacementPlayback.h',
         '<(skia_src_path)/core/SkPictureShader.cpp',
         '<(skia_src_path)/core/SkPictureShader.h',
         '<(skia_src_path)/core/SkPictureStateTree.cpp',
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index a346606..4652136 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -288,6 +288,7 @@
     friend class GrGatherDevice;
     friend class SkDebugCanvas;
     friend class SkPicturePlayback; // to get fData
+    friend class SkPictureReplacementPlayback; // to access OperationList
 
     typedef SkRefCnt INHERITED;
 
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index eee63d2..0c5c74a 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -14,48 +14,6 @@
 #include "SkTDArray.h"
 #include "SkTypes.h"
 
-SkPicturePlayback::PlaybackReplacements::ReplacementInfo*
-SkPicturePlayback::PlaybackReplacements::push() {
-    SkDEBUGCODE(this->validate());
-    return fReplacements.push();
-}
-
-void SkPicturePlayback::PlaybackReplacements::freeAll() {
-    for (int i = 0; i < fReplacements.count(); ++i) {
-        SkDELETE(fReplacements[i].fBM);
-    }
-    fReplacements.reset();
-}
-
-#ifdef SK_DEBUG
-void SkPicturePlayback::PlaybackReplacements::validate() const {
-    // Check that the ranges are monotonically increasing and non-overlapping
-    if (fReplacements.count() > 0) {
-        SkASSERT(fReplacements[0].fStart < fReplacements[0].fStop);
-
-        for (int i = 1; i < fReplacements.count(); ++i) {
-            SkASSERT(fReplacements[i].fStart < fReplacements[i].fStop);
-            SkASSERT(fReplacements[i - 1].fStop < fReplacements[i].fStart);
-        }
-    }
-}
-#endif
-
-// TODO: Replace with hash or pass in "lastLookedUp" hint
-SkPicturePlayback::PlaybackReplacements::ReplacementInfo*
-SkPicturePlayback::PlaybackReplacements::lookupByStart(size_t start) {
-    SkDEBUGCODE(this->validate());
-    for (int i = 0; i < fReplacements.count(); ++i) {
-        if (start == fReplacements[i].fStart) {
-            return &fReplacements[i];
-        } else if (start < fReplacements[i].fStart) {
-            return NULL;  // the ranges are monotonically increasing and non-overlapping
-        }
-    }
-
-    return NULL;
-}
-
 /*
  * Read the next op code and chunk size from 'reader'. The returned size
  * is the entire size of the chunk (including the opcode). Thus, the
@@ -137,79 +95,6 @@
     return true;
 }
 
-bool SkPicturePlayback::replaceOps(SkPictureStateTree::Iterator* iter, 
-                                   SkReader32* reader, 
-                                   SkCanvas* canvas,
-                                   const SkMatrix& initialMatrix) {
-    if (NULL != fReplacements) {
-        // Potentially replace a block of operations with a single drawBitmap call
-        SkPicturePlayback::PlaybackReplacements::ReplacementInfo* temp =
-                                            fReplacements->lookupByStart(reader->offset());
-        if (NULL != temp) {
-            SkASSERT(NULL != temp->fBM);
-            SkASSERT(NULL != temp->fPaint);
-            canvas->save();
-            canvas->setMatrix(initialMatrix);
-            SkRect src = SkRect::Make(temp->fSrcRect);
-            SkRect dst = SkRect::MakeXYWH(temp->fPos.fX, temp->fPos.fY,
-                                          temp->fSrcRect.width(),
-                                          temp->fSrcRect.height());
-            canvas->drawBitmapRectToRect(*temp->fBM, &src, dst, temp->fPaint);
-            canvas->restore();
-
-            if (iter->isValid()) {
-                // This save is needed since the BBH will automatically issue
-                // a restore to balanced the saveLayer we're skipping
-                canvas->save();
-
-                // At this point we know that the PictureStateTree was aiming
-                // for some draw op within temp's saveLayer (although potentially
-                // in a separate saveLayer nested inside it).
-                // We need to skip all the operations inside temp's range
-                // along with all the associated state changes but update
-                // the state tree to the first operation outside temp's range.
-
-                uint32_t skipTo;
-                do {
-                    skipTo = iter->nextDraw();
-                    if (SkPictureStateTree::Iterator::kDrawComplete == skipTo) {
-                        break;
-                    }
-
-                    if (skipTo <= temp->fStop) {
-                        reader->setOffset(skipTo);
-                        uint32_t size;
-                        DrawType op = ReadOpAndSize(reader, &size);
-                        // Since we are relying on the normal SkPictureStateTree
-                        // playback we need to convert any nested saveLayer calls
-                        // it may issue into saves (so that all its internal
-                        // restores will be balanced).
-                        if (SAVE_LAYER == op) {
-                            canvas->save();
-                        }
-                    }
-                } while (skipTo <= temp->fStop);
-
-                if (SkPictureStateTree::Iterator::kDrawComplete == skipTo) {
-                    reader->setOffset(reader->size());      // skip to end
-                    return true;
-                }
-
-                reader->setOffset(skipTo);
-            } else {
-                reader->setOffset(temp->fStop);
-                uint32_t size;
-                SkDEBUGCODE(DrawType op = ) ReadOpAndSize(reader, &size);
-                SkASSERT(RESTORE == op);
-            }
-
-            return true;
-        }
-    }
-
-    return false;
-}
-
 // If 'iter' is valid use it to skip forward through the picture.
 void SkPicturePlayback::StepIterator(SkPictureStateTree::Iterator* iter, SkReader32* reader) {
     if (iter->isValid()) {
@@ -270,10 +155,6 @@
             return;
         }
 
-        if (this->replaceOps(&it, &reader, canvas, initialMatrix)) {
-            continue;
-        }
-
         fCurOffset = reader.offset();
         uint32_t size;
         DrawType op = ReadOpAndSize(&reader, &size);
diff --git a/src/core/SkPicturePlayback.h b/src/core/SkPicturePlayback.h
index 6c32b15..6114fee 100644
--- a/src/core/SkPicturePlayback.h
+++ b/src/core/SkPicturePlayback.h
@@ -17,71 +17,29 @@
 class SkPaint;
 class SkPictureData;
 
+// The basic picture playback class replays the provided picture into a canvas.
+// If the picture was generated with a BBH it is used to accelerate drawing
+// unless disabled via setUseBBH.
 class SkPicturePlayback : SkNoncopyable {
 public:
     SkPicturePlayback(const SkPicture* picture)
         : fPictureData(picture->fData.get())
         , fCurOffset(0)
-        , fUseBBH(true)
-        , fReplacements(NULL) {
+        , fUseBBH(true) {
     }
     virtual ~SkPicturePlayback() { }
 
     virtual void draw(SkCanvas* canvas, SkDrawPictureCallback*);
 
+    // TODO: remove the curOp calls after cleaning up GrGatherDevice
     // Return the ID of the operation currently being executed when playing
     // back. 0 indicates no call is active.
     size_t curOpID() const { return fCurOffset; }
     void resetOpID() { fCurOffset = 0; }
 
+    // TODO: remove setUseBBH after cleaning up GrGatherCanvas
     void setUseBBH(bool useBBH) { fUseBBH = useBBH; }
 
-    // PlaybackReplacements collects op ranges that can be replaced with
-    // a single drawBitmap call (using a precomputed bitmap).
-    class PlaybackReplacements {
-    public:
-        // All the operations between fStart and fStop (inclusive) will be replaced with
-        // a single drawBitmap call using fPos, fBM and fPaint.
-        // fPaint will be NULL if the picture's paint wasn't copyable
-        struct ReplacementInfo {
-            size_t          fStart;
-            size_t          fStop;
-            SkIPoint        fPos;
-            SkBitmap*       fBM;     // fBM is allocated so ReplacementInfo can remain POD
-            const SkPaint*  fPaint;  // Note: this object doesn't own the paint
-
-            SkIRect         fSrcRect;
-        };
-
-        ~PlaybackReplacements() { this->freeAll(); }
-
-        // Add a new replacement range. The replacement ranges should be
-        // sorted in increasing order and non-overlapping (esp. no nested
-        // saveLayers).
-        ReplacementInfo* push();
-
-    private:
-        friend class SkPicturePlayback; // for access to lookupByStart
-
-        // look up a replacement range by its start offset
-        ReplacementInfo* lookupByStart(size_t start);
-
-        void freeAll();
-
-#ifdef SK_DEBUG
-        void validate() const;
-#endif
-
-        SkTDArray<ReplacementInfo> fReplacements;
-    };
-
-    // Replace all the draw ops in the replacement ranges in 'replacements' with
-    // the associated drawBitmap call
-    // Draw replacing cannot be enabled at the same time as draw limiting
-    void setReplacements(PlaybackReplacements* replacements) {
-        fReplacements = replacements;
-    }
-
 protected:
     const SkPictureData* fPictureData;
 
@@ -89,7 +47,6 @@
     size_t fCurOffset;
 
     bool   fUseBBH;
-    PlaybackReplacements* fReplacements;
 
     void handleOp(SkReader32* reader, 
                   DrawType op, 
@@ -104,10 +61,6 @@
     static void StepIterator(SkPictureStateTree::Iterator* iter, SkReader32* reader);
     static void SkipIterTo(SkPictureStateTree::Iterator* iter, 
                            SkReader32* reader, uint32_t skipTo);
-    bool replaceOps(SkPictureStateTree::Iterator* iter, 
-                    SkReader32* reader, 
-                    SkCanvas* canvas,
-                    const SkMatrix& initialMatrix);
 
     static DrawType ReadOpAndSize(SkReader32* reader, uint32_t* size);
 
diff --git a/src/core/SkPictureRangePlayback.h b/src/core/SkPictureRangePlayback.h
index 61d10f5..ee114bd 100644
--- a/src/core/SkPictureRangePlayback.h
+++ b/src/core/SkPictureRangePlayback.h
@@ -1,3 +1,4 @@
+
 /*
  * Copyright 2014 Google Inc.
  *
diff --git a/src/core/SkPictureReplacementPlayback.cpp b/src/core/SkPictureReplacementPlayback.cpp
new file mode 100644
index 0000000..1249806
--- /dev/null
+++ b/src/core/SkPictureReplacementPlayback.cpp
@@ -0,0 +1,171 @@
+
+/*
+ * Copyright 2014 Google Inc.
+ * 
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvas.h"
+#include "SkPicture.h"
+#include "SkPictureData.h"
+#include "SkPictureReplacementPlayback.h"
+
+
+SkPictureReplacementPlayback::PlaybackReplacements::ReplacementInfo*
+SkPictureReplacementPlayback::PlaybackReplacements::push() {
+    SkDEBUGCODE(this->validate());
+    return fReplacements.push();
+}
+
+void SkPictureReplacementPlayback::PlaybackReplacements::freeAll() {
+    for (int i = 0; i < fReplacements.count(); ++i) {
+        SkDELETE(fReplacements[i].fBM);
+    }
+    fReplacements.reset();
+}
+
+#ifdef SK_DEBUG
+void SkPictureReplacementPlayback::PlaybackReplacements::validate() const {
+    // Check that the ranges are monotonically increasing and non-overlapping
+    if (fReplacements.count() > 0) {
+        SkASSERT(fReplacements[0].fStart < fReplacements[0].fStop);
+
+        for (int i = 1; i < fReplacements.count(); ++i) {
+            SkASSERT(fReplacements[i].fStart < fReplacements[i].fStop);
+            SkASSERT(fReplacements[i - 1].fStop < fReplacements[i].fStart);
+        }
+    }
+}
+#endif
+
+// TODO: Replace with hash or pass in "lastLookedUp" hint
+SkPictureReplacementPlayback::PlaybackReplacements::ReplacementInfo*
+SkPictureReplacementPlayback::PlaybackReplacements::lookupByStart(size_t start) {
+    SkDEBUGCODE(this->validate());
+    for (int i = 0; i < fReplacements.count(); ++i) {
+        if (start == fReplacements[i].fStart) {
+            return &fReplacements[i];
+        } else if (start < fReplacements[i].fStart) {
+            return NULL;  // the ranges are monotonically increasing and non-overlapping
+        }
+    }
+
+    return NULL;
+}
+
+bool SkPictureReplacementPlayback::replaceOps(SkPictureStateTree::Iterator* iter,
+                                              SkReader32* reader,
+                                              SkCanvas* canvas,
+                                              const SkMatrix& initialMatrix) {
+    if (NULL != fReplacements) {
+        // Potentially replace a block of operations with a single drawBitmap call
+        PlaybackReplacements::ReplacementInfo* temp =
+                                  fReplacements->lookupByStart(reader->offset());
+        if (NULL != temp) {
+            SkASSERT(NULL != temp->fBM);
+            SkASSERT(NULL != temp->fPaint);
+            canvas->save();
+            canvas->setMatrix(initialMatrix);
+            SkRect src = SkRect::Make(temp->fSrcRect);
+            SkRect dst = SkRect::MakeXYWH(temp->fPos.fX, temp->fPos.fY,
+                                          temp->fSrcRect.width(),
+                                          temp->fSrcRect.height());
+            canvas->drawBitmapRectToRect(*temp->fBM, &src, dst, temp->fPaint);
+            canvas->restore();
+
+            if (iter->isValid()) {
+                // This save is needed since the BBH will automatically issue
+                // a restore to balanced the saveLayer we're skipping
+                canvas->save();
+
+                // At this point we know that the PictureStateTree was aiming
+                // for some draw op within temp's saveLayer (although potentially
+                // in a separate saveLayer nested inside it).
+                // We need to skip all the operations inside temp's range
+                // along with all the associated state changes but update
+                // the state tree to the first operation outside temp's range.
+
+                uint32_t skipTo;
+                do {
+                    skipTo = iter->nextDraw();
+                    if (SkPictureStateTree::Iterator::kDrawComplete == skipTo) {
+                        break;
+                    }
+
+                    if (skipTo <= temp->fStop) {
+                        reader->setOffset(skipTo);
+                        uint32_t size;
+                        DrawType op = ReadOpAndSize(reader, &size);
+                        // Since we are relying on the normal SkPictureStateTree
+                        // playback we need to convert any nested saveLayer calls
+                        // it may issue into saves (so that all its internal
+                        // restores will be balanced).
+                        if (SAVE_LAYER == op) {
+                            canvas->save();
+                        }
+                    }
+                } while (skipTo <= temp->fStop);
+
+                if (SkPictureStateTree::Iterator::kDrawComplete == skipTo) {
+                    reader->setOffset(reader->size());      // skip to end
+                    return true;
+                }
+
+                reader->setOffset(skipTo);
+            } else {
+                reader->setOffset(temp->fStop);
+                uint32_t size;
+                SkDEBUGCODE(DrawType op = ) ReadOpAndSize(reader, &size);
+                SkASSERT(RESTORE == op);
+            }
+
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void SkPictureReplacementPlayback::draw(SkCanvas* canvas, SkDrawPictureCallback* callback) {
+    AutoResetOpID aroi(this);
+    SkASSERT(0 == fCurOffset);
+
+    SkPictureStateTree::Iterator it;
+
+    if (!this->initIterator(&it, canvas, fActiveOpsList)) {
+        return;  // nothing to draw
+    }
+
+    SkReader32 reader(fPictureData->opData()->bytes(), fPictureData->opData()->size());
+
+    StepIterator(&it, &reader);
+
+    // Record this, so we can concat w/ it if we encounter a setMatrix()
+    SkMatrix initialMatrix = canvas->getTotalMatrix();
+
+    SkAutoCanvasRestore acr(canvas, false);
+
+    while (!reader.eof()) {
+        if (NULL != callback && callback->abortDrawing()) {
+            return;
+        }
+
+        if (this->replaceOps(&it, &reader, canvas, initialMatrix)) {
+            continue;
+        }
+
+        fCurOffset = reader.offset();
+        uint32_t size;
+        DrawType op = ReadOpAndSize(&reader, &size);
+        if (NOOP == op) {
+            // NOOPs are to be ignored - do not propagate them any further
+            SkipIterTo(&it, &reader, fCurOffset + size);
+            continue;
+        }
+
+        this->handleOp(&reader, op, size, canvas, initialMatrix);
+
+        StepIterator(&it, &reader);
+    }
+}
diff --git a/src/core/SkPictureReplacementPlayback.h b/src/core/SkPictureReplacementPlayback.h
new file mode 100644
index 0000000..166a3bf
--- /dev/null
+++ b/src/core/SkPictureReplacementPlayback.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPictureReplacementPlayback_DEFINED
+#define SkPictureReplacementPlayback_DEFINED
+
+#include "SkPicturePlayback.h"
+
+// This playback class replaces complete "saveLayer ... restore" runs with a
+// single drawBitmap call.
+class SkPictureReplacementPlayback : public SkPicturePlayback {
+public:
+    // PlaybackReplacements collects op ranges that can be replaced with
+    // a single drawBitmap call (using a precomputed bitmap).
+    class PlaybackReplacements {
+    public:
+        // All the operations between fStart and fStop (inclusive) will be replaced with
+        // a single drawBitmap call using fPos, fBM and fPaint.
+        // fPaint will be NULL if the picture's paint wasn't copyable
+        struct ReplacementInfo {
+            size_t          fStart;
+            size_t          fStop;
+            SkIPoint        fPos;
+            SkBitmap*       fBM;     // fBM is allocated so ReplacementInfo can remain POD
+            const SkPaint*  fPaint;  // Note: this object doesn't own the paint
+
+            SkIRect         fSrcRect;
+        };
+
+        ~PlaybackReplacements() { this->freeAll(); }
+
+        // Add a new replacement range. The replacement ranges should be
+        // sorted in increasing order and non-overlapping (esp. no nested
+        // saveLayers).
+        ReplacementInfo* push();
+
+        // look up a replacement range by its start offset
+        ReplacementInfo* lookupByStart(size_t start);
+
+    private:
+        SkTDArray<ReplacementInfo> fReplacements;
+
+        void freeAll();
+
+#ifdef SK_DEBUG
+        void validate() const;
+#endif
+    };
+
+    // This class doesn't take ownership of either 'replacements' or 'activeOpsList'
+    // The caller must guarantee they exist across any calls to 'draw'.
+    // 'activeOpsList' can be NULL but in that case BBH acceleration will not 
+    // be used ('replacements' can be NULL too but that defeats the purpose
+    // of using this class).
+    SkPictureReplacementPlayback(const SkPicture* picture, 
+                                 PlaybackReplacements* replacements,
+                                 const SkPicture::OperationList* activeOpsList)
+        : INHERITED(picture)
+        , fReplacements(replacements)
+        , fActiveOpsList(activeOpsList) {
+    }
+
+    virtual void draw(SkCanvas* canvas, SkDrawPictureCallback*) SK_OVERRIDE;
+
+private:
+    PlaybackReplacements*           fReplacements;
+    const SkPicture::OperationList* fActiveOpsList;
+
+    // This method checks if the current op pointed at by 'iter' and 'reader'
+    // is within a replacement range. If so, it issues the drawBitmap call,
+    // updates 'iter' and 'reader' to be after the restore operation, and
+    // returns true. If the operation is not in a replacement range (and thus
+    // needs to be drawn normally) false is returned.
+    bool replaceOps(SkPictureStateTree::Iterator* iter,
+                    SkReader32* reader,
+                    SkCanvas* canvas,
+                    const SkMatrix& initialMatrix);
+
+    typedef SkPicturePlayback INHERITED;
+};
+
+#endif
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 0dbc2fb..870ca4a 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -30,8 +30,8 @@
 #include "SkPathEffect.h"
 #include "SkPicture.h"
 #include "SkPictureData.h"
-#include "SkPicturePlayback.h"
 #include "SkPictureRangePlayback.h"
+#include "SkPictureReplacementPlayback.h"
 #include "SkRRect.h"
 #include "SkStroke.h"
 #include "SkSurface.h"
@@ -1938,7 +1938,7 @@
         }
     }
 
-    SkPicturePlayback::PlaybackReplacements replacements;
+    SkPictureReplacementPlayback::PlaybackReplacements replacements;
 
     // Generate the layer and/or ensure it is locked
     for (int i = 0; i < gpuData->numSaveLayers(); ++i) {
@@ -1947,7 +1947,7 @@
 
             const GPUAccelData::SaveLayerInfo& info = gpuData->saveLayerInfo(i);
 
-            SkPicturePlayback::PlaybackReplacements::ReplacementInfo* layerInfo =
+            SkPictureReplacementPlayback::PlaybackReplacements::ReplacementInfo* layerInfo =
                                                                         replacements.push();
             layerInfo->fStart = info.fSaveLayerOpID;
             layerInfo->fStop = info.fRestoreOpID;
@@ -2030,9 +2030,8 @@
     }
 
     // Playback using new layers
-    SkPicturePlayback playback(picture);
+    SkPictureReplacementPlayback playback(picture, &replacements, ops.get());
 
-    playback.setReplacements(&replacements);
     playback.draw(canvas, NULL);
 
     // unlock the layers