michael@0: michael@0: /* michael@0: * Copyright 2010 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: michael@0: #include "GrResourceCache.h" michael@0: #include "GrResource.h" michael@0: michael@0: DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage); michael@0: michael@0: GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() { michael@0: static int32_t gNextType = 0; michael@0: michael@0: int32_t type = sk_atomic_inc(&gNextType); michael@0: if (type >= (1 << 8 * sizeof(ResourceType))) { michael@0: GrCrash("Too many Resource Types"); michael@0: } michael@0: michael@0: return static_cast(type); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource) michael@0: : fKey(key), fResource(resource) { michael@0: // we assume ownership of the resource, and will unref it when we die michael@0: SkASSERT(resource); michael@0: resource->ref(); michael@0: } michael@0: michael@0: GrResourceEntry::~GrResourceEntry() { michael@0: fResource->setCacheEntry(NULL); michael@0: fResource->unref(); michael@0: } michael@0: michael@0: #ifdef SK_DEBUG michael@0: void GrResourceEntry::validate() const { michael@0: SkASSERT(fResource); michael@0: SkASSERT(fResource->getCacheEntry() == this); michael@0: fResource->validate(); michael@0: } michael@0: #endif michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) : michael@0: fMaxCount(maxCount), michael@0: fMaxBytes(maxBytes) { michael@0: #if GR_CACHE_STATS michael@0: fHighWaterEntryCount = 0; michael@0: fHighWaterEntryBytes = 0; michael@0: fHighWaterClientDetachedCount = 0; michael@0: fHighWaterClientDetachedBytes = 0; michael@0: #endif michael@0: michael@0: fEntryCount = 0; michael@0: fEntryBytes = 0; michael@0: fClientDetachedCount = 0; michael@0: fClientDetachedBytes = 0; michael@0: michael@0: fPurging = false; michael@0: michael@0: fOverbudgetCB = NULL; michael@0: fOverbudgetData = NULL; michael@0: } michael@0: michael@0: GrResourceCache::~GrResourceCache() { michael@0: GrAutoResourceCacheValidate atcv(this); michael@0: michael@0: EntryList::Iter iter; michael@0: michael@0: // Unlike the removeAll, here we really remove everything, including locked resources. michael@0: while (GrResourceEntry* entry = fList.head()) { michael@0: GrAutoResourceCacheValidate atcv(this); michael@0: michael@0: // remove from our cache michael@0: fCache.remove(entry->fKey, entry); michael@0: michael@0: // remove from our llist michael@0: this->internalDetach(entry); michael@0: michael@0: delete entry; michael@0: } michael@0: } michael@0: michael@0: void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ michael@0: if (NULL != maxResources) { michael@0: *maxResources = fMaxCount; michael@0: } michael@0: if (NULL != maxResourceBytes) { michael@0: *maxResourceBytes = fMaxBytes; michael@0: } michael@0: } michael@0: michael@0: void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) { michael@0: bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes); michael@0: michael@0: fMaxCount = maxResources; michael@0: fMaxBytes = maxResourceBytes; michael@0: michael@0: if (smaller) { michael@0: this->purgeAsNeeded(); michael@0: } michael@0: } michael@0: michael@0: void GrResourceCache::internalDetach(GrResourceEntry* entry, michael@0: BudgetBehaviors behavior) { michael@0: fList.remove(entry); michael@0: michael@0: // update our stats michael@0: if (kIgnore_BudgetBehavior == behavior) { michael@0: fClientDetachedCount += 1; michael@0: fClientDetachedBytes += entry->resource()->sizeInBytes(); michael@0: michael@0: #if GR_CACHE_STATS michael@0: if (fHighWaterClientDetachedCount < fClientDetachedCount) { michael@0: fHighWaterClientDetachedCount = fClientDetachedCount; michael@0: } michael@0: if (fHighWaterClientDetachedBytes < fClientDetachedBytes) { michael@0: fHighWaterClientDetachedBytes = fClientDetachedBytes; michael@0: } michael@0: #endif michael@0: michael@0: } else { michael@0: SkASSERT(kAccountFor_BudgetBehavior == behavior); michael@0: michael@0: fEntryCount -= 1; michael@0: fEntryBytes -= entry->resource()->sizeInBytes(); michael@0: } michael@0: } michael@0: michael@0: void GrResourceCache::attachToHead(GrResourceEntry* entry, michael@0: BudgetBehaviors behavior) { michael@0: fList.addToHead(entry); michael@0: michael@0: // update our stats michael@0: if (kIgnore_BudgetBehavior == behavior) { michael@0: fClientDetachedCount -= 1; michael@0: fClientDetachedBytes -= entry->resource()->sizeInBytes(); michael@0: } else { michael@0: SkASSERT(kAccountFor_BudgetBehavior == behavior); michael@0: michael@0: fEntryCount += 1; michael@0: fEntryBytes += entry->resource()->sizeInBytes(); michael@0: michael@0: #if GR_CACHE_STATS michael@0: if (fHighWaterEntryCount < fEntryCount) { michael@0: fHighWaterEntryCount = fEntryCount; michael@0: } michael@0: if (fHighWaterEntryBytes < fEntryBytes) { michael@0: fHighWaterEntryBytes = fEntryBytes; michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: // This functor just searches for an entry with only a single ref (from michael@0: // the texture cache itself). Presumably in this situation no one else michael@0: // is relying on the texture. michael@0: class GrTFindUnreffedFunctor { michael@0: public: michael@0: bool operator()(const GrResourceEntry* entry) const { michael@0: return entry->resource()->unique(); michael@0: } michael@0: }; michael@0: michael@0: GrResource* GrResourceCache::find(const GrResourceKey& key, uint32_t ownershipFlags) { michael@0: GrAutoResourceCacheValidate atcv(this); michael@0: michael@0: GrResourceEntry* entry = NULL; michael@0: michael@0: if (ownershipFlags & kNoOtherOwners_OwnershipFlag) { michael@0: GrTFindUnreffedFunctor functor; michael@0: michael@0: entry = fCache.find(key, functor); michael@0: } else { michael@0: entry = fCache.find(key); michael@0: } michael@0: michael@0: if (NULL == entry) { michael@0: return NULL; michael@0: } michael@0: michael@0: if (ownershipFlags & kHide_OwnershipFlag) { michael@0: this->makeExclusive(entry); michael@0: } else { michael@0: // Make this resource MRU michael@0: this->internalDetach(entry); michael@0: this->attachToHead(entry); michael@0: } michael@0: michael@0: return entry->fResource; michael@0: } michael@0: michael@0: void GrResourceCache::addResource(const GrResourceKey& key, michael@0: GrResource* resource, michael@0: uint32_t ownershipFlags) { michael@0: SkASSERT(NULL == resource->getCacheEntry()); michael@0: // we don't expect to create new resources during a purge. In theory michael@0: // this could cause purgeAsNeeded() into an infinite loop (e.g. michael@0: // each resource destroyed creates and locks 2 resources and michael@0: // unlocks 1 thereby causing a new purge). michael@0: SkASSERT(!fPurging); michael@0: GrAutoResourceCacheValidate atcv(this); michael@0: michael@0: GrResourceEntry* entry = SkNEW_ARGS(GrResourceEntry, (key, resource)); michael@0: resource->setCacheEntry(entry); michael@0: michael@0: this->attachToHead(entry); michael@0: fCache.insert(key, entry); michael@0: michael@0: if (ownershipFlags & kHide_OwnershipFlag) { michael@0: this->makeExclusive(entry); michael@0: } michael@0: michael@0: } michael@0: michael@0: void GrResourceCache::makeExclusive(GrResourceEntry* entry) { michael@0: GrAutoResourceCacheValidate atcv(this); michael@0: michael@0: // When scratch textures are detached (to hide them from future finds) they michael@0: // still count against the resource budget michael@0: this->internalDetach(entry, kIgnore_BudgetBehavior); michael@0: fCache.remove(entry->key(), entry); michael@0: michael@0: #ifdef SK_DEBUG michael@0: fExclusiveList.addToHead(entry); michael@0: #endif michael@0: } michael@0: michael@0: void GrResourceCache::removeInvalidResource(GrResourceEntry* entry) { michael@0: // If the resource went invalid while it was detached then purge it michael@0: // This can happen when a 3D context was lost, michael@0: // the client called GrContext::contextDestroyed() to notify Gr, michael@0: // and then later an SkGpuDevice's destructor releases its backing michael@0: // texture (which was invalidated at contextDestroyed time). michael@0: fClientDetachedCount -= 1; michael@0: fEntryCount -= 1; michael@0: size_t size = entry->resource()->sizeInBytes(); michael@0: fClientDetachedBytes -= size; michael@0: fEntryBytes -= size; michael@0: } michael@0: michael@0: void GrResourceCache::makeNonExclusive(GrResourceEntry* entry) { michael@0: GrAutoResourceCacheValidate atcv(this); michael@0: michael@0: #ifdef SK_DEBUG michael@0: fExclusiveList.remove(entry); michael@0: #endif michael@0: michael@0: if (entry->resource()->isValid()) { michael@0: // Since scratch textures still count against the cache budget even michael@0: // when they have been removed from the cache, re-adding them doesn't michael@0: // alter the budget information. michael@0: attachToHead(entry, kIgnore_BudgetBehavior); michael@0: fCache.insert(entry->key(), entry); michael@0: } else { michael@0: this->removeInvalidResource(entry); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Destroying a resource may potentially trigger the unlock of additional michael@0: * resources which in turn will trigger a nested purge. We block the nested michael@0: * purge using the fPurging variable. However, the initial purge will keep michael@0: * looping until either all resources in the cache are unlocked or we've met michael@0: * the budget. There is an assertion in createAndLock to check against a michael@0: * resource's destructor inserting new resources into the cache. If these michael@0: * new resources were unlocked before purgeAsNeeded completed it could michael@0: * potentially make purgeAsNeeded loop infinitely. michael@0: * michael@0: * extraCount and extraBytes are added to the current resource totals to account michael@0: * for incoming resources (e.g., GrContext is about to add 10MB split between michael@0: * 10 textures). michael@0: */ michael@0: void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { michael@0: if (fPurging) { michael@0: return; michael@0: } michael@0: michael@0: fPurging = true; michael@0: michael@0: this->purgeInvalidated(); michael@0: michael@0: this->internalPurge(extraCount, extraBytes); michael@0: if (((fEntryCount+extraCount) > fMaxCount || michael@0: (fEntryBytes+extraBytes) > fMaxBytes) && michael@0: NULL != fOverbudgetCB) { michael@0: // Despite the purge we're still over budget. See if Ganesh can michael@0: // release some resources and purge again. michael@0: if ((*fOverbudgetCB)(fOverbudgetData)) { michael@0: this->internalPurge(extraCount, extraBytes); michael@0: } michael@0: } michael@0: michael@0: fPurging = false; michael@0: } michael@0: michael@0: void GrResourceCache::purgeInvalidated() { michael@0: SkTDArray invalidated; michael@0: fInvalidationInbox.poll(&invalidated); michael@0: michael@0: for (int i = 0; i < invalidated.count(); i++) { michael@0: // We're somewhat missing an opportunity here. We could use the michael@0: // default find functor that gives us back resources whether we own michael@0: // them exclusively or not, and when they're not exclusively owned mark michael@0: // them for purging later when they do become exclusively owned. michael@0: // michael@0: // This is complicated and confusing. May try this in the future. For michael@0: // now, these resources are just LRU'd as if we never got the message. michael@0: while (GrResourceEntry* entry = fCache.find(invalidated[i].key, GrTFindUnreffedFunctor())) { michael@0: this->deleteResource(entry); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void GrResourceCache::deleteResource(GrResourceEntry* entry) { michael@0: SkASSERT(1 == entry->fResource->getRefCnt()); michael@0: michael@0: // remove from our cache michael@0: fCache.remove(entry->key(), entry); michael@0: michael@0: // remove from our llist michael@0: this->internalDetach(entry); michael@0: delete entry; michael@0: } michael@0: michael@0: void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) { michael@0: SkASSERT(fPurging); michael@0: michael@0: bool withinBudget = false; michael@0: bool changed = false; michael@0: michael@0: // The purging process is repeated several times since one pass michael@0: // may free up other resources michael@0: do { michael@0: EntryList::Iter iter; michael@0: michael@0: changed = false; michael@0: michael@0: // Note: the following code relies on the fact that the michael@0: // doubly linked list doesn't invalidate its data/pointers michael@0: // outside of the specific area where a deletion occurs (e.g., michael@0: // in internalDetach) michael@0: GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); michael@0: michael@0: while (NULL != entry) { michael@0: GrAutoResourceCacheValidate atcv(this); michael@0: michael@0: if ((fEntryCount+extraCount) <= fMaxCount && michael@0: (fEntryBytes+extraBytes) <= fMaxBytes) { michael@0: withinBudget = true; michael@0: break; michael@0: } michael@0: michael@0: GrResourceEntry* prev = iter.prev(); michael@0: if (entry->fResource->unique()) { michael@0: changed = true; michael@0: this->deleteResource(entry); michael@0: } michael@0: entry = prev; michael@0: } michael@0: } while (!withinBudget && changed); michael@0: } michael@0: michael@0: void GrResourceCache::purgeAllUnlocked() { michael@0: GrAutoResourceCacheValidate atcv(this); michael@0: michael@0: // we can have one GrResource holding a lock on another michael@0: // so we don't want to just do a simple loop kicking each michael@0: // entry out. Instead change the budget and purge. michael@0: michael@0: size_t savedMaxBytes = fMaxBytes; michael@0: int savedMaxCount = fMaxCount; michael@0: fMaxBytes = (size_t) -1; michael@0: fMaxCount = 0; michael@0: this->purgeAsNeeded(); michael@0: michael@0: #ifdef SK_DEBUG michael@0: SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount); michael@0: SkASSERT(countBytes(fExclusiveList) == fClientDetachedBytes); michael@0: if (!fCache.count()) { michael@0: // Items may have been detached from the cache (such as the backing michael@0: // texture for an SkGpuDevice). The above purge would not have removed michael@0: // them. michael@0: SkASSERT(fEntryCount == fClientDetachedCount); michael@0: SkASSERT(fEntryBytes == fClientDetachedBytes); michael@0: SkASSERT(fList.isEmpty()); michael@0: } michael@0: #endif michael@0: michael@0: fMaxBytes = savedMaxBytes; michael@0: fMaxCount = savedMaxCount; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #ifdef SK_DEBUG michael@0: size_t GrResourceCache::countBytes(const EntryList& list) { michael@0: size_t bytes = 0; michael@0: michael@0: EntryList::Iter iter; michael@0: michael@0: const GrResourceEntry* entry = iter.init(const_cast(list), michael@0: EntryList::Iter::kTail_IterStart); michael@0: michael@0: for ( ; NULL != entry; entry = iter.prev()) { michael@0: bytes += entry->resource()->sizeInBytes(); michael@0: } michael@0: return bytes; michael@0: } michael@0: michael@0: static bool both_zero_or_nonzero(int count, size_t bytes) { michael@0: return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); michael@0: } michael@0: michael@0: void GrResourceCache::validate() const { michael@0: fList.validate(); michael@0: fExclusiveList.validate(); michael@0: SkASSERT(both_zero_or_nonzero(fEntryCount, fEntryBytes)); michael@0: SkASSERT(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes)); michael@0: SkASSERT(fClientDetachedBytes <= fEntryBytes); michael@0: SkASSERT(fClientDetachedCount <= fEntryCount); michael@0: SkASSERT((fEntryCount - fClientDetachedCount) == fCache.count()); michael@0: michael@0: EntryList::Iter iter; michael@0: michael@0: // check that the exclusively held entries are okay michael@0: const GrResourceEntry* entry = iter.init(const_cast(fExclusiveList), michael@0: EntryList::Iter::kHead_IterStart); michael@0: michael@0: for ( ; NULL != entry; entry = iter.next()) { michael@0: entry->validate(); michael@0: } michael@0: michael@0: // check that the shareable entries are okay michael@0: entry = iter.init(const_cast(fList), EntryList::Iter::kHead_IterStart); michael@0: michael@0: int count = 0; michael@0: for ( ; NULL != entry; entry = iter.next()) { michael@0: entry->validate(); michael@0: SkASSERT(fCache.find(entry->key())); michael@0: count += 1; michael@0: } michael@0: SkASSERT(count == fEntryCount - fClientDetachedCount); michael@0: michael@0: size_t bytes = countBytes(fList); michael@0: SkASSERT(bytes == fEntryBytes - fClientDetachedBytes); michael@0: michael@0: bytes = countBytes(fExclusiveList); michael@0: SkASSERT(bytes == fClientDetachedBytes); michael@0: michael@0: SkASSERT(fList.countEntries() == fEntryCount - fClientDetachedCount); michael@0: michael@0: SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount); michael@0: } michael@0: #endif // SK_DEBUG michael@0: michael@0: #if GR_CACHE_STATS michael@0: michael@0: void GrResourceCache::printStats() { michael@0: int locked = 0; michael@0: michael@0: EntryList::Iter iter; michael@0: michael@0: GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); michael@0: michael@0: for ( ; NULL != entry; entry = iter.prev()) { michael@0: if (entry->fResource->getRefCnt() > 1) { michael@0: ++locked; michael@0: } michael@0: } michael@0: michael@0: SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); michael@0: SkDebugf("\t\tEntry Count: current %d (%d locked) high %d\n", michael@0: fEntryCount, locked, fHighWaterEntryCount); michael@0: SkDebugf("\t\tEntry Bytes: current %d high %d\n", michael@0: fEntryBytes, fHighWaterEntryBytes); michael@0: SkDebugf("\t\tDetached Entry Count: current %d high %d\n", michael@0: fClientDetachedCount, fHighWaterClientDetachedCount); michael@0: SkDebugf("\t\tDetached Bytes: current %d high %d\n", michael@0: fClientDetachedBytes, fHighWaterClientDetachedBytes); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////////