Add scratch-only version of performDeferredCleanup

Change-Id: I5707d1da1b69ab1ffaa77d7a391a187ac3e8eba1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/417267
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/bench/GrResourceCacheBench.cpp b/bench/GrResourceCacheBench.cpp
index 6133c07..dc2fef8 100644
--- a/bench/GrResourceCacheBench.cpp
+++ b/bench/GrResourceCacheBench.cpp
@@ -79,7 +79,7 @@
         GrResourceCache* cache = context->priv().getResourceCache();
 
         // Make sure the cache is empty.
-        cache->purgeAllUnlocked();
+        cache->purgeUnlockedResources();
         SkASSERT(0 == cache->getResourceCount() && 0 == cache->getResourceBytes());
 
         GrGpu* gpu = context->priv().getGpu();
@@ -125,7 +125,7 @@
         GrResourceCache* cache = fContext->priv().getResourceCache();
 
         // Make sure the cache is empty.
-        cache->purgeAllUnlocked();
+        cache->purgeUnlockedResources();
         SkASSERT(0 == cache->getResourceCount() && 0 == cache->getResourceBytes());
 
         GrGpu* gpu = fContext->priv().getGpu();
diff --git a/bench/ImageCacheBudgetBench.cpp b/bench/ImageCacheBudgetBench.cpp
index a224cbf..5aa1621 100644
--- a/bench/ImageCacheBudgetBench.cpp
+++ b/bench/ImageCacheBudgetBench.cpp
@@ -11,6 +11,7 @@
 #include "include/core/SkSurface.h"
 #include "include/gpu/GrDirectContext.h"
 #include "src/gpu/GrDirectContextPriv.h"
+#include "src/gpu/GrResourceCache.h"
 #include "tools/ToolUtils.h"
 
 
@@ -45,7 +46,7 @@
     auto context =  canvas->recordingContext()->asDirectContext();
     SkASSERT(context);
     context->flushAndSubmit();
-    context->priv().testingOnly_purgeAllUnlockedResources();
+    context->priv().getResourceCache()->purgeUnlockedResources();
     sk_sp<SkImage> image;
     make_images(&image, 1);
     draw_image(canvas, image.get());
@@ -54,7 +55,7 @@
     context->getResourceCacheUsage(&baselineCount, nullptr);
     baselineCount -= 1; // for the image's textures.
     context->setResourceCacheLimits(baselineCount + approxImagesInBudget, 1 << 30);
-    context->priv().testingOnly_purgeAllUnlockedResources();
+    context->priv().getResourceCache()->purgeUnlockedResources();
 }
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/include/gpu/GrDirectContext.h b/include/gpu/GrDirectContext.h
index 022bad5..807d1b3 100644
--- a/include/gpu/GrDirectContext.h
+++ b/include/gpu/GrDirectContext.h
@@ -253,8 +253,18 @@
     /**
      * Purge GPU resources that haven't been used in the past 'msNotUsed' milliseconds or are
      * otherwise marked for deletion, regardless of whether the context is under budget.
+     *
+     * If 'scratchResourcesOnly' is true all unlocked scratch resources older than 'msNotUsed' will
+     * be purged but the unlocked resources with persistent data will remain. If
+     * 'scratchResourcesOnly' is false then all unlocked resources older than 'msNotUsed' will be
+     * purged.
+     *
+     * @param msNotUsed              Only unlocked resources not used in these last milliseconds
+     *                               will be cleaned up.
+     * @param scratchResourcesOnly   If true only unlocked scratch resources will be purged.
      */
-    void performDeferredCleanup(std::chrono::milliseconds msNotUsed);
+    void performDeferredCleanup(std::chrono::milliseconds msNotUsed,
+                                bool scratchResourcesOnly=false);
 
     // Temporary compatibility API for Android.
     void purgeResourcesNotUsedInMs(std::chrono::milliseconds msNotUsed) {
diff --git a/src/gpu/GrDirectContext.cpp b/src/gpu/GrDirectContext.cpp
index 3a2ae7e..36b5c52 100644
--- a/src/gpu/GrDirectContext.cpp
+++ b/src/gpu/GrDirectContext.cpp
@@ -193,7 +193,7 @@
 
     this->drawingManager()->freeGpuResources();
 
-    fResourceCache->purgeAllUnlocked();
+    fResourceCache->purgeUnlockedResources();
 }
 
 bool GrDirectContext::init() {
@@ -317,7 +317,8 @@
     fGpu->releaseUnlockedBackendObjects();
 }
 
