ccpr: Age out path cache entries

Adds a hook that gets called from GrContext::performDeferredCleanup.

Bug: skia:8452
Change-Id: I4e5f4d263528b21247fbc032a1b4881a23cbb2ff
Reviewed-on: https://skia-review.googlesource.com/c/167181
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCPathCache.cpp b/src/gpu/ccpr/GrCCPathCache.cpp
index 4816858..a5b9a10 100644
--- a/src/gpu/ccpr/GrCCPathCache.cpp
+++ b/src/gpu/ccpr/GrCCPathCache.cpp
@@ -209,17 +209,18 @@
     if (HashNode* node = fHashTable.find(*fScratchKey)) {
         entry = node->entry();
         SkASSERT(fLRU.isInList(entry));
-        if (fuzzy_equals(m, entry->fMaskTransform)) {
-            ++entry->fHitCount;  // The path was reused with a compatible matrix.
-        } else if (CreateIfAbsent::kYes == createIfAbsent && entry->unique()) {
-            // This entry is unique: we can recycle it instead of deleting and malloc-ing a new one.
-            entry->fMaskTransform = m;
-            entry->fHitCount = 1;
-            entry->invalidateAtlas();
-            SkASSERT(!entry->fCurrFlushAtlas);  // Should be null because 'entry' is unique.
-        } else {
-            this->evict(*fScratchKey);
-            entry = nullptr;
+        if (!fuzzy_equals(m, entry->fMaskTransform)) {
+            // The path was reused with an incompatible matrix.
+            if (CreateIfAbsent::kYes == createIfAbsent && entry->unique()) {
+                // This entry is unique: recycle it instead of deleting and malloc-ing a new one.
+                entry->fMaskTransform = m;
+                entry->fHitCount = 0;
+                entry->invalidateAtlas();
+                SkASSERT(!entry->fCurrFlushAtlas);  // Should be null because 'entry' is unique.
+            } else {
+                this->evict(*fScratchKey);
+                entry = nullptr;
+            }
         }
     }
 
@@ -248,10 +249,41 @@
     SkDEBUGCODE(HashNode* node = fHashTable.find(*fScratchKey));
     SkASSERT(node && node->entry() == entry);
     fLRU.addToHead(entry);
+
+    entry->fTimestamp = this->quickPerFlushTimestamp();
+    ++entry->fHitCount;
     return sk_ref_sp(entry);
 }
 
-void GrCCPathCache::purgeAsNeeded() {
+void GrCCPathCache::doPostFlushProcessing() {
+    this->purgeInvalidatedKeys();
+
+    // Mark the per-flush timestamp as needing to be updated with a newer clock reading.
+    fPerFlushTimestamp = GrStdSteadyClock::time_point::min();
+}
+
+void GrCCPathCache::purgeEntriesOlderThan(const GrStdSteadyClock::time_point& purgeTime) {
+    this->purgeInvalidatedKeys();
+
+#ifdef SK_DEBUG
+    auto lastTimestamp = (fLRU.isEmpty())
+            ? GrStdSteadyClock::time_point::max()
+            : fLRU.tail()->fTimestamp;
+#endif
+
+    // Drop every cache entry whose timestamp is older than purgeTime.
+    while (!fLRU.isEmpty() && fLRU.tail()->fTimestamp < purgeTime) {
+#ifdef SK_DEBUG
+        // Verify that fLRU is sorted by timestamp.
+        auto timestamp = fLRU.tail()->fTimestamp;
+        SkASSERT(timestamp >= lastTimestamp);
+        lastTimestamp = timestamp;
+#endif
+        this->evict(*fLRU.tail()->fCacheKey);
+    }
+}
+
+void GrCCPathCache::purgeInvalidatedKeys() {
     SkTArray<sk_sp<Key>> invalidatedKeys;
     fInvalidatedKeysInbox.poll(&invalidatedKeys);
     for (const sk_sp<Key>& key : invalidatedKeys) {