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