support scaledimagecache instantiable using discardablememory
authorreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 9 Dec 2013 22:29:30 +0000 (22:29 +0000)
committerreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 9 Dec 2013 22:29:30 +0000 (22:29 +0000)
Add this to your build/gyp system to use discardable instead of malloc+budget

#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE

R=halcanary@google.com

Review URL: https://codereview.chromium.org/105933003

git-svn-id: http://skia.googlecode.com/svn/trunk@12588 2bbb7eff-a529-9590-31e7-b0007b416f81

src/core/SkBitmapProcState.cpp
src/core/SkScaledImageCache.cpp
src/core/SkScaledImageCache.h
tests/ImageCacheTest.cpp

index 59483d66a151ca6a4172a4c7289d4eb60a9949f1..c93a2b5cd7799fc12d6627fdf7c259e711a9cc76 100644 (file)
@@ -147,7 +147,8 @@ bool SkBitmapProcState::possiblyScaleImage() {
                                         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;
index eba20c4f906644ce16307a4e18d30963d5188e1e..25e29c2fb74aa3738e8b6f7a92ab0f07523dcc78 100644 (file)
 #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
@@ -164,7 +171,7 @@ static inline SkScaledImageCache::Rec* find_rec_in_list(
 }
 #endif
 
-SkScaledImageCache::SkScaledImageCache(size_t byteLimit) {
+void SkScaledImageCache::init() {
     fHead = NULL;
     fTail = NULL;
 #ifdef USE_HASH
@@ -173,11 +180,133 @@ SkScaledImageCache::SkScaledImageCache(size_t byteLimit) {
     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;
@@ -367,30 +496,45 @@ void SkScaledImageCache::unlock(SkScaledImageCache::ID* id) {
 }
 
 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) {
@@ -513,7 +657,11 @@ void SkScaledImageCache::validate() const {
 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() {
@@ -592,6 +740,11 @@ size_t SkScaledImageCache::SetByteLimit(size_t newLimit) {
     return get_cache()->setByteLimit(newLimit);
 }
 
+SkBitmap::Allocator* SkScaledImageCache::GetAllocator() {
+    SkAutoMutexAcquire am(gMutex);
+    return get_cache()->allocator();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkGraphics.h"
index 44ef1f8a2cb8e19369452ae191514eb0cdf3b98c..311db325be887ccddfa36b6790c0639387271786 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "SkBitmap.h"
 
+class SkDiscardableMemory;
 class SkMipMap;
 
 /**
@@ -26,6 +27,12 @@ class SkScaledImageCache {
 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.
@@ -57,9 +64,27 @@ public:
     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();
 
     /**
@@ -124,6 +149,8 @@ public:
      */
     size_t setByteLimit(size_t newLimit);
 
+    SkBitmap::Allocator* allocator() const { return fAllocator; };
+
 public:
     struct Rec;
     struct Key;
@@ -134,6 +161,10 @@ private:
     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;
@@ -149,6 +180,9 @@ private:
     void moveToHead(Rec*);
     void addToHead(Rec*);
     void detach(Rec*);
+
+    void init();    // called by constructors
+
 #ifdef SK_DEBUG
     void validate() const;
 #else
index b8815a32170da030c332ff12ef16889bfc2ee2bb..69de629579f857ca2799892b9b37db9794912e32 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include "Test.h"
+#include "SkDiscardableMemory.h"
 #include "SkScaledImageCache.h"
 
 static void make_bm(SkBitmap* bm, int w, int h) {
@@ -13,27 +14,27 @@ 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);
@@ -42,25 +43,38 @@ static void TestImageCache(skiatest::Reporter* reporter) {
         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)