-void GrDirectContext::performDeferredCleanup(std::chrono::milliseconds msNotUsed) {
+void GrDirectContext::performDeferredCleanup(std::chrono::milliseconds msNotUsed,
+                                             bool scratchResourcesOnly) {
     TRACE_EVENT0("skia.gpu", TRACE_FUNC);
 
     ASSERT_SINGLE_OWNER
@@ -331,7 +332,7 @@
     auto purgeTime = GrStdSteadyClock::now() - msNotUsed;
 
     fResourceCache->purgeAsNeeded();
-    fResourceCache->purgeResourcesNotUsedSince(purgeTime);
+    fResourceCache->purgeResourcesNotUsedSince(purgeTime, scratchResourcesOnly);
 
     // The textBlob Cache doesn't actually hold any GPU resource but this is a convenient
     // place to purge stale blobs
diff --git a/src/gpu/GrDirectContextPriv.cpp b/src/gpu/GrDirectContextPriv.cpp
index 66917a1..b00a1a2 100644
--- a/src/gpu/GrDirectContextPriv.cpp
+++ b/src/gpu/GrDirectContextPriv.cpp
@@ -185,10 +185,6 @@
                                    SkColorInfo(colorType, kPremul_SkAlphaType, nullptr));
 }
 
-void GrDirectContextPriv::testingOnly_purgeAllUnlockedResources() {
-    fContext->fResourceCache->purgeAllUnlocked();
-}
-
 void GrDirectContextPriv::testingOnly_flushAndRemoveOnFlushCallbackObject(
         GrOnFlushCallbackObject* cb) {
     fContext->flushAndSubmit();
diff --git a/src/gpu/GrDirectContextPriv.h b/src/gpu/GrDirectContextPriv.h
index fd205d0..fd8f9af 100644
--- a/src/gpu/GrDirectContextPriv.h
+++ b/src/gpu/GrDirectContextPriv.h
@@ -187,13 +187,6 @@
         if it gets cached or used more generally. */
     sk_sp<SkImage> testingOnly_getFontAtlasImage(GrMaskFormat format, unsigned int index = 0);
 
-    /**
-     * Purge all the unlocked resources from the cache.
-     * This entry point is mainly meant for timing texture uploads
-     * and is not defined in normal builds of Skia.
-     */
-    void testingOnly_purgeAllUnlockedResources();
-
     void testingOnly_flushAndRemoveOnFlushCallbackObject(GrOnFlushCallbackObject*);
 #endif
 
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 64d470c..2a96973 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -564,19 +564,43 @@
     this->validate();
 }
 
