Prepare to enable explicit gpu resource allocation (take 2)

Change-Id: I3fd78d53e8bea84c0217b9fe6e180eaa9e4ac753
Reviewed-on: https://skia-review.googlesource.com/68920
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/include/private/GrSurfaceProxy.h b/include/private/GrSurfaceProxy.h
index 0eccdce..23f6219 100644
--- a/include/private/GrSurfaceProxy.h
+++ b/include/private/GrSurfaceProxy.h
@@ -130,6 +130,7 @@
     void transferRefs() {
         SkASSERT(fTarget);
 
+        SkASSERT(fTarget->fRefCnt > 0);
         fTarget->fRefCnt += (fRefCnt-1); // don't xfer the proxy's creation ref
         fTarget->fPendingReads += fPendingReads;
         fTarget->fPendingWrites += fPendingWrites;
diff --git a/src/core/SkBlurImageFilter.cpp b/src/core/SkBlurImageFilter.cpp
index bb2da09..2f0759c 100644
--- a/src/core/SkBlurImageFilter.cpp
+++ b/src/core/SkBlurImageFilter.cpp
@@ -578,7 +578,7 @@
         result = this->gpuFilter(source, sigma, input, inputBounds, dstBounds,
                                  ctx.outputProperties());
     } else
-    #endif
+#endif
     {
         // If both sigmas will result in a zero width window, there is nothing to do.
         if (sigma.x() < kZeroWindow && sigma.y() < kZeroWindow) {
diff --git a/src/core/SkTMultiMap.h b/src/core/SkTMultiMap.h
index 3d6e38e..987a306 100644
--- a/src/core/SkTMultiMap.h
+++ b/src/core/SkTMultiMap.h
@@ -34,6 +34,7 @@
         for ( ; !iter.done(); ++iter) {
             ValueList* next;
             for (ValueList* cur = &(*iter); cur; cur = next) {
+                HashTraits::OnFree(cur->fValue);
                 next = cur->fNext;
                 delete cur;
             }
@@ -69,20 +70,7 @@
             list = list->fNext;
         }
 
-        if (list->fNext) {
-            ValueList* next = list->fNext;
-            list->fValue = next->fValue;
-            list->fNext = next->fNext;
-            delete next;
-        } else if (prev) {
-            prev->fNext = nullptr;
-            delete list;
-        } else {
-            fHash.remove(key);
-            delete list;
-        }
-
-        --fCount;
+        this->internalRemove(prev, list, key);
     }
 
     T* find(const Key& key) const {
@@ -105,6 +93,23 @@
         return nullptr;
     }
 
+    template<class FindPredicate>
+    T* findAndRemove(const Key& key, const FindPredicate f) {
+        ValueList* list = fHash.find(key);
+
+        ValueList* prev = nullptr;
+        while (list) {
+            if (f(list->fValue)){
+                T* value = list->fValue;
+                this->internalRemove(prev, list, key);
+                return value;
+            }
+            prev = list;
+            list = list->fNext;
+        }
+        return nullptr;
+    }
+
     int count() const { return fCount; }
 
 #ifdef SK_DEBUG
@@ -168,6 +173,24 @@
 private:
     SkTDynamicHash<ValueList, Key> fHash;
     int fCount;
+
+    void internalRemove(ValueList* prev, ValueList* elem, const Key& key) {
+        if (elem->fNext) {
+            ValueList* next = elem->fNext;
+            elem->fValue = next->fValue;
+            elem->fNext = next->fNext;
+            delete next;
+        } else if (prev) {
+            prev->fNext = nullptr;
+            delete elem;
+        } else {
+            fHash.remove(key);
+            delete elem;
+        }
+
+        --fCount;
+    }
+
 };
 
 #endif
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index eaacac2..1aaa6f7 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -171,14 +171,16 @@
     }
 #endif
 
