Notify resource caches when pixelref genID goes stale

patch from issue 954443002 at patchset 40001 (http://crrev.com/954443002#ps40001)

BUG=skia:

Review URL: https://codereview.chromium.org/950363002
diff --git a/src/core/SkBitmapCache.cpp b/src/core/SkBitmapCache.cpp
index 3af5822..c411a1b 100644
--- a/src/core/SkBitmapCache.cpp
+++ b/src/core/SkBitmapCache.cpp
@@ -10,6 +10,20 @@
 #include "SkMipMap.h"
 #include "SkRect.h"
 
+/**
+ *  Use this for bitmapcache and mipmapcache entries.
+ */
+uint64_t SkMakeResourceCacheSharedIDForBitmap(uint32_t bitmapGenID) {
+    uint64_t sharedID = SkSetFourByteTag('b', 'm', 'a', 'p');
+    return (sharedID << 32) | bitmapGenID;
+}
+
+void SkNotifyBitmapGenIDIsStale(uint32_t bitmapGenID) {
+    SkResourceCache::PostPurgeSharedID(SkMakeResourceCacheSharedIDForBitmap(bitmapGenID));
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
 SkBitmap::Allocator* SkBitmapCache::GetAllocator() {
     return SkResourceCache::GetAllocator();
 }
@@ -33,13 +47,13 @@
 
 struct BitmapKey : public SkResourceCache::Key {
 public:
-    BitmapKey(uint32_t genID, SkScalar scaleX, SkScalar scaleY, const SkIRect& bounds)
-    : fGenID(genID)
-    , fScaleX(scaleX)
-    , fScaleY(scaleY)
-    , fBounds(bounds)
+    BitmapKey(uint32_t genID, SkScalar sx, SkScalar sy, const SkIRect& bounds)
+        : fGenID(genID)
+        , fScaleX(sx)
+        , fScaleY(sy)
+        , fBounds(bounds)
     {
-        this->init(&gBitmapKeyNamespaceLabel,
+        this->init(&gBitmapKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(genID),
                    sizeof(fGenID) + sizeof(fScaleX) + sizeof(fScaleY) + sizeof(fBounds));
     }
 
@@ -49,8 +63,6 @@
     SkIRect     fBounds;
 };
 
-//////////////////////////////////////////////////////////////////////////////////////////
-
 struct BitmapRec : public SkResourceCache::Rec {
     BitmapRec(uint32_t genID, SkScalar scaleX, SkScalar scaleY, const SkIRect& bounds,
               const SkBitmap& result)
@@ -58,13 +70,10 @@
         , fBitmap(result)
     {}
 
-    BitmapKey   fKey;
-    SkBitmap    fBitmap;
-
     const Key& getKey() const SK_OVERRIDE { return fKey; }
     size_t bytesUsed() const SK_OVERRIDE { return sizeof(fKey) + fBitmap.getSize(); }
 
-    static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextBitmap) {
+    static bool Finder(const SkResourceCache::Rec& baseRec, void* contextBitmap) {
         const BitmapRec& rec = static_cast<const BitmapRec&>(baseRec);
         SkBitmap* result = (SkBitmap*)contextBitmap;
 
@@ -72,6 +81,10 @@
         result->lockPixels();
         return SkToBool(result->getPixels());
     }
+
+private:
+    BitmapKey   fKey;
+    SkBitmap    fBitmap;
 };
 } // namespace
 
@@ -86,7 +99,7 @@
     }
     BitmapKey key(src.getGenerationID(), invScaleX, invScaleY, get_bounds_from_bitmap(src));
 
-    return CHECK_LOCAL(localCache, find, Find, key, BitmapRec::Visitor, result);
+    return CHECK_LOCAL(localCache, find, Find, key, BitmapRec::Finder, result);
 }
 
 void SkBitmapCache::Add(const SkBitmap& src, SkScalar invScaleX, SkScalar invScaleY,
@@ -105,7 +118,7 @@
                          SkResourceCache* localCache) {
     BitmapKey key(genID, SK_Scalar1, SK_Scalar1, subset);
 
-    return CHECK_LOCAL(localCache, find, Find, key, BitmapRec::Visitor, result);
+    return CHECK_LOCAL(localCache, find, Find, key, BitmapRec::Finder, result);
 }
 
 bool SkBitmapCache::Add(uint32_t genID, const SkIRect& subset, const SkBitmap& result,
@@ -125,11 +138,27 @@
         return true;
     }
 }
