Handle the case when the GrResourceCache timestamp wraps.

NOTREECHECKS=true

Review URL: https://codereview.chromium.org/916103006
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index a2fde2f..aed31b7 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -12,6 +12,7 @@
 #include "SkChecksum.h"
 #include "SkGr.h"
 #include "SkMessageBus.h"
+#include "SkTSort.h"
 
 DECLARE_SKMESSAGEBUS_MESSAGE(GrUniqueKeyInvalidatedMessage);
 
@@ -90,6 +91,11 @@
     SkASSERT(!this->isInCache(resource));
     SkASSERT(!resource->wasDestroyed());
     SkASSERT(!resource->isPurgeable());
+
+    // We must set the timestamp before adding to the array in case the timestamp wraps and we wind
+    // up iterating over all the resources that already have timestamps.
+    resource->cacheAccess().setTimestamp(this->getNextTimestamp());
+
     this->addToNonpurgeableArray(resource);
 
     size_t size = resource->gpuMemorySize();
@@ -112,8 +118,6 @@
         fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
     }
 
-    resource->cacheAccess().setTimestamp(fTimestamp++);
-
     this->purgeAsNeeded();
 }
 
@@ -286,13 +290,15 @@
 void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) {
     SkASSERT(resource);
     SkASSERT(this->isInCache(resource));
+
     if (resource->isPurgeable()) {
         // It's about to become unpurgeable.
         fPurgeableQueue.remove(resource);
         this->addToNonpurgeableArray(resource);
     }
     resource->ref();
-    resource->cacheAccess().setTimestamp(fTimestamp++);
+
+    resource->cacheAccess().setTimestamp(this->getNextTimestamp());
     this->validate();
 }
 
@@ -441,6 +447,73 @@
     SkDEBUGCODE(*index = -1);
 }
 
+uint32_t GrResourceCache::getNextTimestamp() {
+    // If we wrap then all the existing resources will appear older than any resources that get
+    // a timestamp after the wrap.
+    if (0 == fTimestamp) {
+        int count = this->getResourceCount();
+        if (count) {
+            // Reset all the timestamps. We sort the resources by timestamp and then assign
+            // sequential timestamps beginning with 0. This is O(n*lg(n)) but it should be extremely
+            // rare.
+            SkTDArray<GrGpuResource*> sortedPurgeableResources;
+            sortedPurgeableResources.setReserve(fPurgeableQueue.count());
+
+            while (fPurgeableQueue.count()) {
+                *sortedPurgeableResources.append() = fPurgeableQueue.peek();
+                fPurgeableQueue.pop();
+            }
+
+            struct Less {
+                bool operator()(GrGpuResource* a, GrGpuResource* b) {
+                    return CompareTimestamp(a,b);
+                }
+            };
+            Less less;
+            SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end() - 1, less);
+
+            // Pick resources out of the purgeable and non-purgeable arrays based on lowest
+            // timestamp and assign new timestamps.
+            int currP = 0;
+            int currNP = 0;
+            while (currP < sortedPurgeableResources.count() &&
+                   currNP < fNonpurgeableResources.count()) {                
+                uint32_t tsP = sortedPurgeableResources[currP]->cacheAccess().timestamp();
+                uint32_t tsNP = fNonpurgeableResources[currNP]->cacheAccess().timestamp();
+                SkASSERT(tsP != tsNP);
+                if (tsP < tsNP) {
+                    sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
+                } else {
+                    // Correct the index in the nonpurgeable array stored on the resource post-sort.
+                    *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
+                    fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
+                }
+            }
+
+            // The above loop ended when we hit the end of one array. Finish the other one.
+            while (currP < sortedPurgeableResources.count()) {
+                sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
+            }
+            while (currNP < fNonpurgeableResources.count()) {
+                *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
+                fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
+            }
+
+            // Rebuild the queue.
+            for (int i = 0; i < sortedPurgeableResources.count(); ++i) {
+                fPurgeableQueue.insert(sortedPurgeableResources[i]);
+            }
+
+            this->validate();
+            SkASSERT(count == this->getResourceCount());
+
+            // count should be the next timestamp we return.
+            SkASSERT(fTimestamp == SkToU32(count));
+        }        
+    }
+    return fTimestamp++;
+}
+
 #ifdef SK_DEBUG
 void GrResourceCache::validate() const {
     // Reduce the frequency of validations for large resource counts.
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index 3e5df38..8331bf5 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -171,6 +171,9 @@
     void dumpStats(SkString*) const;
 #endif
 
+    // This function is for unit testing and is only defined in test tools.
+    void changeTimestamp(uint32_t newTimestamp);
+
 private:
     ///////////////////////////////////////////////////////////////////////////
     /// @name Methods accessible via ResourceAccess
@@ -192,6 +195,8 @@
     void removeFromNonpurgeableArray(GrGpuResource*);
     bool overBudget() const { return fBudgetedBytes > fMaxBytes || fBudgetedCount > fMaxCount; }
 
+    uint32_t getNextTimestamp();
+
 #ifdef SK_DEBUG
     bool isInCache(const GrGpuResource* r) const;
     void validate() const;
diff --git a/src/gpu/GrTest.cpp b/src/gpu/GrTest.cpp
index 0e1c069..9ddc97d 100644
--- a/src/gpu/GrTest.cpp
+++ b/src/gpu/GrTest.cpp
@@ -125,6 +125,9 @@
 
 #endif
 
+///////////////////////////////////////////////////////////////////////////////
+
+void GrResourceCache::changeTimestamp(uint32_t newTimestamp) { fTimestamp = newTimestamp; }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Code for the mock context. It's built on a mock GrGpu class that does nothing.
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index d9e4c18..8316c6b 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -923,6 +923,71 @@
     }
 }
 
