Add promise images for deferred instantiation of wrapped gpu textures

This will allow a client to make an SkImage that "wraps" a gpu texture,
however the client does need to supply the actual gpu texture at Image
creation time. Instead it is retrieve at flush time via a callback.

Bug: skia:
Change-Id: I6267a55ab7102101a7bd80a6f547b6a870d2df08
Reviewed-on: https://skia-review.googlesource.com/109021
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Cary Clark <caryclark@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index cde24b0..0c34a6c 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -173,6 +173,7 @@
   "$_tests/PremulAlphaRoundTripTest.cpp",
   "$_tests/PrimitiveProcessorTest.cpp",
   "$_tests/ProcessorTest.cpp",
+  "$_tests/PromiseImageTest.cpp",
   "$_tests/ProxyConversionTest.cpp",
   "$_tests/ProxyRefTest.cpp",
   "$_tests/ProxyTest.cpp",
diff --git a/include/core/SkDeferredDisplayListRecorder.h b/include/core/SkDeferredDisplayListRecorder.h
index c985e73..601cd53 100644
--- a/include/core/SkDeferredDisplayListRecorder.h
+++ b/include/core/SkDeferredDisplayListRecorder.h
@@ -8,11 +8,15 @@
 #ifndef SkDeferredDisplayListMaker_DEFINED
 #define SkDeferredDisplayListMaker_DEFINED
 
+#include "SkImageInfo.h"
 #include "SkRefCnt.h"
 #include "SkSurfaceCharacterization.h"
+#include "SkTypes.h"
 
 #include "../private/SkDeferredDisplayList.h"
 
+class GrBackendFormat;
+class GrBackendTexture;
 class GrContext;
 
 class SkCanvas;
@@ -46,6 +50,61 @@
 
     std::unique_ptr<SkDeferredDisplayList> detach();
 
+    // Matches the defines in SkImage_Gpu.h
+    typedef void* TextureContext;
+    typedef void (*TextureReleaseProc)(TextureContext textureContext);
+    typedef void (*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture);
+
+    /**
+        Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The main
+        difference is that the client doesn't have the backend texture on the gpu yet but they know
+        all the properties of the texture. So instead of passing in a GrBackendTexture the client
+        supplies a GrBackendFormat, width, height, and GrMipMapped state.
+
+        When we actually send the draw calls to the GPU, we will call the textureFulfillProc and
+        the client will return a GrBackendTexture to us. The properties of the GrBackendTexture must
+        match those set during the SkImage creation, and it must have a valid backend gpu texture.
+        The gpu texture supplied by the client must stay valid until we call the textureReleaseProc.
+
+        When we are done with the texture returned by the textureFulfillProc we will call the
+        textureReleaseProc passing in the textureContext. This is a signal to the client that they
+        are free to delete the underlying gpu texture. If future draws also use the same promise
+        image we will call the textureFulfillProc again if we've already called the
+        textureReleaseProc. We will always call textureFulfillProc and textureReleaseProc in pairs.
+        In other words we will never call textureFulfillProc or textureReleaseProc multiple times
+        for the same textureContext before calling the other.
+
+        This call is only valid if the SkDeferredDisplayListRecorder is backed by a gpu context.
+
+        @param backendFormat       format of promised gpu texture
+        @param width               width of promised gpu texture
+        @param height              height of promised gpu texture
+        @param mipMapped           mip mapped state of promised gpu texture
+        @param origin              one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin
+        @param colorType           one of: kUnknown_SkColorType, kAlpha_8_SkColorType,
+                                   kRGB_565_SkColorType, kARGB_4444_SkColorType,
+                                   kRGBA_8888_SkColorType, kBGRA_8888_SkColorType,
+                                   kGray_8_SkColorType, kRGBA_F16_SkColorType
+        @param alphaType           one of: kUnknown_SkAlphaType, kOpaque_SkAlphaType,
+                                   kPremul_SkAlphaType, kUnpremul_SkAlphaType
+        @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 textureContext      state passed to textureFulfillProc and textureReleaseProc
+        @return                    created SkImage, or nullptr
+     */
+    sk_sp<SkImage> makePromiseTexture(const GrBackendFormat& backendFormat,
+                                      int width,
+                                      int height,
+                                      GrMipMapped mipMapped,
+                                      GrSurfaceOrigin origin,
+                                      SkColorType colorType,
+                                      SkAlphaType alphaType,
+                                      sk_sp<SkColorSpace> colorSpace,
+                                      TextureFulfillProc textureFulfillProc,
+                                      TextureReleaseProc textureReleaseProc,
+                                      TextureContext textureContext);
+
 private:
     bool init();
 
