Add auto purging for SkPicture-related Ganesh resources (esp. layers)

This is intended to lower the bookkeeping burden for the Layer Caching feature. Cached layers are now automatically purged when a picture is deleted.

R=bsalomon@google.com

Author: robertphillips@google.com

Review URL: https://codereview.chromium.org/408923002
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index 6bd3cc5..d68a9c5 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -964,12 +964,6 @@
     */
     void EXPERIMENTAL_optimize(const SkPicture* picture);
 
-    /** PRIVATE / EXPERIMENTAL -- do not call
-        Purge all the discardable optimization information associated with
-        'picture'. If NULL is passed in, purge all discardable information.
-    */
-    void EXPERIMENTAL_purge(const SkPicture* picture);
-
     /** Draw the picture into this canvas. This method effective brackets the
         playback of the picture's draw calls with save/restore, so the state
         of this canvas will be unchanged after this call.
diff --git a/include/core/SkDevice.h b/include/core/SkDevice.h
index 2e06aab..4a9edee 100644
--- a/include/core/SkDevice.h
+++ b/include/core/SkDevice.h
@@ -338,13 +338,6 @@
 
     /**
      *  PRIVATE / EXPERIMENTAL -- do not call
-     *  Purge all discardable optimization information for 'picture'. If
-     *  picture is NULL then purge discardable information for all pictures.
-     */
-    virtual void EXPERIMENTAL_purge(const SkPicture* picture);
-
-    /**
-     *  PRIVATE / EXPERIMENTAL -- do not call
      *  This entry point gives the backend an opportunity to take over the rendering
      *  of 'picture'. If optimization data is available (due to an earlier
      *  'optimize' call) this entry point should make use of it and return true
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index db996e0..21ebef3 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -190,6 +190,14 @@
     bool suitableForGpuRasterization(GrContext*, const char ** = NULL) const;
 #endif
 
+    class DeletionListener : public SkRefCnt {
+    public:
+        virtual void onDeletion(uint32_t pictureID) = 0;
+    };
+
+    // Takes ref on listener.
+    void addDeletionListener(DeletionListener* listener) const;
+
 private:
     // V2 : adds SkPixelRef's generation ID.
     // V3 : PictInfo tag at beginning, and EOF tag at the end
@@ -237,7 +245,10 @@
     int                   fWidth, fHeight;
     mutable SkAutoTUnref<const AccelData> fAccelData;
 
+    mutable SkTDArray<DeletionListener*> fDeletionListeners;  // pointers are refed
+
     void needsNewGenID() { fUniqueID = SK_InvalidGenID; }
+    void callDeletionListeners();
 
     // Create a new SkPicture from an existing SkPictureData. The new picture
     // takes ownership of 'data'.
diff --git a/include/gpu/SkGpuDevice.h b/include/gpu/SkGpuDevice.h
index 6451e15..9a5a92e 100644
--- a/include/gpu/SkGpuDevice.h
+++ b/include/gpu/SkGpuDevice.h
@@ -143,8 +143,6 @@
     /**  PRIVATE / EXPERIMENTAL -- do not call */
     virtual void EXPERIMENTAL_optimize(const SkPicture* picture) SK_OVERRIDE;
     /**  PRIVATE / EXPERIMENTAL -- do not call */
-    virtual void EXPERIMENTAL_purge(const SkPicture* picture) SK_OVERRIDE;
-    /**  PRIVATE / EXPERIMENTAL -- do not call */
     virtual bool EXPERIMENTAL_drawPicture(SkCanvas* canvas, const SkPicture* picture) SK_OVERRIDE;
 
 private:
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 4b1749a..504c990 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -2386,13 +2386,6 @@
     }
 }
 
