Change implementation of flush-count based GrGpuResource purging

Change default to approx 30seconds (given some API usage assumptions)
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2361093002

Review-Url: https://codereview.chromium.org/2361093002
diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h
index 9305c16..46e48fc 100644
--- a/include/gpu/GrGpuResource.h
+++ b/include/gpu/GrGpuResource.h
@@ -287,6 +287,7 @@
     // This value reflects how recently this resource was accessed in the cache. This is maintained
     // by the cache.
     uint32_t                    fTimestamp;
+    uint32_t                    fExternalFlushCntWhenBecamePurgeable;
 
     static const size_t kInvalidGpuMemorySize = ~static_cast<size_t>(0);
     GrScratchKey                fScratchKey;
diff --git a/src/gpu/GrGpuResource.cpp b/src/gpu/GrGpuResource.cpp
index e64339b..c1578b5 100644
--- a/src/gpu/GrGpuResource.cpp
+++ b/src/gpu/GrGpuResource.cpp
@@ -20,7 +20,8 @@
 }
 
 GrGpuResource::GrGpuResource(GrGpu* gpu)
-    : fGpu(gpu)
+    : fExternalFlushCntWhenBecamePurgeable(0)
+    , fGpu(gpu)
     , fGpuMemorySize(kInvalidGpuMemorySize)
     , fBudgeted(SkBudgeted::kNo)
     , fRefsWrappedObjects(false)
diff --git a/src/gpu/GrGpuResourceCacheAccess.h b/src/gpu/GrGpuResourceCacheAccess.h
index b09dcc1..e91f899 100644
--- a/src/gpu/GrGpuResourceCacheAccess.h
+++ b/src/gpu/GrGpuResourceCacheAccess.h
@@ -58,6 +58,20 @@
     uint32_t timestamp() const { return fResource->fTimestamp; }
     void setTimestamp(uint32_t ts) { fResource->fTimestamp = ts; }
 
+    /** Called by the cache to record when this became purgeable. */
+    void setFlushCntWhenResourceBecamePurgeable(uint32_t cnt) {
+        SkASSERT(fResource->isPurgeable());
+        fResource->fExternalFlushCntWhenBecamePurgeable = cnt;
+    }
+    /**
+     * Called by the cache to determine whether this resource has been puregable for more than
+     * a threshold number of external flushes.
+     */
+    uint32_t flushCntWhenResourceBecamePurgeable() {
+        SkASSERT(fResource->isPurgeable());
+        return fResource->fExternalFlushCntWhenBecamePurgeable;
+    }
+
     int* accessCacheIndex() const { return &fResource->fCacheArrayIndex; }
 
     CacheAccess(GrGpuResource* resource) : fResource(resource) {}
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 529f87c..9462a73 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -74,49 +74,23 @@
     , fBudgetedCount(0)
     , fBudgetedBytes(0)
     , fRequestFlush(false)
-    , fFlushTimestamps(nullptr)
-    , fLastFlushTimestampIndex(0)
+    , fExternalFlushCnt(0)
     , fPreferVRAMUseOverFlushes(caps->preferVRAMUseOverFlushes()) {
     SkDEBUGCODE(fCount = 0;)
     SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr;)
-    this->resetFlushTimestamps();
 }
 
 GrResourceCache::~GrResourceCache() {
     this->releaseAll();
-    delete[] fFlushTimestamps;
 }
 
 void GrResourceCache::setLimits(int count, size_t bytes, int maxUnusedFlushes) {
     fMaxCount = count;
     fMaxBytes = bytes;
     fMaxUnusedFlushes = maxUnusedFlushes;
-    this->resetFlushTimestamps();
     this->purgeAsNeeded();
 }
 
-void GrResourceCache::resetFlushTimestamps() {
-    delete[] 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 = nullptr;
-        return;
-    }
-
-    fFlushTimestamps = new 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));
@@ -390,6 +364,7 @@
     SkASSERT(resource->isPurgeable());
     this->removeFromNonpurgeableArray(resource);
     fPurgeableQueue.insert(resource);
+    resource->cacheAccess().setFlushCntWhenResourceBecamePurgeable(fExternalFlushCnt);
 
     if (SkBudgeted::kNo == resource->resourcePriv().isBudgeted()) {
         // Check whether this resource could still be used as a scratch resource.
@@ -474,20 +449,29 @@
         this->processInvalidUniqueKeys(invalidKeyMsgs);
     }
 
-    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;
+    if (fMaxUnusedFlushes > 0) {
+        // We want to know how many complete flushes have occurred without the resource being used.
+        // If the resource was tagged when fExternalFlushCnt was N then this means it became
+        // purgeable during activity that became the N+1th flush. So when the flush count is N+2
+        // it has sat in the purgeable queue for one entire flush.
+        uint32_t oldestAllowedFlushCnt = fExternalFlushCnt - fMaxUnusedFlushes - 1;
+        // check for underflow
+        if (oldestAllowedFlushCnt < fExternalFlushCnt) {
+            while (fPurgeableQueue.count()) {
+                uint32_t flushWhenResourceBecamePurgeable =
+                        fPurgeableQueue.peek()->cacheAccess().flushCntWhenResourceBecamePurgeable();
+                if (oldestAllowedFlushCnt < flushWhenResourceBecamePurgeable) {
+                    // Resources were given both LRU timestamps and tagged with a flush cnt when
+                    // they first became purgeable. The LRU timestamp won't change again until the
+                    // resource is made non-purgeable again. So, at this point all the remaining
+                    // resources in the timestamp-sorted queue will have a flush count >= to this
+                    // one.
+                    break;
+                }
+                GrGpuResource* resource = fPurgeableQueue.peek();
+                SkASSERT(resource->isPurgeable());
+                resource->cacheAccess().release();
             }
-            GrGpuResource* resource = fPurgeableQueue.peek();
-            SkASSERT(resource->isPurgeable());
-            resource->cacheAccess().release();
         }
     }
 
