blob: f1a51b06c34971b8d4675c1c5aabed829ca45b52 [file] [log] [blame]
bsalomonc8dc1f72014-08-21 13:02:13 -07001
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
bsalomon0ea80f42015-02-11 10:49:59 -080010#include "GrResourceCache.h"
bsalomon3582d3e2015-02-13 14:20:05 -080011#include "GrGpuResourceCacheAccess.h"
hendrikw876c3132015-03-04 10:33:49 -080012#include "GrTracing.h"
bsalomon7775c852014-12-30 12:50:52 -080013#include "SkChecksum.h"
bsalomon71cb0c22014-11-14 12:10:14 -080014#include "SkGr.h"
15#include "SkMessageBus.h"
bsalomonddf30e62015-02-19 11:38:44 -080016#include "SkTSort.h"
bsalomon71cb0c22014-11-14 12:10:14 -080017
bsalomon8718aaf2015-02-19 07:24:21 -080018DECLARE_SKMESSAGEBUS_MESSAGE(GrUniqueKeyInvalidatedMessage);
bsalomon71cb0c22014-11-14 12:10:14 -080019
20//////////////////////////////////////////////////////////////////////////////
21
bsalomon7775c852014-12-30 12:50:52 -080022GrScratchKey::ResourceType GrScratchKey::GenerateResourceType() {
bsalomon24db3b12015-01-23 04:24:04 -080023 static int32_t gType = INHERITED::kInvalidDomain + 1;
bsalomonfe369ee2014-11-10 11:59:06 -080024
bsalomon7775c852014-12-30 12:50:52 -080025 int32_t type = sk_atomic_inc(&gType);
robertphillips9790a7b2015-01-05 12:29:15 -080026 if (type > SK_MaxU16) {
bsalomon71cb0c22014-11-14 12:10:14 -080027 SkFAIL("Too many Resource Types");
28 }
29
30 return static_cast<ResourceType>(type);
31}
32
bsalomon8718aaf2015-02-19 07:24:21 -080033GrUniqueKey::Domain GrUniqueKey::GenerateDomain() {
bsalomon24db3b12015-01-23 04:24:04 -080034 static int32_t gDomain = INHERITED::kInvalidDomain + 1;
bsalomon7775c852014-12-30 12:50:52 -080035
bsalomon24db3b12015-01-23 04:24:04 -080036 int32_t domain = sk_atomic_inc(&gDomain);
kkinnunen016dffb2015-01-23 06:43:05 -080037 if (domain > SK_MaxU16) {
bsalomon8718aaf2015-02-19 07:24:21 -080038 SkFAIL("Too many GrUniqueKey Domains");
bsalomon7775c852014-12-30 12:50:52 -080039 }
bsalomon24db3b12015-01-23 04:24:04 -080040
41 return static_cast<Domain>(domain);
42}
bsalomon3f324322015-04-08 11:01:54 -070043
bsalomon24db3b12015-01-23 04:24:04 -080044uint32_t GrResourceKeyHash(const uint32_t* data, size_t size) {
45 return SkChecksum::Compute(data, size);
bsalomon7775c852014-12-30 12:50:52 -080046}
47
bsalomonfe369ee2014-11-10 11:59:06 -080048//////////////////////////////////////////////////////////////////////////////
49
bsalomon0ea80f42015-02-11 10:49:59 -080050class GrResourceCache::AutoValidate : ::SkNoncopyable {
bsalomon71cb0c22014-11-14 12:10:14 -080051public:
bsalomon0ea80f42015-02-11 10:49:59 -080052 AutoValidate(GrResourceCache* cache) : fCache(cache) { cache->validate(); }
bsalomon71cb0c22014-11-14 12:10:14 -080053 ~AutoValidate() { fCache->validate(); }
54private:
bsalomon0ea80f42015-02-11 10:49:59 -080055 GrResourceCache* fCache;
bsalomon71cb0c22014-11-14 12:10:14 -080056};
57
58 //////////////////////////////////////////////////////////////////////////////
59
bsalomon71cb0c22014-11-14 12:10:14 -080060
bsalomon0ea80f42015-02-11 10:49:59 -080061GrResourceCache::GrResourceCache()
bsalomon9f2d1572015-02-17 11:47:40 -080062 : fTimestamp(0)
63 , fMaxCount(kDefaultMaxCount)
bsalomon71cb0c22014-11-14 12:10:14 -080064 , fMaxBytes(kDefaultMaxSize)
bsalomon3f324322015-04-08 11:01:54 -070065 , fMaxUnusedFlushes(kDefaultMaxUnusedFlushes)
bsalomon71cb0c22014-11-14 12:10:14 -080066#if GR_CACHE_STATS
67 , fHighWaterCount(0)
68 , fHighWaterBytes(0)
bsalomondace19e2014-11-17 07:34:06 -080069 , fBudgetedHighWaterCount(0)
70 , fBudgetedHighWaterBytes(0)
bsalomon71cb0c22014-11-14 12:10:14 -080071#endif
bsalomon71cb0c22014-11-14 12:10:14 -080072 , fBytes(0)
bsalomondace19e2014-11-17 07:34:06 -080073 , fBudgetedCount(0)
74 , fBudgetedBytes(0)
bsalomon71cb0c22014-11-14 12:10:14 -080075 , fOverBudgetCB(NULL)
bsalomon3f324322015-04-08 11:01:54 -070076 , fOverBudgetData(NULL)
77 , fFlushTimestamps(NULL)
78 , fLastFlushTimestampIndex(0){
bsalomonf320e042015-02-17 15:09:34 -080079 SkDEBUGCODE(fCount = 0;)
bsalomon3f324322015-04-08 11:01:54 -070080 SkDEBUGCODE(fNewlyPurgeableResourceForValidation = NULL;)
81 this->resetFlushTimestamps();
bsalomon71cb0c22014-11-14 12:10:14 -080082}
83
bsalomon0ea80f42015-02-11 10:49:59 -080084GrResourceCache::~GrResourceCache() {
bsalomonc8dc1f72014-08-21 13:02:13 -070085 this->releaseAll();
bsalomon3f324322015-04-08 11:01:54 -070086 SkDELETE(fFlushTimestamps);
bsalomonc8dc1f72014-08-21 13:02:13 -070087}
88
bsalomon3f324322015-04-08 11:01:54 -070089void GrResourceCache::setLimits(int count, size_t bytes, int maxUnusedFlushes) {
bsalomon71cb0c22014-11-14 12:10:14 -080090 fMaxCount = count;
91 fMaxBytes = bytes;
bsalomon3f324322015-04-08 11:01:54 -070092 fMaxUnusedFlushes = maxUnusedFlushes;
93 this->resetFlushTimestamps();
bsalomon71cb0c22014-11-14 12:10:14 -080094 this->purgeAsNeeded();
95}
96
bsalomon3f324322015-04-08 11:01:54 -070097void GrResourceCache::resetFlushTimestamps() {
98 SkDELETE(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
bsalomon0ea80f42015-02-11 10:49:59 -0800119void GrResourceCache::insertResource(GrGpuResource* resource) {
bsalomon49f085d2014-09-05 13:34:00 -0700120 SkASSERT(resource);
bsalomon16961262014-08-26 14:01:07 -0700121 SkASSERT(!this->isInCache(resource));
bsalomonf320e042015-02-17 15:09:34 -0800122 SkASSERT(!resource->wasDestroyed());
123 SkASSERT(!resource->isPurgeable());
bsalomonddf30e62015-02-19 11:38:44 -0800124
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
bsalomonf320e042015-02-17 15:09:34 -0800129 this->addToNonpurgeableArray(resource);
bsalomon71cb0c22014-11-14 12:10:14 -0800130
bsalomondace19e2014-11-17 07:34:06 -0800131 size_t size = resource->gpuMemorySize();
bsalomonf320e042015-02-17 15:09:34 -0800132 SkDEBUGCODE(++fCount;)
bsalomon84c8e622014-11-17 09:33:27 -0800133 fBytes += size;
bsalomon82b1d622014-11-14 13:59:57 -0800134#if GR_CACHE_STATS
bsalomonf320e042015-02-17 15:09:34 -0800135 fHighWaterCount = SkTMax(this->getResourceCount(), fHighWaterCount);
bsalomon82b1d622014-11-14 13:59:57 -0800136 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
137#endif
bsalomon3582d3e2015-02-13 14:20:05 -0800138 if (resource->resourcePriv().isBudgeted()) {
bsalomondace19e2014-11-17 07:34:06 -0800139 ++fBudgetedCount;
140 fBudgetedBytes += size;
hendrikw876c3132015-03-04 10:33:49 -0800141 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
142 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
bsalomondace19e2014-11-17 07:34:06 -0800143#if GR_CACHE_STATS
144 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
145 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
146#endif
147 }
bsalomon3582d3e2015-02-13 14:20:05 -0800148 if (resource->resourcePriv().getScratchKey().isValid()) {
bsalomon84c8e622014-11-17 09:33:27 -0800149 SkASSERT(!resource->cacheAccess().isWrapped());
bsalomon3582d3e2015-02-13 14:20:05 -0800150 fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
bsalomon744998e2014-08-28 09:54:34 -0700151 }
bsalomon9f2d1572015-02-17 11:47:40 -0800152
bsalomon71cb0c22014-11-14 12:10:14 -0800153 this->purgeAsNeeded();
bsalomonc8dc1f72014-08-21 13:02:13 -0700154}
155
bsalomon0ea80f42015-02-11 10:49:59 -0800156void GrResourceCache::removeResource(GrGpuResource* resource) {
bsalomon9f2d1572015-02-17 11:47:40 -0800157 this->validate();
bsalomon16961262014-08-26 14:01:07 -0700158 SkASSERT(this->isInCache(resource));
bsalomondace19e2014-11-17 07:34:06 -0800159
bsalomon9f2d1572015-02-17 11:47:40 -0800160 if (resource->isPurgeable()) {
161 fPurgeableQueue.remove(resource);
bsalomonf320e042015-02-17 15:09:34 -0800162 } else {
163 this->removeFromNonpurgeableArray(resource);
bsalomon9f2d1572015-02-17 11:47:40 -0800164 }
165
bsalomondace19e2014-11-17 07:34:06 -0800166 size_t size = resource->gpuMemorySize();
bsalomonf320e042015-02-17 15:09:34 -0800167 SkDEBUGCODE(--fCount;)
bsalomondace19e2014-11-17 07:34:06 -0800168 fBytes -= size;
bsalomon3582d3e2015-02-13 14:20:05 -0800169 if (resource->resourcePriv().isBudgeted()) {
bsalomondace19e2014-11-17 07:34:06 -0800170 --fBudgetedCount;
171 fBudgetedBytes -= size;
hendrikw876c3132015-03-04 10:33:49 -0800172 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
173 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
bsalomondace19e2014-11-17 07:34:06 -0800174 }
175
bsalomon3582d3e2015-02-13 14:20:05 -0800176 if (resource->resourcePriv().getScratchKey().isValid()) {
177 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
bsalomon744998e2014-08-28 09:54:34 -0700178 }
bsalomon8718aaf2015-02-19 07:24:21 -0800179 if (resource->getUniqueKey().isValid()) {
180 fUniqueHash.remove(resource->getUniqueKey());
bsalomon8b79d232014-11-10 10:19:06 -0800181 }
bsalomonb436ed62014-11-17 12:15:56 -0800182 this->validate();
bsalomonc8dc1f72014-08-21 13:02:13 -0700183}
184
bsalomon0ea80f42015-02-11 10:49:59 -0800185void GrResourceCache::abandonAll() {
bsalomon71cb0c22014-11-14 12:10:14 -0800186 AutoValidate av(this);
187
bsalomonf320e042015-02-17 15:09:34 -0800188 while (fNonpurgeableResources.count()) {
189 GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
190 SkASSERT(!back->wasDestroyed());
191 back->cacheAccess().abandon();
bsalomonc8dc1f72014-08-21 13:02:13 -0700192 }
bsalomonf320e042015-02-17 15:09:34 -0800193
194 while (fPurgeableQueue.count()) {
195 GrGpuResource* top = fPurgeableQueue.peek();
196 SkASSERT(!top->wasDestroyed());
197 top->cacheAccess().abandon();
198 }
199
bsalomon744998e2014-08-28 09:54:34 -0700200 SkASSERT(!fScratchMap.count());
bsalomon8718aaf2015-02-19 07:24:21 -0800201 SkASSERT(!fUniqueHash.count());
bsalomonc8dc1f72014-08-21 13:02:13 -0700202 SkASSERT(!fCount);
bsalomonf320e042015-02-17 15:09:34 -0800203 SkASSERT(!this->getResourceCount());
bsalomondace19e2014-11-17 07:34:06 -0800204 SkASSERT(!fBytes);
205 SkASSERT(!fBudgetedCount);
206 SkASSERT(!fBudgetedBytes);
bsalomonc8dc1f72014-08-21 13:02:13 -0700207}
208
bsalomon0ea80f42015-02-11 10:49:59 -0800209void GrResourceCache::releaseAll() {
bsalomon71cb0c22014-11-14 12:10:14 -0800210 AutoValidate av(this);
211
bsalomonf320e042015-02-17 15:09:34 -0800212 while(fNonpurgeableResources.count()) {
213 GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
214 SkASSERT(!back->wasDestroyed());
215 back->cacheAccess().release();
bsalomonc8dc1f72014-08-21 13:02:13 -0700216 }
bsalomonf320e042015-02-17 15:09:34 -0800217
218 while (fPurgeableQueue.count()) {
219 GrGpuResource* top = fPurgeableQueue.peek();
220 SkASSERT(!top->wasDestroyed());
221 top->cacheAccess().release();
222 }
223
bsalomon744998e2014-08-28 09:54:34 -0700224 SkASSERT(!fScratchMap.count());
bsalomon8718aaf2015-02-19 07:24:21 -0800225 SkASSERT(!fUniqueHash.count());
bsalomonc8dc1f72014-08-21 13:02:13 -0700226 SkASSERT(!fCount);
bsalomonf320e042015-02-17 15:09:34 -0800227 SkASSERT(!this->getResourceCount());
bsalomondace19e2014-11-17 07:34:06 -0800228 SkASSERT(!fBytes);
229 SkASSERT(!fBudgetedCount);
230 SkASSERT(!fBudgetedBytes);
bsalomonc8dc1f72014-08-21 13:02:13 -0700231}
bsalomonbcf0a522014-10-08 08:40:09 -0700232
bsalomon0ea80f42015-02-11 10:49:59 -0800233class GrResourceCache::AvailableForScratchUse {
bsalomonbcf0a522014-10-08 08:40:09 -0700234public:
bsalomon000f8292014-10-15 19:04:14 -0700235 AvailableForScratchUse(bool rejectPendingIO) : fRejectPendingIO(rejectPendingIO) { }
bsalomonbcf0a522014-10-08 08:40:09 -0700236
237 bool operator()(const GrGpuResource* resource) const {
bsalomon12299ab2014-11-14 13:33:09 -0800238 if (resource->internalHasRef() || !resource->cacheAccess().isScratch()) {
bsalomon000f8292014-10-15 19:04:14 -0700239 return false;
bsalomonbcf0a522014-10-08 08:40:09 -0700240 }
bsalomon000f8292014-10-15 19:04:14 -0700241 return !fRejectPendingIO || !resource->internalHasPendingIO();
bsalomonbcf0a522014-10-08 08:40:09 -0700242 }
bsalomon1e2530b2014-10-09 09:57:18 -0700243
bsalomonbcf0a522014-10-08 08:40:09 -0700244private:
bsalomon000f8292014-10-15 19:04:14 -0700245 bool fRejectPendingIO;
bsalomonbcf0a522014-10-08 08:40:09 -0700246};
247
bsalomon0ea80f42015-02-11 10:49:59 -0800248GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& scratchKey,
bsalomon9f2d1572015-02-17 11:47:40 -0800249 uint32_t flags) {
bsalomon7775c852014-12-30 12:50:52 -0800250 SkASSERT(scratchKey.isValid());
bsalomon000f8292014-10-15 19:04:14 -0700251
bsalomon71cb0c22014-11-14 12:10:14 -0800252 GrGpuResource* resource;
bsalomon000f8292014-10-15 19:04:14 -0700253 if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFlag)) {
bsalomon71cb0c22014-11-14 12:10:14 -0800254 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true));
bsalomon000f8292014-10-15 19:04:14 -0700255 if (resource) {
bsalomon9f2d1572015-02-17 11:47:40 -0800256 this->refAndMakeResourceMRU(resource);
bsalomonb436ed62014-11-17 12:15:56 -0800257 this->validate();
258 return resource;
bsalomon000f8292014-10-15 19:04:14 -0700259 } 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 }
bsalomon71cb0c22014-11-14 12:10:14 -0800265 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(false));
266 if (resource) {
bsalomon9f2d1572015-02-17 11:47:40 -0800267 this->refAndMakeResourceMRU(resource);
bsalomonb436ed62014-11-17 12:15:56 -0800268 this->validate();
bsalomon71cb0c22014-11-14 12:10:14 -0800269 }
270 return resource;
bsalomonbcf0a522014-10-08 08:40:09 -0700271}
bsalomon8b79d232014-11-10 10:19:06 -0800272
bsalomon0ea80f42015-02-11 10:49:59 -0800273void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) {
bsalomon3582d3e2015-02-13 14:20:05 -0800274 SkASSERT(resource->resourcePriv().getScratchKey().isValid());
275 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
bsalomon10e23ca2014-11-25 05:52:06 -0800276}
277
bsalomonf99e9612015-02-19 08:24:16 -0800278void GrResourceCache::removeUniqueKey(GrGpuResource* resource) {
bsalomon3f324322015-04-08 11:01:54 -0700279 // 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.
bsalomonf99e9612015-02-19 08:24:16 -0800281 if (resource->getUniqueKey().isValid()) {
282 SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey()));
283 fUniqueHash.remove(resource->getUniqueKey());
284 }
285 resource->cacheAccess().removeUniqueKey();
286 this->validate();
bsalomon23e619c2015-02-06 11:54:28 -0800287}
288
bsalomonf99e9612015-02-19 08:24:16 -0800289void GrResourceCache::changeUniqueKey(GrGpuResource* resource, const GrUniqueKey& newKey) {
bsalomon8b79d232014-11-10 10:19:06 -0800290 SkASSERT(resource);
bsalomon71cb0c22014-11-14 12:10:14 -0800291 SkASSERT(this->isInCache(resource));
bsalomon8b79d232014-11-10 10:19:06 -0800292
bsalomonf99e9612015-02-19 08:24:16 -0800293 // 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()));
bsalomon8b79d232014-11-10 10:19:06 -0800298 }
299
bsalomonf99e9612015-02-19 08:24:16 -0800300 // 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
bsalomon71cb0c22014-11-14 12:10:14 -0800321 this->validate();
bsalomon8b79d232014-11-10 10:19:06 -0800322}
bsalomon71cb0c22014-11-14 12:10:14 -0800323
bsalomon9f2d1572015-02-17 11:47:40 -0800324void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) {
bsalomon71cb0c22014-11-14 12:10:14 -0800325 SkASSERT(resource);
326 SkASSERT(this->isInCache(resource));
bsalomonddf30e62015-02-19 11:38:44 -0800327
bsalomon9f2d1572015-02-17 11:47:40 -0800328 if (resource->isPurgeable()) {
329 // It's about to become unpurgeable.
330 fPurgeableQueue.remove(resource);
bsalomonf320e042015-02-17 15:09:34 -0800331 this->addToNonpurgeableArray(resource);
bsalomon9f2d1572015-02-17 11:47:40 -0800332 }
333 resource->ref();
bsalomonddf30e62015-02-19 11:38:44 -0800334
335 resource->cacheAccess().setTimestamp(this->getNextTimestamp());
bsalomonf320e042015-02-17 15:09:34 -0800336 this->validate();
bsalomon71cb0c22014-11-14 12:10:14 -0800337}
338
bsalomon3f324322015-04-08 11:01:54 -0700339void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t flags) {
bsalomon71cb0c22014-11-14 12:10:14 -0800340 SkASSERT(resource);
bsalomon3f324322015-04-08 11:01:54 -0700341 SkASSERT(!resource->wasDestroyed());
342 SkASSERT(flags);
bsalomon71cb0c22014-11-14 12:10:14 -0800343 SkASSERT(this->isInCache(resource));
bsalomon3f324322015-04-08 11:01:54 -0700344 // 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);
bsalomon71cb0c22014-11-14 12:10:14 -0800347
bsalomon3f324322015-04-08 11:01:54 -0700348 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());
bsalomonf320e042015-02-17 15:09:34 -0800367 this->removeFromNonpurgeableArray(resource);
bsalomon9f2d1572015-02-17 11:47:40 -0800368 fPurgeableQueue.insert(resource);
bsalomon71cb0c22014-11-14 12:10:14 -0800369
bsalomon9f2d1572015-02-17 11:47:40 -0800370 if (!resource->resourcePriv().isBudgeted()) {
bsalomonc2f35b72015-01-23 07:19:22 -0800371 // Check whether this resource could still be used as a scratch resource.
bsalomon9f2d1572015-02-17 11:47:40 -0800372 if (!resource->cacheAccess().isWrapped() &&
373 resource->resourcePriv().getScratchKey().isValid()) {
bsalomonc2f35b72015-01-23 07:19:22 -0800374 // We won't purge an existing resource to make room for this one.
bsalomonf320e042015-02-17 15:09:34 -0800375 if (fBudgetedCount < fMaxCount &&
376 fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes) {
bsalomon3582d3e2015-02-13 14:20:05 -0800377 resource->resourcePriv().makeBudgeted();
bsalomon9f2d1572015-02-17 11:47:40 -0800378 return;
bsalomonc2f35b72015-01-23 07:19:22 -0800379 }
bsalomonc2f35b72015-01-23 07:19:22 -0800380 }
381 } else {
bsalomon9f2d1572015-02-17 11:47:40 -0800382 // Purge the resource immediately if we're over budget
bsalomon8718aaf2015-02-19 07:24:21 -0800383 // Also purge if the resource has neither a valid scratch key nor a unique key.
bsalomon3582d3e2015-02-13 14:20:05 -0800384 bool noKey = !resource->resourcePriv().getScratchKey().isValid() &&
bsalomon8718aaf2015-02-19 07:24:21 -0800385 !resource->getUniqueKey().isValid();
bsalomonf320e042015-02-17 15:09:34 -0800386 if (!this->overBudget() && !noKey) {
bsalomon9f2d1572015-02-17 11:47:40 -0800387 return;
bsalomonc2f35b72015-01-23 07:19:22 -0800388 }
389 }
bsalomondace19e2014-11-17 07:34:06 -0800390
bsalomonf320e042015-02-17 15:09:34 -0800391 SkDEBUGCODE(int beforeCount = this->getResourceCount();)
bsalomon9f2d1572015-02-17 11:47:40 -0800392 resource->cacheAccess().release();
393 // We should at least free this resource, perhaps dependent resources as well.
bsalomonf320e042015-02-17 15:09:34 -0800394 SkASSERT(this->getResourceCount() < beforeCount);
bsalomon71cb0c22014-11-14 12:10:14 -0800395 this->validate();
396}
397
bsalomon0ea80f42015-02-11 10:49:59 -0800398void GrResourceCache::didChangeGpuMemorySize(const GrGpuResource* resource, size_t oldSize) {
bsalomon71cb0c22014-11-14 12:10:14 -0800399 // SkASSERT(!fPurging); GrPathRange increases size during flush. :(
400 SkASSERT(resource);
401 SkASSERT(this->isInCache(resource));
402
bsalomondace19e2014-11-17 07:34:06 -0800403 ptrdiff_t delta = resource->gpuMemorySize() - oldSize;
404
405 fBytes += delta;
bsalomon82b1d622014-11-14 13:59:57 -0800406#if GR_CACHE_STATS
407 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
408#endif
bsalomon3582d3e2015-02-13 14:20:05 -0800409 if (resource->resourcePriv().isBudgeted()) {
bsalomondace19e2014-11-17 07:34:06 -0800410 fBudgetedBytes += delta;
hendrikw876c3132015-03-04 10:33:49 -0800411 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
412 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
bsalomondace19e2014-11-17 07:34:06 -0800413#if GR_CACHE_STATS
414 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
415#endif
416 }
bsalomon71cb0c22014-11-14 12:10:14 -0800417
418 this->purgeAsNeeded();
419 this->validate();
420}
421
bsalomon0ea80f42015-02-11 10:49:59 -0800422void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) {
bsalomon84c8e622014-11-17 09:33:27 -0800423 SkASSERT(resource);
424 SkASSERT(this->isInCache(resource));
425
426 size_t size = resource->gpuMemorySize();
427
bsalomon3582d3e2015-02-13 14:20:05 -0800428 if (resource->resourcePriv().isBudgeted()) {
bsalomon84c8e622014-11-17 09:33:27 -0800429 ++fBudgetedCount;
430 fBudgetedBytes += size;
bsalomonafe30052015-01-16 07:32:33 -0800431#if GR_CACHE_STATS
432 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
433 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
434#endif
bsalomon84c8e622014-11-17 09:33:27 -0800435 this->purgeAsNeeded();
436 } else {
437 --fBudgetedCount;
438 fBudgetedBytes -= size;
439 }
hendrikw876c3132015-03-04 10:33:49 -0800440 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
441 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
bsalomon84c8e622014-11-17 09:33:27 -0800442
443 this->validate();
444}
445
bsalomon3f324322015-04-08 11:01:54 -0700446void GrResourceCache::purgeAsNeeded() {
447 SkTArray<GrUniqueKeyInvalidatedMessage> invalidKeyMsgs;
448 fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
449 if (invalidKeyMsgs.count()) {
450 this->processInvalidUniqueKeys(invalidKeyMsgs);
451 }
bsalomon71cb0c22014-11-14 12:10:14 -0800452
bsalomon3f324322015-04-08 11:01:54 -0700453 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()) {
bsalomon9f2d1572015-02-17 11:47:40 -0800472 GrGpuResource* resource = fPurgeableQueue.peek();
473 SkASSERT(resource->isPurgeable());
474 resource->cacheAccess().release();
bsalomon3f324322015-04-08 11:01:54 -0700475 stillOverbudget = this->overBudget();
bsalomon9f2d1572015-02-17 11:47:40 -0800476 }
bsalomon71cb0c22014-11-14 12:10:14 -0800477
bsalomonb436ed62014-11-17 12:15:56 -0800478 this->validate();
bsalomon9f2d1572015-02-17 11:47:40 -0800479
480 if (stillOverbudget) {
481 // Despite the purge we're still over budget. Call our over budget callback. If this frees
bsalomon3f324322015-04-08 11:01:54 -0700482 // any resources then we'll get notified and take appropriate action.
bsalomon9f2d1572015-02-17 11:47:40 -0800483 (*fOverBudgetCB)(fOverBudgetData);
484 this->validate();
485 }
bsalomon71cb0c22014-11-14 12:10:14 -0800486}
487
bsalomon0ea80f42015-02-11 10:49:59 -0800488void GrResourceCache::purgeAllUnlocked() {
bsalomon9f2d1572015-02-17 11:47:40 -0800489 // 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 }
bsalomon71cb0c22014-11-14 12:10:14 -0800496
bsalomonb436ed62014-11-17 12:15:56 -0800497 this->validate();
bsalomon71cb0c22014-11-14 12:10:14 -0800498}
499
bsalomon8718aaf2015-02-19 07:24:21 -0800500void GrResourceCache::processInvalidUniqueKeys(
501 const SkTArray<GrUniqueKeyInvalidatedMessage>& msgs) {
bsalomon23e619c2015-02-06 11:54:28 -0800502 for (int i = 0; i < msgs.count(); ++i) {
bsalomon8718aaf2015-02-19 07:24:21 -0800503 GrGpuResource* resource = this->findAndRefUniqueResource(msgs[i].key());
bsalomon23e619c2015-02-06 11:54:28 -0800504 if (resource) {
bsalomon8718aaf2015-02-19 07:24:21 -0800505 resource->resourcePriv().removeUniqueKey();
bsalomon3f324322015-04-08 11:01:54 -0700506 resource->unref(); // If this resource is now purgeable, the cache will be notified.
bsalomon23e619c2015-02-06 11:54:28 -0800507 }
508 }
509}
510
bsalomonf320e042015-02-17 15:09:34 -0800511void GrResourceCache::addToNonpurgeableArray(GrGpuResource* resource) {
512 int index = fNonpurgeableResources.count();
513 *fNonpurgeableResources.append() = resource;
514 *resource->cacheAccess().accessCacheIndex() = index;
515}
516
517void 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
bsalomonddf30e62015-02-19 11:38:44 -0800529uint32_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));
bsalomon3f324322015-04-08 11:01:54 -0700591
592 // The historical timestamps of flushes are now invalid.
593 this->resetFlushTimestamps();
bsalomonddf30e62015-02-19 11:38:44 -0800594 }
595 }
596 return fTimestamp++;
597}
598
bsalomon3f324322015-04-08 11:01:54 -0700599void 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
bsalomon71cb0c22014-11-14 12:10:14 -0800611#ifdef SK_DEBUG
bsalomon0ea80f42015-02-11 10:49:59 -0800612void GrResourceCache::validate() const {
bsalomonc2f35b72015-01-23 07:19:22 -0800613 // 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
bsalomonf320e042015-02-17 15:09:34 -0800620 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;
bsalomon8718aaf2015-02-19 07:24:21 -0800629 const UniqueHash* fUniqueHash;
bsalomon71cb0c22014-11-14 12:10:14 -0800630
bsalomonf320e042015-02-17 15:09:34 -0800631 Stats(const GrResourceCache* cache) {
632 memset(this, 0, sizeof(*this));
633 fScratchMap = &cache->fScratchMap;
bsalomon8718aaf2015-02-19 07:24:21 -0800634 fUniqueHash = &cache->fUniqueHash;
bsalomon71cb0c22014-11-14 12:10:14 -0800635 }
636
bsalomonf320e042015-02-17 15:09:34 -0800637 void update(GrGpuResource* resource) {
638 fBytes += resource->gpuMemorySize();
bsalomondace19e2014-11-17 07:34:06 -0800639
bsalomonf320e042015-02-17 15:09:34 -0800640 if (!resource->isPurgeable()) {
641 ++fLocked;
642 }
bsalomon9f2d1572015-02-17 11:47:40 -0800643
bsalomonf320e042015-02-17 15:09:34 -0800644 if (resource->cacheAccess().isScratch()) {
bsalomon8718aaf2015-02-19 07:24:21 -0800645 SkASSERT(!resource->getUniqueKey().isValid());
bsalomonf320e042015-02-17 15:09:34 -0800646 ++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() ||
bsalomon8718aaf2015-02-19 07:24:21 -0800651 resource->getUniqueKey().isValid());
bsalomonf320e042015-02-17 15:09:34 -0800652 ++fCouldBeScratch;
653 SkASSERT(fScratchMap->countForKey(resource->resourcePriv().getScratchKey()));
654 SkASSERT(!resource->cacheAccess().isWrapped());
655 }
bsalomon8718aaf2015-02-19 07:24:21 -0800656 const GrUniqueKey& uniqueKey = resource->getUniqueKey();
657 if (uniqueKey.isValid()) {
bsalomonf320e042015-02-17 15:09:34 -0800658 ++fContent;
bsalomon8718aaf2015-02-19 07:24:21 -0800659 SkASSERT(fUniqueHash->find(uniqueKey) == resource);
bsalomonf320e042015-02-17 15:09:34 -0800660 SkASSERT(!resource->cacheAccess().isWrapped());
661 SkASSERT(resource->resourcePriv().isBudgeted());
662 }
663
664 if (resource->resourcePriv().isBudgeted()) {
665 ++fBudgetedCount;
666 fBudgetedBytes += resource->gpuMemorySize();
667 }
bsalomon9f2d1572015-02-17 11:47:40 -0800668 }
bsalomonf320e042015-02-17 15:09:34 -0800669 };
670
671 Stats stats(this);
672
673 for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
bsalomon3f324322015-04-08 11:01:54 -0700674 SkASSERT(!fNonpurgeableResources[i]->isPurgeable() ||
675 fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
bsalomonf320e042015-02-17 15:09:34 -0800676 SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
677 SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
678 stats.update(fNonpurgeableResources[i]);
bsalomon71cb0c22014-11-14 12:10:14 -0800679 }
bsalomon9f2d1572015-02-17 11:47:40 -0800680 for (int i = 0; i < fPurgeableQueue.count(); ++i) {
681 SkASSERT(fPurgeableQueue.at(i)->isPurgeable());
bsalomonf320e042015-02-17 15:09:34 -0800682 SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i);
683 SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed());
684 stats.update(fPurgeableQueue.at(i));
bsalomon9f2d1572015-02-17 11:47:40 -0800685 }
686
bsalomonf320e042015-02-17 15:09:34 -0800687 SkASSERT(fCount == this->getResourceCount());
bsalomondace19e2014-11-17 07:34:06 -0800688 SkASSERT(fBudgetedCount <= fCount);
bsalomonf320e042015-02-17 15:09:34 -0800689 SkASSERT(fBudgetedBytes <= fBytes);
690 SkASSERT(stats.fBytes == fBytes);
691 SkASSERT(stats.fBudgetedBytes == fBudgetedBytes);
692 SkASSERT(stats.fBudgetedCount == fBudgetedCount);
bsalomon71cb0c22014-11-14 12:10:14 -0800693#if GR_CACHE_STATS
bsalomondace19e2014-11-17 07:34:06 -0800694 SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount);
695 SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes);
bsalomonf320e042015-02-17 15:09:34 -0800696 SkASSERT(fBytes <= fHighWaterBytes);
697 SkASSERT(fCount <= fHighWaterCount);
698 SkASSERT(fBudgetedBytes <= fBudgetedHighWaterBytes);
699 SkASSERT(fBudgetedCount <= fBudgetedHighWaterCount);
bsalomon71cb0c22014-11-14 12:10:14 -0800700#endif
bsalomon8718aaf2015-02-19 07:24:21 -0800701 SkASSERT(stats.fContent == fUniqueHash.count());
bsalomonf320e042015-02-17 15:09:34 -0800702 SkASSERT(stats.fScratch + stats.fCouldBeScratch == fScratchMap.count());
bsalomon71cb0c22014-11-14 12:10:14 -0800703
bsalomon3f324322015-04-08 11:01:54 -0700704 // This assertion is not currently valid because we can be in recursive notifyCntReachedZero()
bsalomon12299ab2014-11-14 13:33:09 -0800705 // calls. This will be fixed when subresource registration is explicit.
bsalomondace19e2014-11-17 07:34:06 -0800706 // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount;
bsalomon12299ab2014-11-14 13:33:09 -0800707 // SkASSERT(!overBudget || locked == count || fPurging);
bsalomon71cb0c22014-11-14 12:10:14 -0800708}
bsalomonf320e042015-02-17 15:09:34 -0800709
710bool 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
bsalomon71cb0c22014-11-14 12:10:14 -0800725#endif