Revert "Revert "Reuse GrTexture instances when the same GrBackendTexture is used to""

This reverts commit 426ba463d54c7fdd6f3b94f88b66c10c0212dafd.

Bug: skia:8613
Change-Id: Iacaf40549369110aa95015e8d4579ec41db22d13
Reviewed-on: https://skia-review.googlesource.com/c/182963
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/core/SkDeferredDisplayListRecorder.cpp b/src/core/SkDeferredDisplayListRecorder.cpp
index da02727..716d241 100644
--- a/src/core/SkDeferredDisplayListRecorder.cpp
+++ b/src/core/SkDeferredDisplayListRecorder.cpp
@@ -6,7 +6,7 @@
  */
 
 #include "SkDeferredDisplayListRecorder.h"
-
+#include "SkMessageBus.h"
 #include "SkDeferredDisplayList.h"
 #include "SkSurface.h"
 #include "SkSurfaceCharacterization.h"
@@ -31,26 +31,58 @@
         SkColorType colorType,
         SkAlphaType alphaType,
         sk_sp<SkColorSpace> colorSpace,
-        TextureFulfillProc textureFulfillProc,
-        TextureReleaseProc textureReleaseProc,
-        PromiseDoneProc promiseDoneProc,
-        TextureContext textureContext) {
+        PromiseImageTextureFulfillProc textureFulfillProc,
+        PromiseImageTextureReleaseProc textureReleaseProc,
+        PromiseImageTextureDoneProc textureDoneProc,
+        PromiseImageTextureContext textureContext) {
+    return nullptr;
+}
+
+sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
+        const GrBackendFormat& backendFormat,
+        int width,
+        int height,
+        GrMipMapped mipMapped,
+        GrSurfaceOrigin origin,
+        SkColorType colorType,
+        SkAlphaType alphaType,
+        sk_sp<SkColorSpace> colorSpace,
+        LegacyPromiseImageTextureFulfillProc textureFulfillProc,
+        LegacyPromiseImageTextureReleaseProc textureReleaseProc,
+        PromiseImageTextureDoneProc textureDoneProc,
+        PromiseImageTextureContext textureContext) {
     return nullptr;
 }
 
 sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
-                                                        SkYUVColorSpace yuvColorSpace,
-                                                        const GrBackendFormat yuvaFormats[],
-                                                        const SkISize yuvaSizes[],
-                                                        const SkYUVAIndex yuvaIndices[4],
-                                                        int imageWidth,
-                                                        int imageHeight,
-                                                        GrSurfaceOrigin imageOrigin,
-                                                        sk_sp<SkColorSpace> imageColorSpace,
-                                                        TextureFulfillProc textureFulfillProc,
-                                                        TextureReleaseProc textureReleaseProc,
-                                                        PromiseDoneProc promiseDoneProc,
-                                                        TextureContext textureContexts[]) {
+        SkYUVColorSpace yuvColorSpace,
+        const GrBackendFormat yuvaFormats[],
+        const SkISize yuvaSizes[],
+        const SkYUVAIndex yuvaIndices[4],
+        int imageWidth,
+        int imageHeight,
+        GrSurfaceOrigin imageOrigin,
+        sk_sp<SkColorSpace> imageColorSpace,
+        PromiseImageTextureFulfillProc textureFulfillProc,
+        PromiseImageTextureReleaseProc textureReleaseProc,
+        PromiseImageTextureDoneProc textureDoneProc,
+        PromiseImageTextureContext textureContexts[]) {
+    return nullptr;
+}
+
+sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
+        SkYUVColorSpace yuvColorSpace,
+        const GrBackendFormat yuvaFormats[],
+        const SkISize yuvaSizes[],
+        const SkYUVAIndex yuvaIndices[4],
+        int imageWidth,
+        int imageHeight,
+        GrSurfaceOrigin imageOrigin,
+        sk_sp<SkColorSpace> imageColorSpace,
+        LegacyPromiseImageTextureFulfillProc textureFulfillProc,
+        LegacyPromiseImageTextureReleaseProc textureReleaseProc,
+        PromiseImageTextureDoneProc textureDoneProc,
+        PromiseImageTextureContext textureContexts[]) {
     return nullptr;
 }
 
@@ -60,10 +92,11 @@
 #include "GrProxyProvider.h"
 #include "GrRenderTargetContext.h"
 #include "GrTexture.h"
-
 #include "SkGr.h"
 #include "SkImage_Gpu.h"
 #include "SkImage_GpuYUVA.h"
+#include "SkMakeUnique.h"
+#include "SkPromiseImageTexture.h"
 #include "SkSurface_Gpu.h"
 #include "SkYUVASizeInfo.h"
 
@@ -216,10 +249,10 @@
         SkColorType colorType,
         SkAlphaType alphaType,
         sk_sp<SkColorSpace> colorSpace,
