GrResourceCache.cpp revision f320e04c50a1c8a861bc1d8f50bf732044ff9843
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    , fBytes(0)
71    , fBudgetedCount(0)
72    , fBudgetedBytes(0)
73    , fOverBudgetCB(NULL)
74    , fOverBudgetData(NULL) {
75    SkDEBUGCODE(fCount = 0;)
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(!this->isInCache(resource));
91    SkASSERT(!resource->wasDestroyed());
92    SkASSERT(!resource->isPurgeable());
93    this->addToNonpurgeableArray(resource);
94
95    size_t size = resource->gpuMemorySize();
96    SkDEBUGCODE(++fCount;)
97    fBytes += size;
98#if GR_CACHE_STATS
99    fHighWaterCount = SkTMax(this->getResourceCount(), fHighWaterCount);
100    fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
101#endif
102    if (resource->resourcePriv().isBudgeted()) {
103        ++fBudgetedCount;
104        fBudgetedBytes += size;
105#if GR_CACHE_STATS
106        fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
107        fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
108#endif
109    }
110    if (resource->resourcePriv().getScratchKey().isValid()) {
111        SkASSERT(!resource->cacheAccess().isWrapped());
112        fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
113    }
114
115    resource->cacheAccess().setTimestamp(fTimestamp++);
116
117    this->purgeAsNeeded();
118}
119
120void GrResourceCache::removeResource(GrGpuResource* resource) {
121    this->validate();
122    SkASSERT(this->isInCache(resource));
123
124    if (resource->isPurgeable()) {
125        fPurgeableQueue.remove(resource);
126    } else {
127        this->removeFromNonpurgeableArray(resource);
128    }
129
130    size_t size = resource->gpuMemorySize();
131    SkDEBUGCODE(--fCount;)
132    fBytes -= size;
133    if (resource->resourcePriv().isBudgeted()) {
134        --fBudgetedCount;
135        fBudgetedBytes -= size;
136    }
137
138    if (resource->resourcePriv().getScratchKey().isValid()) {
139        fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
140    }
141    if (resource->getContentKey().isValid()) {
142        fContentHash.remove(resource->getContentKey());
143    }
144    this->validate();
145}
146
147void GrResourceCache::abandonAll() {
148    AutoValidate av(this);
149
150    while (fNonpurgeableResources.count()) {
151        GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
152        SkASSERT(!back->wasDestroyed());
153        back->cacheAccess().abandon();
154    }
155
156    while (fPurgeableQueue.count()) {
157        GrGpuResource* top = fPurgeableQueue.peek();
158        SkASSERT(!top->wasDestroyed());
159        top->cacheAccess().abandon();
160    }
161
162    SkASSERT(!fScratchMap.count());
163    SkASSERT(!fContentHash.count());
164    SkASSERT(!fCount);
165    SkASSERT(!this->getResourceCount());
166    SkASSERT(!fBytes);
167    SkASSERT(!fBudgetedCount);
168    SkASSERT(!fBudgetedBytes);
169}
170
171void GrResourceCache::releaseAll() {
172    AutoValidate av(this);
173
174    while(fNonpurgeableResources.count()) {
175        GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
176        SkASSERT(!back->wasDestroyed());
177        back->cacheAccess().release();
178    }
179
180    while (fPurgeableQueue.count()) {
181        GrGpuResource* top = fPurgeableQueue.peek();
182        SkASSERT(!top->wasDestroyed());
183        top->cacheAccess().release();
184    }
185
186    SkASSERT(!fScratchMap.count());
187    SkASSERT(!fContentHash.count());
188    SkASSERT(!fCount);
189    SkASSERT(!this->getResourceCount());
190    SkASSERT(!fBytes);
191    SkASSERT(!fBudgetedCount);
192    SkASSERT(!fBudgetedBytes);
193}
194
195class GrResourceCache::AvailableForScratchUse {
196public:
197    AvailableForScratchUse(bool rejectPendingIO) : fRejectPendingIO(rejectPendingIO) { }
198
199    bool operator()(const GrGpuResource* resource) const {
200        if (resource->internalHasRef() || !resource->cacheAccess().isScratch()) {
201            return false;
202        }
203        return !fRejectPendingIO || !resource->internalHasPendingIO();
204    }
205
206private:
207    bool fRejectPendingIO;
208};
209
210GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& scratchKey,
211                                                          uint32_t flags) {
212    SkASSERT(scratchKey.isValid());
213
214    GrGpuResource* resource;
215    if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFlag)) {
216        resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true));
217        if (resource) {
218            this->refAndMakeResourceMRU(resource);
219            this->validate();
220            return resource;
221        } else if (flags & kRequireNoPendingIO_ScratchFlag) {
222            return NULL;
223        }
224        // TODO: fail here when kPrefer is specified, we didn't find a resource without pending io,
225        // but there is still space in our budget for the resource.
226    }
227    resource = fScratchMap.find(scratchKey, AvailableForScratchUse(false));
228    if (resource) {
229        this->refAndMakeResourceMRU(resource);
230        this->validate();
231    }
232    return resource;
233}
234
235void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) {
236    SkASSERT(resource->resourcePriv().getScratchKey().isValid());
237    fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
238}
239
240void GrResourceCache::willRemoveContentKey(const GrGpuResource* resource) {
241    // Someone has a ref to this resource in order to invalidate it. When the ref count reaches
242    // zero we will get a notifyPurgable() and figure out what to do with it.
243    SkASSERT(resource->getContentKey().isValid());
244    fContentHash.remove(resource->getContentKey());
245}
246
247bool GrResourceCache::didSetContentKey(GrGpuResource* resource) {
248    SkASSERT(resource);
249    SkASSERT(this->isInCache(resource));
250    SkASSERT(resource->getContentKey().isValid());
251
252    GrGpuResource* res = fContentHash.find(resource->getContentKey());
253    if (NULL != res) {
254        return false;
255    }
256
257    fContentHash.add(resource);
258    this->validate();
259    return true;
260}
261
262void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) {
263    SkASSERT(resource);
264    SkASSERT(this->isInCache(resource));
265    if (resource->isPurgeable()) {
266        // It's about to become unpurgeable.
267        fPurgeableQueue.remove(resource);
268        this->addToNonpurgeableArray(resource);
269    }
270    resource->ref();
271    resource->cacheAccess().setTimestamp(fTimestamp++);
272    this->validate();
273}
274
275void GrResourceCache::notifyPurgeable(GrGpuResource* resource) {
276    SkASSERT(resource);
277    SkASSERT(this->isInCache(resource));
278    SkASSERT(resource->isPurgeable());
279
280    this->removeFromNonpurgeableArray(resource);
281    fPurgeableQueue.insert(resource);
282
283    if (!resource->resourcePriv().isBudgeted()) {
284        // Check whether this resource could still be used as a scratch resource.
285        if (!resource->cacheAccess().isWrapped() &&
286            resource->resourcePriv().getScratchKey().isValid()) {
287            // We won't purge an existing resource to make room for this one.
288            if (fBudgetedCount < fMaxCount &&
289                fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes) {
290                resource->resourcePriv().makeBudgeted();
291                return;
292            }
293        }
294    } else {
295        // Purge the resource immediately if we're over budget
296        // Also purge if the resource has neither a valid scratch key nor a content key.
297        bool noKey = !resource->resourcePriv().getScratchKey().isValid() &&
298                     !resource->getContentKey().isValid();
299        if (!this->overBudget() && !noKey) {
300            return;
301        }
302    }
303
304    SkDEBUGCODE(int beforeCount = this->getResourceCount();)
305    resource->cacheAccess().release();
306    // We should at least free this resource, perhaps dependent resources as well.
307    SkASSERT(this->getResourceCount() < beforeCount);
308    this->validate();
309}
310
311void GrResourceCache::didChangeGpuMemorySize(const GrGpuResource* resource, size_t oldSize) {
312    // SkASSERT(!fPurging); GrPathRange increases size during flush. :(
313    SkASSERT(resource);
314    SkASSERT(this->isInCache(resource));
315
316    ptrdiff_t delta = resource->gpuMemorySize() - oldSize;
317
318    fBytes += delta;
319#if GR_CACHE_STATS
320    fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
321#endif
322    if (resource->resourcePriv().isBudgeted()) {
323        fBudgetedBytes += delta;
324#if GR_CACHE_STATS
325        fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
326#endif
327    }
328
329    this->purgeAsNeeded();
330    this->validate();
331}
332
333void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) {
334    SkASSERT(resource);
335    SkASSERT(this->isInCache(resource));
336
337    size_t size = resource->gpuMemorySize();
338
339    if (resource->resourcePriv().isBudgeted()) {
340        ++fBudgetedCount;
341        fBudgetedBytes += size;
342#if GR_CACHE_STATS
343        fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
344        fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
345#endif
346        this->purgeAsNeeded();
347    } else {
348        --fBudgetedCount;
349        fBudgetedBytes -= size;
350    }
351
352    this->validate();
353}
354
355void GrResourceCache::internalPurgeAsNeeded() {
356    SkASSERT(this->overBudget());
357
358    bool stillOverbudget = true;
359    while (fPurgeableQueue.count()) {
360        GrGpuResource* resource = fPurgeableQueue.peek();
361        SkASSERT(resource->isPurgeable());
362        resource->cacheAccess().release();
363        if (!this->overBudget()) {
364            stillOverbudget = false;
365            break;
366        }
367    }
368
369    this->validate();
370
371    if (stillOverbudget) {
372        // Despite the purge we're still over budget. Call our over budget callback. If this frees
373        // any resources then we'll get notifyPurgeable() calls and take appropriate action.
374        (*fOverBudgetCB)(fOverBudgetData);
375        this->validate();
376    }
377}
378
379void GrResourceCache::purgeAllUnlocked() {
380    // We could disable maintaining the heap property here, but it would add a lot of complexity.
381    // Moreover, this is rarely called.
382    while (fPurgeableQueue.count()) {
383        GrGpuResource* resource = fPurgeableQueue.peek();
384        SkASSERT(resource->isPurgeable());
385        resource->cacheAccess().release();
386    }
387
388    this->validate();
389}
390
391void GrResourceCache::processInvalidContentKeys(
392    const SkTArray<GrContentKeyInvalidatedMessage>& msgs) {
393    for (int i = 0; i < msgs.count(); ++i) {
394        GrGpuResource* resource = this->findAndRefContentResource(msgs[i].key());
395        if (resource) {
396            resource->resourcePriv().removeContentKey();
397            resource->unref(); // will call notifyPurgeable, if it is indeed now purgeable.
398        }
399    }
400}
401
402void GrResourceCache::addToNonpurgeableArray(GrGpuResource* resource) {
403    int index = fNonpurgeableResources.count();
404    *fNonpurgeableResources.append() = resource;
405    *resource->cacheAccess().accessCacheIndex() = index;
406}
407
408void GrResourceCache::removeFromNonpurgeableArray(GrGpuResource* resource) {
409    int* index = resource->cacheAccess().accessCacheIndex();
410    // Fill the whole we will create in the array with the tail object, adjust its index, and
411    // then pop the array
412    GrGpuResource* tail = *(fNonpurgeableResources.end() - 1);
413    SkASSERT(fNonpurgeableResources[*index] == resource);
414    fNonpurgeableResources[*index] = tail;
415    *tail->cacheAccess().accessCacheIndex() = *index;
416    fNonpurgeableResources.pop();
417    SkDEBUGCODE(*index = -1);
418}
419
420#ifdef SK_DEBUG
421void GrResourceCache::validate() const {
422    // Reduce the frequency of validations for large resource counts.
423    static SkRandom gRandom;
424    int mask = (SkNextPow2(fCount + 1) >> 5) - 1;
425    if (~mask && (gRandom.nextU() & mask)) {
426        return;
427    }
428
429    struct Stats {
430        size_t fBytes;
431        int fBudgetedCount;
432        size_t fBudgetedBytes;
433        int fLocked;
434        int fScratch;
435        int fCouldBeScratch;
436        int fContent;
437        const ScratchMap* fScratchMap;
438        const ContentHash* fContentHash;
439
440        Stats(const GrResourceCache* cache) {
441            memset(this, 0, sizeof(*this));
442            fScratchMap = &cache->fScratchMap;
443            fContentHash = &cache->fContentHash;
444        }
445
446        void update(GrGpuResource* resource) {
447            fBytes += resource->gpuMemorySize();
448
449            if (!resource->isPurgeable()) {
450                ++fLocked;
451            }
452
453            if (resource->cacheAccess().isScratch()) {
454                SkASSERT(!resource->getContentKey().isValid());
455                ++fScratch;
456                SkASSERT(fScratchMap->countForKey(resource->resourcePriv().getScratchKey()));
457                SkASSERT(!resource->cacheAccess().isWrapped());
458            } else if (resource->resourcePriv().getScratchKey().isValid()) {
459                SkASSERT(!resource->resourcePriv().isBudgeted() ||
460                         resource->getContentKey().isValid());
461                ++fCouldBeScratch;
462                SkASSERT(fScratchMap->countForKey(resource->resourcePriv().getScratchKey()));
463                SkASSERT(!resource->cacheAccess().isWrapped());
464            }
465            const GrContentKey& contentKey = resource->getContentKey();
466            if (contentKey.isValid()) {
467                ++fContent;
468                SkASSERT(fContentHash->find(contentKey) == resource);
469                SkASSERT(!resource->cacheAccess().isWrapped());
470                SkASSERT(resource->resourcePriv().isBudgeted());
471            }
472
473            if (resource->resourcePriv().isBudgeted()) {
474                ++fBudgetedCount;
475                fBudgetedBytes += resource->gpuMemorySize();
476            }
477        }
478    };
479
480    Stats stats(this);
481
482    for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
483        SkASSERT(!fNonpurgeableResources[i]->isPurgeable());
484        SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
485        SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
486        stats.update(fNonpurgeableResources[i]);
487    }
488    for (int i = 0; i < fPurgeableQueue.count(); ++i) {
489        SkASSERT(fPurgeableQueue.at(i)->isPurgeable());
490        SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i);
491        SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed());
492        stats.update(fPurgeableQueue.at(i));
493    }
494
495    SkASSERT(fCount == this->getResourceCount());
496    SkASSERT(fBudgetedCount <= fCount);
497    SkASSERT(fBudgetedBytes <= fBytes);
498    SkASSERT(stats.fBytes == fBytes);
499    SkASSERT(stats.fBudgetedBytes == fBudgetedBytes);
500    SkASSERT(stats.fBudgetedCount == fBudgetedCount);
501#if GR_CACHE_STATS
502    SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount);
503    SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes);
504    SkASSERT(fBytes <= fHighWaterBytes);
505    SkASSERT(fCount <= fHighWaterCount);
506    SkASSERT(fBudgetedBytes <= fBudgetedHighWaterBytes);
507    SkASSERT(fBudgetedCount <= fBudgetedHighWaterCount);
508#endif
509    SkASSERT(stats.fContent == fContentHash.count());
510    SkASSERT(stats.fScratch + stats.fCouldBeScratch == fScratchMap.count());
511
512    // This assertion is not currently valid because we can be in recursive notifyIsPurgeable()
513    // calls. This will be fixed when subresource registration is explicit.
514    // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount;
515    // SkASSERT(!overBudget || locked == count || fPurging);
516}
517
518bool GrResourceCache::isInCache(const GrGpuResource* resource) const {
519    int index = *resource->cacheAccess().accessCacheIndex();
520    if (index < 0) {
521        return false;
522    }
523    if (index < fPurgeableQueue.count() && fPurgeableQueue.at(index) == resource) {
524        return true;
525    }
526    if (index < fNonpurgeableResources.count() && fNonpurgeableResources[index] == resource) {
527        return true;
528    }
529    SkDEBUGFAIL("Resource index should be -1 or the resource should be in the cache.");
530    return false;
531}
532
533#endif
534