/* * 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 "SkLruImageCache.h" SK_DEFINE_INST_COUNT(SkImageCache) SK_DEFINE_INST_COUNT(SkLruImageCache) static intptr_t NextGenerationID() { static intptr_t gNextID; do { gNextID++; } while (SkImageCache::UNINITIALIZED_ID == gNextID); return gNextID; } class CachedPixels : public SkNoncopyable { public: CachedPixels(size_t length) : fLength(length) , fID(NextGenerationID()) , fLocked(false) { fAddr = sk_malloc_throw(length); } ~CachedPixels() { sk_free(fAddr); } void* getData() { return fAddr; } intptr_t getID() const { return fID; } size_t getLength() const { return fLength; } void lock() { SkASSERT(!fLocked); fLocked = true; } void unlock() { SkASSERT(fLocked); fLocked = false; } bool isLocked() const { return fLocked; } private: void* fAddr; size_t fLength; const intptr_t fID; bool fLocked; SK_DECLARE_INTERNAL_LLIST_INTERFACE(CachedPixels); }; //////////////////////////////////////////////////////////////////////////////////// SkLruImageCache::SkLruImageCache(size_t budget) : fRamBudget(budget) , fRamUsed(0) {} SkLruImageCache::~SkLruImageCache() { // Don't worry about updating pointers. All will be deleted. Iter iter; CachedPixels* pixels = iter.init(fLRU, Iter::kTail_IterStart); while (pixels != NULL) { CachedPixels* prev = iter.prev(); SkASSERT(!pixels->isLocked()); #ifdef SK_DEBUG fRamUsed -= pixels->getLength(); #endif SkDELETE(pixels); pixels = prev; } #ifdef SK_DEBUG SkASSERT(fRamUsed == 0); #endif } #ifdef SK_DEBUG SkImageCache::MemoryStatus SkLruImageCache::getMemoryStatus(intptr_t ID) const { if (SkImageCache::UNINITIALIZED_ID == ID) { return SkImageCache::kFreed_MemoryStatus; } SkAutoMutexAcquire ac(&fMutex); CachedPixels* pixels = this->findByID(ID); if (NULL == pixels) { return SkImageCache::kFreed_MemoryStatus; } if (pixels->isLocked()) { return SkImageCache::kPinned_MemoryStatus; } return SkImageCache::kUnpinned_MemoryStatus; } void SkLruImageCache::purgeAllUnpinnedCaches() { SkAutoMutexAcquire ac(&fMutex); this->purgeTilAtOrBelow(0); } #endif size_t SkLruImageCache::setImageCacheLimit(size_t newLimit) { size_t oldLimit = fRamBudget; SkAutoMutexAcquire ac(&fMutex); fRamBudget = newLimit; this->purgeIfNeeded(); return oldLimit; } void* SkLruImageCache::allocAndPinCache(size_t bytes, intptr_t* ID) { SkAutoMutexAcquire ac(&fMutex); CachedPixels* pixels = SkNEW_ARGS(CachedPixels, (bytes)); if (ID != NULL) { *ID = pixels->getID(); } pixels->lock(); fRamUsed += bytes; fLRU.addToHead(pixels); this->purgeIfNeeded(); return pixels->getData(); } void* SkLruImageCache::pinCache(intptr_t ID, SkImageCache::DataStatus* status) { SkASSERT(ID != SkImageCache::UNINITIALIZED_ID); SkAutoMutexAcquire ac(&fMutex); CachedPixels* pixels = this->findByID(ID); if (NULL == pixels) { return NULL; } if (pixels != fLRU.head()) { fLRU.remove(pixels); fLRU.addToHead(pixels); } SkASSERT(status != NULL); // This cache will never return pinned memory whose data has been overwritten. *status = SkImageCache::kRetained_DataStatus; pixels->lock(); return pixels->getData(); } void SkLruImageCache::releaseCache(intptr_t ID) { SkASSERT(ID != SkImageCache::UNINITIALIZED_ID); SkAutoMutexAcquire ac(&fMutex); CachedPixels* pixels = this->findByID(ID); SkASSERT(pixels != NULL); pixels->unlock(); this->purgeIfNeeded(); } void SkLruImageCache::throwAwayCache(intptr_t ID) { SkASSERT(ID != SkImageCache::UNINITIALIZED_ID); SkAutoMutexAcquire ac(&fMutex); CachedPixels* pixels = this->findByID(ID); if (pixels != NULL) { if (pixels->isLocked()) { pixels->unlock(); } this->removePixels(pixels); } } void SkLruImageCache::removePixels(CachedPixels* pixels) { // Mutex is already locked. SkASSERT(!pixels->isLocked()); const size_t size = pixels->getLength(); SkASSERT(size <= fRamUsed); fLRU.remove(pixels); SkDELETE(pixels); fRamUsed -= size; } CachedPixels* SkLruImageCache::findByID(intptr_t ID) const { // Mutex is already locked. Iter iter; // Start from the head, most recently used. CachedPixels* pixels = iter.init(fLRU, Iter::kHead_IterStart); while (pixels != NULL) { if (pixels->getID() == ID) { return pixels; } pixels = iter.next(); } return NULL; } void SkLruImageCache::purgeIfNeeded() { // Mutex is already locked. if (fRamBudget > 0) { this->purgeTilAtOrBelow(fRamBudget); } } void SkLruImageCache::purgeTilAtOrBelow(size_t limit) { // Mutex is already locked. if (fRamUsed > limit) { Iter iter; // Start from the tail, least recently used. CachedPixels* pixels = iter.init(fLRU, Iter::kTail_IterStart); while (pixels != NULL && fRamUsed > limit) { CachedPixels* prev = iter.prev(); if (!pixels->isLocked()) { this->removePixels(pixels); } pixels = prev; } } }