GrResourceCache.cpp revision f320e04c50a1c8a861bc1d8f50bf732044ff9843
1 2/* 3 * Copyright 2014 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#include "GrResourceCache.h" 11#include "GrGpuResourceCacheAccess.h" 12#include "SkChecksum.h" 13#include "SkGr.h" 14#include "SkMessageBus.h" 15 16DECLARE_SKMESSAGEBUS_MESSAGE(GrContentKeyInvalidatedMessage); 17 18////////////////////////////////////////////////////////////////////////////// 19 20GrScratchKey::ResourceType GrScratchKey::GenerateResourceType() { 21 static int32_t gType = INHERITED::kInvalidDomain + 1; 22 23 int32_t type = sk_atomic_inc(&gType); 24 if (type > SK_MaxU16) { 25 SkFAIL("Too many Resource Types"); 26 } 27 28 return static_cast<ResourceType>(type); 29} 30 31GrContentKey::Domain GrContentKey::GenerateDomain() { 32 static int32_t gDomain = INHERITED::kInvalidDomain + 1; 33 34 int32_t domain = sk_atomic_inc(&gDomain); 35 if (domain > SK_MaxU16) { 36 SkFAIL("Too many Content Key Domains"); 37 } 38 39 return static_cast<Domain>(domain); 40} 41uint32_t GrResourceKeyHash(const uint32_t* data, size_t size) { 42 return SkChecksum::Compute(data, size); 43} 44 45////////////////////////////////////////////////////////////////////////////// 46 47class GrResourceCache::AutoValidate : ::SkNoncopyable { 48public: 49 AutoValidate(GrResourceCache* cache) : fCache(cache) { cache->validate(); } 50 ~AutoValidate() { fCache->validate(); } 51private: 52 GrResourceCache* fCache; 53}; 54 55 ////////////////////////////////////////////////////////////////////////////// 56 57static const int kDefaultMaxCount = 2 * (1 << 10); 58static const size_t kDefaultMaxSize = 96 * (1 << 20); 59 60GrResourceCache::GrResourceCache() 61 : fTimestamp(0) 62 , fMaxCount(kDefaultMaxCount) 63 , fMaxBytes(kDefaultMaxSize) 64#if GR_CACHE_STATS 65 , fHighWaterCount(0) 66 , fHighWaterBytes(0) 67 , fBudgetedHighWaterCount(0) 68 , fBudgetedHighWaterBytes(0) 69#endif 70 , fBytes(0) 71 , fBudgetedCount(0) 72 , fBudgetedBytes(0) 73 , fOverBudgetCB(NULL) 74 , fOverBudgetData(NULL) { 75 SkDEBUGCODE(fCount = 0;) 76} 77 78GrResourceCache::~GrResourceCache() { 79 this->releaseAll(); 80} 81 82void GrResourceCache::setLimits(int count, size_t bytes) { 83 fMaxCount = count; 84 fMaxBytes = bytes; 85 this->purgeAsNeeded(); 86} 87 88void GrResourceCache::insertResource(GrGpuResource* resource) { 89 SkASSERT(resource); 90 SkASSERT(!this->isInCache(resource)); 91 SkASSERT(!resource->wasDestroyed()); 92 SkASSERT(!resource->isPurgeable()); 93 this->addToNonpurgeableArray(resource); 94 95 size_t size = resource->gpuMemorySize(); 96 SkDEBUGCODE(++fCount;) 97 fBytes += size; 98#if GR_CACHE_STATS 99 fHighWaterCount = SkTMax(this->getResourceCount(), fHighWaterCount); 100 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes); 101#endif 102 if (resource->resourcePriv().isBudgeted()) { 103 ++fBudgetedCount; 104 fBudgetedBytes += size; 105#if GR_CACHE_STATS 106 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount); 107 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes); 108#endif 109 } 110 if (resource->resourcePriv().getScratchKey().isValid()) { 111 SkASSERT(!resource->cacheAccess().isWrapped()); 112 fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource); 113 } 114 115 resource->cacheAccess().setTimestamp(fTimestamp++); 116 117 this->purgeAsNeeded(); 118} 119 120void GrResourceCache::removeResource(GrGpuResource* resource) { 121 this->validate(); 122 SkASSERT(this->isInCache(resource)); 123 124 if (resource->isPurgeable()) { 125 fPurgeableQueue.remove(resource); 126 } else { 127 this->removeFromNonpurgeableArray(resource); 128 } 129 130 size_t size = resource->gpuMemorySize(); 131 SkDEBUGCODE(--fCount;) 132 fBytes -= size; 133 if (resource->resourcePriv().isBudgeted()) { 134 --fBudgetedCount; 135 fBudgetedBytes -= size; 136 } 137 138 if (resource->resourcePriv().getScratchKey().isValid()) { 139 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource); 140 } 141 if (resource->getContentKey().isValid()) { 142 fContentHash.remove(resource->getContentKey()); 143 } 144 this->validate(); 145} 146 147void GrResourceCache::abandonAll() { 148 AutoValidate av(this); 149 150 while (fNonpurgeableResources.count()) { 151 GrGpuResource* back = *(fNonpurgeableResources.end() - 1); 152 SkASSERT(!back->wasDestroyed()); 153 back->cacheAccess().abandon(); 154 } 155 156 while (fPurgeableQueue.count()) { 157 GrGpuResource* top = fPurgeableQueue.peek(); 158 SkASSERT(!top->wasDestroyed()); 159 top->cacheAccess().abandon(); 160 } 161 162 SkASSERT(!fScratchMap.count()); 163 SkASSERT(!fContentHash.count()); 164 SkASSERT(!fCount); 165 SkASSERT(!this->getResourceCount()); 166 SkASSERT(!fBytes); 167 SkASSERT(!fBudgetedCount); 168 SkASSERT(!fBudgetedBytes); 169} 170 171void GrResourceCache::releaseAll() { 172 AutoValidate av(this); 173 174 while(fNonpurgeableResources.count()) { 175 GrGpuResource* back = *(fNonpurgeableResources.end() - 1); 176 SkASSERT(!back->wasDestroyed()); 177 back->cacheAccess().release(); 178 } 179 180 while (fPurgeableQueue.count()) { 181 GrGpuResource* top = fPurgeableQueue.peek(); 182 SkASSERT(!top->wasDestroyed()); 183 top->cacheAccess().release(); 184 } 185 186 SkASSERT(!fScratchMap.count()); 187 SkASSERT(!fContentHash.count()); 188 SkASSERT(!fCount); 189 SkASSERT(!this->getResourceCount()); 190 SkASSERT(!fBytes); 191 SkASSERT(!fBudgetedCount); 192 SkASSERT(!fBudgetedBytes); 193} 194 195class GrResourceCache::AvailableForScratchUse { 196public: 197 AvailableForScratchUse(bool rejectPendingIO) : fRejectPendingIO(rejectPendingIO) { } 198 199 bool operator()(const GrGpuResource* resource) const { 200 if (resource->internalHasRef() || !resource->cacheAccess().isScratch()) { 201 return false; 202 } 203 return !fRejectPendingIO || !resource->internalHasPendingIO(); 204 } 205 206private: 207 bool fRejectPendingIO; 208}; 209 210GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& scratchKey, 211 uint32_t flags) { 212 SkASSERT(scratchKey.isValid()); 213 214 GrGpuResource* resource; 215 if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFlag)) { 216 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true)); 217 if (resource) { 218 this->refAndMakeResourceMRU(resource); 219 this->validate(); 220 return resource; 221 } else if (flags & kRequireNoPendingIO_ScratchFlag) { 222 return NULL; 223 } 224 // TODO: fail here when kPrefer is specified, we didn't find a resource without pending io, 225 // but there is still space in our budget for the resource. 226 } 227 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(false)); 228 if (resource) { 229 this->refAndMakeResourceMRU(resource); 230 this->validate(); 231 } 232 return resource; 233} 234 235void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) { 236 SkASSERT(resource->resourcePriv().getScratchKey().isValid()); 237 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource); 238} 239 240void GrResourceCache::willRemoveContentKey(const GrGpuResource* resource) { 241 // Someone has a ref to this resource in order to invalidate it. When the ref count reaches 242 // zero we will get a notifyPurgable() and figure out what to do with it. 243 SkASSERT(resource->getContentKey().isValid()); 244 fContentHash.remove(resource->getContentKey()); 245} 246 247bool GrResourceCache::didSetContentKey(GrGpuResource* resource) { 248 SkASSERT(resource); 249 SkASSERT(this->isInCache(resource)); 250 SkASSERT(resource->getContentKey().isValid()); 251 252 GrGpuResource* res = fContentHash.find(resource->getContentKey()); 253 if (NULL != res) { 254 return false; 255 } 256 257 fContentHash.add(resource); 258 this->validate(); 259 return true; 260} 261 262void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) { 263 SkASSERT(resource); 264 SkASSERT(this->isInCache(resource)); 265 if (resource->isPurgeable()) { 266 // It's about to become unpurgeable. 267 fPurgeableQueue.remove(resource); 268 this->addToNonpurgeableArray(resource); 269 } 270 resource->ref(); 271 resource->cacheAccess().setTimestamp(fTimestamp++); 272 this->validate(); 273} 274 275void GrResourceCache::notifyPurgeable(GrGpuResource* resource) { 276 SkASSERT(resource); 277 SkASSERT(this->isInCache(resource)); 278 SkASSERT(resource->isPurgeable()); 279 280 this->removeFromNonpurgeableArray(resource); 281 fPurgeableQueue.insert(resource); 282 283 if (!resource->resourcePriv().isBudgeted()) { 284 // Check whether this resource could still be used as a scratch resource. 285 if (!resource->cacheAccess().isWrapped() && 286 resource->resourcePriv().getScratchKey().isValid()) { 287 // We won't purge an existing resource to make room for this one. 288 if (fBudgetedCount < fMaxCount && 289 fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes) { 290 resource->resourcePriv().makeBudgeted(); 291 return; 292 } 293 } 294 } else { 295 // Purge the resource immediately if we're over budget 296 // Also purge if the resource has neither a valid scratch key nor a content key. 297 bool noKey = !resource->resourcePriv().getScratchKey().isValid() && 298 !resource->getContentKey().isValid(); 299 if (!this->overBudget() && !noKey) { 300 return; 301 } 302 } 303 304 SkDEBUGCODE(int beforeCount = this->getResourceCount();) 305 resource->cacheAccess().release(); 306 // We should at least free this resource, perhaps dependent resources as well. 307 SkASSERT(this->getResourceCount() < beforeCount); 308 this->validate(); 309} 310 311void GrResourceCache::didChangeGpuMemorySize(const GrGpuResource* resource, size_t oldSize) { 312 // SkASSERT(!fPurging); GrPathRange increases size during flush. :( 313 SkASSERT(resource); 314 SkASSERT(this->isInCache(resource)); 315 316 ptrdiff_t delta = resource->gpuMemorySize() - oldSize; 317 318 fBytes += delta; 319#if GR_CACHE_STATS 320 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes); 321#endif 322 if (resource->resourcePriv().isBudgeted()) { 323 fBudgetedBytes += delta; 324#if GR_CACHE_STATS 325 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes); 326#endif 327 } 328 329 this->purgeAsNeeded(); 330 this->validate(); 331} 332 333void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) { 334 SkASSERT(resource); 335 SkASSERT(this->isInCache(resource)); 336 337 size_t size = resource->gpuMemorySize(); 338 339 if (resource->resourcePriv().isBudgeted()) { 340 ++fBudgetedCount; 341 fBudgetedBytes += size; 342#if GR_CACHE_STATS 343 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes); 344 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount); 345#endif 346 this->purgeAsNeeded(); 347 } else { 348 --fBudgetedCount; 349 fBudgetedBytes -= size; 350 } 351 352 this->validate(); 353} 354 355void GrResourceCache::internalPurgeAsNeeded() { 356 SkASSERT(this->overBudget()); 357 358 bool stillOverbudget = true; 359 while (fPurgeableQueue.count()) { 360 GrGpuResource* resource = fPurgeableQueue.peek(); 361 SkASSERT(resource->isPurgeable()); 362 resource->cacheAccess().release(); 363 if (!this->overBudget()) { 364 stillOverbudget = false; 365 break; 366 } 367 } 368 369 this->validate(); 370 371 if (stillOverbudget) { 372 // Despite the purge we're still over budget. Call our over budget callback. If this frees 373 // any resources then we'll get notifyPurgeable() calls and take appropriate action. 374 (*fOverBudgetCB)(fOverBudgetData); 375 this->validate(); 376 } 377} 378 379void GrResourceCache::purgeAllUnlocked() { 380 // We could disable maintaining the heap property here, but it would add a lot of complexity. 381 // Moreover, this is rarely called. 382 while (fPurgeableQueue.count()) { 383 GrGpuResource* resource = fPurgeableQueue.peek(); 384 SkASSERT(resource->isPurgeable()); 385 resource->cacheAccess().release(); 386 } 387 388 this->validate(); 389} 390 391void GrResourceCache::processInvalidContentKeys( 392 const SkTArray<GrContentKeyInvalidatedMessage>& msgs) { 393 for (int i = 0; i < msgs.count(); ++i) { 394 GrGpuResource* resource = this->findAndRefContentResource(msgs[i].key()); 395 if (resource) { 396 resource->resourcePriv().removeContentKey(); 397 resource->unref(); // will call notifyPurgeable, if it is indeed now purgeable. 398 } 399 } 400} 401 402void GrResourceCache::addToNonpurgeableArray(GrGpuResource* resource) { 403 int index = fNonpurgeableResources.count(); 404 *fNonpurgeableResources.append() = resource; 405 *resource->cacheAccess().accessCacheIndex() = index; 406} 407 408void GrResourceCache::removeFromNonpurgeableArray(GrGpuResource* resource) { 409 int* index = resource->cacheAccess().accessCacheIndex(); 410 // Fill the whole we will create in the array with the tail object, adjust its index, and 411 // then pop the array 412 GrGpuResource* tail = *(fNonpurgeableResources.end() - 1); 413 SkASSERT(fNonpurgeableResources[*index] == resource); 414 fNonpurgeableResources[*index] = tail; 415 *tail->cacheAccess().accessCacheIndex() = *index; 416 fNonpurgeableResources.pop(); 417 SkDEBUGCODE(*index = -1); 418} 419 420#ifdef SK_DEBUG 421void GrResourceCache::validate() const { 422 // Reduce the frequency of validations for large resource counts. 423 static SkRandom gRandom; 424 int mask = (SkNextPow2(fCount + 1) >> 5) - 1; 425 if (~mask && (gRandom.nextU() & mask)) { 426 return; 427 } 428 429 struct Stats { 430 size_t fBytes; 431 int fBudgetedCount; 432 size_t fBudgetedBytes; 433 int fLocked; 434 int fScratch; 435 int fCouldBeScratch; 436 int fContent; 437 const ScratchMap* fScratchMap; 438 const ContentHash* fContentHash; 439 440 Stats(const GrResourceCache* cache) { 441 memset(this, 0, sizeof(*this)); 442 fScratchMap = &cache->fScratchMap; 443 fContentHash = &cache->fContentHash; 444 } 445 446 void update(GrGpuResource* resource) { 447 fBytes += resource->gpuMemorySize(); 448 449 if (!resource->isPurgeable()) { 450 ++fLocked; 451 } 452 453 if (resource->cacheAccess().isScratch()) { 454 SkASSERT(!resource->getContentKey().isValid()); 455 ++fScratch; 456 SkASSERT(fScratchMap->countForKey(resource->resourcePriv().getScratchKey())); 457 SkASSERT(!resource->cacheAccess().isWrapped()); 458 } else if (resource->resourcePriv().getScratchKey().isValid()) { 459 SkASSERT(!resource->resourcePriv().isBudgeted() || 460 resource->getContentKey().isValid()); 461 ++fCouldBeScratch; 462 SkASSERT(fScratchMap->countForKey(resource->resourcePriv().getScratchKey())); 463 SkASSERT(!resource->cacheAccess().isWrapped()); 464 } 465 const GrContentKey& contentKey = resource->getContentKey(); 466 if (contentKey.isValid()) { 467 ++fContent; 468 SkASSERT(fContentHash->find(contentKey) == resource); 469 SkASSERT(!resource->cacheAccess().isWrapped()); 470 SkASSERT(resource->resourcePriv().isBudgeted()); 471 } 472 473 if (resource->resourcePriv().isBudgeted()) { 474 ++fBudgetedCount; 475 fBudgetedBytes += resource->gpuMemorySize(); 476 } 477 } 478 }; 479 480 Stats stats(this); 481 482 for (int i = 0; i < fNonpurgeableResources.count(); ++i) { 483 SkASSERT(!fNonpurgeableResources[i]->isPurgeable()); 484 SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i); 485 SkASSERT(!fNonpurgeableResources[i]->wasDestroyed()); 486 stats.update(fNonpurgeableResources[i]); 487 } 488 for (int i = 0; i < fPurgeableQueue.count(); ++i) { 489 SkASSERT(fPurgeableQueue.at(i)->isPurgeable()); 490 SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i); 491 SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed()); 492 stats.update(fPurgeableQueue.at(i)); 493 } 494 495 SkASSERT(fCount == this->getResourceCount()); 496 SkASSERT(fBudgetedCount <= fCount); 497 SkASSERT(fBudgetedBytes <= fBytes); 498 SkASSERT(stats.fBytes == fBytes); 499 SkASSERT(stats.fBudgetedBytes == fBudgetedBytes); 500 SkASSERT(stats.fBudgetedCount == fBudgetedCount); 501#if GR_CACHE_STATS 502 SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount); 503 SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes); 504 SkASSERT(fBytes <= fHighWaterBytes); 505 SkASSERT(fCount <= fHighWaterCount); 506 SkASSERT(fBudgetedBytes <= fBudgetedHighWaterBytes); 507 SkASSERT(fBudgetedCount <= fBudgetedHighWaterCount); 508#endif 509 SkASSERT(stats.fContent == fContentHash.count()); 510 SkASSERT(stats.fScratch + stats.fCouldBeScratch == fScratchMap.count()); 511 512 // This assertion is not currently valid because we can be in recursive notifyIsPurgeable() 513 // calls. This will be fixed when subresource registration is explicit. 514 // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount; 515 // SkASSERT(!overBudget || locked == count || fPurging); 516} 517 518bool GrResourceCache::isInCache(const GrGpuResource* resource) const { 519 int index = *resource->cacheAccess().accessCacheIndex(); 520 if (index < 0) { 521 return false; 522 } 523 if (index < fPurgeableQueue.count() && fPurgeableQueue.at(index) == resource) { 524 return true; 525 } 526 if (index < fNonpurgeableResources.count() && fNonpurgeableResources[index] == resource) { 527 return true; 528 } 529 SkDEBUGFAIL("Resource index should be -1 or the resource should be in the cache."); 530 return false; 531} 532 533#endif 534