Index: src/gpu/GrResourceCache.cpp |
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a73d11703db0ddd1686916fd8634b340e12661cf |
--- /dev/null |
+++ b/src/gpu/GrResourceCache.cpp |
@@ -0,0 +1,393 @@ |
+ |
+/* |
+ * Copyright 2010 Google Inc. |
+ * |
+ * Use of this source code is governed by a BSD-style license that can be |
+ * found in the LICENSE file. |
+ */ |
+ |
+#include "GrResourceCache.h" |
+#include "GrGpuResource.h" |
+#include "GrGpuResourceCacheAccess.h" |
+#include "GrTexturePriv.h" |
+ |
+DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage); |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+ |
+void GrGpuResource::didChangeGpuMemorySize() const { |
+ fGpuMemorySize = kInvalidGpuMemorySize; |
+ if (this->cacheAccess().isInCache()) { |
+ fCacheEntry->didChangeResourceSize(); |
+ } |
+} |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+ |
+GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() { |
+ static int32_t gNextType = 0; |
+ |
+ int32_t type = sk_atomic_inc(&gNextType); |
+ if (type >= (1 << 8 * sizeof(ResourceType))) { |
+ SkFAIL("Too many Resource Types"); |
+ } |
+ |
+ return static_cast<ResourceType>(type); |
+} |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+ |
+GrResourceCacheEntry::GrResourceCacheEntry(GrResourceCache* resourceCache, GrGpuResource* resource) |
+ : fResourceCache(resourceCache), |
+ fResource(resource), |
+ fCachedSize(resource->gpuMemorySize()) { |
+ // we assume ownership of the resource, and will unref it when we die |
+ SkASSERT(resource); |
+ resource->ref(); |
+} |
+ |
+GrResourceCacheEntry::~GrResourceCacheEntry() { |
+ // We're relying on having the cache entry to remove this from GrResourceCache2's content hash. |
+ // fResource->setCacheEntry(NULL); |
+ fResource->unref(); |
+} |
+ |
+#ifdef SK_DEBUG |
+void GrResourceCacheEntry::validate() const { |
+ SkASSERT(fResourceCache); |
+ SkASSERT(fResource); |
+ SkASSERT(fResource->cacheAccess().getCacheEntry() == this); |
+ SkASSERT(fResource->gpuMemorySize() == fCachedSize); |
+ fResource->validate(); |
+} |
+#endif |
+ |
+void GrResourceCacheEntry::didChangeResourceSize() { |
+ size_t oldSize = fCachedSize; |
+ fCachedSize = fResource->gpuMemorySize(); |
+ if (fCachedSize > oldSize) { |
+ fResourceCache->didIncreaseResourceSize(this, fCachedSize - oldSize); |
+ } else if (fCachedSize < oldSize) { |
+ fResourceCache->didDecreaseResourceSize(this, oldSize - fCachedSize); |
+ } |
+} |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+ |
+GrResourceCache::GrResourceCache(const GrDrawTargetCaps* caps, int maxCount, size_t maxBytes) |
+ : fMaxCount(maxCount) |
+ , fMaxBytes(maxBytes) |
+ , fCaps(SkRef(caps)) { |
+#if GR_CACHE_STATS |
+ fHighWaterEntryCount = 0; |
+ fHighWaterEntryBytes = 0; |
+#endif |
+ |
+ fEntryCount = 0; |
+ fEntryBytes = 0; |
+ |
+ fPurging = false; |
+ |
+ fOverbudgetCB = NULL; |
+ fOverbudgetData = NULL; |
+} |
+ |
+GrResourceCache::~GrResourceCache() { |
+ GrAutoResourceCacheValidate atcv(this); |
+ |
+ EntryList::Iter iter; |
+ |
+ // Unlike the removeAll, here we really remove everything, including locked resources. |
+ while (GrResourceCacheEntry* entry = fList.head()) { |
+ GrAutoResourceCacheValidate atcv(this); |
+ |
+ // remove from our llist |
+ this->internalDetach(entry); |
+ |
+ delete entry; |
+ } |
+} |
+ |
+void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ |
+ if (maxResources) { |
+ *maxResources = fMaxCount; |
+ } |
+ if (maxResourceBytes) { |
+ *maxResourceBytes = fMaxBytes; |
+ } |
+} |
+ |
+void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) { |
+ bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes); |
+ |
+ fMaxCount = maxResources; |
+ fMaxBytes = maxResourceBytes; |
+ |
+ if (smaller) { |
+ this->purgeAsNeeded(); |
+ } |
+} |
+ |
+void GrResourceCache::internalDetach(GrResourceCacheEntry* entry) { |
+ fList.remove(entry); |
+ fEntryCount -= 1; |
+ fEntryBytes -= entry->fCachedSize; |
+} |
+ |
+void GrResourceCache::attachToHead(GrResourceCacheEntry* entry) { |
+ fList.addToHead(entry); |
+ |
+ fEntryCount += 1; |
+ fEntryBytes += entry->fCachedSize; |
+ |
+#if GR_CACHE_STATS |
+ if (fHighWaterEntryCount < fEntryCount) { |
+ fHighWaterEntryCount = fEntryCount; |
+ } |
+ if (fHighWaterEntryBytes < fEntryBytes) { |
+ fHighWaterEntryBytes = fEntryBytes; |
+ } |
+#endif |
+} |
+ |
+ |
+void GrResourceCache::makeResourceMRU(GrGpuResource* resource) { |
+ GrResourceCacheEntry* entry = resource->cacheAccess().getCacheEntry(); |
+ if (entry) { |
+ this->internalDetach(entry); |
+ this->attachToHead(entry); |
+ } |
+} |
+ |
+void GrResourceCache::notifyPurgable(const GrGpuResource* resource) { |
+ // Remove scratch textures from the cache the moment they become purgeable if |
+ // scratch texture reuse is turned off. |
+ SkASSERT(resource->cacheAccess().getCacheEntry()); |
+ if (resource->cacheAccess().isScratch()) { |
+ const GrResourceKey& key = resource->cacheAccess().getScratchKey(); |
+ if (key.getResourceType() == GrTexturePriv::ResourceType() && |
+ !fCaps->reuseScratchTextures() && |
+ !(static_cast<const GrSurface*>(resource)->desc().fFlags & kRenderTarget_GrSurfaceFlag)) { |
+ this->deleteResource(resource->cacheAccess().getCacheEntry()); |
+ } |
+ } |
+} |
+ |
+bool GrResourceCache::addResource(const GrResourceKey& key, GrGpuResource* resource) { |
+ if (NULL != resource->cacheAccess().getCacheEntry()) { |
+ return false; |
+ } |
+ |
+ if (key.isScratch()) { |
+ SkASSERT(resource->cacheAccess().isScratch()); |
+ SkASSERT(key == resource->cacheAccess().getScratchKey()); |
+ } else { |
+ if (!resource->cacheAccess().setContentKey(key)) { |
+ return false; |
+ } |
+ } |
+ |
+ // we don't expect to create new resources during a purge. In theory |
+ // this could cause purgeAsNeeded() into an infinite loop (e.g. |
+ // each resource destroyed creates and locks 2 resources and |
+ // unlocks 1 thereby causing a new purge). |
+ SkASSERT(!fPurging); |
+ GrAutoResourceCacheValidate atcv(this); |
+ |
+ GrResourceCacheEntry* entry = SkNEW_ARGS(GrResourceCacheEntry, (this, resource)); |
+ resource->cacheAccess().setCacheEntry(entry); |
+ |
+ this->attachToHead(entry); |
+ this->purgeAsNeeded(); |
+ return true; |
+} |
+ |
+void GrResourceCache::didIncreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountInc) { |
+ fEntryBytes += amountInc; |
+ this->purgeAsNeeded(); |
+} |
+ |
+void GrResourceCache::didDecreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountDec) { |
+ fEntryBytes -= amountDec; |
+#ifdef SK_DEBUG |
+ this->validate(); |
+#endif |
+} |
+ |
+/** |
+ * Destroying a resource may potentially trigger the unlock of additional |
+ * resources which in turn will trigger a nested purge. We block the nested |
+ * purge using the fPurging variable. However, the initial purge will keep |
+ * looping until either all resources in the cache are unlocked or we've met |
+ * the budget. There is an assertion in createAndLock to check against a |
+ * resource's destructor inserting new resources into the cache. If these |
+ * new resources were unlocked before purgeAsNeeded completed it could |
+ * potentially make purgeAsNeeded loop infinitely. |
+ * |
+ * extraCount and extraBytes are added to the current resource totals to account |
+ * for incoming resources (e.g., GrContext is about to add 10MB split between |
+ * 10 textures). |
+ */ |
+void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { |
+ if (fPurging) { |
+ return; |
+ } |
+ |
+ fPurging = true; |
+ |
+ this->internalPurge(extraCount, extraBytes); |
+ if (((fEntryCount+extraCount) > fMaxCount || |
+ (fEntryBytes+extraBytes) > fMaxBytes) && |
+ fOverbudgetCB) { |
+ // Despite the purge we're still over budget. See if Ganesh can |
+ // release some resources and purge again. |
+ if ((*fOverbudgetCB)(fOverbudgetData)) { |
+ this->internalPurge(extraCount, extraBytes); |
+ } |
+ } |
+ |
+ fPurging = false; |
+} |
+ |
+void GrResourceCache::purgeInvalidated() { |
+ // TODO: Implement this in GrResourceCache2. |
+} |
+ |
+void GrResourceCache::deleteResource(GrResourceCacheEntry* entry) { |
+ SkASSERT(entry->fResource->isPurgable()); |
+ // remove from our llist |
+ this->internalDetach(entry); |
+ delete entry; |
+} |
+ |
+void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) { |
+ SkASSERT(fPurging); |
+ |
+ bool withinBudget = false; |
+ bool changed = false; |
+ |
+ // The purging process is repeated several times since one pass |
+ // may free up other resources |
+ do { |
+ EntryList::Iter iter; |
+ |
+ changed = false; |
+ |
+ // Note: the following code relies on the fact that the |
+ // doubly linked list doesn't invalidate its data/pointers |
+ // outside of the specific area where a deletion occurs (e.g., |
+ // in internalDetach) |
+ GrResourceCacheEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); |
+ |
+ while (entry) { |
+ GrAutoResourceCacheValidate atcv(this); |
+ |
+ if ((fEntryCount+extraCount) <= fMaxCount && |
+ (fEntryBytes+extraBytes) <= fMaxBytes) { |
+ withinBudget = true; |
+ break; |
+ } |
+ |
+ GrResourceCacheEntry* prev = iter.prev(); |
+ if (entry->fResource->isPurgable()) { |
+ changed = true; |
+ this->deleteResource(entry); |
+ } |
+ entry = prev; |
+ } |
+ } while (!withinBudget && changed); |
+} |
+ |
+void GrResourceCache::purgeAllUnlocked() { |
+ GrAutoResourceCacheValidate atcv(this); |
+ |
+ // we can have one GrCacheable holding a lock on another |
+ // so we don't want to just do a simple loop kicking each |
+ // entry out. Instead change the budget and purge. |
+ |
+ size_t savedMaxBytes = fMaxBytes; |
+ int savedMaxCount = fMaxCount; |
+ fMaxBytes = (size_t) -1; |
+ fMaxCount = 0; |
+ this->purgeAsNeeded(); |
+ |
+ fMaxBytes = savedMaxBytes; |
+ fMaxCount = savedMaxCount; |
+} |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+ |
+#ifdef SK_DEBUG |
+size_t GrResourceCache::countBytes(const EntryList& list) { |
+ size_t bytes = 0; |
+ |
+ EntryList::Iter iter; |
+ |
+ const GrResourceCacheEntry* entry = iter.init(const_cast<EntryList&>(list), |
+ EntryList::Iter::kTail_IterStart); |
+ |
+ for ( ; entry; entry = iter.prev()) { |
+ bytes += entry->resource()->gpuMemorySize(); |
+ } |
+ return bytes; |
+} |
+ |
+static bool both_zero_or_nonzero(int count, size_t bytes) { |
+ return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); |
+} |
+ |
+void GrResourceCache::validate() const { |
+ fList.validate(); |
+ SkASSERT(both_zero_or_nonzero(fEntryCount, fEntryBytes)); |
+ |
+ EntryList::Iter iter; |
+ |
+ // check that the shareable entries are okay |
+ const GrResourceCacheEntry* entry = iter.init(const_cast<EntryList&>(fList), |
+ EntryList::Iter::kHead_IterStart); |
+ |
+ int count = 0; |
+ for ( ; entry; entry = iter.next()) { |
+ entry->validate(); |
+ count += 1; |
+ } |
+ SkASSERT(count == fEntryCount); |
+ |
+ size_t bytes = this->countBytes(fList); |
+ SkASSERT(bytes == fEntryBytes); |
+ SkASSERT(fList.countEntries() == fEntryCount); |
+} |
+#endif // SK_DEBUG |
+ |
+#if GR_CACHE_STATS |
+ |
+void GrResourceCache::printStats() { |
+ int locked = 0; |
+ int scratch = 0; |
+ |
+ EntryList::Iter iter; |
+ |
+ GrResourceCacheEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); |
+ |
+ for ( ; entry; entry = iter.prev()) { |
+ if (!entry->fResource->isPurgable()) { |
+ ++locked; |
+ } |
+ if (entry->fResource->cacheAccess().isScratch()) { |
+ ++scratch; |
+ } |
+ } |
+ |
+ float countUtilization = (100.f * fEntryCount) / fMaxCount; |
+ float byteUtilization = (100.f * fEntryBytes) / fMaxBytes; |
+ |
+ SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); |
+ SkDebugf("\t\tEntry Count: current %d (%d locked, %d scratch %.2g%% full), high %d\n", |
+ fEntryCount, locked, scratch, countUtilization, fHighWaterEntryCount); |
+ SkDebugf("\t\tEntry Bytes: current %d (%.2g%% full) high %d\n", |
+ fEntryBytes, byteUtilization, fHighWaterEntryBytes); |
+} |
+ |
+#endif |
+ |
+/////////////////////////////////////////////////////////////////////////////// |