diff --git a/include/gpu/GrBackendSurface.h b/include/gpu/GrBackendSurface.h
index a2df90c..94ab39a 100644
--- a/include/gpu/GrBackendSurface.h
+++ b/include/gpu/GrBackendSurface.h
@@ -26,7 +26,7 @@
     }
 
 #ifdef SK_VULKAN
-    static GrBackendFormat MakeVK(VkFormat format) {
+    static GrBackendFormat MakeVk(VkFormat format) {
         return GrBackendFormat(format);
     }
 #endif
diff --git a/src/core/SkDeferredDisplayListRecorder.cpp b/src/core/SkDeferredDisplayListRecorder.cpp
index 96bb575..45992d8 100644
--- a/src/core/SkDeferredDisplayListRecorder.cpp
+++ b/src/core/SkDeferredDisplayListRecorder.cpp
@@ -7,6 +7,7 @@
 
 #include "SkDeferredDisplayListRecorder.h"
 
+
 #if SK_SUPPORT_GPU
 #include "GrContextPriv.h"
 #include "GrProxyProvider.h"
@@ -14,6 +15,7 @@
 
 #include "SkGpuDevice.h"
 #include "SkGr.h"
+#include "SkImage_Gpu.h"
 #include "SkSurface_Gpu.h"
 #endif
 
@@ -150,3 +152,32 @@
 
 }
 
+sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
+        const GrBackendFormat& backendFormat,
+        int width,
+        int height,
+        GrMipMapped mipMapped,
+        GrSurfaceOrigin origin,
+        SkColorType colorType,
+        SkAlphaType alphaType,
+        sk_sp<SkColorSpace> colorSpace,
+        TextureFulfillProc textureFulfillProc,
+        TextureReleaseProc textureReleaseProc,
+        TextureContext textureContext) {
+#if !defined(SK_RASTER_RECORDER_IMPLEMENTATION) && defined(SK_SUPPORT_GPU)
+    return SkImage_Gpu::MakePromiseTexture(fContext.get(),
+                                           backendFormat,
+                                           width,
+                                           height,
+                                           mipMapped,
+                                           origin,
+                                           colorType,
+                                           alphaType,
+                                           colorSpace,
+                                           textureFulfillProc,
+                                           textureReleaseProc,
+                                           textureContext);
+#else
+    return nullptr;
+#endif
+}
diff --git a/src/gpu/GrProxyProvider.cpp b/src/gpu/GrProxyProvider.cpp
index dbd49a7..7c82544 100644
--- a/src/gpu/GrProxyProvider.cpp
+++ b/src/gpu/GrProxyProvider.cpp
@@ -524,6 +524,20 @@
                                                        GrMipMapped mipMapped,
                                                        GrRenderTargetFlags renderTargetFlags,
                                                        SkBackingFit fit, SkBudgeted budgeted) {
+    // For non-ddl draws always make lazy proxy's single use.
+    LazyInstantiationType lazyType = fResourceProvider ? LazyInstantiationType::kSingleUse
+                                                       : LazyInstantiationType::kMultipleUse;
+    return this->createLazyProxy(std::move(callback), desc, origin, mipMapped, renderTargetFlags,
+                                 fit, budgeted, lazyType);
+}
+
+sk_sp<GrTextureProxy> GrProxyProvider::createLazyProxy(LazyInstantiateCallback&& callback,
+                                                       const GrSurfaceDesc& desc,
+                                                       GrSurfaceOrigin origin,
+                                                       GrMipMapped mipMapped,
+                                                       GrRenderTargetFlags renderTargetFlags,
+                                                       SkBackingFit fit, SkBudgeted budgeted,
+                                                       LazyInstantiationType lazyType) {
     SkASSERT((desc.fWidth <= 0 && desc.fHeight <= 0) ||
              (desc.fWidth > 0 && desc.fHeight > 0));
     uint32_t flags = GrResourceProvider::kNoPendingIO_Flag;
@@ -539,11 +553,6 @@
     }
 #endif
 