+
 //////////////////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+static unsigned gMipMapKeyNamespaceLabel;
+
+struct MipMapKey : public SkResourceCache::Key {
+public:
+    MipMapKey(uint32_t genID, const SkIRect& bounds) : fGenID(genID), fBounds(bounds) {
+        this->init(&gMipMapKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(genID),
+                   sizeof(fGenID) + sizeof(fBounds));
+    }
+
+    uint32_t    fGenID;
+    SkIRect     fBounds;
+};
 
 struct MipMapRec : public SkResourceCache::Rec {
     MipMapRec(const SkBitmap& src, const SkMipMap* result)
-        : fKey(src.getGenerationID(), 0, 0, get_bounds_from_bitmap(src))
+        : fKey(src.getGenerationID(), get_bounds_from_bitmap(src))
         , fMipMap(result)
     {
         fMipMap->attachToCacheAndRef();
@@ -142,7 +171,7 @@
     const Key& getKey() const SK_OVERRIDE { return fKey; }
     size_t bytesUsed() const SK_OVERRIDE { return sizeof(fKey) + fMipMap->size(); }
 
-    static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextMip) {
+    static bool Finder(const SkResourceCache::Rec& baseRec, void* contextMip) {
         const MipMapRec& rec = static_cast<const MipMapRec&>(baseRec);
         const SkMipMap* mm = SkRef(rec.fMipMap);
         // the call to ref() above triggers a "lock" in the case of discardable memory,
@@ -157,15 +186,16 @@
     }
 
 private:
-    BitmapKey       fKey;
+    MipMapKey       fKey;
     const SkMipMap* fMipMap;
 };
+}
 
 const SkMipMap* SkMipMapCache::FindAndRef(const SkBitmap& src, SkResourceCache* localCache) {
-    BitmapKey key(src.getGenerationID(), 0, 0, get_bounds_from_bitmap(src));
+    MipMapKey key(src.getGenerationID(), get_bounds_from_bitmap(src));
     const SkMipMap* result;
 
-    if (!CHECK_LOCAL(localCache, find, Find, key, MipMapRec::Visitor, &result)) {
+    if (!CHECK_LOCAL(localCache, find, Find, key, MipMapRec::Finder, &result)) {
         result = NULL;
     }
     return result;
@@ -184,4 +214,3 @@
     }
     return mipmap;
 }
-
diff --git a/src/core/SkBitmapCache.h b/src/core/SkBitmapCache.h
index ec8a6b8..c54f961 100644
--- a/src/core/SkBitmapCache.h
+++ b/src/core/SkBitmapCache.h
@@ -14,6 +14,10 @@
 class SkResourceCache;
 class SkMipMap;
 
