Consolidate details of lazy proxy creation for promise image textures.

The SkSpecialImage helper is now function-local and *is* the lazy
instantiation callback rather than coordinating with one in the
promise SkImage factory functions.

Bug: skia:
Change-Id: I4883618ef440d09a9ac7959cef254e6f5a49aa55
Reviewed-on: https://skia-review.googlesource.com/c/175822
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index b12fee1..3bd42cf 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -381,53 +381,151 @@
     return true;
 }
 
-/////////////////////////////////////////////////////////////////////////////////////////////////
-sk_sp<GrTexture> SkPromiseImageHelper::getTexture(GrResourceProvider* resourceProvider,
-                                                  GrPixelConfig config) {
-    // 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();
+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) {
+    SkASSERT(context);
+    SkASSERT(width > 0 && height > 0);
+    SkASSERT(doneProc);
+
+    if (!fulfillProc || !releaseProc) {
+        doneProc(textureContext);
+        return nullptr;
     }
 
-    sk_sp<GrTexture> tex;
-    if (!fReleaseHelper) {
-        fFulfillProc(fContext, &fBackendTex);
-        fBackendTex.fConfig = config;
-        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());
+    if (mipMapped == GrMipMapped::kYes &&
+        GrTextureTypeHasRestrictedSampling(backendFormat.textureType())) {
+        // It is invalid to have a GL_TEXTURE_EXTERNAL or GL_TEXTURE_RECTANGLE and have mips as
+        // well.
+        doneProc(textureContext);
+        return nullptr;
     }
-    SkASSERT(tex);
-    // Pass the hard ref off to the texture
-    tex->setRelease(sk_sp<GrReleaseProcHelper>(fReleaseHelper));
-    return tex;
+
+    /**
+     * 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).
+     */
+    class PromiseLazyInstantiateCallback {
+    public:
+        PromiseLazyInstantiateCallback(SkImage_GpuBase::TextureFulfillProc fulfillProc,
+                                       SkImage_GpuBase::TextureReleaseProc releaseProc,
+                                       SkImage_GpuBase::PromiseDoneProc doneProc,
+                                       SkImage_GpuBase::TextureContext context,
+                                       GrPixelConfig config)
+                : fFulfillProc(fulfillProc)
+                , fReleaseProc(releaseProc)
+                , fContext(context)
+                , fConfig(config) {
+            fDoneHelper.reset(new GrReleaseProcHelper(doneProc, context));
+        }
+
+        sk_sp<GrSurface> operator()(GrResourceProvider* resourceProvider) {
+            if (!resourceProvider) {
+                this->reset();
+                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();
+            }
+
+            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));
+            return std::move(tex);
+        }
+
+    private:
+        void reset() {
+            this->resetReleaseHelper();
+            fDoneHelper.reset();
+        }
+
+        // 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;
+
+        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<GrReleaseProcHelper> fDoneHelper;
+    } callback(fulfillProc, releaseProc, doneProc, textureContext, config);
+
+    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
+
+    GrSurfaceDesc desc;
+    desc.fWidth = width;
+    desc.fHeight = height;
+    desc.fConfig = config;
+
+    // We pass kReadOnly here since we should treat content of the client's texture as immutable.
+    return proxyProvider->createLazyProxy(std::move(callback), backendFormat, desc, origin,
+                                          mipMapped, GrInternalSurfaceFlags::kReadOnly,
+                                          SkBackingFit::kExact, SkBudgeted::kNo,
+                                          GrSurfaceProxy::LazyInstantiationType::kDeinstantiate);
 }