Add SkCachedData and use it for SkMipMap
authorreed <reed@google.com>
Thu, 2 Oct 2014 20:47:08 +0000 (13:47 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 2 Oct 2014 20:47:08 +0000 (13:47 -0700)
Review URL: https://codereview.chromium.org/592843003

14 files changed:
gyp/core.gypi
gyp/tests.gypi
src/core/SkBitmapCache.cpp
src/core/SkBitmapCache.h
src/core/SkBitmapProcState.cpp
src/core/SkCachedData.cpp [new file with mode: 0644]
src/core/SkCachedData.h [new file with mode: 0644]
src/core/SkMipMap.cpp
src/core/SkMipMap.h
src/core/SkResourceCache.cpp
src/core/SkResourceCache.h
tests/CachedDataTest.cpp [new file with mode: 0644]
tests/MipMapTest.cpp
tests/SkResourceCacheTest.cpp

index fb10f3219203e918482e496c4a731389f36b37dd..7c419ed4b406d0db73f3df1bf7b028674b328c6c 100644 (file)
@@ -42,6 +42,7 @@
         '<(skia_src_path)/core/SkBlitter_RGB16.cpp',
         '<(skia_src_path)/core/SkBlitter_Sprite.cpp',
         '<(skia_src_path)/core/SkBuffer.cpp',
+        '<(skia_src_path)/core/SkCachedData.cpp',
         '<(skia_src_path)/core/SkCanvas.cpp',
         '<(skia_src_path)/core/SkChunkAlloc.cpp',
         '<(skia_src_path)/core/SkClipStack.cpp',
index 1d38c488e07f0b7872f3a1398b23d82eff627204..db87f3f7a4d987694d5e4e30ddbd6445c3f80b5d 100644 (file)
@@ -58,6 +58,7 @@
     '../tests/BlendTest.cpp',
     '../tests/BlitRowTest.cpp',
     '../tests/BlurTest.cpp',
+    '../tests/CachedDataTest.cpp',
     '../tests/CachedDecodingPixelRefTest.cpp',
     '../tests/CanvasStateHelpers.cpp',
     '../tests/CanvasStateTest.cpp',
index 5044d305186bfc00ef24a197defe42e94b2ec8be..c1c5d06cae2f418b8ff9a15cadc75a6dc219ee16 100644 (file)
@@ -71,7 +71,7 @@ struct BitmapRec : public SkResourceCache::Rec {
 };
 
 #define CHECK_LOCAL(localCache, localName, globalName, ...) \
-    (localCache) ? localCache->localName(__VA_ARGS__) : SkResourceCache::globalName(__VA_ARGS__)
+    ((localCache) ? localCache->localName(__VA_ARGS__) : SkResourceCache::globalName(__VA_ARGS__))
 
 bool SkBitmapCache::Find(const SkBitmap& src, SkScalar invScaleX, SkScalar invScaleY, SkBitmap* result,
                          SkResourceCache* localCache) {
@@ -125,18 +125,17 @@ bool SkBitmapCache::Add(uint32_t genID, const SkIRect& subset, const SkBitmap& r
 struct MipMapRec : public SkResourceCache::Rec {
     MipMapRec(const SkBitmap& src, const SkMipMap* result)
         : fKey(src.getGenerationID(), 0, 0, get_bounds_from_bitmap(src))
-        , fMipMap(SkRef(result))
-    {}
+        , fMipMap(result)
+    {
+        fMipMap->attachToCacheAndRef();
+    }
 
     virtual ~MipMapRec() {
-        fMipMap->unref();
+        fMipMap->detachFromCacheAndUnref();
     }
 
-    BitmapKey       fKey;
-    const SkMipMap* fMipMap;
-
     virtual const Key& getKey() const SK_OVERRIDE { return fKey; }
-    virtual size_t bytesUsed() const SK_OVERRIDE { return sizeof(fKey) + fMipMap->getSize(); }
+    virtual size_t bytesUsed() const SK_OVERRIDE { return sizeof(fKey) + fMipMap->size(); }
 
     static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextMip) {
         const MipMapRec& rec = static_cast<const MipMapRec&>(baseRec);
@@ -146,20 +145,33 @@ struct MipMapRec : public SkResourceCache::Rec {
         // mipmaps don't use the custom allocator yet, so we don't need to check pixels
         return true;
     }
+
+private:
+    BitmapKey       fKey;
+    const SkMipMap* fMipMap;
 };
 
-const SkMipMap* SkMipMapCache::FindAndRef(const SkBitmap& src) {
+const SkMipMap* SkMipMapCache::FindAndRef(const SkBitmap& src, SkResourceCache* localCache) {
     BitmapKey key(src.getGenerationID(), 0, 0, get_bounds_from_bitmap(src));
     const SkMipMap* result;
-    if (!SkResourceCache::Find(key, MipMapRec::Visitor, &result)) {
+
+    if (!CHECK_LOCAL(localCache, find, Find, key, MipMapRec::Visitor, &result)) {
         result = NULL;
     }
     return result;
 }
 
-void SkMipMapCache::Add(const SkBitmap& src, const SkMipMap* result) {
-    if (result) {
-        SkResourceCache::Add(SkNEW_ARGS(MipMapRec, (src, result)));
+static SkResourceCache::DiscardableFactory get_fact(SkResourceCache* localCache) {
+    return localCache ? localCache->GetDiscardableFactory()
+                      : SkResourceCache::GetDiscardableFactory();
+}
+
+const SkMipMap* SkMipMapCache::AddAndRef(const SkBitmap& src, SkResourceCache* localCache) {
+    SkMipMap* mipmap = SkMipMap::Build(src, get_fact(localCache));
+    if (mipmap) {
+        MipMapRec* rec = SkNEW_ARGS(MipMapRec, (src, mipmap));
+        CHECK_LOCAL(localCache, add, Add, rec);
     }
+    return mipmap;
 }
 
index 181c85812aa86ae8e14fb7d66b54af5c700b59ce..ec8a6b803e9cf69196053a45213565c004c401d3 100644 (file)
@@ -52,8 +52,8 @@ public:
 
 class SkMipMapCache {
 public:
-    static const SkMipMap* FindAndRef(const SkBitmap& src);
-    static void Add(const SkBitmap& src, const SkMipMap* result);
+    static const SkMipMap* FindAndRef(const SkBitmap& src, SkResourceCache* localCache = NULL);
+    static const SkMipMap* AddAndRef(const SkBitmap& src, SkResourceCache* localCache = NULL);
 };
 
 #endif
index 6c1dc30449a81ed0a1ddd7686a2cd5eaab5b1a31..4e9a575e02ec0f2f5424544762f197a14fe06669 100644 (file)
@@ -260,11 +260,10 @@ bool SkBitmapProcState::possiblyScaleImage() {
     if (scaleSqd > SK_Scalar1) {
         fCurrMip.reset(SkMipMapCache::FindAndRef(fOrigBitmap));
         if (NULL == fCurrMip.get()) {
-            fCurrMip.reset(SkMipMap::Build(fOrigBitmap));
+            fCurrMip.reset(SkMipMapCache::AddAndRef(fOrigBitmap));
             if (NULL == fCurrMip.get()) {
                 return false;
             }
-            SkMipMapCache::Add(fOrigBitmap, fCurrMip);
         }
 
         SkScalar levelScale = SkScalarInvert(SkScalarSqrt(scaleSqd));
diff --git a/src/core/SkCachedData.cpp b/src/core/SkCachedData.cpp
new file mode 100644 (file)
index 0000000..93d1198
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCachedData.h"
+#include "SkRefCnt.h"
+#include "SkDiscardableMemory.h"
+
+SkCachedData::SkCachedData(void* data, size_t size)
+    : fData(data)
+    , fSize(size)
+    , fRefCnt(1)
+    , fStorageType(kMalloc_StorageType)
+    , fInCache(false)
+    , fIsLocked(true)
+{
+    fStorage.fMalloc = data;
+}
+
+SkCachedData::SkCachedData(size_t size, SkDiscardableMemory* dm)
+    : fData(dm->data())
+    , fSize(size)
+    , fRefCnt(1)
+    , fStorageType(kDiscardableMemory_StorageType)
+    , fInCache(false)
+    , fIsLocked(true)
+{
+    fStorage.fDM = dm;
+}
+
+SkCachedData::~SkCachedData() {
+    switch (fStorageType) {
+        case kMalloc_StorageType:
+            sk_free(fStorage.fMalloc);
+            break;
+        case kDiscardableMemory_StorageType:
+            SkDELETE(fStorage.fDM);
+            break;
+    }
+}
+
+class SkCachedData::AutoMutexWritable {
+public:
+    AutoMutexWritable(const SkCachedData* cd) : fCD(const_cast<SkCachedData*>(cd)) {
+        fCD->fMutex.acquire();
+        fCD->validate();
+    }
+    ~AutoMutexWritable() {
+        fCD->validate();
+        fCD->fMutex.release();
+    }
+
+    SkCachedData* get() { return fCD; }
+    SkCachedData* operator->() { return fCD; }
+
+private:
+    SkCachedData* fCD;
+};
+
+void SkCachedData::internalRef(bool fromCache) const {
+    AutoMutexWritable(this)->inMutexRef(fromCache);
+}
+
+void SkCachedData::internalUnref(bool fromCache) const {
+    if (AutoMutexWritable(this)->inMutexUnref(fromCache)) {
+        // can't delete inside doInternalUnref, since it is locking a mutex (which we own)
+        SkDELETE(this);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+void SkCachedData::inMutexRef(bool fromCache) {
+    if ((1 == fRefCnt) && fInCache) {
+        this->inMutexLock();
+    }
+    
+    fRefCnt += 1;
+    if (fromCache) {
+        SkASSERT(!fInCache);
+        fInCache = true;
+    }
+}
+
+bool SkCachedData::inMutexUnref(bool fromCache) {
+    switch (--fRefCnt) {
+        case 0:
+            // we're going to be deleted, so we need to be unlocked (for DiscardableMemory)
+            if (fIsLocked) {
+                this->inMutexUnlock();
+            }
+            break;
+        case 1:
+            if (fInCache && !fromCache) {
+                // If we're down to 1 owner, and that owner is the cache, this it is safe
+                // to unlock (and mutate fData) even if the cache is in a different thread,
+                // as the cache is NOT allowed to inspect or use fData.
+                this->inMutexUnlock();
+            }
+            break;
+        default:
+            break;
+    }
+    
+    if (fromCache) {
+        SkASSERT(fInCache);
+        fInCache = false;
+    }
+    
+    // return true when we need to be deleted
+    return 0 == fRefCnt;
+}
+
+void SkCachedData::inMutexLock() {
+    fMutex.assertHeld();
+    
+    SkASSERT(!fIsLocked);
+    fIsLocked = true;
+    
+    switch (fStorageType) {
+        case kMalloc_StorageType:
+            this->setData(fStorage.fMalloc);
+            break;
+        case kDiscardableMemory_StorageType:
+            if (fStorage.fDM->lock()) {
+                this->setData(fStorage.fDM->data());
+            } else {
+                this->setData(NULL);   // signal failure to lock, contents are gone
+            }
+            break;
+    }
+}
+
+void SkCachedData::inMutexUnlock() {
+    fMutex.assertHeld();
+    
+    SkASSERT(fIsLocked);
+    fIsLocked = false;
+    
+    switch (fStorageType) {
+        case kMalloc_StorageType:
+            // nothing to do/check
+            break;
+        case kDiscardableMemory_StorageType:
+            if (fData) {    // did the previous lock succeed?
+                fStorage.fDM->unlock();
+            }
+            break;
+    }
+    this->setData(NULL);   // signal that we're in an unlocked state
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+void SkCachedData::validate() const {
+    if (fIsLocked) {
+        SkASSERT((fInCache && fRefCnt > 1) || !fInCache);
+        switch (fStorageType) {
+            case kMalloc_StorageType:
+                SkASSERT(fData == fStorage.fMalloc);
+                break;
+            case kDiscardableMemory_StorageType:
+                // fData can be null or the actual value, depending if DM's lock succeeded
+                break;
+        }
+    } else {
+        SkASSERT((fInCache && 1 == fRefCnt) || (0 == fRefCnt));
+        SkASSERT(NULL == fData);
+    }
+}
+#endif
diff --git a/src/core/SkCachedData.h b/src/core/SkCachedData.h
new file mode 100644 (file)
index 0000000..886ca3e
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkCachedData_DEFINED
+#define SkCachedData_DEFINED
+
+#include "SkThread.h"
+
+class SkDiscardableMemory;
+
+class SkCachedData : ::SkNoncopyable {
+public:
+    SkCachedData(void* mallocData, size_t size);
+    SkCachedData(size_t size, SkDiscardableMemory*);
+    virtual ~SkCachedData();
+
+    size_t size() const { return fSize; }
+    const void* data() const { return fData; }
+
+    void* writable_data() { return fData; }
+
+    void ref() const { this->internalRef(false); }
+    void unref() const { this->internalUnref(false); }
+
+    int testing_only_getRefCnt() const { return fRefCnt; }
+    bool testing_only_isLocked() const { return fIsLocked; }
+    bool testing_only_isInCache() const { return fInCache; }
+
+protected:
+    // called when fData changes. could be NULL.
+    virtual void onDataChange(void* oldData, void* newData) {}
+
+private:
+    SkMutex fMutex;     // could use a pool of these...
+
+    enum StorageType {
+        kDiscardableMemory_StorageType,
+        kMalloc_StorageType
+    };
+
+    union {
+        SkDiscardableMemory*    fDM;
+        void*                   fMalloc;
+    } fStorage;
+    void*       fData;
+    size_t      fSize;
+    int         fRefCnt;    // low-bit means we're owned by the cache
+    StorageType fStorageType;
+    bool        fInCache;
+    bool        fIsLocked;
+
+    void internalRef(bool fromCache) const;
+    void internalUnref(bool fromCache) const;
+
+    void inMutexRef(bool fromCache);
+    bool inMutexUnref(bool fromCache);  // returns true if we should delete "this"
+    void inMutexLock();
+    void inMutexUnlock();
+
+    // called whenever our fData might change (lock or unlock)
+    void setData(void* newData) {
+        if (newData != fData) {
+            // notify our subclasses of the change
+            this->onDataChange(fData, newData);
+            fData = newData;
+        }
+    }
+
+    class AutoMutexWritable;
+
+public:
+#ifdef SK_DEBUG
+    void validate() const;
+#else
+    void validate() const {}
+#endif
+    
+   /*
+     *  Attaching a data to to a SkResourceCache (only one at a time) enables the data to be
+     *  unlocked when the cache is the only owner, thus freeing it to be purged (assuming the
+     *  data is backed by a SkDiscardableMemory).
+     *
+     *  When attached, it also automatically attempts to "lock" the data when the first client
+     *  ref's the data (typically from a find(key, visitor) call).
+     *
+     *  Thus the data will always be "locked" when a non-cache has a ref on it (whether or not
+     *  the lock succeeded to recover the memory -- check data() to see if it is NULL).
+     */
+
+    /*
+     *  Call when adding this instance to a SkResourceCache::Rec subclass
+     *  (typically in the Rec's constructor).
+     */
+    void attachToCacheAndRef() const { this->internalRef(true); }
+
+    /*
+     *  Call when removing this instance from a SkResourceCache::Rec subclass
+     *  (typically in the Rec's destructor).
+     */
+    void detachFromCacheAndUnref() const { this->internalUnref(true); }
+};
+
+#endif
index cb88eb4596be618e9eab7519f889b2fb6a64d3e0..fdfb660ccc66a2aa06deca8a052350b30ead8aaa 100644 (file)
@@ -109,18 +109,18 @@ static void downsampleby2_proc4444(SkBitmap* dst, int x, int y,
     *dst->getAddr16(x >> 1, y >> 1) = (uint16_t)collaps4444(c >> 2);
 }
 
-SkMipMap::Level* SkMipMap::AllocLevels(int levelCount, size_t pixelSize) {
+size_t SkMipMap::AllocLevelsSize(int levelCount, size_t pixelSize) {
     if (levelCount < 0) {
-        return NULL;
+        return 0;
     }
     int64_t size = sk_64_mul(levelCount + 1, sizeof(Level)) + pixelSize;
     if (!sk_64_isS32(size)) {
-        return NULL;
+        return 0;
     }
-    return (Level*)sk_malloc_throw(sk_64_asS32(size));
+    return sk_64_asS32(size);
 }
 
-SkMipMap* SkMipMap::Build(const SkBitmap& src) {
+SkMipMap* SkMipMap::Build(const SkBitmap& src, SkDiscardableFactoryProc fact) {
     void (*proc)(SkBitmap* dst, int x, int y, const SkBitmap& src);
 
     const SkColorType ct = src.colorType();
@@ -165,11 +165,27 @@ SkMipMap* SkMipMap::Build(const SkBitmap& src) {
         return NULL;
     }
 
-    Level* levels = SkMipMap::AllocLevels(countLevels, size);
-    if (NULL == levels) {
+    size_t storageSize = SkMipMap::AllocLevelsSize(countLevels, size);
+    if (0 == storageSize) {
         return NULL;
     }
 
+    SkMipMap* mipmap;
+    if (fact) {
+        SkDiscardableMemory* dm = fact(storageSize);
+        if (NULL == dm) {
+            return NULL;
+        }
+        mipmap = SkNEW_ARGS(SkMipMap, (storageSize, dm));
+    } else {
+        mipmap = SkNEW_ARGS(SkMipMap, (sk_malloc_throw(storageSize), storageSize));
+    }
+
+    // init
+    mipmap->fCount = countLevels;
+    mipmap->fLevels = (Level*)mipmap->writable_data();
+
+    Level* levels = mipmap->fLevels;
     uint8_t*    baseAddr = (uint8_t*)&levels[countLevels];
     uint8_t*    addr = baseAddr;
     int         width = src.width();
@@ -204,25 +220,13 @@ SkMipMap* SkMipMap::Build(const SkBitmap& src) {
     }
     SkASSERT(addr == baseAddr + size);
 
-    return SkNEW_ARGS(SkMipMap, (levels, countLevels, size));
+    return mipmap;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 //static int gCounter;
 
-SkMipMap::SkMipMap(Level* levels, int count, size_t size)
-    : fSize(size), fLevels(levels), fCount(count) {
-    SkASSERT(levels);
-    SkASSERT(count > 0);
-//    SkDebugf("mips %d\n", ++gCounter);
-}
-
-SkMipMap::~SkMipMap() {
-    sk_free(fLevels);
-//    SkDebugf("mips %d\n", --gCounter);
-}
-
 static SkFixed compute_level(SkScalar scale) {
     SkFixed s = SkAbs32(SkScalarToFixed(SkScalarInvert(scale)));
 
@@ -235,6 +239,10 @@ static SkFixed compute_level(SkScalar scale) {
 }
 
 bool SkMipMap::extractLevel(SkScalar scale, Level* levelPtr) const {
+    if (NULL == fLevels) {
+        return false;
+    }
+
     if (scale >= SK_Scalar1) {
         return false;
     }
index ed912ba976956400f2e49daa89935808dd6f08db..4e83b12b3ed0dbf29bb92d3f7b92b4668a6475de 100644 (file)
@@ -8,14 +8,17 @@
 #ifndef SkMipMap_DEFINED
 #define SkMipMap_DEFINED
 
-#include "SkRefCnt.h"
+#include "SkCachedData.h"
 #include "SkScalar.h"
 
 class SkBitmap;
+class SkDiscardableMemory;
 
-class SkMipMap : public SkRefCnt {
+typedef SkDiscardableMemory* (*SkDiscardableFactoryProc)(size_t bytes);
+
+class SkMipMap : public SkCachedData {
 public:
-    static SkMipMap* Build(const SkBitmap& src);
+    static SkMipMap* Build(const SkBitmap& src, SkDiscardableFactoryProc);
 
     struct Level {
         void*       fPixels;
@@ -26,18 +29,22 @@ public:
 
     bool extractLevel(SkScalar scale, Level*) const;
 
-    size_t getSize() const { return fSize; }
+protected:
+    virtual void onDataChange(void* oldData, void* newData) SK_OVERRIDE {
+        fLevels = (Level*)newData; // could be NULL
+    }
 
 private:
-    size_t  fSize;
     Level*  fLevels;
     int     fCount;
 
     // we take ownership of levels, and will free it with sk_free()
-    SkMipMap(Level* levels, int count, size_t size);
-    virtual ~SkMipMap();
+    SkMipMap(void* malloc, size_t size) : INHERITED(malloc, size) {}
+    SkMipMap(size_t size, SkDiscardableMemory* dm) : INHERITED(size, dm) {}
+
+    static size_t AllocLevelsSize(int levelCount, size_t pixelSize);
 
-    static Level* AllocLevels(int levelCount, size_t pixelSize);
+    typedef SkCachedData INHERITED;
 };
 
 #endif
index 3098a9a2a13cf678f21cd2144d07e5575fe4483b..a673ec2d51aa4c39aa847c858f8d74fb63867e36 100644 (file)
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkCachedData.h"
 #include "SkChecksum.h"
 #include "SkResourceCache.h"
 #include "SkMipMap.h"
@@ -263,6 +264,15 @@ size_t SkResourceCache::setTotalByteLimit(size_t newLimit) {
     return prevLimit;
 }
 
+SkCachedData* SkResourceCache::newCachedData(size_t bytes) {
+    if (fDiscardableFactory) {
+        SkDiscardableMemory* dm = fDiscardableFactory(bytes);
+        return dm ? SkNEW_ARGS(SkCachedData, (bytes, dm)) : NULL;
+    } else {
+        return SkNEW_ARGS(SkCachedData, (sk_malloc_throw(bytes), bytes));
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkResourceCache::detach(Rec* rec) {
@@ -442,6 +452,11 @@ SkBitmap::Allocator* SkResourceCache::GetAllocator() {
     return get_cache()->allocator();
 }
 
+SkCachedData* SkResourceCache::NewCachedData(size_t bytes) {
+    SkAutoMutexAcquire am(gMutex);
+    return get_cache()->newCachedData(bytes);
+}
+
 void SkResourceCache::Dump() {
     SkAutoMutexAcquire am(gMutex);
     get_cache()->dump();
index 52196985d392cd327516f3dd97a7853955ba8c9a..98f6d94cf4723fb65d4be3741c4d43ad7de3b77e 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "SkBitmap.h"
 
+class SkCachedData;
 class SkDiscardableMemory;
 class SkMipMap;
 
@@ -136,6 +137,8 @@ public:
      */
     static SkBitmap::Allocator* GetAllocator();
 
+    static SkCachedData* NewCachedData(size_t bytes);
+
     /**
      *  Call SkDebugf() with diagnostic information about the state of the cache
      */
@@ -197,6 +200,8 @@ public:
     DiscardableFactory discardableFactory() const { return fDiscardableFactory; }
     SkBitmap::Allocator* allocator() const { return fAllocator; };
 
+    SkCachedData* newCachedData(size_t bytes);
+
     /**
      *  Call SkDebugf() with diagnostic information about the state of the cache
      */
diff --git a/tests/CachedDataTest.cpp b/tests/CachedDataTest.cpp
new file mode 100644 (file)
index 0000000..f65a46b
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCachedData.h"
+#include "SkDiscardableMemoryPool.h"
+#include "Test.h"
+
+enum LockedState {
+    kUnlocked,
+    kLocked,
+};
+
+enum CachedState {
+    kNotInCache,
+    kInCache,
+};
+
+static void check_data(skiatest::Reporter* reporter, SkCachedData* data,
+                       int refcnt, CachedState cacheState, LockedState lockedState) {
+    REPORTER_ASSERT(reporter, data->testing_only_getRefCnt() == refcnt);
+    REPORTER_ASSERT(reporter, data->testing_only_isInCache() == (kInCache == cacheState));
+    REPORTER_ASSERT(reporter, data->testing_only_isLocked() == (lockedState == kLocked));
+}
+
+static SkCachedData* make_data(size_t size, SkDiscardableMemoryPool* pool) {
+    if (pool) {
+        SkDiscardableMemory* dm = pool->create(size);
+        // the pool "can" return null, but it shouldn't in these controlled conditions
+        SK_ALWAYSBREAK(dm);
+        return SkNEW_ARGS(SkCachedData, (size, dm));
+    } else {
+        return SkNEW_ARGS(SkCachedData, (sk_malloc_throw(size), size));
+    }
+}
+
+// returns with the data locked by client and cache
+static SkCachedData* test_locking(skiatest::Reporter* reporter,
+                                  size_t size, SkDiscardableMemoryPool* pool) {
+    SkCachedData* data = make_data(size, pool);
+    
+    memset(data->writable_data(), 0x80, size);  // just to use writable_data()
+
+    check_data(reporter, data, 1, kNotInCache, kLocked);
+    
+    data->ref();
+    check_data(reporter, data, 2, kNotInCache, kLocked);
+    data->unref();
+    check_data(reporter, data, 1, kNotInCache, kLocked);
+    
+    data->attachToCacheAndRef();
+    check_data(reporter, data, 2, kInCache, kLocked);
+    
+    data->unref();
+    check_data(reporter, data, 1, kInCache, kUnlocked);
+    
+    data->ref();
+    check_data(reporter, data, 2, kInCache, kLocked);
+
+    return data;
+}
+
+/*
+ *  SkCachedData behaves differently (regarding its locked/unlocked state) depending on
+ *  when it is in the cache or not. Being in the cache is signaled by calling attachToCacheAndRef()
+ *  instead of ref(). (and balanced by detachFromCacheAndUnref).
+ *
+ *  Thus, among other things, we test the end-of-life behavior when the client is the last owner
+ *  and when the cache is.
+ */
+DEF_TEST(CachedData, reporter) {
+    SkAutoTUnref<SkDiscardableMemoryPool> pool(SkDiscardableMemoryPool::Create(1000));
+
+    for (int useDiscardable = 0; useDiscardable <= 1; ++useDiscardable) {
+        const size_t size = 100;
+
+        // test with client as last owner
+        SkCachedData* data = test_locking(reporter, size, useDiscardable ? pool.get() : NULL);
+        check_data(reporter, data, 2, kInCache, kLocked);
+        data->detachFromCacheAndUnref();
+        check_data(reporter, data, 1, kNotInCache, kLocked);
+        data->unref();
+
+        // test with cache as last owner
+        data = test_locking(reporter, size, useDiscardable ? pool.get() : NULL);
+        check_data(reporter, data, 2, kInCache, kLocked);
+        data->unref();
+        check_data(reporter, data, 1, kInCache, kUnlocked);
+        data->detachFromCacheAndUnref();
+    }
+}
+
index 33f467244b44b3e311f69e5f1316e2b09dc0ef88..c8396effa6ea133721fc9397c8d56d46927bb652 100644 (file)
@@ -25,7 +25,7 @@ DEF_TEST(MipMap, reporter) {
 
     for (int i = 0; i < 500; ++i) {
         make_bitmap(&bm, rand);
-        SkAutoTUnref<SkMipMap> mm(SkMipMap::Build(bm));
+        SkAutoTUnref<SkMipMap> mm(SkMipMap::Build(bm, NULL));
 
         REPORTER_ASSERT(reporter, !mm->extractLevel(SK_Scalar1, NULL));
         REPORTER_ASSERT(reporter, !mm->extractLevel(SK_Scalar1 * 2, NULL));
index f13476a5a3b681056b07aae7e9923250c92327f1..0e941758eede8bce9de3d51d53376eb9b398c3e7 100644 (file)
@@ -121,6 +121,55 @@ DEF_TEST(BitmapCache_add_rect, reporter) {
     REPORTER_ASSERT(reporter, SkBitmapCache::Find(cachedBitmap.getGenerationID(), rect, &bm, cache));
 }
 
+#include "SkMipMap.h"
+
+enum LockedState {
+    kNotLocked,
+    kLocked,
+};
+
+enum CachedState {
+    kNotInCache,
+    kInCache,
+};
+
+static void check_data(skiatest::Reporter* reporter, const SkCachedData* data,
+                       int refcnt, CachedState cacheState, LockedState lockedState) {
+    REPORTER_ASSERT(reporter, data->testing_only_getRefCnt() == refcnt);
+    REPORTER_ASSERT(reporter, data->testing_only_isInCache() == (kInCache == cacheState));
+    bool isLocked = (data->data() != NULL);
+    REPORTER_ASSERT(reporter, isLocked == (lockedState == kLocked));
+}
+
+static void test_mipmapcache(skiatest::Reporter* reporter, SkResourceCache* cache) {
+    cache->purgeAll();
+
+    SkBitmap src;
+    src.allocN32Pixels(5, 5);
+    src.setImmutable();
+
+    const SkMipMap* mipmap = SkMipMapCache::FindAndRef(src, cache);
+    REPORTER_ASSERT(reporter, NULL == mipmap);
+
+    mipmap = SkMipMapCache::AddAndRef(src, cache);
+    REPORTER_ASSERT(reporter, mipmap);
+    check_data(reporter, mipmap, 2, kInCache, kLocked);
+
+    mipmap->unref();
+    // tricky, since technically after this I'm no longer an owner, but since the cache is
+    // local, I know it won't get purged behind my back
+    check_data(reporter, mipmap, 1, kInCache, kNotLocked);
+
+    // find us again
+    mipmap = SkMipMapCache::FindAndRef(src, cache);
+    check_data(reporter, mipmap, 2, kInCache, kLocked);
+
+    cache->purgeAll();
+    check_data(reporter, mipmap, 1, kNotInCache, kLocked);
+
+    mipmap->unref();
+}
+
 DEF_TEST(BitmapCache_discarded_bitmap, reporter) {
     SkResourceCache::DiscardableFactory factory = SkResourceCache::GetDiscardableFactory();
     SkBitmap::Allocator* allocator = SkBitmapCache::GetAllocator();
@@ -165,4 +214,6 @@ DEF_TEST(BitmapCache_discarded_bitmap, reporter) {
     // We can add the bitmap back to the cache and find it again.
     REPORTER_ASSERT(reporter, SkBitmapCache::Add(cachedBitmap.getGenerationID(), rect, cachedBitmap, cache));
     REPORTER_ASSERT(reporter, SkBitmapCache::Find(cachedBitmap.getGenerationID(), rect, &bm, cache));
+
+    test_mipmapcache(reporter, cache);
 }