-    using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType;
-    // For non-ddl draws always make lazy proxy's single use.
-    LazyInstantiationType lazyType = fResourceProvider ? LazyInstantiationType::kSingleUse
-                                                       : LazyInstantiationType::kMultipleUse;
-
     return sk_sp<GrTextureProxy>(
             SkToBool(kRenderTarget_GrSurfaceFlag & desc.fFlags)
                     ? new GrTextureRenderTargetProxy(std::move(callback), lazyType, desc, origin,
diff --git a/src/gpu/GrProxyProvider.h b/src/gpu/GrProxyProvider.h
index 2f2eeb1..45a3c1a 100644
--- a/src/gpu/GrProxyProvider.h
+++ b/src/gpu/GrProxyProvider.h
@@ -155,6 +155,7 @@
         kYes = true
     };
 
+    using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType;
     /**
      * Creates a texture proxy that will be instantiated by a user-supplied callback during flush.
      * (Stencil is not supported by this method.) The width and height must either both be greater
@@ -167,6 +168,10 @@
      */
     sk_sp<GrTextureProxy> createLazyProxy(LazyInstantiateCallback&&, const GrSurfaceDesc&,
                                           GrSurfaceOrigin, GrMipMapped, GrRenderTargetFlags,
+                                          SkBackingFit, SkBudgeted, LazyInstantiationType);
+
+    sk_sp<GrTextureProxy> createLazyProxy(LazyInstantiateCallback&&, const GrSurfaceDesc&,
+                                          GrSurfaceOrigin, GrMipMapped, GrRenderTargetFlags,
                                           SkBackingFit, SkBudgeted);
 
     sk_sp<GrTextureProxy> createLazyProxy(LazyInstantiateCallback&&, const GrSurfaceDesc&,
diff --git a/src/gpu/GrSurfaceProxyPriv.h b/src/gpu/GrSurfaceProxyPriv.h
index 10203b7..00b3868 100644
--- a/src/gpu/GrSurfaceProxyPriv.h
+++ b/src/gpu/GrSurfaceProxyPriv.h
@@ -80,10 +80,6 @@
                GrSurfaceProxy::LazyInstantiationType::kUninstantiate == lazyInstantiationType();
     }
 
-    void testingOnly_setLazyInstantiationType(GrSurfaceProxy::LazyInstantiationType lazyType) {
-        fProxy->fLazyInstantiationType = lazyType;
-    }
-
     static bool AttachStencilIfNeeded(GrResourceProvider*, GrSurface*, bool needsStencil);
 
 private:
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 6ed3e15..d49835e 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -576,6 +576,148 @@
     return nullptr;
 }
 
