Make GrTexture caching for SkPromiseImageTexture work when same texture
fulfills a different SkImage.

Bug: skia:8613
Change-Id: I7ee14112c69f8aaef223a37dda455259b501a2bf
Reviewed-on: https://skia-review.googlesource.com/c/184440
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/core/SkPromiseImageTexture.cpp b/src/core/SkPromiseImageTexture.cpp
index f4801bb..5c55a9a 100644
--- a/src/core/SkPromiseImageTexture.cpp
+++ b/src/core/SkPromiseImageTexture.cpp
@@ -27,6 +27,11 @@
 void SkPromiseImageTexture::addKeyToInvalidate(uint32_t contextID, const GrUniqueKey& key) {
     SkASSERT(contextID != SK_InvalidUniqueID);
     SkASSERT(key.isValid());
+    for (const auto& msg : fMessages) {
+        if (msg.contextID() == contextID && msg.key() == key) {
+            return;
+        }
+    }
     fMessages.emplace_back(key, contextID);
 }
 
diff --git a/src/gpu/gl/GrGLTexture.h b/src/gpu/gl/GrGLTexture.h
index db83eea..f9d4acd 100644
--- a/src/gpu/gl/GrGLTexture.h
+++ b/src/gpu/gl/GrGLTexture.h
@@ -86,6 +86,7 @@
         fIdleProc = proc;
         fIdleProcContext = context;
     }
+    void* idleContext() const override { return fIdleProcContext; }
 
     // These functions are used to track the texture parameters associated with the texture.
     GrGpu::ResetTimestamp getCachedParamsTimestamp() const { return fParamsTimestamp; }
diff --git a/src/gpu/mock/GrMockTexture.h b/src/gpu/mock/GrMockTexture.h
index ac5eab0..3674de6 100644
--- a/src/gpu/mock/GrMockTexture.h
+++ b/src/gpu/mock/GrMockTexture.h
@@ -52,6 +52,7 @@
         fIdleProc = proc;
         fIdleProcContext = context;
     }
+    void* idleContext() const override { return fIdleProcContext; }
 
 protected:
     // constructor for subclasses
diff --git a/src/gpu/mtl/GrMtlTexture.h b/src/gpu/mtl/GrMtlTexture.h
index 8037c68..2cdb92b 100644
--- a/src/gpu/mtl/GrMtlTexture.h
+++ b/src/gpu/mtl/GrMtlTexture.h
@@ -47,6 +47,7 @@
         fIdleProc = proc;
         fIdleProcContext = context;
     }
+    void* idleContext() const override { return fIdleProcContext; }
 
 protected:
     GrMtlTexture(GrMtlGpu*, const GrSurfaceDesc&, id<MTLTexture>, GrMipMapsStatus);
diff --git a/src/gpu/vk/GrVkTexture.h b/src/gpu/vk/GrVkTexture.h
index d913aed..0592fc8 100644
--- a/src/gpu/vk/GrVkTexture.h
+++ b/src/gpu/vk/GrVkTexture.h
@@ -46,6 +46,7 @@
     }
 
     void setIdleProc(IdleProc, void* context) override;
