Reintroduce deinstantiate lazy proxy types and use for promise images.

This reverts a fraction of b2c5dae65df952999f0a12b9df80bc1433ffa19a to
restore the deinstantiate lazy proxy type, supporting implementation,
and tests.

Use them for promise images to avoid thread safety issues for promise
image resources. Makes promise image instantiation callbacks do a thread
safe unref of their fulfilled GrTexture in GrResourceCache. The
GrResourceCache mechanism for receiving unref messages is extended to
allow multiple pending unrefs. All this is new.


Bug: skia:8800
Change-Id: I7b1d4fea13c053b6fbbd39c0c6eaf567b8bf81f1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/199002
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 8c98928..942c400 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -88,6 +88,8 @@
   "$_src/gpu/GrDefaultGeoProcFactory.h",
   "$_src/gpu/GrDeferredProxyUploader.h",
   "$_src/gpu/GrDeferredUpload.h",
+  "$_src/gpu/GrDeinstantiateProxyTracker.cpp",
+  "$_src/gpu/GrDeinstantiateProxyTracker.h",
   "$_src/gpu/GrDistanceFieldGenFromVector.cpp",
   "$_src/gpu/GrDistanceFieldGenFromVector.h",
   "$_src/gpu/GrDrawingManager.cpp",
diff --git a/include/gpu/GrTexture.h b/include/gpu/GrTexture.h
index 4a7935f..e20ebb0 100644
--- a/include/gpu/GrTexture.h
+++ b/include/gpu/GrTexture.h
@@ -79,7 +79,6 @@
     }
 
 private:
-
     void computeScratchKey(GrScratchKey*) const override;
     size_t onGpuMemorySize() const override;
     void markMipMapsDirty();
