Revert "Revert "Add a new GrResourceCache purging mechanism for purging unused resources.""

This reverts commit 20c322ef0cd04cf8e2592879d05d9f4e6cb19596.

Change-Id: I6df9a8594484837672308dc2c21c7c29b76ffa2c
Reviewed-on: https://skia-review.googlesource.com/10013
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 959a0fa..ea4f422 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -160,6 +160,12 @@
      */
     void purgeAllUnlockedResources();
 
+    /**
+     * Purge GPU resources that haven't been used in the past 'ms' milliseconds, regardless of
+     * whether the context is currently under budget.
+     */
+    void purgeResourcesNotUsedInMs(std::chrono::milliseconds ms);
+
     /** Access the context capabilities */
     const GrCaps* caps() const { return fCaps; }
 
diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h
index e0a7903..2d84c63 100644
--- a/include/gpu/GrGpuResource.h
+++ b/include/gpu/GrGpuResource.h
@@ -315,28 +315,30 @@
     void makeUnbudgeted();
 
 #ifdef SK_DEBUG
-    friend class GrGpu; // for assert in GrGpu to access getGpu
+    friend class GrGpu;  // for assert in GrGpu to access getGpu
 #endif
+
     // An index into a heap when this resource is purgeable or an array when not. This is maintained
     // by the cache.
-    int                         fCacheArrayIndex;
+    int fCacheArrayIndex;
     // This value reflects how recently this resource was accessed in the cache. This is maintained
     // by the cache.
-    uint32_t                    fTimestamp;
-    uint32_t                    fExternalFlushCntWhenBecamePurgeable;
+    uint32_t fTimestamp;
+    uint32_t fExternalFlushCntWhenBecamePurgeable;
+    GrStdSteadyClock::time_point fTimeWhenBecamePurgeable;
 
     static const size_t kInvalidGpuMemorySize = ~static_cast<size_t>(0);
-    GrScratchKey                fScratchKey;
-    GrUniqueKey                 fUniqueKey;
+    GrScratchKey fScratchKey;
+    GrUniqueKey fUniqueKey;
 
     // This is not ref'ed but abandon() or release() will be called before the GrGpu object
     // is destroyed. Those calls set will this to NULL.
-    GrGpu*                      fGpu;
-    mutable size_t              fGpuMemorySize;
+    GrGpu* fGpu;
+    mutable size_t fGpuMemorySize;
 
-    SkBudgeted                  fBudgeted;
-    bool                        fRefsWrappedObjects;
-    const UniqueID              fUniqueID;
+    SkBudgeted fBudgeted;
+    bool fRefsWrappedObjects;
+    const UniqueID fUniqueID;
 
     typedef GrIORef<GrGpuResource> INHERITED;
     friend class GrIORef<GrGpuResource>; // to access notifyAllCntsAreZero and notifyRefCntIsZero.
diff --git a/include/gpu/GrTypesPriv.h b/include/gpu/GrTypesPriv.h
index 28c3335..5db1c24 100644
--- a/include/gpu/GrTypesPriv.h
+++ b/include/gpu/GrTypesPriv.h
@@ -8,9 +8,19 @@
 #ifndef GrTypesPriv_DEFINED
 #define GrTypesPriv_DEFINED
 
+#include <chrono>
 #include "GrTypes.h"
 #include "SkRefCnt.h"
 
+// The old libstdc++ uses the draft name "monotonic_clock" rather than "steady_clock". This might
+// not actually be monotonic, depending on how libstdc++ was built. However, this is only currently
+// used for idle resource purging so it shouldn't cause a correctness problem.
+#if defined(__GLIBCXX__) && (__GLIBCXX__ < 20130000)
+using GrStdSteadyClock = std::chrono::monotonic_clock;
+#else
+using GrStdSteadyClock = std::chrono::steady_clock;
+#endif
+
 /** This enum indicates the type of antialiasing to be performed. */
 enum class GrAAType : unsigned {
     /** No antialiasing */
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 93fd324..56d8117 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -193,6 +193,11 @@
     fResourceCache->purgeAllUnlocked();
 }
 