-        TextureFulfillProc textureFulfillProc,
-        TextureReleaseProc textureReleaseProc,
-        PromiseDoneProc promiseDoneProc,
-        TextureContext textureContext) {
+        PromiseImageTextureFulfillProc textureFulfillProc,
+        PromiseImageTextureReleaseProc textureReleaseProc,
+        PromiseImageTextureDoneProc textureDoneProc,
+        PromiseImageTextureContext textureContext) {
     if (!fContext) {
         return nullptr;
     }
@@ -235,10 +268,107 @@
                                            std::move(colorSpace),
                                            textureFulfillProc,
                                            textureReleaseProc,
-                                           promiseDoneProc,
+                                           textureDoneProc,
                                            textureContext);
 }
 
+// Converts from the old legacy APIs based on GrBackendTexture to the new implementation based on
+// PromiseImageTexture.
+static void wrap_legacy(
+        SkDeferredDisplayListRecorder::LegacyPromiseImageTextureFulfillProc textureFulfillProc,
+        SkDeferredDisplayListRecorder::LegacyPromiseImageTextureReleaseProc textureReleaseProc,
+        SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc textureDoneProc,
+        const SkDeferredDisplayListRecorder::PromiseImageTextureContext textureContexts[],
+        int numTextures,
+        SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc* wrappedFulfillProc,
+        SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc* wrappedReleaseProc,
+        SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc* wrappedDoneProc,
+        SkDeferredDisplayListRecorder::PromiseImageTextureContext wrappedTextureContext[]) {
+    struct WrapperContext {
+        SkDeferredDisplayListRecorder::LegacyPromiseImageTextureFulfillProc fLegacyFulfill;
+        SkDeferredDisplayListRecorder::LegacyPromiseImageTextureReleaseProc fLegacyRelease;
+        SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc fDone;
+        SkDeferredDisplayListRecorder::PromiseImageTextureContext fOriginalContext;
+        std::unique_ptr<SkPromiseImageTexture> fPromiseImageTexture;
+    };
+    *wrappedFulfillProc = [](SkDeferredDisplayListRecorder::PromiseImageTextureContext context) {
+        auto* wc = static_cast<WrapperContext*>(context);
+        GrBackendTexture backendTexture;
+        wc->fLegacyFulfill(wc->fOriginalContext, &backendTexture);
+        wc->fPromiseImageTexture = skstd::make_unique<SkPromiseImageTexture>(backendTexture);
+        return wc->fPromiseImageTexture.get();
+    };
+    *wrappedReleaseProc = [](SkDeferredDisplayListRecorder::PromiseImageTextureContext context,
+                             const SkPromiseImageTexture*) {
+        auto* wc = static_cast<WrapperContext*>(context);
+        wc->fLegacyRelease(wc->fOriginalContext);
+        wc->fPromiseImageTexture.reset();
+    };
+    *wrappedDoneProc = [](SkDeferredDisplayListRecorder::PromiseImageTextureContext context) {
+        const auto* wc = static_cast<WrapperContext*>(context);
+        wc->fDone(wc->fOriginalContext);
+        SkASSERT(!wc->fPromiseImageTexture);
+        delete wc;
+    };
+    for (int i = 0; i < numTextures; ++i) {
+        wrappedTextureContext[i] = new WrapperContext{textureFulfillProc, textureReleaseProc,
+                                                      textureDoneProc, textureContexts[i], nullptr};
+    }
+}
+
+sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
+        const GrBackendFormat& backendFormat,
+        int width,
+        int height,
+        GrMipMapped mipMapped,
+        GrSurfaceOrigin origin,
+        SkColorType colorType,
+        SkAlphaType alphaType,
+        sk_sp<SkColorSpace> colorSpace,
+        LegacyPromiseImageTextureFulfillProc textureFulfillProc,
+        LegacyPromiseImageTextureReleaseProc textureReleaseProc,
+        PromiseImageTextureDoneProc textureDoneProc,
+        PromiseImageTextureContext textureContext) {
+    if (!fContext) {
+        return nullptr;
+    }
+
+    SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc wrappedFulfillProc;
+    SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc wrappedReleaseProc;
+    SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc wrappedDoneProc;
+    SkDeferredDisplayListRecorder::PromiseImageTextureContext wrappedTextureContext;
+    if (!textureDoneProc) {
+        return nullptr;
+    }
+    if (!textureFulfillProc || !textureReleaseProc) {
+        textureDoneProc(textureContext);
+        return nullptr;
+    }
+
+    wrap_legacy(textureFulfillProc,
+                textureReleaseProc,
+                textureDoneProc,
+                &textureContext,
+                1,
+                &wrappedFulfillProc,
+                &wrappedReleaseProc,
+                &wrappedDoneProc,
+                &wrappedTextureContext);
+    return SkImage_Gpu::MakePromiseTexture(fContext.get(),
+                                           backendFormat,
+                                           width,
+                                           height,
+                                           mipMapped,
+                                           origin,
+                                           colorType,
+                                           alphaType,
+                                           std::move(colorSpace),
+                                           wrappedFulfillProc,
+                                           wrappedReleaseProc,
+                                           wrappedDoneProc,
+                                           wrappedTextureContext);
+}
+
 sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
         SkYUVColorSpace yuvColorSpace,
         const GrBackendFormat yuvaFormats[],
@@ -248,10 +378,10 @@
         int imageHeight,
         GrSurfaceOrigin imageOrigin,
         sk_sp<SkColorSpace> imageColorSpace,
