SkResourceCache.cpp revision 49f085dddff10473b6ebf832a974288300224e60
1/*
2 * Copyright 2013 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkChecksum.h"
9#include "SkResourceCache.h"
10#include "SkMipMap.h"
11#include "SkPixelRef.h"
12
13// This can be defined by the caller's build system
14//#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE
15
16#ifndef SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT
17#   define SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT   1024
18#endif
19
20#ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT
21    #define SK_DEFAULT_IMAGE_CACHE_LIMIT     (2 * 1024 * 1024)
22#endif
23
24void SkResourceCache::Key::init(size_t length) {
25    SkASSERT(SkAlign4(length) == length);
26    // 2 is fCount32 and fHash
27    fCount32 = SkToS32(2 + (length >> 2));
28    // skip both of our fields whe computing the murmur
29    fHash = SkChecksum::Murmur3(this->as32() + 2, (fCount32 - 2) << 2);
30}
31
32#include "SkTDynamicHash.h"
33
34class SkResourceCache::Hash :
35    public SkTDynamicHash<SkResourceCache::Rec, SkResourceCache::Key> {};
36
37
38///////////////////////////////////////////////////////////////////////////////
39
40// experimental hash to speed things up
41#define USE_HASH
42
43#if !defined(USE_HASH)
44static inline SkResourceCache::Rec* find_rec_in_list(
45        SkResourceCache::Rec* head, const Key & key) {
46    SkResourceCache::Rec* rec = head;
47    while ((rec != NULL) && (rec->fKey != key)) {
48        rec = rec->fNext;
49    }
50    return rec;
51}
52#endif
53
54void SkResourceCache::init() {
55    fHead = NULL;
56    fTail = NULL;
57#ifdef USE_HASH
58    fHash = new Hash;
59#else
60    fHash = NULL;
61#endif
62    fTotalBytesUsed = 0;
63    fCount = 0;
64    fSingleAllocationByteLimit = 0;
65    fAllocator = NULL;
66
67    // One of these should be explicit set by the caller after we return.
68    fTotalByteLimit = 0;
69    fDiscardableFactory = NULL;
70}
71
72#include "SkDiscardableMemory.h"
73
74class SkOneShotDiscardablePixelRef : public SkPixelRef {
75public:
76    SK_DECLARE_INST_COUNT(SkOneShotDiscardablePixelRef)
77    // Ownership of the discardablememory is transfered to the pixelref
78    SkOneShotDiscardablePixelRef(const SkImageInfo&, SkDiscardableMemory*, size_t rowBytes);
79    ~SkOneShotDiscardablePixelRef();
80
81protected:
82    virtual bool onNewLockPixels(LockRec*) SK_OVERRIDE;
83    virtual void onUnlockPixels() SK_OVERRIDE;
84    virtual size_t getAllocatedSizeInBytes() const SK_OVERRIDE;
85
86private:
87    SkDiscardableMemory* fDM;
88    size_t               fRB;
89    bool                 fFirstTime;
90
91    typedef SkPixelRef INHERITED;
92};
93
94SkOneShotDiscardablePixelRef::SkOneShotDiscardablePixelRef(const SkImageInfo& info,
95                                             SkDiscardableMemory* dm,
96                                             size_t rowBytes)
97    : INHERITED(info)
98    , fDM(dm)
99    , fRB(rowBytes)
100{
101    SkASSERT(dm->data());
102    fFirstTime = true;
103}
104
105SkOneShotDiscardablePixelRef::~SkOneShotDiscardablePixelRef() {
106    SkDELETE(fDM);
107}
108
109bool SkOneShotDiscardablePixelRef::onNewLockPixels(LockRec* rec) {
110    if (fFirstTime) {
111        // we're already locked
112        SkASSERT(fDM->data());
113        fFirstTime = false;
114        goto SUCCESS;
115    }
116
117    // A previous call to onUnlock may have deleted our DM, so check for that
118    if (NULL == fDM) {
119        return false;
120    }
121
122    if (!fDM->lock()) {
123        // since it failed, we delete it now, to free-up the resource
124        delete fDM;
125        fDM = NULL;
126        return false;
127    }
128
129SUCCESS:
130    rec->fPixels = fDM->data();
131    rec->fColorTable = NULL;
132    rec->fRowBytes = fRB;
133    return true;
134}
135
136void SkOneShotDiscardablePixelRef::onUnlockPixels() {
137    SkASSERT(!fFirstTime);
138    fDM->unlock();
139}
140
141size_t SkOneShotDiscardablePixelRef::getAllocatedSizeInBytes() const {
142    return this->info().getSafeSize(fRB);
143}
144
145class SkResourceCacheDiscardableAllocator : public SkBitmap::Allocator {
146public:
147    SkResourceCacheDiscardableAllocator(SkResourceCache::DiscardableFactory factory) {
148        SkASSERT(factory);
149        fFactory = factory;
150    }
151
152    virtual bool allocPixelRef(SkBitmap*, SkColorTable*) SK_OVERRIDE;
153
154private:
155    SkResourceCache::DiscardableFactory fFactory;
156};
157
158bool SkResourceCacheDiscardableAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
159    size_t size = bitmap->getSize();
160    uint64_t size64 = bitmap->computeSize64();
161    if (0 == size || size64 > (uint64_t)size) {
162        return false;
163    }
164
165    SkDiscardableMemory* dm = fFactory(size);
166    if (NULL == dm) {
167        return false;
168    }
169
170    // can we relax this?
171    if (kN32_SkColorType != bitmap->colorType()) {
172        return false;
173    }
174
175    SkImageInfo info = bitmap->info();
176    bitmap->setPixelRef(SkNEW_ARGS(SkOneShotDiscardablePixelRef,
177                                   (info, dm, bitmap->rowBytes())))->unref();
178    bitmap->lockPixels();
179    return bitmap->readyToDraw();
180}
181
182SkResourceCache::SkResourceCache(DiscardableFactory factory) {
183    this->init();
184    fDiscardableFactory = factory;
185
186    fAllocator = SkNEW_ARGS(SkResourceCacheDiscardableAllocator, (factory));
187}
188
189SkResourceCache::SkResourceCache(size_t byteLimit) {
190    this->init();
191    fTotalByteLimit = byteLimit;
192}
193
194SkResourceCache::~SkResourceCache() {
195    SkSafeUnref(fAllocator);
196
197    Rec* rec = fHead;
198    while (rec) {
199        Rec* next = rec->fNext;
200        SkDELETE(rec);
201        rec = next;
202    }
203    delete fHash;
204}
205
206////////////////////////////////////////////////////////////////////////////////
207
208const SkResourceCache::Rec* SkResourceCache::findAndLock(const Key& key) {
209#ifdef USE_HASH
210    Rec* rec = fHash->find(key);
211#else
212    Rec* rec = find_rec_in_list(fHead, key);
213#endif
214    if (rec) {
215        this->moveToHead(rec);  // for our LRU
216        rec->fLockCount += 1;
217    }
218    return rec;
219}
220
221const SkResourceCache::Rec* SkResourceCache::addAndLock(Rec* rec) {
222    SkASSERT(rec);
223    // See if we already have this key (racy inserts, etc.)
224    const Rec* existing = this->findAndLock(rec->getKey());
225    if (existing) {
226        SkDELETE(rec);
227        return existing;
228    }
229
230    this->addToHead(rec);
231    SkASSERT(1 == rec->fLockCount);
232#ifdef USE_HASH
233    SkASSERT(fHash);
234    fHash->add(rec);
235#endif
236    // We may (now) be overbudget, so see if we need to purge something.
237    this->purgeAsNeeded();
238    return rec;
239}
240
241void SkResourceCache::add(Rec* rec) {
242    SkASSERT(rec);
243    // See if we already have this key (racy inserts, etc.)
244    const Rec* existing = this->findAndLock(rec->getKey());
245    if (existing) {
246        SkDELETE(rec);
247        this->unlock(existing);
248        return;
249    }
250
251    this->addToHead(rec);
252    SkASSERT(1 == rec->fLockCount);
253#ifdef USE_HASH
254    SkASSERT(fHash);
255    fHash->add(rec);
256#endif
257    this->unlock(rec);
258}
259
260void SkResourceCache::unlock(SkResourceCache::ID id) {
261    SkASSERT(id);
262
263#ifdef SK_DEBUG
264    {
265        bool found = false;
266        Rec* rec = fHead;
267        while (rec != NULL) {
268            if (rec == id) {
269                found = true;
270                break;
271            }
272            rec = rec->fNext;
273        }
274        SkASSERT(found);
275    }
276#endif
277    const Rec* rec = id;
278    SkASSERT(rec->fLockCount > 0);
279    // We're under our lock, and we're the only possible mutator, so unconsting is fine.
280    const_cast<Rec*>(rec)->fLockCount -= 1;
281
282    // we may have been over-budget, but now have released something, so check
283    // if we should purge.
284    if (0 == rec->fLockCount) {
285        this->purgeAsNeeded();
286    }
287}
288
289void SkResourceCache::purgeAsNeeded() {
290    size_t byteLimit;
291    int    countLimit;
292
293    if (fDiscardableFactory) {
294        countLimit = SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT;
295        byteLimit = SK_MaxU32;  // no limit based on bytes
296    } else {
297        countLimit = SK_MaxS32; // no limit based on count
298        byteLimit = fTotalByteLimit;
299    }
300
301    size_t bytesUsed = fTotalBytesUsed;
302    int    countUsed = fCount;
303
304    Rec* rec = fTail;
305    while (rec) {
306        if (bytesUsed < byteLimit && countUsed < countLimit) {
307            break;
308        }
309
310        Rec* prev = rec->fPrev;
311        if (0 == rec->fLockCount) {
312            size_t used = rec->bytesUsed();
313            SkASSERT(used <= bytesUsed);
314            this->detach(rec);
315#ifdef USE_HASH
316            fHash->remove(rec->getKey());
317#endif
318
319            SkDELETE(rec);
320
321            bytesUsed -= used;
322            countUsed -= 1;
323        }
324        rec = prev;
325    }
326
327    fTotalBytesUsed = bytesUsed;
328    fCount = countUsed;
329}
330
331size_t SkResourceCache::setTotalByteLimit(size_t newLimit) {
332    size_t prevLimit = fTotalByteLimit;
333    fTotalByteLimit = newLimit;
334    if (newLimit < prevLimit) {
335        this->purgeAsNeeded();
336    }
337    return prevLimit;
338}
339
340///////////////////////////////////////////////////////////////////////////////
341
342void SkResourceCache::detach(Rec* rec) {
343    Rec* prev = rec->fPrev;
344    Rec* next = rec->fNext;
345
346    if (!prev) {
347        SkASSERT(fHead == rec);
348        fHead = next;
349    } else {
350        prev->fNext = next;
351    }
352
353    if (!next) {
354        fTail = prev;
355    } else {
356        next->fPrev = prev;
357    }
358
359    rec->fNext = rec->fPrev = NULL;
360}
361
362void SkResourceCache::moveToHead(Rec* rec) {
363    if (fHead == rec) {
364        return;
365    }
366
367    SkASSERT(fHead);
368    SkASSERT(fTail);
369
370    this->validate();
371
372    this->detach(rec);
373
374    fHead->fPrev = rec;
375    rec->fNext = fHead;
376    fHead = rec;
377
378    this->validate();
379}
380
381void SkResourceCache::addToHead(Rec* rec) {
382    this->validate();
383
384    rec->fPrev = NULL;
385    rec->fNext = fHead;
386    if (fHead) {
387        fHead->fPrev = rec;
388    }
389    fHead = rec;
390    if (!fTail) {
391        fTail = rec;
392    }
393    fTotalBytesUsed += rec->bytesUsed();
394    fCount += 1;
395
396    this->validate();
397}
398
399///////////////////////////////////////////////////////////////////////////////
400
401#ifdef SK_DEBUG
402void SkResourceCache::validate() const {
403    if (NULL == fHead) {
404        SkASSERT(NULL == fTail);
405        SkASSERT(0 == fTotalBytesUsed);
406        return;
407    }
408
409    if (fHead == fTail) {
410        SkASSERT(NULL == fHead->fPrev);
411        SkASSERT(NULL == fHead->fNext);
412        SkASSERT(fHead->bytesUsed() == fTotalBytesUsed);
413        return;
414    }
415
416    SkASSERT(NULL == fHead->fPrev);
417    SkASSERT(fHead->fNext);
418    SkASSERT(NULL == fTail->fNext);
419    SkASSERT(fTail->fPrev);
420
421    size_t used = 0;
422    int count = 0;
423    const Rec* rec = fHead;
424    while (rec) {
425        count += 1;
426        used += rec->bytesUsed();
427        SkASSERT(used <= fTotalBytesUsed);
428        rec = rec->fNext;
429    }
430    SkASSERT(fCount == count);
431
432    rec = fTail;
433    while (rec) {
434        SkASSERT(count > 0);
435        count -= 1;
436        SkASSERT(used >= rec->bytesUsed());
437        used -= rec->bytesUsed();
438        rec = rec->fPrev;
439    }
440
441    SkASSERT(0 == count);
442    SkASSERT(0 == used);
443}
444#endif
445
446void SkResourceCache::dump() const {
447    this->validate();
448
449    const Rec* rec = fHead;
450    int locked = 0;
451    while (rec) {
452        locked += rec->fLockCount > 0;
453        rec = rec->fNext;
454    }
455
456    SkDebugf("SkResourceCache: count=%d bytes=%d locked=%d %s\n",
457             fCount, fTotalBytesUsed, locked,
458             fDiscardableFactory ? "discardable" : "malloc");
459}
460
461size_t SkResourceCache::setSingleAllocationByteLimit(size_t newLimit) {
462    size_t oldLimit = fSingleAllocationByteLimit;
463    fSingleAllocationByteLimit = newLimit;
464    return oldLimit;
465}
466
467size_t SkResourceCache::getSingleAllocationByteLimit() const {
468    return fSingleAllocationByteLimit;
469}
470
471///////////////////////////////////////////////////////////////////////////////
472
473#include "SkThread.h"
474
475SK_DECLARE_STATIC_MUTEX(gMutex);
476static SkResourceCache* gResourceCache = NULL;
477static void cleanup_gResourceCache() {
478    // We'll clean this up in our own tests, but disable for clients.
479    // Chrome seems to have funky multi-process things going on in unit tests that
480    // makes this unsafe to delete when the main process atexit()s.
481    // SkLazyPtr does the same sort of thing.
482#if SK_DEVELOPER
483    SkDELETE(gResourceCache);
484#endif
485}
486
487/** Must hold gMutex when calling. */
488static SkResourceCache* get_cache() {
489    // gMutex is always held when this is called, so we don't need to be fancy in here.
490    gMutex.assertHeld();
491    if (NULL == gResourceCache) {
492#ifdef SK_USE_DISCARDABLE_SCALEDIMAGECACHE
493        gResourceCache = SkNEW_ARGS(SkResourceCache, (SkDiscardableMemory::Create));
494#else
495        gResourceCache = SkNEW_ARGS(SkResourceCache, (SK_DEFAULT_IMAGE_CACHE_LIMIT));
496#endif
497        atexit(cleanup_gResourceCache);
498    }
499    return gResourceCache;
500}
501
502void SkResourceCache::Unlock(SkResourceCache::ID id) {
503    SkAutoMutexAcquire am(gMutex);
504    get_cache()->unlock(id);
505
506//    get_cache()->dump();
507}
508
509size_t SkResourceCache::GetTotalBytesUsed() {
510    SkAutoMutexAcquire am(gMutex);
511    return get_cache()->getTotalBytesUsed();
512}
513
514size_t SkResourceCache::GetTotalByteLimit() {
515    SkAutoMutexAcquire am(gMutex);
516    return get_cache()->getTotalByteLimit();
517}
518
519size_t SkResourceCache::SetTotalByteLimit(size_t newLimit) {
520    SkAutoMutexAcquire am(gMutex);
521    return get_cache()->setTotalByteLimit(newLimit);
522}
523
524SkBitmap::Allocator* SkResourceCache::GetAllocator() {
525    SkAutoMutexAcquire am(gMutex);
526    return get_cache()->allocator();
527}
528
529void SkResourceCache::Dump() {
530    SkAutoMutexAcquire am(gMutex);
531    get_cache()->dump();
532}
533
534size_t SkResourceCache::SetSingleAllocationByteLimit(size_t size) {
535    SkAutoMutexAcquire am(gMutex);
536    return get_cache()->setSingleAllocationByteLimit(size);
537}
538
539size_t SkResourceCache::GetSingleAllocationByteLimit() {
540    SkAutoMutexAcquire am(gMutex);
541    return get_cache()->getSingleAllocationByteLimit();
542}
543
544const SkResourceCache::Rec* SkResourceCache::FindAndLock(const Key& key) {
545    SkAutoMutexAcquire am(gMutex);
546    return get_cache()->findAndLock(key);
547}
548
549const SkResourceCache::Rec* SkResourceCache::AddAndLock(Rec* rec) {
550    SkAutoMutexAcquire am(gMutex);
551    return get_cache()->addAndLock(rec);
552}
553
554void SkResourceCache::Add(Rec* rec) {
555    SkAutoMutexAcquire am(gMutex);
556    get_cache()->add(rec);
557}
558
559///////////////////////////////////////////////////////////////////////////////
560
561#include "SkGraphics.h"
562
563size_t SkGraphics::GetResourceCacheTotalBytesUsed() {
564    return SkResourceCache::GetTotalBytesUsed();
565}
566
567size_t SkGraphics::GetResourceCacheTotalByteLimit() {
568    return SkResourceCache::GetTotalByteLimit();
569}
570
571size_t SkGraphics::SetResourceCacheTotalByteLimit(size_t newLimit) {
572    return SkResourceCache::SetTotalByteLimit(newLimit);
573}
574
575size_t SkGraphics::GetResourceCacheSingleAllocationByteLimit() {
576    return SkResourceCache::GetSingleAllocationByteLimit();
577}
578
579size_t SkGraphics::SetResourceCacheSingleAllocationByteLimit(size_t newLimit) {
580    return SkResourceCache::SetSingleAllocationByteLimit(newLimit);
581}
582
583