Allow unbudgeted resources to be recycled by the cache as scratch.

Review URL: https://codereview.chromium.org/870743002
diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h
index d6abc4d..56b73a0 100644
--- a/include/gpu/GrGpuResource.h
+++ b/include/gpu/GrGpuResource.h
@@ -263,6 +263,7 @@
     void notifyIsPurgable() const;
     void removeScratchKey();
     void makeBudgeted();
+    void makeUnbudgeted();
 
 #ifdef SK_DEBUG
     friend class GrGpu; // for assert in GrGpu to access getGpu
diff --git a/src/gpu/GrGpuResource.cpp b/src/gpu/GrGpuResource.cpp
index 3e9964f..15d5888 100644
--- a/src/gpu/GrGpuResource.cpp
+++ b/src/gpu/GrGpuResource.cpp
@@ -86,8 +86,8 @@
     // Currently this can only be called once and can't be called when the resource is scratch.
     SkASSERT(this->internalHasRef());
 
-    // Wrapped resources can never have a key.
-    if (this->isWrapped()) {
+    // Wrapped and uncached resources can never have a content key.
+    if (!this->cacheAccess().isBudgeted()) {
         return false;
     }
 
@@ -138,6 +138,13 @@
     }
 }
 
+void GrGpuResource::makeUnbudgeted() {
+    if (GrGpuResource::kCached_LifeCycle == fLifeCycle && !fContentKey.isValid()) {
+        fLifeCycle = kUncached_LifeCycle;
+        get_resource_cache2(fGpu)->resourceAccess().didChangeBudgetStatus(this);
+    }
+}
+
 uint32_t GrGpuResource::CreateUniqueID() {
     static int32_t gUniqueID = SK_InvalidUniqueID;
     uint32_t id;
diff --git a/src/gpu/GrGpuResourceCacheAccess.h b/src/gpu/GrGpuResourceCacheAccess.h
index 33fe1ad..474438f 100644
--- a/src/gpu/GrGpuResourceCacheAccess.h
+++ b/src/gpu/GrGpuResourceCacheAccess.h
@@ -29,11 +29,12 @@
     }
 
     /**
-     * Is the resource currently cached as scratch? This means it has a valid scratch key and does
-     * not have a content key.
+     * Is the resource currently cached as scratch? This means it is cached, has a valid scratch
+     * key, and does not have a content key.
      */
     bool isScratch() const {
-        return !this->getContentKey().isValid() && fResource->fScratchKey.isValid();
+        return !this->getContentKey().isValid() && fResource->fScratchKey.isValid() &&
+                this->isBudgeted();
     }
 
     /** 
@@ -62,7 +63,11 @@
     /**
      * Does the resource count against the resource budget?
      */
-    bool isBudgeted() const { return GrGpuResource::kCached_LifeCycle == fResource->fLifeCycle; }
+    bool isBudgeted() const {
+        bool ret = GrGpuResource::kCached_LifeCycle == fResource->fLifeCycle;
+        SkASSERT(ret || !this->getContentKey().isValid());
+        return ret;
+    }
 
     /**
      * If the resource is uncached make it cached. Has no effect on resources that are wrapped or
@@ -71,6 +76,12 @@
     void makeBudgeted() { fResource->makeBudgeted(); }
 
     /**
+     * If the resource is cached make it uncached. Has no effect on resources that are wrapped or
+     * already uncached. Furthermore, resources with content keys cannot be made unbudgeted.
+     */
+    void makeUnbudgeted() { fResource->makeUnbudgeted(); }
+
+    /**
      * Called by the cache to delete the resource under normal circumstances.
      */
     void release() {
diff --git a/src/gpu/GrResourceCache2.cpp b/src/gpu/GrResourceCache2.cpp
index 5f31bf0..87c943e 100644
--- a/src/gpu/GrResourceCache2.cpp
+++ b/src/gpu/GrResourceCache2.cpp
@@ -257,23 +257,42 @@
         return;
     }
 
-    // Purge the resource if we're over budget
-    bool overBudget = fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxBytes;
+    bool release = false;
 