-        TextureFulfillProc textureFulfillProc,
-        TextureReleaseProc textureReleaseProc,
-        PromiseDoneProc promiseDoneProc,
-        TextureContext textureContexts[]) {
+        PromiseImageTextureFulfillProc textureFulfillProc,
+        PromiseImageTextureReleaseProc textureReleaseProc,
+        PromiseImageTextureDoneProc textureDoneProc,
+        PromiseImageTextureContext textureContexts[]) {
     if (!fContext) {
         return nullptr;
     }
@@ -267,8 +397,66 @@
                                                    std::move(imageColorSpace),
                                                    textureFulfillProc,
                                                    textureReleaseProc,
-                                                   promiseDoneProc,
+                                                   textureDoneProc,
                                                    textureContexts);
 }
 
+sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
+        SkYUVColorSpace yuvColorSpace,
+        const GrBackendFormat yuvaFormats[],
+        const SkISize yuvaSizes[],
+        const SkYUVAIndex yuvaIndices[4],
+        int imageWidth,
+        int imageHeight,
+        GrSurfaceOrigin imageOrigin,
+        sk_sp<SkColorSpace> imageColorSpace,
+        LegacyPromiseImageTextureFulfillProc textureFulfillProc,
+        LegacyPromiseImageTextureReleaseProc textureReleaseProc,
+        PromiseImageTextureDoneProc textureDoneProc,
+        PromiseImageTextureContext textureContexts[]) {
+    if (!fContext) {
+        return nullptr;
+    }
+
+    int numTextures;
+    bool valid = SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures);
+
+    SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc wrappedFulfillProc;
+    SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc wrappedReleaseProc;
+    SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc wrappedDoneProc;
+    SkDeferredDisplayListRecorder::PromiseImageTextureContext wrappedTextureContexts[4];
+    if (!textureDoneProc) {
+        return nullptr;
+    }
+    if (!valid || !textureFulfillProc || !textureReleaseProc) {
+        for (int i = 0; i < numTextures; ++i) {
+            textureDoneProc(textureContexts[i]);
+        }
+        return nullptr;
+    }
+
+    wrap_legacy(textureFulfillProc,
+                textureReleaseProc,
+                textureDoneProc,
+                textureContexts,
+                numTextures,
+                &wrappedFulfillProc,
+                &wrappedReleaseProc,
+                &wrappedDoneProc,
+                wrappedTextureContexts);
+
+    return SkImage_GpuYUVA::MakePromiseYUVATexture(fContext.get(),
+                                                   yuvColorSpace,
+                                                   yuvaFormats,
+                                                   yuvaSizes,
+                                                   yuvaIndices,
+                                                   imageWidth,
+                                                   imageHeight,
+                                                   imageOrigin,
+                                                   std::move(imageColorSpace),
+                                                   wrappedFulfillProc,
+                                                   wrappedReleaseProc,
+                                                   wrappedDoneProc,
+                                                   wrappedTextureContexts);
+}
 #endif
diff --git a/src/core/SkPromiseImageTexture.cpp b/src/core/SkPromiseImageTexture.cpp
new file mode 100644
index 0000000..76eabc7
--- /dev/null
+++ b/src/core/SkPromiseImageTexture.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPromiseImageTexture.h"
+#include "SkMessageBus.h"
+
+#if SK_SUPPORT_GPU
+
+std::atomic<uint32_t> SkPromiseImageTexture::gUniqueID{1};
+
+SkPromiseImageTexture::SkPromiseImageTexture(const GrBackendTexture& backendTexture) {
+    if (backendTexture.isValid()) {
+        fBackendTexture = backendTexture;
+        fUniqueID = gUniqueID++;
+    }
+}
+
+SkPromiseImageTexture::SkPromiseImageTexture(SkPromiseImageTexture&& that) {
+    *this = std::move(that);
+}
+
+SkPromiseImageTexture& SkPromiseImageTexture::operator=(SkPromiseImageTexture&& that) {
+    for (const auto& msg : fMessages) {
+        SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(msg);
+    }
+    fMessages = that.fMessages;
+    that.fMessages.reset();
+    fBackendTexture = that.fBackendTexture;
+    that.fBackendTexture = {};
+    fUniqueID = that.fUniqueID;
+    that.fUniqueID = SK_InvalidUniqueID;
+    return *this;
+}
+
+SkPromiseImageTexture::~SkPromiseImageTexture() {
+    for (const auto& msg : fMessages) {
+        SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(msg);
+    }
+}
+
+void SkPromiseImageTexture::addKeyToInvalidate(uint32_t contextID, const GrUniqueKey& key) {
+    SkASSERT(contextID != SK_InvalidUniqueID);
+    SkASSERT(key.isValid());
+    fMessages.emplace_back(key, contextID);
+}
+
+#if GR_TEST_UTILS
+SkTArray<GrUniqueKey> SkPromiseImageTexture::testingOnly_uniqueKeysToInvalidate() const {
+    SkTArray<GrUniqueKey> results;
+    for (const auto& msg : fMessages) {
+        results.push_back(msg.key());
+    }
+    return results;
+}
+#endif
+
+#endif
diff --git a/src/gpu/GrResourceProvider.h b/src/gpu/GrResourceProvider.h
index fe1bdd3..84a97a1 100644
--- a/src/gpu/GrResourceProvider.h
+++ b/src/gpu/GrResourceProvider.h
@@ -66,8 +66,7 @@
      * must be sure that if a resource of exists in the cache with the given unique key then it is
      * of type T.
      */