-void SkCanvas::EXPERIMENTAL_purge(const SkPicture* picture) {
-    SkBaseDevice* device = this->getTopDevice();
-    if (NULL != device) {
-        device->EXPERIMENTAL_purge(picture);
-    }
-}
-
 void SkCanvas::drawPicture(const SkPicture* picture) {
     if (NULL != picture) {
         this->onDrawPicture(picture);
diff --git a/src/core/SkDevice.cpp b/src/core/SkDevice.cpp
index 6a09c0b..e71c889 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -134,10 +134,6 @@
     // The base class doesn't perform any analysis but derived classes may
 }
 
-void SkBaseDevice::EXPERIMENTAL_purge(const SkPicture* picture) {
-    // Derived-classes may have data to purge but not the base class
-}
-
 bool SkBaseDevice::EXPERIMENTAL_drawPicture(SkCanvas* canvas, const SkPicture* picture) {
     // The base class doesn't perform any accelerated picture rendering
     return false;
diff --git a/src/core/SkPicture.cpp b/src/core/SkPicture.cpp
index d57de97..0b7a1db 100644
--- a/src/core/SkPicture.cpp
+++ b/src/core/SkPicture.cpp
@@ -144,7 +144,9 @@
 }
 
 // fRecord OK
-SkPicture::~SkPicture() {}
+SkPicture::~SkPicture() {
+    this->callDeletionListeners();
+}
 
 #ifdef SK_SUPPORT_LEGACY_PICTURE_CLONE
 // fRecord TODO, fix by deleting this method
@@ -492,3 +494,21 @@
     , fRecordWillPlayBackBitmaps(SkRecordWillPlaybackBitmaps(*record)) {
     this->needsNewGenID();
 }
+
+// Note that we are assuming that this entry point will only be called from
+// one thread. Currently the only client of this method is 
+// SkGpuDevice::EXPERIMENTAL_optimize which should be only called from a single
+// thread.
+void SkPicture::addDeletionListener(DeletionListener* listener) const {
+    SkASSERT(NULL != listener);
+
+    *fDeletionListeners.append() = SkRef(listener);
+}
+
+void SkPicture::callDeletionListeners() {
+    for (int i = 0; i < fDeletionListeners.count(); ++i) {
+        fDeletionListeners[i]->onDeletion(this->uniqueID());
+    }
+
+    fDeletionListeners.unrefAll();
+}
diff --git a/src/gpu/GrLayerCache.cpp b/src/gpu/GrLayerCache.cpp
index 1a09230..32f5a5b 100644
--- a/src/gpu/GrLayerCache.cpp
+++ b/src/gpu/GrLayerCache.cpp
@@ -9,6 +9,8 @@
 #include "GrGpu.h"
 #include "GrLayerCache.h"
 
+DECLARE_SKMESSAGEBUS_MESSAGE(GrPictureDeletedMessage);
+
 #ifdef SK_DEBUG
 void GrCachedLayer::validate(const GrTexture* backingTexture) const {
     SkASSERT(SK_InvalidGenID != fKey.getPictureID());
@@ -223,7 +225,8 @@
 };
 #endif
 
-void GrLayerCache::purge(const SkPicture* picture) {
+void GrLayerCache::purge(uint32_t pictureID) {
+
     SkDEBUGCODE(GrAutoValidateCache avc(this);)
 
     // We need to find all the layers associated with 'picture' and remove them.
@@ -231,7 +234,7 @@
 
     SkTDynamicHash<GrCachedLayer, GrCachedLayer::Key>::Iter iter(&fLayerHash);
     for (; !iter.done(); ++iter) {
-        if (picture->uniqueID() == (*iter).pictureID()) {
+        if (pictureID == (*iter).pictureID()) {
             *toBeRemoved.append() = &(*iter);
         }
     }
@@ -242,9 +245,34 @@
         SkDELETE(toBeRemoved[i]);
     }
 
-    GrPictureInfo* pictInfo = fPictureHash.find(picture->uniqueID());
+    GrPictureInfo* pictInfo = fPictureHash.find(pictureID);
     if (NULL != pictInfo) {
-        fPictureHash.remove(picture->uniqueID());
+        fPictureHash.remove(pictureID);
         SkDELETE(pictInfo);
     }
 }
+
+class GrPictureDeletionListener : public SkPicture::DeletionListener {
+    virtual void onDeletion(uint32_t pictureID) SK_OVERRIDE{
+        const GrPictureDeletedMessage message = { pictureID };
+        SkMessageBus<GrPictureDeletedMessage>::Post(message);
+    }
+};
+
+void GrLayerCache::trackPicture(const SkPicture* picture) {
+    if (NULL == fDeletionListener) {
+        fDeletionListener.reset(SkNEW(GrPictureDeletionListener));
+    }
+
+    picture->addDeletionListener(fDeletionListener);
+}
+
+void GrLayerCache::processDeletedPictures() {
+    SkTDArray<GrPictureDeletedMessage> deletedPictures;
+    fPictDeletionInbox.poll(&deletedPictures);
+
+    for (int i = 0; i < deletedPictures.count(); i++) {
+        this->purge(deletedPictures[i].pictureID);
+    }
+}
+
diff --git a/src/gpu/GrLayerCache.h b/src/gpu/GrLayerCache.h
index 3d4c98a..0fdd6c9 100644
--- a/src/gpu/GrLayerCache.h
+++ b/src/gpu/GrLayerCache.h
@@ -16,9 +16,15 @@
 #include "GrRect.h"
 #include "SkChecksum.h"
 #include "SkTDynamicHash.h"
+#include "SkMessageBus.h"
 
 class SkPicture;
 
+// The layer cache listens for these messages to purge picture-related resources.
+struct GrPictureDeletedMessage {
+    uint32_t pictureID;
+};
+
 // GrPictureInfo stores the atlas plots used by a single picture. A single 
 // plot may be used to store layers from multiple pictures.
 struct GrPictureInfo {
@@ -146,8 +152,11 @@
     // Inform the cache that layer's cached image is not currently required
     void unlock(GrCachedLayer* layer);
 
-    // Remove all the layers (and unlock any resources) associated with 'picture'
-    void purge(const SkPicture* picture);
+    // Setup to be notified when 'picture' is deleted
+    void trackPicture(const SkPicture* picture);
+
+    // Cleanup after any SkPicture deletions
+    void processDeletedPictures();
 
     SkDEBUGCODE(void validate() const;)
 
@@ -167,11 +176,18 @@
 
     SkTDynamicHash<GrCachedLayer, GrCachedLayer::Key> fLayerHash;
 
+    SkMessageBus<GrPictureDeletedMessage>::Inbox fPictDeletionInbox;
+
+    SkAutoTUnref<SkPicture::DeletionListener> fDeletionListener;
+
     void initAtlas();
     GrCachedLayer* createLayer(const SkPicture* picture, int layerID);
 
+    // Remove all the layers (and unlock any resources) associated with 'pictureID'
+    void purge(uint32_t pictureID);
+
     // for testing
-    friend class GetNumLayers;
+    friend class TestingAccess;
     int numLayers() const { return fLayerHash.count(); }
 };
 
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index b06046a..c35ff41 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1842,6 +1842,8 @@
 }
 
 void SkGpuDevice::EXPERIMENTAL_optimize(const SkPicture* picture) {
+    fContext->getLayerCache()->processDeletedPictures();
+
     SkPicture::AccelData::Key key = GPUAccelData::ComputeAccelDataKey();
 
     const SkPicture::AccelData* existing = picture->EXPERIMENTAL_getAccelData(key);
@@ -1854,6 +1856,8 @@
     picture->EXPERIMENTAL_addAccelData(data);
 
     GatherGPUInfo(picture, data);
+
+    fContext->getLayerCache()->trackPicture(picture);
 }
 
 static void wrap_texture(GrTexture* texture, int width, int height, SkBitmap* result) {
@@ -1862,11 +1866,8 @@
     result->setPixelRef(SkNEW_ARGS(SkGrPixelRef, (info, texture)))->unref();
 }
 
-void SkGpuDevice::EXPERIMENTAL_purge(const SkPicture* picture) {
-    fContext->getLayerCache()->purge(picture);
-}
-
 bool SkGpuDevice::EXPERIMENTAL_drawPicture(SkCanvas* canvas, const SkPicture* picture) {
+    fContext->getLayerCache()->processDeletedPictures();
 
     SkPicture::AccelData::Key key = GPUAccelData::ComputeAccelDataKey();
 
diff --git a/tests/GpuLayerCacheTest.cpp b/tests/GpuLayerCacheTest.cpp
index 8da2b9e..eb7d92c 100644
--- a/tests/GpuLayerCacheTest.cpp
+++ b/tests/GpuLayerCacheTest.cpp
@@ -15,11 +15,14 @@
 
 static const int kNumLayers = 5;
 
-class GetNumLayers {
+class TestingAccess {
 public:
     static int NumLayers(GrLayerCache* cache) {
         return cache->numLayers();
     }
+    static void Purge(GrLayerCache* cache, uint32_t pictureID) {
+        cache->purge(pictureID);
+    }
 };
 
 // Add several layers to the cache
@@ -34,7 +37,7 @@
         GrCachedLayer* layer = cache->findLayer(&picture, i);
         REPORTER_ASSERT(reporter, layer == layers[i]);
 
-        REPORTER_ASSERT(reporter, GetNumLayers::NumLayers(cache) == i+1);
+        REPORTER_ASSERT(reporter, TestingAccess::NumLayers(cache) == i + 1);
 
         REPORTER_ASSERT(reporter, picture.uniqueID() == layers[i]->pictureID());
         REPORTER_ASSERT(reporter, layers[i]->layerID() == i);
@@ -42,6 +45,7 @@
         REPORTER_ASSERT(reporter, !layers[i]->isAtlased());
     }
 
+    cache->trackPicture(&picture);
 }
 
 // This test case exercises the public API of the GrLayerCache class.
@@ -126,22 +130,36 @@
 #endif
         }
 
