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