GrResourceCache.cpp revision 9f2d1571ed1f0ed579e5d7779c46a90e20f30f22
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 , fCount(0) 71 , fBytes(0) 72 , fBudgetedCount(0) 73 , fBudgetedBytes(0) 74 , fOverBudgetCB(NULL) 75 , fOverBudgetData(NULL) { 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(!resource->wasDestroyed()); 91 SkASSERT(!this->isInCache(resource)); 92 fResources.addToHead(resource); 93 94 size_t size = resource->gpuMemorySize(); 95 ++fCount; 96 fBytes += size; 97#if GR_CACHE_STATS 98 fHighWaterCount = SkTMax(fCount, fHighWaterCount); 99 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes); 100#endif 101 if (resource->resourcePriv().isBudgeted()) { 102 ++fBudgetedCount; 103 fBudgetedBytes += size; 104#if GR_CACHE_STATS 105 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount); 106 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes); 107#endif 108 } 109 if (resource->resourcePriv().getScratchKey().isValid()) { 110 SkASSERT(!resource->cacheAccess().isWrapped()); 111 fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource); 112 } 113 114 resource->cacheAccess().setTimestamp(fTimestamp++); 115 116 this->purgeAsNeeded(); 117} 118 119void GrResourceCache::removeResource(GrGpuResource* resource) { 120 this->validate(); 121 SkASSERT(this->isInCache(resource)); 122 123 if (resource->isPurgeable()) { 124 fPurgeableQueue.remove(resource); 125 } 126 127 size_t size = resource->gpuMemorySize(); 128 --fCount; 129 fBytes -= size; 130 if (resource->resourcePriv().isBudgeted()) { 131 --fBudgetedCount; 132 fBudgetedBytes -= size; 133 } 134 135 fResources.remove(resource); 136 if (resource->resourcePriv().getScratchKey().isValid()) { 137 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource); 138 } 139 if (resource->getContentKey().isValid()) { 140 fContentHash.remove(resource->getContentKey()); 141 } 142 this->validate(); 143} 144 145void GrResourceCache::abandonAll() { 146 AutoValidate av(this); 147 148 while (GrGpuResource* head = fResources.head()) { 149 SkASSERT(!head->wasDestroyed()); 150 head->cacheAccess().abandon(); 151 // abandon should have already removed this from the list. 152 SkASSERT(head != fResources.head()); 153 } 154 SkASSERT(!fScratchMap.count()); 155 SkASSERT(!fContentHash.count()); 156 SkASSERT(!fCount); 157 SkASSERT(!fBytes); 158 SkASSERT(!fBudgetedCount); 159 SkASSERT(!fBudgetedBytes); 160} 161 162void GrResourceCache::releaseAll() { 163 AutoValidate av(this); 164 165 while (GrGpuResource* head = fResources.head()) { 166 SkASSERT(!head->wasDestroyed()); 167 head->cacheAccess().release(); 168 // release should have already removed this from the list. 169 SkASSERT(head != fResources.head()); 170 } 171 SkASSERT(!fScratchMap.count()); 172 SkASSERT(!fCount); 173 SkASSERT(!fBytes); 174 SkASSERT(!fBudgetedCount); 175 SkASSERT(!fBudgetedBytes); 176} 177 178class GrResourceCache::AvailableForScratchUse { 179public: 180 AvailableForScratchUse(bool rejectPendingIO) : fRejectPendingIO(rejectPendingIO) { } 181 182 bool operator()(const GrGpuResource* resource) const { 183 if (resource->internalHasRef() || !resource->cacheAccess().isScratch()) { 184 return false; 185 } 186 return !fRejectPendingIO || !resource->internalHasPendingIO(); 187 } 188 189private: 190 bool fRejectPendingIO; 191}; 192 193GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& scratchKey, 194 uint32_t flags) { 195 SkASSERT(scratchKey.isValid()); 196 197 GrGpuResource* resource; 198 if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFlag)) { 199 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true)); 200 if (resource) { 201 this->refAndMakeResourceMRU(resource); 202 this->validate(); 203 return resource; 204 } else if (flags & kRequireNoPendingIO_ScratchFlag) { 205 return NULL; 206 } 207 // TODO: fail here when kPrefer is specified, we didn't find a resource without pending io, 208 // but there is still space in our budget for the resource. 209 } 210 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(false)); 211 if (resource) { 212 this->refAndMakeResourceMRU(resource); 213 this->validate(); 214 } 215 return resource; 216} 217 218void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) { 219 SkASSERT(resource->resourcePriv().getScratchKey().isValid()); 220 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource); 221} 222 223void GrResourceCache::willRemoveContentKey(const GrGpuResource* resource) { 224 // Someone has a ref to this resource in order to invalidate it. When the ref count reaches 225 // zero we will get a notifyPurgable() and figure out what to do with it. 226 SkASSERT(resource->getContentKey().isValid()); 227 fContentHash.remove(resource->getContentKey()); 228} 229 230bool GrResourceCache::didSetContentKey(GrGpuResource* resource) { 231 SkASSERT(resource); 232 SkASSERT(this->isInCache(resource)); 233 SkASSERT(resource->getContentKey().isValid()); 234 235 GrGpuResource* res = fContentHash.find(resource->getContentKey()); 236 if (NULL != res) { 237 return false; 238 } 239 240 fContentHash.add(resource); 241 this->validate(); 242 return true; 243} 244 245void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) { 246 SkASSERT(resource); 247 SkASSERT(this->isInCache(resource)); 248 if (resource->isPurgeable()) { 249 // It's about to become unpurgeable. 250 fPurgeableQueue.remove(resource); 251 } 252 resource->ref(); 253 resource->cacheAccess().setTimestamp(fTimestamp++); 254 SkASSERT(!resource->isPurgeable()); 255} 256 257void GrResourceCache::notifyPurgeable(GrGpuResource* resource) { 258 SkASSERT(resource); 259 SkASSERT(this->isInCache(resource)); 260 SkASSERT(resource->isPurgeable()); 261 262 SkASSERT(-1 == *resource->cacheAccess().accessCacheIndex()); 263 fPurgeableQueue.insert(resource); 264 265 if (!resource->resourcePriv().isBudgeted()) { 266 // Check whether this resource could still be used as a scratch resource. 267 if (!resource->cacheAccess().isWrapped() && 268 resource->resourcePriv().getScratchKey().isValid()) { 269 // We won't purge an existing resource to make room for this one. 270 bool underBudget = fBudgetedCount < fMaxCount && 271 fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes; 272 if (underBudget) { 273 resource->resourcePriv().makeBudgeted(); 274 return; 275 } 276 } 277 } else { 278 // Purge the resource immediately if we're over budget 279 bool overBudget = fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxBytes; 280 281 // Also purge if the resource has neither a valid scratch key nor a content key. 282 bool noKey = !resource->resourcePriv().getScratchKey().isValid() && 283 !resource->getContentKey().isValid(); 284 if (!overBudget && !noKey) { 285 return; 286 } 287 } 288 289 SkDEBUGCODE(int beforeCount = fCount;) 290 resource->cacheAccess().release(); 291 // We should at least free this resource, perhaps dependent resources as well. 292 SkASSERT(fCount < beforeCount); 293 this->validate(); 294} 295 296void GrResourceCache::didChangeGpuMemorySize(const GrGpuResource* resource, size_t oldSize) { 297 // SkASSERT(!fPurging); GrPathRange increases size during flush. :( 298 SkASSERT(resource); 299 SkASSERT(this->isInCache(resource)); 300 301 ptrdiff_t delta = resource->gpuMemorySize() - oldSize; 302 303 fBytes += delta; 304#if GR_CACHE_STATS 305 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes); 306#endif 307 if (resource->resourcePriv().isBudgeted()) { 308 fBudgetedBytes += delta; 309#if GR_CACHE_STATS 310 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes); 311#endif 312 } 313 314 this->purgeAsNeeded(); 315 this->validate(); 316} 317 318void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) { 319 SkASSERT(resource); 320 SkASSERT(this->isInCache(resource)); 321 322 size_t size = resource->gpuMemorySize(); 323 324 if (resource->resourcePriv().isBudgeted()) { 325 ++fBudgetedCount; 326 fBudgetedBytes += size; 327#if GR_CACHE_STATS 328 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes); 329 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount); 330#endif 331 this->purgeAsNeeded(); 332 } else { 333 --fBudgetedCount; 334 fBudgetedBytes -= size; 335 } 336 337 this->validate(); 338} 339 340void GrResourceCache::internalPurgeAsNeeded() { 341 SkASSERT(fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxBytes); 342 343 bool stillOverbudget = true; 344 while (fPurgeableQueue.count()) { 345 GrGpuResource* resource = fPurgeableQueue.peek(); 346 SkASSERT(resource->isPurgeable()); 347 resource->cacheAccess().release(); 348 if (fBudgetedCount <= fMaxCount && fBudgetedBytes <= fMaxBytes) { 349 stillOverbudget = false; 350 break; 351 } 352 } 353 354 this->validate(); 355 356 if (stillOverbudget) { 357 // Despite the purge we're still over budget. Call our over budget callback. If this frees 358 // any resources then we'll get notifyPurgeable() calls and take appropriate action. 359 (*fOverBudgetCB)(fOverBudgetData); 360 this->validate(); 361 } 362} 363 364void GrResourceCache::purgeAllUnlocked() { 365 // We could disable maintaining the heap property here, but it would add a lot of complexity. 366 // Moreover, this is rarely called. 367 while (fPurgeableQueue.count()) { 368 GrGpuResource* resource = fPurgeableQueue.peek(); 369 SkASSERT(resource->isPurgeable()); 370 resource->cacheAccess().release(); 371 } 372 373 this->validate(); 374} 375 376void GrResourceCache::processInvalidContentKeys( 377 const SkTArray<GrContentKeyInvalidatedMessage>& msgs) { 378 for (int i = 0; i < msgs.count(); ++i) { 379 GrGpuResource* resource = this->findAndRefContentResource(msgs[i].key()); 380 if (resource) { 381 resource->resourcePriv().removeContentKey(); 382 resource->unref(); // will call notifyPurgeable, if it is indeed now purgeable. 383 } 384 } 385} 386 387#ifdef SK_DEBUG 388void GrResourceCache::validate() const { 389 // Reduce the frequency of validations for large resource counts. 390 static SkRandom gRandom; 391 int mask = (SkNextPow2(fCount + 1) >> 5) - 1; 392 if (~mask && (gRandom.nextU() & mask)) { 393 return; 394 } 395 396 size_t bytes = 0; 397 int count = 0; 398 int budgetedCount = 0; 399 size_t budgetedBytes = 0; 400 int locked = 0; 401 int scratch = 0; 402 int couldBeScratch = 0; 403 int content = 0; 404 405 ResourceList::Iter iter; 406 GrGpuResource* resource = iter.init(fResources, ResourceList::Iter::kHead_IterStart); 407 for ( ; resource; resource = iter.next()) { 408 bytes += resource->gpuMemorySize(); 409 ++count; 410 411 if (!resource->isPurgeable()) { 412 ++locked; 413 } 414 415 if (resource->cacheAccess().isScratch()) { 416 SkASSERT(!resource->getContentKey().isValid()); 417 ++scratch; 418 SkASSERT(fScratchMap.countForKey(resource->resourcePriv().getScratchKey())); 419 SkASSERT(!resource->cacheAccess().isWrapped()); 420 } else if (resource->resourcePriv().getScratchKey().isValid()) { 421 SkASSERT(!resource->resourcePriv().isBudgeted() || 422 resource->getContentKey().isValid()); 423 ++couldBeScratch; 424 SkASSERT(fScratchMap.countForKey(resource->resourcePriv().getScratchKey())); 425 SkASSERT(!resource->cacheAccess().isWrapped()); 426 } 427 const GrContentKey& contentKey = resource->getContentKey(); 428 if (contentKey.isValid()) { 429 ++content; 430 SkASSERT(fContentHash.find(contentKey) == resource); 431 SkASSERT(!resource->cacheAccess().isWrapped()); 432 SkASSERT(resource->resourcePriv().isBudgeted()); 433 } 434 435 if (resource->resourcePriv().isBudgeted()) { 436 ++budgetedCount; 437 budgetedBytes += resource->gpuMemorySize(); 438 } 439 440 if (!resource->isPurgeable()) { 441 SkASSERT(-1 == *resource->cacheAccess().accessCacheIndex()); 442 } 443 } 444 445 for (int i = 0; i < fPurgeableQueue.count(); ++i) { 446 SkASSERT(fPurgeableQueue.at(i)->isPurgeable()); 447 } 448 449 SkASSERT(fCount - locked == fPurgeableQueue.count()); 450 SkASSERT(fBudgetedCount <= fCount); 451 SkASSERT(fBudgetedBytes <= fBudgetedBytes); 452 SkASSERT(bytes == fBytes); 453 SkASSERT(count == fCount); 454 SkASSERT(budgetedBytes == fBudgetedBytes); 455 SkASSERT(budgetedCount == fBudgetedCount); 456#if GR_CACHE_STATS 457 SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount); 458 SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes); 459 SkASSERT(bytes <= fHighWaterBytes); 460 SkASSERT(count <= fHighWaterCount); 461 SkASSERT(budgetedBytes <= fBudgetedHighWaterBytes); 462 SkASSERT(budgetedCount <= fBudgetedHighWaterCount); 463#endif 464 SkASSERT(content == fContentHash.count()); 465 SkASSERT(scratch + couldBeScratch == fScratchMap.count()); 466 467 // This assertion is not currently valid because we can be in recursive notifyIsPurgeable() 468 // calls. This will be fixed when subresource registration is explicit. 469 // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount; 470 // SkASSERT(!overBudget || locked == count || fPurging); 471} 472#endif 473