-    // Also purge if the resource has neither a valid scratch key nor a content key.
-    bool noKey = !resource->cacheAccess().getScratchKey().isValid() &&
-                 !resource->cacheAccess().getContentKey().isValid();
+    if (resource->cacheAccess().isWrapped()) {
+        release = true;
+    } else if (!resource->cacheAccess().isBudgeted()) {
+        // Check whether this resource could still be used as a scratch resource.
+        if (resource->cacheAccess().getScratchKey().isValid()) {
+            // We won't purge an existing resource to make room for this one.
+            bool underBudget = fBudgetedCount < fMaxCount &&
+                               fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes;
+            if (underBudget) {
+                resource->cacheAccess().makeBudgeted();
+            } else {
+                release = true;
+            }
+        } else {
+            release = true;
+        }
+    } else {
+        // Purge the resource if we're over budget
+        bool overBudget = fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxBytes;
 
-    // Only cached resources should ever have a key.
-    SkASSERT(noKey || resource->cacheAccess().isBudgeted());
+        // Also purge if the resource has neither a valid scratch key nor a content key.
+        bool noKey = !resource->cacheAccess().getScratchKey().isValid() &&
+                     !resource->cacheAccess().getContentKey().isValid();
+        if (overBudget || noKey) {
+            release = true;
+        }
+    }
 
-    if (overBudget || noKey) {
+    if (release) {
         SkDEBUGCODE(int beforeCount = fCount;)
         resource->cacheAccess().release();
         // We should at least free this resource, perhaps dependent resources as well.
         SkASSERT(fCount < beforeCount);
     }
-
     this->validate();
 }
 