-    template <typename T>
-    sk_sp<T> findByUniqueKey(const GrUniqueKey& key) {
+    template <typename T = GrGpuResource> sk_sp<T> findByUniqueKey(const GrUniqueKey& key) {
         return sk_sp<T>(static_cast<T*>(this->findResourceByUniqueKey(key).release()));
     }
 
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 8e00eb4..5de8aa9 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -322,10 +322,10 @@
                                                SkColorType colorType,
                                                SkAlphaType alphaType,
                                                sk_sp<SkColorSpace> colorSpace,
-                                               TextureFulfillProc textureFulfillProc,
-                                               TextureReleaseProc textureReleaseProc,
-                                               PromiseDoneProc promiseDoneProc,
-                                               TextureContext textureContext) {
+                                               PromiseImageTextureFulfillProc textureFulfillProc,
+                                               PromiseImageTextureReleaseProc textureReleaseProc,
+                                               PromiseImageTextureDoneProc promiseDoneProc,
+                                               PromiseImageTextureContext textureContext) {
     // The contract here is that if 'promiseDoneProc' is passed in it should always be called,
     // even if creation of the SkImage fails. Once we call MakePromiseImageLazyProxy it takes
     // responsibility for calling the done proc.
diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h
index de0407d..1f82539 100644
--- a/src/image/SkImage_Gpu.h
+++ b/src/image/SkImage_Gpu.h
@@ -76,8 +76,9 @@
         @param colorSpace          range of colors; may be nullptr
         @param textureFulfillProc  function called to get actual gpu texture
         @param textureReleaseProc  function called when texture can be released
-        @param promiseDoneProc     function called when we will no longer call textureFulfillProc
-        @param textureContext      state passed to textureFulfillProc and textureReleaseProc
+        @param textureDoneProc     function called when we will no longer call textureFulfillProc
+        @param textureContext      state passed to textureFulfillProc, textureReleaseProc, and
+                                   promiseDoneProc
         @return                    created SkImage, or nullptr
      */
     static sk_sp<SkImage> MakePromiseTexture(GrContext* context,
@@ -89,10 +90,10 @@
                                              SkColorType colorType,
                                              SkAlphaType alphaType,
                                              sk_sp<SkColorSpace> colorSpace,
-                                             TextureFulfillProc textureFulfillProc,
-                                             TextureReleaseProc textureReleaseProc,
-                                             PromiseDoneProc promiseDoneProc,
-                                             TextureContext textureContext);
+                                             PromiseImageTextureFulfillProc textureFulfillProc,
+                                             PromiseImageTextureReleaseProc textureReleaseProc,
+                                             PromiseImageTextureDoneProc textureDoneProc,
+                                             PromiseImageTextureContext textureContext);
 
     static sk_sp<SkImage> ConvertYUVATexturesToRGB(GrContext*, SkYUVColorSpace yuvColorSpace,
                                                    const GrBackendTexture yuvaTextures[],
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index 1c013dd..73aecf4 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkImage_GpuBase.h"
 #include "GrBackendSurface.h"
 #include "GrClip.h"
 #include "GrContext.h"
@@ -12,11 +13,11 @@
 #include "GrRenderTargetContext.h"
 #include "GrTexture.h"
 #include "GrTextureAdjuster.h"
-#include "effects/GrYUVtoRGBEffect.h"
 #include "SkBitmapCache.h"
 #include "SkImage_Gpu.h"
-#include "SkImage_GpuBase.h"
+#include "SkPromiseImageTexture.h"
 #include "SkReadPixelsRec.h"
+#include "effects/GrYUVtoRGBEffect.h"
 
 SkImage_GpuBase::SkImage_GpuBase(sk_sp<GrContext> context, int width, int height, uint32_t uniqueID,
                                  SkAlphaType at, sk_sp<SkColorSpace> cs)
@@ -391,9 +392,10 @@
 sk_sp<GrTextureProxy> SkImage_GpuBase::MakePromiseImageLazyProxy(
         GrContext* context, int width, int height, GrSurfaceOrigin origin, GrPixelConfig config,
         GrBackendFormat backendFormat, GrMipMapped mipMapped,
-        SkImage_GpuBase::TextureFulfillProc fulfillProc,
-        SkImage_GpuBase::TextureReleaseProc releaseProc, SkImage_GpuBase::PromiseDoneProc doneProc,
-        SkImage_GpuBase::TextureContext textureContext) {
+        PromiseImageTextureFulfillProc fulfillProc,
+        PromiseImageTextureReleaseProc releaseProc,
+        PromiseImageTextureDoneProc doneProc,
+        PromiseImageTextureContext textureContext) {
     SkASSERT(context);
     SkASSERT(width > 0 && height > 0);
     SkASSERT(doneProc);
@@ -413,115 +415,169 @@
     }
 
     /**
-     * This helper class manages the ref counting for the the ReleaseProc and DoneProc for promise
-     * images. It holds a weak ref on the ReleaseProc (hard refs are owned by GrTextures). The weak
-     * ref allows us to reuse an outstanding ReleaseProc (because we dropped our GrTexture but the
-     * GrTexture isn't done on the GPU) without needing to call FulfillProc again. It also holds a
-     * hard ref on the DoneProc. The idea is that after every flush we may call the ReleaseProc so
-     * that the client can free up their GPU memory if they want to. The life time of the DoneProc
-     * matches that of any outstanding ReleaseProc as well as the PromiseLazyInstantiateCallback.
-     * Thus we won't call the DoneProc until all ReleaseProcs are finished and we are finished with
-     * the PromiseImageHelper (i.e. won't call FulfillProc again).
+     * This class is the lazy instantiation callback for promise images. It manages calling the
+     * client's Fulfill, Release, and Done procs. It attempts to reuse a GrTexture instance in
+     * cases where the client provides the same SkPromiseImageTexture for successive Fulfill calls.
+     * The created GrTexture is given a key based on a unique ID associated with the
+     * SkPromiseImageTexture. When the texture enters "idle" state (meaning it is not being used by
+     * the GPU and is at rest in the resource cache) the client's Release proc is called
+     * using GrTexture's idle proc mechanism. If the same SkPromiseImageTexture is provided for
+     * another fulfill we find the cached GrTexture. If the proxy, and therefore this object,
+     * is destroyed, we invalidate the GrTexture's key. Also if the client overwrites or
+     * destroys their SkPromiseImageTexture we invalidate the key.
+     *
+     * Currently a GrTexture is only reused for a given SkPromiseImageTexture if the
+     * SkPromiseImageTexture is reused in Fulfill for the same promise SkImage. However, we'd
+     * like to relax that so that a SkPromiseImageTexture can be reused with different promise
+     * SkImages that will reuse a single GrTexture.
      */
     class PromiseLazyInstantiateCallback {
     public:
-        PromiseLazyInstantiateCallback(SkImage_GpuBase::TextureFulfillProc fulfillProc,
-                                       SkImage_GpuBase::TextureReleaseProc releaseProc,
-                                       SkImage_GpuBase::PromiseDoneProc doneProc,
-                                       SkImage_GpuBase::TextureContext context,
+        PromiseLazyInstantiateCallback(PromiseImageTextureFulfillProc fulfillProc,
+                                       PromiseImageTextureReleaseProc releaseProc,
+                                       PromiseImageTextureDoneProc doneProc,
+                                       PromiseImageTextureContext context,
                                        GrPixelConfig config)
                 : fFulfillProc(fulfillProc)
                 , fReleaseProc(releaseProc)
                 , fContext(context)
                 , fConfig(config) {
-            fDoneHelper.reset(new GrReleaseProcHelper(doneProc, context));
+            fDoneHelper = sk_make_sp<GrReleaseProcHelper>(doneProc, context);
+            static std::atomic<uint32_t> gUniqueID;
+            fUniqueID = gUniqueID.fetch_add(1) + 1;
+        }
+        ~PromiseLazyInstantiateCallback() {
+            // If we've already released the texture then it is safe to call done now. Here we may
+            // be on any thread.
+            if (fIdleContext && fIdleContext->fWasReleased.load()) {
+                // We still own a ref on fDoneHelper so no other thread can be calling the done
+                // proc.
+                fDoneHelper->callAndClear();
+            }
+            // Remove the key from the texture so that the texture will be removed from the cache.
+            // If we didn't just call the done proc above then it will get called when the texture
+            // is removed from the cache after this message is processed.
+            if (fLastFulfilledKey.isValid()) {
+                SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
+                        GrUniqueKeyInvalidatedMessage(fLastFulfilledKey, fContextID));
+            }
         }
 
         sk_sp<GrSurface> operator()(GrResourceProvider* resourceProvider) {
             if (!resourceProvider) {
-                this->reset();
+                return nullptr;
+            }
+
+            sk_sp<GrTexture> cachedTexture;
+            SkASSERT(fLastFulfilledKey.isValid() == (fLastFulfillID > 0));
+            if (fLastFulfilledKey.isValid()) {
+                auto surf = resourceProvider->findByUniqueKey<GrSurface>(fLastFulfilledKey);
+                if (surf) {
+                    cachedTexture = sk_ref_sp(surf->asTexture());
+                    SkASSERT(cachedTexture);
+                }
+            }
+            // If the release callback hasn't been called already by releasing the GrTexture
+            // then we can be sure that won't happen so long as we have a ref to the texture.
+            // Moreoever, only this thread should be able to change the atomic to true, hence the
+            // relaxed memory order.
+            if (cachedTexture && !fIdleContext->fWasReleased.load(std::memory_order_relaxed)) {
+                return std::move(cachedTexture);
+            }
+            GrBackendTexture backendTexture;
+            SkPromiseImageTexture* promiseTexture = fFulfillProc(fContext);
+            if (!promiseTexture) {
+                fReleaseProc(fContext, nullptr);
+                return sk_sp<GrTexture>();
+            }
+            bool same = promiseTexture->uniqueID() == fLastFulfillID;
+            SkASSERT(!same || fLastFulfilledKey.isValid());
+            if (same && cachedTexture) {
+                SkASSERT(fIdleContext->unique());
+                // Reset the purgeable context so that we balance the new fulfill with a release.
+                fIdleContext->ref();
+                SkASSERT(fIdleContext->fReleaseProc == fReleaseProc);
+                SkASSERT(fIdleContext->fTextureContext == fContext);
+                // Memory order relaxed because only this thread can change fWasReleased to true.
+                fIdleContext->fWasReleased.store(false, std::memory_order_relaxed);
+                cachedTexture->setIdleProc(IdleProc, fIdleContext.get());
+                return std::move(cachedTexture);
+            } else if (cachedTexture) {
+                cachedTexture->resourcePriv().removeUniqueKey();
+                // We don't want calling the client's done proc to be tied to the old texture.
+                cachedTexture->setRelease(nullptr);
+            }
+            fLastFulfillID = promiseTexture->uniqueID();
+
+            backendTexture = promiseTexture->backendTexture();
+            backendTexture.fConfig = fConfig;
+            if (!backendTexture.isValid()) {
+                // Even though the GrBackendTexture is not valid, we must call the release
+                // proc to keep our contract of always calling Fulfill and Release in pairs.
+                fReleaseProc(fContext, promiseTexture);
                 return sk_sp<GrTexture>();
             }
 
-            // Releases the promise helper if there are no outstanding hard refs. This means that we
-            // don't have any ReleaseProcs waiting to be called so we will need to do a fulfill.
-            if (fReleaseHelper && fReleaseHelper->weak_expired()) {
-                this->resetReleaseHelper();
+            auto tex = resourceProvider->wrapBackendTexture(backendTexture, kBorrow_GrWrapOwnership,
+                                                            kRead_GrIOType);
+            if (!tex) {
+                // Even though we failed to wrap the backend texture, we must call the release
+                // proc to keep our contract of always calling Fulfill and Release in pairs.
+                fReleaseProc(fContext, promiseTexture);
+                return sk_sp<GrTexture>();
             }
-
-            sk_sp<GrTexture> tex;
-            if (!fReleaseHelper) {
-                fFulfillProc(fContext, &fBackendTex);
-                fBackendTex.fConfig = fConfig;
-                if (!fBackendTex.isValid()) {
-                    // Even though the GrBackendTexture is not valid, we must call the release
-                    // proc to keep our contract of always calling Fulfill and Release in pairs.
-                    fReleaseProc(fContext);
-                    return sk_sp<GrTexture>();
-                }
-
-                tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership,
-                                                           kRead_GrIOType);
-                if (!tex) {
-                    // Even though the GrBackendTexture is not valid, we must call the release
-                    // proc to keep our contract of always calling Fulfill and Release in pairs.
-                    fReleaseProc(fContext);
-                    return sk_sp<GrTexture>();
-                }
-                fReleaseHelper =
-                        new SkPromiseReleaseProcHelper(fReleaseProc, fContext, fDoneHelper);
-                // Take a weak ref
-                fReleaseHelper->weak_ref();
-            } else {
-                SkASSERT(fBackendTex.isValid());
-                tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership,
-                                                           kRead_GrIOType);
-                if (!tex) {
-                    // We weren't able to make a texture here, but since we are in this branch
-                    // of the calls (promiseHelper.fReleaseHelper is valid) there is already a
-                    // texture out there which will call the release proc so we don't need to
-                    // call it here.
-                    return sk_sp<GrTexture>();
-                }
-
-                SkAssertResult(fReleaseHelper->try_ref());
-            }
-            SkASSERT(tex);
-            // Pass the hard ref off to the texture
-            tex->setRelease(sk_sp<GrReleaseProcHelper>(fReleaseHelper));
+            fIdleContext = sk_make_sp<IdleContext>(fReleaseProc, fContext, promiseTexture);
+            // The texture gets a ref, which is balanced when the idle callback is called.
+            fIdleContext->ref();
+            tex->setIdleProc(IdleProc, fIdleContext.get());
+            tex->setRelease(fDoneHelper);
+            static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+            GrUniqueKey::Builder builder(&fLastFulfilledKey, kDomain, 2, "promise");
+            builder[0] = promiseTexture->uniqueID();
+            builder[1] = fUniqueID;
+            builder.finish();
+            tex->resourcePriv().setUniqueKey(fLastFulfilledKey);
+            SkASSERT(fContextID == SK_InvalidUniqueID ||
+                     fContextID == tex->getContext()->uniqueID());
+            fContextID = tex->getContext()->uniqueID();
+            promiseTexture->addKeyToInvalidate(fContextID, fLastFulfilledKey);
             return std::move(tex);
         }
 
     private:
-        void reset() {
-            this->resetReleaseHelper();
-            fDoneHelper.reset();
+        struct IdleContext : public SkNVRefCnt<IdleContext> {
+            IdleContext(PromiseImageTextureReleaseProc proc, PromiseImageTextureContext context,
+                        const SkPromiseImageTexture* texture)
+                    : fReleaseProc(proc), fTextureContext(context), fPromiseImageTexture(texture) {}
+            PromiseImageTextureReleaseProc fReleaseProc;
+            PromiseImageTextureContext fTextureContext;
+            const SkPromiseImageTexture* fPromiseImageTexture;
+            std::atomic<bool> fWasReleased{false};
+        };
+        static void IdleProc(void* context) {
+            IdleContext* rc = static_cast<IdleContext*>(context);
+            SkASSERT(!rc->fWasReleased.load());
+            rc->fReleaseProc(rc->fTextureContext, rc->fPromiseImageTexture);
+            rc->fWasReleased.store(true);
+            // Drop the texture's implicit ref on the IdleContext.
+            rc->unref();
         }
 
-        // Weak unrefs fReleaseHelper and sets it to null
-        void resetReleaseHelper() {
-            if (fReleaseHelper) {
-                fReleaseHelper->weak_unref();
-                fReleaseHelper = nullptr;
-            }
-        }
-
-        SkImage_GpuBase::TextureFulfillProc fFulfillProc;
-        SkImage_GpuBase::TextureReleaseProc fReleaseProc;
-        SkImage_GpuBase::TextureContext fContext;
+        PromiseImageTextureFulfillProc fFulfillProc;
+        PromiseImageTextureReleaseProc fReleaseProc;
+        PromiseImageTextureContext fContext;
 
         GrPixelConfig fConfig;
-        // We cache the GrBackendTexture so that if we deleted the GrTexture but the the release
-        // proc has yet not been called (this can happen on Vulkan), then we can create a new
-        // texture without needing to call the fulfill proc again.
-        GrBackendTexture fBackendTex;
-        // The fReleaseHelper is used to track a weak ref on the release proc. This helps us make
-        // sure we are always pairing fulfill and release proc calls correctly.
-        SkPromiseReleaseProcHelper* fReleaseHelper = nullptr;
-        // We don't want to call the fDoneHelper until we are done with the PromiseImageHelper and
-        // all ReleaseHelpers are finished. Thus we hold a hard ref here and we will pass a hard ref
-        // to each fReleaseHelper we make.
+        sk_sp<IdleContext> fIdleContext;
         sk_sp<GrReleaseProcHelper> fDoneHelper;
+
+        // ID of the last SkPromiseImageTexture given to us by the client.
+        uint32_t fLastFulfillID = 0;
+        // ID of the GrContext that we are interacting with.
+        uint32_t fContextID = SK_InvalidUniqueID;
+        // Unique ID of this lazy instantiation callback.
+        uint32_t fUniqueID;
+        GrUniqueKey fLastFulfilledKey;
     } callback(fulfillProc, releaseProc, doneProc, textureContext, config);
 
     GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
diff --git a/src/image/SkImage_GpuBase.h b/src/image/SkImage_GpuBase.h
index 7b183b7..5be97c8 100644
--- a/src/image/SkImage_GpuBase.h
+++ b/src/image/SkImage_GpuBase.h
@@ -10,6 +10,7 @@
 
 #include "GrBackendSurface.h"
 #include "GrTypesPriv.h"
+#include "SkDeferredDisplayListRecorder.h"
 #include "SkImage_Base.h"
 #include "SkYUVAIndex.h"
 
@@ -72,9 +73,12 @@
                                                                : kOpaque_SkAlphaType;
     }
 
