GrResourceCache.cpp revision f2e93fc989129f11881919de99a3b8f12081beae
1
2/*
3 * 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.
7 */
8
9
10
11#include "GrResourceCache.h"
12#include "GrResource.h"
13
14GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource)
15        : fKey(key), fResource(resource) {
16    fLockCount = 0;
17
18    // we assume ownership of the resource, and will unref it when we die
19    GrAssert(resource);
20    resource->ref();
21}
22
23GrResourceEntry::~GrResourceEntry() {
24    fResource->setCacheEntry(NULL);
25    fResource->unref();
26}
27
28#if GR_DEBUG
29void GrResourceEntry::validate() const {
30    GrAssert(fLockCount >= 0);
31    GrAssert(fResource);
32    GrAssert(fResource->getCacheEntry() == this);
33    fResource->validate();
34}
35#endif
36
37///////////////////////////////////////////////////////////////////////////////
38
39class GrResourceCache::Key {
40    typedef GrResourceEntry T;
41
42    const GrResourceKey& fKey;
43public:
44    Key(const GrResourceKey& key) : fKey(key) {}
45
46    uint32_t getHash() const { return fKey.hashIndex(); }
47
48    static bool LT(const T& entry, const Key& key) {
49        return entry.key() < key.fKey;
50    }
51    static bool EQ(const T& entry, const Key& key) {
52        return entry.key() == key.fKey;
53    }
54#if GR_DEBUG
55    static uint32_t GetHash(const T& entry) {
56        return entry.key().hashIndex();
57    }
58    static bool LT(const T& a, const T& b) {
59        return a.key() < b.key();
60    }
61    static bool EQ(const T& a, const T& b) {
62        return a.key() == b.key();
63    }
64#endif
65};
66
67///////////////////////////////////////////////////////////////////////////////
68
69GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) :
70        fMaxCount(maxCount),
71        fMaxBytes(maxBytes) {
72#if GR_CACHE_STATS
73    fHighWaterEntryCount          = 0;
74    fHighWaterUnlockedEntryCount  = 0;
75    fHighWaterEntryBytes          = 0;
76    fHighWaterClientDetachedCount = 0;
77    fHighWaterClientDetachedBytes = 0;
78#endif
79
80    fEntryCount                   = 0;
81    fUnlockedEntryCount           = 0;
82    fEntryBytes                   = 0;
83    fClientDetachedCount          = 0;
84    fClientDetachedBytes          = 0;
85
86    fPurging = false;
87}
88
89GrResourceCache::~GrResourceCache() {
90    GrAutoResourceCacheValidate atcv(this);
91
92    EntryList::Iter iter;
93
94    // Unlike the removeAll, here we really remove everything, including locked resources.
95    while (GrResourceEntry* entry = fList.head()) {
96        GrAutoResourceCacheValidate atcv(this);
97
98        // remove from our cache
99        fCache.remove(entry->fKey, entry);
100
101        // remove from our llist
102        this->internalDetach(entry, false);
103
104        delete entry;
105    }
106}
107
108void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{
109    if (maxResources) {
110        *maxResources = fMaxCount;
111    }
112    if (maxResourceBytes) {
113        *maxResourceBytes = fMaxBytes;
114    }
115}
116
117void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) {
118    bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes);
119
120    fMaxCount = maxResources;
121    fMaxBytes = maxResourceBytes;
122
123    if (smaller) {
124        this->purgeAsNeeded();
125    }
126}
127
128void GrResourceCache::internalDetach(GrResourceEntry* entry,
129                                    bool clientDetach) {
130    fList.remove(entry);
131
132    if (!entry->isLocked()) {
133        --fUnlockedEntryCount;
134    }
135
136    // update our stats
137    if (clientDetach) {
138        fClientDetachedCount += 1;
139        fClientDetachedBytes += entry->resource()->sizeInBytes();
140
141#if GR_CACHE_STATS
142        if (fHighWaterClientDetachedCount < fClientDetachedCount) {
143            fHighWaterClientDetachedCount = fClientDetachedCount;
144        }
145        if (fHighWaterClientDetachedBytes < fClientDetachedBytes) {
146            fHighWaterClientDetachedBytes = fClientDetachedBytes;
147        }
148#endif
149
150    } else {
151        fEntryCount -= 1;
152        fEntryBytes -= entry->resource()->sizeInBytes();
153    }
154}
155
156void GrResourceCache::attachToHead(GrResourceEntry* entry,
157                                   bool clientReattach) {
158    fList.addToHead(entry);
159
160    if (!entry->isLocked()) {
161        ++fUnlockedEntryCount;
162#if GR_CACHE_STATS
163        if (fHighWaterUnlockedEntryCount < fUnlockedEntryCount) {
164            fHighWaterUnlockedEntryCount = fUnlockedEntryCount;
165        }
166#endif
167    }
168
169    // update our stats
170    if (clientReattach) {
171        fClientDetachedCount -= 1;
172        fClientDetachedBytes -= entry->resource()->sizeInBytes();
173    } else {
174        fEntryCount += 1;
175        fEntryBytes += entry->resource()->sizeInBytes();
176
177#if GR_CACHE_STATS
178        if (fHighWaterEntryCount < fEntryCount) {
179            fHighWaterEntryCount = fEntryCount;
180        }
181        if (fHighWaterEntryBytes < fEntryBytes) {
182            fHighWaterEntryBytes = fEntryBytes;
183        }
184#endif
185    }
186}
187
188GrResource* GrResourceCache::find(const GrResourceKey& key) {
189    GrAutoResourceCacheValidate atcv(this);
190
191    GrResourceEntry* entry = fCache.find(key);
192    if (NULL == entry) {
193        return NULL;
194    }
195
196    return entry->fResource;
197}
198
199GrResource* GrResourceCache::findAndLock(const GrResourceKey& key) {
200    GrAutoResourceCacheValidate atcv(this);
201
202    GrResourceEntry* entry = fCache.find(key);
203    if (NULL == entry) {
204        return NULL;
205    }
206
207    this->internalDetach(entry, false);
208    this->attachToHead(entry, false);
209
210    this->lock(entry);
211
212    return entry->fResource;
213}
214
215bool GrResourceCache::hasKey(const GrResourceKey& key) const {
216    return NULL != fCache.find(key);
217}
218
219void GrResourceCache::create(const GrResourceKey& key, GrResource* resource) {
220    GrAssert(NULL == resource->getCacheEntry());
221    // we don't expect to create new resources during a purge. In theory
222    // this could cause purgeAsNeeded() into an infinite loop (e.g.
223    // each resource destroyed creates and locks 2 resources and
224    // unlocks 1 thereby causing a new purge).
225    GrAssert(!fPurging);
226    GrAutoResourceCacheValidate atcv(this);
227
228    GrResourceEntry* entry = SkNEW_ARGS(GrResourceEntry, (key, resource));
229    resource->setCacheEntry(entry);
230
231    this->attachToHead(entry, false);
232    fCache.insert(key, entry);
233
234#if GR_DUMP_TEXTURE_UPLOAD
235    GrPrintf("--- add resource to cache %p, count=%d bytes= %d %d\n",
236             entry, fEntryCount, resource->sizeInBytes(), fEntryBytes);
237#endif
238}
239
240void GrResourceCache::createAndLock(const GrResourceKey& key,
241                                    GrResource* resource) {
242    this->create(key, resource);
243
244    GrAssert(NULL != resource->getCacheEntry());
245    this->lock(resource->getCacheEntry());
246}
247
248void GrResourceCache::makeExclusive(GrResourceEntry* entry) {
249    GrAutoResourceCacheValidate atcv(this);
250
251    this->internalDetach(entry, true);
252    fCache.remove(entry->key(), entry);
253
254#if GR_DEBUG
255    fExclusiveList.addToHead(entry);
256#endif
257}
258
259void GrResourceCache::removeInvalidResource(GrResourceEntry* entry) {
260    // If the resource went invalid while it was detached then purge it
261    // This can happen when a 3D context was lost,
262    // the client called GrContext::contextDestroyed() to notify Gr,
263    // and then later an SkGpuDevice's destructor releases its backing
264    // texture (which was invalidated at contextDestroyed time).
265    fClientDetachedCount -= 1;
266    fEntryCount -= 1;
267    size_t size = entry->resource()->sizeInBytes();
268    fClientDetachedBytes -= size;
269    fEntryBytes -= size;
270}
271
272void GrResourceCache::makeNonExclusive(GrResourceEntry* entry) {
273    GrAutoResourceCacheValidate atcv(this);
274
275#if GR_DEBUG
276    fExclusiveList.remove(entry);
277#endif
278
279    if (entry->resource()->isValid()) {
280        attachToHead(entry, true);
281        fCache.insert(entry->key(), entry);
282    } else {
283        this->removeInvalidResource(entry);
284    }
285}
286
287void GrResourceCache::lock(GrResourceEntry* entry) {
288    GrAutoResourceCacheValidate atcv(this);
289
290    GrAssert(entry);
291    GrAssert(fCache.find(entry->key()));
292
293    if (!entry->isLocked()) {
294        --fUnlockedEntryCount;
295    }
296
297    entry->lock();
298}
299
300void GrResourceCache::unlock(GrResourceEntry* entry) {
301    GrAutoResourceCacheValidate atcv(this);
302
303    GrAssert(entry);
304    GrAssert(entry->isLocked());
305    GrAssert(fCache.find(entry->key()));
306
307    entry->unlock();
308    if (!entry->isLocked()) {
309        ++fUnlockedEntryCount;
310#if GR_CACHE_STATS
311        if (fHighWaterUnlockedEntryCount < fUnlockedEntryCount) {
312            fHighWaterUnlockedEntryCount = fUnlockedEntryCount;
313        }
314#endif
315    }
316
317    this->purgeAsNeeded();
318}
319
320/**
321 * Destroying a resource may potentially trigger the unlock of additional
322 * resources which in turn will trigger a nested purge. We block the nested
323 * purge using the fPurging variable. However, the initial purge will keep
324 * looping until either all resources in the cache are unlocked or we've met
325 * the budget. There is an assertion in createAndLock to check against a
326 * resource's destructor inserting new resources into the cache. If these
327 * new resources were unlocked before purgeAsNeeded completed it could
328 * potentially make purgeAsNeeded loop infinitely.
329 */
330void GrResourceCache::purgeAsNeeded() {
331    if (!fPurging) {
332        fPurging = true;
333        bool withinBudget = false;
334        int priorUnlockedEntryCount = 0;
335
336        // The purging process is repeated several times since one pass
337        // may free up other resources
338        do {
339            EntryList::Iter iter;
340
341            priorUnlockedEntryCount = fUnlockedEntryCount;
342
343            // Note: the following code relies on the fact that the
344            // doubly linked list doesn't invalidate its data/pointers
345            // outside of the specific area where a deletion occurs (e.g.,
346            // in internalDetach)
347            GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart);
348
349            while (entry && fUnlockedEntryCount) {
350                GrAutoResourceCacheValidate atcv(this);
351                if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) {
352                    withinBudget = true;
353                    break;
354                }
355
356                GrResourceEntry* prev = iter.prev();
357                if (!entry->isLocked() && entry->fResource->getRefCnt() == 1) {
358                    // remove from our cache
359                    fCache.remove(entry->key(), entry);
360
361                    // remove from our llist
362                    this->internalDetach(entry, false);
363
364        #if GR_DUMP_TEXTURE_UPLOAD
365                    GrPrintf("--- ~resource from cache %p [%d %d]\n",
366                             entry->resource(),
367                             entry->resource()->width(),
368                             entry->resource()->height());
369        #endif
370
371                    delete entry;
372                }
373                entry = prev;
374            }
375        } while (!withinBudget && (fUnlockedEntryCount != priorUnlockedEntryCount));
376        fPurging = false;
377    }
378}
379
380void GrResourceCache::purgeAllUnlocked() {
381    GrAutoResourceCacheValidate atcv(this);
382
383    // we can have one GrResource holding a lock on another
384    // so we don't want to just do a simple loop kicking each
385    // entry out. Instead change the budget and purge.
386
387    int savedMaxBytes = fMaxBytes;
388    int savedMaxCount = fMaxCount;
389    fMaxBytes = (size_t) -1;
390    fMaxCount = 0;
391    this->purgeAsNeeded();
392
393#if GR_DEBUG
394    GrAssert(fExclusiveList.countEntries() == fClientDetachedCount);
395    GrAssert(countBytes(fExclusiveList) == fClientDetachedBytes);
396    GrAssert(!fUnlockedEntryCount);
397    if (!fCache.count()) {
398        // Items may have been detached from the cache (such as the backing
399        // texture for an SkGpuDevice). The above purge would not have removed
400        // them.
401        GrAssert(fEntryCount == fClientDetachedCount);
402        GrAssert(fEntryBytes == fClientDetachedBytes);
403        GrAssert(fList.isEmpty());
404    }
405#endif
406
407    fMaxBytes = savedMaxBytes;
408    fMaxCount = savedMaxCount;
409}
410
411///////////////////////////////////////////////////////////////////////////////
412
413#if GR_DEBUG
414size_t GrResourceCache::countBytes(const EntryList& list) {
415    size_t bytes = 0;
416
417    EntryList::Iter iter;
418
419    const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(list),
420                                             EntryList::Iter::kTail_IterStart);
421
422    for ( ; NULL != entry; entry = iter.prev()) {
423        bytes += entry->resource()->sizeInBytes();
424    }
425    return bytes;
426}
427
428static bool both_zero_or_nonzero(int count, size_t bytes) {
429    return (count == 0 && bytes == 0) || (count > 0 && bytes > 0);
430}
431
432void GrResourceCache::validate() const {
433    fList.validate();
434    fExclusiveList.validate();
435    GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes));
436    GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes));
437    GrAssert(fClientDetachedBytes <= fEntryBytes);
438    GrAssert(fClientDetachedCount <= fEntryCount);
439    GrAssert((fEntryCount - fClientDetachedCount) == fCache.count());
440
441    fCache.validate();
442
443
444    EntryList::Iter iter;
445
446    const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(fExclusiveList),
447                                             EntryList::Iter::kHead_IterStart);
448
449    for ( ; NULL != entry; entry = iter.next()) {
450        entry->validate();
451        GrAssert(entry->isLocked());
452    }
453
454    entry = iter.init(const_cast<EntryList&>(fList), EntryList::Iter::kHead_IterStart);
455
456    int count = 0;
457    int unlockCount = 0;
458    for ( ; NULL != entry; entry = iter.next()) {
459        entry->validate();
460        GrAssert(fCache.find(entry->key()));
461        count += 1;
462        if (!entry->isLocked()) {
463            unlockCount += 1;
464        }
465    }
466    GrAssert(count == fEntryCount - fClientDetachedCount);
467
468    size_t bytes = countBytes(fList);
469    GrAssert(bytes == fEntryBytes  - fClientDetachedBytes);
470
471    bytes = countBytes(fExclusiveList);
472    GrAssert(bytes == fClientDetachedBytes);
473
474    GrAssert(unlockCount == fUnlockedEntryCount);
475
476    GrAssert(fList.countEntries() == fEntryCount - fClientDetachedCount);
477
478    GrAssert(fExclusiveList.countEntries() == fClientDetachedCount);
479}
480#endif // GR_DEBUG
481
482#if GR_CACHE_STATS
483
484void GrResourceCache::printStats() const {
485    SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes);
486    SkDebugf("\t\tEntry Count: current %d high %d\n",
487                fEntryCount, fHighWaterEntryCount);
488    SkDebugf("\t\tUnlocked Entry Count: current %d high %d\n",
489                fUnlockedEntryCount, fHighWaterUnlockedEntryCount);
490    SkDebugf("\t\tEntry Bytes: current %d high %d\n",
491                fEntryBytes, fHighWaterEntryBytes);
492    SkDebugf("\t\tDetached Entry Count: current %d high %d\n",
493                fClientDetachedCount, fHighWaterClientDetachedCount);
494    SkDebugf("\t\tDetached Bytes: current %d high %d\n",
495                fClientDetachedBytes, fHighWaterClientDetachedBytes);
496}
497
498#endif
499
500///////////////////////////////////////////////////////////////////////////////
501