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