@@ -566,13 +550,8 @@
                 fPurgeableQueue.pop();
             }
 
-            struct Less {
-                bool operator()(GrGpuResource* a, GrGpuResource* b) {
-                    return CompareTimestamp(a,b);
-                }
-            };
-            Less less;
-            SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end() - 1, less);
+            SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end() - 1,
+                     CompareTimestamp);
 
             // Pick resources out of the purgeable and non-purgeable arrays based on lowest
             // timestamp and assign new timestamps.
@@ -611,9 +590,6 @@
 
             // count should be the next timestamp we return.
             SkASSERT(fTimestamp == SkToU32(count));
-
-            // The historical timestamps of flushes are now invalid.
-            this->resetFlushTimestamps();
         }
     }
     return fTimestamp++;
@@ -628,13 +604,12 @@
             fRequestFlush = false;
             break;
         case FlushType::kExternal:
-            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;
+            ++fExternalFlushCnt;
+            if (0 == fExternalFlushCnt) {
+                // When this wraps just reset all the purgeable resources' last used flush state.
+                for (int i = 0; i < fPurgeableQueue.count(); ++i) {
+                    fPurgeableQueue.at(i)->cacheAccess().setFlushCntWhenResourceBecamePurgeable(0);
+                }
             }
             break;
     }
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index bf7b237..ae9a4e7 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -40,12 +40,6 @@
  * 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:
@@ -56,11 +50,12 @@
     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    = 64;
+    // Default number of external flushes a budgeted resources can go unused in the cache before it 
+    // is purged. Using a value <= 0 disables this feature.
+    static const int    kDefaultMaxUnusedFlushes =
+            1  * /* flushes per frame */
+            60 * /* fps */
+            30;  /* seconds */
 
     /** Used to access functionality needed by GrGpuResource for lifetime management. */
     class ResourceAccess;
@@ -68,9 +63,9 @@
 
     /**
      * 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.
+     * of external 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, int maxUnusedFlushes = kDefaultMaxUnusedFlushes);
 
@@ -237,7 +232,6 @@
     void refAndMakeResourceMRU(GrGpuResource*);
     /// @}
 
-    void resetFlushTimestamps();
     void processInvalidUniqueKeys(const SkTArray<GrUniqueKeyInvalidatedMessage>&);
     void addToNonpurgeableArray(GrGpuResource*);
     void removeFromNonpurgeableArray(GrGpuResource*);
@@ -321,11 +315,7 @@
     size_t                              fBudgetedBytes;
 
     bool                                fRequestFlush;
-
-    // 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;
+    uint32_t                            fExternalFlushCnt;
 
     InvalidUniqueKeyInbox               fInvalidUniqueKeyInbox;
 
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index b568485..3176366 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -1137,8 +1137,8 @@
         }
 
         // Send flush notifications to the cache. Each flush should purge the oldest resource.
-        for (int i = 0; i < kFlushCount - 1; ++i) {
-            // The first resource was purged after the last flush in the initial loop, hence the -1.
+        for (int i = 0; i < kFlushCount; ++i) {
+            cache->notifyFlushOccurred(GrResourceCache::kExternal);
             REPORTER_ASSERT(reporter, kFlushCount - i - 1 == cache->getResourceCount());
             for (int j = 0; j < i; ++j) {
                 GrUniqueKey k;
@@ -1147,7 +1147,6 @@
                 REPORTER_ASSERT(reporter, !SkToBool(r));
                 SkSafeUnref(r);
             }
-            cache->notifyFlushOccurred(GrResourceCache::kExternal);
         }
 
         REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
@@ -1174,8 +1173,8 @@
 
         for (int i = 0; i < kFlushCount; ++i) {
             // Should get a resource purged every other flush.
-            REPORTER_ASSERT(reporter, kFlushCount - i/2 - 1 == cache->getResourceCount());
             cache->notifyFlushOccurred(GrResourceCache::kExternal);
+            REPORTER_ASSERT(reporter, kFlushCount - i/2 - 1 == cache->getResourceCount());
         }
 
         // Unref all the resources that we kept refs on in the first loop.
@@ -1183,9 +1182,9 @@
             refedResources[i]->unref();
         }
 
-        // When we unref'ed them their timestamps got updated. So nothing should be purged until we
-        // get kFlushCount additional flushes. Then everything should be purged.
-        for (int i = 0; i < kFlushCount; ++i) {
+        // After kFlushCount + 1 flushes they all will have sat in the purgeable queue for
+        // kFlushCount full flushes.
+        for (int i = 0; i < kFlushCount + 1; ++i) {
             REPORTER_ASSERT(reporter, kFlushCount >> 1 == cache->getResourceCount());
             cache->notifyFlushOccurred(GrResourceCache::kExternal);
         }