Home | History | Annotate | Download | only in gpu
      1 
      2 /*
      3  * Copyright 2010 Google Inc.
      4  *
      5  * Use of this source code is governed by a BSD-style license that can be
      6  * found in the LICENSE file.
      7  */
      8 
      9 
     10 
     11 #include "GrResourceCache.h"
     12 #include "GrCacheable.h"
     13 
     14 DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage);
     15 
     16 ///////////////////////////////////////////////////////////////////////////////
     17 
     18 void GrCacheable::didChangeGpuMemorySize() const {
     19     if (this->isInCache()) {
     20         fCacheEntry->didChangeResourceSize();
     21     }
     22 }
     23 
     24 ///////////////////////////////////////////////////////////////////////////////
     25 
     26 GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() {
     27     static int32_t gNextType = 0;
     28 
     29     int32_t type = sk_atomic_inc(&gNextType);
     30     if (type >= (1 << 8 * sizeof(ResourceType))) {
     31         SkFAIL("Too many Resource Types");
     32     }
     33 
     34     return static_cast<ResourceType>(type);
     35 }
     36 
     37 ///////////////////////////////////////////////////////////////////////////////
     38 
     39 GrResourceCacheEntry::GrResourceCacheEntry(GrResourceCache* resourceCache,
     40                                            const GrResourceKey& key,
     41                                            GrCacheable* resource)
     42         : fResourceCache(resourceCache),
     43           fKey(key),
     44           fResource(resource),
     45           fCachedSize(resource->gpuMemorySize()),
     46           fIsExclusive(false) {
     47     // we assume ownership of the resource, and will unref it when we die
     48     SkASSERT(resource);
     49     resource->ref();
     50 }
     51 
     52 GrResourceCacheEntry::~GrResourceCacheEntry() {
     53     fResource->setCacheEntry(NULL);
     54     fResource->unref();
     55 }
     56 
     57 #ifdef SK_DEBUG
     58 void GrResourceCacheEntry::validate() const {
     59     SkASSERT(fResourceCache);
     60     SkASSERT(fResource);
     61     SkASSERT(fResource->getCacheEntry() == this);
     62     SkASSERT(fResource->gpuMemorySize() == fCachedSize);
     63     fResource->validate();
     64 }
     65 #endif
     66 
     67 void GrResourceCacheEntry::didChangeResourceSize() {
     68     size_t oldSize = fCachedSize;
     69     fCachedSize = fResource->gpuMemorySize();
     70     if (fCachedSize > oldSize) {
     71         fResourceCache->didIncreaseResourceSize(this, fCachedSize - oldSize);
     72     } else if (fCachedSize < oldSize) {
     73         fResourceCache->didDecreaseResourceSize(this, oldSize - fCachedSize);
     74     }
     75 }
     76 
     77 ///////////////////////////////////////////////////////////////////////////////
     78 
     79 GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) :
     80         fMaxCount(maxCount),
     81         fMaxBytes(maxBytes) {
     82 #if GR_CACHE_STATS
     83     fHighWaterEntryCount          = 0;
     84     fHighWaterEntryBytes          = 0;
     85     fHighWaterClientDetachedCount = 0;
     86     fHighWaterClientDetachedBytes = 0;
     87 #endif
     88 
     89     fEntryCount                   = 0;
     90     fEntryBytes                   = 0;
     91     fClientDetachedCount          = 0;
     92     fClientDetachedBytes          = 0;
     93 
     94     fPurging                      = false;
     95 
     96     fOverbudgetCB                 = NULL;
     97     fOverbudgetData               = NULL;
     98 }
     99 
    100 GrResourceCache::~GrResourceCache() {
    101     GrAutoResourceCacheValidate atcv(this);
    102 
    103     EntryList::Iter iter;
    104 
    105     // Unlike the removeAll, here we really remove everything, including locked resources.
    106     while (GrResourceCacheEntry* entry = fList.head()) {
    107         GrAutoResourceCacheValidate atcv(this);
    108 
    109         // remove from our cache
    110         fCache.remove(entry->fKey, entry);
    111 
    112         // remove from our llist
    113         this->internalDetach(entry);
    114 
    115         delete entry;
    116     }
    117 }
    118 
    119 void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{
    120     if (NULL != maxResources) {
    121         *maxResources = fMaxCount;
    122     }
    123     if (NULL != maxResourceBytes) {
    124         *maxResourceBytes = fMaxBytes;
    125     }
    126 }
    127 
    128 void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) {
    129     bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes);
    130 
    131     fMaxCount = maxResources;
    132     fMaxBytes = maxResourceBytes;
    133 
    134     if (smaller) {
    135         this->purgeAsNeeded();
    136     }
    137 }
    138 
    139 void GrResourceCache::internalDetach(GrResourceCacheEntry* entry,
    140                                      BudgetBehaviors behavior) {
    141     fList.remove(entry);
    142 
    143     // update our stats
    144     if (kIgnore_BudgetBehavior == behavior) {
    145         fClientDetachedCount += 1;
    146         fClientDetachedBytes += entry->fCachedSize;
    147 
    148 #if GR_CACHE_STATS
    149         if (fHighWaterClientDetachedCount < fClientDetachedCount) {
    150             fHighWaterClientDetachedCount = fClientDetachedCount;
    151         }
    152         if (fHighWaterClientDetachedBytes < fClientDetachedBytes) {
    153             fHighWaterClientDetachedBytes = fClientDetachedBytes;
    154         }
    155 #endif
    156 
    157     } else {
    158         SkASSERT(kAccountFor_BudgetBehavior == behavior);
    159 
    160         fEntryCount -= 1;
    161         fEntryBytes -= entry->fCachedSize;
    162     }
    163 }
    164 
    165 void GrResourceCache::attachToHead(GrResourceCacheEntry* entry,
    166                                    BudgetBehaviors behavior) {
    167     fList.addToHead(entry);
    168 
    169     // update our stats
    170     if (kIgnore_BudgetBehavior == behavior) {
    171         fClientDetachedCount -= 1;
    172         fClientDetachedBytes -= entry->fCachedSize;
    173     } else {
    174         SkASSERT(kAccountFor_BudgetBehavior == behavior);
    175 
    176         fEntryCount += 1;
    177         fEntryBytes += entry->fCachedSize;
    178 
    179 #if GR_CACHE_STATS
    180         if (fHighWaterEntryCount < fEntryCount) {
    181             fHighWaterEntryCount = fEntryCount;
    182         }
    183         if (fHighWaterEntryBytes < fEntryBytes) {
    184             fHighWaterEntryBytes = fEntryBytes;
    185         }
    186 #endif
    187     }
    188 }
    189 
    190 // This functor just searches for an entry with only a single ref (from
    191 // the texture cache itself). Presumably in this situation no one else
    192 // is relying on the texture.
    193 class GrTFindUnreffedFunctor {
    194 public:
    195     bool operator()(const GrResourceCacheEntry* entry) const {
    196         return entry->resource()->unique();
    197     }
    198 };
    199 
    200 GrCacheable* GrResourceCache::find(const GrResourceKey& key, uint32_t ownershipFlags) {
    201     GrAutoResourceCacheValidate atcv(this);
    202 
    203     GrResourceCacheEntry* entry = NULL;
    204 
    205     if (ownershipFlags & kNoOtherOwners_OwnershipFlag) {
    206         GrTFindUnreffedFunctor functor;
    207 
    208         entry = fCache.find<GrTFindUnreffedFunctor>(key, functor);
    209     } else {
    210         entry = fCache.find(key);
    211     }
    212 
    213     if (NULL == entry) {
    214         return NULL;
    215     }
    216 
    217     if (ownershipFlags & kHide_OwnershipFlag) {
    218         this->makeExclusive(entry);
    219     } else {
    220         // Make this resource MRU
    221         this->internalDetach(entry);
    222         this->attachToHead(entry);
    223     }
    224 
    225     return entry->fResource;
    226 }
    227 
    228 void GrResourceCache::addResource(const GrResourceKey& key,
    229                                   GrCacheable* resource,
    230                                   uint32_t ownershipFlags) {
    231     SkASSERT(NULL == resource->getCacheEntry());
    232     // we don't expect to create new resources during a purge. In theory
    233     // this could cause purgeAsNeeded() into an infinite loop (e.g.
    234     // each resource destroyed creates and locks 2 resources and
    235     // unlocks 1 thereby causing a new purge).
    236     SkASSERT(!fPurging);
    237     GrAutoResourceCacheValidate atcv(this);
    238 
    239     GrResourceCacheEntry* entry = SkNEW_ARGS(GrResourceCacheEntry, (this, key, resource));
    240     resource->setCacheEntry(entry);
    241 
    242     this->attachToHead(entry);
    243     fCache.insert(key, entry);
    244 
    245     if (ownershipFlags & kHide_OwnershipFlag) {
    246         this->makeExclusive(entry);
    247     }
    248 
    249 }
    250 
    251 void GrResourceCache::makeExclusive(GrResourceCacheEntry* entry) {
    252     GrAutoResourceCacheValidate atcv(this);
    253 
    254     SkASSERT(!entry->fIsExclusive);
    255     entry->fIsExclusive = true;
    256 
    257     // When scratch textures are detached (to hide them from future finds) they
    258     // still count against the resource budget
    259     this->internalDetach(entry, kIgnore_BudgetBehavior);
    260     fCache.remove(entry->key(), entry);
    261 
    262 #ifdef SK_DEBUG
    263     fExclusiveList.addToHead(entry);
    264 #endif
    265 }
    266 
    267 void GrResourceCache::removeInvalidResource(GrResourceCacheEntry* entry) {
    268     // If the resource went invalid while it was detached then purge it
    269     // This can happen when a 3D context was lost,
    270     // the client called GrContext::contextDestroyed() to notify Gr,
    271     // and then later an SkGpuDevice's destructor releases its backing
    272     // texture (which was invalidated at contextDestroyed time).
    273     // TODO: Safely delete the GrResourceCacheEntry as well.
    274     fClientDetachedCount -= 1;
    275     fEntryCount -= 1;
    276     fClientDetachedBytes -= entry->fCachedSize;
    277     fEntryBytes -= entry->fCachedSize;
    278     entry->fCachedSize = 0;
    279 }
    280 
    281 void GrResourceCache::makeNonExclusive(GrResourceCacheEntry* entry) {
    282     GrAutoResourceCacheValidate atcv(this);
    283 
    284 #ifdef SK_DEBUG
    285     fExclusiveList.remove(entry);
    286 #endif
    287 
    288     if (entry->resource()->isValidOnGpu()) {
    289         // Since scratch textures still count against the cache budget even
    290         // when they have been removed from the cache, re-adding them doesn't
    291         // alter the budget information.
    292         attachToHead(entry, kIgnore_BudgetBehavior);
    293         fCache.insert(entry->key(), entry);
    294 
    295         SkASSERT(entry->fIsExclusive);
    296         entry->fIsExclusive = false;
    297     } else {
    298         this->removeInvalidResource(entry);
    299     }
    300 }
    301 
    302 void GrResourceCache::didIncreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountInc) {
    303     fEntryBytes += amountInc;
    304     if (entry->fIsExclusive) {
    305         fClientDetachedBytes += amountInc;
    306     }
    307     this->purgeAsNeeded();
    308 }
    309 
    310 void GrResourceCache::didDecreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountDec) {
    311     fEntryBytes -= amountDec;
    312     if (entry->fIsExclusive) {
    313         fClientDetachedBytes -= amountDec;
    314     }
    315 #ifdef SK_DEBUG
    316     this->validate();
    317 #endif
    318 }
    319 
    320 /**
    321  * Destroying a resource may potentially trigger the unlock of additional
    322  * resources which in turn will trigger a nested purge. We block the nested
    323  * purge using the fPurging variable. However, the initial purge will keep
    324  * looping until either all resources in the cache are unlocked or we've met
    325  * the budget. There is an assertion in createAndLock to check against a
    326  * resource's destructor inserting new resources into the cache. If these
    327  * new resources were unlocked before purgeAsNeeded completed it could
    328  * potentially make purgeAsNeeded loop infinitely.
    329  *
    330  * extraCount and extraBytes are added to the current resource totals to account
    331  * for incoming resources (e.g., GrContext is about to add 10MB split between
    332  * 10 textures).
    333  */
    334 void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) {
    335     if (fPurging) {
    336         return;
    337     }
    338 
    339     fPurging = true;
    340 
    341     this->purgeInvalidated();
    342 
    343     this->internalPurge(extraCount, extraBytes);
    344     if (((fEntryCount+extraCount) > fMaxCount ||
    345         (fEntryBytes+extraBytes) > fMaxBytes) &&
    346         NULL != fOverbudgetCB) {
    347         // Despite the purge we're still over budget. See if Ganesh can
    348         // release some resources and purge again.
    349         if ((*fOverbudgetCB)(fOverbudgetData)) {
    350             this->internalPurge(extraCount, extraBytes);
    351         }
    352     }
    353 
    354     fPurging = false;
    355 }
    356 
    357 void GrResourceCache::purgeInvalidated() {
    358     SkTDArray<GrResourceInvalidatedMessage> invalidated;
    359     fInvalidationInbox.poll(&invalidated);
    360 
    361     for (int i = 0; i < invalidated.count(); i++) {
    362         // We're somewhat missing an opportunity here.  We could use the
    363         // default find functor that gives us back resources whether we own
    364         // them exclusively or not, and when they're not exclusively owned mark
    365         // them for purging later when they do become exclusively owned.
    366         //
    367         // This is complicated and confusing.  May try this in the future.  For
    368         // now, these resources are just LRU'd as if we never got the message.
    369         while (GrResourceCacheEntry* entry = fCache.find(invalidated[i].key, GrTFindUnreffedFunctor())) {
    370             this->deleteResource(entry);
    371         }
    372     }
    373 }
    374 
    375 void GrResourceCache::deleteResource(GrResourceCacheEntry* entry) {
    376     SkASSERT(1 == entry->fResource->getRefCnt());
    377 
    378     // remove from our cache
    379     fCache.remove(entry->key(), entry);
    380 
    381     // remove from our llist
    382     this->internalDetach(entry);
    383     delete entry;
    384 }
    385 
    386 void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) {
    387     SkASSERT(fPurging);
    388 
    389     bool withinBudget = false;
    390     bool changed = false;
    391 
    392     // The purging process is repeated several times since one pass
    393     // may free up other resources
    394     do {
    395         EntryList::Iter iter;
    396 
    397         changed = false;
    398 
    399         // Note: the following code relies on the fact that the
    400         // doubly linked list doesn't invalidate its data/pointers
    401         // outside of the specific area where a deletion occurs (e.g.,
    402         // in internalDetach)
    403         GrResourceCacheEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart);
    404 
    405         while (NULL != entry) {
    406             GrAutoResourceCacheValidate atcv(this);
    407 
    408             if ((fEntryCount+extraCount) <= fMaxCount &&
    409                 (fEntryBytes+extraBytes) <= fMaxBytes) {
    410                 withinBudget = true;
    411                 break;
    412             }
    413 
    414             GrResourceCacheEntry* prev = iter.prev();
    415             if (entry->fResource->unique()) {
    416                 changed = true;
    417                 this->deleteResource(entry);
    418             }
    419             entry = prev;
    420         }
    421     } while (!withinBudget && changed);
    422 }
    423 
    424 void GrResourceCache::purgeAllUnlocked() {
    425     GrAutoResourceCacheValidate atcv(this);
    426 
    427     // we can have one GrCacheable holding a lock on another
    428     // so we don't want to just do a simple loop kicking each
    429     // entry out. Instead change the budget and purge.
    430 
    431     size_t savedMaxBytes = fMaxBytes;
    432     int savedMaxCount = fMaxCount;
    433     fMaxBytes = (size_t) -1;
    434     fMaxCount = 0;
    435     this->purgeAsNeeded();
    436 
    437 #ifdef SK_DEBUG
    438     SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount);
    439     SkASSERT(countBytes(fExclusiveList) == fClientDetachedBytes);
    440     if (!fCache.count()) {
    441         // Items may have been detached from the cache (such as the backing
    442         // texture for an SkGpuDevice). The above purge would not have removed
    443         // them.
    444         SkASSERT(fEntryCount == fClientDetachedCount);
    445         SkASSERT(fEntryBytes == fClientDetachedBytes);
    446         SkASSERT(fList.isEmpty());
    447     }
    448 #endif
    449 
    450     fMaxBytes = savedMaxBytes;
    451     fMaxCount = savedMaxCount;
    452 }
    453 
    454 ///////////////////////////////////////////////////////////////////////////////
    455 
    456 #ifdef SK_DEBUG
    457 size_t GrResourceCache::countBytes(const EntryList& list) {
    458     size_t bytes = 0;
    459 
    460     EntryList::Iter iter;
    461 
    462     const GrResourceCacheEntry* entry = iter.init(const_cast<EntryList&>(list),
    463                                                   EntryList::Iter::kTail_IterStart);
    464 
    465     for ( ; NULL != entry; entry = iter.prev()) {
    466         bytes += entry->resource()->gpuMemorySize();
    467     }
    468     return bytes;
    469 }
    470 
    471 static bool both_zero_or_nonzero(int count, size_t bytes) {
    472     return (count == 0 && bytes == 0) || (count > 0 && bytes > 0);
    473 }
    474 
    475 void GrResourceCache::validate() const {
    476     fList.validate();
    477     fExclusiveList.validate();
    478     SkASSERT(both_zero_or_nonzero(fEntryCount, fEntryBytes));
    479     SkASSERT(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes));
    480     SkASSERT(fClientDetachedBytes <= fEntryBytes);
    481     SkASSERT(fClientDetachedCount <= fEntryCount);
    482     SkASSERT((fEntryCount - fClientDetachedCount) == fCache.count());
    483 
    484     EntryList::Iter iter;
    485 
    486     // check that the exclusively held entries are okay
    487     const GrResourceCacheEntry* entry = iter.init(const_cast<EntryList&>(fExclusiveList),
    488                                                   EntryList::Iter::kHead_IterStart);
    489 
    490     for ( ; NULL != entry; entry = iter.next()) {
    491         entry->validate();
    492     }
    493 
    494     // check that the shareable entries are okay
    495     entry = iter.init(const_cast<EntryList&>(fList), EntryList::Iter::kHead_IterStart);
    496 
    497     int count = 0;
    498     for ( ; NULL != entry; entry = iter.next()) {
    499         entry->validate();
    500         SkASSERT(fCache.find(entry->key()));
    501         count += 1;
    502     }
    503     SkASSERT(count == fEntryCount - fClientDetachedCount);
    504 
    505     size_t bytes = countBytes(fList);
    506     SkASSERT(bytes == fEntryBytes  - fClientDetachedBytes);
    507 
    508     bytes = countBytes(fExclusiveList);
    509     SkASSERT(bytes == fClientDetachedBytes);
    510 
    511     SkASSERT(fList.countEntries() == fEntryCount - fClientDetachedCount);
    512 
    513     SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount);
    514 }
    515 #endif // SK_DEBUG
    516 
    517 #if GR_CACHE_STATS
    518 
    519 void GrResourceCache::printStats() {
    520     int locked = 0;
    521 
    522     EntryList::Iter iter;
    523 
    524     GrResourceCacheEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart);
    525 
    526     for ( ; NULL != entry; entry = iter.prev()) {
    527         if (entry->fResource->getRefCnt() > 1) {
    528             ++locked;
    529         }
    530     }
    531 
    532     SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes);
    533     SkDebugf("\t\tEntry Count: current %d (%d locked) high %d\n",
    534                 fEntryCount, locked, fHighWaterEntryCount);
    535     SkDebugf("\t\tEntry Bytes: current %d high %d\n",
    536                 fEntryBytes, fHighWaterEntryBytes);
    537     SkDebugf("\t\tDetached Entry Count: current %d high %d\n",
    538                 fClientDetachedCount, fHighWaterClientDetachedCount);
    539     SkDebugf("\t\tDetached Bytes: current %d high %d\n",
    540                 fClientDetachedBytes, fHighWaterClientDetachedBytes);
    541 }
    542 
    543 #endif
    544 
    545 ///////////////////////////////////////////////////////////////////////////////
    546