SkBitmapScaler::RESIZE_BEST,
dest_width,
dest_height,
- simd)) {
+ simd,
+ SkScaledImageCache::GetAllocator())) {
// we failed to create fScaledBitmap, so just return and let
// the scanline proc handle it.
return false;
#include "SkPixelRef.h"
#include "SkRect.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
}
#endif
-SkScaledImageCache::SkScaledImageCache(size_t byteLimit) {
+void SkScaledImageCache::init() {
fHead = NULL;
fTail = NULL;
#ifdef USE_HASH
fHash = NULL;
#endif
fBytesUsed = 0;
- fByteLimit = byteLimit;
fCount = 0;
+ fAllocator = NULL;
+
+ // One of these should be explicit set by the caller after we return.
+ fByteLimit = 0;
+ fDiscardableFactory = NULL;
+}
+
+#include "SkDiscardableMemory.h"
+
+class SkOneShotDiscardablePixelRef : public SkPixelRef {
+public:
+ // Ownership of the discardablememory is transfered to the pixelref
+ SkOneShotDiscardablePixelRef(const SkImageInfo&, SkDiscardableMemory*, size_t rowBytes);
+ ~SkOneShotDiscardablePixelRef();
+
+ SK_DECLARE_UNFLATTENABLE_OBJECT()
+
+protected:
+ virtual void* onLockPixels(SkColorTable**) SK_OVERRIDE;
+ virtual void onUnlockPixels() SK_OVERRIDE;
+ virtual size_t getAllocatedSizeInBytes() const SK_OVERRIDE;
+
+private:
+ SkImageInfo fInfo; // remove when SkPixelRef gets this in baseclass
+
+ 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)
+{
+ fInfo = info; // remove this redundant field when SkPixelRef has info
+
+ SkASSERT(dm->data());
+ fFirstTime = true;
+}
+
+SkOneShotDiscardablePixelRef::~SkOneShotDiscardablePixelRef() {
+ SkDELETE(fDM);
+}
+
+void* SkOneShotDiscardablePixelRef::onLockPixels(SkColorTable** ctable) {
+ if (fFirstTime) {
+ // we're already locked
+ fFirstTime = false;
+ return fDM->data();
+ }
+ return fDM->lock() ? fDM->data() : NULL;
+}
+
+void SkOneShotDiscardablePixelRef::onUnlockPixels() {
+ SkASSERT(!fFirstTime);
+ fDM->unlock();
+}
+
+size_t SkOneShotDiscardablePixelRef::getAllocatedSizeInBytes() const {
+ return fInfo.fHeight * fRB;
+}
+
+class SkScaledImageCacheDiscardableAllocator : public SkBitmap::Allocator {
+public:
+ SkScaledImageCacheDiscardableAllocator(
+ SkScaledImageCache::DiscardableFactory factory) {
+ SkASSERT(factory);
+ fFactory = factory;
+ }
+
+ virtual bool allocPixelRef(SkBitmap*, SkColorTable*) SK_OVERRIDE;
+
+private:
+ SkScaledImageCache::DiscardableFactory fFactory;
+};
+
+bool SkScaledImageCacheDiscardableAllocator::allocPixelRef(SkBitmap* bitmap,
+ SkColorTable* ctable) {
+ size_t size = bitmap->getSize();
+ if (0 == size) {
+ return false;
+ }
+
+ SkDiscardableMemory* dm = fFactory(size);
+ if (NULL == dm) {
+ return false;
+ }
+
+ // can relax when we have bitmap::asImageInfo
+ if (SkBitmap::kARGB_8888_Config != bitmap->config()) {
+ return false;
+ }
+
+ SkImageInfo info = {
+ bitmap->width(),
+ bitmap->height(),
+ kPMColor_SkColorType,
+ bitmap->alphaType()
+ };
+
+ bitmap->setPixelRef(SkNEW_ARGS(SkOneShotDiscardablePixelRef,
+ (info, dm, bitmap->rowBytes())))->unref();
+ bitmap->lockPixels();
+ return bitmap->readyToDraw();
+}
+
+SkScaledImageCache::SkScaledImageCache(DiscardableFactory factory) {
+ this->init();
+ fDiscardableFactory = factory;
+
+ fAllocator = SkNEW_ARGS(SkScaledImageCacheDiscardableAllocator, (factory));
+}
+
+SkScaledImageCache::SkScaledImageCache(size_t byteLimit) {
+ this->init();
+ fByteLimit = byteLimit;
}
SkScaledImageCache::~SkScaledImageCache() {
+ SkSafeUnref(fAllocator);
+
Rec* rec = fHead;
while (rec) {
Rec* next = rec->fNext;
}
void SkScaledImageCache::purgeAsNeeded() {
- size_t byteLimit = fByteLimit;
- size_t bytesUsed = fBytesUsed;
+ 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 = fByteLimit;
+ }
+ size_t bytesUsed = fBytesUsed;
+ int countUsed = fCount;
+
Rec* rec = fTail;
while (rec) {
- if (bytesUsed < byteLimit) {
+ if (bytesUsed < byteLimit && countUsed < countLimit) {
break;
}
+
Rec* prev = rec->fPrev;
if (0 == rec->fLockCount) {
size_t used = rec->bytesUsed();
SkASSERT(used <= bytesUsed);
- bytesUsed -= used;
this->detach(rec);
#ifdef USE_HASH
fHash->remove(rec->fKey);
#endif
SkDELETE(rec);
- fCount -= 1;
+
+ bytesUsed -= used;
+ countUsed -= 1;
}
rec = prev;
}
+
fBytesUsed = bytesUsed;
+ fCount = countUsed;
}
size_t SkScaledImageCache::setByteLimit(size_t newLimit) {
SK_DECLARE_STATIC_MUTEX(gMutex);
static void create_cache(SkScaledImageCache** cache) {
+#ifdef SK_USE_DISCARDABLE_SCALEDIMAGECACHE
+ *cache = SkNEW_ARGS(SkScaledImageCache, (SkDiscardableMemory::Create));
+#else
*cache = SkNEW_ARGS(SkScaledImageCache, (SK_DEFAULT_IMAGE_CACHE_LIMIT));
+#endif
}
static SkScaledImageCache* get_cache() {
return get_cache()->setByteLimit(newLimit);
}
+SkBitmap::Allocator* SkScaledImageCache::GetAllocator() {
+ SkAutoMutexAcquire am(gMutex);
+ return get_cache()->allocator();
+}
+
///////////////////////////////////////////////////////////////////////////////
#include "SkGraphics.h"
#include "SkBitmap.h"
+class SkDiscardableMemory;
class SkMipMap;
/**
public:
struct ID;
+ /**
+ * Returns a locked/pinned SkDiscardableMemory instance for the specified
+ * number of bytes, or NULL on failure.
+ */
+ typedef SkDiscardableMemory* (*DiscardableFactory)(size_t bytes);
+
/*
* The following static methods are thread-safe wrappers around a global
* instance of this cache.
static size_t GetByteLimit();
static size_t SetByteLimit(size_t newLimit);
+ static SkBitmap::Allocator* GetAllocator();
+
///////////////////////////////////////////////////////////////////////////
+ /**
+ * Construct the cache to call DiscardableFactory when it
+ * allocates memory for the pixels. In this mode, the cache has
+ * not explicit budget, and so methods like getBytesUsed() and
+ * getByteLimit() will return 0, and setByteLimit will ignore its argument
+ * and return 0.
+ */
+ SkScaledImageCache(DiscardableFactory);
+
+ /**
+ * Construct the cache, allocating memory with malloc, and respect the
+ * byteLimit, purging automatically when a new image is added to the cache
+ * that pushes the total bytesUsed over the limit. Note: The limit can be
+ * changed at runtime with setByteLimit.
+ */
SkScaledImageCache(size_t byteLimit);
+
~SkScaledImageCache();
/**
*/
size_t setByteLimit(size_t newLimit);
+ SkBitmap::Allocator* allocator() const { return fAllocator; };
+
public:
struct Rec;
struct Key;
class Hash;
Hash* fHash;
+ DiscardableFactory fDiscardableFactory;
+ // the allocator is NULL or one that matches discardables
+ SkBitmap::Allocator* fAllocator;
+
size_t fBytesUsed;
size_t fByteLimit;
int fCount;
void moveToHead(Rec*);
void addToHead(Rec*);
void detach(Rec*);
+
+ void init(); // called by constructors
+
#ifdef SK_DEBUG
void validate() const;
#else
*/
#include "Test.h"
+#include "SkDiscardableMemory.h"
#include "SkScaledImageCache.h"
static void make_bm(SkBitmap* bm, int w, int h) {
bm->allocPixels();
}
-static void TestImageCache(skiatest::Reporter* reporter) {
- static const int COUNT = 10;
- static const int DIM = 256;
- static const size_t defLimit = DIM * DIM * 4 * COUNT + 1024; // 1K slop
- SkScaledImageCache cache(defLimit);
- SkScaledImageCache::ID* id;
+static const int COUNT = 10;
+static const int DIM = 256;
+static void test_cache(skiatest::Reporter* reporter, SkScaledImageCache& cache,
+ bool testPurge) {
+ SkScaledImageCache::ID* id;
+
SkBitmap bm[COUNT];
-
+
SkScalar scale = 2;
for (int i = 0; i < COUNT; ++i) {
SkBitmap tmp;
-
+
make_bm(&bm[i], DIM, DIM);
id = cache.findAndLock(bm[i], scale, scale, &tmp);
REPORTER_ASSERT(reporter, NULL == id);
-
+
make_bm(&tmp, DIM, DIM);
id = cache.addAndLock(bm[i], scale, scale, tmp);
REPORTER_ASSERT(reporter, NULL != id);
-
+
SkBitmap tmp2;
SkScaledImageCache::ID* id2 = cache.findAndLock(bm[i], scale, scale,
&tmp2);
REPORTER_ASSERT(reporter, tmp.width() == tmp2.width());
REPORTER_ASSERT(reporter, tmp.height() == tmp2.height());
cache.unlock(id2);
-
+
cache.unlock(id);
}
-
- // stress test, should trigger purges
- for (size_t i = 0; i < COUNT * 100; ++i) {
- scale += 1;
-
- SkBitmap tmp;
-
- make_bm(&tmp, DIM, DIM);
- id = cache.addAndLock(bm[0], scale, scale, tmp);
- REPORTER_ASSERT(reporter, NULL != id);
- cache.unlock(id);
+
+ if (testPurge) {
+ // stress test, should trigger purges
+ for (size_t i = 0; i < COUNT * 100; ++i) {
+ scale += 1;
+
+ SkBitmap tmp;
+
+ make_bm(&tmp, DIM, DIM);
+ id = cache.addAndLock(bm[0], scale, scale, tmp);
+ REPORTER_ASSERT(reporter, NULL != id);
+ cache.unlock(id);
+ }
}
-
cache.setByteLimit(0);
}
+static void TestImageCache(skiatest::Reporter* reporter) {
+ {
+ static const size_t defLimit = DIM * DIM * 4 * COUNT + 1024; // 1K slop
+ SkScaledImageCache cache(defLimit);
+ test_cache(reporter, cache, true);
+ }
+ {
+ SkScaledImageCache cache(SkDiscardableMemory::Create);
+ test_cache(reporter, cache, false);
+ }
+}
+
#include "TestClassDef.h"
DEFINE_TESTCLASS("ImageCache", TestImageCacheClass, TestImageCache)