• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "src/gpu/ganesh/GrResourceCache.h"
9 
10 #include "include/core/SkString.h"
11 #include "include/gpu/ganesh/GrDirectContext.h"
12 #include "include/gpu/ganesh/GrTypes.h"
13 #include "include/private/base/SingleOwner.h"
14 #include "include/private/base/SkNoncopyable.h"
15 #include "include/private/base/SkTo.h"
16 #include "src/base/SkMathPriv.h"
17 #include "src/base/SkRandom.h"
18 #include "src/base/SkTSort.h"
19 #include "src/core/SkMessageBus.h"
20 #include "src/core/SkTraceEvent.h"
21 #include "src/gpu/ganesh/GrDirectContextPriv.h"
22 #include "src/gpu/ganesh/GrGpuResourceCacheAccess.h"
23 #include "src/gpu/ganesh/GrProxyProvider.h"
24 #include "src/gpu/ganesh/GrThreadSafeCache.h"
25 
26 #include <algorithm>
27 #include <chrono>
28 #include <cstring>
29 #include <vector>
30 
31 using namespace skia_private;
32 
33 DECLARE_SKMESSAGEBUS_MESSAGE(skgpu::UniqueKeyInvalidatedMessage, uint32_t, true)
34 
35 DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceCache::UnrefResourceMessage,
36                              GrDirectContext::DirectContextID,
37                              /*AllowCopyableMessage=*/false)
38 
39 #define ASSERT_SINGLE_OWNER SKGPU_ASSERT_SINGLE_OWNER(fSingleOwner)
40 
41 //////////////////////////////////////////////////////////////////////////////
42 
43 class GrResourceCache::AutoValidate : ::SkNoncopyable {
44 public:
AutoValidate(GrResourceCache * cache)45     AutoValidate(GrResourceCache* cache) : fCache(cache) { cache->validate(); }
~AutoValidate()46     ~AutoValidate() { fCache->validate(); }
47 private:
48     GrResourceCache* fCache;
49 };
50 
51 //////////////////////////////////////////////////////////////////////////////
52 
GrResourceCache(skgpu::SingleOwner * singleOwner,GrDirectContext::DirectContextID owningContextID,uint32_t familyID)53 GrResourceCache::GrResourceCache(skgpu::SingleOwner* singleOwner,
54                                  GrDirectContext::DirectContextID owningContextID,
55                                  uint32_t familyID)
56         : fInvalidUniqueKeyInbox(familyID)
57         , fUnrefResourceInbox(owningContextID)
58         , fOwningContextID(owningContextID)
59         , fContextUniqueID(familyID)
60         , fSingleOwner(singleOwner) {
61     SkASSERT(owningContextID.isValid());
62     SkASSERT(familyID != SK_InvalidUniqueID);
63 }
64 
~GrResourceCache()65 GrResourceCache::~GrResourceCache() {
66     this->releaseAll();
67 }
68 
setLimit(size_t bytes)69 void GrResourceCache::setLimit(size_t bytes) {
70     fMaxBytes = bytes;
71     this->purgeAsNeeded();
72 }
73 
insertResource(GrGpuResource * resource)74 void GrResourceCache::insertResource(GrGpuResource* resource) {
75     ASSERT_SINGLE_OWNER
76     SkASSERT(resource);
77     SkASSERT(!this->isInCache(resource));
78     SkASSERT(!resource->wasDestroyed());
79     SkASSERT(!resource->resourcePriv().isPurgeable());
80 
81     // We must set the timestamp before adding to the array in case the timestamp wraps and we wind
82     // up iterating over all the resources that already have timestamps.
83     resource->cacheAccess().setTimestamp(this->getNextTimestamp());
84 
85     this->addToNonpurgeableArray(resource);
86 
87     size_t size = resource->gpuMemorySize();
88     SkDEBUGCODE(++fCount;)
89     fBytes += size;
90 #if GR_CACHE_STATS
91     fHighWaterCount = std::max(this->getResourceCount(), fHighWaterCount);
92     fHighWaterBytes = std::max(fBytes, fHighWaterBytes);
93 #endif
94     if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) {
95         ++fBudgetedCount;
96         fBudgetedBytes += size;
97         TRACE_COUNTER2("skia.gpu.cache", "skia budget", "used",
98                        fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
99 #if GR_CACHE_STATS
100         fBudgetedHighWaterCount = std::max(fBudgetedCount, fBudgetedHighWaterCount);
101         fBudgetedHighWaterBytes = std::max(fBudgetedBytes, fBudgetedHighWaterBytes);
102 #endif
103     }
104     SkASSERT(!resource->cacheAccess().isUsableAsScratch());
105     this->purgeAsNeeded();
106 }
107 
removeResource(GrGpuResource * resource)108 void GrResourceCache::removeResource(GrGpuResource* resource) {
109     ASSERT_SINGLE_OWNER
110     this->validate();
111     SkASSERT(this->isInCache(resource));
112 
113     size_t size = resource->gpuMemorySize();
114     if (resource->resourcePriv().isPurgeable()) {
115         fPurgeableQueue.remove(resource);
116         fPurgeableBytes -= size;
117     } else {
118         this->removeFromNonpurgeableArray(resource);
119     }
120 
121     SkDEBUGCODE(--fCount;)
122     fBytes -= size;
123     if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) {
124         --fBudgetedCount;
125         fBudgetedBytes -= size;
126         TRACE_COUNTER2("skia.gpu.cache", "skia budget", "used",
127                        fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
128     }
129 
130     if (resource->cacheAccess().isUsableAsScratch()) {
131         fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
132     }
133     if (resource->getUniqueKey().isValid()) {
134         fUniqueHash.remove(resource->getUniqueKey());
135     }
136     this->validate();
137 }
138 
abandonAll()139 void GrResourceCache::abandonAll() {
140     AutoValidate av(this);
141 
142     while (!fNonpurgeableResources.empty()) {
143         GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
144         SkASSERT(!back->wasDestroyed());
145         back->cacheAccess().abandon();
146     }
147 
148     while (fPurgeableQueue.count()) {
149         GrGpuResource* top = fPurgeableQueue.peek();
150         SkASSERT(!top->wasDestroyed());
151         top->cacheAccess().abandon();
152     }
153 
154     fThreadSafeCache->dropAllRefs();
155 
156     SkASSERT(!fScratchMap.count());
157     SkASSERT(!fUniqueHash.count());
158     SkASSERT(!fCount);
159     SkASSERT(!this->getResourceCount());
160     SkASSERT(!fBytes);
161     SkASSERT(!fBudgetedCount);
162     SkASSERT(!fBudgetedBytes);
163     SkASSERT(!fPurgeableBytes);
164 }
165 
releaseAll()166 void GrResourceCache::releaseAll() {
167     AutoValidate av(this);
168 
169     fThreadSafeCache->dropAllRefs();
170 
171     this->processFreedGpuResources();
172 
173     SkASSERT(fProxyProvider); // better have called setProxyProvider
174     SkASSERT(fThreadSafeCache); // better have called setThreadSafeCache too
175 
176     // We must remove the uniqueKeys from the proxies here. While they possess a uniqueKey
177     // they also have a raw pointer back to this class (which is presumably going away)!
178     fProxyProvider->removeAllUniqueKeys();
179 
180     while (!fNonpurgeableResources.empty()) {
181         GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
182         SkASSERT(!back->wasDestroyed());
183         back->cacheAccess().release();
184     }
185 
186     while (fPurgeableQueue.count()) {
187         GrGpuResource* top = fPurgeableQueue.peek();
188         SkASSERT(!top->wasDestroyed());
189         top->cacheAccess().release();
190     }
191 
192     SkASSERT(!fScratchMap.count());
193     SkASSERT(!fUniqueHash.count());
194     SkASSERT(!fCount);
195     SkASSERT(!this->getResourceCount());
196     SkASSERT(!fBytes);
197     SkASSERT(!fBudgetedCount);
198     SkASSERT(!fBudgetedBytes);
199     SkASSERT(!fPurgeableBytes);
200 }
201 
refResource(GrGpuResource * resource)202 void GrResourceCache::refResource(GrGpuResource* resource) {
203     SkASSERT(resource);
204     SkASSERT(resource->getContext()->priv().getResourceCache() == this);
205     if (resource->cacheAccess().hasRef()) {
206         resource->ref();
207     } else {
208         this->refAndMakeResourceMRU(resource);
209     }
210     this->validate();
211 }
212 
findAndRefScratchResource(const skgpu::ScratchKey & scratchKey)213 GrGpuResource* GrResourceCache::findAndRefScratchResource(const skgpu::ScratchKey& scratchKey) {
214     SkASSERT(scratchKey.isValid());
215 
216     GrGpuResource* resource = fScratchMap.find(scratchKey);
217     if (resource) {
218         fScratchMap.remove(scratchKey, resource);
219         this->refAndMakeResourceMRU(resource);
220         this->validate();
221     }
222     return resource;
223 }
224 
willRemoveScratchKey(const GrGpuResource * resource)225 void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) {
226     ASSERT_SINGLE_OWNER
227     SkASSERT(resource->resourcePriv().getScratchKey().isValid());
228     if (resource->cacheAccess().isUsableAsScratch()) {
229         fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
230     }
231 }
232 
removeUniqueKey(GrGpuResource * resource)233 void GrResourceCache::removeUniqueKey(GrGpuResource* resource) {
234     ASSERT_SINGLE_OWNER
235     // Someone has a ref to this resource in order to have removed the key. When the ref count
236     // reaches zero we will get a ref cnt notification and figure out what to do with it.
237     if (resource->getUniqueKey().isValid()) {
238         SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey()));
239         fUniqueHash.remove(resource->getUniqueKey());
240     }
241     resource->cacheAccess().removeUniqueKey();
242     if (resource->cacheAccess().isUsableAsScratch()) {
243         fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
244     }
245 
246     // Removing a unique key from a kUnbudgetedCacheable resource would make the resource
247     // require purging. However, the resource must be ref'ed to get here and therefore can't
248     // be purgeable. We'll purge it when the refs reach zero.
249     SkASSERT(!resource->resourcePriv().isPurgeable());
250     this->validate();
251 }
252 
changeUniqueKey(GrGpuResource * resource,const skgpu::UniqueKey & newKey)253 void GrResourceCache::changeUniqueKey(GrGpuResource* resource, const skgpu::UniqueKey& newKey) {
254     ASSERT_SINGLE_OWNER
255     SkASSERT(resource);
256     SkASSERT(this->isInCache(resource));
257 
258     // If another resource has the new key, remove its key then install the key on this resource.
259     if (newKey.isValid()) {
260         if (GrGpuResource* old = fUniqueHash.find(newKey)) {
261             // If the old resource using the key is purgeable and is unreachable, then remove it.
262             if (!old->resourcePriv().getScratchKey().isValid() &&
263                 old->resourcePriv().isPurgeable()) {
264                 old->cacheAccess().release();
265             } else {
266                 // removeUniqueKey expects an external owner of the resource.
267                 this->removeUniqueKey(sk_ref_sp(old).get());
268             }
269         }
270         SkASSERT(nullptr == fUniqueHash.find(newKey));
271 
272         // Remove the entry for this resource if it already has a unique key.
273         if (resource->getUniqueKey().isValid()) {
274             SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey()));
275             fUniqueHash.remove(resource->getUniqueKey());
276             SkASSERT(nullptr == fUniqueHash.find(resource->getUniqueKey()));
277         } else {
278             // 'resource' didn't have a valid unique key before so it is switching sides. Remove it
279             // from the ScratchMap. The isUsableAsScratch call depends on us not adding the new
280             // unique key until after this check.
281             if (resource->cacheAccess().isUsableAsScratch()) {
282                 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
283             }
284         }
285 
286         resource->cacheAccess().setUniqueKey(newKey);
287         fUniqueHash.add(resource);
288     } else {
289         this->removeUniqueKey(resource);
290     }
291 
292     this->validate();
293 }
294 
refAndMakeResourceMRU(GrGpuResource * resource)295 void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) {
296     ASSERT_SINGLE_OWNER
297     SkASSERT(resource);
298     SkASSERT(this->isInCache(resource));
299 
300     if (resource->resourcePriv().isPurgeable()) {
301         // It's about to become unpurgeable.
302         fPurgeableBytes -= resource->gpuMemorySize();
303         fPurgeableQueue.remove(resource);
304         this->addToNonpurgeableArray(resource);
305     }
306     resource->cacheAccess().ref();
307 
308     resource->cacheAccess().setTimestamp(this->getNextTimestamp());
309     this->validate();
310 }
311 
notifyARefCntReachedZero(GrGpuResource * resource,GrGpuResource::LastRemovedRef removedRef)312 void GrResourceCache::notifyARefCntReachedZero(GrGpuResource* resource,
313                                                GrGpuResource::LastRemovedRef removedRef) {
314     ASSERT_SINGLE_OWNER
315     SkASSERT(resource);
316     SkASSERT(!resource->wasDestroyed());
317     SkASSERT(this->isInCache(resource));
318     // This resource should always be in the nonpurgeable array when this function is called. It
319     // will be moved to the queue if it is newly purgeable.
320     SkASSERT(fNonpurgeableResources[*resource->cacheAccess().accessCacheIndex()] == resource);
321 
322     if (removedRef == GrGpuResource::LastRemovedRef::kMainRef) {
323         if (resource->cacheAccess().isUsableAsScratch()) {
324             fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
325         }
326     }
327 
328     if (resource->cacheAccess().hasRefOrCommandBufferUsage()) {
329         this->validate();
330         return;
331     }
332 
333 #ifdef SK_DEBUG
334     // When the timestamp overflows validate() is called. validate() checks that resources in
335     // the nonpurgeable array are indeed not purgeable. However, the movement from the array to
336     // the purgeable queue happens just below in this function. So we mark it as an exception.
337     if (resource->resourcePriv().isPurgeable()) {
338         fNewlyPurgeableResourceForValidation = resource;
339     }
340 #endif
341     resource->cacheAccess().setTimestamp(this->getNextTimestamp());
342     SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr);
343 
344     if (!resource->resourcePriv().isPurgeable()) {
345         this->validate();
346         return;
347     }
348 
349     this->removeFromNonpurgeableArray(resource);
350     fPurgeableQueue.insert(resource);
351     resource->cacheAccess().setTimeWhenResourceBecomePurgeable();
352     fPurgeableBytes += resource->gpuMemorySize();
353 
354     bool hasUniqueKey = resource->getUniqueKey().isValid();
355 
356     GrBudgetedType budgetedType = resource->resourcePriv().budgetedType();
357 
358     if (budgetedType == GrBudgetedType::kBudgeted) {
359         // Purge the resource immediately if we're over budget
360         // Also purge if the resource has neither a valid scratch key nor a unique key.
361         bool hasKey = resource->resourcePriv().getScratchKey().isValid() || hasUniqueKey;
362         if (!this->overBudget() && hasKey) {
363             return;
364         }
365     } else {
366         // We keep unbudgeted resources with a unique key in the purgeable queue of the cache so
367         // they can be reused again by the image connected to the unique key.
368         if (hasUniqueKey && budgetedType == GrBudgetedType::kUnbudgetedCacheable) {
369             return;
370         }
371         // Check whether this resource could still be used as a scratch resource.
372         if (!resource->resourcePriv().refsWrappedObjects() &&
373             resource->resourcePriv().getScratchKey().isValid()) {
374             // We won't purge an existing resource to make room for this one.
375             if (this->wouldFit(resource->gpuMemorySize())) {
376                 resource->resourcePriv().makeBudgeted();
377                 return;
378             }
379         }
380     }
381 
382     SkDEBUGCODE(int beforeCount = this->getResourceCount();)
383     resource->cacheAccess().release();
384     // We should at least free this resource, perhaps dependent resources as well.
385     SkASSERT(this->getResourceCount() < beforeCount);
386     this->validate();
387 }
388 
didChangeBudgetStatus(GrGpuResource * resource)389 void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) {
390     ASSERT_SINGLE_OWNER
391     SkASSERT(resource);
392     SkASSERT(this->isInCache(resource));
393 
394     size_t size = resource->gpuMemorySize();
395     // Changing from BudgetedType::kUnbudgetedCacheable to another budgeted type could make
396     // resource become purgeable. However, we should never allow that transition. Wrapped
397     // resources are the only resources that can be in that state and they aren't allowed to
398     // transition from one budgeted state to another.
399     SkDEBUGCODE(bool wasPurgeable = resource->resourcePriv().isPurgeable());
400     if (resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) {
401         ++fBudgetedCount;
402         fBudgetedBytes += size;
403 #if GR_CACHE_STATS
404         fBudgetedHighWaterBytes = std::max(fBudgetedBytes, fBudgetedHighWaterBytes);
405         fBudgetedHighWaterCount = std::max(fBudgetedCount, fBudgetedHighWaterCount);
406 #endif
407         if (resource->cacheAccess().isUsableAsScratch()) {
408             fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
409         }
410         this->purgeAsNeeded();
411     } else {
412         SkASSERT(resource->resourcePriv().budgetedType() != GrBudgetedType::kUnbudgetedCacheable);
413         --fBudgetedCount;
414         fBudgetedBytes -= size;
415         if (!resource->cacheAccess().hasRef() && !resource->getUniqueKey().isValid() &&
416             resource->resourcePriv().getScratchKey().isValid()) {
417             fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
418         }
419     }
420     SkASSERT(wasPurgeable == resource->resourcePriv().isPurgeable());
421     TRACE_COUNTER2("skia.gpu.cache", "skia budget", "used",
422                    fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
423 
424     this->validate();
425 }
426 
purgeAsNeeded()427 void GrResourceCache::purgeAsNeeded() {
428     TArray<skgpu::UniqueKeyInvalidatedMessage> invalidKeyMsgs;
429     fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
430     if (!invalidKeyMsgs.empty()) {
431         SkASSERT(fProxyProvider);
432 
433         for (int i = 0; i < invalidKeyMsgs.size(); ++i) {
434             if (invalidKeyMsgs[i].inThreadSafeCache()) {
435                 fThreadSafeCache->remove(invalidKeyMsgs[i].key());
436                 SkASSERT(!fThreadSafeCache->has(invalidKeyMsgs[i].key()));
437             } else {
438                 fProxyProvider->processInvalidUniqueKey(
439                                                     invalidKeyMsgs[i].key(), nullptr,
440                                                     GrProxyProvider::InvalidateGPUResource::kYes);
441                 SkASSERT(!this->findAndRefUniqueResource(invalidKeyMsgs[i].key()));
442             }
443         }
444     }
445 
446     this->processFreedGpuResources();
447 
448     bool stillOverbudget = this->overBudget();
449     while (stillOverbudget && fPurgeableQueue.count()) {
450         GrGpuResource* resource = fPurgeableQueue.peek();
451         SkASSERT(resource->resourcePriv().isPurgeable());
452         resource->cacheAccess().release();
453         stillOverbudget = this->overBudget();
454     }
455 
456     if (stillOverbudget) {
457         fThreadSafeCache->dropUniqueRefs(this);
458 
459         stillOverbudget = this->overBudget();
460         while (stillOverbudget && fPurgeableQueue.count()) {
461             GrGpuResource* resource = fPurgeableQueue.peek();
462             SkASSERT(resource->resourcePriv().isPurgeable());
463             resource->cacheAccess().release();
464             stillOverbudget = this->overBudget();
465         }
466     }
467 
468     this->validate();
469 }
470 
purgeUnlockedResources(const skgpu::StdSteadyClock::time_point * purgeTime,GrPurgeResourceOptions opts)471 void GrResourceCache::purgeUnlockedResources(const skgpu::StdSteadyClock::time_point* purgeTime,
472                                              GrPurgeResourceOptions opts) {
473     if (opts == GrPurgeResourceOptions::kAllResources) {
474         if (purgeTime) {
475             fThreadSafeCache->dropUniqueRefsOlderThan(*purgeTime);
476         } else {
477             fThreadSafeCache->dropUniqueRefs(nullptr);
478         }
479 
480         // We could disable maintaining the heap property here, but it would add a lot of
481         // complexity. Moreover, this is rarely called.
482         while (fPurgeableQueue.count()) {
483             GrGpuResource* resource = fPurgeableQueue.peek();
484 
485             const skgpu::StdSteadyClock::time_point resourceTime =
486                     resource->cacheAccess().timeWhenResourceBecamePurgeable();
487             if (purgeTime && resourceTime >= *purgeTime) {
488                 // Resources were given both LRU timestamps and tagged with a frame number when
489                 // they first became purgeable. The LRU timestamp won't change again until the
490                 // resource is made non-purgeable again. So, at this point all the remaining
491                 // resources in the timestamp-sorted queue will have a frame number >= to this
492                 // one.
493                 break;
494             }
495 
496             SkASSERT(resource->resourcePriv().isPurgeable());
497             resource->cacheAccess().release();
498         }
499     } else {
500         SkASSERT(opts == GrPurgeResourceOptions::kScratchResourcesOnly);
501         // Early out if the very first item is too new to purge to avoid sorting the queue when
502         // nothing will be deleted.
503         if (purgeTime && fPurgeableQueue.count() &&
504             fPurgeableQueue.peek()->cacheAccess().timeWhenResourceBecamePurgeable() >= *purgeTime) {
505             return;
506         }
507 
508         // Sort the queue
509         fPurgeableQueue.sort();
510 
511         // Make a list of the scratch resources to delete
512         SkTDArray<GrGpuResource*> scratchResources;
513         for (int i = 0; i < fPurgeableQueue.count(); i++) {
514             GrGpuResource* resource = fPurgeableQueue.at(i);
515 
516             const skgpu::StdSteadyClock::time_point resourceTime =
517                     resource->cacheAccess().timeWhenResourceBecamePurgeable();
518             if (purgeTime && resourceTime >= *purgeTime) {
519                 // scratch or not, all later iterations will be too recently used to purge.
520                 break;
521             }
522             SkASSERT(resource->resourcePriv().isPurgeable());
523             if (!resource->getUniqueKey().isValid()) {
524                 *scratchResources.append() = resource;
525             }
526         }
527 
528         // Delete the scratch resources. This must be done as a separate pass
529         // to avoid messing up the sorted order of the queue
530         for (int i = 0; i < scratchResources.size(); i++) {
531             scratchResources[i]->cacheAccess().release();
532         }
533     }
534 
535     this->validate();
536 }
537 
purgeToMakeHeadroom(size_t desiredHeadroomBytes)538 bool GrResourceCache::purgeToMakeHeadroom(size_t desiredHeadroomBytes) {
539     AutoValidate av(this);
540     if (desiredHeadroomBytes > fMaxBytes) {
541         return false;
542     }
543     if (this->wouldFit(desiredHeadroomBytes)) {
544         return true;
545     }
546     fPurgeableQueue.sort();
547 
548     size_t projectedBudget = fBudgetedBytes;
549     int purgeCnt = 0;
550     for (int i = 0; i < fPurgeableQueue.count(); i++) {
551         GrGpuResource* resource = fPurgeableQueue.at(i);
552         if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) {
553             projectedBudget -= resource->gpuMemorySize();
554         }
555         if (projectedBudget + desiredHeadroomBytes <= fMaxBytes) {
556             purgeCnt = i + 1;
557             break;
558         }
559     }
560     if (purgeCnt == 0) {
561         return false;
562     }
563 
564     // Success! Release the resources.
565     // Copy to array first so we don't mess with the queue.
566     std::vector<GrGpuResource*> resources;
567     resources.reserve(purgeCnt);
568     for (int i = 0; i < purgeCnt; i++) {
569         resources.push_back(fPurgeableQueue.at(i));
570     }
571     for (GrGpuResource* resource : resources) {
572         resource->cacheAccess().release();
573     }
574     return true;
575 }
576 
purgeUnlockedResources(size_t bytesToPurge,bool preferScratchResources)577 void GrResourceCache::purgeUnlockedResources(size_t bytesToPurge, bool preferScratchResources) {
578 
579     const size_t tmpByteBudget = std::max((size_t)0, fBytes - bytesToPurge);
580     bool stillOverbudget = tmpByteBudget < fBytes;
581 
582     if (preferScratchResources && bytesToPurge < fPurgeableBytes) {
583         // Sort the queue
584         fPurgeableQueue.sort();
585 
586         // Make a list of the scratch resources to delete
587         SkTDArray<GrGpuResource*> scratchResources;
588         size_t scratchByteCount = 0;
589         for (int i = 0; i < fPurgeableQueue.count() && stillOverbudget; i++) {
590             GrGpuResource* resource = fPurgeableQueue.at(i);
591             SkASSERT(resource->resourcePriv().isPurgeable());
592             if (!resource->getUniqueKey().isValid()) {
593                 *scratchResources.append() = resource;
594                 scratchByteCount += resource->gpuMemorySize();
595                 stillOverbudget = tmpByteBudget < fBytes - scratchByteCount;
596             }
597         }
598 
599         // Delete the scratch resources. This must be done as a separate pass
600         // to avoid messing up the sorted order of the queue
601         for (int i = 0; i < scratchResources.size(); i++) {
602             scratchResources[i]->cacheAccess().release();
603         }
604         stillOverbudget = tmpByteBudget < fBytes;
605 
606         this->validate();
607     }
608 
609     // Purge any remaining resources in LRU order
610     if (stillOverbudget) {
611         const size_t cachedByteCount = fMaxBytes;
612         fMaxBytes = tmpByteBudget;
613         this->purgeAsNeeded();
614         fMaxBytes = cachedByteCount;
615     }
616 }
617 
processFreedGpuResources()618 void GrResourceCache::processFreedGpuResources() {
619     TArray<UnrefResourceMessage> msgs;
620     fUnrefResourceInbox.poll(&msgs);
621     // We don't need to do anything other than let the messages delete themselves and call unref.
622 }
623 
addToNonpurgeableArray(GrGpuResource * resource)624 void GrResourceCache::addToNonpurgeableArray(GrGpuResource* resource) {
625     int index = fNonpurgeableResources.size();
626     *fNonpurgeableResources.append() = resource;
627     *resource->cacheAccess().accessCacheIndex() = index;
628 }
629 
removeFromNonpurgeableArray(GrGpuResource * resource)630 void GrResourceCache::removeFromNonpurgeableArray(GrGpuResource* resource) {
631     int* index = resource->cacheAccess().accessCacheIndex();
632     // Fill the hole we will create in the array with the tail object, adjust its index, and
633     // then pop the array
634     GrGpuResource* tail = *(fNonpurgeableResources.end() - 1);
635     SkASSERT(fNonpurgeableResources[*index] == resource);
636     fNonpurgeableResources[*index] = tail;
637     *tail->cacheAccess().accessCacheIndex() = *index;
638     fNonpurgeableResources.pop_back();
639     SkDEBUGCODE(*index = -1);
640 }
641 
getNextTimestamp()642 uint32_t GrResourceCache::getNextTimestamp() {
643     // If we wrap then all the existing resources will appear older than any resources that get
644     // a timestamp after the wrap.
645     if (0 == fTimestamp) {
646         int count = this->getResourceCount();
647         if (count) {
648             // Reset all the timestamps. We sort the resources by timestamp and then assign
649             // sequential timestamps beginning with 0. This is O(n*lg(n)) but it should be extremely
650             // rare.
651             SkTDArray<GrGpuResource*> sortedPurgeableResources;
652             sortedPurgeableResources.reserve(fPurgeableQueue.count());
653 
654             while (fPurgeableQueue.count()) {
655                 *sortedPurgeableResources.append() = fPurgeableQueue.peek();
656                 fPurgeableQueue.pop();
657             }
658 
659             SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end(),
660                      CompareTimestamp);
661 
662             // Pick resources out of the purgeable and non-purgeable arrays based on lowest
663             // timestamp and assign new timestamps.
664             int currP = 0;
665             int currNP = 0;
666             while (currP < sortedPurgeableResources.size() &&
667                    currNP < fNonpurgeableResources.size()) {
668                 uint32_t tsP = sortedPurgeableResources[currP]->cacheAccess().timestamp();
669                 uint32_t tsNP = fNonpurgeableResources[currNP]->cacheAccess().timestamp();
670                 SkASSERT(tsP != tsNP);
671                 if (tsP < tsNP) {
672                     sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
673                 } else {
674                     // Correct the index in the nonpurgeable array stored on the resource post-sort.
675                     *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
676                     fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
677                 }
678             }
679 
680             // The above loop ended when we hit the end of one array. Finish the other one.
681             while (currP < sortedPurgeableResources.size()) {
682                 sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
683             }
684             while (currNP < fNonpurgeableResources.size()) {
685                 *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
686                 fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
687             }
688 
689             // Rebuild the queue.
690             for (int i = 0; i < sortedPurgeableResources.size(); ++i) {
691                 fPurgeableQueue.insert(sortedPurgeableResources[i]);
692             }
693 
694             this->validate();
695             SkASSERT(count == this->getResourceCount());
696 
697             // count should be the next timestamp we return.
698             SkASSERT(fTimestamp == SkToU32(count));
699         }
700     }
701     return fTimestamp++;
702 }
703 
dumpMemoryStatistics(SkTraceMemoryDump * traceMemoryDump) const704 void GrResourceCache::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
705     for (int i = 0; i < fNonpurgeableResources.size(); ++i) {
706         fNonpurgeableResources[i]->dumpMemoryStatistics(traceMemoryDump);
707     }
708     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
709         fPurgeableQueue.at(i)->dumpMemoryStatistics(traceMemoryDump);
710     }
711 }
712 
713 #if GR_CACHE_STATS
getStats(Stats * stats) const714 void GrResourceCache::getStats(Stats* stats) const {
715     stats->reset();
716 
717     stats->fTotal = this->getResourceCount();
718     stats->fNumNonPurgeable = fNonpurgeableResources.size();
719     stats->fNumPurgeable = fPurgeableQueue.count();
720 
721     for (int i = 0; i < fNonpurgeableResources.size(); ++i) {
722         stats->update(fNonpurgeableResources[i]);
723     }
724     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
725         stats->update(fPurgeableQueue.at(i));
726     }
727 }
728 
729 #if defined(GPU_TEST_UTILS)
dumpStats(SkString * out) const730 void GrResourceCache::dumpStats(SkString* out) const {
731     this->validate();
732 
733     Stats stats;
734 
735     this->getStats(&stats);
736 
737     float byteUtilization = (100.f * fBudgetedBytes) / fMaxBytes;
738 
739     out->appendf("Budget: %d bytes\n", (int)fMaxBytes);
740     out->appendf("\t\tEntry Count: current %d"
741                  " (%d budgeted, %d wrapped, %d locked, %d scratch), high %d\n",
742                  stats.fTotal, fBudgetedCount, stats.fWrapped, stats.fNumNonPurgeable,
743                  stats.fScratch, fHighWaterCount);
744     out->appendf("\t\tEntry Bytes: current %d (budgeted %d, %.2g%% full, %d unbudgeted) high %d\n",
745                  SkToInt(fBytes), SkToInt(fBudgetedBytes), byteUtilization,
746                  SkToInt(stats.fUnbudgetedSize), SkToInt(fHighWaterBytes));
747 }
748 
dumpStatsKeyValuePairs(TArray<SkString> * keys,TArray<double> * values) const749 void GrResourceCache::dumpStatsKeyValuePairs(TArray<SkString>* keys,
750                                              TArray<double>* values) const {
751     this->validate();
752 
753     Stats stats;
754     this->getStats(&stats);
755 
756     keys->push_back(SkString("gpu_cache_purgable_entries")); values->push_back(stats.fNumPurgeable);
757 }
758 #endif // defined(GPU_TEST_UTILS)
759 #endif // GR_CACHE_STATS
760 
761 #ifdef SK_DEBUG
validate() const762 void GrResourceCache::validate() const {
763     // Reduce the frequency of validations for large resource counts.
764     static SkRandom gRandom;
765     int mask = (SkNextPow2(fCount + 1) >> 5) - 1;
766     if (~mask && (gRandom.nextU() & mask)) {
767         return;
768     }
769 
770     struct Stats {
771         size_t fBytes;
772         int fBudgetedCount;
773         size_t fBudgetedBytes;
774         int fLocked;
775         int fScratch;
776         int fCouldBeScratch;
777         int fContent;
778         const ScratchMap* fScratchMap;
779         const UniqueHash* fUniqueHash;
780 
781         Stats(const GrResourceCache* cache) {
782             memset(this, 0, sizeof(*this));
783             fScratchMap = &cache->fScratchMap;
784             fUniqueHash = &cache->fUniqueHash;
785         }
786 
787         void update(GrGpuResource* resource) {
788             fBytes += resource->gpuMemorySize();
789 
790             if (!resource->resourcePriv().isPurgeable()) {
791                 ++fLocked;
792             }
793 
794             const skgpu::ScratchKey& scratchKey = resource->resourcePriv().getScratchKey();
795             const skgpu::UniqueKey& uniqueKey = resource->getUniqueKey();
796 
797             if (resource->cacheAccess().isUsableAsScratch()) {
798                 SkASSERT(!uniqueKey.isValid());
799                 SkASSERT(GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType());
800                 SkASSERT(!resource->cacheAccess().hasRef());
801                 ++fScratch;
802                 SkASSERT(fScratchMap->countForKey(scratchKey));
803                 SkASSERT(!resource->resourcePriv().refsWrappedObjects());
804             } else if (scratchKey.isValid()) {
805                 SkASSERT(GrBudgetedType::kBudgeted != resource->resourcePriv().budgetedType() ||
806                          uniqueKey.isValid() || resource->cacheAccess().hasRef());
807                 SkASSERT(!resource->resourcePriv().refsWrappedObjects());
808                 SkASSERT(!fScratchMap->has(resource, scratchKey));
809             }
810             if (uniqueKey.isValid()) {
811                 ++fContent;
812                 SkASSERT(fUniqueHash->find(uniqueKey) == resource);
813                 SkASSERT(GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType() ||
814                          resource->resourcePriv().refsWrappedObjects());
815             }
816 
817             if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) {
818                 ++fBudgetedCount;
819                 fBudgetedBytes += resource->gpuMemorySize();
820             }
821         }
822     };
823 
824     {
825         int count = 0;
826         fScratchMap.foreach([&](const GrGpuResource& resource) {
827             SkASSERT(resource.cacheAccess().isUsableAsScratch());
828             count++;
829         });
830         SkASSERT(count == fScratchMap.count());
831     }
832 
833     Stats stats(this);
834     size_t purgeableBytes = 0;
835 
836     for (int i = 0; i < fNonpurgeableResources.size(); ++i) {
837         SkASSERT(!fNonpurgeableResources[i]->resourcePriv().isPurgeable() ||
838                  fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
839         SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
840         SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
841         stats.update(fNonpurgeableResources[i]);
842     }
843     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
844         SkASSERT(fPurgeableQueue.at(i)->resourcePriv().isPurgeable());
845         SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i);
846         SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed());
847         stats.update(fPurgeableQueue.at(i));
848         purgeableBytes += fPurgeableQueue.at(i)->gpuMemorySize();
849     }
850 
851     SkASSERT(fCount == this->getResourceCount());
852     SkASSERT(fBudgetedCount <= fCount);
853     SkASSERT(fBudgetedBytes <= fBytes);
854     SkASSERT(stats.fBytes == fBytes);
855     SkASSERT(stats.fBudgetedBytes == fBudgetedBytes);
856     SkASSERT(stats.fBudgetedCount == fBudgetedCount);
857     SkASSERT(purgeableBytes == fPurgeableBytes);
858 #if GR_CACHE_STATS
859     SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount);
860     SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes);
861     SkASSERT(fBytes <= fHighWaterBytes);
862     SkASSERT(fCount <= fHighWaterCount);
863     SkASSERT(fBudgetedBytes <= fBudgetedHighWaterBytes);
864     SkASSERT(fBudgetedCount <= fBudgetedHighWaterCount);
865 #endif
866     SkASSERT(stats.fContent == fUniqueHash.count());
867     SkASSERT(stats.fScratch == fScratchMap.count());
868 
869     // This assertion is not currently valid because we can be in recursive notifyCntReachedZero()
870     // calls. This will be fixed when subresource registration is explicit.
871     // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount;
872     // SkASSERT(!overBudget || locked == count || fPurging);
873 }
874 
isInCache(const GrGpuResource * resource) const875 bool GrResourceCache::isInCache(const GrGpuResource* resource) const {
876     int index = *resource->cacheAccess().accessCacheIndex();
877     if (index < 0) {
878         return false;
879     }
880     if (index < fPurgeableQueue.count() && fPurgeableQueue.at(index) == resource) {
881         return true;
882     }
883     if (index < fNonpurgeableResources.size() && fNonpurgeableResources[index] == resource) {
884         return true;
885     }
886     SkDEBUGFAIL("Resource index should be -1 or the resource should be in the cache.");
887     return false;
888 }
889 
890 #endif // SK_DEBUG
891 
892 #if defined(GPU_TEST_UTILS)
893 
countUniqueKeysWithTag(const char * tag) const894 int GrResourceCache::countUniqueKeysWithTag(const char* tag) const {
895     int count = 0;
896     fUniqueHash.foreach([&](const GrGpuResource& resource){
897         if (0 == strcmp(tag, resource.getUniqueKey().tag())) {
898             ++count;
899         }
900     });
901     return count;
902 }
903 
changeTimestamp(uint32_t newTimestamp)904 void GrResourceCache::changeTimestamp(uint32_t newTimestamp) {
905     fTimestamp = newTimestamp;
906 }
907 
visitSurfaces(const std::function<void (const GrSurface *,bool purgeable)> & func) const908 void GrResourceCache::visitSurfaces(
909         const std::function<void(const GrSurface*, bool purgeable)>& func) const {
910 
911     for (int i = 0; i < fNonpurgeableResources.size(); ++i) {
912         if (const GrSurface* surf = fNonpurgeableResources[i]->asSurface()) {
913             func(surf, /* purgeable= */ false);
914         }
915     }
916     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
917         if (const GrSurface* surf = fPurgeableQueue.at(i)->asSurface()) {
918             func(surf, /* purgeable= */ true);
919         }
920     }
921 }
922 
923 #endif // defined(GPU_TEST_UTILS)
924