diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp index a2fde2fef2..aed31b73d2 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 @@ void GrResourceCache::insertResource(GrGpuResource* resource) { 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 @@ void GrResourceCache::insertResource(GrGpuResource* resource) { fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource); } - resource->cacheAccess().setTimestamp(fTimestamp++); - this->purgeAsNeeded(); } @@ -286,13 +290,15 @@ void GrResourceCache::changeUniqueKey(GrGpuResource* resource, const GrUniqueKey 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 @@ void GrResourceCache::removeFromNonpurgeableArray(GrGpuResource* resource) { 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 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 3e5df38d77..8331bf5d16 100644 --- a/src/gpu/GrResourceCache.h +++ b/src/gpu/GrResourceCache.h @@ -171,6 +171,9 @@ public: 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 @@ private: 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 0e1c069ac2..9ddc97d834 100644 --- a/src/gpu/GrTest.cpp +++ b/src/gpu/GrTest.cpp @@ -125,6 +125,9 @@ void GrResourceCache::dumpStats(SkString* out) const { #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 d9e4c18a7d..8316c6b48b 100644 --- a/tests/ResourceCacheTest.cpp +++ b/tests/ResourceCacheTest.cpp @@ -923,6 +923,71 @@ static void test_resource_size_changed(skiatest::Reporter* reporter) { } } +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 shouldPurgeIdxs; + int purgeableCnt = 0; + SkTDArray 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 @@ static void test_large_resource_count(skiatest::Reporter* reporter) { } } - //////////////////////////////////////////////////////////////////////////////// DEF_GPUTEST(ResourceCache, reporter, factory) { for (int type = 0; type < GrContextFactory::kLastGLContextType; ++type) { @@ -1018,6 +1082,7 @@ DEF_GPUTEST(ResourceCache, reporter, factory) { test_purge_invalidated(reporter); test_cache_chained_purge(reporter); test_resource_size_changed(reporter); + test_timestamp_wrap(reporter); test_large_resource_count(reporter); }