Add mechanism to proactively purge old resources in GrResourceCache.

This change leaves the feature turned off by default.

Review URL: https://codereview.chromium.org/1032873002
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 89429f1..48e50b7 100755
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -1477,6 +1477,7 @@
     } else {
         fDrawBuffer->flush();
     }
+    fResourceCache->notifyFlushOccurred();
     fFlushToReduceCacheSize = false;
 }
 
diff --git a/src/gpu/GrGpuResource.cpp b/src/gpu/GrGpuResource.cpp
index a2fc7b3..52fe1e5 100644
--- a/src/gpu/GrGpuResource.cpp
+++ b/src/gpu/GrGpuResource.cpp
@@ -105,14 +105,39 @@
     get_resource_cache(fGpu)->resourceAccess().changeUniqueKey(this, key);
 }
 
-void GrGpuResource::notifyIsPurgeable() const {
+void GrGpuResource::notifyAllCntsAreZero(CntType lastCntTypeToReachZero) const {
     if (this->wasDestroyed()) {
         // We've already been removed from the cache. Goodbye cruel world!
         SkDELETE(this);
-    } else {
-        GrGpuResource* mutableThis = const_cast<GrGpuResource*>(this);
-        get_resource_cache(fGpu)->resourceAccess().notifyPurgeable(mutableThis);
+        return;
     }
+
+    // We should have already handled this fully in notifyRefCntIsZero().
+    SkASSERT(kRef_CntType != lastCntTypeToReachZero);
+
+    GrGpuResource* mutableThis = const_cast<GrGpuResource*>(this);
+    static const uint32_t kFlag =
+        GrResourceCache::ResourceAccess::kAllCntsReachedZero_RefNotificationFlag;
+    get_resource_cache(fGpu)->resourceAccess().notifyCntReachedZero(mutableThis, kFlag);
+}
+
+bool GrGpuResource::notifyRefCountIsZero() const {
+    if (this->wasDestroyed()) {
+        // handle this in notifyAllCntsAreZero().
+        return true;
+    }
+
+    GrGpuResource* mutableThis = const_cast<GrGpuResource*>(this);
+    uint32_t flags =
+        GrResourceCache::ResourceAccess::kRefCntReachedZero_RefNotificationFlag;
+    if (!this->internalHasPendingIO()) {
+        flags |= GrResourceCache::ResourceAccess::kAllCntsReachedZero_RefNotificationFlag;
+    }
+    get_resource_cache(fGpu)->resourceAccess().notifyCntReachedZero(mutableThis, flags);
+
+    // There is no need to call our notifyAllCntsAreZero function at this point since we already
+    // told the cache about the state of cnts.
+    return false;
 }
 
 void GrGpuResource::setScratchKey(const GrScratchKey& scratchKey) {
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index f8b1a12..f1a51b0 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -40,6 +40,7 @@
 
     return static_cast<Domain>(domain);
 }
+
 uint32_t GrResourceKeyHash(const uint32_t* data, size_t size) {
     return SkChecksum::Compute(data, size);
 }
@@ -56,13 +57,12 @@
 
  //////////////////////////////////////////////////////////////////////////////
 
-static const int kDefaultMaxCount = 2 * (1 << 12);
-static const size_t kDefaultMaxSize = 96 * (1 << 20);
 
 GrResourceCache::GrResourceCache()
     : fTimestamp(0)
     , fMaxCount(kDefaultMaxCount)
     , fMaxBytes(kDefaultMaxSize)
+    , fMaxUnusedFlushes(kDefaultMaxUnusedFlushes)
 #if GR_CACHE_STATS
     , fHighWaterCount(0)
     , fHighWaterBytes(0)
@@ -73,20 +73,49 @@
     , fBudgetedCount(0)
     , fBudgetedBytes(0)
     , fOverBudgetCB(NULL)
-    , fOverBudgetData(NULL) {
+    , fOverBudgetData(NULL)
+    , fFlushTimestamps(NULL)
+    , fLastFlushTimestampIndex(0){
     SkDEBUGCODE(fCount = 0;)
+    SkDEBUGCODE(fNewlyPurgeableResourceForValidation = NULL;)
+    this->resetFlushTimestamps();
 }
 
 GrResourceCache::~GrResourceCache() {
     this->releaseAll();
+    SkDELETE(fFlushTimestamps);
 }
 
-void GrResourceCache::setLimits(int count, size_t bytes) {
+void GrResourceCache::setLimits(int count, size_t bytes, int maxUnusedFlushes) {
     fMaxCount = count;
     fMaxBytes = bytes;
+    fMaxUnusedFlushes = maxUnusedFlushes;
+    this->resetFlushTimestamps();
     this->purgeAsNeeded();
 }
 
+void GrResourceCache::resetFlushTimestamps() {
+    SkDELETE(fFlushTimestamps);
+
+    // We assume this number is a power of two when wrapping indices into the timestamp array.
+    fMaxUnusedFlushes = SkNextPow2(fMaxUnusedFlushes);
+
+    // Since our implementation is to store the timestamps of the last fMaxUnusedFlushes flush calls
+    // we just turn the feature off if that array would be large.
+    static const int kMaxSupportedTimestampHistory = 128;
+
+    if (fMaxUnusedFlushes > kMaxSupportedTimestampHistory) {
+        fFlushTimestamps = NULL;
+        return;
+    }
+
+    fFlushTimestamps = SkNEW_ARRAY(uint32_t, fMaxUnusedFlushes);
+    fLastFlushTimestampIndex = 0;
+    // Set all the historical flush timestamps to initially be at the beginning of time (timestamp
+    // 0).
+    sk_bzero(fFlushTimestamps, fMaxUnusedFlushes * sizeof(uint32_t));
+}
+
 void GrResourceCache::insertResource(GrGpuResource* resource) {
     SkASSERT(resource);
     SkASSERT(!this->isInCache(resource));
@@ -247,8 +276,8 @@
 }
 
 void GrResourceCache::removeUniqueKey(GrGpuResource* resource) {
-    // Someone has a ref to this resource in order to invalidate it. When the ref count reaches
-    // zero we will get a notifyPurgable() and figure out what to do with it.
+    // Someone has a ref to this resource in order to have removed the key. When the ref count
+    // reaches zero we will get a ref cnt notification and figure out what to do with it.
     if (resource->getUniqueKey().isValid()) {
         SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey()));
         fUniqueHash.remove(resource->getUniqueKey());
@@ -307,11 +336,34 @@
     this->validate();
 }
 
-void GrResourceCache::notifyPurgeable(GrGpuResource* resource) {
+void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t flags) {
     SkASSERT(resource);
+    SkASSERT(!resource->wasDestroyed());
+    SkASSERT(flags);
     SkASSERT(this->isInCache(resource));
-    SkASSERT(resource->isPurgeable());
+    // This resource should always be in the nonpurgeable array when this function is called. It
+    // will be moved to the queue if it is newly purgeable.
+    SkASSERT(fNonpurgeableResources[*resource->cacheAccess().accessCacheIndex()] == resource);
 
+    if (SkToBool(ResourceAccess::kRefCntReachedZero_RefNotificationFlag & flags)) {
+#ifdef SK_DEBUG
+        // When the timestamp overflows validate() is called. validate() checks that resources in
+        // the nonpurgeable array are indeed not purgeable. However, the movement from the array to
+        // the purgeable queue happens just below in this function. So we mark it as an exception.
+        if (resource->isPurgeable()) {
+            fNewlyPurgeableResourceForValidation = resource;
+        }
+#endif
+        resource->cacheAccess().setTimestamp(this->getNextTimestamp());
+        SkDEBUGCODE(fNewlyPurgeableResourceForValidation = NULL);
+    }
+
+    if (!SkToBool(ResourceAccess::kAllCntsReachedZero_RefNotificationFlag & flags)) {
+        SkASSERT(!resource->isPurgeable());
+        return;
+    }
+
+    SkASSERT(resource->isPurgeable());
     this->removeFromNonpurgeableArray(resource);
     fPurgeableQueue.insert(resource);
 
@@ -391,25 +443,43 @@
     this->validate();
 }
 
-void GrResourceCache::internalPurgeAsNeeded() {
-    SkASSERT(this->overBudget());
+void GrResourceCache::purgeAsNeeded() {
+    SkTArray<GrUniqueKeyInvalidatedMessage> invalidKeyMsgs;
+    fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
+    if (invalidKeyMsgs.count()) {
+        this->processInvalidUniqueKeys(invalidKeyMsgs);
+    }
 
-    bool stillOverbudget = true;
-    while (fPurgeableQueue.count()) {
+    if (fFlushTimestamps) {
+        // Assuming kNumFlushesToDeleteUnusedResource is a power of 2.
+        SkASSERT(SkIsPow2(fMaxUnusedFlushes));
+        int oldestFlushIndex = (fLastFlushTimestampIndex + 1) & (fMaxUnusedFlushes - 1);
+
+        uint32_t oldestAllowedTimestamp = fFlushTimestamps[oldestFlushIndex];
+        while (fPurgeableQueue.count()) {
+            uint32_t oldestResourceTimestamp = fPurgeableQueue.peek()->cacheAccess().timestamp();
+            if (oldestAllowedTimestamp < oldestResourceTimestamp) {
+                break;
+            }
+            GrGpuResource* resource = fPurgeableQueue.peek();
+            SkASSERT(resource->isPurgeable());
+            resource->cacheAccess().release();
+        }
+    }
+
+    bool stillOverbudget = this->overBudget();
+    while (stillOverbudget && fPurgeableQueue.count()) {
         GrGpuResource* resource = fPurgeableQueue.peek();
         SkASSERT(resource->isPurgeable());
         resource->cacheAccess().release();
-        if (!this->overBudget()) {
-            stillOverbudget = false;
-            break;
-        }
+        stillOverbudget = this->overBudget();
     }
 
     this->validate();
 
     if (stillOverbudget) {
         // Despite the purge we're still over budget. Call our over budget callback. If this frees
-        // any resources then we'll get notifyPurgeable() calls and take appropriate action.
+        // any resources then we'll get notified and take appropriate action.
         (*fOverBudgetCB)(fOverBudgetData);
         this->validate();
     }
@@ -433,7 +503,7 @@
         GrGpuResource* resource = this->findAndRefUniqueResource(msgs[i].key());
         if (resource) {
             resource->resourcePriv().removeUniqueKey();
-            resource->unref(); // will call notifyPurgeable, if it is indeed now purgeable.
+            resource->unref(); // If this resource is now purgeable, the cache will be notified.
         }
     }
 }
@@ -518,11 +588,26 @@
 
             // count should be the next timestamp we return.
             SkASSERT(fTimestamp == SkToU32(count));
+            
+            // The historical timestamps of flushes are now invalid.
+            this->resetFlushTimestamps();
         }        
     }
     return fTimestamp++;
 }
 
