Replace GrResourceCache with GrResourceCache2.

BUG=skia:2889

Committed: https://skia.googlesource.com/skia/+/66a450f21a3da174b7eed89a1d5fc8591e8b6ee6

Committed: https://skia.googlesource.com/skia/+/407aa584d183c1bf314f5defd1cf0202e8a96c89

Review URL: https://codereview.chromium.org/716143004
diff --git a/src/gpu/GrResourceCache2.cpp b/src/gpu/GrResourceCache2.cpp
index 83143d7..53e7f88 100644
--- a/src/gpu/GrResourceCache2.cpp
+++ b/src/gpu/GrResourceCache2.cpp
@@ -10,6 +10,13 @@
 #include "GrResourceCache2.h"
 #include "GrGpuResource.h"  
 
+#include "SkGr.h"
+#include "SkMessageBus.h"
+
+DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage);
+
+//////////////////////////////////////////////////////////////////////////////
+
 GrResourceKey& GrResourceKey::NullScratchKey() {
     static const GrCacheID::Key kBogusKey = { { {0} } };
     static GrCacheID kBogusID(ScratchDomain(), kBogusKey);
@@ -27,26 +34,85 @@
     return gDomain;
 }
 
+GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() {
+    static int32_t gNextType = 0;
+
+    int32_t type = sk_atomic_inc(&gNextType);
+    if (type >= (1 << 8 * sizeof(ResourceType))) {
+        SkFAIL("Too many Resource Types");
+    }
+
+    return static_cast<ResourceType>(type);
+}
+
 //////////////////////////////////////////////////////////////////////////////
 
+class GrResourceCache2::AutoValidate : ::SkNoncopyable {
+public:
+    AutoValidate(GrResourceCache2* cache) : fCache(cache) { cache->validate(); }
+    ~AutoValidate() { fCache->validate(); }
+private:
+    GrResourceCache2* fCache;
+};
+
+ //////////////////////////////////////////////////////////////////////////////
+
+static const int kDefaultMaxCount = 2 * (1 << 10);
+static const size_t kDefaultMaxSize = 96 * (1 << 20);
+
+GrResourceCache2::GrResourceCache2()
+    : fMaxCount(kDefaultMaxCount)
+    , fMaxBytes(kDefaultMaxSize)
+#if GR_CACHE_STATS
+    , fHighWaterCount(0)
+    , fHighWaterBytes(0)
+#endif
+    , fCount(0)
+    , fBytes(0)
+    , fPurging(false)
+    , fNewlyPurgableResourceWhilePurging(false)
+    , fOverBudgetCB(NULL)
+    , fOverBudgetData(NULL) {
+}
+
 GrResourceCache2::~GrResourceCache2() {
     this->releaseAll();
 }
 
+void GrResourceCache2::setLimits(int count, size_t bytes) {
+    fMaxCount = count;
+    fMaxBytes = bytes;
+    this->purgeAsNeeded();
+}
+
 void GrResourceCache2::insertResource(GrGpuResource* resource) {
+    AutoValidate av(this);
+
     SkASSERT(resource);
     SkASSERT(!resource->wasDestroyed());
     SkASSERT(!this->isInCache(resource));
+    SkASSERT(!fPurging);
     fResources.addToHead(resource);
+    resource->ref();
+
     ++fCount;
+    SkDEBUGCODE(fHighWaterCount = SkTMax(fCount, fHighWaterCount));
+    fBytes += resource->gpuMemorySize();
+    SkDEBUGCODE(fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes));
     if (!resource->cacheAccess().getScratchKey().isNullScratch()) {
         // TODO(bsalomon): Make this assertion possible.
         // SkASSERT(!resource->isWrapped());
         fScratchMap.insert(resource->cacheAccess().getScratchKey(), resource);
     }
