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    fPrev = fNext = NULL;
18
19    // we assume ownership of the resource, and will unref it when we die
20    GrAssert(resource);
21}
22
23GrResourceEntry::~GrResourceEntry() {
24    fResource->unref();
25}
26
27#if GR_DEBUG
28void GrResourceEntry::validate() const {
29    GrAssert(fLockCount >= 0);
30    GrAssert(fResource);
31    fResource->validate();
32}
33#endif
34
35///////////////////////////////////////////////////////////////////////////////
36
37GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) :
38        fMaxCount(maxCount),
39        fMaxBytes(maxBytes) {
40    fEntryCount          = 0;
41    fUnlockedEntryCount  = 0;
42    fEntryBytes          = 0;
43    fClientDetachedCount = 0;
44    fClientDetachedBytes = 0;
45
46    fHead = fTail = NULL;
47    fPurging = false;
48}
49
50GrResourceCache::~GrResourceCache() {
51    GrAutoResourceCacheValidate atcv(this);
52
53    this->removeAll();
54}
55
56void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{
57    if (maxResources) {
58        *maxResources = fMaxCount;
59    }
60    if (maxResourceBytes) {
61        *maxResourceBytes = fMaxBytes;
62    }
63}
64
65void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) {
66    bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes);
67
68    fMaxCount = maxResources;
69    fMaxBytes = maxResourceBytes;
70
71    if (smaller) {
72        this->purgeAsNeeded();
73    }
74}
75
76void GrResourceCache::internalDetach(GrResourceEntry* entry,
77                                    bool clientDetach) {
78    GrResourceEntry* prev = entry->fPrev;
79    GrResourceEntry* next = entry->fNext;
80
81    if (prev) {
82        prev->fNext = next;
83    } else {
84        fHead = next;
85    }
86    if (next) {
87        next->fPrev = prev;
88    } else {
89        fTail = prev;
90    }
91    if (!entry->isLocked()) {
92        --fUnlockedEntryCount;
93    }
94
95    // update our stats
96    if (clientDetach) {
97        fClientDetachedCount += 1;
98        fClientDetachedBytes += entry->resource()->sizeInBytes();
99    } else {
100        fEntryCount -= 1;
101        fEntryBytes -= entry->resource()->sizeInBytes();
102    }
103}
104
105void GrResourceCache::attachToHead(GrResourceEntry* entry,
106                                  bool clientReattach) {
107    entry->fPrev = NULL;
108    entry->fNext = fHead;
109    if (fHead) {
110        fHead->fPrev = entry;
111    }
112    fHead = entry;
113    if (NULL == fTail) {
114        fTail = entry;
115    }
116    if (!entry->isLocked()) {
117        ++fUnlockedEntryCount;
118    }
119
120    // update our stats
121    if (clientReattach) {
122        fClientDetachedCount -= 1;
123        fClientDetachedBytes -= entry->resource()->sizeInBytes();
124    } else {
125        fEntryCount += 1;
126        fEntryBytes += entry->resource()->sizeInBytes();
127    }
128}
129
130class GrResourceCache::Key {
131    typedef GrResourceEntry T;
132
133    const GrResourceKey& fKey;
134public:
135    Key(const GrResourceKey& key) : fKey(key) {}
136
137    uint32_t getHash() const { return fKey.hashIndex(); }
138
139    static bool LT(const T& entry, const Key& key) {
140        return entry.key() < key.fKey;
141    }
142    static bool EQ(const T& entry, const Key& key) {
143        return entry.key() == key.fKey;
144    }
145#if GR_DEBUG
146    static uint32_t GetHash(const T& entry) {
147        return entry.key().hashIndex();
148    }
149    static bool LT(const T& a, const T& b) {
150        return a.key() < b.key();
151    }
152    static bool EQ(const T& a, const T& b) {
153        return a.key() == b.key();
154    }
155#endif
156};
157
158GrResourceEntry* GrResourceCache::findAndLock(const GrResourceKey& key,
159                                              LockType type) {
160    GrAutoResourceCacheValidate atcv(this);
161
162    GrResourceEntry* entry = fCache.find(key);
163    if (entry) {
164        this->internalDetach(entry, false);
165        // mark the entry as "busy" so it doesn't get purged
166        // do this between detach and attach for locked count tracking
167        if (kNested_LockType == type || !entry->isLocked()) {
168            entry->lock();
169        }
170        this->attachToHead(entry, false);
171    }
172    return entry;
173}
174
175bool GrResourceCache::hasKey(const GrResourceKey& key) const {
176    return NULL != fCache.find(key);
177}
178
179GrResourceEntry* GrResourceCache::createAndLock(const GrResourceKey& key,
180                                              GrResource* resource) {
181    // we don't expect to create new resources during a purge. In theory
182    // this could cause purgeAsNeeded() into an infinite loop (e.g.
183    // each resource destroyed creates and locks 2 resources and
184    // unlocks 1 thereby causing a new purge).
185    GrAssert(!fPurging);
186    GrAutoResourceCacheValidate atcv(this);
187
188    GrResourceEntry* entry = new GrResourceEntry(key, resource);
189
190    // mark the entry as "busy" so it doesn't get purged
191    // do this before attach for locked count tracking
192    entry->lock();
193
194    this->attachToHead(entry, false);
195    fCache.insert(key, entry);
196
197#if GR_DUMP_TEXTURE_UPLOAD
198    GrPrintf("--- add resource to cache %p, count=%d bytes= %d %d\n",
199             entry, fEntryCount, resource->sizeInBytes(), fEntryBytes);
200#endif
201
202    this->purgeAsNeeded();
203    return entry;
204}
205
206void GrResourceCache::detach(GrResourceEntry* entry) {
207    GrAutoResourceCacheValidate atcv(this);
208    internalDetach(entry, true);
209    fCache.remove(entry->fKey, entry);
210}
211
212void GrResourceCache::reattachAndUnlock(GrResourceEntry* entry) {
213    GrAutoResourceCacheValidate atcv(this);
214    if (entry->resource()->isValid()) {
215        attachToHead(entry, true);
216        fCache.insert(entry->key(), entry);
217    } else {
218        // If the resource went invalid while it was detached then purge it
219        // This can happen when a 3D context was lost,
220        // the client called GrContext::contextDestroyed() to notify Gr,
221        // and then later an SkGpuDevice's destructor releases its backing
222        // texture (which was invalidated at contextDestroyed time).
223        fClientDetachedCount -= 1;
224        fEntryCount -= 1;
225        size_t size = entry->resource()->sizeInBytes();
226        fClientDetachedBytes -= size;
227        fEntryBytes -= size;
228    }
229    this->unlock(entry);
230}
231
232void GrResourceCache::unlock(GrResourceEntry* entry) {
233    GrAutoResourceCacheValidate atcv(this);
234
235    GrAssert(entry);
236    GrAssert(entry->isLocked());
237    GrAssert(fCache.find(entry->key()));
238
239    entry->unlock();
240    if (!entry->isLocked()) {
241        ++fUnlockedEntryCount;
242    }
243    this->purgeAsNeeded();
244}
245
246/**
247 * Destroying a resource may potentially trigger the unlock of additional
248 * resources which in turn will trigger a nested purge. We block the nested
249 * purge using the fPurging variable. However, the initial purge will keep
250 * looping until either all resources in the cache are unlocked or we've met
251 * the budget. There is an assertion in createAndLock to check against a
252 * resource's destructor inserting new resources into the cache. If these
253 * new resources were unlocked before purgeAsNeeded completed it could
254 * potentially make purgeAsNeeded loop infinitely.
255 */
256void GrResourceCache::purgeAsNeeded() {
257    if (!fPurging) {
258        fPurging = true;
259        bool withinBudget = false;
260        do {
261            GrResourceEntry* entry = fTail;
262            while (entry && fUnlockedEntryCount) {
263                GrAutoResourceCacheValidate atcv(this);
264                if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) {
265                    withinBudget = true;
266                    break;
267                }
268
269                GrResourceEntry* prev = entry->fPrev;
270                if (!entry->isLocked()) {
271                    // remove from our cache
272                    fCache.remove(entry->fKey, entry);
273
274                    // remove from our llist
275                    this->internalDetach(entry, false);
276
277        #if GR_DUMP_TEXTURE_UPLOAD
278                    GrPrintf("--- ~resource from cache %p [%d %d]\n",
279                             entry->resource(),
280                             entry->resource()->width(),
281                             entry->resource()->height());
282        #endif
283                    delete entry;
284                }
285                entry = prev;
286            }
287        } while (!withinBudget && fUnlockedEntryCount);
288        fPurging = false;
289    }
290}
291
292void GrResourceCache::removeAll() {
293    GrAutoResourceCacheValidate atcv(this);
294
295    GrResourceEntry* entry = fHead;
296
297    // we can have one GrResource holding a lock on another
298    // so we don't want to just do a simple loop kicking each
299    // entry out. Instead change the budget and purge.
300
301    int savedMaxBytes = fMaxBytes;
302    int savedMaxCount = fMaxCount;
303    fMaxBytes = -1;
304    fMaxCount = 0;
305    this->purgeAsNeeded();
306
307#if GR_DEBUG
308    GrAssert(!fUnlockedEntryCount);
309    if (!fCache.count()) {
310        // Items may have been detached from the cache (such as the backing
311        // texture for an SkGpuDevice). The above purge would not have removed
312        // them.
313        GrAssert(fEntryCount == fClientDetachedCount);
314        GrAssert(fEntryBytes == fClientDetachedBytes);
315        GrAssert(NULL == fHead);
316        GrAssert(NULL == fTail);
317    }
318#endif
319
320    fMaxBytes = savedMaxBytes;
321    fMaxCount = savedMaxCount;
322}
323
324///////////////////////////////////////////////////////////////////////////////
325
326#if GR_DEBUG
327static int countMatches(const GrResourceEntry* head, const GrResourceEntry* target) {
328    const GrResourceEntry* entry = head;
329    int count = 0;
330    while (entry) {
331        if (target == entry) {
332            count += 1;
333        }
334        entry = entry->next();
335    }
336    return count;
337}
338
339#if GR_DEBUG
340static bool both_zero_or_nonzero(int count, size_t bytes) {
341    return (count == 0 && bytes == 0) || (count > 0 && bytes > 0);
342}
343#endif
344
345void GrResourceCache::validate() const {
346    GrAssert(!fHead == !fTail);
347    GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes));
348    GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes));
349    GrAssert(fClientDetachedBytes <= fEntryBytes);
350    GrAssert(fClientDetachedCount <= fEntryCount);
351    GrAssert((fEntryCount - fClientDetachedCount) == fCache.count());
352
353    fCache.validate();
354
355    GrResourceEntry* entry = fHead;
356    int count = 0;
357    int unlockCount = 0;
358    size_t bytes = 0;
359    while (entry) {
360        entry->validate();
361        GrAssert(fCache.find(entry->key()));
362        count += 1;
363        bytes += entry->resource()->sizeInBytes();
364        if (!entry->isLocked()) {
365            unlockCount += 1;
366        }
367        entry = entry->fNext;
368    }
369    GrAssert(count == fEntryCount - fClientDetachedCount);
370    GrAssert(bytes == fEntryBytes  - fClientDetachedBytes);
371    GrAssert(unlockCount == fUnlockedEntryCount);
372
373    count = 0;
374    for (entry = fTail; entry; entry = entry->fPrev) {
375        count += 1;
376    }
377    GrAssert(count == fEntryCount - fClientDetachedCount);
378
379    for (int i = 0; i < count; i++) {
380        int matches = countMatches(fHead, fCache.getArray()[i]);
381        GrAssert(1 == matches);
382    }
383}
384#endif
385