Allow GPU resources to not be counted against the cache budget.

BUG=skia:2889

Review URL: https://codereview.chromium.org/721353002
This commit is contained in:
bsalomon 2014-11-17 09:33:27 -08:00 committed by Commit bot
parent bd27e514e2
commit 84c8e62fad
7 changed files with 155 additions and 59 deletions

View File

@ -191,7 +191,7 @@ protected:
backend API calls should be made. */
virtual void onAbandon() { }
bool isWrapped() const { return kWrapped_FlagBit & fFlags; }
bool isWrapped() const { return SkToBool(kWrapped_Flag & fFlags); }
/**
* This entry point should be called whenever gpuMemorySize() should report a different size.
@ -221,7 +221,7 @@ private:
// See comments in CacheAccess.
bool setContentKey(const GrResourceKey& contentKey);
void setBudgeted(bool countsAgainstBudget);
void notifyIsPurgable() const;
#ifdef SK_DEBUG
@ -233,31 +233,39 @@ private:
// We're in an internal doubly linked list owned by GrResourceCache2
SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrGpuResource);
// This is not ref'ed but abandon() or release() will be called before the GrGpu object
// is destroyed. Those calls set will this to NULL.
GrGpu* fGpu;
enum Flags {
/**
* This object wraps a GPU object given to us by the user.
* Lifetime management is left up to the user (i.e., we will not
* free it).
*/
kWrapped_FlagBit = 0x1,
};
static const size_t kInvalidGpuMemorySize = ~static_cast<size_t>(0);
enum Flags {
/**
* The resource counts against the resource cache's budget.
*/
kBudgeted_Flag = 0x1,
uint32_t fFlags;
/**
* This object wraps a GPU object given to us by Skia's client. Skia will not free the
* underlying backend API GPU resources when the GrGpuResource is destroyed. This also
* implies that kBudgeted_Flag is not set.
*/
kWrapped_Flag = 0x2,
mutable size_t fGpuMemorySize;
const uint32_t fUniqueID;
/**
* If set then fContentKey is valid and the resource is cached based on its content.
*/
kContentKeySet_Flag = 0x4,
};
// TODO(bsalomon): Remove GrResourceKey and use different simpler types for content and scratch
// keys.
GrResourceKey fScratchKey;
GrResourceKey fContentKey;
bool fContentKeySet;
// This is not ref'ed but abandon() or release() will be called before the GrGpu object
// is destroyed. Those calls set will this to NULL.
GrGpu* fGpu;
mutable size_t fGpuMemorySize;
uint32_t fFlags;
const uint32_t fUniqueID;
typedef GrIORef<GrGpuResource> INHERITED;
friend class GrIORef<GrGpuResource>; // to access notifyIsPurgable.

View File

