1 /*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8
9 #include "GrResourceCache.h"
10
11 #include "GrCaps.h"
12 #include "GrGpuResourceCacheAccess.h"
13 #include "GrTracing.h"
14 #include "SkGr.h"
15 #include "SkMessageBus.h"
16 #include "SkOpts.h"
17 #include "SkTSort.h"
18
19 DECLARE_SKMESSAGEBUS_MESSAGE(GrUniqueKeyInvalidatedMessage);
20
21 //////////////////////////////////////////////////////////////////////////////
22
GenerateResourceType()23 GrScratchKey::ResourceType GrScratchKey::GenerateResourceType() {
24 static int32_t gType = INHERITED::kInvalidDomain + 1;
25
26 int32_t type = sk_atomic_inc(&gType);
27 if (type > SK_MaxU16) {
28 SkFAIL("Too many Resource Types");
29 }
30
31 return static_cast<ResourceType>(type);
32 }
33
GenerateDomain()34 GrUniqueKey::Domain GrUniqueKey::GenerateDomain() {
35 static int32_t gDomain = INHERITED::kInvalidDomain + 1;
36
37 int32_t domain = sk_atomic_inc(&gDomain);
38 if (domain > SK_MaxU16) {
39 SkFAIL("Too many GrUniqueKey Domains");
40 }
41
42 return static_cast<Domain>(domain);
43 }
44
GrResourceKeyHash(const uint32_t * data,size_t size)45 uint32_t GrResourceKeyHash(const uint32_t* data, size_t size) {
46 return SkOpts::hash(data, size);
47 }
48
49 //////////////////////////////////////////////////////////////////////////////
50
51 class GrResourceCache::AutoValidate : ::SkNoncopyable {
52 public:
AutoValidate(GrResourceCache * cache)53 AutoValidate(GrResourceCache* cache) : fCache(cache) { cache->validate(); }
~AutoValidate()54 ~AutoValidate() { fCache->validate(); }
55 private:
56 GrResourceCache* fCache;
57 };
58
59 //////////////////////////////////////////////////////////////////////////////
60
61
GrResourceCache(const GrCaps * caps)62 GrResourceCache::GrResourceCache(const GrCaps* caps)
63 : fTimestamp(0)
64 , fMaxCount(kDefaultMaxCount)
65 , fMaxBytes(kDefaultMaxSize)
66 , fMaxUnusedFlushes(kDefaultMaxUnusedFlushes)
67 #if GR_CACHE_STATS
68 , fHighWaterCount(0)
69 , fHighWaterBytes(0)
70 , fBudgetedHighWaterCount(0)
71 , fBudgetedHighWaterBytes(0)
72 #endif
73 , fBytes(0)
74 , fBudgetedCount(0)
75 , fBudgetedBytes(0)
76 , fRequestFlush(false)
77 , fExternalFlushCnt(0)
78 , fPreferVRAMUseOverFlushes(caps->preferVRAMUseOverFlushes()) {
79 SkDEBUGCODE(fCount = 0;)
80 SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr;)
81 }
82
~GrResourceCache()83 GrResourceCache::~GrResourceCache() {
84 this->releaseAll();
85 }
86
setLimits(int count,size_t bytes,int maxUnusedFlushes)87 void GrResourceCache::setLimits(int count, size_t bytes, int maxUnusedFlushes) {
88 fMaxCount = count;
89 fMaxBytes = bytes;
90 fMaxUnusedFlushes = maxUnusedFlushes;
91 this->purgeAsNeeded();
92 }
93
insertResource(GrGpuResource * resource)94 void GrResourceCache::insertResource(GrGpuResource* resource) {
95 SkASSERT(resource);
96 SkASSERT(!this->isInCache(resource));
97 SkASSERT(!resource->wasDestroyed());
98 SkASSERT(!resource->isPurgeable());
99
100 // We must set the timestamp before adding to the array in case the timestamp wraps and we wind
101 // up iterating over all the resources that already have timestamps.
102 resource->cacheAccess().setTimestamp(this->getNextTimestamp());
103
104 this->addToNonpurgeableArray(resource);
105
106 size_t size = resource->gpuMemorySize();
107 SkDEBUGCODE(++fCount;)
108 fBytes += size;
109 #if GR_CACHE_STATS
110 fHighWaterCount = SkTMax(this->getResourceCount(), fHighWaterCount);
111 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
112 #endif
113 if (SkBudgeted::kYes == resource->resourcePriv().isBudgeted()) {
114 ++fBudgetedCount;
115 fBudgetedBytes += size;
116 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
117 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
118 #if GR_CACHE_STATS
119 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
120 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
121 #endif
122 }
123 if (resource->resourcePriv().getScratchKey().isValid() &&
124 !resource->getUniqueKey().isValid()) {
125 SkASSERT(!resource->resourcePriv().refsWrappedObjects());
126 fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
127 }
128
129 this->purgeAsNeeded();
130 }
131
removeResource(GrGpuResource * resource)132 void GrResourceCache::removeResource(GrGpuResource* resource) {
133 this->validate();
134 SkASSERT(this->isInCache(resource));
135
136 if (resource->isPurgeable()) {
137 fPurgeableQueue.remove(resource);
138 } else {
139 this->removeFromNonpurgeableArray(resource);
140 }
141
142 size_t size = resource->gpuMemorySize();
143 SkDEBUGCODE(--fCount;)
144 fBytes -= size;
145 if (SkBudgeted::kYes == resource->resourcePriv().isBudgeted()) {
146 --fBudgetedCount;
147 fBudgetedBytes -= size;
148 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
149 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
150 }
151
152 if (resource->resourcePriv().getScratchKey().isValid() &&
153 !resource->getUniqueKey().isValid()) {
154 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
155 }
156 if (resource->getUniqueKey().isValid()) {
157 fUniqueHash.remove(resource->getUniqueKey());
158 }
159 this->validate();
160 }
161
abandonAll()162 void GrResourceCache::abandonAll() {
163 AutoValidate av(this);
164
165 while (fNonpurgeableResources.count()) {
166 GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
167 SkASSERT(!back->wasDestroyed());
168 back->cacheAccess().abandon();
169 }
170
171 while (fPurgeableQueue.count()) {
172 GrGpuResource* top = fPurgeableQueue.peek();
173 SkASSERT(!top->wasDestroyed());
174 top->cacheAccess().abandon();
175 }
176
177 SkASSERT(!fScratchMap.count());
178 SkASSERT(!fUniqueHash.count());
179 SkASSERT(!fCount);
180 SkASSERT(!this->getResourceCount());
181 SkASSERT(!fBytes);
182 SkASSERT(!fBudgetedCount);
183 SkASSERT(!fBudgetedBytes);
184 }
185
releaseAll()186 void GrResourceCache::releaseAll() {
187 AutoValidate av(this);
188
189 while(fNonpurgeableResources.count()) {
190 GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
191 SkASSERT(!back->wasDestroyed());
192 back->cacheAccess().release();
193 }
194
195 while (fPurgeableQueue.count()) {
196 GrGpuResource* top = fPurgeableQueue.peek();
197 SkASSERT(!top->wasDestroyed());
198 top->cacheAccess().release();
199 }
200
201 SkASSERT(!fScratchMap.count());
202 SkASSERT(!fUniqueHash.count());
203 SkASSERT(!fCount);
204 SkASSERT(!this->getResourceCount());
205 SkASSERT(!fBytes);
206 SkASSERT(!fBudgetedCount);
207 SkASSERT(!fBudgetedBytes);
208 }
209
210 class GrResourceCache::AvailableForScratchUse {
211 public:
AvailableForScratchUse(bool rejectPendingIO)212 AvailableForScratchUse(bool rejectPendingIO) : fRejectPendingIO(rejectPendingIO) { }
213
operator ()(const GrGpuResource * resource) const214 bool operator()(const GrGpuResource* resource) const {
215 SkASSERT(!resource->getUniqueKey().isValid() &&
216 resource->resourcePriv().getScratchKey().isValid());
217 if (resource->internalHasRef() || !resource->cacheAccess().isScratch()) {
218 return false;
219 }
220 return !fRejectPendingIO || !resource->internalHasPendingIO();
221 }
222
223 private:
224 bool fRejectPendingIO;
225 };
226
findAndRefScratchResource(const GrScratchKey & scratchKey,size_t resourceSize,uint32_t flags)227 GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& scratchKey,
228 size_t resourceSize,
229 uint32_t flags) {
230 SkASSERT(scratchKey.isValid());
231
232 GrGpuResource* resource;
233 if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFlag)) {
234 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true));
235 if (resource) {
236 this->refAndMakeResourceMRU(resource);
237 this->validate();
238 return resource;
239 } else if (flags & kRequireNoPendingIO_ScratchFlag) {
240 return nullptr;
241 }
242 // We would prefer to consume more available VRAM rather than flushing
243 // immediately, but on ANGLE this can lead to starving of the GPU.
244 if (fPreferVRAMUseOverFlushes && this->wouldFit(resourceSize)) {
245 // kPrefer is specified, we didn't find a resource without pending io,
246 // but there is still space in our budget for the resource so force
247 // the caller to allocate a new resource.
248 return nullptr;
249 }
250 }
251 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(false));
252 if (resource) {
253 this->refAndMakeResourceMRU(resource);
254 this->validate();
255 }
256 return resource;
257 }
258
willRemoveScratchKey(const GrGpuResource * resource)259 void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) {
260 SkASSERT(resource->resourcePriv().getScratchKey().isValid());
261 if (!resource->getUniqueKey().isValid()) {
262 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
263 }
264 }
265
removeUniqueKey(GrGpuResource * resource)266 void GrResourceCache::removeUniqueKey(GrGpuResource* resource) {
267 // Someone has a ref to this resource in order to have removed the key. When the ref count
268 // reaches zero we will get a ref cnt notification and figure out what to do with it.
269 if (resource->getUniqueKey().isValid()) {
270 SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey()));
271 fUniqueHash.remove(resource->getUniqueKey());
272 }
273 resource->cacheAccess().removeUniqueKey();
274
275 if (resource->resourcePriv().getScratchKey().isValid()) {
276 fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
277 }
278
279 this->validate();
280 }
281
changeUniqueKey(GrGpuResource * resource,const GrUniqueKey & newKey)282 void GrResourceCache::changeUniqueKey(GrGpuResource* resource, const GrUniqueKey& newKey) {
283 SkASSERT(resource);
284 SkASSERT(this->isInCache(resource));
285
286 // If another resource has the new key, remove its key then install the key on this resource.
287 if (newKey.isValid()) {
288 // Remove the entry for this resource if it already has a unique key.
289 if (resource->getUniqueKey().isValid()) {
290 SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey()));
291 fUniqueHash.remove(resource->getUniqueKey());
292 SkASSERT(nullptr == fUniqueHash.find(resource->getUniqueKey()));
293 } else {
294 // 'resource' didn't have a valid unique key before so it is switching sides. Remove it
295 // from the ScratchMap
296 if (resource->resourcePriv().getScratchKey().isValid()) {
297 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
298 }
299 }
300
301 if (GrGpuResource* old = fUniqueHash.find(newKey)) {
302 // If the old resource using the key is purgeable and is unreachable, then remove it.
303 if (!old->resourcePriv().getScratchKey().isValid() && old->isPurgeable()) {
304 // release may call validate() which will assert that resource is in fUniqueHash
305 // if it has a valid key. So in debug reset the key here before we assign it.
306 SkDEBUGCODE(resource->cacheAccess().removeUniqueKey();)
307 old->cacheAccess().release();
308 } else {
309 this->removeUniqueKey(old);
310 }
311 }
312 SkASSERT(nullptr == fUniqueHash.find(newKey));
313 resource->cacheAccess().setUniqueKey(newKey);
314 fUniqueHash.add(resource);
315 } else {
316 this->removeUniqueKey(resource);
317 }
318
319 this->validate();
320 }
321
refAndMakeResourceMRU(GrGpuResource * resource)322 void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) {
323 SkASSERT(resource);
324 SkASSERT(this->isInCache(resource));
325
326 if (resource->isPurgeable()) {
327 // It's about to become unpurgeable.
328 fPurgeableQueue.remove(resource);
329 this->addToNonpurgeableArray(resource);
330 }
331 resource->ref();
332
333 resource->cacheAccess().setTimestamp(this->getNextTimestamp());
334 this->validate();
335 }
336
notifyCntReachedZero(GrGpuResource * resource,uint32_t flags)337 void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t flags) {
338 SkASSERT(resource);
339 SkASSERT(!resource->wasDestroyed());
340 SkASSERT(flags);
341 SkASSERT(this->isInCache(resource));
342 // This resource should always be in the nonpurgeable array when this function is called. It
343 // will be moved to the queue if it is newly purgeable.
344 SkASSERT(fNonpurgeableResources[*resource->cacheAccess().accessCacheIndex()] == resource);
345
346 if (SkToBool(ResourceAccess::kRefCntReachedZero_RefNotificationFlag & flags)) {
347 #ifdef SK_DEBUG
348 // When the timestamp overflows validate() is called. validate() checks that resources in
349 // the nonpurgeable array are indeed not purgeable. However, the movement from the array to
350 // the purgeable queue happens just below in this function. So we mark it as an exception.
351 if (resource->isPurgeable()) {
352 fNewlyPurgeableResourceForValidation = resource;
353 }
354 #endif
355 resource->cacheAccess().setTimestamp(this->getNextTimestamp());
356 SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr);
357 }
358
359 if (!SkToBool(ResourceAccess::kAllCntsReachedZero_RefNotificationFlag & flags)) {
360 SkASSERT(!resource->isPurgeable());
361 return;
362 }
363
364 SkASSERT(resource->isPurgeable());
365 this->removeFromNonpurgeableArray(resource);
366 fPurgeableQueue.insert(resource);
367 resource->cacheAccess().setFlushCntWhenResourceBecamePurgeable(fExternalFlushCnt);
368
369 if (SkBudgeted::kNo == resource->resourcePriv().isBudgeted()) {
370 // Check whether this resource could still be used as a scratch resource.
371 if (!resource->resourcePriv().refsWrappedObjects() &&
372 resource->resourcePriv().getScratchKey().isValid()) {
373 // We won't purge an existing resource to make room for this one.
374 if (fBudgetedCount < fMaxCount &&
375 fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes) {
376 resource->resourcePriv().makeBudgeted();
377 return;
378 }
379 }
380 } else {
381 // Purge the resource immediately if we're over budget
382 // Also purge if the resource has neither a valid scratch key nor a unique key.
383 bool noKey = !resource->resourcePriv().getScratchKey().isValid() &&
384 !resource->getUniqueKey().isValid();
385 if (!this->overBudget() && !noKey) {
386 return;
387 }
388 }
389
390 SkDEBUGCODE(int beforeCount = this->getResourceCount();)
391 resource->cacheAccess().release();
392 // We should at least free this resource, perhaps dependent resources as well.
393 SkASSERT(this->getResourceCount() < beforeCount);
394 this->validate();
395 }
396
didChangeGpuMemorySize(const GrGpuResource * resource,size_t oldSize)397 void GrResourceCache::didChangeGpuMemorySize(const GrGpuResource* resource, size_t oldSize) {
398 // SkASSERT(!fPurging); GrPathRange increases size during flush. :(
399 SkASSERT(resource);
400 SkASSERT(this->isInCache(resource));
401
402 ptrdiff_t delta = resource->gpuMemorySize() - oldSize;
403
404 fBytes += delta;
405 #if GR_CACHE_STATS
406 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
407 #endif
408 if (SkBudgeted::kYes == resource->resourcePriv().isBudgeted()) {
409 fBudgetedBytes += delta;
410 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
411 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
412 #if GR_CACHE_STATS
413 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
414 #endif
415 }
416
417 this->purgeAsNeeded();
418 this->validate();
419 }
420
didChangeBudgetStatus(GrGpuResource * resource)421 void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) {
422 SkASSERT(resource);
423 SkASSERT(this->isInCache(resource));
424
425 size_t size = resource->gpuMemorySize();
426
427 if (SkBudgeted::kYes == resource->resourcePriv().isBudgeted()) {
428 ++fBudgetedCount;
429 fBudgetedBytes += size;
430 #if GR_CACHE_STATS
431 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
432 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
433 #endif
434 this->purgeAsNeeded();
435 } else {
436 --fBudgetedCount;
437 fBudgetedBytes -= size;
438 }
439 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
440 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
441
442 this->validate();
443 }
444
purgeAsNeeded()445 void GrResourceCache::purgeAsNeeded() {
446 SkTArray<GrUniqueKeyInvalidatedMessage> invalidKeyMsgs;
447 fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
448 if (invalidKeyMsgs.count()) {
449 this->processInvalidUniqueKeys(invalidKeyMsgs);
450 }
451
452 if (fMaxUnusedFlushes > 0) {
453 // We want to know how many complete flushes have occurred without the resource being used.
454 // If the resource was tagged when fExternalFlushCnt was N then this means it became
455 // purgeable during activity that became the N+1th flush. So when the flush count is N+2
456 // it has sat in the purgeable queue for one entire flush.
457 uint32_t oldestAllowedFlushCnt = fExternalFlushCnt - fMaxUnusedFlushes - 1;
458 // check for underflow
459 if (oldestAllowedFlushCnt < fExternalFlushCnt) {
460 while (fPurgeableQueue.count()) {
461 uint32_t flushWhenResourceBecamePurgeable =
462 fPurgeableQueue.peek()->cacheAccess().flushCntWhenResourceBecamePurgeable();
463 if (oldestAllowedFlushCnt < flushWhenResourceBecamePurgeable) {
464 // Resources were given both LRU timestamps and tagged with a flush cnt when
465 // they first became purgeable. The LRU timestamp won't change again until the
466 // resource is made non-purgeable again. So, at this point all the remaining
467 // resources in the timestamp-sorted queue will have a flush count >= to this
468 // one.
469 break;
470 }
471 GrGpuResource* resource = fPurgeableQueue.peek();
472 SkASSERT(resource->isPurgeable());
473 resource->cacheAccess().release();
474 }
475 }
476 }
477
478 bool stillOverbudget = this->overBudget();
479 while (stillOverbudget && fPurgeableQueue.count()) {
480 GrGpuResource* resource = fPurgeableQueue.peek();
481 SkASSERT(resource->isPurgeable());
482 resource->cacheAccess().release();
483 stillOverbudget = this->overBudget();
484 }
485
486 this->validate();
487
488 if (stillOverbudget) {
489 // Set this so that GrDrawingManager will issue a flush to free up resources with pending
490 // IO that we were unable to purge in this pass.
491 fRequestFlush = true;
492 }
493 }
494
purgeAllUnlocked()495 void GrResourceCache::purgeAllUnlocked() {
496 // We could disable maintaining the heap property here, but it would add a lot of complexity.
497 // Moreover, this is rarely called.
498 while (fPurgeableQueue.count()) {
499 GrGpuResource* resource = fPurgeableQueue.peek();
500 SkASSERT(resource->isPurgeable());
501 resource->cacheAccess().release();
502 }
503
504 this->validate();
505 }
506
processInvalidUniqueKeys(const SkTArray<GrUniqueKeyInvalidatedMessage> & msgs)507 void GrResourceCache::processInvalidUniqueKeys(
508 const SkTArray<GrUniqueKeyInvalidatedMessage>& msgs) {
509 for (int i = 0; i < msgs.count(); ++i) {
510 GrGpuResource* resource = this->findAndRefUniqueResource(msgs[i].key());
511 if (resource) {
512 resource->resourcePriv().removeUniqueKey();
513 resource->unref(); // If this resource is now purgeable, the cache will be notified.
514 }
515 }
516 }
517
addToNonpurgeableArray(GrGpuResource * resource)518 void GrResourceCache::addToNonpurgeableArray(GrGpuResource* resource) {
519 int index = fNonpurgeableResources.count();
520 *fNonpurgeableResources.append() = resource;
521 *resource->cacheAccess().accessCacheIndex() = index;
522 }
523
removeFromNonpurgeableArray(GrGpuResource * resource)524 void GrResourceCache::removeFromNonpurgeableArray(GrGpuResource* resource) {
525 int* index = resource->cacheAccess().accessCacheIndex();
526 // Fill the whole we will create in the array with the tail object, adjust its index, and
527 // then pop the array
528 GrGpuResource* tail = *(fNonpurgeableResources.end() - 1);
529 SkASSERT(fNonpurgeableResources[*index] == resource);
530 fNonpurgeableResources[*index] = tail;
531 *tail->cacheAccess().accessCacheIndex() = *index;
532 fNonpurgeableResources.pop();
533 SkDEBUGCODE(*index = -1);
534 }
535
getNextTimestamp()536 uint32_t GrResourceCache::getNextTimestamp() {
537 // If we wrap then all the existing resources will appear older than any resources that get
538 // a timestamp after the wrap.
539 if (0 == fTimestamp) {
540 int count = this->getResourceCount();
541 if (count) {
542 // Reset all the timestamps. We sort the resources by timestamp and then assign
543 // sequential timestamps beginning with 0. This is O(n*lg(n)) but it should be extremely
544 // rare.
545 SkTDArray<GrGpuResource*> sortedPurgeableResources;
546 sortedPurgeableResources.setReserve(fPurgeableQueue.count());
547
548 while (fPurgeableQueue.count()) {
549 *sortedPurgeableResources.append() = fPurgeableQueue.peek();
550 fPurgeableQueue.pop();
551 }
552
553 SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end() - 1,
554 CompareTimestamp);
555
556 // Pick resources out of the purgeable and non-purgeable arrays based on lowest
557 // timestamp and assign new timestamps.
558 int currP = 0;
559 int currNP = 0;
560 while (currP < sortedPurgeableResources.count() &&
561 currNP < fNonpurgeableResources.count()) {
562 uint32_t tsP = sortedPurgeableResources[currP]->cacheAccess().timestamp();
563 uint32_t tsNP = fNonpurgeableResources[currNP]->cacheAccess().timestamp();
564 SkASSERT(tsP != tsNP);
565 if (tsP < tsNP) {
566 sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
567 } else {
568 // Correct the index in the nonpurgeable array stored on the resource post-sort.
569 *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
570 fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
571 }
572 }
573
574 // The above loop ended when we hit the end of one array. Finish the other one.
575 while (currP < sortedPurgeableResources.count()) {
576 sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
577 }
578 while (currNP < fNonpurgeableResources.count()) {
579 *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
580 fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
581 }
582
583 // Rebuild the queue.
584 for (int i = 0; i < sortedPurgeableResources.count(); ++i) {
585 fPurgeableQueue.insert(sortedPurgeableResources[i]);
586 }
587
588 this->validate();
589 SkASSERT(count == this->getResourceCount());
590
591 // count should be the next timestamp we return.
592 SkASSERT(fTimestamp == SkToU32(count));
593 }
594 }
595 return fTimestamp++;
596 }
597
notifyFlushOccurred(FlushType type)598 void GrResourceCache::notifyFlushOccurred(FlushType type) {
599 switch (type) {
600 case FlushType::kImmediateMode:
601 break;
602 case FlushType::kCacheRequested:
603 SkASSERT(fRequestFlush);
604 fRequestFlush = false;
605 break;
606 case FlushType::kExternal:
607 ++fExternalFlushCnt;
608 if (0 == fExternalFlushCnt) {
609 // When this wraps just reset all the purgeable resources' last used flush state.
610 for (int i = 0; i < fPurgeableQueue.count(); ++i) {
611 fPurgeableQueue.at(i)->cacheAccess().setFlushCntWhenResourceBecamePurgeable(0);
612 }
613 }
614 break;
615 }
616 this->purgeAsNeeded();
617 }
618
dumpMemoryStatistics(SkTraceMemoryDump * traceMemoryDump) const619 void GrResourceCache::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
620 for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
621 fNonpurgeableResources[i]->dumpMemoryStatistics(traceMemoryDump);
622 }
623 for (int i = 0; i < fPurgeableQueue.count(); ++i) {
624 fPurgeableQueue.at(i)->dumpMemoryStatistics(traceMemoryDump);
625 }
626 }
627
628 #ifdef SK_DEBUG
validate() const629 void GrResourceCache::validate() const {
630 // Reduce the frequency of validations for large resource counts.
631 static SkRandom gRandom;
632 int mask = (SkNextPow2(fCount + 1) >> 5) - 1;
633 if (~mask && (gRandom.nextU() & mask)) {
634 return;
635 }
636
637 struct Stats {
638 size_t fBytes;
639 int fBudgetedCount;
640 size_t fBudgetedBytes;
641 int fLocked;
642 int fScratch;
643 int fCouldBeScratch;
644 int fContent;
645 const ScratchMap* fScratchMap;
646 const UniqueHash* fUniqueHash;
647
648 Stats(const GrResourceCache* cache) {
649 memset(this, 0, sizeof(*this));
650 fScratchMap = &cache->fScratchMap;
651 fUniqueHash = &cache->fUniqueHash;
652 }
653
654 void update(GrGpuResource* resource) {
655 fBytes += resource->gpuMemorySize();
656
657 if (!resource->isPurgeable()) {
658 ++fLocked;
659 }
660
661 const GrScratchKey& scratchKey = resource->resourcePriv().getScratchKey();
662 const GrUniqueKey& uniqueKey = resource->getUniqueKey();
663
664 if (resource->cacheAccess().isScratch()) {
665 SkASSERT(!uniqueKey.isValid());
666 ++fScratch;
667 SkASSERT(fScratchMap->countForKey(scratchKey));
668 SkASSERT(!resource->resourcePriv().refsWrappedObjects());
669 } else if (scratchKey.isValid()) {
670 SkASSERT(SkBudgeted::kNo == resource->resourcePriv().isBudgeted() ||
671 uniqueKey.isValid());
672 if (!uniqueKey.isValid()) {
673 ++fCouldBeScratch;
674 SkASSERT(fScratchMap->countForKey(scratchKey));
675 }
676 SkASSERT(!resource->resourcePriv().refsWrappedObjects());
677 }
678 if (uniqueKey.isValid()) {
679 ++fContent;
680 SkASSERT(fUniqueHash->find(uniqueKey) == resource);
681 SkASSERT(!resource->resourcePriv().refsWrappedObjects());
682 SkASSERT(SkBudgeted::kYes == resource->resourcePriv().isBudgeted());
683
684 if (scratchKey.isValid()) {
685 SkASSERT(!fScratchMap->has(resource, scratchKey));
686 }
687 }
688
689 if (SkBudgeted::kYes == resource->resourcePriv().isBudgeted()) {
690 ++fBudgetedCount;
691 fBudgetedBytes += resource->gpuMemorySize();
692 }
693 }
694 };
695
696 {
697 ScratchMap::ConstIter iter(&fScratchMap);
698
699 int count = 0;
700 for ( ; !iter.done(); ++iter) {
701 const GrGpuResource* resource = *iter;
702 SkASSERT(resource->resourcePriv().getScratchKey().isValid());
703 SkASSERT(!resource->getUniqueKey().isValid());
704 count++;
705 }
706 SkASSERT(count == fScratchMap.count()); // ensure the iterator is working correctly
707 }
708
709 Stats stats(this);
710
711 for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
712 SkASSERT(!fNonpurgeableResources[i]->isPurgeable() ||
713 fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
714 SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
715 SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
716 stats.update(fNonpurgeableResources[i]);
717 }
718 for (int i = 0; i < fPurgeableQueue.count(); ++i) {
719 SkASSERT(fPurgeableQueue.at(i)->isPurgeable());
720 SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i);
721 SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed());
722 stats.update(fPurgeableQueue.at(i));
723 }
724
725 SkASSERT(fCount == this->getResourceCount());
726 SkASSERT(fBudgetedCount <= fCount);
727 SkASSERT(fBudgetedBytes <= fBytes);
728 SkASSERT(stats.fBytes == fBytes);
729 SkASSERT(stats.fBudgetedBytes == fBudgetedBytes);
730 SkASSERT(stats.fBudgetedCount == fBudgetedCount);
731 #if GR_CACHE_STATS
732 SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount);
733 SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes);
734 SkASSERT(fBytes <= fHighWaterBytes);
735 SkASSERT(fCount <= fHighWaterCount);
736 SkASSERT(fBudgetedBytes <= fBudgetedHighWaterBytes);
737 SkASSERT(fBudgetedCount <= fBudgetedHighWaterCount);
738 #endif
739 SkASSERT(stats.fContent == fUniqueHash.count());
740 SkASSERT(stats.fScratch + stats.fCouldBeScratch == fScratchMap.count());
741
742 // This assertion is not currently valid because we can be in recursive notifyCntReachedZero()
743 // calls. This will be fixed when subresource registration is explicit.
744 // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount;
745 // SkASSERT(!overBudget || locked == count || fPurging);
746 }
747
isInCache(const GrGpuResource * resource) const748 bool GrResourceCache::isInCache(const GrGpuResource* resource) const {
749 int index = *resource->cacheAccess().accessCacheIndex();
750 if (index < 0) {
751 return false;
752 }
753 if (index < fPurgeableQueue.count() && fPurgeableQueue.at(index) == resource) {
754 return true;
755 }
756 if (index < fNonpurgeableResources.count() && fNonpurgeableResources[index] == resource) {
757 return true;
758 }
759 SkDEBUGFAIL("Resource index should be -1 or the resource should be in the cache.");
760 return false;
761 }
762
763 #endif
764