+void GrResourceCache::notifyFlushOccurred() {
+    if (fFlushTimestamps) {
+        SkASSERT(SkIsPow2(fMaxUnusedFlushes));
+        fLastFlushTimestampIndex = (fLastFlushTimestampIndex + 1) & (fMaxUnusedFlushes - 1);
+        // get the timestamp before accessing fFlushTimestamps because getNextTimestamp will
+        // reallocate fFlushTimestamps on timestamp overflow.
+        uint32_t timestamp = this->getNextTimestamp();
+        fFlushTimestamps[fLastFlushTimestampIndex] = timestamp;
+        this->purgeAsNeeded();
+    }
+}
+
 #ifdef SK_DEBUG
 void GrResourceCache::validate() const {
     // Reduce the frequency of validations for large resource counts.
@@ -586,7 +671,8 @@
     Stats stats(this);
 
     for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
-        SkASSERT(!fNonpurgeableResources[i]->isPurgeable());
+        SkASSERT(!fNonpurgeableResources[i]->isPurgeable() ||
+                 fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
         SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
         SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
         stats.update(fNonpurgeableResources[i]);
@@ -615,7 +701,7 @@
     SkASSERT(stats.fContent == fUniqueHash.count());
     SkASSERT(stats.fScratch + stats.fCouldBeScratch == fScratchMap.count());
 
-    // This assertion is not currently valid because we can be in recursive notifyIsPurgeable()
+    // This assertion is not currently valid because we can be in recursive notifyCntReachedZero()
     // calls. This will be fixed when subresource registration is explicit.
     // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount;
     // SkASSERT(!overBudget || locked == count || fPurging);
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index 8331bf5..5483e19 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -38,20 +38,39 @@
  * A unique key always takes precedence over a scratch key when a resource has both types of keys.
  * If a resource has neither key type then it will be deleted as soon as the last reference to it
  * is dropped.
+ *
+ * When proactive purging is enabled, on every flush, the timestamp of that flush is stored in a
+ * n-sized ring buffer. When purging occurs each purgeable resource's timestamp is compared to the
+ * timestamp of the n-th prior flush. If the resource's last use timestamp is older than the old
+ * flush then the resource is proactively purged even when the cache is under budget. By default
+ * this feature is disabled, though it can be enabled by calling GrResourceCache::setLimits.
  */
 class GrResourceCache {
 public:
     GrResourceCache();
     ~GrResourceCache();
 
+    // Default maximum number of budgeted resources in the cache.
+    static const int    kDefaultMaxCount            = 2 * (1 << 12);
+    // Default maximum number of bytes of gpu memory of budgeted resources in the cache.
+    static const size_t kDefaultMaxSize             = 96 * (1 << 20);
+    // Default number of flushes a budgeted resources can go unused in the cache before it is
+    // purged. Large values disable the feature (as the ring buffer of flush timestamps would be
+    // large). This is currently the default until we decide to enable this feature
+    // of the cache by default.
+    static const int    kDefaultMaxUnusedFlushes    = 1024;
+
     /** Used to access functionality needed by GrGpuResource for lifetime management. */
     class ResourceAccess;
     ResourceAccess resourceAccess();
 
     /**
-     * Sets the cache limits in terms of number of resources and max gpu memory byte size.
+     * Sets the cache limits in terms of number of resources, max gpu memory byte size, and number
+     * of GrContext flushes that a resource can be unused before it is evicted. The latter value is
+     * a suggestion and there is no promise that a resource will be purged immediately after it
+     * hasn't been used in maxUnusedFlushes flushes.
      */
-    void setLimits(int count, size_t bytes);
+    void setLimits(int count, size_t bytes, int maxUnusedFlushes = kDefaultMaxUnusedFlushes);
 
     /**
      * Returns the number of resources.
@@ -136,17 +155,7 @@
 
     /** Purges resources to become under budget and processes resources with invalidated unique
         keys. */
-    void purgeAsNeeded() {
-        SkTArray<GrUniqueKeyInvalidatedMessage> invalidKeyMsgs;
-        fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
-        if (invalidKeyMsgs.count()) {
-            this->processInvalidUniqueKeys(invalidKeyMsgs);
-        }
-        if (fBudgetedCount <= fMaxCount && fBudgetedBytes <= fMaxBytes) {
-            return;
-        }
-        this->internalPurgeAsNeeded();
-    }
+    void purgeAsNeeded();
 
     /** Purges all resources that don't have external owners. */
     void purgeAllUnlocked();
@@ -166,6 +175,8 @@
         fOverBudgetCB = overBudgetCB;
         fOverBudgetData = data;
     }
+    
+    void notifyFlushOccurred();
 
 #if GR_GPU_STATS
     void dumpStats(SkString*) const;
@@ -180,7 +191,7 @@
     ////
     void insertResource(GrGpuResource*);
     void removeResource(GrGpuResource*);
-    void notifyPurgeable(GrGpuResource*);
+    void notifyCntReachedZero(GrGpuResource*, uint32_t flags);
     void didChangeGpuMemorySize(const GrGpuResource*, size_t oldSize);
     void changeUniqueKey(GrGpuResource*, const GrUniqueKey&);
     void removeUniqueKey(GrGpuResource*);
@@ -189,7 +200,7 @@
     void refAndMakeResourceMRU(GrGpuResource*);
     /// @}
 
-    void internalPurgeAsNeeded();
+    void resetFlushTimestamps();
     void processInvalidUniqueKeys(const SkTArray<GrUniqueKeyInvalidatedMessage>&);
     void addToNonpurgeableArray(GrGpuResource*);
     void removeFromNonpurgeableArray(GrGpuResource*);
@@ -251,6 +262,7 @@
     // our budget, used in purgeAsNeeded()
     int                                 fMaxCount;
     size_t                              fMaxBytes;
+    int                                 fMaxUnusedFlushes;
 
 #if GR_CACHE_STATS
     int                                 fHighWaterCount;
@@ -270,7 +282,16 @@
     PFOverBudgetCB                      fOverBudgetCB;
     void*                               fOverBudgetData;
 
+    // We keep track of the "timestamps" of the last n flushes. If a resource hasn't been used in
+    // that time then we well preemptively purge it to reduce memory usage.
+    uint32_t*                           fFlushTimestamps;
+    int                                 fLastFlushTimestampIndex;
+
     InvalidUniqueKeyInbox               fInvalidUniqueKeyInbox;
+
+    // This resource is allowed to be in the nonpurgeable array for the sake of validate() because
+    // we're in the midst of converting it to purgeable status.
+    SkDEBUGCODE(GrGpuResource*          fNewlyPurgeableResourceForValidation;)
 };
 
 class GrResourceCache::ResourceAccess {
@@ -290,9 +311,26 @@
     void removeResource(GrGpuResource* resource) { fCache->removeResource(resource); }
 
     /**
-     * Called by GrGpuResources when they detects that they are newly purgeable.
+     * Notifications that should be sent to the cache when the ref/io cnt status of resources
+     * changes.
      */
-    void notifyPurgeable(GrGpuResource* resource) { fCache->notifyPurgeable(resource); }
+    enum RefNotificationFlags {
+        /** All types of refs on the resource have reached zero. */
+        kAllCntsReachedZero_RefNotificationFlag = 0x1,
+        /** The normal (not pending IO type) ref cnt has reached zero. */
+        kRefCntReachedZero_RefNotificationFlag  = 0x2,
+    };
+    /**
+     * Called by GrGpuResources when they detect that their ref/io cnts have reached zero. When the
+     * normal ref cnt reaches zero the flags that are set should be:
+     *     a) kRefCntReachedZero if a pending IO cnt is still non-zero.
+     *     b) (kRefCntReachedZero | kAllCntsReachedZero) when all pending IO cnts are also zero.
+     * kAllCntsReachedZero is set by itself if a pending IO cnt is decremented to zero and all the
+     * the other cnts are already zero.
+     */
+    void notifyCntReachedZero(GrGpuResource* resource, uint32_t flags) {
+        fCache->notifyCntReachedZero(resource, flags);
+    }
 
     /**
      * Called by GrGpuResources when their sizes change.