+static void test_timestamp_wrap(skiatest::Reporter* reporter) {
+    static const int kCount = 50;
+    static const int kBudgetCnt = kCount / 2;
+    static const int kLockedFreq = 8;
+    static const int kBudgetSize = 0x80000000;
+
+    SkRandom random;
+
+    // Run the test 2*kCount times;
+    for (int i = 0; i < 2 * kCount; ++i ) {
+        Mock mock(kBudgetCnt, kBudgetSize);
+        GrContext* context = mock.context();
+        GrResourceCache* cache = mock.cache();
+
+        // Pick a random number of resources to add before the timestamp will wrap.
+        cache->changeTimestamp(SK_MaxU32 - random.nextULessThan(kCount + 1));
+
+        static const int kNumToPurge = kCount - kBudgetCnt;
+
+        SkTDArray<int> shouldPurgeIdxs;
+        int purgeableCnt = 0;
+        SkTDArray<GrGpuResource*> resourcesToUnref;
+
+        // Add kCount resources, holding onto resources at random so we have a mix of purgeable and
+        // unpurgeable resources.
+        for (int j = 0; j < kCount; ++j) {
+            GrUniqueKey key;
+            make_unique_key<0>(&key, j);
+
+            TestResource* r = SkNEW_ARGS(TestResource, (context->getGpu()));
+            r->resourcePriv().setUniqueKey(key);
+            if (random.nextU() % kLockedFreq) {
+                // Make this is purgeable.
+                r->unref();
+                ++purgeableCnt;
+                if (purgeableCnt <= kNumToPurge) {
+                    *shouldPurgeIdxs.append() = j;
+                }
+            } else {
+                *resourcesToUnref.append() = r;
+            }
+        }
+
+        // Verify that the correct resources were purged.
+        int currShouldPurgeIdx = 0;
+        for (int j = 0; j < kCount; ++j) {
+            GrUniqueKey key;
+            make_unique_key<0>(&key, j);
+            GrGpuResource* res = cache->findAndRefUniqueResource(key);
+            if (currShouldPurgeIdx < shouldPurgeIdxs.count() &&
+                shouldPurgeIdxs[currShouldPurgeIdx] == j) {
+                ++currShouldPurgeIdx;
+                REPORTER_ASSERT(reporter, NULL == res);
+            } else {
+                REPORTER_ASSERT(reporter, NULL != res);
+            }
+            SkSafeUnref(res);
+        }
+
+        for (int j = 0; j < resourcesToUnref.count(); ++j) {
+            resourcesToUnref[j]->unref();
+        }
+    }
+}
+
 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
@@ -982,7 +1047,6 @@
     }
 }
 
-
 ////////////////////////////////////////////////////////////////////////////////
 DEF_GPUTEST(ResourceCache, reporter, factory) {
     for (int type = 0; type < GrContextFactory::kLastGLContextType; ++type) {
@@ -1018,6 +1082,7 @@
     test_purge_invalidated(reporter);
     test_cache_chained_purge(reporter);
     test_resource_size_changed(reporter);
+    test_timestamp_wrap(reporter);
     test_large_resource_count(reporter);
 }