Add lazy proxy's for wrapping backend textures

Bug: skia:
Change-Id: I3bb557cefc35312adc9515b5683d2ed747bb4eb3
Reviewed-on: https://skia-review.googlesource.com/96862
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/include/core/SkImage.h b/include/core/SkImage.h
index 6b63076..deb0313 100644
--- a/include/core/SkImage.h
+++ b/include/core/SkImage.h
@@ -206,6 +206,10 @@
      *
      *  Will return NULL if the specified backend texture is unsupported.
      *
+     *  This is not supported if the GrContext was obtained from a deferred display list canvas
+     *  since if the SkImage never gets used and flushed to a real GrContext, we have no way to be
+     *  able to delete the underlying backend texture.
+     *
      *  DEPRECATED: This factory is deprecated and clients should use the factory below which takes
      *  an SkColorType.
      */
@@ -224,6 +228,10 @@
      *  interpret the backend format supplied by the GrBackendTexture. If the format in the
      *  GrBackendTexture is not compitable with the SkColorType, SkAlphaType, and SkColorSpace we
      *  will return nullptr.
+     *
+     *  This is not supported if the GrContext was obtained from a deferred display list canvas
+     *  since if the SkImage never gets used and flushed to a real GrContext, we have no way to be
+     *  able to delete the underlying backend texture.
      */
     static sk_sp<SkImage> MakeFromAdoptedTexture(GrContext* context,
                                                  const GrBackendTexture& backendTexture,
diff --git a/include/gpu/GrBackendSurface.h b/include/gpu/GrBackendSurface.h
index 4cc6da3..ffe422d 100644
--- a/include/gpu/GrBackendSurface.h
+++ b/include/gpu/GrBackendSurface.h
@@ -86,6 +86,7 @@
     friend class SkImage;
     friend class SkSurface;
     friend class GrBackendTextureImageGenerator;
+    friend class GrProxyProvider;
     friend class GrGpu;
     friend class GrGLGpu;
     friend class GrVkGpu;
diff --git a/src/gpu/GrBackendTextureImageGenerator.cpp b/src/gpu/GrBackendTextureImageGenerator.cpp
index b07115e..064727e 100644
--- a/src/gpu/GrBackendTextureImageGenerator.cpp
+++ b/src/gpu/GrBackendTextureImageGenerator.cpp
@@ -114,15 +114,14 @@
 
     // Must make copies of member variables to capture in the lambda since this image generator may
     // be deleted before we actuallly execute the lambda.
-    GrSurfaceOrigin surfaceOrigin = fSurfaceOrigin;
     sk_sp<GrSemaphore> semaphore = fSemaphore;
     GrBackendTexture backendTexture = fBackendTexture;
     RefHelper* refHelper = fRefHelper;
     refHelper->ref();
 
     sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy(
-            [refHelper, semaphore, backendTexture, surfaceOrigin]
-            (GrResourceProvider* resourceProvider, GrSurfaceOrigin* outOrigin) {
+            [refHelper, semaphore, backendTexture]
+            (GrResourceProvider* resourceProvider, GrSurfaceOrigin* /*outOrigin*/) {
                 if (!resourceProvider) {
                     // If we get here then we never created a texture to pass the refHelper ref off
                     // to. Thus we must unref it ourselves.
@@ -168,7 +167,6 @@
                     tex->setRelease(ReleaseRefHelper_TextureReleaseProc, refHelper);
                 }
 
-                *outOrigin = surfaceOrigin;
                 return tex;
 
             }, desc, mipMapped, SkBackingFit::kExact, SkBudgeted::kNo);
diff --git a/src/gpu/GrProxyProvider.cpp b/src/gpu/GrProxyProvider.cpp
index c49f6b8..1267750 100644
--- a/src/gpu/GrProxyProvider.cpp
+++ b/src/gpu/GrProxyProvider.cpp
@@ -330,17 +330,46 @@
         return nullptr;
     }
 
-    sk_sp<GrTexture> texture(fResourceProvider->wrapBackendTexture(backendTex, ownership));
-    if (!texture) {
-        return nullptr;
-    }
-    if (releaseProc) {
-        texture->setRelease(releaseProc, releaseCtx);
-    }
+    GrSurfaceDesc desc;
+    desc.fOrigin = origin;
+    desc.fWidth = backendTex.width();
+    desc.fHeight = backendTex.height();
+    desc.fConfig = backendTex.config();
+    GrMipMapped mipMapped = backendTex.hasMipMaps() ? GrMipMapped::kYes : GrMipMapped::kNo;
 
-    SkASSERT(!texture->asRenderTarget());   // Strictly a GrTexture
+    sk_sp<GrTextureProxy> proxy = this->createLazyProxy(
+            [backendTex, ownership, releaseProc, releaseCtx]
+            (GrResourceProvider* resourceProvider, GrSurfaceOrigin* /*outOrigin*/) {
+                if (!resourceProvider) {
+                    // This lazy proxy was never initialized. If it has a releaseProc we must call
+                    // it now so that the client knows they can free the underlying backend object.
+                    if (releaseProc) {
+                        releaseProc(releaseCtx);
+                    }
+                    return sk_sp<GrTexture>();
+                }
 
-    return this->createWrapped(std::move(texture), origin);
+                sk_sp<GrTexture> tex = resourceProvider->wrapBackendTexture(backendTex,
+                                                                            ownership);
+                if (!tex) {
+                    return sk_sp<GrTexture>();
+                }
+                if (releaseProc) {
+                    tex->setRelease(releaseProc, releaseCtx);
+                }
+                SkASSERT(!tex->asRenderTarget());   // Strictly a GrTexture
+                // Make sure we match how we created the proxy with SkBudgeted::kNo
+                SkASSERT(SkBudgeted::kNo == tex->resourcePriv().isBudgeted());
+
+                return tex;
+            }, desc, mipMapped, SkBackingFit::kExact, SkBudgeted::kNo);
+
+    if (fResourceProvider) {
+        // In order to reuse code we always create a lazy proxy. When we aren't in DDL mode however,
+        // we're better off instantiating the proxy immediately here.
+        proxy->priv().doLazyInstantiation(fResourceProvider);
+    }
+    return proxy;
 }
 
 sk_sp<GrTextureProxy> GrProxyProvider::createWrappedTextureProxy(const GrBackendTexture& tex,
diff --git a/src/gpu/GrSurfaceProxy.cpp b/src/gpu/GrSurfaceProxy.cpp
index 9dadaca..08e4dd3 100644
--- a/src/gpu/GrSurfaceProxy.cpp
+++ b/src/gpu/GrSurfaceProxy.cpp
@@ -351,7 +351,19 @@
     SkASSERT(fProxy->fLazyInstantiateCallback);
     SkASSERT(!fProxy->fTarget);
 
-    sk_sp<GrTexture> texture = fProxy->fLazyInstantiateCallback(resourceProvider, &fProxy->fOrigin);
+    GrSurfaceOrigin* outOrigin;
+    if (GrSurfaceProxy::LazyState::kPartially == fProxy->lazyInstantiationState()) {
+        // In the partially instantiated case, we set the origin on the SurfaceProxy at creation
+        // time (via a GrSurfaceDesc). In the lambda, the creation of the texture never needs to
+        // know the origin, and it also can't change or have any effect on it. Thus we just pass in
+        // nullptr in this case since it should never be set.
+        outOrigin = nullptr;
+    } else {
+        outOrigin = &fProxy->fOrigin;
+    }
+
+    sk_sp<GrTexture> texture = fProxy->fLazyInstantiateCallback(resourceProvider, outOrigin);
+
 
     // Indicate we are no longer pending lazy instantiation.
     fProxy->fLazyInstantiateCallback = nullptr;
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index fe4c2b9..c00c50d 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -156,6 +156,22 @@
                                                 GrSurfaceOrigin* origin) const {
     SkASSERT(fProxy);
 
+    if (!fContext->contextPriv().resourceProvider() && !fProxy->priv().isInstantiated()) {
+        // This image was created with a DDL context and cannot be instantiated. Thus we return 0
+        // here which is considered invalid for all backends.
+        return 0;
+    }
+
+    if (GrSurfaceProxy::LazyState::kNot != fProxy->lazyInstantiationState()) {
+        SkASSERT(fContext->contextPriv().resourceProvider());
+        fProxy->priv().doLazyInstantiation(fContext->contextPriv().resourceProvider());
+        if (!fProxy->priv().isInstantiated()) {
+            // We failed to instantiate the lazy proxy. Thus we return 0 here which is considered
+            // invalid for all backends.
+            return 0;
+        }
+    }
+
     if (!fProxy->instantiate(fContext->contextPriv().resourceProvider())) {
         return 0;
     }
@@ -335,6 +351,10 @@
 sk_sp<SkImage> SkImage::MakeFromAdoptedTexture(GrContext* ctx,
                                                const GrBackendTexture& tex, GrSurfaceOrigin origin,
                                                SkAlphaType at, sk_sp<SkColorSpace> cs) {
+    if (!ctx->contextPriv().resourceProvider()) {
+        // We have a DDL context and we don't support adopted textures for them.
+        return nullptr;
+    }
     return new_wrapped_texture_common(ctx, tex, origin, at, std::move(cs), kAdopt_GrWrapOwnership,
                                       nullptr, nullptr);
 }
diff --git a/tests/DeferredDisplayListTest.cpp b/tests/DeferredDisplayListTest.cpp
index 1d99d00..8ee24be 100644
--- a/tests/DeferredDisplayListTest.cpp
+++ b/tests/DeferredDisplayListTest.cpp
@@ -9,6 +9,8 @@
 
 #if SK_SUPPORT_GPU
 
+#include "GrBackendSurface.h"
+#include "GrGpu.h"
 #include "SkCanvas.h"
 #include "SkDeferredDisplayListRecorder.h"
 #include "SkGpuDevice.h"
@@ -178,4 +180,160 @@
     }
 }
 