@ -490,7 +490,13 @@ GrTexture* GrContext::createUncachedTexture(const GrSurfaceDesc& descIn,
void* srcData,
size_t rowBytes) {
GrSurfaceDesc descCopy = descIn;
return fGpu->createTexture(descCopy, srcData, rowBytes);
GrTexture* texture = fGpu->createTexture(descCopy, srcData, rowBytes);
if (texture) {
// TODO: It'd be nice to be able to do this before creation so we don't boot something
// out of the cache. We could temporarily boost the cache budget.
texture->cacheAccess().setBudgeted(false);
}
return texture;
}
void GrContext::getResourceCacheLimits(int* maxTextures, size_t* maxTextureBytes) const {

View File

@ -19,15 +19,15 @@ static inline GrResourceCache2* get_resource_cache2(GrGpu* gpu) {
}
GrGpuResource::GrGpuResource(GrGpu* gpu, bool isWrapped)
: fGpu(gpu)
: fScratchKey(GrResourceKey::NullScratchKey())
, fGpu(gpu)
, fGpuMemorySize(kInvalidGpuMemorySize)
, fUniqueID(CreateUniqueID())
, fScratchKey(GrResourceKey::NullScratchKey())
, fContentKeySet(false) {
, fUniqueID(CreateUniqueID()) {
if (isWrapped) {
fFlags = kWrapped_FlagBit;
fFlags = kWrapped_Flag;
} else {
fFlags = 0;
// By default all non-wrapped resources are budgeted.
fFlags = kBudgeted_Flag;
}
}
@ -92,16 +92,16 @@ bool GrGpuResource::setContentKey(const GrResourceKey& contentKey) {
if (this->isWrapped()) {
return false;
}
if (fContentKeySet || this->wasDestroyed()) {
if ((fFlags & kContentKeySet_Flag) || this->wasDestroyed()) {
return false;
}
fContentKey = contentKey;
fContentKeySet = true;
fFlags |= kContentKeySet_Flag;
if (!get_resource_cache2(fGpu)->resourceAccess().didSetContentKey(this)) {
fContentKeySet = false;
fFlags &= ~kContentKeySet_Flag;
return false;
}
return true;
@ -136,3 +136,21 @@ uint32_t GrGpuResource::CreateUniqueID() {
} while (id == SK_InvalidUniqueID);
return id;
}
void GrGpuResource::setBudgeted(bool countsAgainstBudget) {
// Wrapped resources never count against the budget, nothing to do. No point in changing the
// budgeting of destroyed resources.
if (this->isWrapped() || this->wasDestroyed()) {
return;
}
uint32_t oldFlags = fFlags;
if (countsAgainstBudget) {
fFlags |= kBudgeted_Flag;
} else {
fFlags &= ~kBudgeted_Flag;
}
if (fFlags != oldFlags) {
get_resource_cache2(fGpu)->resourceAccess().didChangeBudgetStatus(this);
}
}

View File

@ -28,6 +28,11 @@ public:
return fResource->setContentKey(contentKey);
}
/**
* Changes whether the resource counts against the resource cache budget.
*/
void setBudgeted(bool countsAgainstBudget) { fResource->setBudgeted(countsAgainstBudget); }
/**
* Is the resource currently cached as scratch? This means it has a valid scratch key and does
* not have a content key.
@ -48,14 +53,26 @@ public:
* If the resource is currently cached by a content key, the key is returned, otherwise NULL.
*/
const GrResourceKey* getContentKey() const {
if (fResource->fContentKeySet) {
if (fResource->fFlags & GrGpuResource::kContentKeySet_Flag) {
return &fResource->fContentKey;
}
return NULL;
}
/**
* Is the resource object wrapping an externally allocated GPU resource?
*/
bool isWrapped() const { return fResource->isWrapped(); }
/**
* Does the resource count against the resource budget?
*/
bool isBudgeted() const {
bool ret = SkToBool(GrGpuResource::kBudgeted_Flag & fResource->fFlags);
SkASSERT(!(ret && fResource->isWrapped()));
return ret;
}
/**
* Called by the cache to delete the resource under normal circumstances.
*/

View File

@ -100,12 +100,12 @@ void GrResourceCache2::insertResource(GrGpuResource* resource) {
size_t size = resource->gpuMemorySize();
++fCount;
fBytes += resource->gpuMemorySize();
fBytes += size;
#if GR_CACHE_STATS
fHighWaterCount = SkTMax(fCount, fHighWaterCount);
fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
#endif
if (!resource->cacheAccess().isWrapped()) {
if (resource->cacheAccess().isBudgeted()) {
++fBudgetedCount;
fBudgetedBytes += size;
#if GR_CACHE_STATS
@ -114,8 +114,7 @@ void GrResourceCache2::insertResource(GrGpuResource* resource) {
#endif
}
if (!resource->cacheAccess().getScratchKey().isNullScratch()) {
// TODO(bsalomon): Make this assertion possible.
// SkASSERT(!resource->isWrapped());
SkASSERT(!resource->cacheAccess().isWrapped());
fScratchMap.insert(resource->cacheAccess().getScratchKey(), resource);
}
@ -130,7 +129,7 @@ void GrResourceCache2::removeResource(GrGpuResource* resource) {
size_t size = resource->gpuMemorySize();
--fCount;
fBytes -= size;
if (!resource->cacheAccess().isWrapped()) {
if (resource->cacheAccess().isBudgeted()) {
--fBudgetedCount;
fBudgetedBytes -= size;
}
@ -187,7 +186,6 @@ public:
if (resource->internalHasRef() || !resource->cacheAccess().isScratch()) {
return false;
}
return !fRejectPendingIO || !resource->internalHasPendingIO();
}
@ -293,7 +291,7 @@ void GrResourceCache2::didChangeGpuMemorySize(const GrGpuResource* resource, siz
#if GR_CACHE_STATS
fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
#endif
if (!resource->cacheAccess().isWrapped()) {
if (resource->cacheAccess().isBudgeted()) {
fBudgetedBytes += delta;
#if GR_CACHE_STATS
fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
@ -304,6 +302,26 @@ void GrResourceCache2::didChangeGpuMemorySize(const GrGpuResource* resource, siz
this->validate();
}
void GrResourceCache2::didChangeBudgetStatus(GrGpuResource* resource) {
SkASSERT(!fPurging);
SkASSERT(resource);
SkASSERT(this->isInCache(resource));
size_t size = resource->gpuMemorySize();
if (resource->cacheAccess().isBudgeted()) {
++fBudgetedCount;
fBudgetedBytes += size;
this->purgeAsNeeded();
} else {
--fBudgetedCount;
fBudgetedBytes -= size;
}
this->validate();
}
void GrResourceCache2::internalPurgeAsNeeded() {
SkASSERT(!fPurging);
SkASSERT(!fNewlyPurgableResourceWhilePurging);
@ -410,7 +428,7 @@ void GrResourceCache2::validate() const {
SkASSERT(!resource->cacheAccess().isWrapped());
}
if (!resource->cacheAccess().isWrapped()) {
if (resource->cacheAccess().isBudgeted()) {
++budgetedCount;
budgetedBytes += resource->gpuMemorySize();
}
@ -446,6 +464,8 @@ void GrResourceCache2::printStats() const {
int locked = 0;
int scratch = 0;
int wrapped = 0;
size_t unbudgetedSize = 0;
ResourceList::Iter iter;
GrGpuResource* resource = iter.init(fResources, ResourceList::Iter::kHead_IterStart);
@ -457,17 +477,23 @@ void GrResourceCache2::printStats() const {
if (resource->cacheAccess().isScratch()) {
++scratch;
}
if (resource->cacheAccess().isWrapped()) {
++wrapped;
}
if (!resource->cacheAccess().isBudgeted()) {
unbudgetedSize += resource->gpuMemorySize();
}
}
float countUtilization = (100.f * fBudgetedCount) / fMaxCount;
float byteUtilization = (100.f * fBudgetedBytes) / fMaxBytes;
SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes);
SkDebugf(
"\t\tEntry Count: current %d (%d budgeted, %d locked, %d scratch %.2g%% full), high %d\n",
fCount, fBudgetedCount, locked, scratch, countUtilization, fHighWaterCount);
SkDebugf("\t\tEntry Bytes: current %d (budgeted %d, %.2g%% full) high %d\n",
fBytes, fBudgetedBytes, byteUtilization, fHighWaterBytes);
SkDebugf("\t\tEntry Count: current %d"
" (%d budgeted, %d wrapped, %d locked, %d scratch %.2g%% full), high %d\n",
fCount, fBudgetedCount, wrapped, locked, scratch, countUtilization, fHighWaterCount);
SkDebugf("\t\tEntry Bytes: current %d (budgeted %d, %.2g%% full, %d unbudgeted) high %d\n",
fBytes, fBudgetedBytes, byteUtilization, unbudgetedSize, fHighWaterBytes);
}
#endif

View File

@ -22,7 +22,7 @@
* Resources may have optionally have two types of keys:
* 1) A scratch key. This is for resources whose allocations are cached but not their contents.
* Multiple resources can share the same scratch key. This is so a caller can have two
* resource instances with the same properties (e.g. multipass rendering that ping pongs
* resource instances with the same properties (e.g. multipass rendering that ping-pongs
* between two temporary surfaces. The scratch key is set at resource creation time and
* should never change. Resources need not have a scratch key.
* 2) A content key. This key represents the contents of the resource rather than just its
@ -161,6 +161,7 @@ private:
void notifyPurgable(GrGpuResource*);
void didChangeGpuMemorySize(const GrGpuResource*, size_t oldSize);
bool didSetContentKey(GrGpuResource*);
void didChangeBudgetStatus(GrGpuResource*);
void makeResourceMRU(GrGpuResource*);
/// @}
@ -276,6 +277,12 @@ private:
*/
bool didSetContentKey(GrGpuResource* resource) { return fCache->didSetContentKey(resource); }
/**
* Called by GrGpuResources when they change from budgeted to unbudgeted or vice versa.
*/
void didChangeBudgetStatus(GrGpuResource* resource) { fCache->didChangeBudgetStatus(resource); }
// No taking addresses of this type.
const ResourceAccess* operator&() const;
ResourceAccess* operator&();

View File

@ -173,7 +173,7 @@ static void test_no_key(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, 0 == cache2->getResourceBytes());
}
static void test_wrapped(skiatest::Reporter* reporter) {
static void test_budgeting(skiatest::Reporter* reporter) {
SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
REPORTER_ASSERT(reporter, SkToBool(context));
if (NULL == context) {
@ -199,6 +199,9 @@ static void test_wrapped(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, content->cacheAccess().setContentKey(contentKey));
TestResource* wrapped = new TestResource(context->getGpu(), true);
scratch->setSize(12);
TestResource* unbudgeted = new TestResource(context->getGpu());
unbudgeted->setSize(13);
unbudgeted->cacheAccess().setBudgeted(false);
// Make sure we can't add a content key to the wrapped resource
keyData.fData8[0] = 1;
@ -207,49 +210,60 @@ static void test_wrapped(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, NULL == cache2->findAndRefContentResource(contentKey2));
// Make sure sizes are as we expect
REPORTER_ASSERT(reporter, 3 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, 4 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, scratch->gpuMemorySize() + content->gpuMemorySize() +
wrapped->gpuMemorySize() == cache2->getResourceBytes());
wrapped->gpuMemorySize() + unbudgeted->gpuMemorySize() ==
cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 2 == cache2->getBudgetedResourceCount());
REPORTER_ASSERT(reporter, scratch->gpuMemorySize() + content->gpuMemorySize() ==
cache2->getBudgetedResourceBytes());
// Our refs mean that the resources are non purgable.
cache2->purgeAllUnlocked();
REPORTER_ASSERT(reporter, 3 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, 4 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, scratch->gpuMemorySize() + content->gpuMemorySize() +
wrapped->gpuMemorySize() == cache2->getResourceBytes());
wrapped->gpuMemorySize() + unbudgeted->gpuMemorySize() ==
cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 2 == cache2->getBudgetedResourceCount());
REPORTER_ASSERT(reporter, scratch->gpuMemorySize() + content->gpuMemorySize() ==
cache2->getBudgetedResourceBytes());
// Unreffing the wrapped resource should free it right away.
wrapped->unref();
REPORTER_ASSERT(reporter, 2 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, scratch->gpuMemorySize() + content->gpuMemorySize() ==
cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 3 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, scratch->gpuMemorySize() + content->gpuMemorySize() +
unbudgeted->gpuMemorySize() == cache2->getResourceBytes());
// Now try freeing the other two resources first
// Now try freeing the budgeted resources first
wrapped = new TestResource(context->getGpu(), true);
scratch->setSize(12);
content->unref();
cache2->purgeAllUnlocked();
REPORTER_ASSERT(reporter, 2 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, scratch->gpuMemorySize() + wrapped->gpuMemorySize() ==
cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 3 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, scratch->gpuMemorySize() + wrapped->gpuMemorySize() +
unbudgeted->gpuMemorySize() == cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 1 == cache2->getBudgetedResourceCount());
REPORTER_ASSERT(reporter, scratch->gpuMemorySize() == cache2->getBudgetedResourceBytes());
scratch->unref();
cache2->purgeAllUnlocked();
REPORTER_ASSERT(reporter, 1 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, wrapped->gpuMemorySize() == cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 2 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, unbudgeted->gpuMemorySize() + wrapped->gpuMemorySize() ==
cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceCount());
REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceBytes());
wrapped->unref();
REPORTER_ASSERT(reporter, 1 == cache2->getResourceCount());
REPORTER_ASSERT(reporter, unbudgeted->gpuMemorySize() == cache2->getResourceBytes());
REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceCount());
REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceBytes());
unbudgeted->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());
}
static void test_duplicate_scratch_key(skiatest::Reporter* reporter) {
@ -578,7 +592,7 @@ DEF_GPUTEST(ResourceCache, reporter, factory) {
// The below tests create their own mock contexts.
test_no_key(reporter);
test_wrapped(reporter);
test_budgeting(reporter);
test_duplicate_content_key(reporter);
test_duplicate_scratch_key(reporter);
test_purge_invalidated(reporter);