GrResourceCache.cpp revision 9f2d1571ed1f0ed579e5d7779c46a90e20f30f22
1
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
10#include "GrResourceCache.h"
11#include "GrGpuResourceCacheAccess.h"
12#include "SkChecksum.h"
13#include "SkGr.h"
14#include "SkMessageBus.h"
15
16DECLARE_SKMESSAGEBUS_MESSAGE(GrContentKeyInvalidatedMessage);
17
18//////////////////////////////////////////////////////////////////////////////
19
20GrScratchKey::ResourceType GrScratchKey::GenerateResourceType() {
21    static int32_t gType = INHERITED::kInvalidDomain + 1;
22
23    int32_t type = sk_atomic_inc(&gType);
24    if (type > SK_MaxU16) {
25        SkFAIL("Too many Resource Types");
26    }
27
28    return static_cast<ResourceType>(type);
29}
30
31GrContentKey::Domain GrContentKey::GenerateDomain() {
32    static int32_t gDomain = INHERITED::kInvalidDomain + 1;
33
34    int32_t domain = sk_atomic_inc(&gDomain);
35    if (domain > SK_MaxU16) {
36        SkFAIL("Too many Content Key Domains");
37    }
38
39    return static_cast<Domain>(domain);
40}
41uint32_t GrResourceKeyHash(const uint32_t* data, size_t size) {
42    return SkChecksum::Compute(data, size);
43}
44
45//////////////////////////////////////////////////////////////////////////////
46
47class GrResourceCache::AutoValidate : ::SkNoncopyable {
48public:
49    AutoValidate(GrResourceCache* cache) : fCache(cache) { cache->validate(); }
50    ~AutoValidate() { fCache->validate(); }
51private:
52    GrResourceCache* fCache;
53};
54
55 //////////////////////////////////////////////////////////////////////////////
56
57static const int kDefaultMaxCount = 2 * (1 << 10);
58static const size_t kDefaultMaxSize = 96 * (1 << 20);
59
60GrResourceCache::GrResourceCache()
61    : fTimestamp(0)
62    , fMaxCount(kDefaultMaxCount)
63    , fMaxBytes(kDefaultMaxSize)
64#if GR_CACHE_STATS
65    , fHighWaterCount(0)
66    , fHighWaterBytes(0)
67    , fBudgetedHighWaterCount(0)
68    , fBudgetedHighWaterBytes(0)
69#endif
70    , fCount(0)
71    , fBytes(0)
72    , fBudgetedCount(0)
73    , fBudgetedBytes(0)
74    , fOverBudgetCB(NULL)
75    , fOverBudgetData(NULL) {
76}
77
78GrResourceCache::~GrResourceCache() {
79    this->releaseAll();
80}
81
82void GrResourceCache::setLimits(int count, size_t bytes) {
83    fMaxCount = count;
84    fMaxBytes = bytes;
85    this->purgeAsNeeded();
86}
87
88void GrResourceCache::insertResource(GrGpuResource* resource) {
89    SkASSERT(resource);
90    SkASSERT(!resource->wasDestroyed());
91    SkASSERT(!this->isInCache(resource));
92    fResources.addToHead(resource);
93
94    size_t size = resource->gpuMemorySize();
95    ++fCount;
96    fBytes += size;
97#if GR_CACHE_STATS
98    fHighWaterCount = SkTMax(fCount, fHighWaterCount);
99    fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
100#endif
101    if (resource->resourcePriv().isBudgeted()) {
102        ++fBudgetedCount;
103        fBudgetedBytes += size;
104#if GR_CACHE_STATS
105        fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
106        fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
107#endif
108    }
109    if (resource->resourcePriv().getScratchKey().isValid()) {
110        SkASSERT(!resource->cacheAccess().isWrapped());
111        fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
112    }
113
114    resource->cacheAccess().setTimestamp(fTimestamp++);
115
116    this->purgeAsNeeded();
117}
118
119void GrResourceCache::removeResource(GrGpuResource* resource) {
120    this->validate();
121    SkASSERT(this->isInCache(resource));
122
123    if (resource->isPurgeable()) {
124        fPurgeableQueue.remove(resource);
125    }
126
127    size_t size = resource->gpuMemorySize();
128    --fCount;
129    fBytes -= size;
130    if (resource->resourcePriv().isBudgeted()) {
131        --fBudgetedCount;
132        fBudgetedBytes -= size;
133    }
134
135    fResources.remove(resource);
136    if (resource->resourcePriv().getScratchKey().isValid()) {
137        fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
138    }
139    if (resource->getContentKey().isValid()) {
140        fContentHash.remove(resource->getContentKey());
141    }
142    this->validate();
143}
144
145void GrResourceCache::abandonAll() {
146    AutoValidate av(this);
147
148    while (GrGpuResource* head = fResources.head()) {
149        SkASSERT(!head->wasDestroyed());
150        head->cacheAccess().abandon();
151        // abandon should have already removed this from the list.
152        SkASSERT(head != fResources.head());
153    }
154    SkASSERT(!fScratchMap.count());
155    SkASSERT(!fContentHash.count());
156    SkASSERT(!fCount);
157    SkASSERT(!fBytes);
158    SkASSERT(!fBudgetedCount);
159    SkASSERT(!fBudgetedBytes);
160}
161
162void GrResourceCache::releaseAll() {
163    AutoValidate av(this);
164
165    while (GrGpuResource* head = fResources.head()) {
166        SkASSERT(!head->wasDestroyed());
167        head->cacheAccess().release();
168        // release should have already removed this from the list.
169        SkASSERT(head != fResources.head());
170    }
171    SkASSERT(!fScratchMap.count());
172    SkASSERT(!fCount);
173    SkASSERT(!fBytes);
174    SkASSERT(!fBudgetedCount);
175    SkASSERT(!fBudgetedBytes);
176}
177
178class GrResourceCache::AvailableForScratchUse {
179public:
180    AvailableForScratchUse(bool rejectPendingIO) : fRejectPendingIO(rejectPendingIO) { }
181
182    bool operator()(const GrGpuResource* resource) const {
183        if (resource->internalHasRef() || !resource->cacheAccess().isScratch()) {
184            return false;
185        }
186        return !fRejectPendingIO || !resource->internalHasPendingIO();
187    }
188
189private:
190    bool fRejectPendingIO;
191};
192
193GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& scratchKey,
194                                                          uint32_t flags) {
195    SkASSERT(scratchKey.isValid());
196
197    GrGpuResource* resource;
198    if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFlag)) {
199        resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true));
200        if (resource) {
201            this->refAndMakeResourceMRU(resource);
202            this->validate();
203            return resource;
204        } else if (flags & kRequireNoPendingIO_ScratchFlag) {
205            return NULL;
206        }
207        // TODO: fail here when kPrefer is specified, we didn't find a resource without pending io,
208        // but there is still space in our budget for the resource.
209    }
210    resource = fScratchMap.find(scratchKey, AvailableForScratchUse(false));
211    if (resource) {
212        this->refAndMakeResourceMRU(resource);
213        this->validate();
214    }
215    return resource;
216}
217
218void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) {
219    SkASSERT(resource->resourcePriv().getScratchKey().isValid());
220    fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
221}
222
223void GrResourceCache::willRemoveContentKey(const GrGpuResource* resource) {
224    // Someone has a ref to this resource in order to invalidate it. When the ref count reaches
225    // zero we will get a notifyPurgable() and figure out what to do with it.
226    SkASSERT(resource->getContentKey().isValid());
227    fContentHash.remove(resource->getContentKey());
228}
229
230bool GrResourceCache::didSetContentKey(GrGpuResource* resource) {
231    SkASSERT(resource);
232    SkASSERT(this->isInCache(resource));
233    SkASSERT(resource->getContentKey().isValid());
234
235    GrGpuResource* res = fContentHash.find(resource->getContentKey());
236    if (NULL != res) {
237        return false;
238    }
239
240    fContentHash.add(resource);
241    this->validate();
242    return true;
243}
244
245void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) {
246    SkASSERT(resource);
247    SkASSERT(this->isInCache(resource));
248    if (resource->isPurgeable()) {
249        // It's about to become unpurgeable.
250        fPurgeableQueue.remove(resource);
251    }
252    resource->ref();
253    resource->cacheAccess().setTimestamp(fTimestamp++);
254    SkASSERT(!resource->isPurgeable());
255}
256
257void GrResourceCache::notifyPurgeable(GrGpuResource* resource) {
258    SkASSERT(resource);
259    SkASSERT(this->isInCache(resource));
260    SkASSERT(resource->isPurgeable());
261
262    SkASSERT(-1 == *resource->cacheAccess().accessCacheIndex());
263    fPurgeableQueue.insert(resource);
264
265    if (!resource->resourcePriv().isBudgeted()) {
266        // Check whether this resource could still be used as a scratch resource.
267        if (!resource->cacheAccess().isWrapped() &&
268            resource->resourcePriv().getScratchKey().isValid()) {
269            // We won't purge an existing resource to make room for this one.
270            bool underBudget = fBudgetedCount < fMaxCount &&
271                               fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes;
272            if (underBudget) {
273                resource->resourcePriv().makeBudgeted();
274                return;
275            }
276        }
277    } else {
278        // Purge the resource immediately if we're over budget
279        bool overBudget = fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxBytes;
280
281        // Also purge if the resource has neither a valid scratch key nor a content key.
282        bool noKey = !resource->resourcePriv().getScratchKey().isValid() &&
283                        !resource->getContentKey().isValid();
284        if (!overBudget && !noKey) {
285            return;
286        }
287    }
288
289    SkDEBUGCODE(int beforeCount = fCount;)
290    resource->cacheAccess().release();
291    // We should at least free this resource, perhaps dependent resources as well.
292    SkASSERT(fCount < beforeCount);
293    this->validate();
294}
295
296void GrResourceCache::didChangeGpuMemorySize(const GrGpuResource* resource, size_t oldSize) {
297    // SkASSERT(!fPurging); GrPathRange increases size during flush. :(
298    SkASSERT(resource);
299    SkASSERT(this->isInCache(resource));
300
301    ptrdiff_t delta = resource->gpuMemorySize() - oldSize;
302
303    fBytes += delta;
304#if GR_CACHE_STATS
305    fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
306#endif
307    if (resource->resourcePriv().isBudgeted()) {
308        fBudgetedBytes += delta;
309#if GR_CACHE_STATS
310        fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
311#endif
312    }
313
314    this->purgeAsNeeded();
315    this->validate();
316}
317
318void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) {
319    SkASSERT(resource);
320    SkASSERT(this->isInCache(resource));
321
322    size_t size = resource->gpuMemorySize();
323
324    if (resource->resourcePriv().isBudgeted()) {
325        ++fBudgetedCount;
326        fBudgetedBytes += size;
327#if GR_CACHE_STATS
328        fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
329        fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
330#endif
331        this->purgeAsNeeded();
332    } else {
333        --fBudgetedCount;
334        fBudgetedBytes -= size;
335    }
336
337    this->validate();
338}
339
340void GrResourceCache::internalPurgeAsNeeded() {
341    SkASSERT(fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxBytes);
342
343    bool stillOverbudget = true;
344    while (fPurgeableQueue.count()) {
345        GrGpuResource* resource = fPurgeableQueue.peek();
346        SkASSERT(resource->isPurgeable());
347        resource->cacheAccess().release();
348        if (fBudgetedCount <= fMaxCount && fBudgetedBytes <= fMaxBytes) {
349            stillOverbudget = false;
350            break;
351        }
352    }
353
354    this->validate();
355
356    if (stillOverbudget) {
357        // Despite the purge we're still over budget. Call our over budget callback. If this frees
358        // any resources then we'll get notifyPurgeable() calls and take appropriate action.
359        (*fOverBudgetCB)(fOverBudgetData);
360        this->validate();
361    }
362}
363
364void GrResourceCache::purgeAllUnlocked() {
365    // We could disable maintaining the heap property here, but it would add a lot of complexity.
366    // Moreover, this is rarely called.
367    while (fPurgeableQueue.count()) {
368        GrGpuResource* resource = fPurgeableQueue.peek();
369        SkASSERT(resource->isPurgeable());
370        resource->cacheAccess().release();
371    }
372
373    this->validate();
374}
375
376void GrResourceCache::processInvalidContentKeys(
377    const SkTArray<GrContentKeyInvalidatedMessage>& msgs) {
378    for (int i = 0; i < msgs.count(); ++i) {
379        GrGpuResource* resource = this->findAndRefContentResource(msgs[i].key());
380        if (resource) {
381            resource->resourcePriv().removeContentKey();
382            resource->unref(); // will call notifyPurgeable, if it is indeed now purgeable.
383        }
384    }
385}
386
387#ifdef SK_DEBUG
388void GrResourceCache::validate() const {
389    // Reduce the frequency of validations for large resource counts.
390    static SkRandom gRandom;
391    int mask = (SkNextPow2(fCount + 1) >> 5) - 1;
392    if (~mask && (gRandom.nextU() & mask)) {
393        return;
394    }
395
396    size_t bytes = 0;
397    int count = 0;
398    int budgetedCount = 0;
399    size_t budgetedBytes = 0;
400    int locked = 0;
401    int scratch = 0;
402    int couldBeScratch = 0;
403    int content = 0;
404
405    ResourceList::Iter iter;
406    GrGpuResource* resource = iter.init(fResources, ResourceList::Iter::kHead_IterStart);
407    for ( ; resource; resource = iter.next()) {
408        bytes += resource->gpuMemorySize();
409        ++count;
410
411        if (!resource->isPurgeable()) {
412            ++locked;
413        }
414
415        if (resource->cacheAccess().isScratch()) {
416            SkASSERT(!resource->getContentKey().isValid());
417            ++scratch;
418            SkASSERT(fScratchMap.countForKey(resource->resourcePriv().getScratchKey()));
419            SkASSERT(!resource->cacheAccess().isWrapped());
420        } else if (resource->resourcePriv().getScratchKey().isValid()) {
421            SkASSERT(!resource->resourcePriv().isBudgeted() ||
422                     resource->getContentKey().isValid());
423            ++couldBeScratch;
424            SkASSERT(fScratchMap.countForKey(resource->resourcePriv().getScratchKey()));
425            SkASSERT(!resource->cacheAccess().isWrapped());
426        }
427        const GrContentKey& contentKey = resource->getContentKey();
428        if (contentKey.isValid()) {
429            ++content;
430            SkASSERT(fContentHash.find(contentKey) == resource);
431            SkASSERT(!resource->cacheAccess().isWrapped());
432            SkASSERT(resource->resourcePriv().isBudgeted());
433        }
434
435        if (resource->resourcePriv().isBudgeted()) {
436            ++budgetedCount;
437            budgetedBytes += resource->gpuMemorySize();
438        }
439
440        if (!resource->isPurgeable()) {
441            SkASSERT(-1 == *resource->cacheAccess().accessCacheIndex());
442        }
443    }
444
445    for (int i = 0; i < fPurgeableQueue.count(); ++i) {
446        SkASSERT(fPurgeableQueue.at(i)->isPurgeable());
447    }
448
449    SkASSERT(fCount - locked == fPurgeableQueue.count());
450    SkASSERT(fBudgetedCount <= fCount);
451    SkASSERT(fBudgetedBytes <= fBudgetedBytes);
452    SkASSERT(bytes == fBytes);
453    SkASSERT(count == fCount);
454    SkASSERT(budgetedBytes == fBudgetedBytes);
455    SkASSERT(budgetedCount == fBudgetedCount);
456#if GR_CACHE_STATS
457    SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount);
458    SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes);
459    SkASSERT(bytes <= fHighWaterBytes);
460    SkASSERT(count <= fHighWaterCount);
461    SkASSERT(budgetedBytes <= fBudgetedHighWaterBytes);
462    SkASSERT(budgetedCount <= fBudgetedHighWaterCount);
463#endif
464    SkASSERT(content == fContentHash.count());
465    SkASSERT(scratch + couldBeScratch == fScratchMap.count());
466
467    // This assertion is not currently valid because we can be in recursive notifyIsPurgeable()
468    // calls. This will be fixed when subresource registration is explicit.
469    // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount;
470    // SkASSERT(!overBudget || locked == count || fPurging);
471}
472#endif
473