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