+uint64_t SkMakeResourceCacheSharedIDForBitmap(uint32_t bitmapGenID);
+
+void SkNotifyBitmapGenIDIsStale(uint32_t bitmapGenID);
+
 class SkBitmapCache {
 public:
     /**
diff --git a/src/core/SkMaskCache.cpp b/src/core/SkMaskCache.cpp
index 02d355d..b0399b1 100644
--- a/src/core/SkMaskCache.cpp
+++ b/src/core/SkMaskCache.cpp
@@ -26,7 +26,7 @@
         , fQuality(quality)
         , fRRect(rrect)
     {
-        this->init(&gRRectBlurKeyNamespaceLabel,
+        this->init(&gRRectBlurKeyNamespaceLabel, 0,
                    sizeof(fSigma) + sizeof(fStyle) + sizeof(fQuality) + sizeof(fRRect));
     }
 
@@ -117,7 +117,7 @@
             fSizes[2] = SkSize::Make(rects[0].x() - rects[1].x(), rects[0].y() - rects[1].y());
         }
 
-        this->init(&gRectsBlurKeyNamespaceLabel,
+        this->init(&gRectsBlurKeyNamespaceLabel, 0,
                    sizeof(fSigma) + sizeof(fStyle) + sizeof(fQuality) + sizeof(fSizes));
     }
 
diff --git a/src/core/SkPictureShader.cpp b/src/core/SkPictureShader.cpp
index 1453aee..a32368b 100644
--- a/src/core/SkPictureShader.cpp
+++ b/src/core/SkPictureShader.cpp
@@ -92,7 +92,7 @@
                                       sizeof(fLocalMatrixStorage);
         // This better be packed.
         SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - &fPictureID) == keySize);
-        this->init(&gBitmapSkaderKeyNamespaceLabel, keySize);
+        this->init(&gBitmapSkaderKeyNamespaceLabel, 0, keySize);
     }
 
 private:
diff --git a/src/core/SkPixelRef.cpp b/src/core/SkPixelRef.cpp
index c73c2f4..a5d6c71 100644
--- a/src/core/SkPixelRef.cpp
+++ b/src/core/SkPixelRef.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkBitmapCache.h"
 #include "SkPixelRef.h"
 #include "SkThread.h"
 
@@ -214,12 +215,18 @@
     *fGenIDChangeListeners.append() = listener;
 }
 
+// we need to be called *before* the genID gets changed or zerod
 void SkPixelRef::callGenIDChangeListeners() {
     // We don't invalidate ourselves if we think another SkPixelRef is sharing our genID.
     if (fUniqueGenerationID) {
         for (int i = 0; i < fGenIDChangeListeners.count(); i++) {
             fGenIDChangeListeners[i]->onChange();
         }
+
+        // If we can flag the pixelref somehow whenever it was actually added to the cache,
+        // perhaps it would be nice to only call this notifier in that case. For now we always
+        // call it, since we don't know if it was cached or not.
+        SkNotifyBitmapGenIDIsStale(fGenerationID);
     }
     // Listeners get at most one shot, so whether these triggered or not, blow them away.
     fGenIDChangeListeners.deleteAll();
diff --git a/src/core/SkResourceCache.cpp b/src/core/SkResourceCache.cpp
index 4ed889a..9da90c4 100644
--- a/src/core/SkResourceCache.cpp
+++ b/src/core/SkResourceCache.cpp
@@ -6,12 +6,15 @@
  */
 
 #include "SkChecksum.h"
-#include "SkResourceCache.h"
+#include "SkMessageBus.h"
 #include "SkMipMap.h"
 #include "SkPixelRef.h"
+#include "SkResourceCache.h"
 
 #include <stddef.h>
 
+DECLARE_SKMESSAGEBUS_MESSAGE(SkResourceCache::PurgeSharedIDMessage)
+
 // This can be defined by the caller's build system
 //#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE
 
@@ -23,18 +26,22 @@
     #define SK_DEFAULT_IMAGE_CACHE_LIMIT     (2 * 1024 * 1024)
 #endif
 
-void SkResourceCache::Key::init(void* nameSpace, size_t length) {
+void SkResourceCache::Key::init(void* nameSpace, uint64_t sharedID, size_t length) {
     SkASSERT(SkAlign4(length) == length);
 
     // fCount32 and fHash are not hashed
-    static const int kUnhashedLocal32s = 2;
-    static const int kLocal32s = kUnhashedLocal32s + (sizeof(fNamespace) >> 2);
+    static const int kUnhashedLocal32s = 2; // fCache32 + fHash
+    static const int kSharedIDLocal32s = 2; // fSharedID_lo + fSharedID_hi
+    static const int kHashedLocal32s = kSharedIDLocal32s + (sizeof(fNamespace) >> 2);
+    static const int kLocal32s = kUnhashedLocal32s + kHashedLocal32s;
 
     SK_COMPILE_ASSERT(sizeof(Key) == (kLocal32s << 2), unaccounted_key_locals);
     SK_COMPILE_ASSERT(sizeof(Key) == offsetof(Key, fNamespace) + sizeof(fNamespace),
                       namespace_field_must_be_last);
 
     fCount32 = SkToS32(kLocal32s + (length >> 2));
+    fSharedID_lo = (uint32_t)sharedID;
+    fSharedID_hi = (uint32_t)(sharedID >> 32);
     fNamespace = nameSpace;
     // skip unhashed fields when computing the murmur
     fHash = SkChecksum::Murmur3(this->as32() + kUnhashedLocal32s,
@@ -199,7 +206,9 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-bool SkResourceCache::find(const Key& key, VisitorProc visitor, void* context) {
+bool SkResourceCache::find(const Key& key, FindVisitor visitor, void* context) {
+    this->checkMessages();
+
     Rec* rec = fHash->find(key);
     if (rec) {
         if (visitor(*rec, context)) {
@@ -226,6 +235,8 @@
 static bool gDumpCacheTransactions;
 
 void SkResourceCache::add(Rec* rec) {
+    this->checkMessages();
+    
     SkASSERT(rec);
     // See if we already have this key (racy inserts, etc.)
     Rec* existing = fHash->find(rec->getKey());
@@ -294,6 +305,24 @@
     }
 }
 
+void SkResourceCache::purgeSharedID(uint64_t sharedID) {
+    if (0 == sharedID) {
+        return;
+    }
+
+    // go backwards, just like purgeAsNeeded, just to make the code similar.
+    // could iterate either direction and still be correct.
+    Rec* rec = fTail;
+    while (rec) {
+        Rec* prev = rec->fPrev;
+        if (rec->getKey().getSharedID() == sharedID) {
+//            SkDebugf("purgeSharedID id=%llx rec=%p\n", sharedID, rec);
+            this->remove(rec);
+        }
+        rec = prev;
+    }
+}
+
 size_t SkResourceCache::setTotalByteLimit(size_t newLimit) {
     size_t prevLimit = fTotalByteLimit;
     fTotalByteLimit = newLimit;
@@ -304,6 +333,8 @@
 }
 
 SkCachedData* SkResourceCache::newCachedData(size_t bytes) {
+    this->checkMessages();
+    
     if (fDiscardableFactory) {
         SkDiscardableMemory* dm = fDiscardableFactory(bytes);
         return dm ? SkNEW_ARGS(SkCachedData, (bytes, dm)) : NULL;
@@ -451,6 +482,14 @@
     return limit;
 }
 
+void SkResourceCache::checkMessages() {
+    SkTArray<PurgeSharedIDMessage> msgs;
+    fPurgeSharedIDInbox.poll(&msgs);
+    for (int i = 0; i < msgs.count(); ++i) {
+        this->purgeSharedID(msgs[i].fSharedID);
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkThread.h"
@@ -537,7 +576,7 @@
     return get_cache()->purgeAll();
 }
 
-bool SkResourceCache::Find(const Key& key, VisitorProc visitor, void* context) {
+bool SkResourceCache::Find(const Key& key, FindVisitor visitor, void* context) {
     SkAutoMutexAcquire am(gMutex);
     return get_cache()->find(key, visitor, context);
 }
@@ -547,6 +586,12 @@
     get_cache()->add(rec);
 }
 
+void SkResourceCache::PostPurgeSharedID(uint64_t sharedID) {
+    if (sharedID) {
+        SkMessageBus<PurgeSharedIDMessage>::Post(PurgeSharedIDMessage(sharedID));
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkGraphics.h"
diff --git a/src/core/SkResourceCache.h b/src/core/SkResourceCache.h
index 88ccb87..b43572f 100644
--- a/src/core/SkResourceCache.h
+++ b/src/core/SkResourceCache.h
@@ -9,6 +9,8 @@
 #define SkResourceCache_DEFINED
 
 #include "SkBitmap.h"
+#include "SkMessageBus.h"
+#include "SkTDArray.h"
 
 class SkCachedData;
 class SkDiscardableMemory;
@@ -32,8 +34,12 @@
 
         // must call this after your private data has been written.
         // nameSpace must be unique per Key subclass.
+        // sharedID == 0 means ignore this field : does not support group purging.
         // length must be a multiple of 4
-        void init(void* nameSpace, size_t length);
+        void init(void* nameSpace, uint64_t sharedID, size_t length);
+
+        void* getNamespace() const { return fNamespace; }
+        uint64_t getSharedID() const { return ((uint64_t)fSharedID_hi << 32) | fSharedID_lo; }
 
         // This is only valid after having called init().
         uint32_t hash() const { return fHash; }
@@ -52,6 +58,9 @@
     private:
         int32_t  fCount32;   // local + user contents count32
         uint32_t fHash;
+        // split uint64_t into hi and lo so we don't force ourselves to pad on 32bit machines.
+        uint32_t fSharedID_lo;
+        uint32_t fSharedID_hi;
         void*    fNamespace; // A unique namespace tag. This is hashed.
         /* uint32_t fContents32[] */
 
@@ -80,6 +89,13 @@
         friend class SkResourceCache;
     };
 
+    // Used with SkMessageBus
+    struct PurgeSharedIDMessage {
+        PurgeSharedIDMessage(uint64_t sharedID) : fSharedID(sharedID) {}
+
+        uint64_t    fSharedID;
+    };
+
     typedef const Rec* ID;
 
     /**
@@ -92,7 +108,7 @@
      *  true, then the Rec is considered "valid". If false is returned, the Rec will be considered
      *  "stale" and will be purged from the cache.
      */
-    typedef bool (*VisitorProc)(const Rec&, void* context);
+    typedef bool (*FindVisitor)(const Rec&, void* context);
 
     /**
      *  Returns a locked/pinned SkDiscardableMemory instance for the specified
@@ -109,12 +125,12 @@
      *  Returns true if the visitor was called on a matching Key, and the visitor returned true.
      *
      *  Find() will search the cache for the specified Key. If no match is found, return false and
-     *  do not call the VisitorProc. If a match is found, return whatever the visitor returns.
+     *  do not call the FindVisitor. If a match is found, return whatever the visitor returns.
      *  Its return value is interpreted to mean:
      *      true  : Rec is valid
      *      false : Rec is "stale" -- the cache will purge it.
      */
-    static bool Find(const Key& key, VisitorProc, void* context);
+    static bool Find(const Key& key, FindVisitor, void* context);
     static void Add(Rec*);
 
     static size_t GetTotalBytesUsed();
@@ -140,6 +156,8 @@
 
     static SkCachedData* NewCachedData(size_t bytes);
 
+    static void PostPurgeSharedID(uint64_t sharedID);
+
     /**
      *  Call SkDebugf() with diagnostic information about the state of the cache
      */
@@ -169,12 +187,12 @@
      *  Returns true if the visitor was called on a matching Key, and the visitor returned true.
      *
      *  find() will search the cache for the specified Key. If no match is found, return false and
-     *  do not call the VisitorProc. If a match is found, return whatever the visitor returns.
+     *  do not call the FindVisitor. If a match is found, return whatever the visitor returns.
      *  Its return value is interpreted to mean:
      *      true  : Rec is valid
      *      false : Rec is "stale" -- the cache will purge it.
      */
-    bool find(const Key&, VisitorProc, void* context);
+    bool find(const Key&, FindVisitor, void* context);
     void add(Rec*);
 
     size_t getTotalBytesUsed() const { return fTotalBytesUsed; }
@@ -198,6 +216,8 @@
      */
     size_t setTotalByteLimit(size_t newLimit);
 
+    void purgeSharedID(uint64_t sharedID);
+
     void purgeAll() {
         this->purgeAsNeeded(true);
     }
@@ -228,6 +248,9 @@
     size_t  fSingleAllocationByteLimit;
     int     fCount;
 
+    SkMessageBus<PurgeSharedIDMessage>::Inbox fPurgeSharedIDInbox;
+
+    void checkMessages();
     void purgeAsNeeded(bool forcePurge = false);
 
     // linklist management
diff --git a/src/core/SkYUVPlanesCache.cpp b/src/core/SkYUVPlanesCache.cpp
index 69885fe..5ef89b6 100644
--- a/src/core/SkYUVPlanesCache.cpp
+++ b/src/core/SkYUVPlanesCache.cpp
@@ -5,8 +5,9 @@
  * found in the LICENSE file.
  */
 
-#include "SkYUVPlanesCache.h"
+#include "SkBitmapCache.h"
 #include "SkResourceCache.h"
+#include "SkYUVPlanesCache.h"
 
 #define CHECK_LOCAL(localCache, localName, globalName, ...) \
     ((localCache) ? localCache->localName(__VA_ARGS__) : SkResourceCache::globalName(__VA_ARGS__))
@@ -23,7 +24,8 @@
     YUVPlanesKey(uint32_t genID)
         : fGenID(genID)
     {
-        this->init(&gYUVPlanesKeyNamespaceLabel, sizeof(genID));
+        this->init(&gYUVPlanesKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(genID),
+                   sizeof(genID));
     }
 
     uint32_t fGenID;