GrResourceCache.cpp revision bcf0a52d4f4221b158e68a06ba0c4cc4db011060
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 "GrGpuResource.h" 13#include "GrTexturePriv.h" 14 15 16DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage); 17 18/////////////////////////////////////////////////////////////////////////////// 19 20void GrGpuResource::didChangeGpuMemorySize() const { 21 if (this->isInCache()) { 22 fCacheEntry->didChangeResourceSize(); 23 } 24} 25 26/////////////////////////////////////////////////////////////////////////////// 27 28GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() { 29 static int32_t gNextType = 0; 30 31 int32_t type = sk_atomic_inc(&gNextType); 32 if (type >= (1 << 8 * sizeof(ResourceType))) { 33 SkFAIL("Too many Resource Types"); 34 } 35 36 return static_cast<ResourceType>(type); 37} 38 39/////////////////////////////////////////////////////////////////////////////// 40 41GrResourceCacheEntry::GrResourceCacheEntry(GrResourceCache* resourceCache, 42 const GrResourceKey& key, 43 GrGpuResource* resource) 44 : fResourceCache(resourceCache), 45 fKey(key), 46 fResource(resource), 47 fCachedSize(resource->gpuMemorySize()), 48 fIsExclusive(false) { 49 // we assume ownership of the resource, and will unref it when we die 50 SkASSERT(resource); 51 resource->ref(); 52} 53 54GrResourceCacheEntry::~GrResourceCacheEntry() { 55 fResource->setCacheEntry(NULL); 56 fResource->unref(); 57} 58 59#ifdef SK_DEBUG 60void GrResourceCacheEntry::validate() const { 61 SkASSERT(fResourceCache); 62 SkASSERT(fResource); 63 SkASSERT(fResource->getCacheEntry() == this); 64 SkASSERT(fResource->gpuMemorySize() == fCachedSize); 65 fResource->validate(); 66} 67#endif 68 69void GrResourceCacheEntry::didChangeResourceSize() { 70 size_t oldSize = fCachedSize; 71 fCachedSize = fResource->gpuMemorySize(); 72 if (fCachedSize > oldSize) { 73 fResourceCache->didIncreaseResourceSize(this, fCachedSize - oldSize); 74 } else if (fCachedSize < oldSize) { 75 fResourceCache->didDecreaseResourceSize(this, oldSize - fCachedSize); 76 } 77} 78 79/////////////////////////////////////////////////////////////////////////////// 80 81GrResourceCache::GrResourceCache(const GrDrawTargetCaps* caps, int maxCount, size_t maxBytes) 82 : fMaxCount(maxCount) 83 , fMaxBytes(maxBytes) 84 , fCaps(SkRef(caps)) { 85#if GR_CACHE_STATS 86 fHighWaterEntryCount = 0; 87 fHighWaterEntryBytes = 0; 88#endif 89 90 fEntryCount = 0; 91 fEntryBytes = 0; 92 93 fPurging = false; 94 95 fOverbudgetCB = NULL; 96 fOverbudgetData = NULL; 97} 98 99GrResourceCache::~GrResourceCache() { 100 GrAutoResourceCacheValidate atcv(this); 101 102 EntryList::Iter iter; 103 104 // Unlike the removeAll, here we really remove everything, including locked resources. 105 while (GrResourceCacheEntry* entry = fList.head()) { 106 GrAutoResourceCacheValidate atcv(this); 107 108 // remove from our cache 109 fCache.remove(entry->fKey, entry); 110 111 // remove from our llist 112 this->internalDetach(entry); 113 114 delete entry; 115 } 116} 117 118void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ 119 if (maxResources) { 120 *maxResources = fMaxCount; 121 } 122 if (maxResourceBytes) { 123 *maxResourceBytes = fMaxBytes; 124 } 125} 126 127void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) { 128 bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes); 129 130 fMaxCount = maxResources; 131 fMaxBytes = maxResourceBytes; 132 133 if (smaller) { 134 this->purgeAsNeeded(); 135 } 136} 137 138void GrResourceCache::internalDetach(GrResourceCacheEntry* entry) { 139 fList.remove(entry); 140 fEntryCount -= 1; 141 fEntryBytes -= entry->fCachedSize; 142} 143 144void GrResourceCache::attachToHead(GrResourceCacheEntry* entry) { 145 fList.addToHead(entry); 146 147 fEntryCount += 1; 148 fEntryBytes += entry->fCachedSize; 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// This functor just searches for an entry with only a single ref (from 161// the texture cache itself). Presumably in this situation no one else 162// is relying on the texture. 163class GrTFindUnreffedFunctor { 164public: 165 bool operator()(const GrResourceCacheEntry* entry) const { 166 return entry->resource()->isPurgable(); 167 } 168}; 169 170 171void GrResourceCache::makeResourceMRU(GrGpuResource* resource) { 172 GrResourceCacheEntry* entry = resource->getCacheEntry(); 173 if (entry) { 174 this->internalDetach(entry); 175 this->attachToHead(entry); 176 } 177} 178 179void GrResourceCache::notifyPurgable(const GrGpuResource* resource) { 180 // Remove scratch textures from the cache the moment they become purgeable if 181 // scratch texture reuse is turned off. 182 SkASSERT(resource->getCacheEntry()); 183 if (resource->getCacheEntry()->key().getResourceType() == GrTexturePriv::ResourceType() && 184 resource->fIsScratch && 185 !fCaps->reuseScratchTextures() && 186 !(static_cast<const GrTexture*>(resource)->desc().fFlags & 187 kRenderTarget_GrTextureFlagBit)) { 188 this->deleteResource(resource->getCacheEntry()); 189 } 190} 191 192GrGpuResource* GrResourceCache::find(const GrResourceKey& key) { 193 GrAutoResourceCacheValidate atcv(this); 194 195 GrResourceCacheEntry* entry = NULL; 196 197 entry = fCache.find(key); 198 199 if (NULL == entry) { 200 return NULL; 201 } 202 203 // Make this resource MRU 204 this->internalDetach(entry); 205 this->attachToHead(entry); 206 207 // GrResourceCache2 is responsible for scratch resources. 208 SkASSERT(GrGpuResource::kNo_IsScratch == entry->resource()->fIsScratch); 209 return entry->fResource; 210} 211 212void GrResourceCache::addResource(const GrResourceKey& key, GrGpuResource* resource) { 213 SkASSERT(NULL == resource->getCacheEntry()); 214 // we don't expect to create new resources during a purge. In theory 215 // this could cause purgeAsNeeded() into an infinite loop (e.g. 216 // each resource destroyed creates and locks 2 resources and 217 // unlocks 1 thereby causing a new purge). 218 SkASSERT(!fPurging); 219 GrAutoResourceCacheValidate atcv(this); 220 221 GrResourceCacheEntry* entry = SkNEW_ARGS(GrResourceCacheEntry, (this, key, resource)); 222 resource->setCacheEntry(entry); 223 224 this->attachToHead(entry); 225 fCache.insert(key, entry); 226 227 this->purgeAsNeeded(); 228} 229 230void GrResourceCache::didIncreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountInc) { 231 fEntryBytes += amountInc; 232 this->purgeAsNeeded(); 233} 234 235void GrResourceCache::didDecreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountDec) { 236 fEntryBytes -= amountDec; 237#ifdef SK_DEBUG 238 this->validate(); 239#endif 240} 241 242/** 243 * Destroying a resource may potentially trigger the unlock of additional 244 * resources which in turn will trigger a nested purge. We block the nested 245 * purge using the fPurging variable. However, the initial purge will keep 246 * looping until either all resources in the cache are unlocked or we've met 247 * the budget. There is an assertion in createAndLock to check against a 248 * resource's destructor inserting new resources into the cache. If these 249 * new resources were unlocked before purgeAsNeeded completed it could 250 * potentially make purgeAsNeeded loop infinitely. 251 * 252 * extraCount and extraBytes are added to the current resource totals to account 253 * for incoming resources (e.g., GrContext is about to add 10MB split between 254 * 10 textures). 255 */ 256void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { 257 if (fPurging) { 258 return; 259 } 260 261 fPurging = true; 262 263 this->purgeInvalidated(); 264 265 this->internalPurge(extraCount, extraBytes); 266 if (((fEntryCount+extraCount) > fMaxCount || 267 (fEntryBytes+extraBytes) > fMaxBytes) && 268 fOverbudgetCB) { 269 // Despite the purge we're still over budget. See if Ganesh can 270 // release some resources and purge again. 271 if ((*fOverbudgetCB)(fOverbudgetData)) { 272 this->internalPurge(extraCount, extraBytes); 273 } 274 } 275 276 fPurging = false; 277} 278 279void GrResourceCache::purgeInvalidated() { 280 SkTDArray<GrResourceInvalidatedMessage> invalidated; 281 fInvalidationInbox.poll(&invalidated); 282 283 for (int i = 0; i < invalidated.count(); i++) { 284 while (GrResourceCacheEntry* entry = fCache.find(invalidated[i].key, GrTFindUnreffedFunctor())) { 285 this->deleteResource(entry); 286 } 287 } 288} 289 290void GrResourceCache::deleteResource(GrResourceCacheEntry* entry) { 291 SkASSERT(entry->fResource->isPurgable()); 292 293 // remove from our cache 294 fCache.remove(entry->key(), entry); 295 296 // remove from our llist 297 this->internalDetach(entry); 298 delete entry; 299} 300 301void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) { 302 SkASSERT(fPurging); 303 304 bool withinBudget = false; 305 bool changed = false; 306 307 // The purging process is repeated several times since one pass 308 // may free up other resources 309 do { 310 EntryList::Iter iter; 311 312 changed = false; 313 314 // Note: the following code relies on the fact that the 315 // doubly linked list doesn't invalidate its data/pointers 316 // outside of the specific area where a deletion occurs (e.g., 317 // in internalDetach) 318 GrResourceCacheEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 319 320 while (entry) { 321 GrAutoResourceCacheValidate atcv(this); 322 323 if ((fEntryCount+extraCount) <= fMaxCount && 324 (fEntryBytes+extraBytes) <= fMaxBytes) { 325 withinBudget = true; 326 break; 327 } 328 329 GrResourceCacheEntry* prev = iter.prev(); 330 if (entry->fResource->isPurgable()) { 331 changed = true; 332 this->deleteResource(entry); 333 } 334 entry = prev; 335 } 336 } while (!withinBudget && changed); 337} 338 339void GrResourceCache::purgeAllUnlocked() { 340 GrAutoResourceCacheValidate atcv(this); 341 342 // we can have one GrCacheable holding a lock on another 343 // so we don't want to just do a simple loop kicking each 344 // entry out. Instead change the budget and purge. 345 346 size_t savedMaxBytes = fMaxBytes; 347 int savedMaxCount = fMaxCount; 348 fMaxBytes = (size_t) -1; 349 fMaxCount = 0; 350 this->purgeAsNeeded(); 351 352#ifdef SK_DEBUG 353 if (!fCache.count()) { 354 SkASSERT(fList.isEmpty()); 355 } 356#endif 357 358 fMaxBytes = savedMaxBytes; 359 fMaxCount = savedMaxCount; 360} 361 362/////////////////////////////////////////////////////////////////////////////// 363 364#ifdef SK_DEBUG 365size_t GrResourceCache::countBytes(const EntryList& list) { 366 size_t bytes = 0; 367 368 EntryList::Iter iter; 369 370 const GrResourceCacheEntry* entry = iter.init(const_cast<EntryList&>(list), 371 EntryList::Iter::kTail_IterStart); 372 373 for ( ; entry; entry = iter.prev()) { 374 bytes += entry->resource()->gpuMemorySize(); 375 } 376 return bytes; 377} 378 379static bool both_zero_or_nonzero(int count, size_t bytes) { 380 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); 381} 382 383void GrResourceCache::validate() const { 384 fList.validate(); 385 SkASSERT(both_zero_or_nonzero(fEntryCount, fEntryBytes)); 386 SkASSERT(fEntryCount == fCache.count()); 387 388 EntryList::Iter iter; 389 390 // check that the shareable entries are okay 391 const GrResourceCacheEntry* entry = iter.init(const_cast<EntryList&>(fList), 392 EntryList::Iter::kHead_IterStart); 393 394 int count = 0; 395 for ( ; entry; entry = iter.next()) { 396 entry->validate(); 397 SkASSERT(fCache.find(entry->key())); 398 count += 1; 399 } 400 SkASSERT(count == fEntryCount); 401 402 size_t bytes = this->countBytes(fList); 403 SkASSERT(bytes == fEntryBytes); 404 SkASSERT(fList.countEntries() == fEntryCount); 405} 406#endif // SK_DEBUG 407 408#if GR_CACHE_STATS 409 410void GrResourceCache::printStats() { 411 int locked = 0; 412 413 EntryList::Iter iter; 414 415 GrResourceCacheEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); 416 417 for ( ; entry; entry = iter.prev()) { 418 if (entry->fResource->getRefCnt() > 1) { 419 ++locked; 420 } 421 } 422 423 SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); 424 SkDebugf("\t\tEntry Count: current %d (%d locked) high %d\n", 425 fEntryCount, locked, fHighWaterEntryCount); 426 SkDebugf("\t\tEntry Bytes: current %d high %d\n", 427 fEntryBytes, fHighWaterEntryBytes); 428} 429 430#endif 431 432/////////////////////////////////////////////////////////////////////////////// 433