First pass at pre-rendering saveLayers for GPU
authorrobertphillips@google.com <robertphillips@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 7 May 2014 21:31:09 +0000 (21:31 +0000)
committerrobertphillips@google.com <robertphillips@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 7 May 2014 21:31:09 +0000 (21:31 +0000)
https://codereview.chromium.org/261663003/

git-svn-id: http://skia.googlecode.com/svn/trunk@14632 2bbb7eff-a529-9590-31e7-b0007b416f81

include/core/SkPicture.h
src/core/SkPicturePlayback.cpp
src/core/SkPicturePlayback.h
src/core/SkPictureStateTree.cpp
src/core/SkPictureStateTree.h
src/gpu/GrPictureUtils.cpp
src/gpu/GrPictureUtils.h
src/gpu/SkGpuDevice.cpp

index bd723e8..c0b776c 100644 (file)
@@ -477,6 +477,7 @@ private:
     friend class SkPicturePlayback;
     friend class SkPictureRecorder;
     friend class SkGpuDevice;
+    friend class GrGatherCanvas;
     friend class GrGatherDevice;
     friend class SkDebugCanvas;
 
index 291774b..94fe421 100644 (file)
@@ -23,6 +23,33 @@ template <typename T> int SafeCount(const T* obj) {
  */
 #define SPEW_CLIP_SKIPPINGx
 
+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
+
 SkPicturePlayback::SkPicturePlayback(const SkPicture* picture, const SkPictInfo& info)
     : fPicture(picture)
     , fInfo(info) {
@@ -205,6 +232,10 @@ void SkPicturePlayback::init() {
     fStateTree = NULL;
     fCachedActiveOps = NULL;
     fCurOffset = 0;
+    fUseBBH = true;
+    fStart = 0;
+    fStop = 0;
+    fReplacements = NULL;
 }
 
 SkPicturePlayback::~SkPicturePlayback() {
@@ -744,6 +775,21 @@ private:
     SkPicturePlayback* fPlayback;
 };
 
+// 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;
+}
+
 void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback) {
     SkAutoResetOpID aroi(this);
     SkASSERT(0 == fCurOffset);
@@ -769,20 +815,24 @@ void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback)
     TextContainer text;
     const SkTDArray<void*>* activeOps = NULL;
 
-    if (NULL != fStateTree && NULL != fBoundingHierarchy) {
-        SkRect clipBounds;
-        if (canvas.getClipBounds(&clipBounds)) {
-            SkIRect query;
-            clipBounds.roundOut(&query);
+    // When draw limits are enabled (i.e., 0 != fStart || 0 != fStop) the state
+    // tree isn't used to pick and choose the draw operations
+    if (0 == fStart && 0 == fStop) {
+        if (fUseBBH && NULL != fStateTree && NULL != fBoundingHierarchy) {
+            SkRect clipBounds;
+            if (canvas.getClipBounds(&clipBounds)) {
+                SkIRect query;
+                clipBounds.roundOut(&query);
+
+                const SkPicture::OperationList& activeOpsList = this->getActiveOps(query);
+                if (activeOpsList.valid()) {
+                    if (0 == activeOpsList.numOps()) {
+                        return;     // nothing to draw
+                    }
 
-            const SkPicture::OperationList& activeOpsList = this->getActiveOps(query);
-            if (activeOpsList.valid()) {
-                if (0 == activeOpsList.numOps()) {
-                    return;     // nothing to draw
+                    // Since the opList is valid we know it is our derived class
+                    activeOps = &((const CachedOperationList&)activeOpsList).fOps;
                 }
-
-                // Since the opList is valid we know it is our derived class
-                activeOps = &((const CachedOperationList&)activeOpsList).fOps;
             }
         }
     }
@@ -791,6 +841,14 @@ void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback)
         SkPictureStateTree::Iterator() :
         fStateTree->getIterator(*activeOps, &canvas);
 
+    if (0 != fStart || 0 != fStop) {
+        reader.setOffset(fStart);
+        uint32_t size;
+        SkDEBUGCODE(DrawType op =) read_op_and_size(&reader, &size);
+        SkASSERT(SAVE_LAYER == op);
+        reader.setOffset(fStart+size);
+    }
+
     if (it.isValid()) {
         uint32_t skipTo = it.nextDraw();
         if (kDrawComplete == skipTo) {
@@ -821,6 +879,60 @@ void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback)
             return;
         }
 #endif
+        if (0 != fStart || 0 != fStop) {
+            size_t offset = reader.offset() ;
+            if (offset >= fStop) {
+                uint32_t size;
+                SkDEBUGCODE(DrawType op =) read_op_and_size(&reader, &size);
+                SkASSERT(RESTORE == op);
+                return;
+            }
+        }
+
+        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.drawBitmap(*temp->fBM, temp->fPos.fX, temp->fPos.fY, temp->fPaint);
+
+                if (it.isValid()) {
+                    // This save is needed since the BBH will automatically issue
+                    // a restore to balanced the saveLayer we're skipping
+                    canvas.save();
+                    // Note: This skipping only works if the client only issues
+                    // well behaved saveLayer calls (i.e., doesn't use 
+                    // kMatrix_SaveFlag or kClip_SaveFlag in isolation)
+
+                    // 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.
+                    SkASSERT(it.peekDraw() >= temp->fStart && it.peekDraw() <= temp->fStop);
+
+                    while (kDrawComplete != it.peekDraw() && it.peekDraw() <= temp->fStop) {
+                        it.skipDraw();
+                    }
+
+                    if (kDrawComplete == it.peekDraw()) {
+                        break;
+                    }
+
+                    uint32_t skipTo = it.nextDraw();
+                    reader.setOffset(skipTo);
+                } else {
+                    reader.setOffset(temp->fStop);
+                    uint32_t size;
+                    SkDEBUGCODE(DrawType op =) read_op_and_size(&reader, &size);
+                    SkASSERT(RESTORE == op);
+                }
+                continue;
+            }
+        }
 
 #ifdef SPEW_CLIP_SKIPPING
         opCount++;
@@ -915,8 +1027,7 @@ void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback)
                 SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed);
                 bool doAA = ClipParams_unpackDoAA(packed);
                 size_t offsetToRestore = reader.readInt();
