Chromium Code Reviews| Index: src/core/SkScaledImageCache.cpp |
| diff --git a/src/core/SkScaledImageCache.cpp b/src/core/SkScaledImageCache.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..1570103e4318c4de56c7d8a25bd2e45a80e509cb |
| --- /dev/null |
| +++ b/src/core/SkScaledImageCache.cpp |
| @@ -0,0 +1,347 @@ |
| +#include "SkScaledImageCache.h" |
| +#include "SkPixelRef.h" |
| +#include "SkRect.h" |
| + |
| +static const size_t DEF_BYTE_LIMIT = 2 * 1024 * 1024; |
|
ernstm
2013/07/23 06:36:50
Two megabyte is quite low. Should we set this to a
reed1
2013/07/23 14:50:29
There is now a define that chrome can set in skia.
|
| + |
| +struct Key { |
| + bool init(const SkBitmap& bm, SkScalar scaleX, SkScalar scaleY) { |
| + SkPixelRef* pr = bm.pixelRef(); |
| + if (!pr) { |
| + return false; |
| + } |
| + |
| + size_t offset = bm.pixelRefOffset(); |
| + size_t rowBytes = bm.rowBytes(); |
| + int x = (offset % rowBytes) >> 2; |
| + int y = offset / rowBytes; |
| + |
| + fGenID = pr->getGenerationID(); |
| + fBounds.set(x, y, x + bm.width(), y + bm.height()); |
| + fScaleX = scaleX; |
| + fScaleY = scaleY; |
| + return true; |
| + } |
| + |
| + bool operator<(const Key& other) { |
| + const uint32_t* a = &fGenID; |
| + const uint32_t* b = &other.fGenID; |
| + for (int i = 0; i < 7; ++i) { |
| + if (a[i] < b[i]) { |
| + return true; |
| + } |
| + if (a[i] > b[i]) { |
| + return false; |
| + } |
| + } |
| + return false; |
| + } |
| + |
| + bool operator==(const Key& other) { |
| + const uint32_t* a = &fGenID; |
| + const uint32_t* b = &other.fGenID; |
| + for (int i = 0; i < 7; ++i) { |
| + if (a[i] != b[i]) { |
| + return false; |
| + } |
| + } |
| + return true; |
| + } |
| + |
| + uint32_t fGenID; |
| + SkIRect fBounds; |
| + float fScaleX; |
| + float fScaleY; |
| +}; |
| + |
| +struct SkScaledImageCache::Rec { |
| + Rec(const Key& key, const SkBitmap& bm) : fKey(key), fBitmap(bm) { |
| + fLockCount = 1; |
| + } |
| + |
| + size_t bytesUsed() const { |
| + return fBitmap.getSize(); |
| + } |
| + |
| + Rec* fNext; |
| + Rec* fPrev; |
| + |
| + int32_t fLockCount; |
| + Key fKey; |
| + SkBitmap fBitmap; |
| +}; |
| + |
| +SkScaledImageCache::SkScaledImageCache() { |
| + fHead = NULL; |
| + fTail = NULL; |
| + fBytesUsed = 0; |
| + fByteLimit = DEF_BYTE_LIMIT; |
| + fCount = 0; |
| +} |
| + |
| +SkScaledImageCache::~SkScaledImageCache() { |
| + Rec* rec = fHead; |
| + while (rec) { |
| + Rec* next = rec->fNext; |
| + SkDELETE(rec); |
| + rec = next; |
| + } |
| +} |
| + |
| +SkScaledImageCache::ID* SkScaledImageCache::findAndLock(const SkBitmap& orig, |
|
humper
2013/07/23 01:25:44
Possibly worth a comment here saying that any nece
reed1
2013/07/23 14:50:29
Done.
|
| + SkScalar scaleX, |
| + SkScalar scaleY, |
| + SkBitmap* scaled) { |
| + Key key; |
| + if (!key.init(orig, scaleX, scaleY)) { |
| + return NULL; |
| + } |
| + |
| + Rec* rec = fHead; |
| + while (rec != NULL) { |
| + if (rec->fKey == key) { |
| + this->moveToHead(rec); // for our LRU |
| + rec->fLockCount += 1; |
| + *scaled = rec->fBitmap; |
| +// SkDebugf("Found: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), rec->fLockCount); |
| + return (ID*)rec; |
| + } |
| + rec = rec->fNext; |
| + } |
| + return NULL; |
| +} |
| + |
| +SkScaledImageCache::ID* SkScaledImageCache::addAndLock(const SkBitmap& orig, |
|
humper
2013/07/23 01:25:44
same as above re: comment
reed1
2013/07/23 14:50:29
Done.
|
| + SkScalar scaleX, |
| + SkScalar scaleY, |
| + const SkBitmap& scaled) { |
| + Key key; |
| + if (!key.init(orig, scaleX, scaleY)) { |
| + return NULL; |
| + } |
| + |
| + Rec* rec = SkNEW_ARGS(Rec, (key, scaled)); |
| + this->addToHead(rec); |
| + SkASSERT(1 == rec->fLockCount); |
| + |
| +// SkDebugf("Added: [%d %d]\n", rec->fBitmap.width(), rec->fBitmap.height()); |
| + |
| + // We may (now) be overbudget, so see if we need to purge something. |
| + this->purgeAsNeeded(); |
| + return (ID*)rec; |
| +} |
| + |
| +void SkScaledImageCache::unlock(SkScaledImageCache::ID* id) { |
|
humper
2013/07/23 01:25:44
ditto above re: comment
reed1
2013/07/23 14:50:29
Done.
|
| + SkASSERT(id); |
| + |
| +#ifdef SK_DEBUG |
| + { |
| + bool found = false; |
| + Rec* rec = fHead; |
| + while (rec != NULL) { |
| + if ((ID*)rec == id) { |
| + found = true; |
| + break; |
| + } |
| + rec = rec->fNext; |
| + } |
| + SkASSERT(found); |
| + } |
| +#endif |
| + Rec* rec = (Rec*)id; |
| + SkASSERT(rec->fLockCount > 0); |
| + rec->fLockCount -= 1; |
| + |
| +// SkDebugf("Unlock: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), rec->fLockCount); |
| + |
| + // we may have been over-budget, but now have released something, so check |
| + // if we should purge. |
| + if (0 == rec->fLockCount) { |
| + this->purgeAsNeeded(); |
| + } |
| +} |
| + |
| +void SkScaledImageCache::purgeAsNeeded() { |
| + size_t byteLimit = fByteLimit; |
| + size_t bytesUsed = fBytesUsed; |
| + |
| + Rec* rec = fTail; |
| + while (rec) { |
| + if (bytesUsed < byteLimit) { |
| + break; |
| + } |
| + Rec* prev = rec->fPrev; |
| + if (0 == rec->fLockCount) { |
| +// SkDebugf("Purge: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), fCount); |
| + size_t used = rec->bytesUsed(); |
| + SkASSERT(used <= bytesUsed); |
| + bytesUsed -= used; |
| + this->detach(rec); |
| + SkDELETE(rec); |
| + fCount -= 1; |
| + } |
| + rec = prev; |
| + } |
| + fBytesUsed = bytesUsed; |
| +} |
| + |
| +/////////////////////////////////////////////////////////////////////////////// |
| + |
| +void SkScaledImageCache::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 SkScaledImageCache::moveToHead(Rec* rec) { |
| + if (fHead == rec) { |
| + return; |
| + } |
| + |
| + SkASSERT(fHead); |
| + SkASSERT(fTail); |
| + |
| + this->validate(); |
| + |
| + if (3 == fCount) |
| + SkDebugf(""); |
|
humper
2013/07/23 01:25:44
Obviously the debug statements are intended to be
reed1
2013/07/23 14:50:29
Done.
|
| + this->detach(rec); |
| + |
| + fHead->fPrev = rec; |
| + rec->fNext = fHead; |
| + fHead = rec; |
| + |
| + this->validate(); |
| +} |
| + |
| +void SkScaledImageCache::addToHead(Rec* rec) { |
| + this->validate(); |
| + |
| + rec->fPrev = NULL; |
| + rec->fNext = fHead; |
| + if (fHead) { |
| + fHead->fPrev = rec; |
| + } |
| + fHead = rec; |
| + if (!fTail) { |
| + fTail = rec; |
| + } |
| + fBytesUsed += rec->bytesUsed(); |
| + fCount += 1; |
| + |
| + this->validate(); |
| +} |
| + |
| +#ifdef SK_DEBUG |
| +void SkScaledImageCache::validate() const { |
| + if (NULL == fHead) { |
| + SkASSERT(NULL == fTail); |
| + SkASSERT(0 == fBytesUsed); |
| + return; |
| + } |
| + |
| + if (fHead == fTail) { |
| + SkASSERT(NULL == fHead->fPrev); |
| + SkASSERT(NULL == fHead->fNext); |
| + SkASSERT(fHead->bytesUsed() == fBytesUsed); |
| + 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 <= fBytesUsed); |
| + 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 |
| + |
| +/////////////////////////////////////////////////////////////////////////////// |
| + |
| +#include "SkThread.h" |
| + |
| +static SkMutex gMutex; |
| + |
| +static SkScaledImageCache* get_cache() { |
| + static SkScaledImageCache* gCache; |
|
humper
2013/07/23 01:25:44
I know the standard says that this will be initial
reed1
2013/07/23 14:50:29
I don't know if anything is written in the doc, bu
|
| + if (!gCache) { |
| + gCache = SkNEW(SkScaledImageCache); |
| + } |
| + return gCache; |
| +} |
| + |
| +SkScaledImageCache::ID* SkScaledImageCache::FindAndLock(const SkBitmap& orig, |
| + SkScalar scaleX, |
| + SkScalar scaleY, |
| + SkBitmap* scaled) { |
| + SkAutoMutexAcquire am(gMutex); |
| + return get_cache()->findAndLock(orig, scaleX, scaleY, scaled); |
| +} |
| + |
| +SkScaledImageCache::ID* SkScaledImageCache::AddAndLock(const SkBitmap& orig, |
| + SkScalar scaleX, |
| + SkScalar scaleY, |
| + const SkBitmap& scaled) { |
| + SkAutoMutexAcquire am(gMutex); |
| + return get_cache()->addAndLock(orig, scaleX, scaleY, scaled); |
| +} |
| + |
| +void SkScaledImageCache::Unlock(SkScaledImageCache::ID* id) { |
| + SkAutoMutexAcquire am(gMutex); |
| + return get_cache()->unlock(id); |
| +} |
| + |
| +size_t SkScaledImageCache::GetByteLimit() { |
| + SkAutoMutexAcquire am(gMutex); |
| + return get_cache()->fByteLimit; |
| +} |
| + |
| +size_t SkScaledImageCache::SetByteLimit(size_t newLimit) { |
| + SkAutoMutexAcquire am(gMutex); |
| + SkScaledImageCache* cache = get_cache(); |
| + size_t prevLimit = cache->fByteLimit; |
| + cache->fByteLimit = newLimit; |
| + cache->purgeAsNeeded(); |
| + return prevLimit; |
| +} |
| + |
| +size_t SkScaledImageCache::GetBytesUsed() { |
| + SkAutoMutexAcquire am(gMutex); |
| + return get_cache()->fBytesUsed; |
| +} |
| + |