-    GrResourceAllocator alloc(fContext->resourceProvider());
-    for (int i = 0; i < fOpLists.count(); ++i) {
-        fOpLists[i]->gatherProxyIntervals(&alloc);
-    }
+    {
+        GrResourceAllocator alloc(fContext->resourceProvider());
+        for (int i = 0; i < fOpLists.count(); ++i) {
+            fOpLists[i]->gatherProxyIntervals(&alloc);
+        }
 
 #ifdef MDB_ALLOC_RESOURCES
-    alloc.assign();
+        alloc.assign();
 #endif
+    }
 
     for (int i = 0; i < fOpLists.count(); ++i) {
         if (!fOpLists[i]->instantiate(fContext->resourceProvider())) {
diff --git a/src/gpu/GrRenderTargetOpList.cpp b/src/gpu/GrRenderTargetOpList.cpp
index 09486c7..e6b91ff 100644
--- a/src/gpu/GrRenderTargetOpList.cpp
+++ b/src/gpu/GrRenderTargetOpList.cpp
@@ -257,14 +257,18 @@
 }
 
 void GrRenderTargetOpList::gatherProxyIntervals(GrResourceAllocator* alloc) const {
-    if (!fRecordedOps.count()) {
-        return;
-    }
-
     unsigned int cur = alloc->numOps();
 
     // Add the interval for all the writes to this opList's target
-    alloc->addInterval(fTarget.get(), cur, cur+fRecordedOps.count()-1);
+    if (fRecordedOps.count()) {
+        alloc->addInterval(fTarget.get(), cur, cur+fRecordedOps.count()-1);
+    } else {
+        // This can happen if there is a loadOp (e.g., a clear) but no other draws. In this case we
+        // still need to add an interval for the destination so we create a fake op# for
+        // the missing clear op.
+        alloc->addInterval(fTarget.get());
+        alloc->incOps();
+    }
 
     auto gather = [ alloc ] (GrSurfaceProxy* p) {
         alloc->addInterval(p);
@@ -273,9 +277,11 @@
         const GrOp* op = fRecordedOps[i].fOp.get(); // only diff from the GrTextureOpList version
         if (op) {
             op->visitProxies(gather);
-
-            alloc->incOps();
         }
+
+        // Even though the op may have been moved we still need to increment the op count to
+        // keep all the math consistent.
+        alloc->incOps();
     }
 }
 
diff --git a/src/gpu/GrResourceAllocator.cpp b/src/gpu/GrResourceAllocator.cpp
index 62f780b..f263802 100644
--- a/src/gpu/GrResourceAllocator.cpp
+++ b/src/gpu/GrResourceAllocator.cpp
@@ -7,8 +7,12 @@
 
 #include "GrResourceAllocator.h"
 
+#include "GrGpuResourcePriv.h"
+#include "GrResourceProvider.h"
+#include "GrSurfacePriv.h"
 #include "GrSurfaceProxy.h"
 #include "GrSurfaceProxyPriv.h"
+#include "GrTextureProxy.h"
 
 void GrResourceAllocator::addInterval(GrSurfaceProxy* proxy,
                                       unsigned int start, unsigned int end) {
@@ -17,15 +21,18 @@
 
     if (Interval* intvl = fIntvlHash.find(proxy->uniqueID().asUInt())) {
         // Revise the interval for an existing use
-        //SkASSERT(intvl->fEnd <= end);
-        intvl->fEnd = end;
+        // TODO: this assert is failing on the copy_on_write_retain GM!
+        //SkASSERT(intvl->end() <= start);
+        if (intvl->end() < end) {
+            intvl->extendEnd(end);
+        }
         return;
     }
 
     Interval* newIntvl;
     if (fFreeIntervalList) {
         newIntvl = fFreeIntervalList;
-        fFreeIntervalList = newIntvl->fNext;
+        fFreeIntervalList = newIntvl->next();
         newIntvl->resetTo(proxy, start, end);
     } else {
         newIntvl = fIntervalAllocator.make<Interval>(proxy, start, end);
@@ -38,7 +45,7 @@
 GrResourceAllocator::Interval* GrResourceAllocator::IntervalList::popHead() {
     Interval* temp = fHead;
     if (temp) {
-        fHead = temp->fNext;
+        fHead = temp->next();
     }
     return temp;
 }
@@ -46,36 +53,36 @@
 // TODO: fuse this with insertByIncreasingEnd
 void GrResourceAllocator::IntervalList::insertByIncreasingStart(Interval* intvl) {
     if (!fHead) {
-        intvl->fNext = nullptr;
+        intvl->setNext(nullptr);
         fHead = intvl;
-    } else if (intvl->fStart <= fHead->fStart) {
-        intvl->fNext = fHead;
+    } else if (intvl->start() <= fHead->start()) {
+        intvl->setNext(fHead);
         fHead = intvl;
     } else {
         Interval* prev = fHead;
-        Interval* next = prev->fNext;
-        for (; next && intvl->fStart > next->fStart; prev = next, next = next->fNext) {
+        Interval* next = prev->next();
+        for (; next && intvl->start() > next->start(); prev = next, next = next->next()) {
         }
-        intvl->fNext = next;
-        prev->fNext = intvl;
+        intvl->setNext(next);
+        prev->setNext(intvl);
     }
 }
 
 // TODO: fuse this with insertByIncreasingStart
 void GrResourceAllocator::IntervalList::insertByIncreasingEnd(Interval* intvl) {
     if (!fHead) {
-        intvl->fNext = nullptr;
+        intvl->setNext(nullptr);
         fHead = intvl;
-    } else if (intvl->fEnd <= fHead->fEnd) {
-        intvl->fNext = fHead;
+    } else if (intvl->end() <= fHead->end()) {
+        intvl->setNext(fHead);
         fHead = intvl;
     } else {
         Interval* prev = fHead;
-        Interval* next = prev->fNext;
-        for (; next && intvl->fEnd > next->fEnd; prev = next, next = next->fNext) {
+        Interval* next = prev->next();
+        for (; next && intvl->end() > next->end(); prev = next, next = next->next()) {
         }
-        intvl->fNext = next;
-        prev->fNext = intvl;
+        intvl->setNext(next);
+        prev->setNext(intvl);
     }
 }
 
@@ -87,8 +94,15 @@
         return; // can't do it w/o a valid scratch key
     }
 
+    if (surface->getUniqueKey().isValid()) {
+        // If the surface has a unique key we throw it back into the resource cache.
+        // If things get really tight 'findSurfaceFor' may pull it back out but there is
+        // no need to have it in tight rotation.
+        return;
+    }
+
     // TODO: fix this insertion so we get a more LRU-ish behavior
-    fFreePool.insert(key, surface);
+    fFreePool.insert(key, SkRef(surface));
 }
 
 // First try to reuse one of the recently allocated/used GrSurfaces in the free pool.
@@ -100,9 +114,19 @@
 
     proxy->priv().computeScratchKey(&key);
 
-    GrSurface* surface = fFreePool.find(key);
+    auto filter = [&] (const GrSurface* s) {
+        return !proxy->priv().requiresNoPendingIO() || !s->surfacePriv().hasPendingIO();
+    };
+    sk_sp<GrSurface> surface(fFreePool.findAndRemove(key, filter));
     if (surface) {
-        return sk_ref_sp(surface);
+        if (SkBudgeted::kYes == proxy->isBudgeted() &&
+            SkBudgeted::kNo == surface->resourcePriv().isBudgeted()) {
+            // This gets the job done but isn't quite correct. It would be better to try to
+            // match budgeted proxies w/ budgeted surface and unbudgeted w/ unbudgeted.
+            surface->resourcePriv().makeBudgeted();
+        }
+
+        return surface;
     }
 
     // Failing that, try to grab a new one from the resource cache
@@ -112,12 +136,12 @@
 // Remove any intervals that end before the current index. Return their GrSurfaces
 // to the free pool.
 void GrResourceAllocator::expire(unsigned int curIndex) {
-    while (!fActiveIntvls.empty() && fActiveIntvls.peekHead()->fEnd < curIndex) {
+    while (!fActiveIntvls.empty() && fActiveIntvls.peekHead()->end() < curIndex) {
         Interval* temp = fActiveIntvls.popHead();
-        this->freeUpSurface(temp->fProxy->priv().peekSurface());
+        this->freeUpSurface(temp->proxy()->priv().peekSurface());
 
         // Add temp to the free interval list so it can be reused
-        temp->fNext = fFreeIntervalList;
+        temp->setNext(fFreeIntervalList);
         fFreeIntervalList = temp;
     }
 }
@@ -127,51 +151,26 @@
     SkDEBUGCODE(fAssigned = true;)
 
     while (Interval* cur = fIntvlList.popHead()) {
-        this->expire(cur->fStart);
+        this->expire(cur->start());
 
-        if (cur->fProxy->priv().isInstantiated()) {
+        if (cur->proxy()->priv().isInstantiated()) {
             fActiveIntvls.insertByIncreasingEnd(cur);
             continue;
         }
 
         // TODO: add over budget handling here?
-        sk_sp<GrSurface> surface = this->findSurfaceFor(cur->fProxy);
+        sk_sp<GrSurface> surface = this->findSurfaceFor(cur->proxy());
         if (surface) {
-            cur->fProxy->priv().assign(std::move(surface));
+            // TODO: make getUniqueKey virtual on GrSurfaceProxy
+            GrTextureProxy* tex = cur->proxy()->asTextureProxy();
+            if (tex && tex->getUniqueKey().isValid()) {
+                fResourceProvider->assignUniqueKeyToResource(tex->getUniqueKey(), surface.get());
+                SkASSERT(surface->getUniqueKey() == tex->getUniqueKey());
+            }
+
+            cur->proxy()->priv().assign(std::move(surface));
         }
         // TODO: handle resouce allocation failure upstack
         fActiveIntvls.insertByIncreasingEnd(cur);
     }
 }
-
-#ifdef SK_DEBUG
-void GrResourceAllocator::dump() {
-    unsigned int min = fNumOps+1;
-    unsigned int max = 0;
-    for(const Interval* cur = fIntvlList.peekHead(); cur; cur = cur->fNext) {
-        SkDebugf("{ %d,%d }: [%d, %d]\n",
-                 cur->fProxy->uniqueID().asUInt(), cur->fProxy->underlyingUniqueID().asUInt(),
-                 cur->fStart, cur->fEnd);
-        if (min > cur->fStart) {
-            min = cur->fStart;
-        }
-        if (max < cur->fEnd) {
-            max = cur->fEnd;
-        }
-    }
-
-    for(const Interval* cur = fIntvlList.peekHead(); cur; cur = cur->fNext) {
-        SkDebugf("{ %3d,%3d }: ",
-                 cur->fProxy->uniqueID().asUInt(), cur->fProxy->underlyingUniqueID().asUInt());
-        for (unsigned int i = min; i <= max; ++i) {
-            if (i >= cur->fStart && i <= cur->fEnd) {
-                SkDebugf("x");
-            } else {
-                SkDebugf(" ");
-            }
-        }
-        SkDebugf("\n");
-    }
-}
-#endif
-
diff --git a/src/gpu/GrResourceAllocator.h b/src/gpu/GrResourceAllocator.h
index 3be4836..d7aed05 100644
--- a/src/gpu/GrResourceAllocator.h
+++ b/src/gpu/GrResourceAllocator.h
@@ -54,7 +54,6 @@
     }
 
     void assign();
-    SkDEBUGCODE(void dump();)
 
 private:
     class Interval;
@@ -72,6 +71,7 @@
         }
 
         static uint32_t Hash(const GrScratchKey& key) { return key.hash(); }
+        static void OnFree(GrSurface* s) { s->unref(); }
     };
     typedef SkTMultiMap<GrSurface, GrScratchKey, FreePoolTraits> FreePoolMultiMap;
 
@@ -98,12 +98,27 @@
             fNext = nullptr;
         }
 