@@ -389,6 +408,13 @@
 
 #ifdef SK_DEBUG
 void GrResourceCache2::validate() const {
+    // Reduce the frequency of validations for large resource counts.
+    static SkRandom gRandom;
+    int mask = (SkNextPow2(fCount + 1) >> 5) - 1;
+    if (~mask && (gRandom.nextU() & mask)) {
+        return;
+    }
+
     size_t bytes = 0;
     int count = 0;
     int budgetedCount = 0;
@@ -414,7 +440,8 @@
             SkASSERT(fScratchMap.countForKey(resource->cacheAccess().getScratchKey()));
             SkASSERT(!resource->cacheAccess().isWrapped());
         } else if (resource->cacheAccess().getScratchKey().isValid()) {
-            SkASSERT(resource->cacheAccess().getContentKey().isValid());
+            SkASSERT(!resource->cacheAccess().isBudgeted() ||
+                     resource->cacheAccess().getContentKey().isValid());
             ++couldBeScratch;
             SkASSERT(fScratchMap.countForKey(resource->cacheAccess().getScratchKey()));
             SkASSERT(!resource->cacheAccess().isWrapped());
@@ -424,6 +451,7 @@
             ++content;
             SkASSERT(fContentHash.find(contentKey) == resource);
             SkASSERT(!resource->cacheAccess().isWrapped());
+            SkASSERT(resource->cacheAccess().isBudgeted());
         }
 
         if (resource->cacheAccess().isBudgeted()) {
diff --git a/src/gpu/GrTexture.cpp b/src/gpu/GrTexture.cpp
index 30dbbb6..6bd4586 100644
--- a/src/gpu/GrTexture.cpp
+++ b/src/gpu/GrTexture.cpp
@@ -87,7 +87,7 @@
     : INHERITED(gpu, lifeCycle, desc)
     , fMipMapsStatus(kNotAllocated_MipMapsStatus) {
 
-    if (kCached_LifeCycle == lifeCycle) {
+    if (kWrapped_LifeCycle != lifeCycle) {
         GrScratchKey key;
         GrTexturePriv::ComputeScratchKey(desc, &key);
         this->setScratchKey(key);
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index 6b7d68c..f98aaf1 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -97,8 +97,10 @@
         this->registerWithCache();
     }
 
-    static TestResource* CreateScratchTestResource(GrGpu* gpu, SimulatedProperty property) {
-        return SkNEW_ARGS(TestResource, (gpu, property, kScratchConstructor));
+    static TestResource* CreateScratchTestResource(GrGpu* gpu,
+                                                   SimulatedProperty property,
+                                                   bool cached = true) {
+        return SkNEW_ARGS(TestResource, (gpu, property, cached, kScratchConstructor));
     }
 
     ~TestResource() {
@@ -132,8 +134,8 @@
 private:
     static const int kScratchKeyFieldCnt = 6;
 
-    TestResource(GrGpu* gpu, SimulatedProperty property, ScratchConstructor)
-        : INHERITED(gpu, kCached_LifeCycle)
+    TestResource(GrGpu* gpu, SimulatedProperty property, bool cached, ScratchConstructor)
+        : INHERITED(gpu, cached ? kCached_LifeCycle : kUncached_LifeCycle)
         , fToDelete(NULL)
         , fSize(kDefaultSize)
         , fProperty(property) {
@@ -154,16 +156,29 @@
 };
 int TestResource::fNumAlive = 0;
 
-static void test_no_key(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
+class Mock {
+public:
+    Mock(int maxCnt, size_t maxBytes) {
+        fContext.reset(GrContext::CreateMockContext());
+        SkASSERT(fContext);
+        fContext->setResourceCacheLimits(maxCnt, maxBytes);
+        GrResourceCache2* cache2 = fContext->getResourceCache2();
+        cache2->purgeAllUnlocked();
+        SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
     }
-    context->setResourceCacheLimits(10, 30000);
-    GrResourceCache2* cache2 = context->getResourceCache2();
-    cache2->purgeAllUnlocked();
-    SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
+
+    GrResourceCache2* cache() { return fContext->getResourceCache2(); }
+
+    GrContext* context() { return fContext; }
+
+private:
+    SkAutoTUnref<GrContext> fContext;
+};
+
+static void test_no_key(skiatest::Reporter* reporter) {
+    Mock mock(10, 30000);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     // Create a bunch of resources with no keys
     TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
@@ -217,16 +232,9 @@
 }
 
 static void test_budgeting(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
-    }
-    context->setResourceCacheLimits(10, 300);
-    GrResourceCache2* cache2 = context->getResourceCache2();
-    cache2->purgeAllUnlocked();
-    SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
-    SkASSERT(0 == cache2->getBudgetedResourceCount() && 0 == cache2->getBudgetedResourceBytes());
+    Mock mock(10, 300);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     GrContentKey contentKey;
     make_content_key<0>(&contentKey, 0);
@@ -310,16 +318,9 @@
 }
 
 static void test_unbudgeted(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
-    }
-    context->setResourceCacheLimits(10, 300);
-    GrResourceCache2* cache2 = context->getResourceCache2();
-    cache2->purgeAllUnlocked();
-    SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
-    SkASSERT(0 == cache2->getBudgetedResourceCount() && 0 == cache2->getBudgetedResourceBytes());
+    Mock mock(10, 30000);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     GrContentKey contentKey;
     make_content_key<0>(&contentKey, 0);
@@ -382,16 +383,70 @@
     REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceBytes());
 }
 
-static void test_duplicate_scratch_key(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
+static void test_unbudgeted_to_scratch(skiatest::Reporter* reporter) {
+    Mock mock(10, 300);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
+
+    TestResource* resource =
+        TestResource::CreateScratchTestResource(context->getGpu(),
+                                                TestResource::kProperty1_SimulatedProperty, false);
+    GrScratchKey key;
+    TestResource::ComputeScratchKey(TestResource::kProperty1_SimulatedProperty, &key);
+
+    size_t size = resource->gpuMemorySize();
+    for (int i = 0; i < 2; ++i) {
+        // Since this resource is unbudgeted, it should not be reachable as scratch.
+        REPORTER_ASSERT(reporter, resource->cacheAccess().getScratchKey() == key);
+        REPORTER_ASSERT(reporter, !resource->cacheAccess().isScratch());
+        REPORTER_ASSERT(reporter, !resource->cacheAccess().isBudgeted());
+        REPORTER_ASSERT(reporter, NULL == cache2->findAndRefScratchResource(key));
+        REPORTER_ASSERT(reporter, 1 == cache2->getResourceCount());
+        REPORTER_ASSERT(reporter, size == cache2->getResourceBytes());
+        REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceCount());
+        REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceBytes());
+
+        // Once it is unrefed, it should become available as scratch.
+        resource->unref();
+        REPORTER_ASSERT(reporter, 1 == cache2->getResourceCount());
+        REPORTER_ASSERT(reporter, size == cache2->getResourceBytes());
+        REPORTER_ASSERT(reporter, 1 == cache2->getBudgetedResourceCount());
+        REPORTER_ASSERT(reporter, size == cache2->getBudgetedResourceBytes());
+        resource = static_cast<TestResource*>(cache2->findAndRefScratchResource(key));
+        REPORTER_ASSERT(reporter, resource);
+        REPORTER_ASSERT(reporter, resource->cacheAccess().getScratchKey() == key);
+        REPORTER_ASSERT(reporter, resource->cacheAccess().isScratch());
+        REPORTER_ASSERT(reporter, resource->cacheAccess().isBudgeted());
+
+        if (0 == i) {
+            // If made unbudgeted, it should return to original state: ref'ed and unbudgeted. Try 
+            // the above tests again.
+            resource->cacheAccess().makeUnbudgeted();
+        } else {
+            // After the second time around, try removing the scratch key
+            resource->cacheAccess().removeScratchKey();
+            REPORTER_ASSERT(reporter, 1 == cache2->getResourceCount());
+            REPORTER_ASSERT(reporter, size == cache2->getResourceBytes());
+            REPORTER_ASSERT(reporter, 1 == cache2->getBudgetedResourceCount());
+            REPORTER_ASSERT(reporter, size == cache2->getBudgetedResourceBytes());
+            REPORTER_ASSERT(reporter, !resource->cacheAccess().getScratchKey().isValid());
+            REPORTER_ASSERT(reporter, !resource->cacheAccess().isScratch());
+            REPORTER_ASSERT(reporter, resource->cacheAccess().isBudgeted());
+
+            // now when it is unrefed it should die since it has no key.
+            resource->unref();
+            REPORTER_ASSERT(reporter, 0 == cache2->getResourceCount());
+            REPORTER_ASSERT(reporter, 0 == cache2->getResourceBytes());
+            REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceCount());
+            REPORTER_ASSERT(reporter, 0 == cache2->getBudgetedResourceBytes());
+        }
     }
-    context->setResourceCacheLimits(5, 30000);
-    GrResourceCache2* cache2 = context->getResourceCache2();
-    cache2->purgeAllUnlocked();
-    SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
+}
+
+static void test_duplicate_scratch_key(skiatest::Reporter* reporter) {
+    Mock mock(5, 30000);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     // Create two resources that have the same scratch key.
     TestResource* a =
@@ -436,15 +491,9 @@
 }
 
 static void test_remove_scratch_key(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
-    }
-    context->setResourceCacheLimits(5, 30000);
-    GrResourceCache2* cache2 = context->getResourceCache2();
-    cache2->purgeAllUnlocked();
-    SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
+    Mock mock(5, 30000);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     // Create two resources that have the same scratch key.
     TestResource* a =
@@ -504,15 +553,9 @@
 }
 
 static void test_scratch_key_consistency(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
-    }
-    context->setResourceCacheLimits(5, 30000);
-    GrResourceCache2* cache2 = context->getResourceCache2();
-    cache2->purgeAllUnlocked();
-    SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
+    Mock mock(5, 30000);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     // Create two resources that have the same scratch key.
     TestResource* a =
