GrResourceCache.cpp revision a79919883e275e7a5e00afc50be10cc721f6ba1d
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 15GrResourceKey::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 28GrResourceEntry::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 GrAssert(resource); 32 resource->ref(); 33} 34 35GrResourceEntry::~GrResourceEntry() { 36 fResource->setCacheEntry(NULL); 37 fResource->unref(); 38} 39 40#if GR_DEBUG 41void GrResourceEntry::validate() const { 42 GrAssert(fResource); 43 GrAssert(fResource->getCacheEntry() == this); 44 fResource->validate(); 45} 46#endif 47 48/////////////////////////////////////////////////////////////////////////////// 49 50GrResourceCache::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 71GrResourceCache::~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 90void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ 91 if (maxResources) { 92 *maxResources = fMaxCount; 93 } 94 if (maxResourceBytes) { 95 *maxResourceBytes = fMaxBytes; 96 } 97} 98 99void 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 110void 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 GrAssert(kAccountFor_BudgetBehavior == behavior); 130 131 fEntryCount -= 1; 132 fEntryBytes -= entry->resource()->sizeInBytes(); 133 } 134} 135 136void 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 GrAssert(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. 164class GrTFindUnreffedFunctor { 165public: 166 bool operator()(const GrResourceEntry* entry) const { 167 return 1 == entry->resource()->getRefCnt(); 168 } 169}; 170 171GrResource* 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 199bool GrResourceCache::hasKey(const GrResourceKey& key) const { 200 return NULL != fCache.find(key); 201} 202 203void GrResourceCache::addResource(const GrResourceKey& key, 204 GrResource* resource, 205 uint32_t ownershipFlags) { 206 GrAssert(NULL == resource->getCacheEntry()); 207 // we don't expect to create new resources during a purge. In theory 208 // this could cause purgeAsNeeded() into an infinite loop (e.g. 209 // each resource destroyed creates and locks 2 resources and 210 // unlocks 1 thereby causing a new purge). 211 GrAssert(!fPurging); 212 GrAutoResourceCacheValidate atcv(this); 213 214 GrResourceEntry* entry = SkNEW_ARGS(GrResourceEntry, (key, resource)); 215 resource->setCacheEntry(entry); 216 217 this->attachToHead(entry); 218 fCache.insert(key, entry); 219 220 if (ownershipFlags & kHide_OwnershipFlag) { 221 this->makeExclusive(entry); 222 } 223 224} 225 226void GrResourceCache::makeExclusive(GrResourceEntry* entry) { 227 GrAutoResourceCacheValidate atcv(this); 228 229 // When scratch textures are detached (to hide them from future finds) they 230 // still count against the resource budget 231 this->internalDetach(entry, kIgnore_BudgetBehavior); 232 fCache.remove(entry->key(), entry); 233 234#if GR_DEBUG 235 fExclusiveList.addToHead(entry); 236#endif 237} 238 239void GrResourceCache::removeInvalidResource(GrResourceEntry* entry) { 240 // If the resource went invalid while it was detached then purge it 241 // This can happen when a 3D context was lost, 242 // the client called GrContext::contextDestroyed() to notify Gr, 243 // and then later an SkGpuDevice's destructor releases its backing 244 // texture (which was invalidated at contextDestroyed time). 245 fClientDetachedCount -= 1; 246 fEntryCount -= 1; 247 size_t size = entry->resource()->sizeInBytes(); 248 fClientDetachedBytes -= size; 249 fEntryBytes -= size; 250} 251 252void GrResourceCache::makeNonExclusive(GrResourceEntry* entry) { 253 GrAutoResourceCacheValidate atcv(this); 254 255#if GR_DEBUG 256 fExclusiveList.remove(entry); 257#endif 258 259 if (entry->resource()->isValid()) { 260 // Since scratch textures still count against the cache budget even 261 // when they have been removed from the cache, re-adding them doesn't 262 // alter the budget information. 263 attachToHead(entry, kIgnore_BudgetBehavior); 264 fCache.insert(entry->key(), entry); 265 } else { 266 this->removeInvalidResource(entry); 267 } 268} 269 270/** 271 * Destroying a resource may potentially trigger the unlock of additional 272 * resources which in turn will trigger a nested purge. We block the nested 273 * purge using the fPurging variable. However, the initial purge will keep 274 * looping until either all resources in the cache are unlocked or we've met 275 * the budget. There is an assertion in createAndLock to check against a 276 * resource's destructor inserting new resources into the cache. If these 277 * new resources were unlocked before purgeAsNeeded completed it could 278 * potentially make purgeAsNeeded loop infinitely. 279 * 280 * extraCount and extraBytes are added to the current resource totals to account 281 * for incoming resources (e.g., GrContext is about to add 10MB split between 282 * 10 textures). 283 */ 284void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { 285 if (fPurging) { 286 return; 287 } 288 289 fPurging = true; 290 291 this->internalPurge(extraCount, extraBytes); 292 if (((fEntryCount+extraCount) > fMaxCount || 293 (fEntryBytes+extraBytes) > fMaxBytes) && 294 NULL != fOverbudgetCB) { 295 // Despite the purge we're still over budget. See if Ganesh can 296 // release some resources and purge again. 297 if ((*fOverbudgetCB)(fOverbudgetData)) { 298 this->internalPurge(extraCount, extraBytes); 299 } 300 } 301 302 fPurging = false; 303} 304 305void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) { 306 SkASSERT(fPurging); 307 308 bool withinBudget = false; 309 bool changed = false; 310 311 // The purging process is repeated several times since one pass 312 // may free up other resources 313 do { 314 EntryList::Iter iter; 315 316 changed = false; 317 318 // Note: the following code relies on the fact that the 319 // doubly linked list doesn't invalidate its data/pointers 320 // outside of the specific area where a deletion occurs (e.g., 321 // in internalDetach) 322 GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 323 324 while (NULL != entry) { 325 GrAutoResourceCacheValidate atcv(this); 326 327 if ((fEntryCount+extraCount) <= fMaxCount && 328 (fEntryBytes+extraBytes) <= fMaxBytes) { 329 withinBudget = true; 330 break; 331 } 332 333 GrResourceEntry* prev = iter.prev(); 334 if (1 == entry->fResource->getRefCnt()) { 335 changed = true; 336 337 // remove from our cache 338 fCache.remove(entry->key(), entry); 339 340 // remove from our llist 341 this->internalDetach(entry); 342 delete entry; 343 } 344 entry = prev; 345 } 346 } while (!withinBudget && changed); 347} 348 349void GrResourceCache::purgeAllUnlocked() { 350 GrAutoResourceCacheValidate atcv(this); 351 352 // we can have one GrResource holding a lock on another 353 // so we don't want to just do a simple loop kicking each 354 // entry out. Instead change the budget and purge. 355 356 int savedMaxBytes = fMaxBytes; 357 int savedMaxCount = fMaxCount; 358 fMaxBytes = (size_t) -1; 359 fMaxCount = 0; 360 this->purgeAsNeeded(); 361 362#if GR_DEBUG 363 GrAssert(fExclusiveList.countEntries() == fClientDetachedCount); 364 GrAssert(countBytes(fExclusiveList) == fClientDetachedBytes); 365 if (!fCache.count()) { 366 // Items may have been detached from the cache (such as the backing 367 // texture for an SkGpuDevice). The above purge would not have removed 368 // them. 369 GrAssert(fEntryCount == fClientDetachedCount); 370 GrAssert(fEntryBytes == fClientDetachedBytes); 371 GrAssert(fList.isEmpty()); 372 } 373#endif 374 375 fMaxBytes = savedMaxBytes; 376 fMaxCount = savedMaxCount; 377} 378 379/////////////////////////////////////////////////////////////////////////////// 380 381#if GR_DEBUG 382size_t GrResourceCache::countBytes(const EntryList& list) { 383 size_t bytes = 0; 384 385 EntryList::Iter iter; 386 387 const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(list), 388 EntryList::Iter::kTail_IterStart); 389 390 for ( ; NULL != entry; entry = iter.prev()) { 391 bytes += entry->resource()->sizeInBytes(); 392 } 393 return bytes; 394} 395 396static bool both_zero_or_nonzero(int count, size_t bytes) { 397 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); 398} 399 400void GrResourceCache::validate() const { 401 fList.validate(); 402 fExclusiveList.validate(); 403 GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes)); 404 GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes)); 405 GrAssert(fClientDetachedBytes <= fEntryBytes); 406 GrAssert(fClientDetachedCount <= fEntryCount); 407 GrAssert((fEntryCount - fClientDetachedCount) == fCache.count()); 408 409 fCache.validate(); 410 411 412 EntryList::Iter iter; 413 414 // check that the exclusively held entries are okay 415 const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(fExclusiveList), 416 EntryList::Iter::kHead_IterStart); 417 418 for ( ; NULL != entry; entry = iter.next()) { 419 entry->validate(); 420 } 421 422 // check that the shareable entries are okay 423 entry = iter.init(const_cast<EntryList&>(fList), EntryList::Iter::kHead_IterStart); 424 425 int count = 0; 426 for ( ; NULL != entry; entry = iter.next()) { 427 entry->validate(); 428 GrAssert(fCache.find(entry->key())); 429 count += 1; 430 } 431 GrAssert(count == fEntryCount - fClientDetachedCount); 432 433 size_t bytes = countBytes(fList); 434 GrAssert(bytes == fEntryBytes - fClientDetachedBytes); 435 436 bytes = countBytes(fExclusiveList); 437 GrAssert(bytes == fClientDetachedBytes); 438 439 GrAssert(fList.countEntries() == fEntryCount - fClientDetachedCount); 440 441 GrAssert(fExclusiveList.countEntries() == fClientDetachedCount); 442} 443#endif // GR_DEBUG 444 445#if GR_CACHE_STATS 446 447void GrResourceCache::printStats() { 448 int locked = 0; 449 450 EntryList::Iter iter; 451 452 GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 453 454 for ( ; NULL != entry; entry = iter.prev()) { 455 if (entry->fResource->getRefCnt() > 1) { 456 ++locked; 457 } 458 } 459 460 SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); 461 SkDebugf("\t\tEntry Count: current %d (%d locked) high %d\n", 462 fEntryCount, locked, fHighWaterEntryCount); 463 SkDebugf("\t\tEntry Bytes: current %d high %d\n", 464 fEntryBytes, fHighWaterEntryBytes); 465 SkDebugf("\t\tDetached Entry Count: current %d high %d\n", 466 fClientDetachedCount, fHighWaterClientDetachedCount); 467 SkDebugf("\t\tDetached Bytes: current %d high %d\n", 468 fClientDetachedBytes, fHighWaterClientDetachedBytes); 469} 470 471#endif 472 473/////////////////////////////////////////////////////////////////////////////// 474