+void GrContext::purgeResourcesNotUsedInMs(std::chrono::milliseconds ms) {
+    ASSERT_SINGLE_OWNER
+    fResourceCache->purgeResourcesNotUsedSince(GrStdSteadyClock::now() - ms);
+}
+
 void GrContext::getResourceCacheUsage(int* resourceCount, size_t* resourceBytes) const {
     ASSERT_SINGLE_OWNER
 
diff --git a/src/gpu/GrGpuResourceCacheAccess.h b/src/gpu/GrGpuResourceCacheAccess.h
index e91f899..cfc18e7 100644
--- a/src/gpu/GrGpuResourceCacheAccess.h
+++ b/src/gpu/GrGpuResourceCacheAccess.h
@@ -63,6 +63,10 @@
         SkASSERT(fResource->isPurgeable());
         fResource->fExternalFlushCntWhenBecamePurgeable = cnt;
     }
+    void setTimeWhenResourceBecomePurgeable() {
+        SkASSERT(fResource->isPurgeable());
+        fResource->fTimeWhenBecamePurgeable = GrStdSteadyClock::now();
+    }
     /**
      * Called by the cache to determine whether this resource has been puregable for more than
      * a threshold number of external flushes.
@@ -71,6 +75,14 @@
         SkASSERT(fResource->isPurgeable());
         return fResource->fExternalFlushCntWhenBecamePurgeable;
     }
+    /**
+     * Called by the cache to determine whether this resource should be purged based on the length
+     * of time it has been available for purging.
+     */
+    GrStdSteadyClock::time_point timeWhenResourceBecamePurgeable() {
+        SkASSERT(fResource->isPurgeable());
+        return fResource->fTimeWhenBecamePurgeable;
+    }
 
     int* accessCacheIndex() const { return &fResource->fCacheArrayIndex; }
 
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 9462a73..596af6d 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -365,6 +365,7 @@
     this->removeFromNonpurgeableArray(resource);
     fPurgeableQueue.insert(resource);
     resource->cacheAccess().setFlushCntWhenResourceBecamePurgeable(fExternalFlushCnt);
+    resource->cacheAccess().setTimeWhenResourceBecomePurgeable();
 
     if (SkBudgeted::kNo == resource->resourcePriv().isBudgeted()) {
         // Check whether this resource could still be used as a scratch resource.
@@ -504,6 +505,24 @@
     this->validate();
 }
 
