GrResourceCache.cpp revision adacc7067ad617cdc7bbef39192ca80f4b4d27f9
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 SkASSERT(resource); 32 resource->ref(); 33} 34 35GrResourceEntry::~GrResourceEntry() { 36 fResource->setCacheEntry(NULL); 37 fResource->unref(); 38} 39 40#ifdef SK_DEBUG 41void GrResourceEntry::validate() const { 42 SkASSERT(fResource); 43 SkASSERT(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 (NULL != maxResources) { 92 *maxResources = fMaxCount; 93 } 94 if (NULL != 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 SkASSERT(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 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. 164class GrTFindUnreffedFunctor { 165public: 166 bool operator()(const GrResourceEntry* entry) const { 167 return entry->resource()->unique(); 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 199void 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 222void 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 235void 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 248void 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 */ 280void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { 281 if (fPurging) { 282 return; 283 } 284 285 fPurging = true; 286 287 this->internalPurge(extraCount, extraBytes); 288 if (((fEntryCount+extraCount) > fMaxCount || 289 (fEntryBytes+extraBytes) > fMaxBytes) && 290 NULL != fOverbudgetCB) { 291 // Despite the purge we're still over budget. See if Ganesh can 292 // release some resources and purge again. 293 if ((*fOverbudgetCB)(fOverbudgetData)) { 294 this->internalPurge(extraCount, extraBytes); 295 } 296 } 297 298 fPurging = false; 299} 300 301void GrResourceCache::deleteResource(GrResourceEntry* entry) { 302 SkASSERT(1 == entry->fResource->getRefCnt()); 303 304 // remove from our cache 305 fCache.remove(entry->key(), entry); 306 307 // remove from our llist 308 this->internalDetach(entry); 309 delete entry; 310} 311 312void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) { 313 SkASSERT(fPurging); 314 315 bool withinBudget = false; 316 bool changed = false; 317 318 // The purging process is repeated several times since one pass 319 // may free up other resources 320 do { 321 EntryList::Iter iter; 322 323 changed = false; 324 325 // Note: the following code relies on the fact that the 326 // doubly linked list doesn't invalidate its data/pointers 327 // outside of the specific area where a deletion occurs (e.g., 328 // in internalDetach) 329 GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 330 331 while (NULL != entry) { 332 GrAutoResourceCacheValidate atcv(this); 333 334 if ((fEntryCount+extraCount) <= fMaxCount && 335 (fEntryBytes+extraBytes) <= fMaxBytes) { 336 withinBudget = true; 337 break; 338 } 339 340 GrResourceEntry* prev = iter.prev(); 341 if (entry->fResource->unique()) { 342 changed = true; 343 this->deleteResource(entry); 344 } 345 entry = prev; 346 } 347 } while (!withinBudget && changed); 348} 349 350void GrResourceCache::purgeAllUnlocked() { 351 GrAutoResourceCacheValidate atcv(this); 352 353 // we can have one GrResource holding a lock on another 354 // so we don't want to just do a simple loop kicking each 355 // entry out. Instead change the budget and purge. 356 357 size_t savedMaxBytes = fMaxBytes; 358 int savedMaxCount = fMaxCount; 359 fMaxBytes = (size_t) -1; 360 fMaxCount = 0; 361 this->purgeAsNeeded(); 362 363#ifdef SK_DEBUG 364 SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount); 365 SkASSERT(countBytes(fExclusiveList) == fClientDetachedBytes); 366 if (!fCache.count()) { 367 // Items may have been detached from the cache (such as the backing 368 // texture for an SkGpuDevice). The above purge would not have removed 369 // them. 370 SkASSERT(fEntryCount == fClientDetachedCount); 371 SkASSERT(fEntryBytes == fClientDetachedBytes); 372 SkASSERT(fList.isEmpty()); 373 } 374#endif 375 376 fMaxBytes = savedMaxBytes; 377 fMaxCount = savedMaxCount; 378} 379 380/////////////////////////////////////////////////////////////////////////////// 381 382#ifdef SK_DEBUG 383size_t GrResourceCache::countBytes(const EntryList& list) { 384 size_t bytes = 0; 385 386 EntryList::Iter iter; 387 388 const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(list), 389 EntryList::Iter::kTail_IterStart); 390 391 for ( ; NULL != entry; entry = iter.prev()) { 392 bytes += entry->resource()->sizeInBytes(); 393 } 394 return bytes; 395} 396 397static bool both_zero_or_nonzero(int count, size_t bytes) { 398 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); 399} 400 401void GrResourceCache::validate() const { 402 fList.validate(); 403 fExclusiveList.validate(); 404 SkASSERT(both_zero_or_nonzero(fEntryCount, fEntryBytes)); 405 SkASSERT(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes)); 406 SkASSERT(fClientDetachedBytes <= fEntryBytes); 407 SkASSERT(fClientDetachedCount <= fEntryCount); 408 SkASSERT((fEntryCount - fClientDetachedCount) == fCache.count()); 409 410 fCache.validate(); 411 412 413 EntryList::Iter iter; 414 415 // check that the exclusively held entries are okay 416 const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(fExclusiveList), 417 EntryList::Iter::kHead_IterStart); 418 419 for ( ; NULL != entry; entry = iter.next()) { 420 entry->validate(); 421 } 422 423 // check that the shareable entries are okay 424 entry = iter.init(const_cast<EntryList&>(fList), EntryList::Iter::kHead_IterStart); 425 426 int count = 0; 427 for ( ; NULL != entry; entry = iter.next()) { 428 entry->validate(); 429 SkASSERT(fCache.find(entry->key())); 430 count += 1; 431 } 432 SkASSERT(count == fEntryCount - fClientDetachedCount); 433 434 size_t bytes = countBytes(fList); 435 SkASSERT(bytes == fEntryBytes - fClientDetachedBytes); 436 437 bytes = countBytes(fExclusiveList); 438 SkASSERT(bytes == fClientDetachedBytes); 439 440 SkASSERT(fList.countEntries() == fEntryCount - fClientDetachedCount); 441 442 SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount); 443} 444#endif // SK_DEBUG 445 446#if GR_CACHE_STATS 447 448void GrResourceCache::printStats() { 449 int locked = 0; 450 451 EntryList::Iter iter; 452 453 GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 454 455 for ( ; NULL != entry; entry = iter.prev()) { 456 if (entry->fResource->getRefCnt() > 1) { 457 ++locked; 458 } 459 } 460 461 SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); 462 SkDebugf("\t\tEntry Count: current %d (%d locked) high %d\n", 463 fEntryCount, locked, fHighWaterEntryCount); 464 SkDebugf("\t\tEntry Bytes: current %d high %d\n", 465 fEntryBytes, fHighWaterEntryBytes); 466 SkDebugf("\t\tDetached Entry Count: current %d high %d\n", 467 fClientDetachedCount, fHighWaterClientDetachedCount); 468 SkDebugf("\t\tDetached Bytes: current %d high %d\n", 469 fClientDetachedBytes, fHighWaterClientDetachedBytes); 470} 471 472#endif 473 474/////////////////////////////////////////////////////////////////////////////// 475