+class PromiseImageHelper {
+public:
+    PromiseImageHelper(SkImage_Gpu::TextureFulfillProc fulFillProc,
+                       SkImage_Gpu::TextureReleaseProc releaseProc,
+                       SkImage_Gpu::TextureContext context)
+            : fFulfillProc(fulFillProc)
+            , fReleaseProc(releaseProc)
+            , fContext(context) {}
+
+    void reset() {
+        this->resetReleaseHelper();
+    }
+
+    sk_sp<GrTexture> getTexture(GrResourceProvider* resourceProvider) {
+        // 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);
+            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);
+            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 GrReleaseProcHelper(fReleaseProc, fContext);
+            // Take a weak ref
+            fReleaseHelper->weak_ref();
+        } else {
+            SkASSERT(fBackendTex.isValid());
+            tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership);
+            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 tex;
+    }
+
+private:
+    // Weak unrefs fReleaseHelper and sets it to null
+    void resetReleaseHelper() {
+        if (fReleaseHelper) {
+            fReleaseHelper->weak_unref();
+            fReleaseHelper = nullptr;
+        }
+    }
+
+    SkImage_Gpu::TextureFulfillProc fFulfillProc;
+    SkImage_Gpu::TextureReleaseProc fReleaseProc;
+    SkImage_Gpu::TextureContext     fContext;
+
+    // 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.
+    GrReleaseProcHelper* fReleaseHelper = nullptr;
+};
+
+sk_sp<SkImage> SkImage_Gpu::MakePromiseTexture(GrContext* context,
+                                               const GrBackendFormat& backendFormat,
+                                               int width,
+                                               int height,
+                                               GrMipMapped mipMapped,
+                                               GrSurfaceOrigin origin,
+                                               SkColorType colorType,
+                                               SkAlphaType alphaType,
+                                               sk_sp<SkColorSpace> colorSpace,
+                                               TextureFulfillProc textureFulfillProc,
+                                               TextureReleaseProc textureReleaseProc,
+                                               TextureContext textureContext) {
+    if (!context) {
+        return nullptr;
+    }
+
+    if (width <= 0 || height <= 0) {
+        return nullptr;
+    }
+
+    if (!textureFulfillProc || !textureReleaseProc) {
+        return nullptr;
+    }
+
+    SkImageInfo info = SkImageInfo::Make(width, height, colorType, alphaType, colorSpace);
+    if (!SkImageInfoIsValidAllowNumericalCS(info)) {
+        return nullptr;
+    }
+    GrPixelConfig config = kUnknown_GrPixelConfig;
+    if (!context->caps()->getConfigFromBackendFormat(backendFormat, colorType, &config)) {
+        return nullptr;
+    }
+
+    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
+
+    GrSurfaceDesc desc;
+    desc.fWidth = width;
+    desc.fHeight = height;
+    desc.fConfig = config;
+
+    PromiseImageHelper promiseHelper(textureFulfillProc, textureReleaseProc, textureContext);
+
+    sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy(
+            [promiseHelper] (GrResourceProvider* resourceProvider) mutable {
+                if (!resourceProvider) {
+                    promiseHelper.reset();
+                    return sk_sp<GrTexture>();
+                }
+
+                return promiseHelper.getTexture(resourceProvider);
+            }, desc, origin, mipMapped, GrRenderTargetFlags::kNone, SkBackingFit::kExact,
+               SkBudgeted::kNo, GrSurfaceProxy::LazyInstantiationType::kUninstantiate);
+
+    if (!proxy) {
+        return nullptr;
+    }
+
+    return sk_make_sp<SkImage_Gpu>(context, kNeedNewImageUniqueID, alphaType, std::move(proxy),
+                                   std::move(colorSpace), SkBudgeted::kNo);
+}
+
 sk_sp<SkImage> SkImage::MakeCrossContextFromEncoded(GrContext* context, sk_sp<SkData> encoded,
                                                     bool buildMips, SkColorSpace* dstColorSpace) {
     sk_sp<SkImage> codecImage = SkImage::MakeFromEncoded(std::move(encoded));
diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h
index 9cc28c3..24a8c78 100644
--- a/src/image/SkImage_Gpu.h
+++ b/src/image/SkImage_Gpu.h
@@ -60,6 +60,59 @@
     sk_sp<SkImage> onMakeColorSpace(sk_sp<SkColorSpace>, SkColorType,
                                     SkTransferFunctionBehavior) const override;
 
+    typedef ReleaseContext TextureContext;
+    typedef void (*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture);
+
+    /**
+        Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The main
+        difference is that the client doesn't have the backend texture on the gpu yet but they know
+        all the properties of the texture. So instead of passing in a GrBackendTexture the client
+        supplies a GrBackendFormat, width, height, and GrMipMapped state.
+
+        When we actually send the draw calls to the GPU, we will call the textureFulfillProc and
+        the client will return a GrBackendTexture to us. The properties of the GrBackendTexture must
+        match those set during the SkImage creation, and it must have a valid backend gpu texture.
+        The gpu texture supplied by the client must stay valid until we call the textureReleaseProc.
+
+        When we are done with the texture returned by the textureFulfillProc we will call the
+        textureReleaseProc passing in the textureContext. This is a signal to the client that they
+        are free to delete the underlying gpu texture. If future draws also use the same promise
+        image we will call the textureFulfillProc again if we've already called the
+        textureReleaseProc. We will always call textureFulfillProc and textureReleaseProc in pairs.
+        In other words we will never call textureFulfillProc or textureReleaseProc multiple times
+        for the same textureContext before calling the other.
+
+        @param context             Gpu context
+        @param backendFormat       format of promised gpu texture
+        @param width               width of promised gpu texture
+        @param height              height of promised gpu texture
+        @param mipMapped           mip mapped state of promised gpu texture
+        @param origin              one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin
+        @param colorType           one of: kUnknown_SkColorType, kAlpha_8_SkColorType,
+                                   kRGB_565_SkColorType, kARGB_4444_SkColorType,
+                                   kRGBA_8888_SkColorType, kBGRA_8888_SkColorType,
+                                   kGray_8_SkColorType, kRGBA_F16_SkColorType
+        @param alphaType           one of: kUnknown_SkAlphaType, kOpaque_SkAlphaType,
+                                   kPremul_SkAlphaType, kUnpremul_SkAlphaType
+        @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 textureContext      state passed to textureFulfillProc and textureReleaseProc
+        @return                    created SkImage, or nullptr
+     */
+    static sk_sp<SkImage> MakePromiseTexture(GrContext* context,
+                                             const GrBackendFormat& backendFormat,
+                                             int width,
+                                             int height,
+                                             GrMipMapped mipMapped,
+                                             GrSurfaceOrigin origin,
+                                             SkColorType colorType,
+                                             SkAlphaType alphaType,
+                                             sk_sp<SkColorSpace> colorSpace,
+                                             TextureFulfillProc textureFulfillProc,
+                                             TextureReleaseProc textureReleaseProc,
+                                             TextureContext textureContext);
+
     bool onIsValid(GrContext*) const override;
 
 private:
diff --git a/tests/DeferredDisplayListTest.cpp b/tests/DeferredDisplayListTest.cpp
index b0c80c3..6700ba5 100644
--- a/tests/DeferredDisplayListTest.cpp
+++ b/tests/DeferredDisplayListTest.cpp
@@ -42,9 +42,9 @@
         if (kRGBA_8888_SkColorType == colorType) {
             VkFormat format =  caps->srgbSupport() ? VK_FORMAT_R8G8B8A8_SRGB
                                                    : VK_FORMAT_R8G8B8A8_UNORM;
-            return GrBackendFormat::MakeVK(format);
+            return GrBackendFormat::MakeVk(format);
         } else if (kRGBA_F16_SkColorType == colorType) {
-            return GrBackendFormat::MakeVK(VK_FORMAT_R16G16B16A16_SFLOAT);
+            return GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_SFLOAT);
         }
         break;
 #endif