@@ -571,15 +614,9 @@
 }
 
 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);
-    GrResourceCache2* cache2 = context->getResourceCache2();
-    cache2->purgeAllUnlocked();
-    SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
+    Mock mock(5, 30000);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     GrContentKey key;
     make_content_key<0>(&key, 0);
@@ -624,22 +661,15 @@
 }
 
 static void test_purge_invalidated(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
-    }
+    Mock mock(5, 30000);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     GrContentKey key1, key2, key3;
     make_content_key<0>(&key1, 1);
     make_content_key<0>(&key2, 2);
     make_content_key<0>(&key3, 3);
     
-    context->setResourceCacheLimits(5, 30000);
-    GrResourceCache2* cache2 = context->getResourceCache2();
-    cache2->purgeAllUnlocked();
-    SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
-
     // Add three resources to the cache.
     TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
     TestResource* b = SkNEW_ARGS(TestResource, (context->getGpu()));
@@ -685,67 +715,52 @@
 }
 
 static void test_cache_chained_purge(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
-    }
+    Mock mock(3, 30000);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     GrContentKey key1, key2;
     make_content_key<0>(&key1, 1);
     make_content_key<0>(&key2, 2);
 
-    {
-        context->setResourceCacheLimits(3, 30000);
-        GrResourceCache2* cache2 = context->getResourceCache2();
-        cache2->purgeAllUnlocked();
-        SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
 
-        TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
-        TestResource* b = SkNEW_ARGS(TestResource, (context->getGpu()));
-        a->cacheAccess().setContentKey(key1);
-        b->cacheAccess().setContentKey(key2);
+    TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
+    TestResource* b = SkNEW_ARGS(TestResource, (context->getGpu()));
+    a->cacheAccess().setContentKey(key1);
+    b->cacheAccess().setContentKey(key2);
 
-        // Make a cycle
-        a->setUnrefWhenDestroyed(b);
-        b->setUnrefWhenDestroyed(a);
+    // Make a cycle
+    a->setUnrefWhenDestroyed(b);
+    b->setUnrefWhenDestroyed(a);
 
-        REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
+    REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
 
-        a->unref();
-        b->unref();
+    a->unref();
+    b->unref();
 
-        REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
+    REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
 
-        cache2->purgeAllUnlocked();
-        REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
+    cache2->purgeAllUnlocked();
+    REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
 
-        // Break the cycle
-        a->setUnrefWhenDestroyed(NULL);
-        REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
+    // Break the cycle
+    a->setUnrefWhenDestroyed(NULL);
+    REPORTER_ASSERT(reporter, 2 == TestResource::NumAlive());
 
-        cache2->purgeAllUnlocked();
-        REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
-    }
+    cache2->purgeAllUnlocked();
+    REPORTER_ASSERT(reporter, 0 == TestResource::NumAlive());
 }
 
 static void test_resource_size_changed(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
-    }
-
     GrContentKey key1, key2;
     make_content_key<0>(&key1, 1);
     make_content_key<0>(&key2, 2);
 
     // Test changing resources sizes (both increase & decrease).
     {
-        context->setResourceCacheLimits(3, 30000);
-        GrResourceCache2* cache2 = context->getResourceCache2();
-        cache2->purgeAllUnlocked();
-        SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
+        Mock mock(3, 30000);
+        GrContext* context = mock.context();
+        GrResourceCache2* cache2 = mock.cache();
 
         TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
         a->cacheAccess().setContentKey(key1);
@@ -770,10 +785,9 @@
 
     // Test increasing a resources size beyond the cache budget.
     {
-        context->setResourceCacheLimits(2, 300);
-        GrResourceCache2* cache2 = context->getResourceCache2();
-        cache2->purgeAllUnlocked();
-        SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
+        Mock mock(2, 300);
+        GrContext* context = mock.context();
+        GrResourceCache2* cache2 = mock.cache();
 
         TestResource* a = SkNEW_ARGS(TestResource, (context->getGpu()));
         a->setSize(100);
@@ -800,21 +814,14 @@
 }
 
 static void test_large_resource_count(skiatest::Reporter* reporter) {
-    SkAutoTUnref<GrContext> context(GrContext::CreateMockContext());
-    REPORTER_ASSERT(reporter, SkToBool(context));
-    if (NULL == context) {
-        return;
-    }
-
-    static const int kResourceCnt = 2000;
     // Set the cache size to double the resource count because we're going to create 2x that number
     // resources, using two different key domains. Add a little slop to the bytes because we resize
     // down to 1 byte after creating the resource.
-    context->setResourceCacheLimits(2 * kResourceCnt, 2 * kResourceCnt + 1000);
-    GrResourceCache2* cache2 = context->getResourceCache2();
-    cache2->purgeAllUnlocked();
-    SkASSERT(0 == cache2->getResourceCount() && 0 == cache2->getResourceBytes());
+    static const int kResourceCnt = 2000;
 
+    Mock mock(2 * kResourceCnt, 2 * kResourceCnt + 1000);
+    GrContext* context = mock.context();
+    GrResourceCache2* cache2 = mock.cache();
 
     for (int i = 0; i < kResourceCnt; ++i) {
         GrContentKey key1, key2;
@@ -892,6 +899,7 @@
     test_no_key(reporter);
     test_budgeting(reporter);
     test_unbudgeted(reporter);
+    test_unbudgeted_to_scratch(reporter);
     test_duplicate_content_key(reporter);
     test_duplicate_scratch_key(reporter);
     test_remove_scratch_key(reporter);