-    typedef ReleaseContext TextureContext;
-    typedef void(*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture);
-    typedef void(*PromiseDoneProc)(TextureContext textureContext);
+    using PromiseImageTextureContext = SkDeferredDisplayListRecorder::PromiseImageTextureContext;
+    using PromiseImageTextureFulfillProc =
+            SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc;
+    using PromiseImageTextureReleaseProc =
+            SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc;
+    using PromiseImageTextureDoneProc = SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc;
 
 protected:
     // Helper for making a lazy proxy for a promise image. The PromiseDoneProc we be called,
@@ -83,8 +87,8 @@
     // be null.
     static sk_sp<GrTextureProxy> MakePromiseImageLazyProxy(
             GrContext*, int width, int height, GrSurfaceOrigin, GrPixelConfig, GrBackendFormat,
-            GrMipMapped, SkImage_GpuBase::TextureFulfillProc, SkImage_GpuBase::TextureReleaseProc,
-            SkImage_GpuBase::PromiseDoneProc, SkImage_GpuBase::TextureContext);
+            GrMipMapped, PromiseImageTextureFulfillProc, PromiseImageTextureReleaseProc,
+            PromiseImageTextureDoneProc, PromiseImageTextureContext);
 
     static bool RenderYUVAToRGBA(GrContext* ctx, GrRenderTargetContext* renderTargetContext,
                                  const SkRect& rect, SkYUVColorSpace yuvColorSpace,
@@ -99,31 +103,4 @@
     typedef SkImage_Base INHERITED;
 };
 