-                SkASSERT(!offsetToRestore || \
-                         offsetToRestore >= reader.offset());
+                SkASSERT(!offsetToRestore || offsetToRestore >= reader.offset());
                 canvas.clipRRect(rrect, regionOp, doAA);
                 if (canvas.isClipEmpty() && offsetToRestore) {
 #ifdef SPEW_CLIP_SKIPPING
index 28fdd63..7ac8dd8 100644 (file)
@@ -5,6 +5,7 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+
 #ifndef SkPicturePlayback_DEFINED
 #define SkPicturePlayback_DEFINED
 
@@ -91,6 +92,8 @@ public:
 
     const SkPicture::OperationList& getActiveOps(const SkIRect& queryRect);
 
+    void setUseBBH(bool useBBH) { fUseBBH = useBBH; }
+
     void draw(SkCanvas& canvas, SkDrawPictureCallback*);
 
     void serialize(SkWStream*, SkPicture::EncodeBitmap) const;
@@ -227,6 +230,7 @@ private:    // these help us with reading/writing
 
 private:
     friend class SkPicture;
+    friend class SkGpuDevice;   // for access to setDrawLimits & setReplacements
 
     // The picture that owns this SkPicturePlayback object
     const SkPicture* fPicture;
@@ -248,6 +252,68 @@ private:
     SkBBoxHierarchy* fBoundingHierarchy;
     SkPictureStateTree* fStateTree;
 
+    // Limit the opcode playback to be between the offsets 'start' and 'stop'.
+    // The opcode at 'start' should be a saveLayer while the opcode at
+    // 'stop' should be a restore. Neither of those commands will be issued.
+    // Set both start & stop to 0 to disable draw limiting
+    // Draw limiting cannot be enabled at the same time as draw replacing
+    void setDrawLimits(size_t start, size_t stop) {
+        SkASSERT(NULL == fReplacements);
+        fStart = start;
+        fStop = stop;
+    }
+
+    // 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;
+            SkPoint         fPos;
+            SkBitmap*       fBM;
+            const SkPaint*  fPaint;  // Note: this object doesn't own the paint
+        };
+
+        ~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) {
+        SkASSERT(fStart == 0 && fStop == 0);
+        fReplacements = replacements;
+    }
+
+    bool   fUseBBH;
+    size_t fStart;
+    size_t fStop;
+    PlaybackReplacements* fReplacements;
+
     class CachedOperationList : public SkPicture::OperationList {
     public:
         CachedOperationList() {
index f6d54ff..39a1a47 100644 (file)
@@ -114,27 +114,60 @@ void SkPictureStateTree::Iterator::setCurrentMatrix(const SkMatrix* matrix) {
     fCurrentMatrix = matrix;
 }
 
-uint32_t SkPictureStateTree::Iterator::nextDraw() {
+uint32_t SkPictureStateTree::Iterator::peekDraw() {
+    SkASSERT(this->isValid());
+    if (fPlaybackIndex >= fDraws->count()) {
+        return kDrawComplete;
+    }
+
+    Draw* draw = static_cast<Draw*>((*fDraws)[fPlaybackIndex]);
+    return draw->fOffset;    
+}
+
+uint32_t SkPictureStateTree::Iterator::skipDraw() {
     SkASSERT(this->isValid());
     if (fPlaybackIndex >= fDraws->count()) {
+        return this->finish();
+    }
+
+    Draw* draw = static_cast<Draw*>((*fDraws)[fPlaybackIndex]);
+
+    if (fSave) {
+        fCanvas->save();
+        fSave = false;
+    }
+
+    fNodes.rewind();
+
+    ++fPlaybackIndex;
+    return draw->fOffset;
+}
+
+uint32_t SkPictureStateTree::Iterator::finish() {
+    if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) {
+        fCanvas->restore();
+    }
+
+    for (fCurrentNode = fCurrentNode->fParent; fCurrentNode;
+            fCurrentNode = fCurrentNode->fParent) {
+        // Note: we call restore() twice when both flags are set.
+        if (fCurrentNode->fFlags & Node::kSave_Flag) {
+            fCanvas->restore();
+        }
         if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) {
             fCanvas->restore();
         }
+    }
 
-        for (fCurrentNode = fCurrentNode->fParent; fCurrentNode;
-             fCurrentNode = fCurrentNode->fParent) {
-            // Note: we call restore() twice when both flags are set.
-            if (fCurrentNode->fFlags & Node::kSave_Flag) {
-                fCanvas->restore();
-            }
-            if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) {
-                fCanvas->restore();
-            }
-        }
+    fCanvas->setMatrix(fPlaybackMatrix);
+    fCurrentMatrix = NULL;
+    return kDrawComplete;
+}
 
