Handle the case when the GrResourceCache timestamp wraps.

NOTREECHECKS=true

Review URL: https://codereview.chromium.org/916103006
This commit is contained in:
bsalomon 2015-02-19 11:38:44 -08:00 committed by Commit bot
parent 6364807151
commit ddf30e64fe
4 changed files with 150 additions and 4 deletions

View File

@ -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<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.

View File

@ -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;

View File

@ -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.

View File

@ -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<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 @@ 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);
}