+        const GrSurfaceProxy* proxy() const { return fProxy; }
+        GrSurfaceProxy* proxy() { return fProxy; }
+        unsigned int start() const { return fStart; }
+        unsigned int end() const { return fEnd; }
+        const Interval* next() const { return fNext; }
+        Interval* next() { return fNext; }
+
+        void setNext(Interval* next) { fNext = next; }
+
+        void extendEnd(unsigned int newEnd) {
+            SkASSERT(newEnd >= fEnd);
+            fEnd = newEnd;
+        }
+
         // for SkTDynamicHash
         static const uint32_t& GetKey(const Interval& intvl) {
             return intvl.fProxyID;
         }
         static uint32_t Hash(const uint32_t& key) { return key; }
 
+    private:
         GrSurfaceProxy* fProxy;
         uint32_t        fProxyID; // This is here b.c. DynamicHash requires a ref to the key
         unsigned int    fStart;
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index 771196c..9424532 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -368,6 +368,7 @@
         }
 
         static uint32_t Hash(const GrScratchKey& key) { return key.hash(); }
+        static void OnFree(GrGpuResource*) { }
     };
     typedef SkTMultiMap<GrGpuResource, GrScratchKey, ScratchMapTraits> ScratchMap;
 
diff --git a/src/gpu/GrSurface.cpp b/src/gpu/GrSurface.cpp
index 24b42f3..ec503d7 100644
--- a/src/gpu/GrSurface.cpp
+++ b/src/gpu/GrSurface.cpp
@@ -9,6 +9,7 @@
 #include "GrContext.h"
 #include "GrOpList.h"
 #include "GrRenderTarget.h"