-        fCanvas->setMatrix(fPlaybackMatrix);
-        fCurrentMatrix = NULL;
-        return kDrawComplete;
+uint32_t SkPictureStateTree::Iterator::nextDraw() {
+    SkASSERT(this->isValid());
+    if (fPlaybackIndex >= fDraws->count()) {
+        return this->finish();
     }
 
     Draw* draw = static_cast<Draw*>((*fDraws)[fPlaybackIndex]);
index d61bf03..a77e094 100644 (file)
@@ -74,8 +74,29 @@ public:
      */
     class Iterator {
     public:
-        /** Returns the next offset into the picture stream, or kDrawComplete if complete. */
+        /** Returns the next op offset needed to create the drawing state
+            required by the queued up draw operation or the offset of the queued 
+            up draw operation itself. In the latter case, the next draw operation
+            will move into the queued up slot.
+            It retuns kDrawComplete when done. 
+            TODO: this might be better named nextOp
+        */
         uint32_t nextDraw();
+        /** Peek at the currently queued up draw op's offset. Note that this can
+            be different then what 'nextDraw' would return b.c. it is
+            the offset of the next _draw_ op while 'nextDraw' can return
+            the offsets to saveLayer and clip ops while it is creating the proper 
+            drawing context for the queued up draw op.
+        */
+        uint32_t peekDraw();
+        /** Stop trying to create the drawing context for the currently queued 
+            up _draw_ operation and queue up the next one. This call returns
+            the offset of the skipped _draw_ operation. Obviously (since the
+            correct drawing context has not been established), the skipped
+            _draw_ operation should not be issued. Returns kDrawComplete if
+            the end of the draw operations is reached.
+         */
+        uint32_t skipDraw();
         static const uint32_t kDrawComplete = SK_MaxU32;
         Iterator() : fPlaybackMatrix(), fValid(false) { }
         bool isValid() const { return fValid; }
@@ -111,6 +132,8 @@ public:
         // Whether or not this is a valid iterator (the default public constructor sets this false)
         bool fValid;
 
+        uint32_t finish();
+
         friend class SkPictureStateTree;
     };
 
index 996a32d..a66f34c 100644 (file)
@@ -9,6 +9,7 @@
 #include "SkDevice.h"
 #include "SkDraw.h"
 #include "SkPaintPriv.h"
+#include "SkPicturePlayback.h"
 
 SkPicture::AccelData::Key GPUAccelData::ComputeAccelDataKey() {
     static const SkPicture::AccelData::Key gGPUID = SkPicture::AccelData::GenerateDomain();
@@ -249,7 +250,15 @@ public:
     }
 
     virtual void drawPicture(SkPicture& picture) SK_OVERRIDE {
+        // BBH-based rendering doesn't re-issue many of the operations the gather
+        // process cares about (e.g., saves and restores) so it must be disabled.
+        if (NULL != picture.fPlayback) {
+            picture.fPlayback->setUseBBH(false);
+        }
         picture.draw(this);
+        if (NULL != picture.fPlayback) {
+            picture.fPlayback->setUseBBH(true);
+        }
     }
 protected:
     // disable aa for speed
index c625298..b8a7814 100644 (file)
@@ -68,10 +68,9 @@ public:
     // incorporate the clip and matrix state into the key
     static SkPicture::AccelData::Key ComputeAccelDataKey();
 
-protected:
+private:
     SkTDArray<SaveLayerInfo> fSaveLayerInfo;
 
-private:
     typedef SkPicture::AccelData INHERITED;
 };
 
