blob: 6a6c16697ac7130795177a673effac567dd0eb7d [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);
31 fResource->validate();
reed@google.comac10a2d2010-12-22 21:39:39 +000032}
33#endif
34
35///////////////////////////////////////////////////////////////////////////////
36
bsalomon@google.com8f7e1da2012-06-21 19:48:32 +000037GrResourceCache::GrResourceCache(size_t maxBytes) {
38 fMaxBytes = maxBytes;
reed@google.comac10a2d2010-12-22 21:39:39 +000039 fEntryCount = 0;
bsalomon@google.coma5a1da82011-08-05 14:02:41 +000040 fUnlockedEntryCount = 0;
reed@google.comac10a2d2010-12-22 21:39:39 +000041 fEntryBytes = 0;
42 fClientDetachedCount = 0;
43 fClientDetachedBytes = 0;
44
45 fHead = fTail = NULL;
bsalomon@google.coma5a1da82011-08-05 14:02:41 +000046 fPurging = false;
reed@google.comac10a2d2010-12-22 21:39:39 +000047}
48
bsalomon@google.com50398bf2011-07-26 20:45:30 +000049GrResourceCache::~GrResourceCache() {
50 GrAutoResourceCacheValidate atcv(this);
reed@google.com01804b42011-01-18 21:50:41 +000051
bsalomon@google.com8fe72472011-03-30 21:26:44 +000052 this->removeAll();
reed@google.comac10a2d2010-12-22 21:39:39 +000053}
54
bsalomon@google.com8f7e1da2012-06-21 19:48:32 +000055void GrResourceCache::setBudget(size_t maxResourceBytes) {
56 bool smaller = maxResourceBytes < fMaxBytes;
reed@google.com01804b42011-01-18 21:50:41 +000057
bsalomon@google.com50398bf2011-07-26 20:45:30 +000058 fMaxBytes = maxResourceBytes;
reed@google.com01804b42011-01-18 21:50:41 +000059
60 if (smaller) {
61 this->purgeAsNeeded();
62 }
63}
64
bsalomon@google.com50398bf2011-07-26 20:45:30 +000065void GrResourceCache::internalDetach(GrResourceEntry* entry,
reed@google.comac10a2d2010-12-22 21:39:39 +000066 bool clientDetach) {
bsalomon@google.com50398bf2011-07-26 20:45:30 +000067 GrResourceEntry* prev = entry->fPrev;
68 GrResourceEntry* next = entry->fNext;
reed@google.comac10a2d2010-12-22 21:39:39 +000069
70 if (prev) {
71 prev->fNext = next;
72 } else {
73 fHead = next;
74 }
75 if (next) {
76 next->fPrev = prev;
77 } else {
78 fTail = prev;
79 }
bsalomon@google.coma5a1da82011-08-05 14:02:41 +000080 if (!entry->isLocked()) {
81 --fUnlockedEntryCount;
82 }
reed@google.comac10a2d2010-12-22 21:39:39 +000083
84 // update our stats
85 if (clientDetach) {
86 fClientDetachedCount += 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +000087 fClientDetachedBytes += entry->resource()->sizeInBytes();
reed@google.comac10a2d2010-12-22 21:39:39 +000088 } else {
89 fEntryCount -= 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +000090 fEntryBytes -= entry->resource()->sizeInBytes();
reed@google.comac10a2d2010-12-22 21:39:39 +000091 }
92}
93
bsalomon@google.com50398bf2011-07-26 20:45:30 +000094void GrResourceCache::attachToHead(GrResourceEntry* entry,
reed@google.comac10a2d2010-12-22 21:39:39 +000095 bool clientReattach) {
96 entry->fPrev = NULL;
97 entry->fNext = fHead;
98 if (fHead) {
99 fHead->fPrev = entry;
100 }
101 fHead = entry;
102 if (NULL == fTail) {
103 fTail = entry;
104 }
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000105 if (!entry->isLocked()) {
106 ++fUnlockedEntryCount;
107 }
reed@google.comac10a2d2010-12-22 21:39:39 +0000108
109 // update our stats
110 if (clientReattach) {
111 fClientDetachedCount -= 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000112 fClientDetachedBytes -= entry->resource()->sizeInBytes();
reed@google.comac10a2d2010-12-22 21:39:39 +0000113 } else {
114 fEntryCount += 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000115 fEntryBytes += entry->resource()->sizeInBytes();
reed@google.comac10a2d2010-12-22 21:39:39 +0000116 }
117}
118
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000119class GrResourceCache::Key {
120 typedef GrResourceEntry T;
reed@google.comac10a2d2010-12-22 21:39:39 +0000121
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000122 const GrResourceKey& fKey;
reed@google.comac10a2d2010-12-22 21:39:39 +0000123public:
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000124 Key(const GrResourceKey& key) : fKey(key) {}
reed@google.comac10a2d2010-12-22 21:39:39 +0000125
126 uint32_t getHash() const { return fKey.hashIndex(); }
reed@google.com01804b42011-01-18 21:50:41 +0000127
reed@google.comac10a2d2010-12-22 21:39:39 +0000128 static bool LT(const T& entry, const Key& key) {
129 return entry.key() < key.fKey;
130 }
131 static bool EQ(const T& entry, const Key& key) {
132 return entry.key() == key.fKey;
133 }
134#if GR_DEBUG
135 static uint32_t GetHash(const T& entry) {
136 return entry.key().hashIndex();
137 }
138 static bool LT(const T& a, const T& b) {
139 return a.key() < b.key();
140 }
141 static bool EQ(const T& a, const T& b) {
142 return a.key() == b.key();
143 }
144#endif
145};
146
bsalomon@google.com558a75b2011-08-08 17:01:14 +0000147GrResourceEntry* GrResourceCache::findAndLock(const GrResourceKey& key,
148 LockType type) {
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000149 GrAutoResourceCacheValidate atcv(this);
reed@google.comac10a2d2010-12-22 21:39:39 +0000150
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000151 GrResourceEntry* entry = fCache.find(key);
reed@google.comac10a2d2010-12-22 21:39:39 +0000152 if (entry) {
153 this->internalDetach(entry, false);
reed@google.comac10a2d2010-12-22 21:39:39 +0000154 // mark the entry as "busy" so it doesn't get purged
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000155 // do this between detach and attach for locked count tracking
bsalomon@google.com558a75b2011-08-08 17:01:14 +0000156 if (kNested_LockType == type || !entry->isLocked()) {
157 entry->lock();
158 }
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000159 this->attachToHead(entry, false);
reed@google.comac10a2d2010-12-22 21:39:39 +0000160 }
161 return entry;
162}
163
bsalomon@google.comfb309512011-11-30 14:13:48 +0000164bool GrResourceCache::hasKey(const GrResourceKey& key) const {
165 return NULL != fCache.find(key);
166}
167
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000168GrResourceEntry* GrResourceCache::createAndLock(const GrResourceKey& key,
169 GrResource* resource) {
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000170 // we don't expect to create new resources during a purge. In theory
171 // this could cause purgeAsNeeded() into an infinite loop (e.g.
172 // each resource destroyed creates and locks 2 resources and
173 // unlocks 1 thereby causing a new purge).
174 GrAssert(!fPurging);
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000175 GrAutoResourceCacheValidate atcv(this);
reed@google.comac10a2d2010-12-22 21:39:39 +0000176
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000177 GrResourceEntry* entry = new GrResourceEntry(key, resource);
reed@google.comac10a2d2010-12-22 21:39:39 +0000178
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000179 // mark the entry as "busy" so it doesn't get purged
180 // do this before attach for locked count tracking
181 entry->lock();
182
reed@google.comac10a2d2010-12-22 21:39:39 +0000183 this->attachToHead(entry, false);
184 fCache.insert(key, entry);
185
186#if GR_DUMP_TEXTURE_UPLOAD
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000187 GrPrintf("--- add resource to cache %p, count=%d bytes= %d %d\n",
188 entry, fEntryCount, resource->sizeInBytes(), fEntryBytes);
reed@google.comac10a2d2010-12-22 21:39:39 +0000189#endif
190
reed@google.comac10a2d2010-12-22 21:39:39 +0000191 this->purgeAsNeeded();
192 return entry;
193}
194
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000195void GrResourceCache::detach(GrResourceEntry* entry) {
bsalomon@google.come9a98942011-08-22 17:06:16 +0000196 GrAutoResourceCacheValidate atcv(this);
reed@google.comac10a2d2010-12-22 21:39:39 +0000197 internalDetach(entry, true);
198 fCache.remove(entry->fKey, entry);
199}
200
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000201void GrResourceCache::reattachAndUnlock(GrResourceEntry* entry) {
bsalomon@google.com60879752011-09-15 20:43:53 +0000202 GrAutoResourceCacheValidate atcv(this);
203 if (entry->resource()->isValid()) {
204 attachToHead(entry, true);
205 fCache.insert(entry->key(), entry);
206 } else {
207 // If the resource went invalid while it was detached then purge it
208 // This can happen when a 3D context was lost,
209 // the client called GrContext::contextDestroyed() to notify Gr,
210 // and then later an SkGpuDevice's destructor releases its backing
211 // texture (which was invalidated at contextDestroyed time).
212 fClientDetachedCount -= 1;
213 fEntryCount -= 1;
214 size_t size = entry->resource()->sizeInBytes();
bsalomon@google.com8f7e1da2012-06-21 19:48:32 +0000215 GrAssert(size > 0);
bsalomon@google.com60879752011-09-15 20:43:53 +0000216 fClientDetachedBytes -= size;
217 fEntryBytes -= size;
218 }
219 this->unlock(entry);
reed@google.comac10a2d2010-12-22 21:39:39 +0000220}
221
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000222void GrResourceCache::unlock(GrResourceEntry* entry) {
223 GrAutoResourceCacheValidate atcv(this);
reed@google.com01804b42011-01-18 21:50:41 +0000224
reed@google.comac10a2d2010-12-22 21:39:39 +0000225 GrAssert(entry);
226 GrAssert(entry->isLocked());
227 GrAssert(fCache.find(entry->key()));
228
229 entry->unlock();
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000230 if (!entry->isLocked()) {
231 ++fUnlockedEntryCount;
232 }
reed@google.comac10a2d2010-12-22 21:39:39 +0000233 this->purgeAsNeeded();
234}
235
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000236/**
237 * Destroying a resource may potentially trigger the unlock of additional
238 * resources which in turn will trigger a nested purge. We block the nested
239 * purge using the fPurging variable. However, the initial purge will keep
240 * looping until either all resources in the cache are unlocked or we've met
241 * the budget. There is an assertion in createAndLock to check against a
242 * resource's destructor inserting new resources into the cache. If these
243 * new resources were unlocked before purgeAsNeeded completed it could
244 * potentially make purgeAsNeeded loop infinitely.
245 */
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000246void GrResourceCache::purgeAsNeeded() {
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000247 if (!fPurging) {
248 fPurging = true;
249 bool withinBudget = false;
250 do {
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000251 GrResourceEntry* entry = fTail;
252 while (entry && fUnlockedEntryCount) {
bsalomon@google.come9a98942011-08-22 17:06:16 +0000253 GrAutoResourceCacheValidate atcv(this);
bsalomon@google.com8f7e1da2012-06-21 19:48:32 +0000254 if (fEntryBytes <= fMaxBytes) {
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000255 withinBudget = true;
256 break;
257 }
reed@google.com01804b42011-01-18 21:50:41 +0000258
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000259 GrResourceEntry* prev = entry->fPrev;
260 if (!entry->isLocked()) {
261 // remove from our cache
262 fCache.remove(entry->fKey, entry);
reed@google.comac10a2d2010-12-22 21:39:39 +0000263
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000264 // remove from our llist
265 this->internalDetach(entry, false);
reed@google.com01804b42011-01-18 21:50:41 +0000266
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000267 #if GR_DUMP_TEXTURE_UPLOAD
268 GrPrintf("--- ~resource from cache %p [%d %d]\n",
269 entry->resource(),
270 entry->resource()->width(),
271 entry->resource()->height());
272 #endif
273 delete entry;
274 }
275 entry = prev;
276 }
277 } while (!withinBudget && fUnlockedEntryCount);
278 fPurging = false;
reed@google.comac10a2d2010-12-22 21:39:39 +0000279 }
280}
281
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000282void GrResourceCache::removeAll() {
bsalomon@google.come9a98942011-08-22 17:06:16 +0000283 GrAutoResourceCacheValidate atcv(this);
reed@google.com01804b42011-01-18 21:50:41 +0000284
bsalomon@google.come9a98942011-08-22 17:06:16 +0000285 // we can have one GrResource holding a lock on another
286 // so we don't want to just do a simple loop kicking each
287 // entry out. Instead change the budget and purge.
reed@google.comac10a2d2010-12-22 21:39:39 +0000288
bsalomon@google.come9a98942011-08-22 17:06:16 +0000289 int savedMaxBytes = fMaxBytes;
bsalomon@google.com8f7e1da2012-06-21 19:48:32 +0000290 fMaxBytes = 0;
bsalomon@google.come9a98942011-08-22 17:06:16 +0000291 this->purgeAsNeeded();
292
twiz@google.com0ec107f2012-02-21 19:15:53 +0000293#if GR_DEBUG
bsalomon@google.come9a98942011-08-22 17:06:16 +0000294 GrAssert(!fUnlockedEntryCount);
twiz@google.com0ec107f2012-02-21 19:15:53 +0000295 if (!fCache.count()) {
296 // Items may have been detached from the cache (such as the backing
297 // texture for an SkGpuDevice). The above purge would not have removed
298 // them.
299 GrAssert(fEntryCount == fClientDetachedCount);
300 GrAssert(fEntryBytes == fClientDetachedBytes);
301 GrAssert(NULL == fHead);
302 GrAssert(NULL == fTail);
303 }
304#endif
bsalomon@google.come9a98942011-08-22 17:06:16 +0000305
306 fMaxBytes = savedMaxBytes;
reed@google.comac10a2d2010-12-22 21:39:39 +0000307}
308
309///////////////////////////////////////////////////////////////////////////////
310
311#if GR_DEBUG
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000312static int countMatches(const GrResourceEntry* head, const GrResourceEntry* target) {
313 const GrResourceEntry* entry = head;
reed@google.comac10a2d2010-12-22 21:39:39 +0000314 int count = 0;
315 while (entry) {
316 if (target == entry) {
317 count += 1;
318 }
319 entry = entry->next();
320 }
321 return count;
322}
323
reed@google.comb89a6432011-02-07 13:20:30 +0000324#if GR_DEBUG
325static bool both_zero_or_nonzero(int count, size_t bytes) {
326 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0);
327}
328#endif
329
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000330void GrResourceCache::validate() const {
reed@google.comac10a2d2010-12-22 21:39:39 +0000331 GrAssert(!fHead == !fTail);
reed@google.comb89a6432011-02-07 13:20:30 +0000332 GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes));
333 GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes));
reed@google.comac10a2d2010-12-22 21:39:39 +0000334 GrAssert(fClientDetachedBytes <= fEntryBytes);
335 GrAssert(fClientDetachedCount <= fEntryCount);
336 GrAssert((fEntryCount - fClientDetachedCount) == fCache.count());
reed@google.com01804b42011-01-18 21:50:41 +0000337
reed@google.comac10a2d2010-12-22 21:39:39 +0000338 fCache.validate();
339
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000340 GrResourceEntry* entry = fHead;
reed@google.comac10a2d2010-12-22 21:39:39 +0000341 int count = 0;
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000342 int unlockCount = 0;
reed@google.comac10a2d2010-12-22 21:39:39 +0000343 size_t bytes = 0;
344 while (entry) {
345 entry->validate();
346 GrAssert(fCache.find(entry->key()));
347 count += 1;
bsalomon@google.com50398bf2011-07-26 20:45:30 +0000348 bytes += entry->resource()->sizeInBytes();
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000349 if (!entry->isLocked()) {
350 unlockCount += 1;
351 }
reed@google.comac10a2d2010-12-22 21:39:39 +0000352 entry = entry->fNext;
353 }
354 GrAssert(count == fEntryCount - fClientDetachedCount);
355 GrAssert(bytes == fEntryBytes - fClientDetachedBytes);
bsalomon@google.coma5a1da82011-08-05 14:02:41 +0000356 GrAssert(unlockCount == fUnlockedEntryCount);
reed@google.comac10a2d2010-12-22 21:39:39 +0000357
358 count = 0;
359 for (entry = fTail; entry; entry = entry->fPrev) {
360 count += 1;
361 }
362 GrAssert(count == fEntryCount - fClientDetachedCount);
363
364 for (int i = 0; i < count; i++) {
365 int matches = countMatches(fHead, fCache.getArray()[i]);
366 GrAssert(1 == matches);
367 }
368}
369#endif