+void GrResourceCache::purgeResourcesNotUsedSince(GrStdSteadyClock::time_point purgeTime) {
+    while (fPurgeableQueue.count()) {
+        const GrStdSteadyClock::time_point resourceTime =
+                fPurgeableQueue.peek()->cacheAccess().timeWhenResourceBecamePurgeable();
+        if (resourceTime >= purgeTime) {
+            // Resources were given both LRU timestamps and tagged with a frame number 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 frame number >= to this
+            // one.
+            break;
+        }
+        GrGpuResource* resource = fPurgeableQueue.peek();
+        SkASSERT(resource->isPurgeable());
+        resource->cacheAccess().release();
+    }
+}
+
 void GrResourceCache::processInvalidUniqueKeys(
     const SkTArray<GrUniqueKeyInvalidatedMessage>& msgs) {
     for (int i = 0; i < msgs.count(); ++i) {
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index 5f08a51..d871c9a 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -50,8 +50,9 @@
     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 external flushes a budgeted resources can go unused in the cache before it 
-    // is purged. Using a value <= 0 disables this feature.
+    // 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. This will be removed once Chrome
+    // starts using time-based purging.
     static const int    kDefaultMaxUnusedFlushes =
             1  * /* flushes per frame */
             60 * /* fps */
@@ -159,6 +160,9 @@
     /** Purges all resources that don't have external owners. */
     void purgeAllUnlocked();
 
+    /** Purge all resources not used since the passed in time. */
+    void purgeResourcesNotUsedSince(GrStdSteadyClock::time_point);
+
     /** Returns true if the cache would like a flush to occur in order to make more resources
         purgeable. */
     bool requestsFlush() const { return fRequestFlush; }
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index 9573161..a7a2b45 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -9,7 +9,7 @@
 #include "SkTypes.h"
 
 #if SK_SUPPORT_GPU
-
+#include <thread>
 #include "GrContext.h"
 #include "GrContextFactory.h"
 #include "GrGpu.h"
@@ -1241,6 +1241,108 @@
     REPORTER_ASSERT(reporter, 10 == cache->getResourceCount());
 }
 
+static void test_time_purge(skiatest::Reporter* reporter) {
+    Mock mock(1000000, 1000000);
+    GrContext* context = mock.context();
+    GrResourceCache* cache = mock.cache();
+
+    static constexpr int kCnts[] = {1, 10, 1024};
+    auto nowish = []() {
+        // We sleep so that we ensure we get a value that is greater than the last call to
+        // GrStdSteadyClock::now().
+        std::this_thread::sleep_for(GrStdSteadyClock::duration(5));
+        auto result = GrStdSteadyClock::now();
+        // Also sleep afterwards so we don't get this value again.
+        std::this_thread::sleep_for(GrStdSteadyClock::duration(5));
+        return result;
+    };
+
+    for (int cnt : kCnts) {
+        std::unique_ptr<GrStdSteadyClock::time_point[]> timeStamps(
+                new GrStdSteadyClock::time_point[cnt]);
+        {
+            // Insert resources and get time points between each addition.
+            for (int i = 0; i < cnt; ++i) {
+                TestResource* r = new TestResource(context->getGpu());
+                GrUniqueKey k;
+                make_unique_key<1>(&k, i);
+                r->resourcePriv().setUniqueKey(k);
+                r->unref();
+                timeStamps.get()[i] = nowish();
+            }
+
+            // Purge based on the time points between resource additions. Each purge should remove
+            // the oldest resource.
+            for (int i = 0; i < cnt; ++i) {
+                cache->purgeResourcesNotUsedSince(timeStamps[i]);
+                REPORTER_ASSERT(reporter, cnt - i - 1 == cache->getResourceCount());
+                for (int j = 0; j < i; ++j) {
+                    GrUniqueKey k;
+                    make_unique_key<1>(&k, j);
+                    GrGpuResource* r = cache->findAndRefUniqueResource(k);
+                    REPORTER_ASSERT(reporter, !SkToBool(r));
+                    SkSafeUnref(r);
+                }
+            }
+
+            REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+            cache->purgeAllUnlocked();
+        }
+
+        // Do a similar test but where we leave refs on some resources to prevent them from being
+        // purged.
+        {
+            std::unique_ptr<GrGpuResource* []> refedResources(new GrGpuResource*[cnt / 2]);
+            for (int i = 0; i < cnt; ++i) {
+                TestResource* r = new TestResource(context->getGpu());
+                GrUniqueKey k;
+                make_unique_key<1>(&k, i);
+                r->resourcePriv().setUniqueKey(k);
+                // Leave a ref on every other resource, beginning with the first.
+                if (SkToBool(i & 0x1)) {
+                    refedResources.get()[i / 2] = r;
+                } else {
+                    r->unref();
+                }
+                timeStamps.get()[i] = nowish();
+            }
+
+            for (int i = 0; i < cnt; ++i) {
+                // Should get a resource purged every other frame.
+                cache->purgeResourcesNotUsedSince(timeStamps[i]);
+                REPORTER_ASSERT(reporter, cnt - i / 2 - 1 == cache->getResourceCount());
+            }
+
+            // Unref all the resources that we kept refs on in the first loop.
+            for (int i = 0; i < (cnt / 2); ++i) {
+                refedResources.get()[i]->unref();
+                cache->purgeResourcesNotUsedSince(nowish());
+                REPORTER_ASSERT(reporter, cnt / 2 - i - 1 == cache->getResourceCount());
+            }
+
+            cache->purgeAllUnlocked();
+        }
+
+        REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+
+        // Verify that calling flush() on a GrContext with nothing to do will not trigger resource
+        // eviction
+        context->flush();
+        for (int i = 0; i < 10; ++i) {
+            TestResource* r = new TestResource(context->getGpu());
+            GrUniqueKey k;
+            make_unique_key<1>(&k, i);
+            r->resourcePriv().setUniqueKey(k);
+            r->unref();
+        }
+        REPORTER_ASSERT(reporter, 10 == cache->getResourceCount());
+        context->flush();
+        REPORTER_ASSERT(reporter, 10 == cache->getResourceCount());
+        cache->purgeResourcesNotUsedSince(nowish());
+        REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+    }
+}
+
 static void test_large_resource_count(skiatest::Reporter* reporter) {
     // Set the cache size to double the resource count because we're going to create 2x that number
     // resources, using two different key domains. Add a little slop to the bytes because we resize
@@ -1393,6 +1495,7 @@
     test_resource_size_changed(reporter);
     test_timestamp_wrap(reporter);
     test_flush(reporter);
+    test_time_purge(reporter);
     test_large_resource_count(reporter);
     test_custom_data(reporter);
     test_abandoned(reporter);