GrResourceCache.cpp revision 88cb22b6b4816c7a9ca6c5b795965b4606f9eb7b
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 14DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage); 15 16GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() { 17 static int32_t gNextType = 0; 18 19 int32_t type = sk_atomic_inc(&gNextType); 20 if (type >= (1 << 8 * sizeof(ResourceType))) { 21 SkFAIL("Too many Resource Types"); 22 } 23 24 return static_cast<ResourceType>(type); 25} 26 27/////////////////////////////////////////////////////////////////////////////// 28 29GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource) 30 : fKey(key), fResource(resource) { 31 // we assume ownership of the resource, and will unref it when we die 32 SkASSERT(resource); 33 resource->ref(); 34} 35 36GrResourceEntry::~GrResourceEntry() { 37 fResource->setCacheEntry(NULL); 38 fResource->unref(); 39} 40 41#ifdef SK_DEBUG 42void GrResourceEntry::validate() const { 43 SkASSERT(fResource); 44 SkASSERT(fResource->getCacheEntry() == this); 45 fResource->validate(); 46} 47#endif 48 49/////////////////////////////////////////////////////////////////////////////// 50 51GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) : 52 fMaxCount(maxCount), 53 fMaxBytes(maxBytes) { 54#if GR_CACHE_STATS 55 fHighWaterEntryCount = 0; 56 fHighWaterEntryBytes = 0; 57 fHighWaterClientDetachedCount = 0; 58 fHighWaterClientDetachedBytes = 0; 59#endif 60 61 fEntryCount = 0; 62 fEntryBytes = 0; 63 fClientDetachedCount = 0; 64 fClientDetachedBytes = 0; 65 66 fPurging = false; 67 68 fOverbudgetCB = NULL; 69 fOverbudgetData = NULL; 70} 71 72GrResourceCache::~GrResourceCache() { 73 GrAutoResourceCacheValidate atcv(this); 74 75 EntryList::Iter iter; 76 77 // Unlike the removeAll, here we really remove everything, including locked resources. 78 while (GrResourceEntry* entry = fList.head()) { 79 GrAutoResourceCacheValidate atcv(this); 80 81 // remove from our cache 82 fCache.remove(entry->fKey, entry); 83 84 // remove from our llist 85 this->internalDetach(entry); 86 87 delete entry; 88 } 89} 90 91void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ 92 if (NULL != maxResources) { 93 *maxResources = fMaxCount; 94 } 95 if (NULL != maxResourceBytes) { 96 *maxResourceBytes = fMaxBytes; 97 } 98} 99 100void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) { 101 bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes); 102 103 fMaxCount = maxResources; 104 fMaxBytes = maxResourceBytes; 105 106 if (smaller) { 107 this->purgeAsNeeded(); 108 } 109} 110 111void GrResourceCache::internalDetach(GrResourceEntry* entry, 112 BudgetBehaviors behavior) { 113 fList.remove(entry); 114 115 // update our stats 116 if (kIgnore_BudgetBehavior == behavior) { 117 fClientDetachedCount += 1; 118 fClientDetachedBytes += entry->resource()->sizeInBytes(); 119 120#if GR_CACHE_STATS 121 if (fHighWaterClientDetachedCount < fClientDetachedCount) { 122 fHighWaterClientDetachedCount = fClientDetachedCount; 123 } 124 if (fHighWaterClientDetachedBytes < fClientDetachedBytes) { 125 fHighWaterClientDetachedBytes = fClientDetachedBytes; 126 } 127#endif 128 129 } else { 130 SkASSERT(kAccountFor_BudgetBehavior == behavior); 131 132 fEntryCount -= 1; 133 fEntryBytes -= entry->resource()->sizeInBytes(); 134 } 135} 136 137void GrResourceCache::attachToHead(GrResourceEntry* entry, 138 BudgetBehaviors behavior) { 139 fList.addToHead(entry); 140 141 // update our stats 142 if (kIgnore_BudgetBehavior == behavior) { 143 fClientDetachedCount -= 1; 144 fClientDetachedBytes -= entry->resource()->sizeInBytes(); 145 } else { 146 SkASSERT(kAccountFor_BudgetBehavior == behavior); 147 148 fEntryCount += 1; 149 fEntryBytes += entry->resource()->sizeInBytes(); 150 151#if GR_CACHE_STATS 152 if (fHighWaterEntryCount < fEntryCount) { 153 fHighWaterEntryCount = fEntryCount; 154 } 155 if (fHighWaterEntryBytes < fEntryBytes) { 156 fHighWaterEntryBytes = fEntryBytes; 157 } 158#endif 159 } 160} 161 162// This functor just searches for an entry with only a single ref (from 163// the texture cache itself). Presumably in this situation no one else 164// is relying on the texture. 165class GrTFindUnreffedFunctor { 166public: 167 bool operator()(const GrResourceEntry* entry) const { 168 return entry->resource()->unique(); 169 } 170}; 171 172GrResource* GrResourceCache::find(const GrResourceKey& key, uint32_t ownershipFlags) { 173 GrAutoResourceCacheValidate atcv(this); 174 175 GrResourceEntry* entry = NULL; 176 177 if (ownershipFlags & kNoOtherOwners_OwnershipFlag) { 178 GrTFindUnreffedFunctor functor; 179 180 entry = fCache.find<GrTFindUnreffedFunctor>(key, functor); 181 } else { 182 entry = fCache.find(key); 183 } 184 185 if (NULL == entry) { 186 return NULL; 187 } 188 189 if (ownershipFlags & kHide_OwnershipFlag) { 190 this->makeExclusive(entry); 191 } else { 192 // Make this resource MRU 193 this->internalDetach(entry); 194 this->attachToHead(entry); 195 } 196 197 return entry->fResource; 198} 199 200void GrResourceCache::addResource(const GrResourceKey& key, 201 GrResource* resource, 202 uint32_t ownershipFlags) { 203 SkASSERT(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 SkASSERT(!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 (ownershipFlags & kHide_OwnershipFlag) { 218 this->makeExclusive(entry); 219 } 220 221} 222 223void GrResourceCache::makeExclusive(GrResourceEntry* entry) { 224 GrAutoResourceCacheValidate atcv(this); 225 226 // When scratch textures are detached (to hide them from future finds) they 227 // still count against the resource budget 228 this->internalDetach(entry, kIgnore_BudgetBehavior); 229 fCache.remove(entry->key(), entry); 230 231#ifdef SK_DEBUG 232 fExclusiveList.addToHead(entry); 233#endif 234} 235 236void GrResourceCache::removeInvalidResource(GrResourceEntry* entry) { 237 // If the resource went invalid while it was detached then purge it 238 // This can happen when a 3D context was lost, 239 // the client called GrContext::contextDestroyed() to notify Gr, 240 // and then later an SkGpuDevice's destructor releases its backing 241 // texture (which was invalidated at contextDestroyed time). 242 fClientDetachedCount -= 1; 243 fEntryCount -= 1; 244 size_t size = entry->resource()->sizeInBytes(); 245 fClientDetachedBytes -= size; 246 fEntryBytes -= size; 247} 248 249void GrResourceCache::makeNonExclusive(GrResourceEntry* entry) { 250 GrAutoResourceCacheValidate atcv(this); 251 252#ifdef SK_DEBUG 253 fExclusiveList.remove(entry); 254#endif 255 256 if (entry->resource()->isValid()) { 257 // Since scratch textures still count against the cache budget even 258 // when they have been removed from the cache, re-adding them doesn't 259 // alter the budget information. 260 attachToHead(entry, kIgnore_BudgetBehavior); 261 fCache.insert(entry->key(), entry); 262 } else { 263 this->removeInvalidResource(entry); 264 } 265} 266 267/** 268 * Destroying a resource may potentially trigger the unlock of additional 269 * resources which in turn will trigger a nested purge. We block the nested 270 * purge using the fPurging variable. However, the initial purge will keep 271 * looping until either all resources in the cache are unlocked or we've met 272 * the budget. There is an assertion in createAndLock to check against a 273 * resource's destructor inserting new resources into the cache. If these 274 * new resources were unlocked before purgeAsNeeded completed it could 275 * potentially make purgeAsNeeded loop infinitely. 276 * 277 * extraCount and extraBytes are added to the current resource totals to account 278 * for incoming resources (e.g., GrContext is about to add 10MB split between 279 * 10 textures). 280 */ 281void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { 282 if (fPurging) { 283 return; 284 } 285 286 fPurging = true; 287 288 this->purgeInvalidated(); 289 290 this->internalPurge(extraCount, extraBytes); 291 if (((fEntryCount+extraCount) > fMaxCount || 292 (fEntryBytes+extraBytes) > fMaxBytes) && 293 NULL != fOverbudgetCB) { 294 // Despite the purge we're still over budget. See if Ganesh can 295 // release some resources and purge again. 296 if ((*fOverbudgetCB)(fOverbudgetData)) { 297 this->internalPurge(extraCount, extraBytes); 298 } 299 } 300 301 fPurging = false; 302} 303 304void GrResourceCache::purgeInvalidated() { 305 SkTDArray<GrResourceInvalidatedMessage> invalidated; 306 fInvalidationInbox.poll(&invalidated); 307 308 for (int i = 0; i < invalidated.count(); i++) { 309 // We're somewhat missing an opportunity here. We could use the 310 // default find functor that gives us back resources whether we own 311 // them exclusively or not, and when they're not exclusively owned mark 312 // them for purging later when they do become exclusively owned. 313 // 314 // This is complicated and confusing. May try this in the future. For 315 // now, these resources are just LRU'd as if we never got the message. 316 while (GrResourceEntry* entry = fCache.find(invalidated[i].key, GrTFindUnreffedFunctor())) { 317 this->deleteResource(entry); 318 } 319 } 320} 321 322void 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 333void 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 371void 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 404size_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 418static bool both_zero_or_nonzero(int count, size_t bytes) { 419 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); 420} 421 422void 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 EntryList::Iter iter; 432 433 // check that the exclusively held entries are okay 434 const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(fExclusiveList), 435 EntryList::Iter::kHead_IterStart); 436 437 for ( ; NULL != entry; entry = iter.next()) { 438 entry->validate(); 439 } 440 441 // check that the shareable entries are okay 442 entry = iter.init(const_cast<EntryList&>(fList), EntryList::Iter::kHead_IterStart); 443 444 int count = 0; 445 for ( ; NULL != entry; entry = iter.next()) { 446 entry->validate(); 447 SkASSERT(fCache.find(entry->key())); 448 count += 1; 449 } 450 SkASSERT(count == fEntryCount - fClientDetachedCount); 451 452 size_t bytes = countBytes(fList); 453 SkASSERT(bytes == fEntryBytes - fClientDetachedBytes); 454 455 bytes = countBytes(fExclusiveList); 456 SkASSERT(bytes == fClientDetachedBytes); 457 458 SkASSERT(fList.countEntries() == fEntryCount - fClientDetachedCount); 459 460 SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount); 461} 462#endif // SK_DEBUG 463 464#if GR_CACHE_STATS 465 466void GrResourceCache::printStats() { 467 int locked = 0; 468 469 EntryList::Iter iter; 470 471 GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 472 473 for ( ; NULL != entry; entry = iter.prev()) { 474 if (entry->fResource->getRefCnt() > 1) { 475 ++locked; 476 } 477 } 478 479 SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); 480 SkDebugf("\t\tEntry Count: current %d (%d locked) high %d\n", 481 fEntryCount, locked, fHighWaterEntryCount); 482 SkDebugf("\t\tEntry Bytes: current %d high %d\n", 483 fEntryBytes, fHighWaterEntryBytes); 484 SkDebugf("\t\tDetached Entry Count: current %d high %d\n", 485 fClientDetachedCount, fHighWaterClientDetachedCount); 486 SkDebugf("\t\tDetached Bytes: current %d high %d\n", 487 fClientDetachedBytes, fHighWaterClientDetachedBytes); 488} 489 490#endif 491 492/////////////////////////////////////////////////////////////////////////////// 493