diff --git a/tests/LazyProxyTest.cpp b/tests/LazyProxyTest.cpp
index 88cbd01..b7c4a9c 100644
--- a/tests/LazyProxyTest.cpp
+++ b/tests/LazyProxyTest.cpp
@@ -217,7 +217,8 @@
     using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType;
     for (bool doInstantiate : {true, false}) {
         for (auto lazyType : {LazyInstantiationType::kSingleUse,
-                              LazyInstantiationType::kMultipleUse}) {
+                              LazyInstantiationType::kMultipleUse,
+                              LazyInstantiationType::kUninstantiate}) {
             int testCount = 0;
             int* testCountPtr = &testCount;
             sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy(
@@ -229,10 +230,8 @@
                         *testCountPtr = 1;
                         return sk_sp<GrTexture>();
                     },
-                    desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact,
-                    SkBudgeted::kNo);
-
-            proxy->priv().testingOnly_setLazyInstantiationType(lazyType);
+                    desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, GrRenderTargetFlags::kNone,
+                    SkBackingFit::kExact, SkBudgeted::kNo, lazyType);
 
             REPORTER_ASSERT(reporter, 0 == testCount);
 
@@ -423,10 +422,8 @@
                     texture->setRelease(UninstantiateReleaseProc, releasePtr);
                     return texture;
                 },
-                desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact,
-                SkBudgeted::kNo);
-
-        lazyProxy->priv().testingOnly_setLazyInstantiationType(lazyType);
+                desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, GrRenderTargetFlags::kNone,
+                SkBackingFit::kExact, SkBudgeted::kNo, lazyType);
 
         rtc->priv().testingOnly_addDrawOp(skstd::make_unique<LazyUninstantiateTestOp>(lazyProxy));
 
