diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h index c5b8ca5fbb..806a45aa11 100644 --- a/include/gpu/GrGpuResource.h +++ b/include/gpu/GrGpuResource.h @@ -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. diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index 7012988ab8..e7449da23b 100755 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -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 { diff --git a/src/gpu/GrGpuResource.cpp b/src/gpu/GrGpuResource.cpp index ea3756b8dd..8dbbd83ef5 100644 --- a/src/gpu/GrGpuResource.cpp +++ b/src/gpu/GrGpuResource.cpp @@ -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); + } +} diff --git a/src/gpu/GrGpuResourceCacheAccess.h b/src/gpu/GrGpuResourceCacheAccess.h index 7d20fff473..c55bb07774 100644 --- a/src/gpu/GrGpuResourceCacheAccess.h +++ b/src/gpu/GrGpuResourceCacheAccess.h @@ -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. */ diff --git a/src/gpu/GrResourceCache2.cpp b/src/gpu/GrResourceCache2.cpp index d5590d0e20..9764e62329 100644 --- a/src/gpu/GrResourceCache2.cpp +++ b/src/gpu/GrResourceCache2.cpp @@ -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 diff --git a/src/gpu/GrResourceCache2.h b/src/gpu/GrResourceCache2.h index b0394e3c63..9331e9dfd3 100644 --- a/src/gpu/GrResourceCache2.h +++ b/src/gpu/GrResourceCache2.h @@ -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&(); diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp index 5fef3d0e75..930bd1217c 100644 --- a/tests/ResourceCacheTest.cpp +++ b/tests/ResourceCacheTest.cpp @@ -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);