rename ScaledImageCache to ResourceCache
BUG=skia:
R=bsalomon@google.com, mtklein@google.com
Author: reed@google.com
Review URL: https://codereview.chromium.org/511283002
diff --git a/src/core/SkResourceCache.cpp b/src/core/SkResourceCache.cpp
new file mode 100644
index 0000000..7ffdfff
--- /dev/null
+++ b/src/core/SkResourceCache.cpp
@@ -0,0 +1,582 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkChecksum.h"
+#include "SkResourceCache.h"
+#include "SkMipMap.h"
+#include "SkPixelRef.h"
+
+// This can be defined by the caller's build system
+//#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE
+
+#ifndef SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT
+# define SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT 1024
+#endif
+
+#ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT
+ #define SK_DEFAULT_IMAGE_CACHE_LIMIT (2 * 1024 * 1024)
+#endif
+
+void SkResourceCache::Key::init(size_t length) {
+ SkASSERT(SkAlign4(length) == length);
+ // 2 is fCount32 and fHash
+ fCount32 = SkToS32(2 + (length >> 2));
+ // skip both of our fields whe computing the murmur
+ fHash = SkChecksum::Murmur3(this->as32() + 2, (fCount32 - 2) << 2);
+}
+
+#include "SkTDynamicHash.h"
+
+class SkResourceCache::Hash :
+ public SkTDynamicHash<SkResourceCache::Rec, SkResourceCache::Key> {};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+// experimental hash to speed things up
+#define USE_HASH
+
+#if !defined(USE_HASH)
+static inline SkResourceCache::Rec* find_rec_in_list(
+ SkResourceCache::Rec* head, const Key & key) {
+ SkResourceCache::Rec* rec = head;
+ while ((rec != NULL) && (rec->fKey != key)) {
+ rec = rec->fNext;
+ }
+ return rec;
+}
+#endif
+
+void SkResourceCache::init() {
+ fHead = NULL;
+ fTail = NULL;
+#ifdef USE_HASH
+ fHash = new Hash;
+#else
+ fHash = NULL;
+#endif
+ fTotalBytesUsed = 0;
+ fCount = 0;
+ fSingleAllocationByteLimit = 0;
+ fAllocator = NULL;
+
+ // One of these should be explicit set by the caller after we return.
+ fTotalByteLimit = 0;
+ fDiscardableFactory = NULL;
+}
+
+#include "SkDiscardableMemory.h"
+
+class SkOneShotDiscardablePixelRef : public SkPixelRef {
+public:
+ SK_DECLARE_INST_COUNT(SkOneShotDiscardablePixelRef)
+ // Ownership of the discardablememory is transfered to the pixelref
+ SkOneShotDiscardablePixelRef(const SkImageInfo&, SkDiscardableMemory*, size_t rowBytes);
+ ~SkOneShotDiscardablePixelRef();
+
+protected:
+ virtual bool onNewLockPixels(LockRec*) SK_OVERRIDE;
+ virtual void onUnlockPixels() SK_OVERRIDE;
+ virtual size_t getAllocatedSizeInBytes() const SK_OVERRIDE;
+
+private:
+ SkDiscardableMemory* fDM;
+ size_t fRB;
+ bool fFirstTime;
+
+ typedef SkPixelRef INHERITED;
+};
+
+SkOneShotDiscardablePixelRef::SkOneShotDiscardablePixelRef(const SkImageInfo& info,
+ SkDiscardableMemory* dm,
+ size_t rowBytes)
+ : INHERITED(info)
+ , fDM(dm)
+ , fRB(rowBytes)
+{
+ SkASSERT(dm->data());
+ fFirstTime = true;
+}
+
+SkOneShotDiscardablePixelRef::~SkOneShotDiscardablePixelRef() {
+ SkDELETE(fDM);
+}
+
+bool SkOneShotDiscardablePixelRef::onNewLockPixels(LockRec* rec) {
+ if (fFirstTime) {
+ // we're already locked
+ SkASSERT(fDM->data());
+ fFirstTime = false;
+ goto SUCCESS;
+ }
+
+ // A previous call to onUnlock may have deleted our DM, so check for that
+ if (NULL == fDM) {
+ return false;
+ }
+
+ if (!fDM->lock()) {
+ // since it failed, we delete it now, to free-up the resource
+ delete fDM;
+ fDM = NULL;
+ return false;
+ }
+
+SUCCESS:
+ rec->fPixels = fDM->data();
+ rec->fColorTable = NULL;
+ rec->fRowBytes = fRB;
+ return true;
+}
+
+void SkOneShotDiscardablePixelRef::onUnlockPixels() {
+ SkASSERT(!fFirstTime);
+ fDM->unlock();
+}
+
+size_t SkOneShotDiscardablePixelRef::getAllocatedSizeInBytes() const {
+ return this->info().getSafeSize(fRB);
+}
+
+class SkResourceCacheDiscardableAllocator : public SkBitmap::Allocator {
+public:
+ SkResourceCacheDiscardableAllocator(SkResourceCache::DiscardableFactory factory) {
+ SkASSERT(factory);
+ fFactory = factory;
+ }
+
+ virtual bool allocPixelRef(SkBitmap*, SkColorTable*) SK_OVERRIDE;
+
+private:
+ SkResourceCache::DiscardableFactory fFactory;
+};
+
+bool SkResourceCacheDiscardableAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
+ size_t size = bitmap->getSize();
+ uint64_t size64 = bitmap->computeSize64();
+ if (0 == size || size64 > (uint64_t)size) {
+ return false;
+ }
+
+ SkDiscardableMemory* dm = fFactory(size);
+ if (NULL == dm) {
+ return false;
+ }
+
+ // can we relax this?
+ if (kN32_SkColorType != bitmap->colorType()) {
+ return false;
+ }
+
+ SkImageInfo info = bitmap->info();
+ bitmap->setPixelRef(SkNEW_ARGS(SkOneShotDiscardablePixelRef,
+ (info, dm, bitmap->rowBytes())))->unref();
+ bitmap->lockPixels();
+ return bitmap->readyToDraw();
+}
+
+SkResourceCache::SkResourceCache(DiscardableFactory factory) {
+ this->init();
+ fDiscardableFactory = factory;
+
+ fAllocator = SkNEW_ARGS(SkResourceCacheDiscardableAllocator, (factory));
+}
+
+SkResourceCache::SkResourceCache(size_t byteLimit) {
+ this->init();
+ fTotalByteLimit = byteLimit;
+}
+
+SkResourceCache::~SkResourceCache() {
+ SkSafeUnref(fAllocator);
+
+ Rec* rec = fHead;
+ while (rec) {
+ Rec* next = rec->fNext;
+ SkDELETE(rec);
+ rec = next;
+ }
+ delete fHash;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const SkResourceCache::Rec* SkResourceCache::findAndLock(const Key& key) {
+#ifdef USE_HASH
+ Rec* rec = fHash->find(key);
+#else
+ Rec* rec = find_rec_in_list(fHead, key);
+#endif
+ if (rec) {
+ this->moveToHead(rec); // for our LRU
+ rec->fLockCount += 1;
+ }
+ return rec;
+}
+
+const SkResourceCache::Rec* SkResourceCache::addAndLock(Rec* rec) {
+ SkASSERT(rec);
+ // See if we already have this key (racy inserts, etc.)
+ const Rec* existing = this->findAndLock(rec->getKey());
+ if (NULL != existing) {
+ SkDELETE(rec);
+ return existing;
+ }
+
+ this->addToHead(rec);
+ SkASSERT(1 == rec->fLockCount);
+#ifdef USE_HASH
+ SkASSERT(fHash);
+ fHash->add(rec);
+#endif
+ // We may (now) be overbudget, so see if we need to purge something.
+ this->purgeAsNeeded();
+ return rec;
+}
+
+void SkResourceCache::add(Rec* rec) {
+ SkASSERT(rec);
+ // See if we already have this key (racy inserts, etc.)
+ const Rec* existing = this->findAndLock(rec->getKey());
+ if (NULL != existing) {
+ SkDELETE(rec);
+ this->unlock(existing);
+ return;
+ }
+
+ this->addToHead(rec);
+ SkASSERT(1 == rec->fLockCount);
+#ifdef USE_HASH
+ SkASSERT(fHash);
+ fHash->add(rec);
+#endif
+ this->unlock(rec);
+}
+
+void SkResourceCache::unlock(SkResourceCache::ID id) {
+ SkASSERT(id);
+
+#ifdef SK_DEBUG
+ {
+ bool found = false;
+ Rec* rec = fHead;
+ while (rec != NULL) {
+ if (rec == id) {
+ found = true;
+ break;
+ }
+ rec = rec->fNext;
+ }
+ SkASSERT(found);
+ }
+#endif
+ const Rec* rec = id;
+ SkASSERT(rec->fLockCount > 0);
+ // We're under our lock, and we're the only possible mutator, so unconsting is fine.
+ const_cast<Rec*>(rec)->fLockCount -= 1;
+
+ // we may have been over-budget, but now have released something, so check
+ // if we should purge.
+ if (0 == rec->fLockCount) {
+ this->purgeAsNeeded();
+ }
+}
+
+void SkResourceCache::purgeAsNeeded() {
+ size_t byteLimit;
+ int countLimit;
+
+ if (fDiscardableFactory) {
+ countLimit = SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT;
+ byteLimit = SK_MaxU32; // no limit based on bytes
+ } else {
+ countLimit = SK_MaxS32; // no limit based on count
+ byteLimit = fTotalByteLimit;
+ }
+
+ size_t bytesUsed = fTotalBytesUsed;
+ int countUsed = fCount;
+
+ Rec* rec = fTail;
+ while (rec) {
+ if (bytesUsed < byteLimit && countUsed < countLimit) {
+ break;
+ }
+
+ Rec* prev = rec->fPrev;
+ if (0 == rec->fLockCount) {
+ size_t used = rec->bytesUsed();
+ SkASSERT(used <= bytesUsed);
+ this->detach(rec);
+#ifdef USE_HASH
+ fHash->remove(rec->getKey());
+#endif
+
+ SkDELETE(rec);
+
+ bytesUsed -= used;
+ countUsed -= 1;
+ }
+ rec = prev;
+ }
+
+ fTotalBytesUsed = bytesUsed;
+ fCount = countUsed;
+}
+
+size_t SkResourceCache::setTotalByteLimit(size_t newLimit) {
+ size_t prevLimit = fTotalByteLimit;
+ fTotalByteLimit = newLimit;
+ if (newLimit < prevLimit) {
+ this->purgeAsNeeded();
+ }
+ return prevLimit;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkResourceCache::detach(Rec* rec) {
+ Rec* prev = rec->fPrev;
+ Rec* next = rec->fNext;
+
+ if (!prev) {
+ SkASSERT(fHead == rec);
+ fHead = next;
+ } else {
+ prev->fNext = next;
+ }
+
+ if (!next) {
+ fTail = prev;
+ } else {
+ next->fPrev = prev;
+ }
+
+ rec->fNext = rec->fPrev = NULL;
+}
+
+void SkResourceCache::moveToHead(Rec* rec) {
+ if (fHead == rec) {
+ return;
+ }
+
+ SkASSERT(fHead);
+ SkASSERT(fTail);
+
+ this->validate();
+
+ this->detach(rec);
+
+ fHead->fPrev = rec;
+ rec->fNext = fHead;
+ fHead = rec;
+
+ this->validate();
+}
+
+void SkResourceCache::addToHead(Rec* rec) {
+ this->validate();
+
+ rec->fPrev = NULL;
+ rec->fNext = fHead;
+ if (fHead) {
+ fHead->fPrev = rec;
+ }
+ fHead = rec;
+ if (!fTail) {
+ fTail = rec;
+ }
+ fTotalBytesUsed += rec->bytesUsed();
+ fCount += 1;
+
+ this->validate();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+void SkResourceCache::validate() const {
+ if (NULL == fHead) {
+ SkASSERT(NULL == fTail);
+ SkASSERT(0 == fTotalBytesUsed);
+ return;
+ }
+
+ if (fHead == fTail) {
+ SkASSERT(NULL == fHead->fPrev);
+ SkASSERT(NULL == fHead->fNext);
+ SkASSERT(fHead->bytesUsed() == fTotalBytesUsed);
+ return;
+ }
+
+ SkASSERT(NULL == fHead->fPrev);
+ SkASSERT(NULL != fHead->fNext);
+ SkASSERT(NULL == fTail->fNext);
+ SkASSERT(NULL != fTail->fPrev);
+
+ size_t used = 0;
+ int count = 0;
+ const Rec* rec = fHead;
+ while (rec) {
+ count += 1;
+ used += rec->bytesUsed();
+ SkASSERT(used <= fTotalBytesUsed);
+ rec = rec->fNext;
+ }
+ SkASSERT(fCount == count);
+
+ rec = fTail;
+ while (rec) {
+ SkASSERT(count > 0);
+ count -= 1;
+ SkASSERT(used >= rec->bytesUsed());
+ used -= rec->bytesUsed();
+ rec = rec->fPrev;
+ }
+
+ SkASSERT(0 == count);
+ SkASSERT(0 == used);
+}
+#endif
+
+void SkResourceCache::dump() const {
+ this->validate();
+
+ const Rec* rec = fHead;
+ int locked = 0;
+ while (rec) {
+ locked += rec->fLockCount > 0;
+ rec = rec->fNext;
+ }
+
+ SkDebugf("SkResourceCache: count=%d bytes=%d locked=%d %s\n",
+ fCount, fTotalBytesUsed, locked,
+ fDiscardableFactory ? "discardable" : "malloc");
+}
+
+size_t SkResourceCache::setSingleAllocationByteLimit(size_t newLimit) {
+ size_t oldLimit = fSingleAllocationByteLimit;
+ fSingleAllocationByteLimit = newLimit;
+ return oldLimit;
+}
+
+size_t SkResourceCache::getSingleAllocationByteLimit() const {
+ return fSingleAllocationByteLimit;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkThread.h"
+
+SK_DECLARE_STATIC_MUTEX(gMutex);
+static SkResourceCache* gResourceCache = NULL;
+static void cleanup_gResourceCache() {
+ // We'll clean this up in our own tests, but disable for clients.
+ // Chrome seems to have funky multi-process things going on in unit tests that
+ // makes this unsafe to delete when the main process atexit()s.
+ // SkLazyPtr does the same sort of thing.
+#if SK_DEVELOPER
+ SkDELETE(gResourceCache);
+#endif
+}
+
+/** Must hold gMutex when calling. */
+static SkResourceCache* get_cache() {
+ // gMutex is always held when this is called, so we don't need to be fancy in here.
+ gMutex.assertHeld();
+ if (NULL == gResourceCache) {
+#ifdef SK_USE_DISCARDABLE_SCALEDIMAGECACHE
+ gResourceCache = SkNEW_ARGS(SkResourceCache, (SkDiscardableMemory::Create));
+#else
+ gResourceCache = SkNEW_ARGS(SkResourceCache, (SK_DEFAULT_IMAGE_CACHE_LIMIT));
+#endif
+ atexit(cleanup_gResourceCache);
+ }
+ return gResourceCache;
+}
+
+void SkResourceCache::Unlock(SkResourceCache::ID id) {
+ SkAutoMutexAcquire am(gMutex);
+ get_cache()->unlock(id);
+
+// get_cache()->dump();
+}
+
+size_t SkResourceCache::GetTotalBytesUsed() {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->getTotalBytesUsed();
+}
+
+size_t SkResourceCache::GetTotalByteLimit() {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->getTotalByteLimit();
+}
+
+size_t SkResourceCache::SetTotalByteLimit(size_t newLimit) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->setTotalByteLimit(newLimit);
+}
+
+SkBitmap::Allocator* SkResourceCache::GetAllocator() {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->allocator();
+}
+
+void SkResourceCache::Dump() {
+ SkAutoMutexAcquire am(gMutex);
+ get_cache()->dump();
+}
+
+size_t SkResourceCache::SetSingleAllocationByteLimit(size_t size) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->setSingleAllocationByteLimit(size);
+}
+
+size_t SkResourceCache::GetSingleAllocationByteLimit() {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->getSingleAllocationByteLimit();
+}
+
+const SkResourceCache::Rec* SkResourceCache::FindAndLock(const Key& key) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->findAndLock(key);
+}
+
+const SkResourceCache::Rec* SkResourceCache::AddAndLock(Rec* rec) {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->addAndLock(rec);
+}
+
+void SkResourceCache::Add(Rec* rec) {
+ SkAutoMutexAcquire am(gMutex);
+ get_cache()->add(rec);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkGraphics.h"
+
+size_t SkGraphics::GetResourceCacheTotalBytesUsed() {
+ return SkResourceCache::GetTotalBytesUsed();
+}
+
+size_t SkGraphics::GetResourceCacheTotalByteLimit() {
+ return SkResourceCache::GetTotalByteLimit();
+}
+
+size_t SkGraphics::SetResourceCacheTotalByteLimit(size_t newLimit) {
+ return SkResourceCache::SetTotalByteLimit(newLimit);
+}
+
+size_t SkGraphics::GetResourceCacheSingleAllocationByteLimit() {
+ return SkResourceCache::GetSingleAllocationByteLimit();
+}
+
+size_t SkGraphics::SetResourceCacheSingleAllocationByteLimit(size_t newLimit) {
+ return SkResourceCache::SetSingleAllocationByteLimit(newLimit);
+}
+