+        //--------------------------------------------------------------------
         // Free them all SkGpuDevice-style. This will not free up the
         // atlas' texture but will eliminate all the layers.
-        cache.purge(picture);
+        TestingAccess::Purge(&cache, picture->uniqueID());
 
-        REPORTER_ASSERT(reporter, GetNumLayers::NumLayers(&cache) == 0);
+        REPORTER_ASSERT(reporter, TestingAccess::NumLayers(&cache) == 0);
         // TODO: add VRAM/resource cache check here
-#if 0
+
+        //--------------------------------------------------------------------
+        // Test out the GrContext-style purge. This should remove all the layers
+        // and the atlas.
         // Re-create the layers
-        create_layers(reporter, &cache, picture);
+        create_layers(reporter, &cache, *picture);
 
         // Free them again GrContext-style. This should free up everything.
         cache.freeAll();
 
-        REPORTER_ASSERT(reporter, GetNumLayers::NumLayers(&cache) == 0);
+        REPORTER_ASSERT(reporter, TestingAccess::NumLayers(&cache) == 0);
         // TODO: add VRAM/resource cache check here
-#endif
+
+        //--------------------------------------------------------------------
+        // Test out the MessageBus-style purge. This will not free the atlas
+        // but should eliminate the free-floating layers.
+        create_layers(reporter, &cache, *picture);
+
+        picture.reset(NULL);
+        cache.processDeletedPictures();
+
+        REPORTER_ASSERT(reporter, TestingAccess::NumLayers(&cache) == 0);
+        // TODO: add VRAM/resource cache check here
     }
 }
 
