blob: 982d45e3dd60dc73697db9021bf9d098b5912a45 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
reed@google.comac10a2d2010-12-22 21:39:39 +00002/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00003 * Copyright 2010 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.
reed@google.comac10a2d2010-12-22 21:39:39 +00007 */
8
9
epoger@google.comec3ed6a2011-07-28 14:26:00 +000010
bsalomon@google.com50398bf2011-07-26 20:45:30 +000011#include "GrResourceCache.h"
12#include "GrResource.h"
reed@google.comac10a2d2010-12-22 21:39:39 +000013
bsalomon@google.com50398bf2011-07-26 20:45:30 +000014GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource)
15 : fKey(key), fResource(resource) {
reed@google.comac10a2d2010-12-22 21:39:39 +000016 fLockCount = 0;
17 fPrev = fNext = NULL;
18
bsalomon@google.com50398bf2011-07-26 20:45:30 +000019 // we assume ownership of the resource, and will unref it when we die
20 GrAssert(resource);
reed@google.comac10a2d2010-12-22 21:39:39 +000021}
22
bsalomon@google.com50398bf2011-07-26 20:45:30 +000023GrResourceEntry::~GrResourceEntry() {
24 fResource->unref();
reed@google.comac10a2d2010-12-22 21:39:39 +000025}
26
27#if GR_DEBUG
bsalomon@google.com50398bf2011-07-26 20:45:30 +000028void GrResourceEntry::validate() const {
reed@google.comac10a2d2010-12-22 21:39:39 +000029 GrAssert(fLockCount >= 0);
bsalomon@google.com50398bf2011-07-26 20:45:30 +000030 GrAssert(fResource);
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +000031 GrAssert(fResource->getCacheEntry() == this);
bsalomon@google.com50398bf2011-07-26 20:45:30 +000032 fResource->validate();
reed@google.comac10a2d2010-12-22 21:39:39 +000033}
34#endif
35
36///////////////////////////////////////////////////////////////////////////////
37
bsalomon@google.com07fc0d12012-06-22 15:15:59 +000038GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) :
39 fMaxCount(maxCount),
40 fMaxBytes(maxBytes) {
reed@google.comac10a2d2010-12-22 21:39:39 +000041 fEntryCount = 0;
bsalomon@google.coma5a1da82011-08-05 14:02:41 +000042 fUnlockedEntryCount = 0;
reed@google.comac10a2d2010-12-22 21:39:39 +000043 fEntryBytes = 0;
44 fClientDetachedCount = 0;
45 fClientDetachedBytes = 0;
46
47 fHead = fTail = NULL;
bsalomon@google.coma5a1da82011-08-05 14:02:41 +000048 fPurging = false;
reed@google.comac10a2d2010-12-22 21:39:39 +000049}
50
bsalomon@google.com50398bf2011-07-26 20:45:30 +000051GrResourceCache::~GrResourceCache() {
52 GrAutoResourceCacheValidate atcv(this);
reed@google.com01804b42011-01-18 21:50:41 +000053
bsalomon@google.com8fe72472011-03-30 21:26:44 +000054 this->removeAll();
reed@google.comac10a2d2010-12-22 21:39:39 +000055}
56
bsalomon@google.com07fc0d12012-06-22 15:15:59 +000057void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{
58 if (maxResources) {
59 *maxResources = fMaxCount;
60 }
61 if (maxResourceBytes) {
62 *maxResourceBytes = fMaxBytes;
63 }
64}
reed@google.com01804b42011-01-18 21:50:41 +000065
bsalomon@google.com07fc0d12012-06-22 15:15:59 +000066void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) {
67 bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes);
68
69 fMaxCount = maxResources;
bsalomon@google.com50398bf2011-07-26 20:45:30 +000070 fMaxBytes = maxResourceBytes;
reed@google.com01804b42011-01-18 21:50:41 +000071
72 if (smaller) {
73 this->purgeAsNeeded();
74 }
75}
76
bsalomon@google.com50398bf2011-07-26 20:45:30 +000077void GrResourceCache::internalDetach(GrResourceEntry* entry,
reed@google.comac10a2d2010-12-22 21:39:39 +000078 bool clientDetach) {
bsalomon@google.com50398bf2011-07-26 20:45:30 +000079 GrResourceEntry* prev = entry->fPrev;
80 GrResourceEntry* next = entry->fNext;
reed@google.comac10a2d2010-12-22 21:39:39 +000081
82 if (prev) {
83 prev->fNext = next;
84 } else {
85 fHead = next;
86 }
87 if (next) {
88 next->fPrev = prev;
89 } else {
90 fTail = prev;
91 }
bsalomon@google.coma5a1da82011-08-05 14:02:41 +000092 if (!entry->isLocked()) {
93 --fUnlockedEntryCount;
94 }
reed@google.comac10a2d2010-12-22 21:39:39 +000095
robertphillips@google.com15c0fea2012-06-22 12:41:43 +000096 entry->fPrev = NULL;
97 entry->fNext = NULL;
98
reed@google.comac10a2d2010-12-22 21:39:39 +000099 // update our stats
100 if (clientDetach) {
101 fClientDetachedCount += 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000102 fClientDetachedBytes += entry->resource()->sizeInBytes();
reed@google.comac10a2d2010-12-22 21:39:39 +0000103 } else {
104 fEntryCount -= 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000105 fEntryBytes -= entry->resource()->sizeInBytes();
reed@google.comac10a2d2010-12-22 21:39:39 +0000106 }
107}
108
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000109void GrResourceCache::attachToHead(GrResourceEntry* entry,
reed@google.comac10a2d2010-12-22 21:39:39 +0000110 bool clientReattach) {
111 entry->fPrev = NULL;
112 entry->fNext = fHead;
113 if (fHead) {
114 fHead->fPrev = entry;
115 }
116 fHead = entry;
117 if (NULL == fTail) {
118 fTail = entry;
119 }
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000120 if (!entry->isLocked()) {
121 ++fUnlockedEntryCount;
122 }
reed@google.comac10a2d2010-12-22 21:39:39 +0000123
124 // update our stats
125 if (clientReattach) {
126 fClientDetachedCount -= 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000127 fClientDetachedBytes -= entry->resource()->sizeInBytes();
reed@google.comac10a2d2010-12-22 21:39:39 +0000128 } else {
129 fEntryCount += 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000130 fEntryBytes += entry->resource()->sizeInBytes();
reed@google.comac10a2d2010-12-22 21:39:39 +0000131 }
132}
133
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000134class GrResourceCache::Key {
135 typedef GrResourceEntry T;
reed@google.comac10a2d2010-12-22 21:39:39 +0000136
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000137 const GrResourceKey& fKey;
reed@google.comac10a2d2010-12-22 21:39:39 +0000138public:
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000139 Key(const GrResourceKey& key) : fKey(key) {}
reed@google.comac10a2d2010-12-22 21:39:39 +0000140
141 uint32_t getHash() const { return fKey.hashIndex(); }
reed@google.com01804b42011-01-18 21:50:41 +0000142
reed@google.comac10a2d2010-12-22 21:39:39 +0000143 static bool LT(const T& entry, const Key& key) {
144 return entry.key() < key.fKey;
145 }
146 static bool EQ(const T& entry, const Key& key) {
147 return entry.key() == key.fKey;
148 }
149#if GR_DEBUG
150 static uint32_t GetHash(const T& entry) {
151 return entry.key().hashIndex();
152 }
153 static bool LT(const T& a, const T& b) {
154 return a.key() < b.key();
155 }
156 static bool EQ(const T& a, const T& b) {
157 return a.key() == b.key();
158 }
159#endif
160};
161
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000162GrResource* GrResourceCache::findAndLock(const GrResourceKey& key,
163 LockType type) {
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000164 GrAutoResourceCacheValidate atcv(this);
reed@google.comac10a2d2010-12-22 21:39:39 +0000165
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000166 GrResourceEntry* entry = fCache.find(key);
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000167 if (NULL == entry) {
168 return NULL;
reed@google.comac10a2d2010-12-22 21:39:39 +0000169 }
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000170
171 this->internalDetach(entry, false);
172 // mark the entry as "busy" so it doesn't get purged
173 // do this between detach and attach for locked count tracking
174 if (kNested_LockType == type || !entry->isLocked()) {
175 entry->lock();
176 }
177 this->attachToHead(entry, false);
178
179 return entry->fResource;
reed@google.comac10a2d2010-12-22 21:39:39 +0000180}
181
bsalomon@google.comfb309512011-11-30 14:13:48 +0000182bool GrResourceCache::hasKey(const GrResourceKey& key) const {
183 return NULL != fCache.find(key);
184}
185
robertphillips@google.com15c0fea2012-06-22 12:41:43 +0000186GrResourceEntry* GrResourceCache::create(const GrResourceKey& key,
187 GrResource* resource,
robertphillips@google.com386319e2012-06-27 14:59:18 +0000188 bool lock,
189 bool clientReattach) {
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000190 // we don't expect to create new resources during a purge. In theory
191 // this could cause purgeAsNeeded() into an infinite loop (e.g.
192 // each resource destroyed creates and locks 2 resources and
193 // unlocks 1 thereby causing a new purge).
194 GrAssert(!fPurging);
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000195 GrAutoResourceCacheValidate atcv(this);
reed@google.comac10a2d2010-12-22 21:39:39 +0000196
tomhudson@google.comc377baf2012-07-09 20:17:56 +0000197 GrResourceEntry* entry = SkNEW_ARGS(GrResourceEntry, (key, resource));
reed@google.comac10a2d2010-12-22 21:39:39 +0000198
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000199 resource->setCacheEntry(entry);
200
robertphillips@google.com15c0fea2012-06-22 12:41:43 +0000201 if (lock) {
202 // mark the entry as "busy" so it doesn't get purged
203 // do this before attach for locked count tracking
204 entry->lock();
205 }
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000206
robertphillips@google.com386319e2012-06-27 14:59:18 +0000207 this->attachToHead(entry, clientReattach);
reed@google.comac10a2d2010-12-22 21:39:39 +0000208 fCache.insert(key, entry);
209
210#if GR_DUMP_TEXTURE_UPLOAD
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000211 GrPrintf("--- add resource to cache %p, count=%d bytes= %d %d\n",
212 entry, fEntryCount, resource->sizeInBytes(), fEntryBytes);
reed@google.comac10a2d2010-12-22 21:39:39 +0000213#endif
214
reed@google.comac10a2d2010-12-22 21:39:39 +0000215 this->purgeAsNeeded();
216 return entry;
217}
218
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000219void GrResourceCache::createAndLock(const GrResourceKey& key,
220 GrResource* resource) {
221 GrAssert(NULL == resource->getCacheEntry());
222 this->create(key, resource, true, false);
robertphillips@google.com15c0fea2012-06-22 12:41:43 +0000223}
224
225void GrResourceCache::attach(const GrResourceKey& key,
226 GrResource* resource) {
robertphillips@google.com1f47f4f2012-08-16 14:49:16 +0000227 GrAssert(NULL == resource->getCacheEntry());
robertphillips@google.com386319e2012-06-27 14:59:18 +0000228 this->create(key, resource, false, true);
robertphillips@google.com15c0fea2012-06-22 12:41:43 +0000229}
230
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000231void GrResourceCache::detach(GrResourceEntry* entry) {
bsalomon@google.come9a98942011-08-22 17:06:16 +0000232 GrAutoResourceCacheValidate atcv(this);
robertphillips@google.com15c0fea2012-06-22 12:41:43 +0000233 this->internalDetach(entry, true);
reed@google.comac10a2d2010-12-22 21:39:39 +0000234 fCache.remove(entry->fKey, entry);
235}
236
robertphillips@google.com15c0fea2012-06-22 12:41:43 +0000237void GrResourceCache::freeEntry(GrResourceEntry* entry) {
238 GrAssert(NULL == entry->fNext && NULL == entry->fPrev);
239 delete entry;
240}
241
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000242void GrResourceCache::reattachAndUnlock(GrResourceEntry* entry) {
bsalomon@google.com60879752011-09-15 20:43:53 +0000243 GrAutoResourceCacheValidate atcv(this);
244 if (entry->resource()->isValid()) {
245 attachToHead(entry, true);
246 fCache.insert(entry->key(), entry);
247 } else {
248 // If the resource went invalid while it was detached then purge it
249 // This can happen when a 3D context was lost,
250 // the client called GrContext::contextDestroyed() to notify Gr,
251 // and then later an SkGpuDevice's destructor releases its backing
252 // texture (which was invalidated at contextDestroyed time).
253 fClientDetachedCount -= 1;
254 fEntryCount -= 1;
255 size_t size = entry->resource()->sizeInBytes();
256 fClientDetachedBytes -= size;
257 fEntryBytes -= size;
258 }
259 this->unlock(entry);
reed@google.comac10a2d2010-12-22 21:39:39 +0000260}
261
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000262void GrResourceCache::unlock(GrResourceEntry* entry) {
263 GrAutoResourceCacheValidate atcv(this);
reed@google.com01804b42011-01-18 21:50:41 +0000264
reed@google.comac10a2d2010-12-22 21:39:39 +0000265 GrAssert(entry);
266 GrAssert(entry->isLocked());
267 GrAssert(fCache.find(entry->key()));
268
269 entry->unlock();
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000270 if (!entry->isLocked()) {
271 ++fUnlockedEntryCount;
272 }
reed@google.comac10a2d2010-12-22 21:39:39 +0000273 this->purgeAsNeeded();
274}
275
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000276/**
277 * Destroying a resource may potentially trigger the unlock of additional
278 * resources which in turn will trigger a nested purge. We block the nested
279 * purge using the fPurging variable. However, the initial purge will keep
280 * looping until either all resources in the cache are unlocked or we've met
281 * the budget. There is an assertion in createAndLock to check against a
282 * resource's destructor inserting new resources into the cache. If these
283 * new resources were unlocked before purgeAsNeeded completed it could
284 * potentially make purgeAsNeeded loop infinitely.
285 */
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000286void GrResourceCache::purgeAsNeeded() {
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000287 if (!fPurging) {
288 fPurging = true;
289 bool withinBudget = false;
290 do {
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000291 GrResourceEntry* entry = fTail;
292 while (entry && fUnlockedEntryCount) {
bsalomon@google.come9a98942011-08-22 17:06:16 +0000293 GrAutoResourceCacheValidate atcv(this);
bsalomon@google.com07fc0d12012-06-22 15:15:59 +0000294 if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) {
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000295 withinBudget = true;
296 break;
297 }
reed@google.com01804b42011-01-18 21:50:41 +0000298
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000299 GrResourceEntry* prev = entry->fPrev;
300 if (!entry->isLocked()) {
301 // remove from our cache
302 fCache.remove(entry->fKey, entry);
reed@google.comac10a2d2010-12-22 21:39:39 +0000303
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000304 // remove from our llist
305 this->internalDetach(entry, false);
reed@google.com01804b42011-01-18 21:50:41 +0000306
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000307 #if GR_DUMP_TEXTURE_UPLOAD
308 GrPrintf("--- ~resource from cache %p [%d %d]\n",
309 entry->resource(),
310 entry->resource()->width(),
311 entry->resource()->height());
312 #endif
313 delete entry;
314 }
315 entry = prev;
316 }
317 } while (!withinBudget && fUnlockedEntryCount);
318 fPurging = false;
reed@google.comac10a2d2010-12-22 21:39:39 +0000319 }
320}
321
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000322void GrResourceCache::removeAll() {
bsalomon@google.come9a98942011-08-22 17:06:16 +0000323 GrAutoResourceCacheValidate atcv(this);
reed@google.com01804b42011-01-18 21:50:41 +0000324
bsalomon@google.come9a98942011-08-22 17:06:16 +0000325 // we can have one GrResource holding a lock on another
326 // so we don't want to just do a simple loop kicking each
327 // entry out. Instead change the budget and purge.
reed@google.comac10a2d2010-12-22 21:39:39 +0000328
bsalomon@google.come9a98942011-08-22 17:06:16 +0000329 int savedMaxBytes = fMaxBytes;
bsalomon@google.com07fc0d12012-06-22 15:15:59 +0000330 int savedMaxCount = fMaxCount;
331 fMaxBytes = (size_t) -1;
332 fMaxCount = 0;
bsalomon@google.come9a98942011-08-22 17:06:16 +0000333 this->purgeAsNeeded();
334
twiz@google.com0ec107f2012-02-21 19:15:53 +0000335#if GR_DEBUG
bsalomon@google.come9a98942011-08-22 17:06:16 +0000336 GrAssert(!fUnlockedEntryCount);
twiz@google.com0ec107f2012-02-21 19:15:53 +0000337 if (!fCache.count()) {
338 // Items may have been detached from the cache (such as the backing
339 // texture for an SkGpuDevice). The above purge would not have removed
340 // them.
341 GrAssert(fEntryCount == fClientDetachedCount);
342 GrAssert(fEntryBytes == fClientDetachedBytes);
343 GrAssert(NULL == fHead);
344 GrAssert(NULL == fTail);
345 }
346#endif
bsalomon@google.come9a98942011-08-22 17:06:16 +0000347
348 fMaxBytes = savedMaxBytes;
bsalomon@google.com07fc0d12012-06-22 15:15:59 +0000349 fMaxCount = savedMaxCount;
reed@google.comac10a2d2010-12-22 21:39:39 +0000350}
351
352///////////////////////////////////////////////////////////////////////////////
353
354#if GR_DEBUG
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000355static int countMatches(const GrResourceEntry* head, const GrResourceEntry* target) {
356 const GrResourceEntry* entry = head;
reed@google.comac10a2d2010-12-22 21:39:39 +0000357 int count = 0;
358 while (entry) {
359 if (target == entry) {
360 count += 1;
361 }
362 entry = entry->next();
363 }
364 return count;
365}
366
reed@google.comb89a6432011-02-07 13:20:30 +0000367#if GR_DEBUG
368static bool both_zero_or_nonzero(int count, size_t bytes) {
369 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0);
370}
371#endif
372
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000373void GrResourceCache::validate() const {
reed@google.comac10a2d2010-12-22 21:39:39 +0000374 GrAssert(!fHead == !fTail);
reed@google.comb89a6432011-02-07 13:20:30 +0000375 GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes));
376 GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes));
reed@google.comac10a2d2010-12-22 21:39:39 +0000377 GrAssert(fClientDetachedBytes <= fEntryBytes);
378 GrAssert(fClientDetachedCount <= fEntryCount);
379 GrAssert((fEntryCount - fClientDetachedCount) == fCache.count());
reed@google.com01804b42011-01-18 21:50:41 +0000380
reed@google.comac10a2d2010-12-22 21:39:39 +0000381 fCache.validate();
382
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000383 GrResourceEntry* entry = fHead;
reed@google.comac10a2d2010-12-22 21:39:39 +0000384 int count = 0;
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000385 int unlockCount = 0;
reed@google.comac10a2d2010-12-22 21:39:39 +0000386 size_t bytes = 0;
387 while (entry) {
388 entry->validate();
389 GrAssert(fCache.find(entry->key()));
390 count += 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000391 bytes += entry->resource()->sizeInBytes();
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000392 if (!entry->isLocked()) {
393 unlockCount += 1;
394 }
reed@google.comac10a2d2010-12-22 21:39:39 +0000395 entry = entry->fNext;
396 }
397 GrAssert(count == fEntryCount - fClientDetachedCount);
398 GrAssert(bytes == fEntryBytes - fClientDetachedBytes);
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000399 GrAssert(unlockCount == fUnlockedEntryCount);
reed@google.comac10a2d2010-12-22 21:39:39 +0000400
401 count = 0;
402 for (entry = fTail; entry; entry = entry->fPrev) {
403 count += 1;
404 }
405 GrAssert(count == fEntryCount - fClientDetachedCount);
406
407 for (int i = 0; i < count; i++) {
408 int matches = countMatches(fHead, fCache.getArray()[i]);
409 GrAssert(1 == matches);
410 }
411}
412#endif