diff --git a/tests/PromiseImageTest.cpp b/tests/PromiseImageTest.cpp
new file mode 100644
index 0000000..b475147
--- /dev/null
+++ b/tests/PromiseImageTest.cpp
@@ -0,0 +1,203 @@
+/*
+ * 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 "Test.h"
+
+#if SK_SUPPORT_GPU
+
+#include "GrBackendSurface.h"
+#include "GrContextPriv.h"
+#include "GrGpu.h"
+#include "GrTest.h"
+#include "SkDeferredDisplayListRecorder.h"
+#include "SkImage_Gpu.h"
+
+using namespace sk_gpu_test;
+
+struct PromiseTextureChecker {
+    explicit PromiseTextureChecker(const GrBackendTexture& tex)
+            : fTexture(tex)
+            , fFulfillCount(0)
+            , fReleaseCount(0) {}
+    GrBackendTexture fTexture;
+    int fFulfillCount;
+    int fReleaseCount;
+    static void Fulfill(void* self, GrBackendTexture* outTexture) {
+        static_cast<PromiseTextureChecker*>(self)->fFulfillCount++;
+        *outTexture = static_cast<PromiseTextureChecker*>(self)->fTexture;
+    }
+    static void Release(void* self) {
+        static_cast<PromiseTextureChecker*>(self)->fReleaseCount++;
+    }
+};
+
+// Because Vulkan may delay when it actually calls the ReleaseProcs depending on when command
+// buffers finish their work, we need some slight wiggle room in what values we expect for fulfill
+// and release counts.
+static bool check_fulfill_and_release_cnts(const PromiseTextureChecker& promiseChecker,
+                                           bool countsMustBeEqual,
+                                           int expectedFulfillCnt,
+                                           int expectedReleaseCnt,
+                                           bool expectedRequired,
+                                           skiatest::Reporter* reporter) {
+    bool result = true;
+    int countDiff = promiseChecker.fFulfillCount - promiseChecker.fReleaseCount;
+    // FulfillCount should always equal ReleaseCount or be at most one higher
+    if (countDiff != 0) {
+        if (countsMustBeEqual) {
+            result = false;
+            REPORTER_ASSERT(reporter, 0 == countDiff);
+        } else if (countDiff != 1) {
+            result = false;
+            REPORTER_ASSERT(reporter, 0 == countDiff || 1 == countDiff);
+        }
+    }
+
+    int fulfillDiff = expectedFulfillCnt - promiseChecker.fFulfillCount;
+    REPORTER_ASSERT(reporter, fulfillDiff >= 0);
+    if (fulfillDiff != 0) {
+        if (expectedRequired) {
+            result = false;
+            REPORTER_ASSERT(reporter, expectedFulfillCnt == promiseChecker.fFulfillCount);
+        } else if (fulfillDiff > 1) {
+            result = false;
+            REPORTER_ASSERT(reporter, fulfillDiff <= 1);
+        }
+    }
+
+    int releaseDiff = expectedReleaseCnt - promiseChecker.fReleaseCount;
+    REPORTER_ASSERT(reporter, releaseDiff >= 0);
+    if (releaseDiff != 0) {
+        if (expectedRequired) {
+            result = false;
+            REPORTER_ASSERT(reporter, expectedReleaseCnt == promiseChecker.fReleaseCount);
+        } else if (releaseDiff > 1) {
+            result = false;
+            REPORTER_ASSERT(reporter, releaseDiff <= 1);
+        }
+    }
+
+    return result;
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTest, reporter, ctxInfo) {
+    const int kWidth = 10;
+    const int kHeight = 10;
+    std::unique_ptr<uint32_t[]> pixels(new uint32_t[kWidth * kHeight]);
+
+    GrContext* ctx = ctxInfo.grContext();
+    GrGpu* gpu = ctx->contextPriv().getGpu();
+
+    GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture(
+               pixels.get(), kWidth, kHeight, kRGBA_8888_GrPixelConfig, true, GrMipMapped::kNo);
+    REPORTER_ASSERT(reporter, backendTex.isValid());
+
+    GrBackendFormat backendFormat = GrTest::CreateBackendFormatFromTexture(backendTex);
+    REPORTER_ASSERT(reporter, backendFormat.isValid());
+
+    PromiseTextureChecker promiseChecker(backendTex);
+    GrSurfaceOrigin texOrigin = kTopLeft_GrSurfaceOrigin;
+    sk_sp<SkImage> refImg(
+        SkImage_Gpu::MakePromiseTexture(ctx, backendFormat, kWidth, kHeight, GrMipMapped::kNo,
+                                        texOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
+                                        nullptr,
+                                        PromiseTextureChecker::Fulfill,
+                                        PromiseTextureChecker::Release,
+                                        &promiseChecker));
+
+    SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight);
+    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info);
+    SkCanvas* canvas = surface->getCanvas();
+
+    int expectedFulfillCnt = 0;
+    int expectedReleaseCnt = 0;
+
+    canvas->drawImage(refImg, 0, 0);
+    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
+                                                             true,
+                                                             expectedFulfillCnt,
+                                                             expectedReleaseCnt,
+                                                             true,
+                                                             reporter));
+
+    bool isVulkan = kVulkan_GrBackend == ctx->contextPriv().getBackend();
+    canvas->flush();
+    expectedFulfillCnt++;
+    expectedReleaseCnt++;
+    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
+                                                             !isVulkan,
+                                                             expectedFulfillCnt,
+                                                             expectedReleaseCnt,
+                                                             !isVulkan,
+                                                             reporter));
+
+    gpu->testingOnly_flushGpuAndSync();
+    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
+                                                             true,
+                                                             expectedFulfillCnt,
+                                                             expectedReleaseCnt,
+                                                             true,
+                                                             reporter));
+
+    canvas->drawImage(refImg, 0, 0);
+    canvas->drawImage(refImg, 0, 0);
+
+    canvas->flush();
+    expectedFulfillCnt++;
+    expectedReleaseCnt++;
+
+    gpu->testingOnly_flushGpuAndSync();
+    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
+                                                             true,
+                                                             expectedFulfillCnt,
+                                                             expectedReleaseCnt,
+                                                             true,
+                                                             reporter));
+
+    // Now test code path on Vulkan where we released the texture, but the GPU isn't done with
+    // resource yet and we do another draw. We should only call fulfill on the first draw and
+    // use the cached GrBackendTexture on the second. Release should only be called after the second
+    // draw is finished.
+    canvas->drawImage(refImg, 0, 0);
+    canvas->flush();
+    expectedFulfillCnt++;
+    expectedReleaseCnt++;
+    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
+                                                             !isVulkan,
+                                                             expectedFulfillCnt,
+                                                             expectedReleaseCnt,
+                                                             !isVulkan,
+                                                             reporter));
+
+    canvas->drawImage(refImg, 0, 0);
+    canvas->flush();
+    expectedFulfillCnt++;
+
+    gpu->testingOnly_flushGpuAndSync();
+    expectedReleaseCnt++;
+    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
+                                                             true,
+                                                             expectedFulfillCnt,
+                                                             expectedReleaseCnt,
+                                                             !isVulkan,
+                                                             reporter));
+    expectedFulfillCnt = promiseChecker.fFulfillCount;
+    expectedReleaseCnt = promiseChecker.fReleaseCount;
+
+    refImg.reset();
+
+    REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
+                                                             true,
+                                                             expectedFulfillCnt,
+                                                             expectedReleaseCnt,
+                                                             true,
+                                                             reporter));
+
+    gpu->deleteTestingOnlyBackendTexture(&backendTex);
+}
+
+#endif
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index 2160c7f..ae86aae 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -78,6 +78,30 @@
     }
 }
 
+GrBackendFormat CreateBackendFormatFromTexture(const GrBackendTexture& tex) {
+    switch (tex.backend()) {
+#ifdef SK_VULKAN
+        case kVulkan_GrBackend: {
+            const GrVkImageInfo* vkInfo = tex.getVkImageInfo();
+            SkASSERT(vkInfo);
+            return GrBackendFormat::MakeVk(vkInfo->fFormat);
+        }
+#endif
+        case kOpenGL_GrBackend: {
+            const GrGLTextureInfo* glInfo = tex.getGLTextureInfo();
+            SkASSERT(glInfo);
+            return GrBackendFormat::MakeGL(glInfo->fFormat, glInfo->fTarget);
+        }
+        case kMock_GrBackend: {
+            const GrMockTextureInfo* mockInfo = tex.getMockTextureInfo();
+            SkASSERT(mockInfo);
+            return GrBackendFormat::MakeMock(mockInfo->fConfig);
+        }
+        default:
+            return GrBackendFormat();
+    }
+}
+
 }  // namespace GrTest
 
 bool GrSurfaceProxy::isWrapped_ForTesting() const {
diff --git a/tools/gpu/GrTest.h b/tools/gpu/GrTest.h
index 6666ab1..76e5e93 100644
--- a/tools/gpu/GrTest.h
+++ b/tools/gpu/GrTest.h
@@ -22,6 +22,8 @@
     // TODO: remove this. It is only used in the SurfaceSemaphores Test.
     GrBackendTexture CreateBackendTexture(GrBackend, int width, int height,
                                           GrPixelConfig, GrMipMapped, GrBackendObject);
+
+    GrBackendFormat CreateBackendFormatFromTexture(const GrBackendTexture& tex);
 };
 
 #endif