Adds a mechanism for GrCacheable objects to notify the resource cache
when their size has changed. GrResourceCacheEntry now holds a
reference to the cache, and a cached value of the resource's most
recent size.

Also utilizes this new functionality for mipmaps, and adds a test for
changing resource sizes.

R=bsalomon@google.com, robertphillips@google.com

Author: cdalton@nvidia.com

Review URL: https://codereview.chromium.org/257093002

git-svn-id: http://skia.googlecode.com/svn/trunk@14576 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 26f7592..529c3a5 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -13,6 +13,16 @@
 
 DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage);
 
+///////////////////////////////////////////////////////////////////////////////
+
+void GrCacheable::didChangeGpuMemorySize() const {
+    if (this->isInCache()) {
+        fCacheEntry->didChangeResourceSize();
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
 GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() {
     static int32_t gNextType = 0;
 
@@ -26,8 +36,14 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrResourceCacheEntry::GrResourceCacheEntry(const GrResourceKey& key, GrCacheable* resource)
-        : fKey(key), fResource(resource) {
+GrResourceCacheEntry::GrResourceCacheEntry(GrResourceCache* resourceCache,
+                                           const GrResourceKey& key,
+                                           GrCacheable* resource)
+        : fResourceCache(resourceCache),
+          fKey(key),
+          fResource(resource),
+          fCachedSize(resource->gpuMemorySize()),
+          fIsExclusive(false) {
     // we assume ownership of the resource, and will unref it when we die
     SkASSERT(resource);
     resource->ref();
@@ -40,12 +56,24 @@
 
 #ifdef SK_DEBUG
 void GrResourceCacheEntry::validate() const {
+    SkASSERT(fResourceCache);
     SkASSERT(fResource);
     SkASSERT(fResource->getCacheEntry() == this);
+    SkASSERT(fResource->gpuMemorySize() == fCachedSize);
     fResource->validate();
 }
 #endif
 
+void GrResourceCacheEntry::didChangeResourceSize() {
+    size_t oldSize = fCachedSize;
+    fCachedSize = fResource->gpuMemorySize();
+    if (fCachedSize > oldSize) {
+        fResourceCache->didIncreaseResourceSize(this, fCachedSize - oldSize);
+    } else if (fCachedSize < oldSize) {
+        fResourceCache->didDecreaseResourceSize(this, oldSize - fCachedSize);
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) :
@@ -115,7 +143,7 @@
     // update our stats
     if (kIgnore_BudgetBehavior == behavior) {
         fClientDetachedCount += 1;
-        fClientDetachedBytes += entry->resource()->gpuMemorySize();
+        fClientDetachedBytes += entry->fCachedSize;
 
 #if GR_CACHE_STATS
         if (fHighWaterClientDetachedCount < fClientDetachedCount) {
@@ -130,7 +158,7 @@
         SkASSERT(kAccountFor_BudgetBehavior == behavior);
 
         fEntryCount -= 1;
-        fEntryBytes -= entry->resource()->gpuMemorySize();
+        fEntryBytes -= entry->fCachedSize;
     }
 }
 
@@ -141,12 +169,12 @@
     // update our stats
     if (kIgnore_BudgetBehavior == behavior) {
         fClientDetachedCount -= 1;
-        fClientDetachedBytes -= entry->resource()->gpuMemorySize();
+        fClientDetachedBytes -= entry->fCachedSize;
     } else {
         SkASSERT(kAccountFor_BudgetBehavior == behavior);
 
         fEntryCount += 1;
-        fEntryBytes += entry->resource()->gpuMemorySize();
+        fEntryBytes += entry->fCachedSize;
 
 #if GR_CACHE_STATS
         if (fHighWaterEntryCount < fEntryCount) {
@@ -208,7 +236,7 @@
     SkASSERT(!fPurging);
     GrAutoResourceCacheValidate atcv(this);
 
-    GrResourceCacheEntry* entry = SkNEW_ARGS(GrResourceCacheEntry, (key, resource));
+    GrResourceCacheEntry* entry = SkNEW_ARGS(GrResourceCacheEntry, (this, key, resource));
     resource->setCacheEntry(entry);
 
     this->attachToHead(entry);
@@ -223,6 +251,9 @@
 void GrResourceCache::makeExclusive(GrResourceCacheEntry* entry) {
     GrAutoResourceCacheValidate atcv(this);
 
+    SkASSERT(!entry->fIsExclusive);
+    entry->fIsExclusive = true;
+
     // When scratch textures are detached (to hide them from future finds) they
     // still count against the resource budget
     this->internalDetach(entry, kIgnore_BudgetBehavior);
@@ -239,11 +270,12 @@
     // the client called GrContext::contextDestroyed() to notify Gr,
     // and then later an SkGpuDevice's destructor releases its backing
     // texture (which was invalidated at contextDestroyed time).
+    // TODO: Safely delete the GrResourceCacheEntry as well.
     fClientDetachedCount -= 1;
     fEntryCount -= 1;
-    size_t size = entry->resource()->gpuMemorySize();
-    fClientDetachedBytes -= size;
-    fEntryBytes -= size;
+    fClientDetachedBytes -= entry->fCachedSize;
+    fEntryBytes -= entry->fCachedSize;
+    entry->fCachedSize = 0;
 }
 
 void GrResourceCache::makeNonExclusive(GrResourceCacheEntry* entry) {
@@ -259,11 +291,32 @@
         // alter the budget information.
         attachToHead(entry, kIgnore_BudgetBehavior);
         fCache.insert(entry->key(), entry);
+
+        SkASSERT(entry->fIsExclusive);
+        entry->fIsExclusive = false;
     } else {
         this->removeInvalidResource(entry);
     }
 }
 
+void GrResourceCache::didIncreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountInc) {
+    fEntryBytes += amountInc;
+    if (entry->fIsExclusive) {
+        fClientDetachedBytes += amountInc;
+    }
+    this->purgeAsNeeded();
+}
+
+void GrResourceCache::didDecreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountDec) {
+    fEntryBytes -= amountDec;
+    if (entry->fIsExclusive) {
+        fClientDetachedBytes -= amountDec;
+    }
+#ifdef SK_DEBUG
+    this->validate();
+#endif
+}
+
 /**
  * Destroying a resource may potentially trigger the unlock of additional
  * resources which in turn will trigger a nested purge. We block the nested