-
-/**
- * This helper holds the normal hard ref for the Release proc as well as a hard ref on the DoneProc.
- * Thus when a GrTexture is being released, it will unref both the ReleaseProc and DoneProc.
- */
-class SkPromiseReleaseProcHelper : public GrReleaseProcHelper {
-public:
-    SkPromiseReleaseProcHelper(SkImage_GpuBase::TextureReleaseProc releaseProc,
-                               SkImage_GpuBase::TextureContext context,
-                               sk_sp<GrReleaseProcHelper> doneHelper)
-        : INHERITED(releaseProc, context)
-        , fDoneProcHelper(std::move(doneHelper)) {
-    }
-
-    void weak_dispose() const override {
-        // Call the inherited weak_dispose first so that we call the ReleaseProc before the DoneProc
-        // if we hold the last ref to the DoneProc.
-        INHERITED::weak_dispose();
-        fDoneProcHelper.reset();
-    }
-
-private:
-    mutable sk_sp<GrReleaseProcHelper> fDoneProcHelper;
-
-    typedef GrReleaseProcHelper INHERITED;
-};
-
 #endif
diff --git a/src/image/SkImage_GpuYUVA.cpp b/src/image/SkImage_GpuYUVA.cpp
index aa1d0b3..340f1ef 100644
--- a/src/image/SkImage_GpuYUVA.cpp
+++ b/src/image/SkImage_GpuYUVA.cpp
@@ -206,19 +206,20 @@
 
 
 /////////////////////////////////////////////////////////////////////////////////////////////////
