Add GrExternalTextureData and SkCrossContextImageData

GrExternalTextureData is an API for exporting the backend-specific
information about a texture in a type-safe way, and without pointing
into the GrTexture. The new detachBackendTexture API lets us release
ownership of a texture to the client.

SkCrossContextImageData is the public API that lets clients upload
textures on one thread/GrContext, then safely transfer ownership to
another thread and GrContext for rendering.

Only GL is implemented/supported right now. Vulkan support requires
that we add thread-safe memory pools, or otherwise transfer the
actual memory block containing the texture to the new context.

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

BUG=skia:

Change-Id: I48ebd57d1ea0cfd3a1db10c475f2903afb821966
Reviewed-on: https://skia-review.googlesource.com/8960
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/tests/CrossContextImageTest.cpp b/tests/CrossContextImageTest.cpp
new file mode 100644
index 0000000..e37b095
--- /dev/null
+++ b/tests/CrossContextImageTest.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+
+#if SK_SUPPORT_GPU
+
+#include "GrContextFactory.h"
+#include "Resources.h"
+#include "SkAutoPixmapStorage.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkCrossContextImageData.h"
+#include "SkSemaphore.h"
+#include "SkSurface.h"
+#include "SkThreadUtils.h"
+#include "Test.h"
+
+using namespace sk_gpu_test;
+
+static SkImageInfo read_pixels_info(SkImage* image) {
+    return SkImageInfo::MakeN32(image->width(), image->height(), image->alphaType());
+}
+
+static bool colors_are_close(SkColor a, SkColor b, int error) {
+    return SkTAbs((int)SkColorGetR(a) - (int)SkColorGetR(b)) <= error &&
+           SkTAbs((int)SkColorGetG(a) - (int)SkColorGetG(b)) <= error &&
+           SkTAbs((int)SkColorGetB(a) - (int)SkColorGetB(b)) <= error;
+}
+
+static void assert_equal(skiatest::Reporter* reporter, SkImage* a, SkImage* b, int error) {
+    REPORTER_ASSERT(reporter, a->width() == b->width());
+    REPORTER_ASSERT(reporter, a->height() == b->height());
+
+    SkAutoPixmapStorage pmapA, pmapB;
+    pmapA.alloc(read_pixels_info(a));
+    pmapB.alloc(read_pixels_info(b));
+
+    REPORTER_ASSERT(reporter, a->readPixels(pmapA, 0, 0));
+    REPORTER_ASSERT(reporter, b->readPixels(pmapB, 0, 0));
+
+    for (int y = 0; y < a->height(); ++y) {
+        for (int x = 0; x < a->width(); ++x) {
+            SkColor ca = pmapA.getColor(x, y);
+            SkColor cb = pmapB.getColor(x, y);
+            if (!error) {
+                if (ca != cb) {
+                    ERRORF(reporter, "Expected 0x%08x but got 0x%08x at (%d, %d)", ca, cb, x, y);
+                    return;
+                }
+            } else {
+                if (!colors_are_close(ca, cb, error)) {
+                    ERRORF(reporter, "Expected 0x%08x +-%d but got 0x%08x at (%d, %d)",
+                           ca, error, cb, x, y);
+                    return;
+                }
+            }
+        }
+    }
+}
+
+static void draw_image_test_pattern(SkCanvas* canvas) {
+    canvas->clear(SK_ColorWHITE);
+    SkPaint paint;
+    paint.setColor(SK_ColorBLACK);
+    canvas->drawRect(SkRect::MakeXYWH(5, 5, 10, 10), paint);
+}
+
+static sk_sp<SkImage> create_test_image() {
+    SkBitmap bm;
+    bm.allocN32Pixels(20, 20, true);
+    SkCanvas canvas(bm);
+    draw_image_test_pattern(&canvas);
+
+    return SkImage::MakeFromBitmap(bm);
+}
+
+static sk_sp<SkData> create_test_data(SkEncodedImageFormat format) {
+    auto image = create_test_image();
+    return sk_sp<SkData>(image->encode(format, 100));
+}
+
+DEF_GPUTEST(CrossContextImage_SameContext, reporter, /*factory*/) {
+    GrContextFactory factory;
+    sk_sp<SkImage> testImage = create_test_image();
+
+    // Test both PNG and JPG, to exercise GPU YUV conversion
+    for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) {
+        sk_sp<SkData> encoded = create_test_data(format);
+
+        for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) {
+            GrContextFactory::ContextType ctxType = static_cast<GrContextFactory::ContextType>(i);
+            if (!sk_gpu_test::GrContextFactory::IsRenderingContext(ctxType)) {
+                continue;
+            }
+
+            ContextInfo info = factory.getContextInfo(ctxType);
+            if (!info.grContext()) {
+                continue;
+            }
+
+            auto ccid = SkCrossContextImageData::MakeFromEncoded(info.grContext(), encoded,
+                                                                 nullptr);
+            REPORTER_ASSERT(reporter, ccid != nullptr);
+
+            auto image = SkImage::MakeFromCrossContextImageData(info.grContext(), std::move(ccid));
+            REPORTER_ASSERT(reporter, image != nullptr);
+
+            // JPEG encode -> decode won't round trip the image perfectly
+            assert_equal(reporter, testImage.get(), image.get(),
+                         SkEncodedImageFormat::kJPEG == format ? 2 : 0);
+        }
+    }
+}
+
+DEF_GPUTEST(CrossContextImage_SharedContextSameThread, reporter, /*factory*/) {
+    GrContextFactory factory;
+    sk_sp<SkImage> testImage = create_test_image();
+
+    // Test both PNG and JPG, to exercise GPU YUV conversion
+    for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) {
+        sk_sp<SkData> encoded = create_test_data(format);
+
+        for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) {
+            GrContextFactory::ContextType ctxType = static_cast<GrContextFactory::ContextType>(i);
+            if (!sk_gpu_test::GrContextFactory::IsRenderingContext(ctxType)) {
+                continue;
+            }
+
+            ContextInfo info = factory.getContextInfo(ctxType);
+            if (!info.grContext()) {
+                continue;
+            }
+            auto ccid = SkCrossContextImageData::MakeFromEncoded(info.grContext(), encoded,
+                                                                 nullptr);
+            REPORTER_ASSERT(reporter, ccid != nullptr);
+
+            ContextInfo info2 = factory.getSharedContextInfo(info.grContext());
+            auto image = SkImage::MakeFromCrossContextImageData(info2.grContext(), std::move(ccid));
+            REPORTER_ASSERT(reporter, image != nullptr);
+
+            // JPEG encode -> decode won't round trip the image perfectly
+            assert_equal(reporter, testImage.get(), image.get(),
+                         SkEncodedImageFormat::kJPEG == format ? 2 : 0);
+        }
+    }
+}
+
+namespace {
+struct CrossContextImage_ThreadContext {
+    GrContext* fGrContext;
+    sk_gpu_test::TestContext* fTestContext;
+    SkSemaphore fSemaphore;
+    std::unique_ptr<SkCrossContextImageData> fCCID;
+    sk_sp<SkData> fEncoded;
+};
+}
+
+static void upload_image_thread_proc(void* data) {
+    CrossContextImage_ThreadContext* ctx = static_cast<CrossContextImage_ThreadContext*>(data);
+    ctx->fTestContext->makeCurrent();
+    ctx->fCCID = SkCrossContextImageData::MakeFromEncoded(ctx->fGrContext, ctx->fEncoded, nullptr);
+    ctx->fSemaphore.signal();
+}
+
+DEF_GPUTEST(CrossContextImage_SharedContextOtherThread, reporter, /*factory*/) {
+    sk_sp<SkImage> testImage = create_test_image();
+
+    // Test both PNG and JPG, to exercise GPU YUV conversion
+    for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) {
+        // Use a new factory for each batch of tests. Otherwise the shared context will still be
+        // current on the upload thread when we do the second iteration, and we get undefined
+        // behavior.
+        GrContextFactory factory;
+        sk_sp<SkData> encoded = create_test_data(format);
+
+        for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) {
+            GrContextFactory::ContextType ctxType = static_cast<GrContextFactory::ContextType>(i);
+            if (!sk_gpu_test::GrContextFactory::IsRenderingContext(ctxType)) {
+                continue;
+            }
+
+            // Create two GrContexts in a share group
+            ContextInfo info = factory.getContextInfo(ctxType);
+            if (!info.grContext()) {
+                continue;
+            }
+            ContextInfo info2 = factory.getSharedContextInfo(info.grContext());
+            if (!info2.grContext()) {
+                continue;
+            }
+
+            // Make the first one current (on this thread) again
+            info.testContext()->makeCurrent();
+
+            // Bundle up data for the worker thread
+            CrossContextImage_ThreadContext ctx;
+            ctx.fGrContext = info2.grContext();
+            ctx.fTestContext = info2.testContext();
+            ctx.fEncoded = encoded;
+
+            SkThread uploadThread(upload_image_thread_proc, &ctx);
+            SkAssertResult(uploadThread.start());
+
+            ctx.fSemaphore.wait();
+            auto image = SkImage::MakeFromCrossContextImageData(info.grContext(),
+                                                                std::move(ctx.fCCID));
+            REPORTER_ASSERT(reporter, image != nullptr);
+
+            // JPEG encode -> decode won't round trip the image perfectly
+            assert_equal(reporter, testImage.get(), image.get(),
+                         SkEncodedImageFormat::kJPEG == format ? 2 : 0);
+
+            uploadThread.join();
+        }
+    }
+}
+
+#endif