+    
+    this->purgeAsNeeded();
 }
 
 void GrResourceCache2::removeResource(GrGpuResource* resource) {
+    AutoValidate av(this);
+
+    --fCount;
+    fBytes -= resource->gpuMemorySize();
     SkASSERT(this->isInCache(resource));
     fResources.remove(resource);    
     if (!resource->cacheAccess().getScratchKey().isNullScratch()) {
@@ -55,13 +121,16 @@
     if (const GrResourceKey* contentKey = resource->cacheAccess().getContentKey()) {
         fContentHash.remove(*contentKey);
     }
-    --fCount;
 }
 
 void GrResourceCache2::abandonAll() {
+    AutoValidate av(this);
+
+    SkASSERT(!fPurging);
     while (GrGpuResource* head = fResources.head()) {
         SkASSERT(!head->wasDestroyed());
         head->abandon();
+        head->unref();
         // abandon should have already removed this from the list.
         SkASSERT(head != fResources.head());
     }
@@ -71,9 +140,13 @@
 }
 
 void GrResourceCache2::releaseAll() {
+    AutoValidate av(this);
+
+    SkASSERT(!fPurging);
     while (GrGpuResource* head = fResources.head()) {
         SkASSERT(!head->wasDestroyed());
         head->release();
+        head->unref();
         // release should have already removed this from the list.
         SkASSERT(head != fResources.head());
     }
@@ -99,11 +172,16 @@
 
 GrGpuResource* GrResourceCache2::findAndRefScratchResource(const GrResourceKey& scratchKey,
                                                            uint32_t flags) {
+    AutoValidate av(this);
+
+    SkASSERT(!fPurging);
     SkASSERT(scratchKey.isScratch());
 
+    GrGpuResource* resource;
     if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFlag)) {
-        GrGpuResource* resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true));
+        resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true));
         if (resource) {
+            this->makeResourceMRU(resource);
             return SkRef(resource);
         } else if (flags & kRequireNoPendingIO_ScratchFlag) {
             return NULL;
@@ -111,11 +189,18 @@
         // TODO: fail here when kPrefer is specified, we didn't find a resource without pending io,
         // but there is still space in our budget for the resource.
     }
-    return SkSafeRef(fScratchMap.find(scratchKey, AvailableForScratchUse(false)));
+    resource = fScratchMap.find(scratchKey, AvailableForScratchUse(false));
+    if (resource) {
+        resource->ref();
+        this->makeResourceMRU(resource);
+    }
+    return resource;
 }
 
 bool GrResourceCache2::didSetContentKey(GrGpuResource* resource) {
+    SkASSERT(!fPurging);
     SkASSERT(resource);
+    SkASSERT(this->isInCache(resource));
     SkASSERT(resource->cacheAccess().getContentKey());
     SkASSERT(!resource->cacheAccess().getContentKey()->isScratch());
 
@@ -125,5 +210,215 @@
     }
 
     fContentHash.add(resource);
+    this->validate();
     return true;
 }
