Use GrResourceCache2 to service content key lookups

BUG=skia:2889

Review URL: https://codereview.chromium.org/707493002
diff --git a/bench/GrResourceCacheBench.cpp b/bench/GrResourceCacheBench.cpp
index 30d4cd4..91c77fb 100644
--- a/bench/GrResourceCacheBench.cpp
+++ b/bench/GrResourceCacheBench.cpp
@@ -6,13 +6,15 @@
  * found in the LICENSE file.
  */
 
+#include "Benchmark.h"
+
 #if SK_SUPPORT_GPU
 
-#include "Benchmark.h"
 #include "GrGpuResource.h"
 #include "GrContext.h"
 #include "GrGpu.h"
 #include "GrResourceCache.h"
+#include "GrResourceCache2.h"
 #include "GrStencilBuffer.h"
 #include "GrTexture.h"
 #include "GrTexturePriv.h"
@@ -117,18 +119,18 @@
     }
 }
 
-static void check_cache_contents_or_die(GrResourceCache* cache, int k) {
+static void check_cache_contents_or_die(GrResourceCache2* cache, int k) {
     // Benchmark find calls that succeed.
     {
         GrSurfaceDesc desc;
         get_texture_desc(k, &desc);
         GrResourceKey key = TextureResource::ComputeKey(desc);
-        GrGpuResource* item = cache->find(key);
-        if (NULL == item) {
+        SkAutoTUnref<GrGpuResource> item(cache->findAndRefContentResource(key));
+        if (!item) {
             SkFAIL("cache add does not work as expected");
             return;
         }
-        if (static_cast<TextureResource*>(item)->fID != k) {
+        if (static_cast<TextureResource*>(item.get())->fID != k) {
             SkFAIL("cache add does not work as expected");
             return;
         }
@@ -137,12 +139,12 @@
         int w, h, s;
         get_stencil(k, &w, &h, &s);
         GrResourceKey key = StencilResource::ComputeKey(w, h, s);
-        GrGpuResource* item = cache->find(key);
-        if (NULL == item) {
+        SkAutoTUnref<GrGpuResource> item(cache->findAndRefContentResource(key));
+        if (!item) {
             SkFAIL("cache add does not work as expected");
             return;
         }
-        if (static_cast<TextureResource*>(item)->fID != k) {
+        if (static_cast<TextureResource*>(item.get())->fID != k) {
             SkFAIL("cache add does not work as expected");
             return;
         }
@@ -154,7 +156,7 @@
         get_texture_desc(k, &desc);
         desc.fHeight |= 1;
         GrResourceKey key = TextureResource::ComputeKey(desc);
-        GrGpuResource* item = cache->find(key);
+        SkAutoTUnref<GrGpuResource> item(cache->findAndRefContentResource(key));
         if (item) {
             SkFAIL("cache add does not work as expected");
             return;
@@ -165,7 +167,7 @@
         get_stencil(k, &w, &h, &s);
         h |= 1;
         GrResourceKey key = StencilResource::ComputeKey(w, h, s);
-        GrGpuResource* item = cache->find(key);
+        SkAutoTUnref<GrGpuResource> item(cache->findAndRefContentResource(key));
         if (item) {
             SkFAIL("cache add does not work as expected");
             return;
@@ -176,12 +178,11 @@
 class GrResourceCacheBenchAdd : public Benchmark {
     enum {
         RESOURCE_COUNT = CACHE_SIZE_COUNT / 2,
-        DUPLICATE_COUNT = CACHE_SIZE_COUNT / 4,
     };
 
 public:
     virtual bool isSuitableFor(Backend backend) SK_OVERRIDE {
-        return backend == kGPU_Backend;
+        return backend == kNonRendering_Backend;
     }
 
 protected:
@@ -190,18 +191,32 @@
     }
 
     virtual void onDraw(const int loops, SkCanvas* canvas) SK_OVERRIDE {
-        GrGpu* gpu = canvas->getGrContext()->getGpu();
+        SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
+        if (NULL == context) {
+            return;
+        }
+        // Set the cache budget to be very large so no purging occurs.
+        context->setResourceCacheLimits(2 * RESOURCE_COUNT, 1 << 30);
+
+        GrResourceCache* cache = context->getResourceCache();
+        GrResourceCache2* cache2 = context->getResourceCache2();
+
+        // Make sure the cache is empty.
+        cache->purgeAllUnlocked();
+        SkASSERT(0 == cache->getCachedResourceCount() && 0 == cache->getCachedResourceBytes());
+
+        GrGpu* gpu = context->getGpu();
 
         for (int i = 0; i < loops; ++i) {
-            GrResourceCache cache(gpu->caps(), CACHE_SIZE_COUNT, CACHE_SIZE_BYTES);
-            populate_cache(&cache, gpu, DUPLICATE_COUNT);
-            populate_cache(&cache, gpu, RESOURCE_COUNT);
+            SkASSERT(0 == cache->getCachedResourceCount() && 0 == cache->getCachedResourceBytes());
+
+            populate_cache(cache, gpu, RESOURCE_COUNT);
 
             // Check that cache works.
             for (int k = 0; k < RESOURCE_COUNT; k += 33) {
-                check_cache_contents_or_die(&cache, k);
+                check_cache_contents_or_die(cache2, k);
             }
-            cache.purgeAllUnlocked();
+            cache->purgeAllUnlocked();
         }
     }
 
@@ -211,13 +226,12 @@
 
 class GrResourceCacheBenchFind : public Benchmark {
     enum {
-        RESOURCE_COUNT = (CACHE_SIZE_COUNT / 2) - 100,
-        DUPLICATE_COUNT = 100
+        RESOURCE_COUNT = CACHE_SIZE_COUNT / 2,
     };
 
 public:
     virtual bool isSuitableFor(Backend backend) SK_OVERRIDE {
-        return backend == kGPU_Backend;
+        return backend == kNonRendering_Backend;
     }
 
 protected:
@@ -226,14 +240,27 @@
     }
 
     virtual void onDraw(const int loops, SkCanvas* canvas) SK_OVERRIDE {
-        GrGpu* gpu = canvas->getGrContext()->getGpu();
-        GrResourceCache cache(gpu->caps(), CACHE_SIZE_COUNT, CACHE_SIZE_BYTES);
-        populate_cache(&cache, gpu, DUPLICATE_COUNT);
-        populate_cache(&cache, gpu, RESOURCE_COUNT);
+        SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
+        if (NULL == context) {
+            return;
+        }
+        // Set the cache budget to be very large so no purging occurs.
+        context->setResourceCacheLimits(2 * RESOURCE_COUNT, 1 << 30);
+
+        GrResourceCache* cache = context->getResourceCache();
+        GrResourceCache2* cache2 = context->getResourceCache2();
+
+        // Make sure the cache is empty.
+        cache->purgeAllUnlocked();
+        SkASSERT(0 == cache->getCachedResourceCount() && 0 == cache->getCachedResourceBytes());
+
+        GrGpu* gpu = context->getGpu();
+
+        populate_cache(cache, gpu, RESOURCE_COUNT);
 
         for (int i = 0; i < loops; ++i) {
             for (int k = 0; k < RESOURCE_COUNT; ++k) {
-                check_cache_contents_or_die(&cache, k);
+                check_cache_contents_or_die(cache2, k);
             }
         }
     }
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 2adc842..97acda1 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -897,7 +897,7 @@
      * called to check the cache for a SB that matches an RT's criteria.
      */
     void addStencilBuffer(GrStencilBuffer* sb);
-    GrStencilBuffer* findStencilBuffer(int width, int height, int sampleCnt);
+    GrStencilBuffer* findAndRefStencilBuffer(int width, int height, int sampleCnt);
 
     GrPathRenderer* getPathRenderer(
                     const SkPath& path,
diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h
index 4ffe17e..d3341d9 100644
--- a/include/gpu/GrGpuResource.h
+++ b/include/gpu/GrGpuResource.h
@@ -172,7 +172,7 @@
      */
     virtual size_t gpuMemorySize() const = 0;
 
-    void setCacheEntry(GrResourceCacheEntry* cacheEntry) { fCacheEntry = cacheEntry; }
+    bool setCacheEntry(GrResourceCacheEntry* cacheEntry);
     GrResourceCacheEntry* getCacheEntry() const { return fCacheEntry; }
     bool isScratch() const;
 
diff --git a/src/core/SkTMultiMap.h b/src/core/SkTMultiMap.h
index 70076f0..1168ed6 100644
--- a/src/core/SkTMultiMap.h
+++ b/src/core/SkTMultiMap.h
@@ -102,6 +102,19 @@
 
     int count() const { return fCount; }
 
+#ifdef SK_DEBUG
+    // This is not particularly fast and only used for validation, so debug only.
+    int countForKey(const Key& key) const {
+        int count = 0;
+        ValueList* list = fHash.find(key);
+        while (list) {
+            list = list->fNext;
+            ++count;
+        }
+        return count;
+    }
+#endif
+
 private:
     SkTDynamicHash<ValueList, Key> fHash;
     int fCount;
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 83ef58f..6f9395b 100755
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -255,20 +255,20 @@
                                         const GrCacheID& cacheID,
                                         const GrTextureParams* params) {
     GrResourceKey resourceKey = GrTexturePriv::ComputeKey(fGpu, params, desc, cacheID);
-    GrGpuResource* resource = fResourceCache->find(resourceKey);
+
+    GrGpuResource* resource = this->findAndRefCachedResource(resourceKey);
     if (resource) {
-        resource->ref();
+        SkASSERT(static_cast<GrSurface*>(resource)->asTexture());
         return static_cast<GrSurface*>(resource)->asTexture();
-    } else {
-        return NULL;
     }
+    return NULL;
 }
 
 bool GrContext::isTextureInCache(const GrSurfaceDesc& desc,
                                  const GrCacheID& cacheID,
                                  const GrTextureParams* params) const {
     GrResourceKey resourceKey = GrTexturePriv::ComputeKey(fGpu, params, desc, cacheID);
-    return fResourceCache->hasKey(resourceKey);
+    return fResourceCache2->hasContentKey(resourceKey);
 }
 
 void GrContext::addStencilBuffer(GrStencilBuffer* sb) {
@@ -280,12 +280,9 @@
     fResourceCache->addResource(resourceKey, sb);
 }
 
-GrStencilBuffer* GrContext::findStencilBuffer(int width, int height,
-                                              int sampleCnt) {
-    GrResourceKey resourceKey = GrStencilBuffer::ComputeKey(width,
-                                                            height,
-                                                            sampleCnt);
-    GrGpuResource* resource = fResourceCache->find(resourceKey);
+GrStencilBuffer* GrContext::findAndRefStencilBuffer(int width, int height, int sampleCnt) {
+    GrResourceKey resourceKey = GrStencilBuffer::ComputeKey(width, height, sampleCnt);
+    GrGpuResource* resource = this->findAndRefCachedResource(resourceKey);
     return static_cast<GrStencilBuffer*>(resource);
 }
 
@@ -1755,8 +1752,10 @@
 }
 
 GrGpuResource* GrContext::findAndRefCachedResource(const GrResourceKey& resourceKey) {
-    GrGpuResource* resource = fResourceCache->find(resourceKey);
-    SkSafeRef(resource);
+    GrGpuResource* resource = fResourceCache2->findAndRefContentResource(resourceKey);
+    if (resource) {
+        fResourceCache->makeResourceMRU(resource);
+    }
     return resource;
 }
 
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index 64ee29b..ab24c54 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -84,10 +84,8 @@
 
 bool GrGpu::attachStencilBufferToRenderTarget(GrRenderTarget* rt) {
     SkASSERT(NULL == rt->getStencilBuffer());
-    GrStencilBuffer* sb =
-        this->getContext()->findStencilBuffer(rt->width(),
-                                              rt->height(),
-                                              rt->numSamples());
+    SkAutoTUnref<GrStencilBuffer> sb(
+        this->getContext()->findAndRefStencilBuffer(rt->width(), rt->height(), rt->numSamples()));
     if (sb) {
         rt->setStencilBuffer(sb);
         bool attached = this->attachStencilBufferToRenderTarget(sb, rt);
@@ -96,8 +94,7 @@
         }
         return attached;
     }
-    if (this->createStencilBufferForRenderTarget(rt,
-                                                 rt->width(), rt->height())) {
+    if (this->createStencilBufferForRenderTarget(rt, rt->width(), rt->height())) {
         // Right now we're clearing the stencil buffer here after it is
         // attached to an RT for the first time. When we start matching
         // stencil buffers with smaller color targets this will no longer
diff --git a/src/gpu/GrGpuResource.cpp b/src/gpu/GrGpuResource.cpp
index 705cdea..b77acff 100644
--- a/src/gpu/GrGpuResource.cpp
+++ b/src/gpu/GrGpuResource.cpp
@@ -78,6 +78,24 @@
     }
 }
 
+bool GrGpuResource::setCacheEntry(GrResourceCacheEntry* cacheEntry) {
+    // GrResourceCache never changes the cacheEntry once one has been added.
+    SkASSERT(NULL == cacheEntry || NULL == fCacheEntry);
+
+    fCacheEntry = cacheEntry;
+    if (this->wasDestroyed() || NULL == cacheEntry) {
+        return true;
+    }
+
+    if (!cacheEntry->key().isScratch()) {
+        if (!get_resource_cache2(fGpu)->didAddContentKey(this)) {
+            fCacheEntry = NULL;
+            return false;
+        }
+    }
+    return true;
+}
+
 void GrGpuResource::notifyIsPurgable() const {
     if (fCacheEntry && !this->wasDestroyed()) {
         get_resource_cache(fGpu)->notifyPurgable(this);
@@ -92,6 +110,7 @@
 }
 
 const GrResourceKey* GrGpuResource::getContentKey() const {
+    // Currently scratch resources have a cache entry in GrResourceCache with a scratch key.
     if (fCacheEntry && !fCacheEntry->key().isScratch()) {
         return &fCacheEntry->key();
     }
@@ -99,8 +118,8 @@
 }
 
 bool GrGpuResource::isScratch() const {
-    // Currently scratch resources have a cache entry in GrResourceCache with a scratch key.
-    return NULL != fCacheEntry && fCacheEntry->key().isScratch();
+    SkASSERT(fScratchKey.isScratch());
+    return NULL == this->getContentKey() && !fScratchKey.isNullScratch();
 }
 
 uint32_t GrGpuResource::CreateUniqueID() {
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 9754d44..8eed4d4 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -49,7 +49,8 @@
 }
 
 GrResourceCacheEntry::~GrResourceCacheEntry() {
-    fResource->setCacheEntry(NULL);
+    // We're relying on having the cache entry to remove this from GrResourceCache2's content hash.
+    // fResource->setCacheEntry(NULL);
     fResource->unref();
 }
 
@@ -185,26 +186,11 @@
     }
 }
 
-GrGpuResource* GrResourceCache::find(const GrResourceKey& key) {
-    // GrResourceCache2 is responsible for scratch resources.
-    SkASSERT(!key.isScratch());
-
-    GrAutoResourceCacheValidate atcv(this);
-
-    GrResourceCacheEntry* entry = fCache.find(key);
-    if (NULL == entry) {
-        return NULL;
+bool GrResourceCache::addResource(const GrResourceKey& key, GrGpuResource* resource) {
+    if (NULL != resource->getCacheEntry()) {
+        return false;
     }
 
-    // Make this resource MRU
-    this->internalDetach(entry);
-    this->attachToHead(entry);
-
-    return entry->fResource;
-}
-
-void GrResourceCache::addResource(const GrResourceKey& key, GrGpuResource* resource) {
-    SkASSERT(NULL == resource->getCacheEntry());
     // we don't expect to create new resources during a purge. In theory
     // this could cause purgeAsNeeded() into an infinite loop (e.g.
     // each resource destroyed creates and locks 2 resources and
@@ -213,12 +199,16 @@
     GrAutoResourceCacheValidate atcv(this);
 
     GrResourceCacheEntry* entry = SkNEW_ARGS(GrResourceCacheEntry, (this, key, resource));
-    resource->setCacheEntry(entry);
+    if (!resource->setCacheEntry(entry)) {
+        SkDELETE(entry);
+        this->purgeAsNeeded();
+        return false;
+    }
 
     this->attachToHead(entry);
     fCache.insert(key, entry);
-
     this->purgeAsNeeded();
+    return true;
 }
 
 void GrResourceCache::didIncreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountInc) {
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index f6d064a..874f16a 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -140,12 +140,6 @@
      */
     int getCachedResourceCount() const { return fEntryCount; }
 
-    /**
-     *  Search for an entry with the same Key. If found, return it.
-     *  If not found, return null.
-     */
-    GrGpuResource* find(const GrResourceKey& key);
-
     void makeResourceMRU(GrGpuResource*);
 
     /** Called by GrGpuResources when they detects that they are newly purgable. */
@@ -157,14 +151,11 @@
      *
      *  Ownership of the resource is transferred to the resource cache,
      *  which will unref() it when it is purged or deleted.
+     *
+     *  This can fail if the key is already taken, or the resource is already in
+     *  the cache.
      */
-    void addResource(const GrResourceKey& key, GrGpuResource* resource);
-
-    /**
-     * Determines if the cache contains an entry matching a key. If a matching
-     * entry exists but was detached then it will not be found.
-     */
-    bool hasKey(const GrResourceKey& key) const { return SkToBool(fCache.find(key)); }
+    bool addResource(const GrResourceKey& key, GrGpuResource* resource);
 
     /**
      * Notify the cache that the size of a resource has changed.
diff --git a/src/gpu/GrResourceCache2.cpp b/src/gpu/GrResourceCache2.cpp
index 6bc23a3..65e522a 100644
--- a/src/gpu/GrResourceCache2.cpp
+++ b/src/gpu/GrResourceCache2.cpp
@@ -9,7 +9,6 @@
 
 #include "GrResourceCache2.h"
 #include "GrGpuResource.h"  
-#include "SkRefCnt.h"
 
 GrResourceCache2::~GrResourceCache2() {
     this->releaseAll();
@@ -22,6 +21,8 @@
     fResources.addToHead(resource);
     ++fCount;
     if (!resource->getScratchKey().isNullScratch()) {
+        // TODO(bsalomon): Make this assertion possible.
+        // SkASSERT(!resource->isWrapped());
         fScratchMap.insert(resource->getScratchKey(), resource);
     }
 }
@@ -32,6 +33,9 @@
     if (!resource->getScratchKey().isNullScratch()) {
         fScratchMap.remove(resource->getScratchKey(), resource);
     }
+    if (const GrResourceKey* contentKey = resource->getContentKey()) {
+        fContentHash.remove(*contentKey);
+    }
     --fCount;
 }
 
@@ -43,6 +47,7 @@
         SkASSERT(head != fResources.head());
     }
     SkASSERT(!fScratchMap.count());
+    SkASSERT(!fContentHash.count());
     SkASSERT(!fCount);
 }
 
@@ -89,3 +94,25 @@
     }
     return SkSafeRef(fScratchMap.find(scratchKey, AvailableForScratchUse(false)));
 }
+
+void GrResourceCache2::willRemoveContentKey(const GrGpuResource* resource) {
+    SkASSERT(resource);
+    SkASSERT(resource->getContentKey());
+    SkDEBUGCODE(GrGpuResource* res = fContentHash.find(*resource->getContentKey()));
+    SkASSERT(res == resource);
+
+    fContentHash.remove(*resource->getContentKey());
+}
+
+bool GrResourceCache2::didAddContentKey(GrGpuResource* resource) {
+    SkASSERT(resource);
+    SkASSERT(resource->getContentKey());
+
+    GrGpuResource* res = fContentHash.find(*resource->getContentKey());
+    if (NULL != res) {
+        return false;
+    }
+
+    fContentHash.add(resource);
+    return true;
+}
diff --git a/src/gpu/GrResourceCache2.h b/src/gpu/GrResourceCache2.h
index a365a5a..9424c40 100644
--- a/src/gpu/GrResourceCache2.h
+++ b/src/gpu/GrResourceCache2.h
@@ -11,6 +11,7 @@
 
 #include "GrGpuResource.h"
 #include "GrResourceKey.h"
+#include "SkRefCnt.h"
 #include "SkTInternalLList.h"
 #include "SkTMultiMap.h"
 
@@ -28,6 +29,14 @@
 
     void removeResource(GrGpuResource*);
 
+    void willRemoveContentKey(const GrGpuResource*);
+
+    // This currently returns a bool and fails when an existing resource has a key that collides
+    // with the new content key. In the future it will null out the content key for the existing
+    // resource. The failure is a temporary measure taken because duties are split between two
+    // cache objects currently.
+    bool didAddContentKey(GrGpuResource*);
+
     void abandonAll();
 
     void releaseAll();
@@ -39,6 +48,24 @@
         kRequireNoPendingIO_ScratchFlag = 0x2,
     };
     GrGpuResource* findAndRefScratchResource(const GrResourceKey& scratchKey, uint32_t flags = 0);
+    
+#ifdef SK_DEBUG
+    // This is not particularly fast and only used for validation, so debug only.
+    int countScratchEntriesForKey(const GrResourceKey& scratchKey) const {
+        SkASSERT(scratchKey.isScratch());
+        return fScratchMap.countForKey(scratchKey);
+    }
+#endif
+
+    GrGpuResource* findAndRefContentResource(const GrResourceKey& contentKey) {
+        SkASSERT(!contentKey.isScratch());
+        return SkSafeRef(fContentHash.find(contentKey));
+    }
+
+    bool hasContentKey(const GrResourceKey& contentKey) const {
+        SkASSERT(!contentKey.isScratch());
+        return SkToBool(fContentHash.find(contentKey));
+    }
 
 private:
 #ifdef SK_DEBUG
@@ -56,10 +83,21 @@
     };
     typedef SkTMultiMap<GrGpuResource, GrResourceKey, ScratchMapTraits> ScratchMap;
 
+    struct ContentHashTraits {
+        static const GrResourceKey& GetKey(const GrGpuResource& r) {
+            return *r.getContentKey();
+        }
+
+        static uint32_t Hash(const GrResourceKey& key) { return key.getHash(); }
+    };
+    typedef SkTDynamicHash<GrGpuResource, GrResourceKey, ContentHashTraits> ContentHash;
+
     int                                 fCount;
     SkTInternalLList<GrGpuResource>     fResources;
     // This map holds all resources that can be used as scratch resources.
-    ScratchMap                          fScratchMap; 
+    ScratchMap                          fScratchMap;
+    // This holds all resources that have content keys.
+    ContentHash                         fContentHash;
 };
 
 #endif
diff --git a/src/gpu/GrTexture.cpp b/src/gpu/GrTexture.cpp
index 2139540..72e8225 100644
--- a/src/gpu/GrTexture.cpp
+++ b/src/gpu/GrTexture.cpp
@@ -121,7 +121,10 @@
 GrTexture::GrTexture(GrGpu* gpu, bool isWrapped, const GrSurfaceDesc& desc)
     : INHERITED(gpu, isWrapped, desc)
     , fMipMapsStatus(kNotAllocated_MipMapsStatus) {
-    this->setScratchKey(GrTexturePriv::ComputeScratchKey(desc));
+
+    if (!isWrapped) {
+        this->setScratchKey(GrTexturePriv::ComputeScratchKey(desc));
+    }
     // only make sense if alloc size is pow2
     fShiftFixedX = 31 - SkCLZ(fDesc.fWidth);
     fShiftFixedY = 31 - SkCLZ(fDesc.fHeight);
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index 2827cbf..24ecb94 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -65,11 +65,21 @@
 
 public:
     SK_DECLARE_INST_COUNT(TestResource);
-    TestResource(GrGpu* gpu, size_t size = kDefaultSize)
+    TestResource(GrGpu* gpu)
         : INHERITED(gpu, false)
         , fCache(NULL)
         , fToDelete(NULL)
-        , fSize(size) {
+        , fSize(kDefaultSize) {
+        ++fNumAlive;
+        this->registerWithCache();
+    }
+
+    TestResource(GrGpu* gpu, const GrResourceKey& scratchKey)
+        : INHERITED(gpu, false)
+        , fCache(NULL)
+        , fToDelete(NULL)
+        , fSize(kDefaultSize) {
+        this->setScratchKey(scratchKey);
         ++fNumAlive;
         this->registerWithCache();
     }
@@ -108,52 +118,189 @@
 };
 int TestResource::fNumAlive = 0;
 
-static void test_purge_invalidated(skiatest::Reporter* reporter, GrContext* context) {
-    GrCacheID::Domain domain = GrCacheID::GenerateDomain();
+static void test_duplicate_scratch_key(skiatest::Reporter* reporter) {
+    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
+    REPORTER_ASSERT(reporter, SkToBool(context));
+    if (NULL == context) {
+        return;
+    }
+    context->setResourceCacheLimits(5, 30000);
+    GrResourceCache* cache = context->getResourceCache();
+    SkDEBUGCODE(GrResourceCache2* cache2 = context->getResourceCache2();)
+    cache->purgeAllUnlocked();
+    SkASSERT(0 == cache->getCachedResourceCount() && 0 == cache->getCachedResourceBytes());
+
     GrCacheID::Key keyData;
-    keyData.fData64[0] = 5;
-    keyData.fData64[1] = 18;
+    GrCacheID::Domain domain = GrResourceKey::ScratchDomain();
     GrResourceKey::ResourceType t = GrResourceKey::GenerateResourceType();
-    GrResourceKey key(GrCacheID(domain, keyData), t, 0);
-    
+    GrResourceKey scratchKey(GrCacheID(domain, keyData), t, 0);
+
+    // Create two resources that have the same scratch key.
+    TestResource* a = new TestResource(context->getGpu(), scratchKey);
+    TestResource* b = new TestResource(context->getGpu(), scratchKey);
+    a->setSize(11);
+    b->setSize(12);
+    // Scratch resources are registered with GrResourceCache2 just by existing. There are 2.
+    SkDEBUGCODE(REPORTER_ASSERT(reporter, 2 == cache2->countScratchEntriesForKey(scratchKey));)
+
+    REPORTER_ASSERT(reporter, cache->addResource(scratchKey, a));
+
+    SkDEBUGCODE(REPORTER_ASSERT(reporter, 2 == cache2->countScratchEntriesForKey(scratchKey));)
+
+    // Can't add the same resource twice.
+    REPORTER_ASSERT(reporter, !cache->addResource(scratchKey, a));
+    REPORTER_ASSERT(reporter, 1 == cache->getCachedResourceCount());
+    REPORTER_ASSERT(reporter, a->gpuMemorySize() == cache->getCachedResourceBytes());
+    SkDEBUGCODE(REPORTER_ASSERT(reporter, 2 == cache2->countScratchEntriesForKey(scratchKey));)
+
+    // Add a second with the same key.
+    REPORTER_ASSERT(reporter, cache->addResource(scratchKey, b));
+    REPORTER_ASSERT(reporter, 2 == cache->getCachedResourceCount());
+    REPORTER_ASSERT(reporter, a->gpuMemorySize() + b->gpuMemorySize() ==
+                              cache->getCachedResourceBytes());
+    REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
+    SkDEBUGCODE(REPORTER_ASSERT(reporter, 2 == cache2->countScratchEntriesForKey(scratchKey));)
+
+    // Our refs mean that the resources are non purgable.
+    cache->purgeAllUnlocked();
+    REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
+    REPORTER_ASSERT(reporter, 2 == cache->getCachedResourceCount());
+
+    // Unref but don't purge
+    a->unref();
+    b->unref();
+    REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
+    SkDEBUGCODE(REPORTER_ASSERT(reporter, 2 == cache2->countScratchEntriesForKey(scratchKey));)
+
+    // Purge again. This time resources should be purgable.
+    cache->purgeAllUnlocked();
+    REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
+    REPORTER_ASSERT(reporter, 0 == cache->getCachedResourceCount());
+    SkDEBUGCODE(REPORTER_ASSERT(reporter, 0 == cache2->countScratchEntriesForKey(scratchKey));)
+}
+
+static void test_duplicate_content_key(skiatest::Reporter* reporter) {
+    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
+    REPORTER_ASSERT(reporter, SkToBool(context));
+    if (NULL == context) {
+        return;
+    }
     context->setResourceCacheLimits(5, 30000);
     GrResourceCache* cache = context->getResourceCache();
     cache->purgeAllUnlocked();
     SkASSERT(0 == cache->getCachedResourceCount() && 0 == cache->getCachedResourceBytes());
 
-    // Add two resources with the same key that delete each other from the cache when destroyed.
+    GrCacheID::Domain domain = GrCacheID::GenerateDomain();
+    GrCacheID::Key keyData;
+    memset(&keyData, 0, sizeof(keyData));
+    GrResourceKey::ResourceType t = GrResourceKey::GenerateResourceType();
+    GrResourceKey key(GrCacheID(domain, keyData), t, 0);
+    
+
+    // Create two resources that we will attempt to register with the same content key.
     TestResource* a = new TestResource(context->getGpu());
     TestResource* b = new TestResource(context->getGpu());
-    cache->addResource(key, a);
-    cache->addResource(key, b);
-    // Circle back.
-    a->setDeleteWhenDestroyed(cache, b);
-    b->setDeleteWhenDestroyed(cache, a);
-    a->unref();
+    a->setSize(11);
+    b->setSize(12);
+    REPORTER_ASSERT(reporter, cache->addResource(key, a));
+    // Can't add the same or another resource with the same key.
+    REPORTER_ASSERT(reporter, !cache->addResource(key, a));
+    REPORTER_ASSERT(reporter, !cache->addResource(key, b));
+    REPORTER_ASSERT(reporter, 1 == cache->getCachedResourceCount());
+    REPORTER_ASSERT(reporter, a->gpuMemorySize() == cache->getCachedResourceBytes());
+    REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
+
     b->unref();
+    cache->purgeAllUnlocked();
+    a->setSize(10);
+    REPORTER_ASSERT(reporter, 1 == cache->getCachedResourceCount());
+    REPORTER_ASSERT(reporter, 1 == TestResource::NumAlive());
 
-    // Add a third independent resource also with the same key.
-    GrGpuResource* r = new TestResource(context->getGpu());
-    cache->addResource(key, r);
-    r->unref();
-
-    // Invalidate all three, all three should be purged and destroyed.
-    REPORTER_ASSERT(reporter, 3 == TestResource::NumAlive());
-    const GrResourceInvalidatedMessage msg = { key };
-    SkMessageBus<GrResourceInvalidatedMessage>::Post(msg);
-    cache->purgeAsNeeded();
+    a->unref();
+    cache->purgeAllUnlocked();
+    REPORTER_ASSERT(reporter, 0 == cache->getCachedResourceCount());
+    REPORTER_ASSERT(reporter, 0 == cache->getCachedResourceBytes());
     REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
 }
 
-static void test_cache_delete_on_destruction(skiatest::Reporter* reporter,
-                                             GrContext* context) {
+static void test_purge_invalidated(skiatest::Reporter* reporter) {
+    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
+    REPORTER_ASSERT(reporter, SkToBool(context));
+    if (NULL == context) {
+        return;
+    }
+
     GrCacheID::Domain domain = GrCacheID::GenerateDomain();
     GrCacheID::Key keyData;
-    keyData.fData64[0] = 5;
-    keyData.fData64[1] = 0;
+    memset(&keyData, 0, sizeof(keyData));
+
     GrResourceKey::ResourceType t = GrResourceKey::GenerateResourceType();
 
-    GrResourceKey key(GrCacheID(domain, keyData), t, 0);
+    keyData.fData64[0] = 1;
+    GrResourceKey key1(GrCacheID(domain, keyData), t, 0);
+    keyData.fData64[0] = 2;
+    GrResourceKey key2(GrCacheID(domain, keyData), t, 0);
+    keyData.fData64[0] = 3;
+    GrResourceKey key3(GrCacheID(domain, keyData), t, 0);
+    
+    context->setResourceCacheLimits(5, 30000);
+    GrResourceCache* cache = context->getResourceCache();
+    GrResourceCache2* cache2 = context->getResourceCache2();
+    cache->purgeAllUnlocked();
+    SkASSERT(0 == cache->getCachedResourceCount() && 0 == cache->getCachedResourceBytes());
+
+    // Add three resources to the cache.
+    TestResource* a = new TestResource(context->getGpu());
+    TestResource* b = new TestResource(context->getGpu());
+    TestResource* c = new TestResource(context->getGpu());
+    cache->addResource(key1, a);
+    cache->addResource(key2, b);
+    cache->addResource(key3, c);
+    a->unref();
+    b->unref();
+    c->unref();
+
+    REPORTER_ASSERT(reporter, cache2->hasContentKey(key1));
+    REPORTER_ASSERT(reporter, cache2->hasContentKey(key2));
+    REPORTER_ASSERT(reporter, cache2->hasContentKey(key3));
+
+    // Invalidate two of the three, they should be purged and destroyed.
+    REPORTER_ASSERT(reporter, 3 == TestResource::NumAlive());
+    const GrResourceInvalidatedMessage msg1 = { key1 };
+    SkMessageBus<GrResourceInvalidatedMessage>::Post(msg1);
+    const GrResourceInvalidatedMessage msg2 = { key2 };
+    SkMessageBus<GrResourceInvalidatedMessage>::Post(msg2);
+    cache->purgeAsNeeded();
+    REPORTER_ASSERT(reporter, 1 == TestResource::NumAlive());
+    REPORTER_ASSERT(reporter, !cache2->hasContentKey(key1));
+    REPORTER_ASSERT(reporter, !cache2->hasContentKey(key2));
+    REPORTER_ASSERT(reporter, cache2->hasContentKey(key3));
+
+    // Invalidate the third.
+    const GrResourceInvalidatedMessage msg3 = { key3 };
+    SkMessageBus<GrResourceInvalidatedMessage>::Post(msg3);
+    cache->purgeAsNeeded();
+    REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
+    REPORTER_ASSERT(reporter, !cache2->hasContentKey(key3));
+}
+
+static void test_cache_delete_on_destruction(skiatest::Reporter* reporter) {
+    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
+    REPORTER_ASSERT(reporter, SkToBool(context));
+    if (NULL == context) {
+        return;
+    }
+
+    GrCacheID::Domain domain = GrCacheID::GenerateDomain();
+    GrCacheID::Key keyData;
+    memset(&keyData, 0, sizeof(keyData));
+    GrResourceKey::ResourceType t = GrResourceKey::GenerateResourceType();
+
+    keyData.fData64[0] = 1;
+    GrResourceKey key1(GrCacheID(domain, keyData), t, 0);
+
+    keyData.fData64[0] = 2;
+    GrResourceKey key2(GrCacheID(domain, keyData), t, 0);
 
     {
         context->setResourceCacheLimits(3, 30000);
@@ -163,15 +310,17 @@
 
         TestResource* a = new TestResource(context->getGpu());
         TestResource* b = new TestResource(context->getGpu());
-        cache->addResource(key, a);
-        cache->addResource(key, b);
+        cache->addResource(key1, a);
+        cache->addResource(key2, b);
 
         a->setDeleteWhenDestroyed(cache, b);
         b->setDeleteWhenDestroyed(cache, a);
 
         a->unref();
         b->unref();
+
         REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
+
         cache->purgeAllUnlocked();
         REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
     }
@@ -180,10 +329,11 @@
         GrResourceCache* cache = context->getResourceCache();
         cache->purgeAllUnlocked();
         SkASSERT(0 == cache->getCachedResourceCount() && 0 == cache->getCachedResourceBytes());
+
         TestResource* a = new TestResource(context->getGpu());
         TestResource* b = new TestResource(context->getGpu());
-        cache->addResource(key, a);
-        cache->addResource(key, b);
+        cache->addResource(key1, a);
+        cache->addResource(key2, b);
 
         a->setDeleteWhenDestroyed(cache, b);
         b->setDeleteWhenDestroyed(cache, a);
@@ -192,13 +342,17 @@
         b->unref();
 
         cache->deleteResource(a->getCacheEntry());
-
         REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
     }
 }
 
-static void test_resource_size_changed(skiatest::Reporter* reporter,
-                                       GrContext* context) {
+static void test_resource_size_changed(skiatest::Reporter* reporter) {
+    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
+    REPORTER_ASSERT(reporter, SkToBool(context));
+    if (NULL == context) {
+        return;
+    }
+
     GrCacheID::Domain domain = GrCacheID::GenerateDomain();
     GrResourceKey::ResourceType t = GrResourceKey::GenerateResourceType();
 
@@ -216,6 +370,7 @@
     {
         context->setResourceCacheLimits(3, 30000);
         GrResourceCache* cache = context->getResourceCache();
+        GrResourceCache2* cache2 = context->getResourceCache2();
         cache->purgeAllUnlocked();
         SkASSERT(0 == cache->getCachedResourceCount() && 0 == cache->getCachedResourceBytes());
 
@@ -231,9 +386,12 @@
 
         REPORTER_ASSERT(reporter, 200 == cache->getCachedResourceBytes());
         REPORTER_ASSERT(reporter, 2 == cache->getCachedResourceCount());
-
-        static_cast<TestResource*>(cache->find(key2))->setSize(200);
-        static_cast<TestResource*>(cache->find(key1))->setSize(50);
+        {
+            SkAutoTUnref<TestResource> find2(static_cast<TestResource*>(cache2->findAndRefContentResource(key2)));
+            find2->setSize(200);
+            SkAutoTUnref<TestResource> find1(static_cast<TestResource*>(cache2->findAndRefContentResource(key1)));
+            find1->setSize(50);
+        }
 
         REPORTER_ASSERT(reporter, 250 == cache->getCachedResourceBytes());
         REPORTER_ASSERT(reporter, 2 == cache->getCachedResourceCount());
@@ -243,22 +401,28 @@
     {
         context->setResourceCacheLimits(2, 300);
         GrResourceCache* cache = context->getResourceCache();
+        GrResourceCache2* cache2 = context->getResourceCache2();
         cache->purgeAllUnlocked();
         SkASSERT(0 == cache->getCachedResourceCount() && 0 == cache->getCachedResourceBytes());
 
-        TestResource* a = new TestResource(context->getGpu(), 100);
+        TestResource* a = new TestResource(context->getGpu());
+        a->setSize(100);
         cache->addResource(key1, a);
         a->unref();
 
-        TestResource* b = new TestResource(context->getGpu(), 100);
+        TestResource* b = new TestResource(context->getGpu());
+        b->setSize(100);
         cache->addResource(key2, b);
         b->unref();
 
         REPORTER_ASSERT(reporter, 200 == cache->getCachedResourceBytes());
         REPORTER_ASSERT(reporter, 2 == cache->getCachedResourceCount());
 
-        static_cast<TestResource*>(cache->find(key2))->setSize(201);
-        REPORTER_ASSERT(reporter, !cache->hasKey(key1));
+        {
+            SkAutoTUnref<TestResource> find2(static_cast<TestResource*>(cache2->findAndRefContentResource(key2)));
+            find2->setSize(201);
+        }
+        REPORTER_ASSERT(reporter, !cache2->hasContentKey(key1));
 
         REPORTER_ASSERT(reporter, 201 == cache->getCachedResourceBytes());
         REPORTER_ASSERT(reporter, 1 == cache->getCachedResourceCount());
@@ -286,16 +450,12 @@
         test_cache(reporter, context, surface->getCanvas());
     }
 
-    // The below tests use a mock context.
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
-    }
-
-    test_purge_invalidated(reporter, context);
-    test_cache_delete_on_destruction(reporter, context);
-    test_resource_size_changed(reporter, context);
+    // The below tests create their own mock contexts.
+    test_duplicate_content_key(reporter);
+    test_duplicate_scratch_key(reporter);
+    test_purge_invalidated(reporter);
+    test_cache_delete_on_destruction(reporter);
+    test_resource_size_changed(reporter);
 }
 
 #endif