+#include "GrResourceProvider.h"
 #include "GrSurfacePriv.h"
 #include "GrTexture.h"
 
@@ -18,8 +19,12 @@
 size_t GrSurface::WorstCaseSize(const GrSurfaceDesc& desc, bool useNextPow2) {
     size_t size;
 
-    int width = useNextPow2 ? GrNextPow2(desc.fWidth) : desc.fWidth;
-    int height = useNextPow2 ? GrNextPow2(desc.fHeight) : desc.fHeight;
+    int width = useNextPow2
+                ? SkTMax(GrResourceProvider::kMinScratchTextureSize, GrNextPow2(desc.fWidth))
+                : desc.fWidth;
+    int height = useNextPow2
+                ? SkTMax(GrResourceProvider::kMinScratchTextureSize, GrNextPow2(desc.fHeight))
+                : desc.fHeight;
 
     bool isRenderTarget = SkToBool(desc.fFlags & kRenderTarget_GrSurfaceFlag);
     if (isRenderTarget) {
@@ -53,8 +58,12 @@
                               int colorSamplesPerPixel,
                               GrMipMapped mipMapped,
                               bool useNextPow2) {
-    width = useNextPow2 ? GrNextPow2(width) : width;
-    height = useNextPow2 ? GrNextPow2(height) : height;
+    width = useNextPow2
+            ? SkTMax(GrResourceProvider::kMinScratchTextureSize, GrNextPow2(width))
+            : width;
+    height = useNextPow2
+            ? SkTMax(GrResourceProvider::kMinScratchTextureSize, GrNextPow2(height))
+            : height;
 
     SkASSERT(kUnknown_GrPixelConfig != config);
     size_t colorSize = (size_t)width * height * GrBytesPerPixel(config);
diff --git a/src/gpu/GrSurfaceProxyPriv.h b/src/gpu/GrSurfaceProxyPriv.h
index f88c5cd..1f953f4 100644
--- a/src/gpu/GrSurfaceProxyPriv.h
+++ b/src/gpu/GrSurfaceProxyPriv.h
@@ -10,6 +10,8 @@
 
 #include "GrSurfaceProxy.h"
 
+#include "GrResourceProvider.h"
+
 /** Class that adds methods to GrSurfaceProxy that are only intended for use internal to Skia.
     This class is purely a privileged window into GrSurfaceProxy. It should never have additional
     data members or virtual methods. */
@@ -56,6 +58,10 @@
     // Assign this proxy the provided GrSurface as its backing surface
     void assign(sk_sp<GrSurface> surface) { fProxy->assign(std::move(surface)); }
 
+    bool requiresNoPendingIO() const {
+        return fProxy->fFlags & GrResourceProvider::kNoPendingIO_Flag;
+    }
+
     // Don't abuse this call!!!!!!!
     bool isExact() const { return SkBackingFit::kExact == fProxy->fFit; }
 
diff --git a/src/gpu/GrTextureOpList.cpp b/src/gpu/GrTextureOpList.cpp
index 9a3d15b..5c4fe3c 100644
--- a/src/gpu/GrTextureOpList.cpp
+++ b/src/gpu/GrTextureOpList.cpp
@@ -124,14 +124,18 @@
 }
 
 void GrTextureOpList::gatherProxyIntervals(GrResourceAllocator* alloc) const {
-    if (!fRecordedOps.count()) {
-        return;
-    }
-
     unsigned int cur = alloc->numOps();
 
     // Add the interval for all the writes to this opList's target
-    alloc->addInterval(fTarget.get(), cur, cur+fRecordedOps.count()-1);
+    if (fRecordedOps.count()) {
+        alloc->addInterval(fTarget.get(), cur, cur+fRecordedOps.count()-1);
+    } else {
+        // This can happen if there is a loadOp (e.g., a clear) but no other draws. In this case we
+        // still need to add an interval for the destination so we create a fake op# for
+        // the missing clear op.
+        alloc->addInterval(fTarget.get());
+        alloc->incOps();
+    }
 
     auto gather = [ alloc ] (GrSurfaceProxy* p) {
         alloc->addInterval(p);
@@ -140,9 +144,11 @@
         const GrOp* op = fRecordedOps[i].get(); // only diff from the GrRenderTargetOpList version
         if (op) {
             op->visitProxies(gather);
-
-            alloc->incOps();
         }
+
+        // Even though the op may have been moved we still need to increment the op count to
+        // keep all the math consistent.
+        alloc->incOps();
     }
 }