-sk_sp<SkImage> SkImage_GpuYUVA::MakePromiseYUVATexture(GrContext* context,
-                                                       SkYUVColorSpace yuvColorSpace,
-                                                       const GrBackendFormat yuvaFormats[],
-                                                       const SkISize yuvaSizes[],
-                                                       const SkYUVAIndex yuvaIndices[4],
-                                                       int imageWidth,
-                                                       int imageHeight,
-                                                       GrSurfaceOrigin imageOrigin,
-                                                       sk_sp<SkColorSpace> imageColorSpace,
-                                                       TextureFulfillProc textureFulfillProc,
-                                                       TextureReleaseProc textureReleaseProc,
-                                                       PromiseDoneProc promiseDoneProc,
-                                                       TextureContext textureContexts[]) {
+sk_sp<SkImage> SkImage_GpuYUVA::MakePromiseYUVATexture(
+        GrContext* context,
+        SkYUVColorSpace yuvColorSpace,
+        const GrBackendFormat yuvaFormats[],
+        const SkISize yuvaSizes[],
+        const SkYUVAIndex yuvaIndices[4],
+        int imageWidth,
+        int imageHeight,
+        GrSurfaceOrigin imageOrigin,
+        sk_sp<SkColorSpace> imageColorSpace,
+        PromiseImageTextureFulfillProc textureFulfillProc,
+        PromiseImageTextureReleaseProc textureReleaseProc,
+        PromiseImageTextureDoneProc promiseDoneProc,
+        PromiseImageTextureContext textureContexts[]) {
     int numTextures;
     bool valid = SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures);
 
@@ -289,4 +290,3 @@
                                        kNeedNewImageUniqueID, yuvColorSpace, proxies, numTextures,
                                        yuvaIndices, imageOrigin, std::move(imageColorSpace));
 }