+static constexpr int kSize = 8;
+
+struct TextureReleaseChecker {
+    TextureReleaseChecker() : fReleaseCount(0) {}
+    int fReleaseCount;
+    static void Release(void* self) {
+        static_cast<TextureReleaseChecker*>(self)->fReleaseCount++;
+    }
+};
+
+enum class DDLStage { kMakeImage, kDrawImage, kDetach, kDrawDDL };
+
+// This tests the ability to create and use wrapped textures in a DDL world
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DDLWrapBackendTest, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+    GrGpu* gpu = context->contextPriv().getGpu();
+    for (auto lastStage : { DDLStage::kMakeImage, DDLStage::kDrawImage,
+                            DDLStage::kDetach, DDLStage::kDrawDDL } ) {
+        for (auto earlyImageReset : { false , true } ) {
+            GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture(
+                    nullptr, kSize, kSize, kRGBA_8888_GrPixelConfig, false, GrMipMapped::kNo);
+            if (!backendTex.isValid()) {
+                continue;
+            }
+
+            SurfaceParameters params;
+
+            sk_sp<SkSurface> s = params.make(context);
+            if (!s) {
+                gpu->deleteTestingOnlyBackendTexture(&backendTex);
+                continue;
+            }
+
+            SkSurfaceCharacterization c;
+            SkAssertResult(s->characterize(&c));
+
+            std::unique_ptr<SkDeferredDisplayListRecorder> recorder(
+                    new SkDeferredDisplayListRecorder(c));
+
+            SkCanvas* canvas = recorder->getCanvas();
+            if (!canvas) {
+                gpu->deleteTestingOnlyBackendTexture(&backendTex);
+                continue;
+            }
+
+            GrContext* deferredContext = canvas->getGrContext();
+            if (!deferredContext) {
+                gpu->deleteTestingOnlyBackendTexture(&backendTex);
+                continue;
+            }
+
+            sk_sp<SkImage> image = SkImage::MakeFromAdoptedTexture(deferredContext, backendTex,
+                                                                   kTopLeft_GrSurfaceOrigin,
+                                                                   kRGBA_8888_SkColorType,
+                                                                   kPremul_SkAlphaType, nullptr);
+            // Adopted Textures are not supported in DDL
+            REPORTER_ASSERT(reporter, !image);
+
+            TextureReleaseChecker releaseChecker;
+            image = SkImage::MakeFromTexture(deferredContext, backendTex,
+                                             kTopLeft_GrSurfaceOrigin,
+                                             kRGBA_8888_SkColorType,
+                                             kPremul_SkAlphaType, nullptr,
+                                             TextureReleaseChecker::Release, &releaseChecker);
+
+            REPORTER_ASSERT(reporter, image);
+            if (!image) {
+                gpu->deleteTestingOnlyBackendTexture(&backendTex);
+                continue;
+            }
+
+            if (DDLStage::kMakeImage == lastStage) {
+                REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+                image.reset();
+                REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount);
+                recorder.reset();
+                REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount);
+                gpu->deleteTestingOnlyBackendTexture(&backendTex);
+                continue;
+            }
+
+            canvas->drawImage(image.get(), 0, 0);
+
+            if (earlyImageReset) {
+                REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+                image.reset();
+                // Ref should still be held by DDL recorder since we did the draw
+                REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+            }
+
+            if (DDLStage::kDrawImage == lastStage) {
+                REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+                recorder.reset();
+                if (earlyImageReset) {
+                    REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount);
+                } else {
+                    REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+                    image.reset();
+                    REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount);
+                }
+                gpu->deleteTestingOnlyBackendTexture(&backendTex);
+                continue;
+            }
+
+            std::unique_ptr<SkDeferredDisplayList> ddl = recorder->detach();
+            if (DDLStage::kDetach == lastStage) {
+                REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+                recorder.reset();
+                // DDL TODO: Once copies of OpLists from the recorder to DDL are implemented we can
+                // uncomment this check. Currently the texture is getting reset when the recorder
+                // goes away (assuming we did an earlyImageReset).
+                // REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+                ddl.reset();
+                if (earlyImageReset) {
+                    REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount);
+                } else {
+                    REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+                    image.reset();
+                    REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount);
+                }
+                gpu->deleteTestingOnlyBackendTexture(&backendTex);
+                continue;
+            }
+
+            REPORTER_ASSERT(reporter, s->draw(ddl.get()));
+
+            REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+            recorder.reset();
+            // DDL TODO: Once copies of OpLists from the recorder to DDL are implemented we can
+            // uncomment these checks. Currently the texture is getting released when the recorder
+            // goes away (assuming we did an earlyImageReset).
+            // REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+            ddl.reset();
+            // REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+
+            // Force all draws to flush and sync by calling a read pixels
+            SkImageInfo imageInfo = SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType,
+                                                      kPremul_SkAlphaType);
+            SkBitmap bitmap;
+            bitmap.allocPixels(imageInfo);
+            s->readPixels(imageInfo, bitmap.getPixels(), 0, 0, 0);
+
+            if (earlyImageReset) {
+                REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount);
+            } else {
+                REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
+                image.reset();
+                REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount);
+            }
+
+            gpu->deleteTestingOnlyBackendTexture(&backendTex);
+        }
+    }
+}
+
+
 #endif