GrResourceCache.cpp revision 49f085dddff10473b6ebf832a974288300224e60
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 "GrGpuResource.h" 13 14DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage); 15 16/////////////////////////////////////////////////////////////////////////////// 17 18void GrGpuResource::didChangeGpuMemorySize() const { 19 if (this->isInCache()) { 20 fCacheEntry->didChangeResourceSize(); 21 } 22} 23 24/////////////////////////////////////////////////////////////////////////////// 25 26GrResourceKey::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 39GrResourceCacheEntry::GrResourceCacheEntry(GrResourceCache* resourceCache, 40 const GrResourceKey& key, 41 GrGpuResource* 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 52GrResourceCacheEntry::~GrResourceCacheEntry() { 53 fResource->setCacheEntry(NULL); 54 fResource->unref(); 55} 56 57#ifdef SK_DEBUG 58void 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 67void 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 79GrResourceCache::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 100GrResourceCache::~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 119void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ 120 if (maxResources) { 121 *maxResources = fMaxCount; 122 } 123 if (maxResourceBytes) { 124 *maxResourceBytes = fMaxBytes; 125 } 126} 127 128void 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 139void 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 165void 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. 193class GrTFindUnreffedFunctor { 194public: 195 bool operator()(const GrResourceCacheEntry* entry) const { 196 return entry->resource()->unique(); 197 } 198}; 199 200GrGpuResource* 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 228void GrResourceCache::addResource(const GrResourceKey& key, 229 GrGpuResource* 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 251void 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 267void 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::abandonContext() 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 281void GrResourceCache::makeNonExclusive(GrResourceCacheEntry* entry) { 282 GrAutoResourceCacheValidate atcv(this); 283 284#ifdef SK_DEBUG 285 fExclusiveList.remove(entry); 286#endif 287 288 if (!entry->resource()->wasDestroyed()) { 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 302void GrResourceCache::didIncreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountInc) { 303 fEntryBytes += amountInc; 304 if (entry->fIsExclusive) { 305 fClientDetachedBytes += amountInc; 306 } 307 this->purgeAsNeeded(); 308} 309 310void 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 */ 334void 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 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 357void 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 375void GrResourceCache::deleteResource(GrResourceCacheEntry* entry) { 376 SkASSERT(entry->fResource->unique()); 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 386void 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 (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 424void 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 457size_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 ( ; entry; entry = iter.prev()) { 466 bytes += entry->resource()->gpuMemorySize(); 467 } 468 return bytes; 469} 470 471static bool both_zero_or_nonzero(int count, size_t bytes) { 472 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); 473} 474 475void 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 ( ; 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 ( ; 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 519void 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 ( ; 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