Add plot-based purging to GrLayerCache

This CL allows a GrPlot full of atlased layer to be purged from the atlas to make room for new layers.

R=jvanverth@google.com

Author: robertphillips@google.com

Review URL: https://codereview.chromium.org/411703003
diff --git a/src/gpu/GrAtlas.h b/src/gpu/GrAtlas.h
index 9ccbc53..1e91d1e 100644
--- a/src/gpu/GrAtlas.h
+++ b/src/gpu/GrAtlas.h
@@ -81,6 +81,12 @@
     public:
         bool isEmpty() const { return 0 == fPlots.count(); }
 
+#ifdef SK_DEBUG
+        bool contains(const GrPlot* plot) const { 
+            return fPlots.contains(const_cast<GrPlot*>(plot)); 
+        }
+#endif
+
     private:
         SkTDArray<GrPlot*> fPlots;
 
@@ -112,6 +118,18 @@
 
     void uploadPlotsToTexture();
 
+    enum IterOrder {
+        kLRUFirst_IterOrder,
+        kMRUFirst_IterOrder
+    };
+
+    typedef GrPlotList::Iter PlotIter;
+    GrPlot* iterInit(PlotIter* iter, IterOrder order) {
+        return iter->init(fPlotList, kLRUFirst_IterOrder == order 
+                                                       ? GrPlotList::Iter::kTail_IterStart
+                                                       : GrPlotList::Iter::kHead_IterStart);
+    }
+
 private:
     void makeMRU(GrPlot* plot);
 
diff --git a/src/gpu/GrLayerCache.cpp b/src/gpu/GrLayerCache.cpp
index 32f5a5b..2a57dcc 100644
--- a/src/gpu/GrLayerCache.cpp
+++ b/src/gpu/GrLayerCache.cpp
@@ -27,6 +27,7 @@
     } else {
         SkASSERT(fRect.isEmpty());
         SkASSERT(NULL == fPlot);
+        SkASSERT(!fLocked);     // layers without a texture cannot be locked
     }
 
     if (NULL != fPlot) {
@@ -35,6 +36,13 @@
         SkASSERT(NULL != fTexture && backingTexture == fTexture);
         SkASSERT(!fRect.isEmpty());
     }