-void GrResourceCache::purgeUnlockedResources(bool scratchResourcesOnly) {
+void GrResourceCache::purgeUnlockedResources(const GrStdSteadyClock::time_point* purgeTime,
+                                             bool scratchResourcesOnly) {
 
     if (!scratchResourcesOnly) {
-        fThreadSafeCache->dropUniqueRefs(nullptr);
+        if (purgeTime) {
+            fThreadSafeCache->dropUniqueRefsOlderThan(*purgeTime);
+        } else {
+            fThreadSafeCache->dropUniqueRefs(nullptr);
+        }
 
         // We could disable maintaining the heap property here, but it would add a lot of
         // complexity. Moreover, this is rarely called.
         while (fPurgeableQueue.count()) {
             GrGpuResource* resource = fPurgeableQueue.peek();
+
+            const GrStdSteadyClock::time_point resourceTime =
+                    resource->cacheAccess().timeWhenResourceBecamePurgeable();
+            if (purgeTime && 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;
+            }
+
             SkASSERT(resource->resourcePriv().isPurgeable());
             resource->cacheAccess().release();
         }
     } else {
+        // Early out if the very first item is too new to purge to avoid sorting the queue when
+        // nothing will be deleted.
+        if (purgeTime && fPurgeableQueue.count() &&
+            fPurgeableQueue.peek()->cacheAccess().timeWhenResourceBecamePurgeable() >= *purgeTime) {
+            return;
+        }
+
         // Sort the queue
         fPurgeableQueue.sort();
 
@@ -584,6 +608,13 @@
         SkTDArray<GrGpuResource*> scratchResources;
         for (int i = 0; i < fPurgeableQueue.count(); i++) {
             GrGpuResource* resource = fPurgeableQueue.at(i);
+
+            const GrStdSteadyClock::time_point resourceTime =
+                    resource->cacheAccess().timeWhenResourceBecamePurgeable();
+            if (purgeTime && resourceTime >= *purgeTime) {
+                // scratch or not, all later iterations will be too recently used to purge.
+                break;
+            }
             SkASSERT(resource->resourcePriv().isPurgeable());
             if (!resource->getUniqueKey().isValid()) {
                 *scratchResources.append() = resource;
@@ -600,26 +631,6 @@
     this->validate();
 }
 
-void GrResourceCache::purgeResourcesNotUsedSince(GrStdSteadyClock::time_point purgeTime) {
-    fThreadSafeCache->dropUniqueRefsOlderThan(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->resourcePriv().isPurgeable());
-        resource->cacheAccess().release();
-    }
-}
-
 bool GrResourceCache::purgeToMakeHeadroom(size_t desiredHeadroomBytes) {
     AutoValidate av(this);
     if (desiredHeadroomBytes > fMaxBytes) {
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index eb52ca2..198ace2 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -154,16 +154,20 @@
         keys. */
     void purgeAsNeeded();
 
-    /** Purges all resources that don't have external owners. */
-    void purgeAllUnlocked() { this->purgeUnlockedResources(false); }
-
     // Purge unlocked resources. If 'scratchResourcesOnly' is true the purgeable resources
     // containing persistent data are spared. If it is false then all purgeable resources will
     // be deleted.
-    void purgeUnlockedResources(bool scratchResourcesOnly);
+    void purgeUnlockedResources(bool scratchResourcesOnly=false) {
+        this->purgeUnlockedResources(/*purgeTime=*/nullptr, scratchResourcesOnly);
+    }
 
-    /** Purge all resources not used since the passed in time. */
-    void purgeResourcesNotUsedSince(GrStdSteadyClock::time_point);
+    // Purge unlocked resources not used since the passed point in time. If 'scratchResourcesOnly'
+    // is true the purgeable resources containing persistent data are spared. If it is false then
+    // all purgeable resources older than 'purgeTime' will be deleted.
+    void purgeResourcesNotUsedSince(GrStdSteadyClock::time_point purgeTime,
+                                    bool scratchResourcesOnly=false) {
+        this->purgeUnlockedResources(&purgeTime, scratchResourcesOnly);
+    }
 
     /** If it's possible to purge enough resources to get the provided amount of budget
         headroom, do so and return true. If it's not possible, do nothing and return false.
@@ -273,6 +277,9 @@
 
     uint32_t getNextTimestamp();
 
+    void purgeUnlockedResources(const GrStdSteadyClock::time_point* purgeTime,
+                                bool scratchResourcesOnly);
+
 #ifdef SK_DEBUG
     bool isInCache(const GrGpuResource* r) const;
     void validate() const;
diff --git a/tests/GrSurfaceTest.cpp b/tests/GrSurfaceTest.cpp
index 0f94d5e..717039c 100644
--- a/tests/GrSurfaceTest.cpp
+++ b/tests/GrSurfaceTest.cpp
@@ -281,7 +281,7 @@
                             }
                         }
 
-                        dContext->priv().testingOnly_purgeAllUnlockedResources();
+                        dContext->priv().getResourceCache()->purgeUnlockedResources();
                     }
 
                     // Try creating the texture as a deferred proxy.
@@ -311,7 +311,7 @@
                                 }
                             }
                         }
-                        dContext->priv().testingOnly_purgeAllUnlockedResources();
+                        dContext->priv().getResourceCache()->purgeUnlockedResources();
                     }
                 }
             }
diff --git a/tests/PathRendererCacheTests.cpp b/tests/PathRendererCacheTests.cpp
index 2024b17..fbd6750 100644
--- a/tests/PathRendererCacheTests.cpp
+++ b/tests/PathRendererCacheTests.cpp
@@ -124,7 +124,7 @@
     }
     dContext->flushAndSubmit();
     REPORTER_ASSERT(reporter, SkPathPriv::GenIDChangeListenersCount(path) == 20);
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     // The listeners don't actually purge until we try to add another one.
     draw_path(dContext.get(), rtc.get(), path, pathRenderer.get(), aaType, style);
     REPORTER_ASSERT(reporter, SkPathPriv::GenIDChangeListenersCount(path) == 1);
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index 9e4a27c..3ca5acc 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -331,7 +331,7 @@
         SkASSERT(fDContext);
         fDContext->setResourceCacheLimit(maxBytes);
         GrResourceCache* cache = fDContext->priv().getResourceCache();
-        cache->purgeAllUnlocked();
+        cache->purgeUnlockedResources();
         SkASSERT(0 == cache->getResourceCount() && 0 == cache->getResourceBytes());
     }
 
@@ -364,7 +364,7 @@
                               d->gpuMemorySize() == cache->getResourceBytes());
 
     // Should be safe to purge without deleting the resources since we still have refs.
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 4 == TestResource::NumAlive());
 
     // Since the resources have neither unique nor scratch keys, delete immediately upon unref.
@@ -578,7 +578,7 @@
     REPORTER_ASSERT(reporter, 0 == cache->getPurgeableBytes());
 
     // Our refs mean that the resources are non purgeable.
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 5 == cache->getResourceCount());
     REPORTER_ASSERT(reporter, scratch->gpuMemorySize() + unique->gpuMemorySize() +
                                               wrappedCacheable->gpuMemorySize() +
@@ -607,7 +607,7 @@
     REPORTER_ASSERT(reporter, 11 == cache->getPurgeableBytes());
     // This will free 'unique' but not wrappedCacheable which has a key. That requires the key to be
     // removed to be freed.
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 4 == cache->getResourceCount());
 
     wrappedCacheableViaKey = cache->findAndRefUniqueResource(uniqueKey2);
@@ -631,7 +631,7 @@
 
     scratch->unref();
     REPORTER_ASSERT(reporter, 10 == cache->getPurgeableBytes());
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 3 == cache->getResourceCount());
     REPORTER_ASSERT(reporter, unbudgeted->gpuMemorySize() + wrappedCacheable->gpuMemorySize() +
                                               wrappedUncacheable->gpuMemorySize() ==
@@ -719,7 +719,7 @@
     REPORTER_ASSERT(reporter, 21 == cache->getBudgetedResourceBytes());
     REPORTER_ASSERT(reporter, 21 == cache->getPurgeableBytes());
 
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
     REPORTER_ASSERT(reporter, 0 == cache->getResourceBytes());
     REPORTER_ASSERT(reporter, 0 == cache->getBudgetedResourceCount());
@@ -824,7 +824,7 @@
                               cache->getResourceBytes());
 
     // Our refs mean that the resources are non purgeable.
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
     REPORTER_ASSERT(reporter, 2 == cache->getResourceCount());
 
@@ -836,7 +836,7 @@
     SkDEBUGCODE(REPORTER_ASSERT(reporter, 2 == cache->countScratchEntriesForKey(scratchKey));)
 
     // Purge again. This time resources should be purgeable.
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
     REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
     SkDEBUGCODE(REPORTER_ASSERT(reporter, 0 == cache->countScratchEntriesForKey(scratchKey));)
@@ -1017,7 +1017,7 @@
     REPORTER_ASSERT(reporter, 1 == TestResource::NumAlive());
 
     // c shouldn't be purged because it is ref'ed.
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 1 == cache->getResourceCount());
     REPORTER_ASSERT(reporter, c->gpuMemorySize() == cache->getResourceBytes());
     REPORTER_ASSERT(reporter, 1 == TestResource::NumAlive());
@@ -1111,7 +1111,7 @@
     SkSafeUnref(scratch);
 
     // Get rid of c.
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     scratch = cache->findAndRefScratchResource(scratchKey);
     REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
     REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
@@ -1146,14 +1146,14 @@
 
     REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
 
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
 
     // Break the cycle
     unownedA->setUnrefWhenDestroyed(nullptr);
     REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
 
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
 }
 
@@ -1267,7 +1267,7 @@
             }
 
             REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
-            cache->purgeAllUnlocked();
+            cache->purgeUnlockedResources();
         }
 
         // Do a similar test but where we leave refs on some resources to prevent them from being
@@ -1301,7 +1301,37 @@
                 REPORTER_ASSERT(reporter, cnt / 2 - i - 1 == cache->getResourceCount());
             }
 
-            cache->purgeAllUnlocked();
+            cache->purgeUnlockedResources();
+        }
+
+        REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
+
+        // Do a similar test where we alternate adding scratch and uniquely keyed resources, but
+        // then purge old scratch resources.
+        {
+            for (int i = 0; i < cnt; ++i) {
+                const bool isScratch = (i % 2 == 0);
+                const SkBudgeted budgeted = SkBudgeted::kYes;
+                const TestResource::SimulatedProperty property = TestResource::kA_SimulatedProperty;
+                TestResource* r = isScratch ? TestResource::CreateScratch(gpu, budgeted, property)
+                                            : new TestResource(gpu, budgeted, property);
+                if (!isScratch) {
+                    GrUniqueKey k;
+                    make_unique_key<1>(&k, i);
+                    r->resourcePriv().setUniqueKey(k);
+                }
+                r->unref();
+                timeStamps.get()[i] = nowish();
+            }
+
+            for (int i = 0; i < cnt; ++i) {
+                // Should get a resource purged every other frame, since the uniquely keyed
+                // resources will not be considered.
+                cache->purgeResourcesNotUsedSince(timeStamps[i], /*scratchResourcesOnly=*/true);
+                REPORTER_ASSERT(reporter, cnt - i / 2 - 1 == cache->getResourceCount());
+            }
+            // Unref remaining resources
+            cache->purgeResourcesNotUsedSince(nowish());
         }
 
         REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
@@ -1421,7 +1451,7 @@
         }
 
         // ensure all are purged before the next
-        dContext->priv().testingOnly_purgeAllUnlockedResources();
+        dContext->priv().getResourceCache()->purgeUnlockedResources();
         REPORTER_ASSERT(reporter, 0 == cache->getBudgetedResourceCount());
         REPORTER_ASSERT(reporter, 0 == cache->getPurgeableBytes());
 
diff --git a/tests/TextureProxyTest.cpp b/tests/TextureProxyTest.cpp
index ab92f82..b4d5165 100644
--- a/tests/TextureProxyTest.cpp
+++ b/tests/TextureProxyTest.cpp
@@ -188,7 +188,7 @@
 
     // Mega-purging it should remove it from both the hash and the cache
     proxy = nullptr;
-    cache->purgeAllUnlocked();
+    cache->purgeUnlockedResources();
     if (!expectResourceToOutliveProxy) {
         expectedCacheCount -= cacheEntriesPerProxy;
     }
@@ -276,7 +276,7 @@
     }
 #endif
 
-    dContext->priv().testingOnly_purgeAllUnlockedResources();
+    dContext->priv().getResourceCache()->purgeUnlockedResources();
 
     REPORTER_ASSERT(reporter, 0 == proxyProvider->numUniqueKeyProxies_TestOnly());
     REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
@@ -319,7 +319,7 @@
     REPORTER_ASSERT(reporter, cacheEntriesPerProxy == cache->getResourceCount());
 
     proxy = nullptr;
-    dContext->priv().testingOnly_purgeAllUnlockedResources();
+    dContext->priv().getResourceCache()->purgeUnlockedResources();
 
     REPORTER_ASSERT(reporter, 0 == proxyProvider->numUniqueKeyProxies_TestOnly());
     REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
@@ -352,7 +352,7 @@
         }
 
         REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
-        cache->purgeAllUnlocked();
+        cache->purgeUnlockedResources();
     }
 
     basic_test(direct, reporter, create_wrapped_backend(direct), cacheEntriesPerProxy);