Allow unbudgeted resources to be recycled by the cache as scratch.

Review URL: https://codereview.chromium.org/870743002
This commit is contained in:
bsalomon 2015-01-23 07:19:22 -08:00 committed by Commit bot
parent 4dffede04a
commit c2f35b750a
6 changed files with 205 additions and 150 deletions

View File

@ -263,6 +263,7 @@ private:
void notifyIsPurgable() const;
void removeScratchKey();
void makeBudgeted();
void makeUnbudgeted();
#ifdef SK_DEBUG
friend class GrGpu; // for assert in GrGpu to access getGpu

View File

@ -86,8 +86,8 @@ bool GrGpuResource::setContentKey(const GrContentKey& key) {
// Currently this can only be called once and can't be called when the resource is scratch.
SkASSERT(this->internalHasRef());
// Wrapped resources can never have a key.
if (this->isWrapped()) {
// Wrapped and uncached resources can never have a content key.
if (!this->cacheAccess().isBudgeted()) {
return false;
}
@ -138,6 +138,13 @@ void GrGpuResource::makeBudgeted() {
}
}
void GrGpuResource::makeUnbudgeted() {
if (GrGpuResource::kCached_LifeCycle == fLifeCycle && !fContentKey.isValid()) {
fLifeCycle = kUncached_LifeCycle;
get_resource_cache2(fGpu)->resourceAccess().didChangeBudgetStatus(this);
}
}
uint32_t GrGpuResource::CreateUniqueID() {
static int32_t gUniqueID = SK_InvalidUniqueID;
uint32_t id;

View File

@ -29,11 +29,12 @@ public:
}
/**
* Is the resource currently cached as scratch? This means it has a valid scratch key and does
* not have a content key.
* Is the resource currently cached as scratch? This means it is cached, has a valid scratch
* key, and does not have a content key.
*/
bool isScratch() const {
return !this->getContentKey().isValid() && fResource->fScratchKey.isValid();
return !this->getContentKey().isValid() && fResource->fScratchKey.isValid() &&
this->isBudgeted();
}
/**
@ -62,7 +63,11 @@ public:
/**
* Does the resource count against the resource budget?
*/
bool isBudgeted() const { return GrGpuResource::kCached_LifeCycle == fResource->fLifeCycle; }
bool isBudgeted() const {
bool ret = GrGpuResource::kCached_LifeCycle == fResource->fLifeCycle;
SkASSERT(ret || !this->getContentKey().isValid());
return ret;
}
/**
* If the resource is uncached make it cached. Has no effect on resources that are wrapped or
@ -70,6 +75,12 @@ public:
*/
void makeBudgeted() { fResource->makeBudgeted(); }
/**
* If the resource is cached make it uncached. Has no effect on resources that are wrapped or
* already uncached. Furthermore, resources with content keys cannot be made unbudgeted.
*/
void makeUnbudgeted() { fResource->makeUnbudgeted(); }
/**
* Called by the cache to delete the resource under normal circumstances.
*/

View File

@ -257,23 +257,42 @@ void GrResourceCache2::notifyPurgable(GrGpuResource* resource) {
return;
}
// Purge the resource if we're over budget
bool overBudget = fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxBytes;
bool release = false;
// Also purge if the resource has neither a valid scratch key nor a content key.
bool noKey = !resource->cacheAccess().getScratchKey().isValid() &&
!resource->cacheAccess().getContentKey().isValid();
if (resource->cacheAccess().isWrapped()) {
release = true;
} else if (!resource->cacheAccess().isBudgeted()) {
// Check whether this resource could still be used as a scratch resource.
if (resource->cacheAccess().getScratchKey().isValid()) {
// We won't purge an existing resource to make room for this one.
bool underBudget = fBudgetedCount < fMaxCount &&
fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes;
if (underBudget) {
resource->cacheAccess().makeBudgeted();
} else {
release = true;
}
} else {
release = true;
}
} else {
// Purge the resource if we're over budget
bool overBudget = fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxBytes;
// Only cached resources should ever have a key.
SkASSERT(noKey || resource->cacheAccess().isBudgeted());
// Also purge if the resource has neither a valid scratch key nor a content key.
bool noKey = !resource->cacheAccess().getScratchKey().isValid() &&
!resource->cacheAccess().getContentKey().isValid();
if (overBudget || noKey) {
release = true;
}
}
if (overBudget || noKey) {
if (release) {
SkDEBUGCODE(int beforeCount = fCount;)
resource->cacheAccess().release();
// We should at least free this resource, perhaps dependent resources as well.
SkASSERT(fCount < beforeCount);
}
this->validate();
}
@ -389,6 +408,13 @@ void GrResourceCache2::purgeAllUnlocked() {
#ifdef SK_DEBUG
void GrResourceCache2::validate() const {
// Reduce the frequency of validations for large resource counts.
static SkRandom gRandom;
int mask = (SkNextPow2(fCount + 1) >> 5) - 1;
if (~mask && (gRandom.nextU() & mask)) {
return;
}
size_t bytes = 0;
int count = 0;
int budgetedCount = 0;
@ -414,7 +440,8 @@ void GrResourceCache2::validate() const {
SkASSERT(fScratchMap.countForKey(resource->cacheAccess().getScratchKey()));
SkASSERT(!resource->cacheAccess().isWrapped());
} else if (resource->cacheAccess().getScratchKey().isValid()) {
SkASSERT(resource->cacheAccess().getContentKey().isValid());
SkASSERT(!resource->cacheAccess().isBudgeted() ||
resource->cacheAccess().getContentKey().isValid());
++couldBeScratch;
SkASSERT(fScratchMap.countForKey(resource->cacheAccess().getScratchKey()));
SkASSERT(!resource->cacheAccess().isWrapped());
@ -424,6 +451,7 @@ void GrResourceCache2::validate() const {
++content;
SkASSERT(fContentHash.find(contentKey) == resource);
SkASSERT(!resource->cacheAccess().isWrapped());
SkASSERT(resource->cacheAccess().isBudgeted());
}
if (resource->cacheAccess().isBudgeted()) {

View File

@ -87,7 +87,7 @@ GrTexture::GrTexture(GrGpu* gpu, LifeCycle lifeCycle, const GrSurfaceDesc& desc)
: INHERITED(gpu, lifeCycle, desc)
, fMipMapsStatus(kNotAllocated_MipMapsStatus) {
if (kCached_LifeCycle == lifeCycle) {
if (kWrapped_LifeCycle != lifeCycle) {
GrScratchKey key;
GrTexturePriv::ComputeScratchKey(desc, &key);
this->setScratchKey(key);

View File

@ -97,8 +97,10 @@ public:
this->registerWithCache();
}
static TestResource* CreateScratchTestResource(GrGpu* gpu, SimulatedProperty property) {
return SkNEW_ARGS(TestResource, (gpu, property, kScratchConstructor));
static TestResource* CreateScratchTestResource(GrGpu* gpu,
SimulatedProperty property,
bool cached = true) {
return SkNEW_ARGS(TestResource, (gpu, property, cached, kScratchConstructor));
}
~TestResource() {
@ -132,8 +134,8 @@ public:
private:
static const int kScratchKeyFieldCnt = 6;
TestResource(GrGpu* gpu, SimulatedProperty property, ScratchConstructor)
: INHERITED(gpu, kCached_LifeCycle)
TestResource(GrGpu* gpu, SimulatedProperty property, bool cached, ScratchConstructor)
: INHERITED(gpu, cached ? kCached_LifeCycle : kUncached_LifeCycle)
, fToDelete(NULL)
, fSize(kDefaultSize)
, fProperty(property) {
@ -154,16 +156,29 @@ private:
};
int TestResource::fNumAlive = 0;
static void test_no_key(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
class Mock {
public:
Mock(int maxCnt, size_t maxBytes) {
fContext.reset(GrContext::CreateMockContext());
SkASSERT(fContext);
fContext->setResourceCacheLimits(maxCnt, maxBytes);
GrResourceCache2* cache2 = fContext->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
}
context->setResourceCacheLimits(10, 30000);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
GrResourceCache2* cache() { return fContext->getResourceCache2(); }
GrContext* context() { return fContext; }
private:
SkAutoTUnref<GrContext> fContext;
};
static void test_no_key(skiatest::Reporter* reporter) {
Mock mock(10, 30000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
// Create a bunch of resources with no keys
TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
@ -217,16 +232,9 @@ template <int> static void make_content_key(GrContentKey* key, int data) {
}
static void test_budgeting(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
}
context->setResourceCacheLimits(10, 300);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
SkASSERT(0 == cache2->getBudgetedResourceCount() && 0 == cache2->getBudgetedResourceBytes());
Mock mock(10, 300);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
GrContentKey contentKey;
make_content_key<0>(&contentKey, 0);
@ -310,16 +318,9 @@ static void test_budgeting(skiatest::Reporter* reporter) {
}
static void test_unbudgeted(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
}
context->setResourceCacheLimits(10, 300);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
SkASSERT(0 == cache2->getBudgetedResourceCount() && 0 == cache2->getBudgetedResourceBytes());
Mock mock(10, 30000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
GrContentKey contentKey;
make_content_key<0>(&contentKey, 0);
@ -382,16 +383,70 @@ static void test_unbudgeted(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceBytes());
}
static void test_duplicate_scratch_key(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
static void test_unbudgeted_to_scratch(skiatest::Reporter* reporter) {
Mock mock(10, 300);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
TestResource* resource =
TestResource::CreateScratchTestResource(context->getGpu(),
TestResource::kProperty1_SimulatedProperty, false);
GrScratchKey key;
TestResource::ComputeScratchKey(TestResource::kProperty1_SimulatedProperty, &key);
size_t size = resource->gpuMemorySize();
for (int i = 0; i < 2; ++i) {
// Since this resource is unbudgeted, it should not be reachable as scratch.
REPORTER_ASSERT(reporter, resource->cacheAccess().getScratchKey() == key);
REPORTER_ASSERT(reporter, !resource->cacheAccess().isScratch());
REPORTER_ASSERT(reporter, !resource->cacheAccess().isBudgeted());
REPORTER_ASSERT(reporter, NULL == cache2->findAndRefScratchResource(key));
REPORTER_ASSERT(reporter, 1 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, size == cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceCount());
REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceBytes());
// Once it is unrefed, it should become available as scratch.
resource->unref();
REPORTER_ASSERT(reporter, 1 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, size == cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 1 == cache2->getBudgetedResourceCount());
REPORTER_ASSERT(reporter, size == cache2->getBudgetedResourceBytes());
resource = static_cast<TestResource*>(cache2->findAndRefScratchResource(key));
REPORTER_ASSERT(reporter, resource);
REPORTER_ASSERT(reporter, resource->cacheAccess().getScratchKey() == key);
REPORTER_ASSERT(reporter, resource->cacheAccess().isScratch());
REPORTER_ASSERT(reporter, resource->cacheAccess().isBudgeted());
if (0 == i) {
// If made unbudgeted, it should return to original state: ref'ed and unbudgeted. Try
// the above tests again.
resource->cacheAccess().makeUnbudgeted();
} else {
// After the second time around, try removing the scratch key
resource->cacheAccess().removeScratchKey();
REPORTER_ASSERT(reporter, 1 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, size == cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 1 == cache2->getBudgetedResourceCount());
REPORTER_ASSERT(reporter, size == cache2->getBudgetedResourceBytes());
REPORTER_ASSERT(reporter, !resource->cacheAccess().getScratchKey().isValid());
REPORTER_ASSERT(reporter, !resource->cacheAccess().isScratch());
REPORTER_ASSERT(reporter, resource->cacheAccess().isBudgeted());
// now when it is unrefed it should die since it has no key.
resource->unref();
REPORTER_ASSERT(reporter, 0 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, 0 == cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceCount());
REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceBytes());
}
}
context->setResourceCacheLimits(5, 30000);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
}
static void test_duplicate_scratch_key(skiatest::Reporter* reporter) {
Mock mock(5, 30000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
// Create two resources that have the same scratch key.
TestResource* a =
@ -436,15 +491,9 @@ static void test_duplicate_scratch_key(skiatest::Reporter* reporter) {
}
static void test_remove_scratch_key(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
}
context->setResourceCacheLimits(5, 30000);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
Mock mock(5, 30000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
// Create two resources that have the same scratch key.
TestResource* a =
@ -504,15 +553,9 @@ static void test_remove_scratch_key(skiatest::Reporter* reporter) {
}
static void test_scratch_key_consistency(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
}
context->setResourceCacheLimits(5, 30000);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
Mock mock(5, 30000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
// Create two resources that have the same scratch key.
TestResource* a =
@ -571,15 +614,9 @@ static void test_scratch_key_consistency(skiatest::Reporter* reporter) {
}
static void test_duplicate_content_key(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
}
context->setResourceCacheLimits(5, 30000);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
Mock mock(5, 30000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
GrContentKey key;
make_content_key<0>(&key, 0);
@ -624,22 +661,15 @@ static void test_duplicate_content_key(skiatest::Reporter* reporter) {
}
static void test_purge_invalidated(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
}
Mock mock(5, 30000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
GrContentKey key1, key2, key3;
make_content_key<0>(&key1, 1);
make_content_key<0>(&key2, 2);
make_content_key<0>(&key3, 3);
context->setResourceCacheLimits(5, 30000);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
// Add three resources to the cache.
TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
TestResource* b = SkNEW_ARGS(TestResource, (context->getGpu()));
@ -685,67 +715,52 @@ static void test_purge_invalidated(skiatest::Reporter* reporter) {
}
static void test_cache_chained_purge(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
}
Mock mock(3, 30000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
GrContentKey key1, key2;
make_content_key<0>(&key1, 1);
make_content_key<0>(&key2, 2);
{
context->setResourceCacheLimits(3, 30000);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
TestResource* b = SkNEW_ARGS(TestResource, (context->getGpu()));
a->cacheAccess().setContentKey(key1);
b->cacheAccess().setContentKey(key2);
TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
TestResource* b = SkNEW_ARGS(TestResource, (context->getGpu()));
a->cacheAccess().setContentKey(key1);
b->cacheAccess().setContentKey(key2);
// Make a cycle
a->setUnrefWhenDestroyed(b);
b->setUnrefWhenDestroyed(a);
// Make a cycle
a->setUnrefWhenDestroyed(b);
b->setUnrefWhenDestroyed(a);
REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
a->unref();
b->unref();
a->unref();
b->unref();
REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
cache2->purgeAllUnlocked();
REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
cache2->purgeAllUnlocked();
REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
// Break the cycle
a->setUnrefWhenDestroyed(NULL);
REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
// Break the cycle
a->setUnrefWhenDestroyed(NULL);
REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
cache2->purgeAllUnlocked();
REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
}
cache2->purgeAllUnlocked();
REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
}
static void test_resource_size_changed(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
}
GrContentKey key1, key2;
make_content_key<0>(&key1, 1);
make_content_key<0>(&key2, 2);
// Test changing resources sizes (both increase & decrease).
{
context->setResourceCacheLimits(3, 30000);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
Mock mock(3, 30000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
a->cacheAccess().setContentKey(key1);
@ -770,10 +785,9 @@ static void test_resource_size_changed(skiatest::Reporter* reporter) {
// Test increasing a resources size beyond the cache budget.
{
context->setResourceCacheLimits(2, 300);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
Mock mock(2, 300);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
a->setSize(100);
@ -800,21 +814,14 @@ static void test_resource_size_changed(skiatest::Reporter* reporter) {
}
static void test_large_resource_count(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
return;
}
static const int kResourceCnt = 2000;
// 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
// down to 1 byte after creating the resource.
context->setResourceCacheLimits(2 * kResourceCnt, 2 * kResourceCnt + 1000);
GrResourceCache2* cache2 = context->getResourceCache2();
cache2->purgeAllUnlocked();
SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
static const int kResourceCnt = 2000;
Mock mock(2 * kResourceCnt, 2 * kResourceCnt + 1000);
GrContext* context = mock.context();
GrResourceCache2* cache2 = mock.cache();
for (int i = 0; i < kResourceCnt; ++i) {
GrContentKey key1, key2;
@ -892,6 +899,7 @@ DEF_GPUTEST(ResourceCache, reporter, factory) {
test_no_key(reporter);
test_budgeting(reporter);
test_unbudgeted(reporter);
test_unbudgeted_to_scratch(reporter);
test_duplicate_content_key(reporter);
test_duplicate_scratch_key(reporter);
test_remove_scratch_key(reporter);