+
+    if (fLocked) {
+        // If a layer is locked it must have a texture (though it need not be
+        // the atlas-backing texture) and occupy some space.
+        SkASSERT(NULL != fTexture);
+        SkASSERT(!fRect.isEmpty());
+    }
 }
 
 class GrAutoValidateLayer : ::SkNoncopyable {
@@ -65,6 +73,7 @@
 GrLayerCache::GrLayerCache(GrContext* context)
     : fContext(context) {
     this->initAtlas();
+    memset(fPlotLocks, 0, sizeof(fPlotLocks));
 }
 
 GrLayerCache::~GrLayerCache() {
@@ -81,12 +90,8 @@
 }
 
 void GrLayerCache::initAtlas() {
-    static const int kAtlasTextureWidth = 1024;
-    static const int kAtlasTextureHeight = 1024;
-
     SkASSERT(NULL == fAtlas.get());
 
-    // The layer cache only gets 1 plot
     SkISize textureSize = SkISize::Make(kAtlasTextureWidth, kAtlasTextureHeight);
     fAtlas.reset(SkNEW_ARGS(GrAtlas, (fContext->getGpu(), kSkia8888_GrPixelConfig,
                                       kRenderTarget_GrTextureFlagBit,
@@ -138,7 +143,7 @@
 bool GrLayerCache::lock(GrCachedLayer* layer, const GrTextureDesc& desc) {
     SkDEBUGCODE(GrAutoValidateLayer avl(fAtlas->getTexture(), layer);)
 
-    if (NULL != layer->texture()) {
+    if (layer->locked()) {
         // This layer is already locked
 #ifdef SK_DEBUG
         if (layer->isAtlased()) {
@@ -151,7 +156,13 @@
     }
 
 #if USE_ATLAS
-    {
+    if (layer->isAtlased()) {
+        // Hooray it is still in the atlas - make sure it stays there
+        layer->setLocked(true);
+        fPlotLocks[layer->plot()->id()]++;
+        return true;
+    } else if (PlausiblyAtlasable(desc.fWidth, desc.fHeight)) {
+        // Not in the atlas - will it fit?
         GrPictureInfo* pictInfo = fPictureHash.find(layer->pictureID());
         if (NULL == pictInfo) {
             pictInfo = SkNEW_ARGS(GrPictureInfo, (layer->pictureID()));
@@ -159,17 +170,29 @@
         }
 
         SkIPoint16 loc;
-        GrPlot* plot = fAtlas->addToAtlas(&pictInfo->fPlotUsage, 
-                                          desc.fWidth, desc.fHeight, 
-                                          NULL, &loc);
-        // addToAtlas can allocate the backing texture
-        SkDEBUGCODE(avl.setBackingTexture(fAtlas->getTexture()));
-        if (NULL != plot) {
-            GrIRect16 bounds = GrIRect16::MakeXYWH(loc.fX, loc.fY,
-                                                   SkToS16(desc.fWidth), SkToS16(desc.fHeight));
-            layer->setTexture(fAtlas->getTexture(), bounds);
-            layer->setPlot(plot);
-            return false;
+        for (int i = 0; i < 2; ++i) { // extra pass in case we fail to add but are able to purge
+            GrPlot* plot = fAtlas->addToAtlas(&pictInfo->fPlotUsage,
+                                              desc.fWidth, desc.fHeight,
+                                              NULL, &loc);
+            // addToAtlas can allocate the backing texture
+            SkDEBUGCODE(avl.setBackingTexture(fAtlas->getTexture()));
+            if (NULL != plot) {
+                // The layer was successfully added to the atlas
+                GrIRect16 bounds = GrIRect16::MakeXYWH(loc.fX, loc.fY,
+                                                       SkToS16(desc.fWidth), 
+                                                       SkToS16(desc.fHeight));
+                layer->setTexture(fAtlas->getTexture(), bounds);
+                layer->setPlot(plot);
+                layer->setLocked(true);
+                fPlotLocks[layer->plot()->id()]++;
+                return false;
+            }
+
+            // The layer was rejected by the atlas (even though we know it is 
+            // plausibly atlas-able). See if a plot can be purged and try again.
+            if (!this->purgePlot()) {
+                break;  // We weren't able to purge any plots
+            }
         }
     }
 #endif
@@ -179,35 +202,69 @@
     // This can yield a lot of re-rendering
     layer->setTexture(fContext->lockAndRefScratchTexture(desc, GrContext::kApprox_ScratchTexMatch),
                       GrIRect16::MakeWH(SkToS16(desc.fWidth), SkToS16(desc.fHeight)));
+    layer->setLocked(true);
     return false;
 }
 
 void GrLayerCache::unlock(GrCachedLayer* layer) {
     SkDEBUGCODE(GrAutoValidateLayer avl(fAtlas->getTexture(), layer);)
 
-    if (NULL == layer || NULL == layer->texture()) {
+    if (NULL == layer || !layer->locked()) {
+        // invalid or not locked
         return;
     }
 
     if (layer->isAtlased()) {
-        SkASSERT(layer->texture() == fAtlas->getTexture());
+        const int plotID = layer->plot()->id();
 
-        GrPictureInfo* pictInfo = fPictureHash.find(layer->pictureID());
-        SkASSERT(NULL != pictInfo);
-        pictInfo->fPlotUsage.isEmpty(); // just to silence compiler warnings for the time being
-
-        // TODO: purging from atlas goes here
+        SkASSERT(fPlotLocks[plotID] > 0);
+        fPlotLocks[plotID]--;
+        // At this point we could aggressively clear out un-locked plots but
+        // by delaying we may be able to reuse some of the atlased layers later.
     } else {
         fContext->unlockScratchTexture(layer->texture());
         layer->setTexture(NULL, GrIRect16::MakeEmpty());
     }
+
+    layer->setLocked(false);
 }
 
 #ifdef SK_DEBUG
 void GrLayerCache::validate() const {
+    int plotLocks[kNumPlotsX * kNumPlotsY];
+    memset(plotLocks, 0, sizeof(plotLocks));
+
     SkTDynamicHash<GrCachedLayer, GrCachedLayer::Key>::ConstIter iter(&fLayerHash);
     for (; !iter.done(); ++iter) {
-        (*iter).validate(fAtlas->getTexture());
+        const GrCachedLayer* layer = &(*iter);
+
+        layer->validate(fAtlas->getTexture());
+
+        const GrPictureInfo* pictInfo = fPictureHash.find(layer->pictureID());
+        if (NULL != pictInfo) {
+            // In aggressive cleanup mode a picture info should only exist if
+            // it has some atlased layers
+            SkASSERT(!pictInfo->fPlotUsage.isEmpty());
+        } else {
+            // If there is no picture info for this layer then all of its
+            // layers should be non-atlased.
+            SkASSERT(!layer->isAtlased());
+        }
+
+        if (NULL != layer->plot()) {
+            SkASSERT(NULL != pictInfo);
+            SkASSERT(pictInfo->fPictureID == layer->pictureID());
+
+            SkASSERT(pictInfo->fPlotUsage.contains(layer->plot()));
+
+            if (layer->locked()) {
+                plotLocks[layer->plot()->id()]++;
+            }
+        } 
+    }
+
+    for (int i = 0; i < kNumPlotsX*kNumPlotsY; ++i) {
+        SkASSERT(plotLocks[i] == fPlotLocks[i]);
     }
 }
 
@@ -252,6 +309,53 @@
     }
 }
 
+bool GrLayerCache::purgePlot() {
+    SkDEBUGCODE(GrAutoValidateCache avc(this);)
+
+    GrAtlas::PlotIter iter;
+    GrPlot* plot;
+    for (plot = fAtlas->iterInit(&iter, GrAtlas::kLRUFirst_IterOrder);
+         NULL != plot;
+         plot = iter.prev()) {
+        if (fPlotLocks[plot->id()] > 0) {
+            continue;
+        }
+
+        // We need to find all the layers in 'plot' and remove them.
+        SkTDArray<GrCachedLayer*> toBeRemoved;
+
+        SkTDynamicHash<GrCachedLayer, GrCachedLayer::Key>::Iter iter(&fLayerHash);
+        for (; !iter.done(); ++iter) {
+            if (plot == (*iter).plot()) {
+                *toBeRemoved.append() = &(*iter);
+            }
+        }
+
+        for (int i = 0; i < toBeRemoved.count(); ++i) {
+            SkASSERT(!toBeRemoved[i]->locked());
+
+            GrPictureInfo* pictInfo = fPictureHash.find(toBeRemoved[i]->pictureID());
+            SkASSERT(NULL != pictInfo);
+
+            GrAtlas::RemovePlot(&pictInfo->fPlotUsage, plot);
+
+            // Aggressively remove layers and, if now totally uncached, picture info
+            fLayerHash.remove(GrCachedLayer::GetKey(*toBeRemoved[i]));
+            SkDELETE(toBeRemoved[i]);
+
+            if (pictInfo->fPlotUsage.isEmpty()) {
+                fPictureHash.remove(pictInfo->fPictureID);
+                SkDELETE(pictInfo);
+            }
+        }
+
+        plot->resetRects();
+        return true;
+    }
+
+    return false;
+}
+
 class GrPictureDeletionListener : public SkPicture::DeletionListener {
     virtual void onDeletion(uint32_t pictureID) SK_OVERRIDE{
         const GrPictureDeletedMessage message = { pictureID };
diff --git a/src/gpu/GrLayerCache.h b/src/gpu/GrLayerCache.h
index 0fdd6c9..e66d333 100644
--- a/src/gpu/GrLayerCache.h
+++ b/src/gpu/GrLayerCache.h
@@ -10,7 +10,6 @@
 
 #define USE_ATLAS 0
 
-#include "GrAllocPool.h"
 #include "GrAtlas.h"
 #include "GrPictureUtils.h"
 #include "GrRect.h"
@@ -47,6 +46,9 @@
 // get a ref to the GrTexture in which they reside. In both cases 'fRect' 
 // contains the layer's extent in its texture.
 // Atlased layers also get a pointer to the plot in which they reside.
+// For non-atlased layers the lock field just corresponds to locking in
+// the resource cache. For atlased layers it implements an additional level
+// of locking to allow atlased layers to be reused multiple times.
 struct GrCachedLayer {
 public:
     // For SkTDynamicHash
@@ -77,7 +79,8 @@
         : fKey(pictureID, layerID)
         , fTexture(NULL)
         , fRect(GrIRect16::MakeEmpty())
-        , fPlot(NULL) {
+        , fPlot(NULL)
+        , fLocked(false) {
         SkASSERT(SK_InvalidGenID != pictureID && layerID >= 0);
     }
 
@@ -104,14 +107,17 @@
 
     bool isAtlased() const { return NULL != fPlot; }
 
+    void setLocked(bool locked) { fLocked = locked; }
+    bool locked() const { return fLocked; }
+
+    SkDEBUGCODE(const GrPlot* plot() const { return fPlot; })
     SkDEBUGCODE(void validate(const GrTexture* backingTexture) const;)
 
 private:
     const Key       fKey;
 
     // fTexture is a ref on the atlasing texture for atlased layers and a
-    // ref on a GrTexture for non-atlased textures. In both cases, if this is
-    // non-NULL, that means that the texture is locked in the texture cache.
+    // ref on a GrTexture for non-atlased textures.
     GrTexture*      fTexture;
 
     // For both atlased and non-atlased layers 'fRect' contains the  bound of
@@ -122,6 +128,14 @@
     // For atlased layers, fPlot stores the atlas plot in which the layer rests.
     // It is always NULL for non-atlased layers.
     GrPlot*         fPlot;
+
+    // For non-atlased layers 'fLocked' should always match "NULL != fTexture".
+    // (i.e., if there is a texture it is locked).
+    // For atlased layers, 'fLocked' is true if the layer is in a plot and
+    // actively required for rendering. If the layer is in a plot but not
+    // actively required for rendering, then 'fLocked' is false. If the
+    // layer isn't in a plot then is can never be locked.
+    bool            fLocked;
 };
 
 // The GrLayerCache caches pre-computed saveLayers for later rendering.
@@ -161,9 +175,15 @@
     SkDEBUGCODE(void validate() const;)
 
 private:
+    static const int kAtlasTextureWidth = 1024;
+    static const int kAtlasTextureHeight = 1024;
+
     static const int kNumPlotsX = 2;
     static const int kNumPlotsY = 2;
 
+    static const int kPlotWidth = kAtlasTextureWidth / kNumPlotsX;
+    static const int kPlotHeight = kAtlasTextureHeight / kNumPlotsY;
+
     GrContext*                fContext;  // pointer back to owning context
     SkAutoTDelete<GrAtlas>    fAtlas;    // TODO: could lazily allocate
 
@@ -180,12 +200,28 @@
 
     SkAutoTUnref<SkPicture::DeletionListener> fDeletionListener;
 
+    // This implements a plot-centric locking mechanism (since the atlas
+    // backing texture is always locked). Each layer that is locked (i.e.,
+    // needed for the current rendering) in a plot increments the plot lock
+    // count for that plot. Similarly, once a rendering is complete all the
+    // layers used in it decrement the lock count for the used plots.
+    // Plots with a 0 lock count are open for recycling/purging.
+    int fPlotLocks[kNumPlotsX * kNumPlotsY];
+
     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);
 
+    static bool PlausiblyAtlasable(int width, int height) {
+        return width <= kPlotWidth && height <= kPlotHeight;
+    }
+
+    // Try to find a purgeable plot and clear it out. Return true if a plot
+    // was purged; false otherwise.
+    bool purgePlot();
+
     // for testing
     friend class TestingAccess;
     int numLayers() const { return fLayerHash.count(); }
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index c35ff41..0abf9d8 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -38,6 +38,7 @@
 #include "SkTLazy.h"
 #include "SkUtils.h"
 #include "SkVertState.h"
+#include "SkXfermode.h"
 #include "SkErrorInternals.h"
 
 #define CACHE_COMPATIBLE_DEVICE_TEXTURES 1
@@ -2023,6 +2024,7 @@
                     // TODO: ensure none of the atlased layers contain a clear call!
                     SkPaint paint;
                     paint.setColor(SK_ColorTRANSPARENT);
+                    paint.setXfermode(SkXfermode::Create(SkXfermode::kSrc_Mode))->unref();
                     canvas->drawRect(bound, paint);
                 } else {
                     canvas->clear(SK_ColorTRANSPARENT);