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 GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource) 15 : fKey(key), fResource(resource) { 16 fLockCount = 0; 17 fPrev = fNext = NULL; 18 19 // we assume ownership of the resource, and will unref it when we die 20 GrAssert(resource); 21 } 22 23 GrResourceEntry::~GrResourceEntry() { 24 fResource->unref(); 25 } 26 27 #if GR_DEBUG 28 void GrResourceEntry::validate() const { 29 GrAssert(fLockCount >= 0); 30 GrAssert(fResource); 31 fResource->validate(); 32 } 33 #endif 34 35 /////////////////////////////////////////////////////////////////////////////// 36 37 GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) : 38 fMaxCount(maxCount), 39 fMaxBytes(maxBytes) { 40 fEntryCount = 0; 41 fUnlockedEntryCount = 0; 42 fEntryBytes = 0; 43 fClientDetachedCount = 0; 44 fClientDetachedBytes = 0; 45 46 fHead = fTail = NULL; 47 fPurging = false; 48 } 49 50 GrResourceCache::~GrResourceCache() { 51 GrAutoResourceCacheValidate atcv(this); 52 53 this->removeAll(); 54 } 55 56 void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ 57 if (maxResources) { 58 *maxResources = fMaxCount; 59 } 60 if (maxResourceBytes) { 61 *maxResourceBytes = fMaxBytes; 62 } 63 } 64 65 void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) { 66 bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes); 67 68 fMaxCount = maxResources; 69 fMaxBytes = maxResourceBytes; 70 71 if (smaller) { 72 this->purgeAsNeeded(); 73 } 74 } 75 76 void GrResourceCache::internalDetach(GrResourceEntry* entry, 77 bool clientDetach) { 78 GrResourceEntry* prev = entry->fPrev; 79 GrResourceEntry* next = entry->fNext; 80 81 if (prev) { 82 prev->fNext = next; 83 } else { 84 fHead = next; 85 } 86 if (next) { 87 next->fPrev = prev; 88 } else { 89 fTail = prev; 90 } 91 if (!entry->isLocked()) { 92 --fUnlockedEntryCount; 93 } 94 95 // update our stats 96 if (clientDetach) { 97 fClientDetachedCount += 1; 98 fClientDetachedBytes += entry->resource()->sizeInBytes(); 99 } else { 100 fEntryCount -= 1; 101 fEntryBytes -= entry->resource()->sizeInBytes(); 102 } 103 } 104 105 void GrResourceCache::attachToHead(GrResourceEntry* entry, 106 bool clientReattach) { 107 entry->fPrev = NULL; 108 entry->fNext = fHead; 109 if (fHead) { 110 fHead->fPrev = entry; 111 } 112 fHead = entry; 113 if (NULL == fTail) { 114 fTail = entry; 115 } 116 if (!entry->isLocked()) { 117 ++fUnlockedEntryCount; 118 } 119 120 // update our stats 121 if (clientReattach) { 122 fClientDetachedCount -= 1; 123 fClientDetachedBytes -= entry->resource()->sizeInBytes(); 124 } else { 125 fEntryCount += 1; 126 fEntryBytes += entry->resource()->sizeInBytes(); 127 } 128 } 129 130 class GrResourceCache::Key { 131 typedef GrResourceEntry T; 132 133 const GrResourceKey& fKey; 134 public: 135 Key(const GrResourceKey& key) : fKey(key) {} 136 137 uint32_t getHash() const { return fKey.hashIndex(); } 138 139 static bool LT(const T& entry, const Key& key) { 140 return entry.key() < key.fKey; 141 } 142 static bool EQ(const T& entry, const Key& key) { 143 return entry.key() == key.fKey; 144 } 145 #if GR_DEBUG 146 static uint32_t GetHash(const T& entry) { 147 return entry.key().hashIndex(); 148 } 149 static bool LT(const T& a, const T& b) { 150 return a.key() < b.key(); 151 } 152 static bool EQ(const T& a, const T& b) { 153 return a.key() == b.key(); 154 } 155 #endif 156 }; 157 158 GrResourceEntry* GrResourceCache::findAndLock(const GrResourceKey& key, 159 LockType type) { 160 GrAutoResourceCacheValidate atcv(this); 161 162 GrResourceEntry* entry = fCache.find(key); 163 if (entry) { 164 this->internalDetach(entry, false); 165 // mark the entry as "busy" so it doesn't get purged 166 // do this between detach and attach for locked count tracking 167 if (kNested_LockType == type || !entry->isLocked()) { 168 entry->lock(); 169 } 170 this->attachToHead(entry, false); 171 } 172 return entry; 173 } 174 175 bool GrResourceCache::hasKey(const GrResourceKey& key) const { 176 return NULL != fCache.find(key); 177 } 178 179 GrResourceEntry* GrResourceCache::createAndLock(const GrResourceKey& key, 180 GrResource* resource) { 181 // we don't expect to create new resources during a purge. In theory 182 // this could cause purgeAsNeeded() into an infinite loop (e.g. 183 // each resource destroyed creates and locks 2 resources and 184 // unlocks 1 thereby causing a new purge). 185 GrAssert(!fPurging); 186 GrAutoResourceCacheValidate atcv(this); 187 188 GrResourceEntry* entry = new GrResourceEntry(key, resource); 189 190 // mark the entry as "busy" so it doesn't get purged 191 // do this before attach for locked count tracking 192 entry->lock(); 193 194 this->attachToHead(entry, false); 195 fCache.insert(key, entry); 196 197 #if GR_DUMP_TEXTURE_UPLOAD 198 GrPrintf("--- add resource to cache %p, count=%d bytes= %d %d\n", 199 entry, fEntryCount, resource->sizeInBytes(), fEntryBytes); 200 #endif 201 202 this->purgeAsNeeded(); 203 return entry; 204 } 205 206 void GrResourceCache::detach(GrResourceEntry* entry) { 207 GrAutoResourceCacheValidate atcv(this); 208 internalDetach(entry, true); 209 fCache.remove(entry->fKey, entry); 210 } 211 212 void GrResourceCache::reattachAndUnlock(GrResourceEntry* entry) { 213 GrAutoResourceCacheValidate atcv(this); 214 if (entry->resource()->isValid()) { 215 attachToHead(entry, true); 216 fCache.insert(entry->key(), entry); 217 } else { 218 // If the resource went invalid while it was detached then purge it 219 // This can happen when a 3D context was lost, 220 // the client called GrContext::contextDestroyed() to notify Gr, 221 // and then later an SkGpuDevice's destructor releases its backing 222 // texture (which was invalidated at contextDestroyed time). 223 fClientDetachedCount -= 1; 224 fEntryCount -= 1; 225 size_t size = entry->resource()->sizeInBytes(); 226 fClientDetachedBytes -= size; 227 fEntryBytes -= size; 228 } 229 this->unlock(entry); 230 } 231 232 void GrResourceCache::unlock(GrResourceEntry* entry) { 233 GrAutoResourceCacheValidate atcv(this); 234 235 GrAssert(entry); 236 GrAssert(entry->isLocked()); 237 GrAssert(fCache.find(entry->key())); 238 239 entry->unlock(); 240 if (!entry->isLocked()) { 241 ++fUnlockedEntryCount; 242 } 243 this->purgeAsNeeded(); 244 } 245 246 /** 247 * Destroying a resource may potentially trigger the unlock of additional 248 * resources which in turn will trigger a nested purge. We block the nested 249 * purge using the fPurging variable. However, the initial purge will keep 250 * looping until either all resources in the cache are unlocked or we've met 251 * the budget. There is an assertion in createAndLock to check against a 252 * resource's destructor inserting new resources into the cache. If these 253 * new resources were unlocked before purgeAsNeeded completed it could 254 * potentially make purgeAsNeeded loop infinitely. 255 */ 256 void GrResourceCache::purgeAsNeeded() { 257 if (!fPurging) { 258 fPurging = true; 259 bool withinBudget = false; 260 do { 261 GrResourceEntry* entry = fTail; 262 while (entry && fUnlockedEntryCount) { 263 GrAutoResourceCacheValidate atcv(this); 264 if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) { 265 withinBudget = true; 266 break; 267 } 268 269 GrResourceEntry* prev = entry->fPrev; 270 if (!entry->isLocked()) { 271 // remove from our cache 272 fCache.remove(entry->fKey, entry); 273 274 // remove from our llist 275 this->internalDetach(entry, false); 276 277 #if GR_DUMP_TEXTURE_UPLOAD 278 GrPrintf("--- ~resource from cache %p [%d %d]\n", 279 entry->resource(), 280 entry->resource()->width(), 281 entry->resource()->height()); 282 #endif 283 delete entry; 284 } 285 entry = prev; 286 } 287 } while (!withinBudget && fUnlockedEntryCount); 288 fPurging = false; 289 } 290 } 291 292 void GrResourceCache::removeAll() { 293 GrAutoResourceCacheValidate atcv(this); 294 295 GrResourceEntry* entry = fHead; 296 297 // we can have one GrResource holding a lock on another 298 // so we don't want to just do a simple loop kicking each 299 // entry out. Instead change the budget and purge. 300 301 int savedMaxBytes = fMaxBytes; 302 int savedMaxCount = fMaxCount; 303 fMaxBytes = -1; 304 fMaxCount = 0; 305 this->purgeAsNeeded(); 306 307 #if GR_DEBUG 308 GrAssert(!fUnlockedEntryCount); 309 if (!fCache.count()) { 310 // Items may have been detached from the cache (such as the backing 311 // texture for an SkGpuDevice). The above purge would not have removed 312 // them. 313 GrAssert(fEntryCount == fClientDetachedCount); 314 GrAssert(fEntryBytes == fClientDetachedBytes); 315 GrAssert(NULL == fHead); 316 GrAssert(NULL == fTail); 317 } 318 #endif 319 320 fMaxBytes = savedMaxBytes; 321 fMaxCount = savedMaxCount; 322 } 323 324 /////////////////////////////////////////////////////////////////////////////// 325 326 #if GR_DEBUG 327 static int countMatches(const GrResourceEntry* head, const GrResourceEntry* target) { 328 const GrResourceEntry* entry = head; 329 int count = 0; 330 while (entry) { 331 if (target == entry) { 332 count += 1; 333 } 334 entry = entry->next(); 335 } 336 return count; 337 } 338 339 #if GR_DEBUG 340 static bool both_zero_or_nonzero(int count, size_t bytes) { 341 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); 342 } 343 #endif 344 345 void GrResourceCache::validate() const { 346 GrAssert(!fHead == !fTail); 347 GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes)); 348 GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes)); 349 GrAssert(fClientDetachedBytes <= fEntryBytes); 350 GrAssert(fClientDetachedCount <= fEntryCount); 351 GrAssert((fEntryCount - fClientDetachedCount) == fCache.count()); 352 353 fCache.validate(); 354 355 GrResourceEntry* entry = fHead; 356 int count = 0; 357 int unlockCount = 0; 358 size_t bytes = 0; 359 while (entry) { 360 entry->validate(); 361 GrAssert(fCache.find(entry->key())); 362 count += 1; 363 bytes += entry->resource()->sizeInBytes(); 364 if (!entry->isLocked()) { 365 unlockCount += 1; 366 } 367 entry = entry->fNext; 368 } 369 GrAssert(count == fEntryCount - fClientDetachedCount); 370 GrAssert(bytes == fEntryBytes - fClientDetachedBytes); 371 GrAssert(unlockCount == fUnlockedEntryCount); 372 373 count = 0; 374 for (entry = fTail; entry; entry = entry->fPrev) { 375 count += 1; 376 } 377 GrAssert(count == fEntryCount - fClientDetachedCount); 378 379 for (int i = 0; i < count; i++) { 380 int matches = countMatches(fHead, fCache.getArray()[i]); 381 GrAssert(1 == matches); 382 } 383 } 384 #endif 385