GrResourceCache.cpp revision f2e93fc989129f11881919de99a3b8f12081beae
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 14GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource) 15 : fKey(key), fResource(resource) { 16 fLockCount = 0; 17 18 // we assume ownership of the resource, and will unref it when we die 19 GrAssert(resource); 20 resource->ref(); 21} 22 23GrResourceEntry::~GrResourceEntry() { 24 fResource->setCacheEntry(NULL); 25 fResource->unref(); 26} 27 28#if GR_DEBUG 29void GrResourceEntry::validate() const { 30 GrAssert(fLockCount >= 0); 31 GrAssert(fResource); 32 GrAssert(fResource->getCacheEntry() == this); 33 fResource->validate(); 34} 35#endif 36 37/////////////////////////////////////////////////////////////////////////////// 38 39class GrResourceCache::Key { 40 typedef GrResourceEntry T; 41 42 const GrResourceKey& fKey; 43public: 44 Key(const GrResourceKey& key) : fKey(key) {} 45 46 uint32_t getHash() const { return fKey.hashIndex(); } 47 48 static bool LT(const T& entry, const Key& key) { 49 return entry.key() < key.fKey; 50 } 51 static bool EQ(const T& entry, const Key& key) { 52 return entry.key() == key.fKey; 53 } 54#if GR_DEBUG 55 static uint32_t GetHash(const T& entry) { 56 return entry.key().hashIndex(); 57 } 58 static bool LT(const T& a, const T& b) { 59 return a.key() < b.key(); 60 } 61 static bool EQ(const T& a, const T& b) { 62 return a.key() == b.key(); 63 } 64#endif 65}; 66 67/////////////////////////////////////////////////////////////////////////////// 68 69GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) : 70 fMaxCount(maxCount), 71 fMaxBytes(maxBytes) { 72#if GR_CACHE_STATS 73 fHighWaterEntryCount = 0; 74 fHighWaterUnlockedEntryCount = 0; 75 fHighWaterEntryBytes = 0; 76 fHighWaterClientDetachedCount = 0; 77 fHighWaterClientDetachedBytes = 0; 78#endif 79 80 fEntryCount = 0; 81 fUnlockedEntryCount = 0; 82 fEntryBytes = 0; 83 fClientDetachedCount = 0; 84 fClientDetachedBytes = 0; 85 86 fPurging = false; 87} 88 89GrResourceCache::~GrResourceCache() { 90 GrAutoResourceCacheValidate atcv(this); 91 92 EntryList::Iter iter; 93 94 // Unlike the removeAll, here we really remove everything, including locked resources. 95 while (GrResourceEntry* entry = fList.head()) { 96 GrAutoResourceCacheValidate atcv(this); 97 98 // remove from our cache 99 fCache.remove(entry->fKey, entry); 100 101 // remove from our llist 102 this->internalDetach(entry, false); 103 104 delete entry; 105 } 106} 107 108void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ 109 if (maxResources) { 110 *maxResources = fMaxCount; 111 } 112 if (maxResourceBytes) { 113 *maxResourceBytes = fMaxBytes; 114 } 115} 116 117void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) { 118 bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes); 119 120 fMaxCount = maxResources; 121 fMaxBytes = maxResourceBytes; 122 123 if (smaller) { 124 this->purgeAsNeeded(); 125 } 126} 127 128void GrResourceCache::internalDetach(GrResourceEntry* entry, 129 bool clientDetach) { 130 fList.remove(entry); 131 132 if (!entry->isLocked()) { 133 --fUnlockedEntryCount; 134 } 135 136 // update our stats 137 if (clientDetach) { 138 fClientDetachedCount += 1; 139 fClientDetachedBytes += entry->resource()->sizeInBytes(); 140 141#if GR_CACHE_STATS 142 if (fHighWaterClientDetachedCount < fClientDetachedCount) { 143 fHighWaterClientDetachedCount = fClientDetachedCount; 144 } 145 if (fHighWaterClientDetachedBytes < fClientDetachedBytes) { 146 fHighWaterClientDetachedBytes = fClientDetachedBytes; 147 } 148#endif 149 150 } else { 151 fEntryCount -= 1; 152 fEntryBytes -= entry->resource()->sizeInBytes(); 153 } 154} 155 156void GrResourceCache::attachToHead(GrResourceEntry* entry, 157 bool clientReattach) { 158 fList.addToHead(entry); 159 160 if (!entry->isLocked()) { 161 ++fUnlockedEntryCount; 162#if GR_CACHE_STATS 163 if (fHighWaterUnlockedEntryCount < fUnlockedEntryCount) { 164 fHighWaterUnlockedEntryCount = fUnlockedEntryCount; 165 } 166#endif 167 } 168 169 // update our stats 170 if (clientReattach) { 171 fClientDetachedCount -= 1; 172 fClientDetachedBytes -= entry->resource()->sizeInBytes(); 173 } else { 174 fEntryCount += 1; 175 fEntryBytes += entry->resource()->sizeInBytes(); 176 177#if GR_CACHE_STATS 178 if (fHighWaterEntryCount < fEntryCount) { 179 fHighWaterEntryCount = fEntryCount; 180 } 181 if (fHighWaterEntryBytes < fEntryBytes) { 182 fHighWaterEntryBytes = fEntryBytes; 183 } 184#endif 185 } 186} 187 188GrResource* GrResourceCache::find(const GrResourceKey& key) { 189 GrAutoResourceCacheValidate atcv(this); 190 191 GrResourceEntry* entry = fCache.find(key); 192 if (NULL == entry) { 193 return NULL; 194 } 195 196 return entry->fResource; 197} 198 199GrResource* GrResourceCache::findAndLock(const GrResourceKey& key) { 200 GrAutoResourceCacheValidate atcv(this); 201 202 GrResourceEntry* entry = fCache.find(key); 203 if (NULL == entry) { 204 return NULL; 205 } 206 207 this->internalDetach(entry, false); 208 this->attachToHead(entry, false); 209 210 this->lock(entry); 211 212 return entry->fResource; 213} 214 215bool GrResourceCache::hasKey(const GrResourceKey& key) const { 216 return NULL != fCache.find(key); 217} 218 219void GrResourceCache::create(const GrResourceKey& key, GrResource* resource) { 220 GrAssert(NULL == resource->getCacheEntry()); 221 // we don't expect to create new resources during a purge. In theory 222 // this could cause purgeAsNeeded() into an infinite loop (e.g. 223 // each resource destroyed creates and locks 2 resources and 224 // unlocks 1 thereby causing a new purge). 225 GrAssert(!fPurging); 226 GrAutoResourceCacheValidate atcv(this); 227 228 GrResourceEntry* entry = SkNEW_ARGS(GrResourceEntry, (key, resource)); 229 resource->setCacheEntry(entry); 230 231 this->attachToHead(entry, false); 232 fCache.insert(key, entry); 233 234#if GR_DUMP_TEXTURE_UPLOAD 235 GrPrintf("--- add resource to cache %p, count=%d bytes= %d %d\n", 236 entry, fEntryCount, resource->sizeInBytes(), fEntryBytes); 237#endif 238} 239 240void GrResourceCache::createAndLock(const GrResourceKey& key, 241 GrResource* resource) { 242 this->create(key, resource); 243 244 GrAssert(NULL != resource->getCacheEntry()); 245 this->lock(resource->getCacheEntry()); 246} 247 248void GrResourceCache::makeExclusive(GrResourceEntry* entry) { 249 GrAutoResourceCacheValidate atcv(this); 250 251 this->internalDetach(entry, true); 252 fCache.remove(entry->key(), entry); 253 254#if GR_DEBUG 255 fExclusiveList.addToHead(entry); 256#endif 257} 258 259void GrResourceCache::removeInvalidResource(GrResourceEntry* entry) { 260 // If the resource went invalid while it was detached then purge it 261 // This can happen when a 3D context was lost, 262 // the client called GrContext::contextDestroyed() to notify Gr, 263 // and then later an SkGpuDevice's destructor releases its backing 264 // texture (which was invalidated at contextDestroyed time). 265 fClientDetachedCount -= 1; 266 fEntryCount -= 1; 267 size_t size = entry->resource()->sizeInBytes(); 268 fClientDetachedBytes -= size; 269 fEntryBytes -= size; 270} 271 272void GrResourceCache::makeNonExclusive(GrResourceEntry* entry) { 273 GrAutoResourceCacheValidate atcv(this); 274 275#if GR_DEBUG 276 fExclusiveList.remove(entry); 277#endif 278 279 if (entry->resource()->isValid()) { 280 attachToHead(entry, true); 281 fCache.insert(entry->key(), entry); 282 } else { 283 this->removeInvalidResource(entry); 284 } 285} 286 287void GrResourceCache::lock(GrResourceEntry* entry) { 288 GrAutoResourceCacheValidate atcv(this); 289 290 GrAssert(entry); 291 GrAssert(fCache.find(entry->key())); 292 293 if (!entry->isLocked()) { 294 --fUnlockedEntryCount; 295 } 296 297 entry->lock(); 298} 299 300void GrResourceCache::unlock(GrResourceEntry* entry) { 301 GrAutoResourceCacheValidate atcv(this); 302 303 GrAssert(entry); 304 GrAssert(entry->isLocked()); 305 GrAssert(fCache.find(entry->key())); 306 307 entry->unlock(); 308 if (!entry->isLocked()) { 309 ++fUnlockedEntryCount; 310#if GR_CACHE_STATS 311 if (fHighWaterUnlockedEntryCount < fUnlockedEntryCount) { 312 fHighWaterUnlockedEntryCount = fUnlockedEntryCount; 313 } 314#endif 315 } 316 317 this->purgeAsNeeded(); 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 */ 330void GrResourceCache::purgeAsNeeded() { 331 if (!fPurging) { 332 fPurging = true; 333 bool withinBudget = false; 334 int priorUnlockedEntryCount = 0; 335 336 // The purging process is repeated several times since one pass 337 // may free up other resources 338 do { 339 EntryList::Iter iter; 340 341 priorUnlockedEntryCount = fUnlockedEntryCount; 342 343 // Note: the following code relies on the fact that the 344 // doubly linked list doesn't invalidate its data/pointers 345 // outside of the specific area where a deletion occurs (e.g., 346 // in internalDetach) 347 GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 348 349 while (entry && fUnlockedEntryCount) { 350 GrAutoResourceCacheValidate atcv(this); 351 if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) { 352 withinBudget = true; 353 break; 354 } 355 356 GrResourceEntry* prev = iter.prev(); 357 if (!entry->isLocked() && entry->fResource->getRefCnt() == 1) { 358 // remove from our cache 359 fCache.remove(entry->key(), entry); 360 361 // remove from our llist 362 this->internalDetach(entry, false); 363 364 #if GR_DUMP_TEXTURE_UPLOAD 365 GrPrintf("--- ~resource from cache %p [%d %d]\n", 366 entry->resource(), 367 entry->resource()->width(), 368 entry->resource()->height()); 369 #endif 370 371 delete entry; 372 } 373 entry = prev; 374 } 375 } while (!withinBudget && (fUnlockedEntryCount != priorUnlockedEntryCount)); 376 fPurging = false; 377 } 378} 379 380void GrResourceCache::purgeAllUnlocked() { 381 GrAutoResourceCacheValidate atcv(this); 382 383 // we can have one GrResource holding a lock on another 384 // so we don't want to just do a simple loop kicking each 385 // entry out. Instead change the budget and purge. 386 387 int savedMaxBytes = fMaxBytes; 388 int savedMaxCount = fMaxCount; 389 fMaxBytes = (size_t) -1; 390 fMaxCount = 0; 391 this->purgeAsNeeded(); 392 393#if GR_DEBUG 394 GrAssert(fExclusiveList.countEntries() == fClientDetachedCount); 395 GrAssert(countBytes(fExclusiveList) == fClientDetachedBytes); 396 GrAssert(!fUnlockedEntryCount); 397 if (!fCache.count()) { 398 // Items may have been detached from the cache (such as the backing 399 // texture for an SkGpuDevice). The above purge would not have removed 400 // them. 401 GrAssert(fEntryCount == fClientDetachedCount); 402 GrAssert(fEntryBytes == fClientDetachedBytes); 403 GrAssert(fList.isEmpty()); 404 } 405#endif 406 407 fMaxBytes = savedMaxBytes; 408 fMaxCount = savedMaxCount; 409} 410 411/////////////////////////////////////////////////////////////////////////////// 412 413#if GR_DEBUG 414size_t GrResourceCache::countBytes(const EntryList& list) { 415 size_t bytes = 0; 416 417 EntryList::Iter iter; 418 419 const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(list), 420 EntryList::Iter::kTail_IterStart); 421 422 for ( ; NULL != entry; entry = iter.prev()) { 423 bytes += entry->resource()->sizeInBytes(); 424 } 425 return bytes; 426} 427 428static bool both_zero_or_nonzero(int count, size_t bytes) { 429 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); 430} 431 432void GrResourceCache::validate() const { 433 fList.validate(); 434 fExclusiveList.validate(); 435 GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes)); 436 GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes)); 437 GrAssert(fClientDetachedBytes <= fEntryBytes); 438 GrAssert(fClientDetachedCount <= fEntryCount); 439 GrAssert((fEntryCount - fClientDetachedCount) == fCache.count()); 440 441 fCache.validate(); 442 443 444 EntryList::Iter iter; 445 446 const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(fExclusiveList), 447 EntryList::Iter::kHead_IterStart); 448 449 for ( ; NULL != entry; entry = iter.next()) { 450 entry->validate(); 451 GrAssert(entry->isLocked()); 452 } 453 454 entry = iter.init(const_cast<EntryList&>(fList), EntryList::Iter::kHead_IterStart); 455 456 int count = 0; 457 int unlockCount = 0; 458 for ( ; NULL != entry; entry = iter.next()) { 459 entry->validate(); 460 GrAssert(fCache.find(entry->key())); 461 count += 1; 462 if (!entry->isLocked()) { 463 unlockCount += 1; 464 } 465 } 466 GrAssert(count == fEntryCount - fClientDetachedCount); 467 468 size_t bytes = countBytes(fList); 469 GrAssert(bytes == fEntryBytes - fClientDetachedBytes); 470 471 bytes = countBytes(fExclusiveList); 472 GrAssert(bytes == fClientDetachedBytes); 473 474 GrAssert(unlockCount == fUnlockedEntryCount); 475 476 GrAssert(fList.countEntries() == fEntryCount - fClientDetachedCount); 477 478 GrAssert(fExclusiveList.countEntries() == fClientDetachedCount); 479} 480#endif // GR_DEBUG 481 482#if GR_CACHE_STATS 483 484void GrResourceCache::printStats() const { 485 SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); 486 SkDebugf("\t\tEntry Count: current %d high %d\n", 487 fEntryCount, fHighWaterEntryCount); 488 SkDebugf("\t\tUnlocked Entry Count: current %d high %d\n", 489 fUnlockedEntryCount, fHighWaterUnlockedEntryCount); 490 SkDebugf("\t\tEntry Bytes: current %d high %d\n", 491 fEntryBytes, fHighWaterEntryBytes); 492 SkDebugf("\t\tDetached Entry Count: current %d high %d\n", 493 fClientDetachedCount, fHighWaterClientDetachedCount); 494 SkDebugf("\t\tDetached Bytes: current %d high %d\n", 495 fClientDetachedBytes, fHighWaterClientDetachedBytes); 496} 497 498#endif 499 500/////////////////////////////////////////////////////////////////////////////// 501