+
+void GrResourceCache2::makeResourceMRU(GrGpuResource* resource) {
+    AutoValidate av(this);
+
+    SkASSERT(!fPurging);
+    SkASSERT(resource);
+    SkASSERT(this->isInCache(resource));
+    fResources.remove(resource);    
+    fResources.addToHead(resource);    
+}
+
+void GrResourceCache2::notifyPurgable(const GrGpuResource* resource) {
+    SkASSERT(resource);
+    SkASSERT(this->isInCache(resource));
+    SkASSERT(resource->isPurgable());
+
+    // We can't purge if in the middle of purging because purge is iterating. Instead record
+    // that additional resources became purgable.
+    if (fPurging) {
+        fNewlyPurgableResourceWhilePurging = true;
+        return;
+    }
+
+    // Purge the resource if we're over budget
+    bool overBudget = fCount > fMaxCount || fBytes > fMaxBytes;
+
+    // We should not be over budget here unless all resources are unpuragble.
+#ifdef SK_DEBUG
+    if (overBudget) {
+        ResourceList::Iter iter;
+        GrGpuResource* r = iter.init(fResources, ResourceList::Iter::kHead_IterStart);
+        for ( ; r; r = iter.next()) {
+            SkASSERT(r == resource || !r->isPurgable());
+        }
+    }
+#endif
+
+    // Also purge if the resource has neither a valid scratch key nor a content key.
+    bool noKey = !resource->cacheAccess().isScratch() &&
+                 (NULL == resource->cacheAccess().getContentKey());
+
+    if (overBudget || noKey) {
+        SkDEBUGCODE(int beforeCount = fCount;)
+        resource->unref();
+        // We should at least have freed resource. It may have in turn freed other resources.
+        SkASSERT(fCount < beforeCount);
+    }
+
+    this->validate();
+}
+
+void GrResourceCache2::didChangeGpuMemorySize(const GrGpuResource* resource, size_t oldSize) {
+    // SkASSERT(!fPurging); GrPathRange increases size during flush. :(
+    SkASSERT(resource);
+    SkASSERT(this->isInCache(resource));
+
+    fBytes += resource->gpuMemorySize() - oldSize;
+    SkDEBUGCODE(fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes));
+
+    this->purgeAsNeeded();
+    this->validate();
+}
+
+void GrResourceCache2::internalPurgeAsNeeded() {
+    SkASSERT(!fPurging);
+    SkASSERT(!fNewlyPurgableResourceWhilePurging);
+    SkASSERT(fCount > fMaxCount || fBytes > fMaxBytes);
+
+    fPurging = true;
+
+    AutoValidate av(this); // Put this after setting fPurging so we're allowed to be over budget.
+
+    bool overBudget = true;
+    do {
+        fNewlyPurgableResourceWhilePurging = false;
+        ResourceList::Iter resourceIter;
+        GrGpuResource* resource = resourceIter.init(fResources,
+                                                    ResourceList::Iter::kTail_IterStart);
+
+        while (resource) {
+            GrGpuResource* prev = resourceIter.prev();
+            if (resource->isPurgable()) {
+                resource->unref();
+            }
+            resource = prev;
+            if (fCount <= fMaxCount && fBytes <= fMaxBytes) {
+                overBudget = false;
+                resource = NULL;
+            }
+        }
+
+        if (!fNewlyPurgableResourceWhilePurging && overBudget && fOverBudgetCB) {
+            // Despite the purge we're still over budget. Call our over budget callback.
+            (*fOverBudgetCB)(fOverBudgetData);
+        }
+    } while (overBudget && fNewlyPurgableResourceWhilePurging);
+
+    fNewlyPurgableResourceWhilePurging = false;
+    fPurging = false;
+}
+
+void GrResourceCache2::purgeAllUnlocked() {
+    SkASSERT(!fPurging);
+    SkASSERT(!fNewlyPurgableResourceWhilePurging);
+
+    fPurging = true;
+
+    AutoValidate av(this); // Put this after setting fPurging so we're allowed to be over budget.
+
+    do {
+        fNewlyPurgableResourceWhilePurging = false;
+        ResourceList::Iter resourceIter;
+        GrGpuResource* resource =
+            resourceIter.init(fResources, ResourceList::Iter::kTail_IterStart);
+
+        while (resource) {
+            GrGpuResource* prev = resourceIter.prev();
+            if (resource->isPurgable()) {
+                resource->unref();
+            } 
+            resource = prev;
+        }
+
+        if (!fNewlyPurgableResourceWhilePurging && fCount && fOverBudgetCB) {
+            (*fOverBudgetCB)(fOverBudgetData);
+        }
+    } while (fNewlyPurgableResourceWhilePurging);
+    fPurging = false;
+}
+
+#ifdef SK_DEBUG
+void GrResourceCache2::validate() const {
+    size_t bytes = 0;
+    int count = 0;
+    int locked = 0;
+    int scratch = 0;
+    int couldBeScratch = 0;
+    int content = 0;
+
+    ResourceList::Iter iter;
+    GrGpuResource* resource = iter.init(fResources, ResourceList::Iter::kHead_IterStart);
+    for ( ; resource; resource = iter.next()) {
+        bytes += resource->gpuMemorySize();
+        ++count;
+
+        if (!resource->isPurgable()) {
+            ++locked;
+        }
+
+        if (resource->cacheAccess().isScratch()) {
+            SkASSERT(NULL == resource->cacheAccess().getContentKey());
+            ++scratch;
+            SkASSERT(fScratchMap.countForKey(resource->cacheAccess().getScratchKey()));
+        } else if (!resource->cacheAccess().getScratchKey().isNullScratch()) {
+            SkASSERT(NULL != resource->cacheAccess().getContentKey());
+            ++couldBeScratch;
+            SkASSERT(fScratchMap.countForKey(resource->cacheAccess().getScratchKey()));
+        }
+
+        if (const GrResourceKey* contentKey = resource->cacheAccess().getContentKey()) {
+            ++content;
+            SkASSERT(fContentHash.find(*contentKey) == resource);
+        }
+    }
+
+    SkASSERT(bytes == fBytes);
+    SkASSERT(count == fCount);
+#if GR_CACHE_STATS
+    SkASSERT(bytes <= fHighWaterBytes);
+    SkASSERT(count <= fHighWaterCount);
+#endif
+    SkASSERT(content == fContentHash.count());
+    SkASSERT(scratch + couldBeScratch == fScratchMap.count());
+
+    bool overBudget = bytes > fMaxBytes || count > fMaxCount;
+    SkASSERT(!overBudget || locked == count || fPurging);
+}
+#endif
+
+#if GR_CACHE_STATS
+void GrResourceCache2::printStats() const {
+    this->validate();
+
+    int locked = 0;
+    int scratch = 0;
+
+    ResourceList::Iter iter;
+    GrGpuResource* resource = iter.init(fResources, ResourceList::Iter::kHead_IterStart);
+
+    for ( ; resource; resource = iter.next()) {
+        if (!resource->isPurgable()) {
+            ++locked;
+        }
+        if (resource->cacheAccess().isScratch()) {
+            ++scratch;
+        }
+    }
+
+    float countUtilization = (100.f * fCount) / fMaxCount;
+    float byteUtilization = (100.f * fBytes) / fMaxBytes;
+
+    SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes);
+    SkDebugf("\t\tEntry Count: current %d (%d locked, %d scratch %.2g%% full), high %d\n",
+                fCount, locked, scratch, countUtilization, fHighWaterCount);
+    SkDebugf("\t\tEntry Bytes: current %d (%.2g%% full) high %d\n",
+                fBytes, byteUtilization, fHighWaterBytes);
+}
+
+#endif