-
diff --git a/src/image/SkImage_GpuYUVA.h b/src/image/SkImage_GpuYUVA.h
index c580284..1a6ab93 100644
--- a/src/image/SkImage_GpuYUVA.h
+++ b/src/image/SkImage_GpuYUVA.h
@@ -53,7 +53,6 @@
     // Returns a ref-ed texture proxy with miplevels
     sk_sp<GrTextureProxy> asMippedTextureProxyRef() const;
 
-
     /**
         Create a new SkImage_GpuYUVA that's very similar to SkImage created by MakeFromYUVATextures.
         The main difference is that the client doesn't have the backend textures on the gpu yet but
@@ -90,8 +89,9 @@
         @param imageColorSpace     range of colors; may be nullptr
         @param textureFulfillProc  function called to get actual gpu texture
         @param textureReleaseProc  function called when texture can be released
-        @param promiseDoneProc     function called when we will no longer call textureFulfillProc
-        @param textureContext      state passed to textureFulfillProc and textureReleaseProc
+        @param textureDoneProc     function called when we will no longer call textureFulfillProc
+        @param textureContexts     per-texture state passed to textureFulfillProc,
+                                   textureReleaseProc, and textureDoneProc
         @return                    created SkImage, or nullptr
      */
     static sk_sp<SkImage> MakePromiseYUVATexture(GrContext* context,
@@ -103,10 +103,10 @@
                                                  int height,
                                                  GrSurfaceOrigin imageOrigin,
                                                  sk_sp<SkColorSpace> imageColorSpace,
-                                                 TextureFulfillProc textureFulfillProc,
-                                                 TextureReleaseProc textureReleaseProc,
-                                                 PromiseDoneProc promiseDoneProc,
-                                                 TextureContext textureContexts[]);
+                                                 PromiseImageTextureFulfillProc textureFulfillProc,
+                                                 PromiseImageTextureReleaseProc textureReleaseProc,
+                                                 PromiseImageTextureDoneProc textureDoneProc,
+                                                 PromiseImageTextureContext textureContexts[]);
 
 private:
     // This array will usually only be sparsely populated.