Added SkImage::MakeCrossContextFromEncoded

Designed for Flutter's threading architecture, with
an eye to being useful to other clients. Under the
hood, uses a new image generator class to lazily wrap
a texture for multiple GrContexts.

Re-land of https://skia-review.googlesource.com/c/14180/

Bug: skia:
Change-Id: I3dd382640629b79b3058f18fee68d043566e43e5
Reviewed-on: https://skia-review.googlesource.com/15895
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/tests/ImageTest.cpp b/tests/ImageTest.cpp
index 2c4ba8f..c747b8e 100644
--- a/tests/ImageTest.cpp
+++ b/tests/ImageTest.cpp
@@ -34,6 +34,7 @@
 #if SK_SUPPORT_GPU
 #include "GrContextPriv.h"
 #include "GrGpu.h"
+#include "GrResourceCache.h"
 #include "GrTest.h"
 #endif
 
@@ -806,6 +807,7 @@
         static_cast<TextureReleaseChecker*>(self)->fReleaseCount++;
     }
 };
+
 DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(SkImage_NewFromTextureRelease, reporter, ctxInfo) {
     const int kWidth = 10;
     const int kHeight = 10;
@@ -854,6 +856,135 @@
     ctxInfo.grContext()->getGpu()->deleteTestingOnlyBackendTexture(backendTexHandle);
 }
 
+DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(SkImage_MakeCrossContextRelease, reporter, ctxInfo) {
+    GrContext* ctx = ctxInfo.grContext();
+
+    // If we don't have proper support for this feature, the factory will fallback to returning
+    // codec-backed images. Those will "work", but some of our checks will fail because we expect
+    // the cross-context images not to work on multiple contexts at once.
+    if (!ctx->caps()->crossContextTextureSupport()) {
+        return;
+    }
+
+    // We test three lifetime patterns for a single context:
+    // 1) Create image, free image
+    // 2) Create image, draw, flush, free image
+    // 3) Create image, draw, free image, flush
+    // ... and then repeat the last two patterns with drawing on a second* context:
+    // 4) Create image, draw*, flush*, free image
+    // 5) Create image, draw*, free iamge, flush*
+
+    sk_sp<SkData> data = GetResourceAsData("mandrill_128.png");
+    SkASSERT(data.get());
+
+    // Case #1: Create image, free image
+    {
+        sk_sp<SkImage> refImg(SkImage::MakeCrossContextFromEncoded(ctx, data, false, nullptr));
+        refImg.reset(nullptr); // force a release of the image
+    }
+
+    SkImageInfo info = SkImageInfo::MakeN32Premul(128, 128);
+    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info);
+    SkCanvas* canvas = surface->getCanvas();
+
+    // Case #2: Create image, draw, flush, free image
+    {
+        sk_sp<SkImage> refImg(SkImage::MakeCrossContextFromEncoded(ctx, data, false, nullptr));
+
+        canvas->drawImage(refImg, 0, 0);
+        canvas->flush();
+
+        refImg.reset(nullptr); // force a release of the image
+    }
+
+    // Case #3: Create image, draw, free image, flush
+    {
+        sk_sp<SkImage> refImg(SkImage::MakeCrossContextFromEncoded(ctx, data, false, nullptr));
+
+        canvas->drawImage(refImg, 0, 0);
+        refImg.reset(nullptr); // force a release of the image
+
+        canvas->flush();
+    }
+
+    // Configure second context
+    sk_gpu_test::TestContext* testContext = ctxInfo.testContext();
+
+    GrContextFactory otherFactory;
+    ContextInfo otherContextInfo = otherFactory.getContextInfo(pick_second_context_type(ctxInfo));
+    GrContext* otherCtx = otherContextInfo.grContext();
+    sk_gpu_test::TestContext* otherTestContext = otherContextInfo.testContext();
+
+    surface = SkSurface::MakeRenderTarget(otherCtx, SkBudgeted::kNo, info);
+    canvas = surface->getCanvas();
+
+    // Case #4: Create image, draw*, flush*, free image
+    {
+        testContext->makeCurrent();
+        sk_sp<SkImage> refImg(SkImage::MakeCrossContextFromEncoded(ctx, data, false, nullptr));
+
+        otherTestContext->makeCurrent();
+        canvas->drawImage(refImg, 0, 0);
+        canvas->flush();
+
+        testContext->makeCurrent();
+        refImg.reset(nullptr); // force a release of the image
+    }
+
+    // Case #5: Create image, draw*, free image, flush*
+    {
+        testContext->makeCurrent();
+        sk_sp<SkImage> refImg(SkImage::MakeCrossContextFromEncoded(ctx, data, false, nullptr));
+
+        otherTestContext->makeCurrent();
+        canvas->drawImage(refImg, 0, 0);
+
+        testContext->makeCurrent();
+        refImg.reset(nullptr); // force a release of the image
+
+        otherTestContext->makeCurrent();
+        canvas->flush();
+    }
+
+    // Case #6: Verify that only one context can be using the image at a time
+    {
+        testContext->makeCurrent();
+        sk_sp<SkImage> refImg(SkImage::MakeCrossContextFromEncoded(ctx, data, false, nullptr));
+
+        // Any context should be able to borrow the texture at this point
+        sk_sp<SkColorSpace> texColorSpace;
+        sk_sp<GrTextureProxy> proxy = as_IB(refImg)->asTextureProxyRef(
+            ctx, GrSamplerParams::ClampNoFilter(), nullptr, &texColorSpace, nullptr);
+        REPORTER_ASSERT(reporter, proxy);
+
+        // But once it's borrowed, no other context should be able to borrow
+        otherTestContext->makeCurrent();
+        sk_sp<GrTextureProxy> otherProxy = as_IB(refImg)->asTextureProxyRef(
+            otherCtx, GrSamplerParams::ClampNoFilter(), nullptr, &texColorSpace, nullptr);
+        REPORTER_ASSERT(reporter, !otherProxy);
+
+        // Original context (that's already borrowing) should be okay
+        testContext->makeCurrent();
+        sk_sp<GrTextureProxy> proxySecondRef = as_IB(refImg)->asTextureProxyRef(
+            ctx, GrSamplerParams::ClampNoFilter(), nullptr, &texColorSpace, nullptr);
+        REPORTER_ASSERT(reporter, proxySecondRef);
+
+        // Releae all refs from the original context
+        proxy.reset(nullptr);
+        proxySecondRef.reset(nullptr);
+
+        // Now we should be able to borrow the texture from the other context
+        otherTestContext->makeCurrent();
+        otherProxy = as_IB(refImg)->asTextureProxyRef(
+            otherCtx, GrSamplerParams::ClampNoFilter(), nullptr, &texColorSpace, nullptr);
+        REPORTER_ASSERT(reporter, otherProxy);
+
+        // Release everything
+        otherProxy.reset(nullptr);
+        refImg.reset(nullptr);
+    }
+}
+
 static void check_images_same(skiatest::Reporter* reporter, const SkImage* a, const SkImage* b) {
     if (a->width() != b->width() || a->height() != b->height()) {
         ERRORF(reporter, "Images must have the same size");