diff --git a/tools/PictureBenchmark.cpp b/tools/PictureBenchmark.cpp
index 70ce180..e2a726f 100644
--- a/tools/PictureBenchmark.cpp
+++ b/tools/PictureBenchmark.cpp
@@ -81,12 +81,6 @@
     fRenderer->render(NULL);
     fRenderer->resetState(true);   // flush, swapBuffers and Finish
 
-    if (fPreprocess) {
-        if (NULL != fRenderer->getCanvas()) {
-            fRenderer->getCanvas()->EXPERIMENTAL_purge(pict);
-        }
-    }
-
     if (fPurgeDecodedTex) {
         fRenderer->purgeTextures();
     }
@@ -220,12 +214,6 @@
 
                 SkAssertResult(perRunTimerData.appendTimes(perRunTimer.get()));
 
-                if (fPreprocess) {
-                    if (NULL != fRenderer->getCanvas()) {
-                        fRenderer->getCanvas()->EXPERIMENTAL_purge(pict);
-                    }
-                }
-
                 if (fPurgeDecodedTex) {
                     fRenderer->purgeTextures();
                 }
diff --git a/tools/render_pictures_main.cpp b/tools/render_pictures_main.cpp
index 13ae6ea..c2c7875 100644
--- a/tools/render_pictures_main.cpp
+++ b/tools/render_pictures_main.cpp
@@ -205,12 +205,6 @@
         SkDebugf("Failed to render %s\n", inputFilename.c_str());
     }
 
-    if (FLAGS_preprocess) {
-        if (NULL != renderer.getCanvas()) {
-            renderer.getCanvas()->EXPERIMENTAL_purge(renderer.getPicture());
-        }
-    }
-
     renderer.end();
 
     SkDELETE(picture);