+    void* idleContext() const override { return fIdleProcContext; }
 
 protected:
     GrVkTexture(GrVkGpu*, const GrSurfaceDesc&, const GrVkImageInfo&, sk_sp<GrVkImageLayout>,
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index 8d90be5..0e74923 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -17,6 +17,7 @@
 #include "SkImage_Gpu.h"
 #include "SkPromiseImageTexture.h"
 #include "SkReadPixelsRec.h"
+#include "SkTLList.h"
 #include "effects/GrYUVtoRGBEffect.h"
 
 SkImage_GpuBase::SkImage_GpuBase(sk_sp<GrContext> context, int width, int height, uint32_t uniqueID,
@@ -405,18 +406,11 @@
                 : fFulfillProc(fulfillProc)
                 , fConfig(config) {
             auto doneHelper = sk_make_sp<GrReleaseProcHelper>(doneProc, context);
-            fIdleContext = sk_make_sp<IdleContext>(releaseProc, context, std::move(doneHelper));
-            static std::atomic<uint32_t> gUniqueID;
-            fUniqueID = gUniqueID.fetch_add(1) + 1;
+            fReleaseContext = sk_make_sp<IdleContext::PromiseImageReleaseContext>(
+                    releaseProc, context, std::move(doneHelper));
         }
 
-        ~PromiseLazyInstantiateCallback() {
-            // Remove the key from the texture so that the texture will be removed from the cache.
-            if (fLastFulfilledKey.isValid()) {
-                SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
-                        GrUniqueKeyInvalidatedMessage(fLastFulfilledKey, fContextID));
-            }
-        }
+        ~PromiseLazyInstantiateCallback() = default;
 
         sk_sp<GrSurface> operator()(GrResourceProvider* resourceProvider) {
             if (!resourceProvider) {
@@ -434,24 +428,22 @@
             }
             // 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.
-            if (cachedTexture && !fIdleContext->wasReleased()) {
+            if (cachedTexture && !fReleaseContext->isReleased()) {
                 return std::move(cachedTexture);
             }
             GrBackendTexture backendTexture;
             sk_sp<SkPromiseImageTexture> promiseTexture =
-                    fFulfillProc(fIdleContext->textureContext());
-            fIdleContext->reset();
+                    fFulfillProc(fReleaseContext->textureContext());
+            fReleaseContext->notifyWasFulfilled();
             if (!promiseTexture) {
-                IdleContext::IdleProc(fIdleContext.get());
+                fReleaseContext->release();
                 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();
-                cachedTexture->setIdleProc(IdleContext::IdleProc, fIdleContext.get());
+                SkASSERT(fReleaseContext->unique());
+                this->addToIdleContext(cachedTexture.get());
                 return std::move(cachedTexture);
             } else if (cachedTexture) {
                 cachedTexture->resourcePriv().removeUniqueKey();
@@ -463,7 +455,7 @@
             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.
-                IdleContext::IdleProc(fIdleContext.get());
+                fReleaseContext->release();
                 return sk_sp<GrTexture>();
             }
 
@@ -472,16 +464,15 @@
             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.
-                IdleContext::IdleProc(fIdleContext.get());
+                fReleaseContext->release();
                 return sk_sp<GrTexture>();
             }
             // The texture gets a ref, which is balanced when the idle callback is called.
-            fIdleContext->ref();
-            tex->setIdleProc(IdleContext::IdleProc, fIdleContext.get());
+            this->addToIdleContext(tex.get());
             static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
             GrUniqueKey::Builder builder(&fLastFulfilledKey, kDomain, 2, "promise");
             builder[0] = promiseTexture->uniqueID();
-            builder[1] = fUniqueID;
+            builder[1] = fConfig;
             builder.finish();
             tex->resourcePriv().setUniqueKey(fLastFulfilledKey);
             SkASSERT(fContextID == SK_InvalidUniqueID ||
@@ -498,39 +489,74 @@
         // ownership of the IdleContext. Thus, the IdleContext is destroyed and calls the Done proc
         // after the last fulfilled texture goes idle and calls the Release proc or the proxy's
         // destructor destroys the lazy callback, whichever comes last.
-        class IdleContext : public SkNVRefCnt<IdleContext> {
+        class IdleContext {
         public:
-            IdleContext() = delete;
-            IdleContext(PromiseImageTextureReleaseProc releaseProc,
-                        PromiseImageTextureContext textureContext,
-                        sk_sp<GrReleaseProcHelper> doneHelper)
-                    : fReleaseProc(releaseProc)
-                    , fTextureContext(textureContext)
-                    , fDoneHelper(std::move(doneHelper)) {}
+            class PromiseImageReleaseContext;
 
-            ~IdleContext() { SkASSERT(fWasReleased); }
+            IdleContext() = default;
 
-            void reset() { fWasReleased = false; }
-            bool wasReleased() const { return fWasReleased; }
+            ~IdleContext() = default;
 
-            PromiseImageTextureContext textureContext() const { return fTextureContext; }
-
-            static void IdleProc(void* context) {
-                IdleContext* rc = static_cast<IdleContext*>(context);
-                rc->fReleaseProc(rc->fTextureContext);
-                rc->fWasReleased = true;
-                // Drop the texture's implicit ref on the IdleContext.
-                rc->unref();
+            void addImageReleaseContext(sk_sp<PromiseImageReleaseContext> context) {
+                fReleaseContexts.addToHead(std::move(context));
             }
 
+            static void IdleProc(void* context) {
+                IdleContext* idleContext = static_cast<IdleContext*>(context);
+                for (ReleaseContextList::Iter iter = idleContext->fReleaseContexts.headIter();
+                     iter.get();
+                     iter.next()) {
+                    (*iter.get())->release();
+                }
+                idleContext->fReleaseContexts.reset();
+                delete idleContext;
+            }
+
+            class PromiseImageReleaseContext : public SkNVRefCnt<PromiseImageReleaseContext> {
+            public:
+                PromiseImageReleaseContext(PromiseImageTextureReleaseProc releaseProc,
+                                           PromiseImageTextureContext textureContext,
+                                           sk_sp<GrReleaseProcHelper> doneHelper)
+                        : fReleaseProc(releaseProc)
+                        , fTextureContext(textureContext)
+                        , fDoneHelper(std::move(doneHelper)) {}
+
+                ~PromiseImageReleaseContext() { SkASSERT(fIsReleased); }
+
+                void release() {
+                    SkASSERT(!fIsReleased);
+                    fReleaseProc(fTextureContext);
+                    fIsReleased = true;
+                }
+
+                void notifyWasFulfilled() { fIsReleased = false; }
+                bool isReleased() const { return fIsReleased; }
+
+                PromiseImageTextureContext textureContext() const { return fTextureContext; }
+
+            private:
+                PromiseImageTextureReleaseProc fReleaseProc;
+                PromiseImageTextureContext fTextureContext;
+                sk_sp<GrReleaseProcHelper> fDoneHelper;
+                bool fIsReleased = true;
+            };
+
         private:
-            PromiseImageTextureReleaseProc fReleaseProc;
-            PromiseImageTextureContext fTextureContext;
-            sk_sp<GrReleaseProcHelper> fDoneHelper;
-            bool fWasReleased = true;
+            using ReleaseContextList = SkTLList<sk_sp<PromiseImageReleaseContext>, 4>;
+            ReleaseContextList fReleaseContexts;
         };
 
-        sk_sp<IdleContext> fIdleContext;
+        void addToIdleContext(GrTexture* texture) {
+            SkASSERT(!fReleaseContext->isReleased());
+            IdleContext* idleContext = static_cast<IdleContext*>(texture->idleContext());
+            if (!idleContext) {
+                idleContext = new IdleContext();
+                texture->setIdleProc(IdleContext::IdleProc, idleContext);
+            }
+            idleContext->addImageReleaseContext(fReleaseContext);
+        }
+
+        sk_sp<IdleContext::PromiseImageReleaseContext> fReleaseContext;
         PromiseImageTextureFulfillProc fFulfillProc;
         GrPixelConfig fConfig;
 
@@ -538,8 +564,6 @@
         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);