index 517f082..1a6a3e4 100644 (file)
@@ -28,6 +28,7 @@
 #include "SkMaskFilter.h"
 #include "SkPathEffect.h"
 #include "SkPicture.h"
+#include "SkPicturePlayback.h"
 #include "SkRRect.h"
 #include "SkStroke.h"
 #include "SkSurface.h"
@@ -1920,6 +1921,12 @@ void SkGpuDevice::EXPERIMENTAL_optimize(SkPicture* picture) {
     GatherGPUInfo(picture, data);
 }
 
+static void wrap_texture(GrTexture* texture, int width, int height, SkBitmap* result) {
+    SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
+    result->setConfig(info);
+    result->setPixelRef(SkNEW_ARGS(SkGrPixelRef, (info, texture)))->unref();
+}
+
 void SkGpuDevice::EXPERIMENTAL_purge(SkPicture* picture) {
 
 }
@@ -1940,30 +1947,148 @@ bool SkGpuDevice::EXPERIMENTAL_drawPicture(SkCanvas* canvas, SkPicture* picture)
         pullForward[i] = false;
     }
 
-    SkIRect clip;
+    SkRect clipBounds;
+    if (!canvas->getClipBounds(&clipBounds)) {
+        return true;
+    }
+    SkIRect query;
+    clipBounds.roundOut(&query);
 
-    fClipData.getConservativeBounds(this->width(), this->height(), &clip, NULL);
+    const SkPicture::OperationList& ops = picture->EXPERIMENTAL_getActiveOps(query);
 
