xref: /aosp_15_r20/external/skia/src/gpu/ganesh/GrResourceCache.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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     } else if (!resource->cacheAccess().hasRefOrCommandBufferUsage() &&
306                resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) {
307         SkASSERT(fNumBudgetedResourcesFlushWillMakePurgeable > 0);
308         fNumBudgetedResourcesFlushWillMakePurgeable--;
309     }
310     resource->cacheAccess().ref();
311 
312     resource->cacheAccess().setTimestamp(this->getNextTimestamp());
313     this->validate();
314 }
315 
notifyARefCntReachedZero(GrGpuResource * resource,GrGpuResource::LastRemovedRef removedRef)316 void GrResourceCache::notifyARefCntReachedZero(GrGpuResource* resource,
317                                                GrGpuResource::LastRemovedRef removedRef) {
318     ASSERT_SINGLE_OWNER
319     SkASSERT(resource);
320     SkASSERT(!resource->wasDestroyed());
321     SkASSERT(this->isInCache(resource));
322     // This resource should always be in the nonpurgeable array when this function is called. It
323     // will be moved to the queue if it is newly purgeable.
324     SkASSERT(fNonpurgeableResources[*resource->cacheAccess().accessCacheIndex()] == resource);
325 
326     if (removedRef == GrGpuResource::LastRemovedRef::kMainRef) {
327         if (resource->cacheAccess().isUsableAsScratch()) {
328             fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
329         }
330     }
331 
332     if (resource->cacheAccess().hasRefOrCommandBufferUsage()) {
333         this->validate();
334         return;
335     }
336 
337 #ifdef SK_DEBUG
338     // When the timestamp overflows validate() is called. validate() checks that resources in
339     // the nonpurgeable array are indeed not purgeable. However, the movement from the array to
340     // the purgeable queue happens just below in this function. So we mark it as an exception.
341     if (resource->resourcePriv().isPurgeable()) {
342         fNewlyPurgeableResourceForValidation = resource;
343     }
344 #endif
345     resource->cacheAccess().setTimestamp(this->getNextTimestamp());
346     SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr);
347 
348     if (!resource->resourcePriv().isPurgeable() &&
349         resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) {
350         ++fNumBudgetedResourcesFlushWillMakePurgeable;
351     }
352 
353     if (!resource->resourcePriv().isPurgeable()) {
354         this->validate();
355         return;
356     }
357 
358     this->removeFromNonpurgeableArray(resource);
359     fPurgeableQueue.insert(resource);
360     resource->cacheAccess().setTimeWhenResourceBecomePurgeable();
361     fPurgeableBytes += resource->gpuMemorySize();
362 
363     bool hasUniqueKey = resource->getUniqueKey().isValid();
364 
365     GrBudgetedType budgetedType = resource->resourcePriv().budgetedType();
366 
367     if (budgetedType == GrBudgetedType::kBudgeted) {
368         // Purge the resource immediately if we're over budget
369         // Also purge if the resource has neither a valid scratch key nor a unique key.
370         bool hasKey = resource->resourcePriv().getScratchKey().isValid() || hasUniqueKey;
371         if (!this->overBudget() && hasKey) {
372             return;
373         }
374     } else {
375         // We keep unbudgeted resources with a unique key in the purgeable queue of the cache so
376         // they can be reused again by the image connected to the unique key.
377         if (hasUniqueKey && budgetedType == GrBudgetedType::kUnbudgetedCacheable) {
378             return;
379         }
380         // Check whether this resource could still be used as a scratch resource.
381         if (!resource->resourcePriv().refsWrappedObjects() &&
382             resource->resourcePriv().getScratchKey().isValid()) {
383             // We won't purge an existing resource to make room for this one.
384             if (this->wouldFit(resource->gpuMemorySize())) {
385                 resource->resourcePriv().makeBudgeted();
386                 return;
387             }
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 
didChangeBudgetStatus(GrGpuResource * resource)398 void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) {
399     ASSERT_SINGLE_OWNER
400     SkASSERT(resource);
401     SkASSERT(this->isInCache(resource));
402 
403     size_t size = resource->gpuMemorySize();
404     // Changing from BudgetedType::kUnbudgetedCacheable to another budgeted type could make
405     // resource become purgeable. However, we should never allow that transition. Wrapped
406     // resources are the only resources that can be in that state and they aren't allowed to
407     // transition from one budgeted state to another.
408     SkDEBUGCODE(bool wasPurgeable = resource->resourcePriv().isPurgeable());
409     if (resource->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted) {
410         ++fBudgetedCount;
411         fBudgetedBytes += size;
412 #if GR_CACHE_STATS
413         fBudgetedHighWaterBytes = std::max(fBudgetedBytes, fBudgetedHighWaterBytes);
414         fBudgetedHighWaterCount = std::max(fBudgetedCount, fBudgetedHighWaterCount);
415 #endif
416         if (!resource->resourcePriv().isPurgeable() &&
417             !resource->cacheAccess().hasRefOrCommandBufferUsage()) {
418             ++fNumBudgetedResourcesFlushWillMakePurgeable;
419         }
420         if (resource->cacheAccess().isUsableAsScratch()) {
421             fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
422         }
423         this->purgeAsNeeded();
424     } else {
425         SkASSERT(resource->resourcePriv().budgetedType() != GrBudgetedType::kUnbudgetedCacheable);
426         --fBudgetedCount;
427         fBudgetedBytes -= size;
428         if (!resource->resourcePriv().isPurgeable() &&
429             !resource->cacheAccess().hasRefOrCommandBufferUsage()) {
430             --fNumBudgetedResourcesFlushWillMakePurgeable;
431         }
432         if (!resource->cacheAccess().hasRef() && !resource->getUniqueKey().isValid() &&
433             resource->resourcePriv().getScratchKey().isValid()) {
434             fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
435         }
436     }
437     SkASSERT(wasPurgeable == resource->resourcePriv().isPurgeable());
438     TRACE_COUNTER2("skia.gpu.cache", "skia budget", "used",
439                    fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
440 
441     this->validate();
442 }
443 
purgeAsNeeded()444 void GrResourceCache::purgeAsNeeded() {
445     TArray<skgpu::UniqueKeyInvalidatedMessage> invalidKeyMsgs;
446     fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
447     if (!invalidKeyMsgs.empty()) {
448         SkASSERT(fProxyProvider);
449 
450         for (int i = 0; i < invalidKeyMsgs.size(); ++i) {
451             if (invalidKeyMsgs[i].inThreadSafeCache()) {
452                 fThreadSafeCache->remove(invalidKeyMsgs[i].key());
453                 SkASSERT(!fThreadSafeCache->has(invalidKeyMsgs[i].key()));
454             } else {
455                 fProxyProvider->processInvalidUniqueKey(
456                                                     invalidKeyMsgs[i].key(), nullptr,
457                                                     GrProxyProvider::InvalidateGPUResource::kYes);
458                 SkASSERT(!this->findAndRefUniqueResource(invalidKeyMsgs[i].key()));
459             }
460         }
461     }
462 
463     this->processFreedGpuResources();
464 
465     bool stillOverbudget = this->overBudget();
466     while (stillOverbudget && fPurgeableQueue.count()) {
467         GrGpuResource* resource = fPurgeableQueue.peek();
468         SkASSERT(resource->resourcePriv().isPurgeable());
469         resource->cacheAccess().release();
470         stillOverbudget = this->overBudget();
471     }
472 
473     if (stillOverbudget) {
474         fThreadSafeCache->dropUniqueRefs(this);
475 
476         stillOverbudget = this->overBudget();
477         while (stillOverbudget && fPurgeableQueue.count()) {
478             GrGpuResource* resource = fPurgeableQueue.peek();
479             SkASSERT(resource->resourcePriv().isPurgeable());
480             resource->cacheAccess().release();
481             stillOverbudget = this->overBudget();
482         }
483     }
484 
485     this->validate();
486 }
487 
purgeUnlockedResources(const skgpu::StdSteadyClock::time_point * purgeTime,GrPurgeResourceOptions opts)488 void GrResourceCache::purgeUnlockedResources(const skgpu::StdSteadyClock::time_point* purgeTime,
489                                              GrPurgeResourceOptions opts) {
490     if (opts == GrPurgeResourceOptions::kAllResources) {
491         if (purgeTime) {
492             fThreadSafeCache->dropUniqueRefsOlderThan(*purgeTime);
493         } else {
494             fThreadSafeCache->dropUniqueRefs(nullptr);
495         }
496 
497         // We could disable maintaining the heap property here, but it would add a lot of
498         // complexity. Moreover, this is rarely called.
499         while (fPurgeableQueue.count()) {
500             GrGpuResource* resource = fPurgeableQueue.peek();
501 
502             const skgpu::StdSteadyClock::time_point resourceTime =
503                     resource->cacheAccess().timeWhenResourceBecamePurgeable();
504             if (purgeTime && resourceTime >= *purgeTime) {
505                 // Resources were given both LRU timestamps and tagged with a frame number when
506                 // they first became purgeable. The LRU timestamp won't change again until the
507                 // resource is made non-purgeable again. So, at this point all the remaining
508                 // resources in the timestamp-sorted queue will have a frame number >= to this
509                 // one.
510                 break;
511             }
512 
513             SkASSERT(resource->resourcePriv().isPurgeable());
514             resource->cacheAccess().release();
515         }
516     } else {
517         SkASSERT(opts == GrPurgeResourceOptions::kScratchResourcesOnly);
518         // Early out if the very first item is too new to purge to avoid sorting the queue when
519         // nothing will be deleted.
520         if (purgeTime && fPurgeableQueue.count() &&
521             fPurgeableQueue.peek()->cacheAccess().timeWhenResourceBecamePurgeable() >= *purgeTime) {
522             return;
523         }
524 
525         // Sort the queue
526         fPurgeableQueue.sort();
527 
528         // Make a list of the scratch resources to delete
529         SkTDArray<GrGpuResource*> scratchResources;
530         for (int i = 0; i < fPurgeableQueue.count(); i++) {
531             GrGpuResource* resource = fPurgeableQueue.at(i);
532 
533             const skgpu::StdSteadyClock::time_point resourceTime =
534                     resource->cacheAccess().timeWhenResourceBecamePurgeable();
535             if (purgeTime && resourceTime >= *purgeTime) {
536                 // scratch or not, all later iterations will be too recently used to purge.
537                 break;
538             }
539             SkASSERT(resource->resourcePriv().isPurgeable());
540             if (!resource->getUniqueKey().isValid()) {
541                 *scratchResources.append() = resource;
542             }
543         }
544 
545         // Delete the scratch resources. This must be done as a separate pass
546         // to avoid messing up the sorted order of the queue
547         for (int i = 0; i < scratchResources.size(); i++) {
548             scratchResources[i]->cacheAccess().release();
549         }
550     }
551 
552     this->validate();
553 }
554 
purgeToMakeHeadroom(size_t desiredHeadroomBytes)555 bool GrResourceCache::purgeToMakeHeadroom(size_t desiredHeadroomBytes) {
556     AutoValidate av(this);
557     if (desiredHeadroomBytes > fMaxBytes) {
558         return false;
559     }
560     if (this->wouldFit(desiredHeadroomBytes)) {
561         return true;
562     }
563     fPurgeableQueue.sort();
564 
565     size_t projectedBudget = fBudgetedBytes;
566     int purgeCnt = 0;
567     for (int i = 0; i < fPurgeableQueue.count(); i++) {
568         GrGpuResource* resource = fPurgeableQueue.at(i);
569         if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) {
570             projectedBudget -= resource->gpuMemorySize();
571         }
572         if (projectedBudget + desiredHeadroomBytes <= fMaxBytes) {
573             purgeCnt = i + 1;
574             break;
575         }
576     }
577     if (purgeCnt == 0) {
578         return false;
579     }
580 
581     // Success! Release the resources.
582     // Copy to array first so we don't mess with the queue.
583     std::vector<GrGpuResource*> resources;
584     resources.reserve(purgeCnt);
585     for (int i = 0; i < purgeCnt; i++) {
586         resources.push_back(fPurgeableQueue.at(i));
587     }
588     for (GrGpuResource* resource : resources) {
589         resource->cacheAccess().release();
590     }
591     return true;
592 }
593 
purgeUnlockedResources(size_t bytesToPurge,bool preferScratchResources)594 void GrResourceCache::purgeUnlockedResources(size_t bytesToPurge, bool preferScratchResources) {
595 
596     const size_t tmpByteBudget = std::max((size_t)0, fBytes - bytesToPurge);
597     bool stillOverbudget = tmpByteBudget < fBytes;
598 
599     if (preferScratchResources && bytesToPurge < fPurgeableBytes) {
600         // Sort the queue
601         fPurgeableQueue.sort();
602 
603         // Make a list of the scratch resources to delete
604         SkTDArray<GrGpuResource*> scratchResources;
605         size_t scratchByteCount = 0;
606         for (int i = 0; i < fPurgeableQueue.count() && stillOverbudget; i++) {
607             GrGpuResource* resource = fPurgeableQueue.at(i);
608             SkASSERT(resource->resourcePriv().isPurgeable());
609             if (!resource->getUniqueKey().isValid()) {
610                 *scratchResources.append() = resource;
611                 scratchByteCount += resource->gpuMemorySize();
612                 stillOverbudget = tmpByteBudget < fBytes - scratchByteCount;
613             }
614         }
615 
616         // Delete the scratch resources. This must be done as a separate pass
617         // to avoid messing up the sorted order of the queue
618         for (int i = 0; i < scratchResources.size(); i++) {
619             scratchResources[i]->cacheAccess().release();
620         }
621         stillOverbudget = tmpByteBudget < fBytes;
622 
623         this->validate();
624     }
625 
626     // Purge any remaining resources in LRU order
627     if (stillOverbudget) {
628         const size_t cachedByteCount = fMaxBytes;
629         fMaxBytes = tmpByteBudget;
630         this->purgeAsNeeded();
631         fMaxBytes = cachedByteCount;
632     }
633 }
634 
requestsFlush() const635 bool GrResourceCache::requestsFlush() const {
636     return this->overBudget() && !fPurgeableQueue.count() &&
637            fNumBudgetedResourcesFlushWillMakePurgeable > 0;
638 }
639 
processFreedGpuResources()640 void GrResourceCache::processFreedGpuResources() {
641     TArray<UnrefResourceMessage> msgs;
642     fUnrefResourceInbox.poll(&msgs);
643     // We don't need to do anything other than let the messages delete themselves and call unref.
644 }
645 
addToNonpurgeableArray(GrGpuResource * resource)646 void GrResourceCache::addToNonpurgeableArray(GrGpuResource* resource) {
647     int index = fNonpurgeableResources.size();
648     *fNonpurgeableResources.append() = resource;
649     *resource->cacheAccess().accessCacheIndex() = index;
650 }
651 
removeFromNonpurgeableArray(GrGpuResource * resource)652 void GrResourceCache::removeFromNonpurgeableArray(GrGpuResource* resource) {
653     int* index = resource->cacheAccess().accessCacheIndex();
654     // Fill the hole we will create in the array with the tail object, adjust its index, and
655     // then pop the array
656     GrGpuResource* tail = *(fNonpurgeableResources.end() - 1);
657     SkASSERT(fNonpurgeableResources[*index] == resource);
658     fNonpurgeableResources[*index] = tail;
659     *tail->cacheAccess().accessCacheIndex() = *index;
660     fNonpurgeableResources.pop_back();
661     SkDEBUGCODE(*index = -1);
662 }
663 
getNextTimestamp()664 uint32_t GrResourceCache::getNextTimestamp() {
665     // If we wrap then all the existing resources will appear older than any resources that get
666     // a timestamp after the wrap.
667     if (0 == fTimestamp) {
668         int count = this->getResourceCount();
669         if (count) {
670             // Reset all the timestamps. We sort the resources by timestamp and then assign
671             // sequential timestamps beginning with 0. This is O(n*lg(n)) but it should be extremely
672             // rare.
673             SkTDArray<GrGpuResource*> sortedPurgeableResources;
674             sortedPurgeableResources.reserve(fPurgeableQueue.count());
675 
676             while (fPurgeableQueue.count()) {
677                 *sortedPurgeableResources.append() = fPurgeableQueue.peek();
678                 fPurgeableQueue.pop();
679             }
680 
681             SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end(),
682                      CompareTimestamp);
683 
684             // Pick resources out of the purgeable and non-purgeable arrays based on lowest
685             // timestamp and assign new timestamps.
686             int currP = 0;
687             int currNP = 0;
688             while (currP < sortedPurgeableResources.size() &&
689                    currNP < fNonpurgeableResources.size()) {
690                 uint32_t tsP = sortedPurgeableResources[currP]->cacheAccess().timestamp();
691                 uint32_t tsNP = fNonpurgeableResources[currNP]->cacheAccess().timestamp();
692                 SkASSERT(tsP != tsNP);
693                 if (tsP < tsNP) {
694                     sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
695                 } else {
696                     // Correct the index in the nonpurgeable array stored on the resource post-sort.
697                     *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
698                     fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
699                 }
700             }
701 
702             // The above loop ended when we hit the end of one array. Finish the other one.
703             while (currP < sortedPurgeableResources.size()) {
704                 sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
705             }
706             while (currNP < fNonpurgeableResources.size()) {
707                 *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
708                 fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
709             }
710 
711             // Rebuild the queue.
712             for (int i = 0; i < sortedPurgeableResources.size(); ++i) {
713                 fPurgeableQueue.insert(sortedPurgeableResources[i]);
714             }
715 
716             this->validate();
717             SkASSERT(count == this->getResourceCount());
718 
719             // count should be the next timestamp we return.
720             SkASSERT(fTimestamp == SkToU32(count));
721         }
722     }
723     return fTimestamp++;
724 }
725 
dumpMemoryStatistics(SkTraceMemoryDump * traceMemoryDump) const726 void GrResourceCache::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
727     for (int i = 0; i < fNonpurgeableResources.size(); ++i) {
728         fNonpurgeableResources[i]->dumpMemoryStatistics(traceMemoryDump);
729     }
730     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
731         fPurgeableQueue.at(i)->dumpMemoryStatistics(traceMemoryDump);
732     }
733 }
734 
735 #if GR_CACHE_STATS
getStats(Stats * stats) const736 void GrResourceCache::getStats(Stats* stats) const {
737     stats->reset();
738 
739     stats->fTotal = this->getResourceCount();
740     stats->fNumNonPurgeable = fNonpurgeableResources.size();
741     stats->fNumPurgeable = fPurgeableQueue.count();
742 
743     for (int i = 0; i < fNonpurgeableResources.size(); ++i) {
744         stats->update(fNonpurgeableResources[i]);
745     }
746     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
747         stats->update(fPurgeableQueue.at(i));
748     }
749 }
750 
751 #if defined(GPU_TEST_UTILS)
dumpStats(SkString * out) const752 void GrResourceCache::dumpStats(SkString* out) const {
753     this->validate();
754 
755     Stats stats;
756 
757     this->getStats(&stats);
758 
759     float byteUtilization = (100.f * fBudgetedBytes) / fMaxBytes;
760 
761     out->appendf("Budget: %d bytes\n", (int)fMaxBytes);
762     out->appendf("\t\tEntry Count: current %d"
763                  " (%d budgeted, %d wrapped, %d locked, %d scratch), high %d\n",
764                  stats.fTotal, fBudgetedCount, stats.fWrapped, stats.fNumNonPurgeable,
765                  stats.fScratch, fHighWaterCount);
766     out->appendf("\t\tEntry Bytes: current %d (budgeted %d, %.2g%% full, %d unbudgeted) high %d\n",
767                  SkToInt(fBytes), SkToInt(fBudgetedBytes), byteUtilization,
768                  SkToInt(stats.fUnbudgetedSize), SkToInt(fHighWaterBytes));
769 }
770 
dumpStatsKeyValuePairs(TArray<SkString> * keys,TArray<double> * values) const771 void GrResourceCache::dumpStatsKeyValuePairs(TArray<SkString>* keys,
772                                              TArray<double>* values) const {
773     this->validate();
774 
775     Stats stats;
776     this->getStats(&stats);
777 
778     keys->push_back(SkString("gpu_cache_purgable_entries")); values->push_back(stats.fNumPurgeable);
779 }
780 #endif // defined(GPU_TEST_UTILS)
781 #endif // GR_CACHE_STATS
782 
783 #ifdef SK_DEBUG
validate() const784 void GrResourceCache::validate() const {
785     // Reduce the frequency of validations for large resource counts.
786     static SkRandom gRandom;
787     int mask = (SkNextPow2(fCount + 1) >> 5) - 1;
788     if (~mask && (gRandom.nextU() & mask)) {
789         return;
790     }
791 
792     struct Stats {
793         size_t fBytes;
794         int fBudgetedCount;
795         size_t fBudgetedBytes;
796         int fLocked;
797         int fScratch;
798         int fCouldBeScratch;
799         int fContent;
800         const ScratchMap* fScratchMap;
801         const UniqueHash* fUniqueHash;
802 
803         Stats(const GrResourceCache* cache) {
804             memset(this, 0, sizeof(*this));
805             fScratchMap = &cache->fScratchMap;
806             fUniqueHash = &cache->fUniqueHash;
807         }
808 
809         void update(GrGpuResource* resource) {
810             fBytes += resource->gpuMemorySize();
811 
812             if (!resource->resourcePriv().isPurgeable()) {
813                 ++fLocked;
814             }
815 
816             const skgpu::ScratchKey& scratchKey = resource->resourcePriv().getScratchKey();
817             const skgpu::UniqueKey& uniqueKey = resource->getUniqueKey();
818 
819             if (resource->cacheAccess().isUsableAsScratch()) {
820                 SkASSERT(!uniqueKey.isValid());
821                 SkASSERT(GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType());
822                 SkASSERT(!resource->cacheAccess().hasRef());
823                 ++fScratch;
824                 SkASSERT(fScratchMap->countForKey(scratchKey));
825                 SkASSERT(!resource->resourcePriv().refsWrappedObjects());
826             } else if (scratchKey.isValid()) {
827                 SkASSERT(GrBudgetedType::kBudgeted != resource->resourcePriv().budgetedType() ||
828                          uniqueKey.isValid() || resource->cacheAccess().hasRef());
829                 SkASSERT(!resource->resourcePriv().refsWrappedObjects());
830                 SkASSERT(!fScratchMap->has(resource, scratchKey));
831             }
832             if (uniqueKey.isValid()) {
833                 ++fContent;
834                 SkASSERT(fUniqueHash->find(uniqueKey) == resource);
835                 SkASSERT(GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType() ||
836                          resource->resourcePriv().refsWrappedObjects());
837             }
838 
839             if (GrBudgetedType::kBudgeted == resource->resourcePriv().budgetedType()) {
840                 ++fBudgetedCount;
841                 fBudgetedBytes += resource->gpuMemorySize();
842             }
843         }
844     };
845 
846     {
847         int count = 0;
848         fScratchMap.foreach([&](const GrGpuResource& resource) {
849             SkASSERT(resource.cacheAccess().isUsableAsScratch());
850             count++;
851         });
852         SkASSERT(count == fScratchMap.count());
853     }
854 
855     Stats stats(this);
856     size_t purgeableBytes = 0;
857     int numBudgetedResourcesFlushWillMakePurgeable = 0;
858 
859     for (int i = 0; i < fNonpurgeableResources.size(); ++i) {
860         SkASSERT(!fNonpurgeableResources[i]->resourcePriv().isPurgeable() ||
861                  fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
862         SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
863         SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
864         if (fNonpurgeableResources[i]->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted &&
865             !fNonpurgeableResources[i]->cacheAccess().hasRefOrCommandBufferUsage() &&
866             fNewlyPurgeableResourceForValidation != fNonpurgeableResources[i]) {
867             ++numBudgetedResourcesFlushWillMakePurgeable;
868         }
869         stats.update(fNonpurgeableResources[i]);
870     }
871     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
872         SkASSERT(fPurgeableQueue.at(i)->resourcePriv().isPurgeable());
873         SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i);
874         SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed());
875         stats.update(fPurgeableQueue.at(i));
876         purgeableBytes += fPurgeableQueue.at(i)->gpuMemorySize();
877     }
878 
879     SkASSERT(fCount == this->getResourceCount());
880     SkASSERT(fBudgetedCount <= fCount);
881     SkASSERT(fBudgetedBytes <= fBytes);
882     SkASSERT(stats.fBytes == fBytes);
883     SkASSERT(fNumBudgetedResourcesFlushWillMakePurgeable ==
884              numBudgetedResourcesFlushWillMakePurgeable);
885     SkASSERT(stats.fBudgetedBytes == fBudgetedBytes);
886     SkASSERT(stats.fBudgetedCount == fBudgetedCount);
887     SkASSERT(purgeableBytes == fPurgeableBytes);
888 #if GR_CACHE_STATS
889     SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount);
890     SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes);
891     SkASSERT(fBytes <= fHighWaterBytes);
892     SkASSERT(fCount <= fHighWaterCount);
893     SkASSERT(fBudgetedBytes <= fBudgetedHighWaterBytes);
894     SkASSERT(fBudgetedCount <= fBudgetedHighWaterCount);
895 #endif
896     SkASSERT(stats.fContent == fUniqueHash.count());
897     SkASSERT(stats.fScratch == fScratchMap.count());
898 
899     // This assertion is not currently valid because we can be in recursive notifyCntReachedZero()
900     // calls. This will be fixed when subresource registration is explicit.
901     // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount;
902     // SkASSERT(!overBudget || locked == count || fPurging);
903 }
904 
isInCache(const GrGpuResource * resource) const905 bool GrResourceCache::isInCache(const GrGpuResource* resource) const {
906     int index = *resource->cacheAccess().accessCacheIndex();
907     if (index < 0) {
908         return false;
909     }
910     if (index < fPurgeableQueue.count() && fPurgeableQueue.at(index) == resource) {
911         return true;
912     }
913     if (index < fNonpurgeableResources.size() && fNonpurgeableResources[index] == resource) {
914         return true;
915     }
916     SkDEBUGFAIL("Resource index should be -1 or the resource should be in the cache.");
917     return false;
918 }
919 
920 #endif // SK_DEBUG
921 
922 #if defined(GPU_TEST_UTILS)
923 
countUniqueKeysWithTag(const char * tag) const924 int GrResourceCache::countUniqueKeysWithTag(const char* tag) const {
925     int count = 0;
926     fUniqueHash.foreach([&](const GrGpuResource& resource){
927         if (0 == strcmp(tag, resource.getUniqueKey().tag())) {
928             ++count;
929         }
930     });
931     return count;
932 }
933 
changeTimestamp(uint32_t newTimestamp)934 void GrResourceCache::changeTimestamp(uint32_t newTimestamp) {
935     fTimestamp = newTimestamp;
936 }
937 
visitSurfaces(const std::function<void (const GrSurface *,bool purgeable)> & func) const938 void GrResourceCache::visitSurfaces(
939         const std::function<void(const GrSurface*, bool purgeable)>& func) const {
940 
941     for (int i = 0; i < fNonpurgeableResources.size(); ++i) {
942         if (const GrSurface* surf = fNonpurgeableResources[i]->asSurface()) {
943             func(surf, /* purgeable= */ false);
944         }
945     }
946     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
947         if (const GrSurface* surf = fPurgeableQueue.at(i)->asSurface()) {
948             func(surf, /* purgeable= */ true);
949         }
950     }
951 }
952 
953 #endif // defined(GPU_TEST_UTILS)
954