Handle the case when the GrResourceCache timestamp wraps.
NOTREECHECKS=true Review URL: https://codereview.chromium.org/916103006
This commit is contained in:
parent
6364807151
commit
ddf30e64fe
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user