-    SkMatrix inv;
-    if (!fContext->getMatrix().invert(&inv)) {
-        return false;
-    }
+    // This code pre-renders the entire layer since it will be cached and potentially
+    // reused with different clips (e.g., in different tiles). Because of this the
+    // clip will not be limiting the size of the pre-rendered layer. kSaveLayerMaxSize
+    // is used to limit which clips are pre-rendered.
+    static const int kSaveLayerMaxSize = 256;
+
+    if (ops.valid()) {
+        // In this case the picture has been generated with a BBH so we use
+        // the BBH to limit the pre-rendering to just the layers needed to cover 
+        // the region being drawn 
+        for (int i = 0; i < ops.numOps(); ++i) {
+            uint32_t offset = ops.offset(i);
+
+            // For now we're saving all the layers in the GPUAccelData so they
+            // can be nested. Additionally, the nested layers appear before 
+            // their parent in the list.
+            for (int j = 0 ; j < gpuData->numSaveLayers(); ++j) {
+                const GPUAccelData::SaveLayerInfo& info = gpuData->saveLayerInfo(j);
+
+                if (pullForward[j]) {
+                    continue;            // already pulling forward
+                }
 
-    SkRect r = SkRect::Make(clip);
-    inv.mapRect(&r);
-    r.roundOut(&clip);
+                if (offset < info.fSaveLayerOpID || offset > info.fRestoreOpID) {
+                    continue;            // the op isn't in this range
+                }
 
-    const SkPicture::OperationList& ops = picture->EXPERIMENTAL_getActiveOps(clip);
+                // TODO: once this code is more stable unsuitable layers can
+                // just be omitted during the optimization stage
+                if (!info.fValid ||
+                    kSaveLayerMaxSize < info.fSize.fWidth ||
+                    kSaveLayerMaxSize < info.fSize.fHeight ||
+                    info.fIsNested) {
+                    continue;            // this layer is unsuitable
+                }
 
-    for (int i = 0; i < ops.numOps(); ++i) {
+                pullForward[j] = true;
+            }
+        }
+    } else {
+        // In this case there is no BBH associated with the picture. Pre-render
+        // all the layers
+        // TODO: intersect the bounds of each layer with the clip region to
+        // reduce the number of pre-rendered layers
         for (int j = 0; j < gpuData->numSaveLayers(); ++j) {
             const GPUAccelData::SaveLayerInfo& info = gpuData->saveLayerInfo(j);
 
-            if (ops.offset(i) > info.fSaveLayerOpID && ops.offset(i) < info.fRestoreOpID) {
-                pullForward[j] = true;
+            // TODO: once this code is more stable unsuitable layers can
+            // just be omitted during the optimization stage
+            if (!info.fValid ||
+                kSaveLayerMaxSize < info.fSize.fWidth ||
+                kSaveLayerMaxSize < info.fSize.fHeight ||
+                info.fIsNested) {
+                continue;
             }
+
+            pullForward[j] = true;   
         }
     }
 
-    return false;
+    SkPicturePlayback::PlaybackReplacements replacements;
+
+    for (int i = 0; i < gpuData->numSaveLayers(); ++i) {
+        if (pullForward[i]) {
+            GrCachedLayer* layer = fContext->getLayerCache()->findLayerOrCreate(picture, i);
+
+            const GPUAccelData::SaveLayerInfo& info = gpuData->saveLayerInfo(i);
+
+            if (NULL != picture->fPlayback) {
+                SkPicturePlayback::PlaybackReplacements::ReplacementInfo* layerInfo = 
+                                                                        replacements.push();
+                layerInfo->fStart = info.fSaveLayerOpID;
+                layerInfo->fStop = info.fRestoreOpID;
+                layerInfo->fPos = info.fOffset;
+
+                GrTextureDesc desc;
+                desc.fFlags = kRenderTarget_GrTextureFlagBit;
+                desc.fWidth = info.fSize.fWidth;
+                desc.fHeight = info.fSize.fHeight;
+                desc.fConfig = kSkia8888_GrPixelConfig;
+                // TODO: need to deal with sample count
+
+                bool bNeedsRendering = true;
+
+                // This just uses scratch textures and doesn't cache the texture.
+                // This can yield a lot of re-rendering
+                if (NULL == layer->getTexture()) {
+                    layer->setTexture(fContext->lockAndRefScratchTexture(desc, 
+                                                        GrContext::kApprox_ScratchTexMatch));
+                    if (NULL == layer->getTexture()) {
+                        continue;
+                    }
+                } else {
+                    bNeedsRendering = false;
+                }
+
+                layerInfo->fBM = SkNEW(SkBitmap);
+                wrap_texture(layer->getTexture(), desc.fWidth, desc.fHeight, layerInfo->fBM);
+
+                SkASSERT(info.fPaint);
+                layerInfo->fPaint = info.fPaint;
+
+                if (bNeedsRendering) {
+                    SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTargetDirect(
+                                                        layer->getTexture()->asRenderTarget()));
+
+                    SkCanvas* canvas = surface->getCanvas();
+
+                    canvas->setMatrix(info.fCTM);
+                    canvas->clear(SK_ColorTRANSPARENT);
+
+                    picture->fPlayback->setDrawLimits(info.fSaveLayerOpID, info.fRestoreOpID);
+                    picture->fPlayback->draw(*canvas, NULL);
+                    picture->fPlayback->setDrawLimits(0, 0);
+                    canvas->flush();
+                }
+            }
+        }
+    }
+
+    // Playback using new layers
+    picture->fPlayback->setReplacements(&replacements);
+    picture->fPlayback->draw(*canvas, NULL);
+    picture->fPlayback->setReplacements(NULL);
+
+    for (int i = 0; i < gpuData->numSaveLayers(); ++i) {
+        GrCachedLayer* layer = fContext->getLayerCache()->findLayerOrCreate(picture, i);
+
+        if (NULL != layer->getTexture()) {
+            fContext->unlockScratchTexture(layer->getTexture());
+            layer->setTexture(NULL);
+        }
+    }
+
+    return true;
 }