1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/skia/trunk/src/gpu/GrResourceCache.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,492 @@ 1.4 + 1.5 +/* 1.6 + * Copyright 2010 Google Inc. 1.7 + * 1.8 + * Use of this source code is governed by a BSD-style license that can be 1.9 + * found in the LICENSE file. 1.10 + */ 1.11 + 1.12 + 1.13 + 1.14 +#include "GrResourceCache.h" 1.15 +#include "GrResource.h" 1.16 + 1.17 +DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage); 1.18 + 1.19 +GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() { 1.20 + static int32_t gNextType = 0; 1.21 + 1.22 + int32_t type = sk_atomic_inc(&gNextType); 1.23 + if (type >= (1 << 8 * sizeof(ResourceType))) { 1.24 + GrCrash("Too many Resource Types"); 1.25 + } 1.26 + 1.27 + return static_cast<ResourceType>(type); 1.28 +} 1.29 + 1.30 +/////////////////////////////////////////////////////////////////////////////// 1.31 + 1.32 +GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource) 1.33 + : fKey(key), fResource(resource) { 1.34 + // we assume ownership of the resource, and will unref it when we die 1.35 + SkASSERT(resource); 1.36 + resource->ref(); 1.37 +} 1.38 + 1.39 +GrResourceEntry::~GrResourceEntry() { 1.40 + fResource->setCacheEntry(NULL); 1.41 + fResource->unref(); 1.42 +} 1.43 + 1.44 +#ifdef SK_DEBUG 1.45 +void GrResourceEntry::validate() const { 1.46 + SkASSERT(fResource); 1.47 + SkASSERT(fResource->getCacheEntry() == this); 1.48 + fResource->validate(); 1.49 +} 1.50 +#endif 1.51 + 1.52 +/////////////////////////////////////////////////////////////////////////////// 1.53 + 1.54 +GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) : 1.55 + fMaxCount(maxCount), 1.56 + fMaxBytes(maxBytes) { 1.57 +#if GR_CACHE_STATS 1.58 + fHighWaterEntryCount = 0; 1.59 + fHighWaterEntryBytes = 0; 1.60 + fHighWaterClientDetachedCount = 0; 1.61 + fHighWaterClientDetachedBytes = 0; 1.62 +#endif 1.63 + 1.64 + fEntryCount = 0; 1.65 + fEntryBytes = 0; 1.66 + fClientDetachedCount = 0; 1.67 + fClientDetachedBytes = 0; 1.68 + 1.69 + fPurging = false; 1.70 + 1.71 + fOverbudgetCB = NULL; 1.72 + fOverbudgetData = NULL; 1.73 +} 1.74 + 1.75 +GrResourceCache::~GrResourceCache() { 1.76 + GrAutoResourceCacheValidate atcv(this); 1.77 + 1.78 + EntryList::Iter iter; 1.79 + 1.80 + // Unlike the removeAll, here we really remove everything, including locked resources. 1.81 + while (GrResourceEntry* entry = fList.head()) { 1.82 + GrAutoResourceCacheValidate atcv(this); 1.83 + 1.84 + // remove from our cache 1.85 + fCache.remove(entry->fKey, entry); 1.86 + 1.87 + // remove from our llist 1.88 + this->internalDetach(entry); 1.89 + 1.90 + delete entry; 1.91 + } 1.92 +} 1.93 + 1.94 +void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ 1.95 + if (NULL != maxResources) { 1.96 + *maxResources = fMaxCount; 1.97 + } 1.98 + if (NULL != maxResourceBytes) { 1.99 + *maxResourceBytes = fMaxBytes; 1.100 + } 1.101 +} 1.102 + 1.103 +void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) { 1.104 + bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes); 1.105 + 1.106 + fMaxCount = maxResources; 1.107 + fMaxBytes = maxResourceBytes; 1.108 + 1.109 + if (smaller) { 1.110 + this->purgeAsNeeded(); 1.111 + } 1.112 +} 1.113 + 1.114 +void GrResourceCache::internalDetach(GrResourceEntry* entry, 1.115 + BudgetBehaviors behavior) { 1.116 + fList.remove(entry); 1.117 + 1.118 + // update our stats 1.119 + if (kIgnore_BudgetBehavior == behavior) { 1.120 + fClientDetachedCount += 1; 1.121 + fClientDetachedBytes += entry->resource()->sizeInBytes(); 1.122 + 1.123 +#if GR_CACHE_STATS 1.124 + if (fHighWaterClientDetachedCount < fClientDetachedCount) { 1.125 + fHighWaterClientDetachedCount = fClientDetachedCount; 1.126 + } 1.127 + if (fHighWaterClientDetachedBytes < fClientDetachedBytes) { 1.128 + fHighWaterClientDetachedBytes = fClientDetachedBytes; 1.129 + } 1.130 +#endif 1.131 + 1.132 + } else { 1.133 + SkASSERT(kAccountFor_BudgetBehavior == behavior); 1.134 + 1.135 + fEntryCount -= 1; 1.136 + fEntryBytes -= entry->resource()->sizeInBytes(); 1.137 + } 1.138 +} 1.139 + 1.140 +void GrResourceCache::attachToHead(GrResourceEntry* entry, 1.141 + BudgetBehaviors behavior) { 1.142 + fList.addToHead(entry); 1.143 + 1.144 + // update our stats 1.145 + if (kIgnore_BudgetBehavior == behavior) { 1.146 + fClientDetachedCount -= 1; 1.147 + fClientDetachedBytes -= entry->resource()->sizeInBytes(); 1.148 + } else { 1.149 + SkASSERT(kAccountFor_BudgetBehavior == behavior); 1.150 + 1.151 + fEntryCount += 1; 1.152 + fEntryBytes += entry->resource()->sizeInBytes(); 1.153 + 1.154 +#if GR_CACHE_STATS 1.155 + if (fHighWaterEntryCount < fEntryCount) { 1.156 + fHighWaterEntryCount = fEntryCount; 1.157 + } 1.158 + if (fHighWaterEntryBytes < fEntryBytes) { 1.159 + fHighWaterEntryBytes = fEntryBytes; 1.160 + } 1.161 +#endif 1.162 + } 1.163 +} 1.164 + 1.165 +// This functor just searches for an entry with only a single ref (from 1.166 +// the texture cache itself). Presumably in this situation no one else 1.167 +// is relying on the texture. 1.168 +class GrTFindUnreffedFunctor { 1.169 +public: 1.170 + bool operator()(const GrResourceEntry* entry) const { 1.171 + return entry->resource()->unique(); 1.172 + } 1.173 +}; 1.174 + 1.175 +GrResource* GrResourceCache::find(const GrResourceKey& key, uint32_t ownershipFlags) { 1.176 + GrAutoResourceCacheValidate atcv(this); 1.177 + 1.178 + GrResourceEntry* entry = NULL; 1.179 + 1.180 + if (ownershipFlags & kNoOtherOwners_OwnershipFlag) { 1.181 + GrTFindUnreffedFunctor functor; 1.182 + 1.183 + entry = fCache.find<GrTFindUnreffedFunctor>(key, functor); 1.184 + } else { 1.185 + entry = fCache.find(key); 1.186 + } 1.187 + 1.188 + if (NULL == entry) { 1.189 + return NULL; 1.190 + } 1.191 + 1.192 + if (ownershipFlags & kHide_OwnershipFlag) { 1.193 + this->makeExclusive(entry); 1.194 + } else { 1.195 + // Make this resource MRU 1.196 + this->internalDetach(entry); 1.197 + this->attachToHead(entry); 1.198 + } 1.199 + 1.200 + return entry->fResource; 1.201 +} 1.202 + 1.203 +void GrResourceCache::addResource(const GrResourceKey& key, 1.204 + GrResource* resource, 1.205 + uint32_t ownershipFlags) { 1.206 + SkASSERT(NULL == resource->getCacheEntry()); 1.207 + // we don't expect to create new resources during a purge. In theory 1.208 + // this could cause purgeAsNeeded() into an infinite loop (e.g. 1.209 + // each resource destroyed creates and locks 2 resources and 1.210 + // unlocks 1 thereby causing a new purge). 1.211 + SkASSERT(!fPurging); 1.212 + GrAutoResourceCacheValidate atcv(this); 1.213 + 1.214 + GrResourceEntry* entry = SkNEW_ARGS(GrResourceEntry, (key, resource)); 1.215 + resource->setCacheEntry(entry); 1.216 + 1.217 + this->attachToHead(entry); 1.218 + fCache.insert(key, entry); 1.219 + 1.220 + if (ownershipFlags & kHide_OwnershipFlag) { 1.221 + this->makeExclusive(entry); 1.222 + } 1.223 + 1.224 +} 1.225 + 1.226 +void GrResourceCache::makeExclusive(GrResourceEntry* entry) { 1.227 + GrAutoResourceCacheValidate atcv(this); 1.228 + 1.229 + // When scratch textures are detached (to hide them from future finds) they 1.230 + // still count against the resource budget 1.231 + this->internalDetach(entry, kIgnore_BudgetBehavior); 1.232 + fCache.remove(entry->key(), entry); 1.233 + 1.234 +#ifdef SK_DEBUG 1.235 + fExclusiveList.addToHead(entry); 1.236 +#endif 1.237 +} 1.238 + 1.239 +void GrResourceCache::removeInvalidResource(GrResourceEntry* entry) { 1.240 + // If the resource went invalid while it was detached then purge it 1.241 + // This can happen when a 3D context was lost, 1.242 + // the client called GrContext::contextDestroyed() to notify Gr, 1.243 + // and then later an SkGpuDevice's destructor releases its backing 1.244 + // texture (which was invalidated at contextDestroyed time). 1.245 + fClientDetachedCount -= 1; 1.246 + fEntryCount -= 1; 1.247 + size_t size = entry->resource()->sizeInBytes(); 1.248 + fClientDetachedBytes -= size; 1.249 + fEntryBytes -= size; 1.250 +} 1.251 + 1.252 +void GrResourceCache::makeNonExclusive(GrResourceEntry* entry) { 1.253 + GrAutoResourceCacheValidate atcv(this); 1.254 + 1.255 +#ifdef SK_DEBUG 1.256 + fExclusiveList.remove(entry); 1.257 +#endif 1.258 + 1.259 + if (entry->resource()->isValid()) { 1.260 + // Since scratch textures still count against the cache budget even 1.261 + // when they have been removed from the cache, re-adding them doesn't 1.262 + // alter the budget information. 1.263 + attachToHead(entry, kIgnore_BudgetBehavior); 1.264 + fCache.insert(entry->key(), entry); 1.265 + } else { 1.266 + this->removeInvalidResource(entry); 1.267 + } 1.268 +} 1.269 + 1.270 +/** 1.271 + * Destroying a resource may potentially trigger the unlock of additional 1.272 + * resources which in turn will trigger a nested purge. We block the nested 1.273 + * purge using the fPurging variable. However, the initial purge will keep 1.274 + * looping until either all resources in the cache are unlocked or we've met 1.275 + * the budget. There is an assertion in createAndLock to check against a 1.276 + * resource's destructor inserting new resources into the cache. If these 1.277 + * new resources were unlocked before purgeAsNeeded completed it could 1.278 + * potentially make purgeAsNeeded loop infinitely. 1.279 + * 1.280 + * extraCount and extraBytes are added to the current resource totals to account 1.281 + * for incoming resources (e.g., GrContext is about to add 10MB split between 1.282 + * 10 textures). 1.283 + */ 1.284 +void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { 1.285 + if (fPurging) { 1.286 + return; 1.287 + } 1.288 + 1.289 + fPurging = true; 1.290 + 1.291 + this->purgeInvalidated(); 1.292 + 1.293 + this->internalPurge(extraCount, extraBytes); 1.294 + if (((fEntryCount+extraCount) > fMaxCount || 1.295 + (fEntryBytes+extraBytes) > fMaxBytes) && 1.296 + NULL != fOverbudgetCB) { 1.297 + // Despite the purge we're still over budget. See if Ganesh can 1.298 + // release some resources and purge again. 1.299 + if ((*fOverbudgetCB)(fOverbudgetData)) { 1.300 + this->internalPurge(extraCount, extraBytes); 1.301 + } 1.302 + } 1.303 + 1.304 + fPurging = false; 1.305 +} 1.306 + 1.307 +void GrResourceCache::purgeInvalidated() { 1.308 + SkTDArray<GrResourceInvalidatedMessage> invalidated; 1.309 + fInvalidationInbox.poll(&invalidated); 1.310 + 1.311 + for (int i = 0; i < invalidated.count(); i++) { 1.312 + // We're somewhat missing an opportunity here. We could use the 1.313 + // default find functor that gives us back resources whether we own 1.314 + // them exclusively or not, and when they're not exclusively owned mark 1.315 + // them for purging later when they do become exclusively owned. 1.316 + // 1.317 + // This is complicated and confusing. May try this in the future. For 1.318 + // now, these resources are just LRU'd as if we never got the message. 1.319 + while (GrResourceEntry* entry = fCache.find(invalidated[i].key, GrTFindUnreffedFunctor())) { 1.320 + this->deleteResource(entry); 1.321 + } 1.322 + } 1.323 +} 1.324 + 1.325 +void GrResourceCache::deleteResource(GrResourceEntry* entry) { 1.326 + SkASSERT(1 == entry->fResource->getRefCnt()); 1.327 + 1.328 + // remove from our cache 1.329 + fCache.remove(entry->key(), entry); 1.330 + 1.331 + // remove from our llist 1.332 + this->internalDetach(entry); 1.333 + delete entry; 1.334 +} 1.335 + 1.336 +void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) { 1.337 + SkASSERT(fPurging); 1.338 + 1.339 + bool withinBudget = false; 1.340 + bool changed = false; 1.341 + 1.342 + // The purging process is repeated several times since one pass 1.343 + // may free up other resources 1.344 + do { 1.345 + EntryList::Iter iter; 1.346 + 1.347 + changed = false; 1.348 + 1.349 + // Note: the following code relies on the fact that the 1.350 + // doubly linked list doesn't invalidate its data/pointers 1.351 + // outside of the specific area where a deletion occurs (e.g., 1.352 + // in internalDetach) 1.353 + GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 1.354 + 1.355 + while (NULL != entry) { 1.356 + GrAutoResourceCacheValidate atcv(this); 1.357 + 1.358 + if ((fEntryCount+extraCount) <= fMaxCount && 1.359 + (fEntryBytes+extraBytes) <= fMaxBytes) { 1.360 + withinBudget = true; 1.361 + break; 1.362 + } 1.363 + 1.364 + GrResourceEntry* prev = iter.prev(); 1.365 + if (entry->fResource->unique()) { 1.366 + changed = true; 1.367 + this->deleteResource(entry); 1.368 + } 1.369 + entry = prev; 1.370 + } 1.371 + } while (!withinBudget && changed); 1.372 +} 1.373 + 1.374 +void GrResourceCache::purgeAllUnlocked() { 1.375 + GrAutoResourceCacheValidate atcv(this); 1.376 + 1.377 + // we can have one GrResource holding a lock on another 1.378 + // so we don't want to just do a simple loop kicking each 1.379 + // entry out. Instead change the budget and purge. 1.380 + 1.381 + size_t savedMaxBytes = fMaxBytes; 1.382 + int savedMaxCount = fMaxCount; 1.383 + fMaxBytes = (size_t) -1; 1.384 + fMaxCount = 0; 1.385 + this->purgeAsNeeded(); 1.386 + 1.387 +#ifdef SK_DEBUG 1.388 + SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount); 1.389 + SkASSERT(countBytes(fExclusiveList) == fClientDetachedBytes); 1.390 + if (!fCache.count()) { 1.391 + // Items may have been detached from the cache (such as the backing 1.392 + // texture for an SkGpuDevice). The above purge would not have removed 1.393 + // them. 1.394 + SkASSERT(fEntryCount == fClientDetachedCount); 1.395 + SkASSERT(fEntryBytes == fClientDetachedBytes); 1.396 + SkASSERT(fList.isEmpty()); 1.397 + } 1.398 +#endif 1.399 + 1.400 + fMaxBytes = savedMaxBytes; 1.401 + fMaxCount = savedMaxCount; 1.402 +} 1.403 + 1.404 +/////////////////////////////////////////////////////////////////////////////// 1.405 + 1.406 +#ifdef SK_DEBUG 1.407 +size_t GrResourceCache::countBytes(const EntryList& list) { 1.408 + size_t bytes = 0; 1.409 + 1.410 + EntryList::Iter iter; 1.411 + 1.412 + const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(list), 1.413 + EntryList::Iter::kTail_IterStart); 1.414 + 1.415 + for ( ; NULL != entry; entry = iter.prev()) { 1.416 + bytes += entry->resource()->sizeInBytes(); 1.417 + } 1.418 + return bytes; 1.419 +} 1.420 + 1.421 +static bool both_zero_or_nonzero(int count, size_t bytes) { 1.422 + return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); 1.423 +} 1.424 + 1.425 +void GrResourceCache::validate() const { 1.426 + fList.validate(); 1.427 + fExclusiveList.validate(); 1.428 + SkASSERT(both_zero_or_nonzero(fEntryCount, fEntryBytes)); 1.429 + SkASSERT(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes)); 1.430 + SkASSERT(fClientDetachedBytes <= fEntryBytes); 1.431 + SkASSERT(fClientDetachedCount <= fEntryCount); 1.432 + SkASSERT((fEntryCount - fClientDetachedCount) == fCache.count()); 1.433 + 1.434 + EntryList::Iter iter; 1.435 + 1.436 + // check that the exclusively held entries are okay 1.437 + const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(fExclusiveList), 1.438 + EntryList::Iter::kHead_IterStart); 1.439 + 1.440 + for ( ; NULL != entry; entry = iter.next()) { 1.441 + entry->validate(); 1.442 + } 1.443 + 1.444 + // check that the shareable entries are okay 1.445 + entry = iter.init(const_cast<EntryList&>(fList), EntryList::Iter::kHead_IterStart); 1.446 + 1.447 + int count = 0; 1.448 + for ( ; NULL != entry; entry = iter.next()) { 1.449 + entry->validate(); 1.450 + SkASSERT(fCache.find(entry->key())); 1.451 + count += 1; 1.452 + } 1.453 + SkASSERT(count == fEntryCount - fClientDetachedCount); 1.454 + 1.455 + size_t bytes = countBytes(fList); 1.456 + SkASSERT(bytes == fEntryBytes - fClientDetachedBytes); 1.457 + 1.458 + bytes = countBytes(fExclusiveList); 1.459 + SkASSERT(bytes == fClientDetachedBytes); 1.460 + 1.461 + SkASSERT(fList.countEntries() == fEntryCount - fClientDetachedCount); 1.462 + 1.463 + SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount); 1.464 +} 1.465 +#endif // SK_DEBUG 1.466 + 1.467 +#if GR_CACHE_STATS 1.468 + 1.469 +void GrResourceCache::printStats() { 1.470 + int locked = 0; 1.471 + 1.472 + EntryList::Iter iter; 1.473 + 1.474 + GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 1.475 + 1.476 + for ( ; NULL != entry; entry = iter.prev()) { 1.477 + if (entry->fResource->getRefCnt() > 1) { 1.478 + ++locked; 1.479 + } 1.480 + } 1.481 + 1.482 + SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); 1.483 + SkDebugf("\t\tEntry Count: current %d (%d locked) high %d\n", 1.484 + fEntryCount, locked, fHighWaterEntryCount); 1.485 + SkDebugf("\t\tEntry Bytes: current %d high %d\n", 1.486 + fEntryBytes, fHighWaterEntryBytes); 1.487 + SkDebugf("\t\tDetached Entry Count: current %d high %d\n", 1.488 + fClientDetachedCount, fHighWaterClientDetachedCount); 1.489 + SkDebugf("\t\tDetached Bytes: current %d high %d\n", 1.490 + fClientDetachedBytes, fHighWaterClientDetachedBytes); 1.491 +} 1.492 + 1.493 +#endif 1.494 + 1.495 +///////////////////////////////////////////////////////////////////////////////