Add SkMaskCache

BUG=skia:

Review URL: https://codereview.chromium.org/670063004
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 4f9379f..553e36d 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -104,6 +104,7 @@
         '<(skia_src_path)/core/SkLineClipper.cpp',
         '<(skia_src_path)/core/SkMallocPixelRef.cpp',
         '<(skia_src_path)/core/SkMask.cpp',
+        '<(skia_src_path)/core/SkMaskCache.cpp',
         '<(skia_src_path)/core/SkMaskFilter.cpp',
         '<(skia_src_path)/core/SkMaskGamma.cpp',
         '<(skia_src_path)/core/SkMaskGamma.h',
diff --git a/gyp/tests.gypi b/gyp/tests.gypi
index 827d6e3..b6e0606 100644
--- a/gyp/tests.gypi
+++ b/gyp/tests.gypi
@@ -133,6 +133,7 @@
     '../tests/LazyPtrTest.cpp',
     '../tests/MD5Test.cpp',
     '../tests/MallocPixelRefTest.cpp',
+    '../tests/MaskCacheTest.cpp',
     '../tests/MathTest.cpp',
     '../tests/Matrix44Test.cpp',
     '../tests/MatrixClipCollapseTest.cpp',
diff --git a/src/core/SkMaskCache.cpp b/src/core/SkMaskCache.cpp
new file mode 100644
index 0000000..4520e97
--- /dev/null
+++ b/src/core/SkMaskCache.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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 "SkMaskCache.h"
+
+#define CHECK_LOCAL(localCache, localName, globalName, ...) \
+    ((localCache) ? localCache->localName(__VA_ARGS__) : SkResourceCache::globalName(__VA_ARGS__))
+
+struct MaskValue {
+    SkMask          fMask;
+    SkCachedData*   fData;
+};
+
+namespace {
+static unsigned gRRectBlurKeyNamespaceLabel;
+
+struct RRectBlurKey : public SkResourceCache::Key {
+public:
+    RRectBlurKey(SkScalar sigma, const SkRRect& rrect, SkBlurStyle style, SkBlurQuality quality)
+        : fSigma(sigma)
+        , fRRect(rrect)
+        , fStyle(style)
+        , fQuality(quality) {
+        this->init(&gRRectBlurKeyNamespaceLabel,
+                   sizeof(fSigma) + sizeof(fRRect) + sizeof(fStyle) + sizeof(fQuality));
+    }
+
+    SkScalar   fSigma;
+    SkRRect    fRRect;
+    int32_t    fStyle;
+    int32_t    fQuality;
+};
+
+struct RRectBlurRec : public SkResourceCache::Rec {
+    RRectBlurRec(RRectBlurKey key, const SkMask& mask, SkCachedData* data)
+        : fKey(key)
+    {
+        fValue.fMask = mask;
+        fValue.fData = data;
+        fValue.fData->attachToCacheAndRef();
+    }
+    ~RRectBlurRec() {
+        fValue.fData->detachFromCacheAndUnref();
+    }
+
+    RRectBlurKey   fKey;
+    MaskValue      fValue;
+
+    virtual const Key& getKey() const SK_OVERRIDE { return fKey; }
+    virtual size_t bytesUsed() const SK_OVERRIDE { return sizeof(*this) + fValue.fData->size(); }
+
+    static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextData) {
+        const RRectBlurRec& rec = static_cast<const RRectBlurRec&>(baseRec);
+        MaskValue* result = (MaskValue*)contextData;
+
+        SkCachedData* tmpData = rec.fValue.fData;
+        tmpData->ref();
+        if (NULL == tmpData->data()) {
+            tmpData->unref();
+            return false;
+        }
+        *result = rec.fValue;
+        return true;
+    }
+};
+} // namespace
+
+SkCachedData* SkMaskCache::FindAndRef(SkScalar sigma, const SkRRect& rrect, SkBlurStyle style,
+                                      SkBlurQuality quality, SkMask* mask,
+                                      SkResourceCache* localCache) {
+    MaskValue result;
+    RRectBlurKey key(sigma, rrect, style, quality);
+    if (!CHECK_LOCAL(localCache, find, Find, key, RRectBlurRec::Visitor, &result)) {
+        return NULL;
+    }
+
+    *mask = result.fMask;
+    mask->fImage = (uint8_t*)(result.fData->data());
+    return result.fData;
+}
+
+void SkMaskCache::Add(SkScalar sigma, const SkRRect& rrect, SkBlurStyle style,
+                      SkBlurQuality quality, const SkMask& mask, SkCachedData* data,
+                      SkResourceCache* localCache) {
+    RRectBlurKey key(sigma, rrect, style, quality);
+    return CHECK_LOCAL(localCache, add, Add, SkNEW_ARGS(RRectBlurRec, (key, mask, data)));
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+static unsigned gRectsBlurKeyNamespaceLabel;
+
+struct RectsBlurKey : public SkResourceCache::Key {
+public:
+    RectsBlurKey(SkScalar sigma, int count, const SkRect rects[], SkBlurStyle style)
+        : fSigma(sigma)
+        , fRecCount(count)
+        , fStyle(style){
+        SkASSERT(1 == count || 2 == count);
+        fRects[0] = SkRect::MakeEmpty();
+        fRects[1] = SkRect::MakeEmpty();
+        for (int i = 0; i < count; i++) {
+            fRects[i] = rects[i];
+        }
+        this->init(&gRectsBlurKeyNamespaceLabel,
+                   sizeof(fSigma) + sizeof(fRecCount) + sizeof(fRects) + sizeof(fStyle));
+    }
+
+    SkScalar    fSigma;
+    int         fRecCount;
+    SkRect      fRects[2];
+    int32_t     fStyle;
+};
+
+struct RectsBlurRec : public SkResourceCache::Rec {
+    RectsBlurRec(RectsBlurKey key, const SkMask& mask, SkCachedData* data)
+        : fKey(key)
+    {
+        fValue.fMask = mask;
+        fValue.fData = data;
+        fValue.fData->attachToCacheAndRef();
+    }
+    ~RectsBlurRec() {
+        fValue.fData->detachFromCacheAndUnref();
+    }
+
+    RectsBlurKey   fKey;
+    MaskValue      fValue;
+
+    virtual const Key& getKey() const SK_OVERRIDE { return fKey; }
+    virtual size_t bytesUsed() const SK_OVERRIDE { return sizeof(*this) + fValue.fData->size(); }
+
+    static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextData) {
+        const RectsBlurRec& rec = static_cast<const RectsBlurRec&>(baseRec);
+        MaskValue* result = (MaskValue*)contextData;
+
+        SkCachedData* tmpData = rec.fValue.fData;
+        tmpData->ref();
+        if (NULL == tmpData->data()) {
+            tmpData->unref();
+            return false;
+        }
+        *result = rec.fValue;
+        return true;
+    }
+};
+} // namespace
+
+SkCachedData* SkMaskCache::FindAndRef(SkScalar sigma, const SkRect rects[], int count,
+                                      SkBlurStyle style, SkMask* mask,
+                                      SkResourceCache* localCache) {
+    MaskValue result;
+    RectsBlurKey key(sigma, count, rects, style);
+    if (!CHECK_LOCAL(localCache, find, Find, key, RectsBlurRec::Visitor, &result)) {
+        return NULL;
+    }
+
+    *mask = result.fMask;
+    mask->fImage = (uint8_t*)(result.fData->data());
+    return result.fData;
+}
+
+void SkMaskCache::Add(SkScalar sigma, const SkRect rects[], int count, SkBlurStyle style,
+                      const SkMask& mask, SkCachedData* data,
+                      SkResourceCache* localCache) {
+    RectsBlurKey key(sigma, count, rects, style);
+    return CHECK_LOCAL(localCache, add, Add, SkNEW_ARGS(RectsBlurRec, (key, mask, data)));
+}
diff --git a/src/core/SkMaskCache.h b/src/core/SkMaskCache.h
new file mode 100644
index 0000000..f44ed38
--- /dev/null
+++ b/src/core/SkMaskCache.h
@@ -0,0 +1,42 @@
+/*
+ * 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 SkMaskCache_DEFINED
+#define SkMaskCache_DEFINED
+
+#include "SkBlurTypes.h"
+#include "SkCachedData.h"
+#include "SkMask.h"
+#include "SkRect.h"
+#include "SkResourceCache.h"
+#include "SkRRect.h"
+
+class SkMaskCache {
+public:
+    /**
+     * On success, return a ref to the SkCachedData that holds the pixels, and have mask
+     * already point to that memory.
+     *
+     * On failure, return NULL.
+     */
+    static SkCachedData* FindAndRef(SkScalar sigma, const SkRRect& rrect, SkBlurStyle style,
+                                    SkBlurQuality quality, SkMask* mask,
+                                    SkResourceCache* localCache = NULL);
+    static SkCachedData* FindAndRef(SkScalar sigma, const SkRect rects[], int count,
+                                    SkBlurStyle style,SkMask* mask,
+                                    SkResourceCache* localCache = NULL);
+
+    /**
+     * Add a mask and its pixel-data to the cache.
+     */
+    static void Add(SkScalar sigma, const SkRRect& rrect, SkBlurStyle style, SkBlurQuality quality,
+                    const SkMask& mask, SkCachedData* data, SkResourceCache* localCache = NULL);
+    static void Add(SkScalar sigma, const SkRect rects[], int count, SkBlurStyle style,
+                    const SkMask& mask, SkCachedData* data, SkResourceCache* localCache = NULL);
+};
+
+#endif
diff --git a/src/core/SkResourceCache.cpp b/src/core/SkResourceCache.cpp
index 1eb53cd..ae8412d 100644
--- a/src/core/SkResourceCache.cpp
+++ b/src/core/SkResourceCache.cpp
@@ -303,6 +303,15 @@
     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) {
@@ -482,6 +491,11 @@
     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();
diff --git a/src/core/SkResourceCache.h b/src/core/SkResourceCache.h
index c16913f..883ed18 100644
--- a/src/core/SkResourceCache.h
+++ b/src/core/SkResourceCache.h
@@ -137,6 +137,8 @@
      */
     static SkBitmap::Allocator* GetAllocator();
 
+    static SkCachedData* NewCachedData(size_t bytes);
+
     /**
      *  Call SkDebugf() with diagnostic information about the state of the cache
      */
diff --git a/tests/MaskCacheTest.cpp b/tests/MaskCacheTest.cpp
new file mode 100644
index 0000000..0b83c62
--- /dev/null
+++ b/tests/MaskCacheTest.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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 "SkMaskCache.h"
+#include "SkResourceCache.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));
+    bool isLocked = (data->data() != NULL);
+    REPORTER_ASSERT(reporter, isLocked == (lockedState == kLocked));
+}
+
+DEF_TEST(RRectMaskCache, reporter) {
+    SkResourceCache cache(1024);
+
+    SkScalar sigma = 0.8f;
+    SkRect rect = SkRect::MakeWH(100, 100);
+    SkRRect rrect;
+    rrect.setRectXY(rect, 30, 30);
+    SkBlurStyle style = kNormal_SkBlurStyle;
+    SkBlurQuality quality = kLow_SkBlurQuality;
+    SkMask mask;
+
+    SkCachedData* data = SkMaskCache::FindAndRef(sigma, rrect, style, quality, &mask, &cache);
+    REPORTER_ASSERT(reporter, NULL == data);
+
+    size_t size = 256;
+    data = cache.newCachedData(size);
+    memset(data->writable_data(), 0xff, size);
+    mask.fBounds.setXYWH(0, 0, 100, 100);
+    mask.fRowBytes = 100;
+    mask.fFormat = SkMask::kBW_Format;
+    SkMaskCache::Add(sigma, rrect, style, quality, mask, data, &cache);
+    check_data(reporter, data, 2, kInCache, kLocked);
+
+    data->unref();
+    check_data(reporter, data, 1, kInCache, kUnlocked);
+
+    sk_bzero(&mask, sizeof(mask));
+    data = SkMaskCache::FindAndRef(sigma, rrect, style, quality, &mask, &cache);
+    REPORTER_ASSERT(reporter, data);
+    REPORTER_ASSERT(reporter, data->size() == size);
+    REPORTER_ASSERT(reporter, mask.fBounds.top() == 0 && mask.fBounds.bottom() == 100);
+    REPORTER_ASSERT(reporter, data->data() == (const void*)mask.fImage);
+    check_data(reporter, data, 2, kInCache, kLocked);
+
+    cache.purgeAll();
+    check_data(reporter, data, 1, kNotInCache, kLocked);
+    data->unref();
+}
+
+DEF_TEST(RectsMaskCache, reporter) {
+    SkResourceCache cache(1024);
+
+    SkScalar sigma = 0.8f;
+    SkRect rect = SkRect::MakeWH(100, 100);
+    SkRect rects[2] = {rect};
+    SkBlurStyle style = kNormal_SkBlurStyle;
+    SkMask mask;
+
+    SkCachedData* data = SkMaskCache::FindAndRef(sigma, rects, 1, style, &mask, &cache);
+    REPORTER_ASSERT(reporter, NULL == data);
+
+    size_t size = 256;
+    data = cache.newCachedData(size);
+    memset(data->writable_data(), 0xff, size);
+    mask.fBounds.setXYWH(0, 0, 100, 100);
+    mask.fRowBytes = 100;
+    mask.fFormat = SkMask::kBW_Format;
+    SkMaskCache::Add(sigma, rects, 1, style, mask, data, &cache);
+    check_data(reporter, data, 2, kInCache, kLocked);
+
+    data->unref();
+    check_data(reporter, data, 1, kInCache, kUnlocked);
+
+    sk_bzero(&mask, sizeof(mask));
+    data = SkMaskCache::FindAndRef(sigma, rects, 1, style, &mask, &cache);
+    REPORTER_ASSERT(reporter, data);
+    REPORTER_ASSERT(reporter, data->size() == size);
+    REPORTER_ASSERT(reporter, mask.fBounds.top() == 0 && mask.fBounds.bottom() == 100);
+    REPORTER_ASSERT(reporter, data->data() == (const void*)mask.fImage);
+    check_data(reporter, data, 2, kInCache, kLocked);
+
+    cache.purgeAll();
+    check_data(reporter, data, 1, kNotInCache, kLocked);
+    data->unref();
+}