diff --git a/include/private/GrSurfaceProxy.h b/include/private/GrSurfaceProxy.h
index 7c572bb..c9eec6c 100644
--- a/include/private/GrSurfaceProxy.h
+++ b/include/private/GrSurfaceProxy.h
@@ -208,6 +208,8 @@
     enum class LazyInstantiationType {
         kSingleUse,      // Instantiation callback is allowed to be called only once.
         kMultipleUse,    // Instantiation callback can be called multiple times.
+        kDeinstantiate,  // Instantiation callback can be called multiple times,
+                         // but we will deinstantiate the proxy after every flush.
     };
 
     enum class LazyState {
diff --git a/src/gpu/GrBackendTextureImageGenerator.cpp b/src/gpu/GrBackendTextureImageGenerator.cpp
index e184e8e..1bba019 100644
--- a/src/gpu/GrBackendTextureImageGenerator.cpp
+++ b/src/gpu/GrBackendTextureImageGenerator.cpp
@@ -42,7 +42,7 @@
     // Attach our texture to this context's resource cache. This ensures that deletion will happen
     // in the correct thread/context. This adds the only ref to the texture that will persist from
     // this point. That ref will be released when the generator's RefHelper is freed.
-    context->priv().getResourceCache()->insertCrossContextGpuResource(texture.get());
+    context->priv().getResourceCache()->insertDelayedResourceUnref(texture.get());
 
     GrBackendTexture backendTexture = texture->getBackendTexture();
     GrBackendFormat backendFormat = backendTexture.getBackendFormat();
diff --git a/src/gpu/GrDeinstantiateProxyTracker.cpp b/src/gpu/GrDeinstantiateProxyTracker.cpp
new file mode 100644
index 0000000..9870617
--- /dev/null
+++ b/src/gpu/GrDeinstantiateProxyTracker.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrDeinstantiateProxyTracker.h"
+
+#include "GrSurfaceProxy.h"
+#include "GrSurfaceProxyPriv.h"
+
+void GrDeinstantiateProxyTracker::addProxy(GrSurfaceProxy* proxy) {
+#ifdef SK_DEBUG
+    using LazyType = GrSurfaceProxy::LazyInstantiationType;
+    SkASSERT(LazyType::kDeinstantiate == proxy->priv().lazyInstantiationType());
+    for (int i = 0; i < fProxies.count(); ++i) {
+        SkASSERT(proxy != fProxies[i].get());
+    }
+#endif
+    fProxies.push_back(sk_ref_sp(proxy));
+}
+
+void GrDeinstantiateProxyTracker::deinstantiateAllProxies() {
+    for (int i = 0; i < fProxies.count(); ++i) {
+        GrSurfaceProxy* proxy = fProxies[i].get();
+        SkASSERT(proxy->priv().isSafeToDeinstantiate());
+        proxy->deinstantiate();
+    }
+
+    fProxies.reset();
+}
diff --git a/src/gpu/GrDeinstantiateProxyTracker.h b/src/gpu/GrDeinstantiateProxyTracker.h
new file mode 100644
index 0000000..2555ab1
--- /dev/null
+++ b/src/gpu/GrDeinstantiateProxyTracker.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrDeinstantiateProxyTracker_DEFINED
+#define GrDeinstantiateProxyTracker_DEFINED
+
+#include "GrSurfaceProxy.h"
+#include "SkTArray.h"
+
+class GrDeinstantiateProxyTracker {
+public:
+    GrDeinstantiateProxyTracker() {}
+
+    // Adds a proxy which will be deinstantiated at the end of flush. The same proxy may not be
+    // added multiple times.
+    void addProxy(GrSurfaceProxy* proxy);
+
+    // Loops through all tracked proxies and deinstantiates them.
+    void deinstantiateAllProxies();
+
+private:
+    SkTArray<sk_sp<GrSurfaceProxy>> fProxies;
+};
+
+#endif
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index b6d45ff..10da8f9 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -285,7 +285,7 @@
     bool flushed = false;
 
     {
-        GrResourceAllocator alloc(resourceProvider);
+        GrResourceAllocator alloc(resourceProvider, flushState.deinstantiateProxyTracker());
         for (int i = 0; i < fDAG.numOpLists(); ++i) {
             if (fDAG.opList(i)) {
                 fDAG.opList(i)->gatherProxyIntervals(&alloc);
@@ -336,13 +336,20 @@
     GrSemaphoresSubmitted result = gpu->finishFlush(proxy, access, flags, numSemaphores,
                                                     backendSemaphores);
 
+    flushState.deinstantiateProxyTracker()->deinstantiateAllProxies();
+
     // Give the cache a chance to purge resources that become purgeable due to flushing.
     if (flushed) {
         resourceCache->purgeAsNeeded();
+        flushed = false;
     }
     for (GrOnFlushCallbackObject* onFlushCBObject : fOnFlushCBObjects) {
         onFlushCBObject->postFlush(fTokenTracker.nextTokenToFlush(), fFlushingOpListIDs.begin(),
                                    fFlushingOpListIDs.count());
+        flushed = true;
+    }
+    if (flushed) {
+        resourceCache->purgeAsNeeded();
     }
     fFlushingOpListIDs.reset();
     fFlushing = false;
diff --git a/src/gpu/GrOpFlushState.h b/src/gpu/GrOpFlushState.h
index bc10920..07983d4 100644
--- a/src/gpu/GrOpFlushState.h
+++ b/src/gpu/GrOpFlushState.h
@@ -12,6 +12,7 @@
 #include "GrAppliedClip.h"
 #include "GrBufferAllocPool.h"
 #include "GrDeferredUpload.h"
+#include "GrDeinstantiateProxyTracker.h"
 #include "GrRenderTargetProxy.h"
 #include "SkArenaAlloc.h"
 #include "SkArenaAllocList.h"
@@ -108,6 +109,8 @@
     // permissible).
     GrAtlasManager* atlasManager() const final;
 
+    GrDeinstantiateProxyTracker* deinstantiateProxyTracker() { return &fDeinstantiateProxyTracker; }
+
 private:
     /** GrMeshDrawOp::Target override. */
     SkArenaAlloc* allocator() override { return &fArena; }
@@ -161,6 +164,9 @@
     // Variables that are used to track where we are in lists as ops are executed
     SkArenaAllocList<Draw>::Iter fCurrDraw;
     SkArenaAllocList<InlineUpload>::Iter fCurrUpload;
+
+    // Used to track the proxies that need to be deinstantiated after we finish a flush
+    GrDeinstantiateProxyTracker fDeinstantiateProxyTracker;
 };
 
 #endif
diff --git a/src/gpu/GrResourceAllocator.cpp b/src/gpu/GrResourceAllocator.cpp
index 81c2709..0d3c34e 100644
--- a/src/gpu/GrResourceAllocator.cpp
+++ b/src/gpu/GrResourceAllocator.cpp
@@ -7,6 +7,7 @@
 
 #include "GrResourceAllocator.h"
 
+#include "GrDeinstantiateProxyTracker.h"
 #include "GrGpuResourcePriv.h"
 #include "GrOpList.h"
 #include "GrRenderTargetProxy.h"
@@ -106,7 +107,12 @@
     if (proxy->readOnly() || !fResourceProvider->explicitlyAllocateGPUResources()) {
         // FIXME: remove this once we can do the lazy instantiation from assign instead.
         if (GrSurfaceProxy::LazyState::kNot != proxy->lazyInstantiationState()) {
-            proxy->priv().doLazyInstantiation(fResourceProvider);
+            if (proxy->priv().doLazyInstantiation(fResourceProvider)) {
+                if (proxy->priv().lazyInstantiationType() ==
+                    GrSurfaceProxy::LazyInstantiationType::kDeinstantiate) {
+                    fDeinstantiateTracker->addProxy(proxy);
+                }
+            }
         }
     }
 }
@@ -369,6 +375,11 @@
         if (GrSurfaceProxy::LazyState::kNot != cur->proxy()->lazyInstantiationState()) {
             if (!cur->proxy()->priv().doLazyInstantiation(fResourceProvider)) {
                 *outError = AssignError::kFailedProxyInstantiation;
+            } else {
+                if (GrSurfaceProxy::LazyInstantiationType::kDeinstantiate ==
+                    cur->proxy()->priv().lazyInstantiationType()) {
+                    fDeinstantiateTracker->addProxy(cur->proxy());
+                }
             }
         } else if (sk_sp<GrSurface> surface = this->findSurfaceFor(cur->proxy(), needsStencil)) {
             // TODO: make getUniqueKey virtual on GrSurfaceProxy
diff --git a/src/gpu/GrResourceAllocator.h b/src/gpu/GrResourceAllocator.h
index cb366e6..ea1250f 100644
--- a/src/gpu/GrResourceAllocator.h
+++ b/src/gpu/GrResourceAllocator.h
@@ -16,6 +16,7 @@
 #include "SkTDynamicHash.h"
 #include "SkTMultiMap.h"
 
+class GrDeinstantiateProxyTracker;
 class GrResourceProvider;
 
 // Print out explicit allocation information
@@ -41,8 +42,8 @@
  */
 class GrResourceAllocator {
 public:
-    GrResourceAllocator(GrResourceProvider* resourceProvider)
-            : fResourceProvider(resourceProvider) {}
+    GrResourceAllocator(GrResourceProvider* resourceProvider, GrDeinstantiateProxyTracker* tracker)
+            : fResourceProvider(resourceProvider), fDeinstantiateTracker(tracker) {}
 
     ~GrResourceAllocator();
 
@@ -211,6 +212,7 @@
     static const int kInitialArenaSize = 128 * sizeof(Interval);
 
     GrResourceProvider*          fResourceProvider;
+    GrDeinstantiateProxyTracker* fDeinstantiateTracker;
     FreePoolMultiMap             fFreePool;          // Recently created/used GrSurfaces
     IntvlHash                    fIntvlHash;         // All the intervals, hashed by proxyID
 
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 9b17d92..c36634c 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -14,6 +14,7 @@
 #include "GrTexture.h"
 #include "GrTextureProxyCacheAccess.h"
 #include "GrTracing.h"
+#include "SkExchange.h"
 #include "SkGr.h"
 #include "SkMessageBus.h"
 #include "SkOpts.h"
@@ -67,7 +68,44 @@
     GrResourceCache* fCache;
 };
 
- //////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+
+inline GrResourceCache::ResourceAwaitingUnref::ResourceAwaitingUnref() = default;
+
+inline GrResourceCache::ResourceAwaitingUnref::ResourceAwaitingUnref(GrGpuResource* resource)
+        : fResource(resource), fNumUnrefs(1) {}
+
+inline GrResourceCache::ResourceAwaitingUnref::ResourceAwaitingUnref(ResourceAwaitingUnref&& that) {
+    fResource = skstd::exchange(that.fResource, nullptr);
+    fNumUnrefs = skstd::exchange(that.fNumUnrefs, 0);
+}
+
+inline GrResourceCache::ResourceAwaitingUnref& GrResourceCache::ResourceAwaitingUnref::operator=(
+        ResourceAwaitingUnref&& that) {
+    fResource = skstd::exchange(that.fResource, nullptr);
+    fNumUnrefs = skstd::exchange(that.fNumUnrefs, 0);
+    return *this;
+}
+
+inline GrResourceCache::ResourceAwaitingUnref::~ResourceAwaitingUnref() {
+    if (fResource) {
+        for (int i = 0; i < fNumUnrefs; ++i) {
+            fResource->unref();
+        }
+    }
+}
+
+inline void GrResourceCache::ResourceAwaitingUnref::addRef() { ++fNumUnrefs; }
+
+inline void GrResourceCache::ResourceAwaitingUnref::unref() {
+    SkASSERT(fNumUnrefs > 0);
+    fResource->unref();
+    --fNumUnrefs;
+}
+
+inline bool GrResourceCache::ResourceAwaitingUnref::finished() { return !fNumUnrefs; }
+
+//////////////////////////////////////////////////////////////////////////////
 
 GrResourceCache::GrResourceCache(const GrCaps* caps, GrSingleOwner* singleOwner,
                                  uint32_t contextUniqueID)
@@ -179,10 +217,9 @@
 void GrResourceCache::abandonAll() {
     AutoValidate av(this);
 
-    for (int i = 0; i < fResourcesWaitingForFreeMsg.count(); ++i) {
-        fResourcesWaitingForFreeMsg[i]->cacheAccess().abandon();
-    }
-    fResourcesWaitingForFreeMsg.reset();
+    // We need to make sure to free any resources that were waiting on a free message but never
+    // received one.
+    fResourcesAwaitingUnref.reset();
 
     while (fNonpurgeableResources.count()) {
         GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
@@ -204,7 +241,7 @@
     SkASSERT(!fBudgetedCount);
     SkASSERT(!fBudgetedBytes);
     SkASSERT(!fPurgeableBytes);
-    SkASSERT(!fResourcesWaitingForFreeMsg.count());
+    SkASSERT(!fResourcesAwaitingUnref.count());
 }
 
 void GrResourceCache::releaseAll() {
@@ -214,10 +251,7 @@
 
     // We need to make sure to free any resources that were waiting on a free message but never
     // received one.
-    for (int i = 0; i < fResourcesWaitingForFreeMsg.count(); ++i) {
-        fResourcesWaitingForFreeMsg[i]->unref();
-    }
-    fResourcesWaitingForFreeMsg.reset();
+    fResourcesAwaitingUnref.reset();
 
     SkASSERT(fProxyProvider); // better have called setProxyProvider
     // We must remove the uniqueKeys from the proxies here. While they possess a uniqueKey
@@ -244,7 +278,7 @@
     SkASSERT(!fBudgetedCount);
     SkASSERT(!fBudgetedBytes);
     SkASSERT(!fPurgeableBytes);
-    SkASSERT(!fResourcesWaitingForFreeMsg.count());
+    SkASSERT(!fResourcesAwaitingUnref.count());
 }
 
 class GrResourceCache::AvailableForScratchUse {
@@ -605,10 +639,14 @@
     }
 }
 
-void GrResourceCache::insertCrossContextGpuResource(GrGpuResource* resource) {
+void GrResourceCache::insertDelayedResourceUnref(GrGpuResource* resource) {
     resource->ref();
-    SkASSERT(!fResourcesWaitingForFreeMsg.contains(resource));
-    fResourcesWaitingForFreeMsg.push_back(resource);
+    uint32_t id = resource->uniqueID().asUInt();
+    if (auto* data = fResourcesAwaitingUnref.find(id)) {
+        data->addRef();
+    } else {
+        fResourcesAwaitingUnref.set(id, {resource});
+    }
 }
 
 void GrResourceCache::processFreedGpuResources() {
@@ -616,14 +654,17 @@
     fFreedGpuResourceInbox.poll(&msgs);
     for (int i = 0; i < msgs.count(); ++i) {
         SkASSERT(msgs[i].fOwningUniqueID == fContextUniqueID);
-        int index = fResourcesWaitingForFreeMsg.find(msgs[i].fResource);
+        uint32_t id = msgs[i].fResource->uniqueID().asUInt();
+        ResourceAwaitingUnref* info = fResourcesAwaitingUnref.find(id);
         // If we called release or abandon on the GrContext we will have already released our ref on
         // the GrGpuResource. If then the message arrives before the actual GrContext gets destroyed
         // we will try to process the message when we destroy the GrContext. This protects us from
         // trying to unref the resource twice.
-        if (index != -1) {
-            fResourcesWaitingForFreeMsg.removeShuffle(index);
-            msgs[i].fResource->unref();
+        if (info) {
+            info->unref();
+            if (info->finished()) {
+                fResourcesAwaitingUnref.remove(id);
+            }
         }
     }
 }
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index c948864..84378e6 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -16,6 +16,7 @@
 #include "SkRefCnt.h"
 #include "SkTArray.h"
 #include "SkTDPQueue.h"
+#include "SkTHash.h"
 #include "SkTInternalLList.h"
 #include "SkTMultiMap.h"
 
@@ -195,7 +196,7 @@
     bool requestsFlush() const { return this->overBudget() && !fPurgeableQueue.count(); }
 
     /** Maintain a ref to this resource until we receive a GrGpuResourceFreedMessage. */
-    void insertCrossContextGpuResource(GrGpuResource* resource);
+    void insertDelayedResourceUnref(GrGpuResource* resource);
 
 #if GR_CACHE_STATS
     struct Stats {
@@ -305,6 +306,25 @@
     };
     typedef SkTDynamicHash<GrGpuResource, GrUniqueKey, UniqueHashTraits> UniqueHash;
 
+    class ResourceAwaitingUnref {
+    public:
+        ResourceAwaitingUnref();
+        ResourceAwaitingUnref(GrGpuResource* resource);
+        ResourceAwaitingUnref(const ResourceAwaitingUnref&) = delete;
+        ResourceAwaitingUnref& operator=(const ResourceAwaitingUnref&) = delete;
+        ResourceAwaitingUnref(ResourceAwaitingUnref&&);
+        ResourceAwaitingUnref& operator=(ResourceAwaitingUnref&&);
+        ~ResourceAwaitingUnref();
+        void addRef();
+        void unref();
+        bool finished();
+
+    private:
+        GrGpuResource* fResource = nullptr;
+        int fNumUnrefs = 0;
+    };
+    using ReourcesAwaitingUnref = SkTHashMap<uint32_t, ResourceAwaitingUnref>;
+
     static bool CompareTimestamp(GrGpuResource* const& a, GrGpuResource* const& b) {
         return a->cacheAccess().timestamp() < b->cacheAccess().timestamp();
     }
@@ -353,8 +373,7 @@
 
     InvalidUniqueKeyInbox               fInvalidUniqueKeyInbox;
     FreedGpuResourceInbox               fFreedGpuResourceInbox;
-
-    SkTDArray<GrGpuResource*>           fResourcesWaitingForFreeMsg;
+    ReourcesAwaitingUnref               fResourcesAwaitingUnref;
 
     uint32_t                            fContextUniqueID;
     GrSingleOwner*                      fSingleOwner;
diff --git a/src/gpu/GrSurfaceProxyPriv.h b/src/gpu/GrSurfaceProxyPriv.h
index 8fe7f83..3b383a5 100644
--- a/src/gpu/GrSurfaceProxyPriv.h
+++ b/src/gpu/GrSurfaceProxyPriv.h
@@ -60,6 +60,11 @@
         return fProxy->fLazyInstantiationType;
     }
 
+    bool isSafeToDeinstantiate() const {
+        return SkToBool(fProxy->fTarget) && SkToBool(fProxy->fLazyInstantiateCallback) &&
+               GrSurfaceProxy::LazyInstantiationType::kDeinstantiate == lazyInstantiationType();
+    }
+
     static bool SK_WARN_UNUSED_RESULT AttachStencilIfNeeded(GrResourceProvider*, GrSurface*,
                                                             bool needsStencil);
 
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index ec2e651..908be47 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -445,12 +445,23 @@
 
         ~PromiseLazyInstantiateCallback() {
             if (fIdleCallback) {
+                SkASSERT(!fTexture);
                 // We were never fulfilled. Pass false so done proc is still called.
                 fIdleCallback->abandon();
             }
+            // Our destructor can run on any thread. We trigger the unref of fTexture by message.
+            if (fTexture) {
+                SkASSERT(!fIdleCallback);
+                SkMessageBus<GrGpuResourceFreedMessage>::Post({fTexture, fTextureContextID});
+            }
         }
 
         sk_sp<GrSurface> operator()(GrResourceProvider* resourceProvider) {
+            // Our proxy is getting instantiated for the second+ time. We are only allowed to call
+            // Fulfill once. So return our cached result.
+            if (fTexture) {
+                return sk_ref_sp(fTexture);
+            }
             SkASSERT(fIdleCallback);
             PromiseImageTextureContext textureContext = fIdleCallback->context();
             sk_sp<SkPromiseImageTexture> promiseTexture = fFulfillProc(textureContext);
@@ -495,10 +506,20 @@
             }
             tex->addIdleProc(std::move(fIdleCallback));
             promiseTexture->addKeyToInvalidate(tex->getContext()->priv().contextID(), key);
+            fTexture = tex.get();
+            // We need to hold on to the GrTexture in case our proxy gets reinstantiated. However,
+            // we can't unref in our destructor because we may be on another thread then. So we
+            // let the cache know it is waiting on an unref message. We will send that message from
+            // our destructor.
+            GrContext* context = fTexture->getContext();
+            context->priv().getResourceCache()->insertDelayedResourceUnref(fTexture);
+            fTextureContextID = context->priv().contextID();
             return std::move(tex);
         }
 
     private:
+        GrTexture* fTexture = nullptr;
+        uint32_t fTextureContextID = SK_InvalidUniqueID;
         sk_sp<GrRefCntedCallback> fIdleCallback;
         PromiseImageTextureFulfillProc fFulfillProc;
         GrPixelConfig fConfig;
@@ -515,5 +536,5 @@
     return proxyProvider->createLazyProxy(std::move(callback), backendFormat, desc, origin,
                                           mipMapped, GrInternalSurfaceFlags::kReadOnly,
                                           SkBackingFit::kExact, SkBudgeted::kNo,
-                                          GrSurfaceProxy::LazyInstantiationType::kSingleUse);
+                                          GrSurfaceProxy::LazyInstantiationType::kDeinstantiate);
 }
diff --git a/tests/GrSurfaceTest.cpp b/tests/GrSurfaceTest.cpp
index ce12da6..375d34c 100644
--- a/tests/GrSurfaceTest.cpp
+++ b/tests/GrSurfaceTest.cpp
@@ -488,6 +488,28 @@
                 // Now that the draw is fully consumed by the GPU, the texture should be idle.
                 REPORTER_ASSERT(reporter, idleIDs.find(2) != idleIDs.end());
 
+                // Make a proxy that should deinstantiate even if we keep a ref on it.
+                auto deinstantiateLazyCB = [&make, &context](GrResourceProvider* rp) {
+                    return make(context, 3);
+                };
+                proxy = context->priv().proxyProvider()->createLazyProxy(
+                        deinstantiateLazyCB, backendFormat, desc,
+                        GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
+                        GrInternalSurfaceFlags ::kNone, SkBackingFit::kExact, budgeted,
+                        GrSurfaceProxy::LazyInstantiationType::kDeinstantiate);
+                rtc->drawTexture(GrNoClip(), std::move(proxy), GrSamplerState::Filter::kNearest,
+                                 SkBlendMode::kSrcOver, SkPMColor4f(), SkRect::MakeWH(w, h),
+                                 SkRect::MakeWH(w, h), GrAA::kNo, GrQuadAAFlags::kNone,
+                                 SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(), nullptr);
+                // At this point the proxy shouldn't even be instantiated, there is no texture with
+                // id 3.
+                REPORTER_ASSERT(reporter, idleIDs.find(3) == idleIDs.end());
+                context->flush();
+                context->priv().getGpu()->testingOnly_flushGpuAndSync();
+                // Now that the draw is fully consumed, we should have deinstantiated the proxy and
+                // the texture it made should be idle.
+                REPORTER_ASSERT(reporter, idleIDs.find(3) != idleIDs.end());
+
                 // Make sure we make the call during various shutdown scenarios where the texture
                 // might persist after context is destroyed, abandoned, etc. We test three
                 // variations of each scenario. One where the texture is just created. Another,
@@ -501,7 +523,7 @@
                 if (api == GrBackendApi::kVulkan) {
                     continue;
                 }
-                int id = 3;
+                int id = 4;
                 enum class DrawType {
                     kNoDraw,
                     kDraw,
diff --git a/tests/LazyProxyTest.cpp b/tests/LazyProxyTest.cpp
index f3b90e9..91f7335 100644
--- a/tests/LazyProxyTest.cpp
+++ b/tests/LazyProxyTest.cpp
@@ -249,8 +249,9 @@
 
     using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType;
     for (bool doInstantiate : {true, false}) {
-        for (auto lazyType :
-             {LazyInstantiationType::kSingleUse, LazyInstantiationType::kMultipleUse}) {
+        for (auto lazyType : {LazyInstantiationType::kSingleUse,
+                              LazyInstantiationType::kMultipleUse,
+                              LazyInstantiationType::kDeinstantiate}) {
             int testCount = 0;
             // Sets an integer to 1 when the callback is called and -1 when it is deleted.
             class TestCallback {
@@ -454,7 +455,7 @@
                 ctx->priv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
 
     using LazyType = GrSurfaceProxy::LazyInstantiationType;
-    for (auto lazyType : {LazyType::kSingleUse, LazyType::kMultipleUse}) {
+    for (auto lazyType : {LazyType::kSingleUse, LazyType::kMultipleUse, LazyType::kDeinstantiate}) {
         sk_sp<GrRenderTargetContext> rtc = ctx->priv().makeDeferredRenderTargetContext(
                 format, SkBackingFit::kExact, 100, 100,
                 kRGBA_8888_GrPixelConfig, nullptr);
@@ -497,7 +498,11 @@
         ctx->flush();
 
         REPORTER_ASSERT(reporter, 1 == instantiateTestValue);
-        REPORTER_ASSERT(reporter, 0 == releaseTestValue);
+        if (LazyType::kDeinstantiate == lazyType) {
+            REPORTER_ASSERT(reporter, 1 == releaseTestValue);
+        } else {
+            REPORTER_ASSERT(reporter, 0 == releaseTestValue);
+        }
 
         // This should cause the uninstantiate proxies to be instantiated again but have no effect
         // on the others
@@ -506,11 +511,20 @@
         rtc->priv().testingOnly_addDrawOp(LazyDeinstantiateTestOp::Make(ctx.get(), lazyProxy));
         ctx->flush();
 
-        REPORTER_ASSERT(reporter, 1 == instantiateTestValue);
-        REPORTER_ASSERT(reporter, 0 == releaseTestValue);
+        if (LazyType::kDeinstantiate == lazyType) {
+            REPORTER_ASSERT(reporter, 2 == instantiateTestValue);
+            REPORTER_ASSERT(reporter, 2 == releaseTestValue);
+        } else {
+            REPORTER_ASSERT(reporter, 1 == instantiateTestValue);
+            REPORTER_ASSERT(reporter, 0 == releaseTestValue);
+        }
 
         lazyProxy.reset();
-        REPORTER_ASSERT(reporter, 1 == releaseTestValue);
+        if (LazyType::kDeinstantiate == lazyType) {
+            REPORTER_ASSERT(reporter, 2 == releaseTestValue);
+        } else {
+            REPORTER_ASSERT(reporter, 1 == releaseTestValue);
+        }
 
         gpu->deleteTestingOnlyBackendTexture(backendTex);
     }
diff --git a/tests/PromiseImageTest.cpp b/tests/PromiseImageTest.cpp
index 93a09ca..789cb24 100644
--- a/tests/PromiseImageTest.cpp
+++ b/tests/PromiseImageTest.cpp
@@ -331,6 +331,13 @@
         auto surf = ctx->priv().resourceProvider()->findByUniqueKey<GrSurface>(key);
         REPORTER_ASSERT(reporter, !surf);
     }
+
+    // Must do this to ensure all callbacks occur before the PromiseImageChecker goes out of scope.
+    alphaImg.reset();
+    grayImg.reset();
+    ctx->flush();
+    ctx->priv().getGpu()->testingOnly_flushGpuAndSync();
+
     gpu->deleteTestingOnlyBackendTexture(backendTex1);
 }
 
@@ -461,7 +468,10 @@
     surface->flush();
     canvas->drawImage(image, 5, 0);
     surface->flush();
-    // Must call this to ensure that all callbacks are performed before the checker is destroyed.
+    // Must call these to ensure that all callbacks are performed before the checker is destroyed.
+    image.reset();
+    ctx->flush();
     gpu->testingOnly_flushGpuAndSync();
+
     gpu->deleteTestingOnlyBackendTexture(backendTex);
 }
diff --git a/tests/ResourceAllocatorTest.cpp b/tests/ResourceAllocatorTest.cpp
index 69118fc..a71221d 100644
--- a/tests/ResourceAllocatorTest.cpp
+++ b/tests/ResourceAllocatorTest.cpp
@@ -10,6 +10,7 @@
 #include "Test.h"
 
 #include "GrContextPriv.h"
+#include "GrDeinstantiateProxyTracker.h"
 #include "GrGpu.h"
 #include "GrProxyProvider.h"
 #include "GrResourceAllocator.h"
@@ -89,7 +90,8 @@
 // assigned different GrSurfaces.
 static void overlap_test(skiatest::Reporter* reporter, GrResourceProvider* resourceProvider,
                          GrSurfaceProxy* p1, GrSurfaceProxy* p2, bool expectedResult) {
-    GrResourceAllocator alloc(resourceProvider);
+    GrDeinstantiateProxyTracker deinstantiateTracker;
+    GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker);
 
     alloc.addInterval(p1, 0, 4);
     alloc.addInterval(p2, 1, 2);
@@ -111,7 +113,8 @@
 static void non_overlap_test(skiatest::Reporter* reporter, GrResourceProvider* resourceProvider,
                              GrSurfaceProxy* p1, GrSurfaceProxy* p2,
                              bool expectedResult) {
-    GrResourceAllocator alloc(resourceProvider);
+    GrDeinstantiateProxyTracker deinstantiateTracker;
+    GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker);
 
     alloc.addInterval(p1, 0, 2);
     alloc.addInterval(p2, 3, 5);
@@ -285,7 +288,7 @@
 }
 
 sk_sp<GrSurfaceProxy> make_lazy(GrProxyProvider* proxyProvider, const GrCaps* caps,
-                                const ProxyParams& p) {
+                                const ProxyParams& p, bool deinstantiate) {
     GrColorType grCT = SkColorTypeToGrColorType(p.fColorType);
     GrPixelConfig config = GrColorTypeToPixelConfig(grCT, GrSRGBEncoded::kNo);
 
@@ -308,7 +311,8 @@
         }
     };
     const GrBackendFormat format = caps->getBackendFormatFromColorType(p.fColorType);
-    auto lazyType = GrSurfaceProxy::LazyInstantiationType ::kSingleUse;
+    auto lazyType = deinstantiate ? GrSurfaceProxy::LazyInstantiationType ::kDeinstantiate
+                                  : GrSurfaceProxy::LazyInstantiationType ::kSingleUse;
     GrInternalSurfaceFlags flags = GrInternalSurfaceFlags::kNone;
     return proxyProvider->createLazyProxy(callback, format, desc, p.fOrigin, GrMipMapped::kNo,
                                           flags, p.fFit, SkBudgeted::kNo, lazyType);
@@ -330,20 +334,28 @@
         rtParams.fIsRT = true;
         auto proxyProvider = context->priv().proxyProvider();
         auto caps = context->priv().caps();
-        auto p0 = make_lazy(proxyProvider, caps, texParams);
+        auto p0 = make_lazy(proxyProvider, caps, texParams, true);
+        auto p1 = make_lazy(proxyProvider, caps, texParams, false);
         texParams.fFit = rtParams.fFit = SkBackingFit::kApprox;
-        auto p1 = make_lazy(proxyProvider, caps, rtParams);
+        auto p2 = make_lazy(proxyProvider, caps, rtParams, true);
+        auto p3 = make_lazy(proxyProvider, caps, rtParams, false);
 
+        GrDeinstantiateProxyTracker deinstantiateTracker;
         {
-            GrResourceAllocator alloc(resourceProvider);
+            GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker);
             alloc.addInterval(p0.get(), 0, 1);
             alloc.addInterval(p1.get(), 0, 1);
+            alloc.addInterval(p2.get(), 0, 1);
+            alloc.addInterval(p3.get(), 0, 1);
             alloc.markEndOfOpList(0);
             int startIndex, stopIndex;
             GrResourceAllocator::AssignError error;
             alloc.assign(&startIndex, &stopIndex, &error);
         }
-        REPORTER_ASSERT(reporter, p0->isInstantiated());
+        deinstantiateTracker.deinstantiateAllProxies();
+        REPORTER_ASSERT(reporter, !p0->isInstantiated());
         REPORTER_ASSERT(reporter, p1->isInstantiated());
+        REPORTER_ASSERT(reporter, !p2->isInstantiated());
+        REPORTER_ASSERT(reporter, p3->isInstantiated());
     }
 }
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index 9bd5415..a1af2b3 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -1545,18 +1545,18 @@
     GrGpu* gpu = context->priv().getGpu();
 
     TestResource* wrapped1 = TestResource::CreateWrapped(gpu, GrWrapCacheable::kYes);
-    cache->insertCrossContextGpuResource(wrapped1);
+    cache->insertDelayedResourceUnref(wrapped1);
 
     REPORTER_ASSERT(reporter, 1 == TestResource::NumAlive());
 
     TestResource* wrapped2 = TestResource::CreateWrapped(gpu, GrWrapCacheable::kYes);
-    cache->insertCrossContextGpuResource(wrapped2);
+    cache->insertDelayedResourceUnref(wrapped2);
 
     // An uncacheable cross-context should not be purged as soon as we drop our ref. This
     // is because inserting it as a cross-context resource actually holds a ref until the
     // message is received.
     TestResource* wrapped3 = TestResource::CreateWrapped(gpu, GrWrapCacheable::kNo);
-    cache->insertCrossContextGpuResource(wrapped3);
+    cache->insertDelayedResourceUnref(wrapped3);
 
     REPORTER_ASSERT(reporter, 3 == TestResource::NumAlive());