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.

BUG=skia:

Change-Id: I784a3a74be69807df038c7d192eaed002c7e45ca
Reviewed-on: https://skia-review.googlesource.com/8529
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/gn/core.gni b/gn/core.gni
index b6ad6a9..7dd997e 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -390,6 +390,7 @@
   "$_include/core/SkColor.h",
   "$_include/core/SkColorFilter.h",
   "$_include/core/SkColorPriv.h",
+  "$_include/core/SkCrossContextImageData.h",
   "$_include/core/SkData.h",
   "$_include/core/SkDeque.h",
   "$_include/core/SkDrawable.h",
diff --git a/gn/gpu.gni b/gn/gpu.gni
index ae05f2c..f3e479d 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -18,6 +18,7 @@
   "$_include/gpu/GrContextOptions.h",
   "$_include/gpu/GrContext.h",
   "$_include/gpu/GrCoordTransform.h",
+  "$_include/gpu/GrExternalTextureData.h",
   "$_include/gpu/GrFragmentProcessor.h",
   "$_include/gpu/GrGpuResource.h",
   "$_include/gpu/GrPaint.h",
diff --git a/gn/tests.gni b/gn/tests.gni
index 6646f3d..f8c95d7 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -44,6 +44,7 @@
   "$_tests/ColorTest.cpp",
   "$_tests/CopySurfaceTest.cpp",
   "$_tests/CPlusPlusEleven.cpp",
+  "$_tests/CrossContextImageTest.cpp",
   "$_tests/CTest.cpp",
   "$_tests/DashPathEffectTest.cpp",
   "$_tests/DataRefTest.cpp",
diff --git a/include/core/SkCrossContextImageData.h b/include/core/SkCrossContextImageData.h
new file mode 100644
index 0000000..bbee6e7
--- /dev/null
+++ b/include/core/SkCrossContextImageData.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkCrossContextImageData_DEFINED
+#define SkCrossContextImageData_DEFINED
+
+#include "SkImage.h"
+
+#if SK_SUPPORT_GPU
+#include "GrExternalTextureData.h"
+#endif
+
+class SK_API SkCrossContextImageData : SkNoncopyable {
+public:
+    /**
+     *  Decodes and uploads the encoded data to a texture using the supplied GrContext, then
+     *  returns an instance of SkCrossContextImageData that can be used to transport that texture
+     *  to a different GrContext, across thread boundaries. The GrContext used here, and the one
+     *  used to reconstruct the texture-backed image later must be in the same GL share group,
+     *  or otherwise be able to share resources. After calling this, you *must* construct exactly
+     *  one SkImage from the returned value, using SkImage::MakeFromCrossContextImageData.
+     *
+     *  The texture will be decoded and uploaded to be suitable for use with surfaces that have the
+     *  supplied destination color space. The color space of the texture itself will be determined
+     *  from the encoded data.
+     */
+    static std::unique_ptr<SkCrossContextImageData> MakeFromEncoded(
+        GrContext*, sk_sp<SkData>, SkColorSpace* dstColorSpace);
+
+private:
+    SkCrossContextImageData(sk_sp<SkImage> image) : fImage(std::move(image)) {
+        SkASSERT(!fImage->isTextureBacked());
+    }
+
+#if SK_SUPPORT_GPU
+    SkCrossContextImageData(const GrBackendTextureDesc& desc,
+                            std::unique_ptr<GrExternalTextureData> textureData,
+                            SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace)
+            : fAlphaType(alphaType)
+            , fColorSpace(std::move(colorSpace))
+            , fDesc(desc)
+            , fTextureData(std::move(textureData)) {
+        // Point our texture desc at our copy of the backend information
+        fDesc.fTextureHandle = fTextureData->getBackendObject();
+    }
+#endif
+
+    // For non-GPU backed images
+    sk_sp<SkImage> fImage;
+
+#if SK_SUPPORT_GPU
+    // GPU-backed images store some generic information (needed to reconstruct the SkImage),
+    // and some backend-specific info (to reconstruct the texture).
+    SkAlphaType fAlphaType;
+    sk_sp<SkColorSpace> fColorSpace;
+    GrBackendTextureDesc fDesc;
+    std::unique_ptr<GrExternalTextureData> fTextureData;
+#endif
+
+    friend class SkImage;
+};
+
+#endif
diff --git a/include/core/SkImage.h b/include/core/SkImage.h
index cbd1b29..93d8564 100644
--- a/include/core/SkImage.h
+++ b/include/core/SkImage.h
@@ -18,6 +18,7 @@
 class SkData;
 class SkCanvas;
 class SkColorTable;
+class SkCrossContextImageData;
 class SkImageGenerator;
 class SkPaint;
 class SkPicture;
@@ -324,6 +325,14 @@
     sk_sp<SkImage> makeTextureImage(GrContext*, SkColorSpace* dstColorSpace) const;
 
     /**
+     *  Constructs a texture backed image from data that was previously uploaded on another thread
+     *  and GrContext. The GrContext used to upload the data must be in the same GL share group as
+     *  the one passed in here, or otherwise be able to share resources with the passed in context.
+     */
+    static sk_sp<SkImage> MakeFromCrossContextImageData(GrContext*,
+                                                        std::unique_ptr<SkCrossContextImageData>);
+
+    /**
      * If the image is texture-backed this will make a raster copy of it (or nullptr if reading back
      * the pixels fails). Otherwise, it returns the original image.
      */
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index f268b88..2c24050 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -325,6 +325,12 @@
     void prepareSurfaceForExternalIO(GrSurface*);
 
     /**
+     * As above, but additionally flushes the backend API (eg calls glFlush), and returns a fence
+     * that can be used to determine if the surface is safe to use on another context or thread.
+     */
+    GrFence SK_WARN_UNUSED_RESULT prepareSurfaceForExternalIOAndFlush(GrSurface*);
+
+    /**
      * An ID associated with this context, guaranteed to be unique.
      */
     uint32_t uniqueID() { return fUniqueID; }
diff --git a/include/gpu/GrExternalTextureData.h b/include/gpu/GrExternalTextureData.h
new file mode 100644
index 0000000..9ab819c
--- /dev/null
+++ b/include/gpu/GrExternalTextureData.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef GrExternalTextureData_DEFINED
+#define GrExternalTextureData_DEFINED
+
+#include "GrTypes.h"
+#include "GrTypesPriv.h"
+
+class SK_API GrExternalTextureData : SkNoncopyable {
+public:
+    GrExternalTextureData(GrFence fence) : fFence(fence) {}
+    virtual ~GrExternalTextureData() {}
+    virtual GrBackend getBackend() const = 0;
+    GrFence getFence() const { return fFence; }
+
+protected:
+    virtual GrBackendObject getBackendObject() const = 0;
+
+    GrFence fFence;
+
+    friend class SkCrossContextImageData;
+};
+
+#endif
diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h
index cc7e7aa..e0a7903 100644
--- a/include/gpu/GrGpuResource.h
+++ b/include/gpu/GrGpuResource.h
@@ -260,6 +260,11 @@
     // final class).
     void registerWithCacheWrapped();
 
+    // This is only called by resources that are being exported from Ganesh to client code. It
+    // ensures that the cache can no longer reach this resource, and that it no longer counts
+    // against the budget.
+    void detachFromCache();
+
     GrGpuResource(GrGpu*);
     virtual ~GrGpuResource();
 
diff --git a/include/gpu/GrTexture.h b/include/gpu/GrTexture.h
index c032cbe..1f63958 100644
--- a/include/gpu/GrTexture.h
+++ b/include/gpu/GrTexture.h
@@ -14,6 +14,7 @@
 #include "SkPoint.h"
 #include "SkRefCnt.h"
 
+class GrExternalTextureData;
 class GrTexturePriv;
 
 class GrTexture : virtual public GrSurface {
@@ -49,6 +50,7 @@
               GrSamplerParams::FilterMode highestFilterMode, bool wasMipMapDataProvided);
 
     void validateDesc() const;
+    virtual std::unique_ptr<GrExternalTextureData> detachBackendTexture() = 0;
 
 private:
     void computeScratchKey(GrScratchKey*) const override;
diff --git a/include/gpu/gl/GrGLTypes.h b/include/gpu/gl/GrGLTypes.h
index 5b9e31d..d03363c 100644
--- a/include/gpu/gl/GrGLTypes.h
+++ b/include/gpu/gl/GrGLTypes.h
@@ -9,6 +9,7 @@
 #ifndef GrGLTypes_DEFINED
 #define GrGLTypes_DEFINED
 
+#include "GrExternalTextureData.h"
 #include "GrGLConfig.h"
 
 /**
@@ -112,6 +113,23 @@
     GrGLuint fID;
 };
 
+class GrGLExternalTextureData : public GrExternalTextureData {
+public:
+    GrGLExternalTextureData(const GrGLTextureInfo& info, GrFence fence)
+            : INHERITED(fence)
+            , fInfo(info) {}
+    GrBackend getBackend() const override { return kOpenGL_GrBackend; }
+
+protected:
+    GrBackendObject getBackendObject() const override {
+        return reinterpret_cast<GrBackendObject>(&fInfo);
+    }
+
+    GrGLTextureInfo fInfo;
+
+    typedef GrExternalTextureData INHERITED;
+};
+
 GR_STATIC_ASSERT(sizeof(GrBackendObject) >= sizeof(const GrGLTextureInfo*));
 
 #endif
diff --git a/include/gpu/vk/GrVkTypes.h b/include/gpu/vk/GrVkTypes.h
index aa1334a..e9a3121 100644
--- a/include/gpu/vk/GrVkTypes.h
+++ b/include/gpu/vk/GrVkTypes.h
@@ -9,6 +9,7 @@
 #ifndef GrVkTypes_DEFINED
 #define GrVkTypes_DEFINED
 
+#include "GrExternalTextureData.h"
 #include "GrTypes.h"
 #include "vk/GrVkDefines.h"
 
@@ -59,6 +60,23 @@
     void updateImageLayout(VkImageLayout layout) { fImageLayout = layout; }
 };
 
+class GrVkExternalTextureData : public GrExternalTextureData {
+public:
+    GrVkExternalTextureData(const GrVkImageInfo& info, GrFence fence)
+            : INHERITED(fence)
+            , fInfo(info) {}
+    GrBackend getBackend() const override { return kVulkan_GrBackend; }
+
+protected:
+    GrBackendObject getBackendObject() const override {
+        return reinterpret_cast<GrBackendObject>(&fInfo);
+    }
+
+    GrVkImageInfo fInfo;
+
+    typedef GrExternalTextureData INHERITED;
+};
+
 GR_STATIC_ASSERT(sizeof(GrBackendObject) >= sizeof(const GrVkImageInfo*));
 
 #endif
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 4b1324e..e816687 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -545,6 +545,13 @@
     fDrawingManager->prepareSurfaceForExternalIO(surface);
 }
 
+GrFence GrContext::prepareSurfaceForExternalIOAndFlush(GrSurface* surface) {
+    this->prepareSurfaceForExternalIO(surface);
+    GrFence fence = fGpu->insertFence();
+    fGpu->flush();
+    return fence;
+}
+
 void GrContext::flushSurfaceWrites(GrSurface* surface) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 0aed3d2..f7108106 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -382,6 +382,10 @@
     virtual bool waitFence(GrFence, uint64_t timeout = 1000) const = 0;
     virtual void deleteFence(GrFence) const = 0;
 
+    // Ensures that all queued up driver-level commands have been sent to the GPU. For example, on
+    // OpenGL, this calls glFlush.
+    virtual void flush() = 0;
+
     ///////////////////////////////////////////////////////////////////////////
     // Debugging and Stats
 
diff --git a/src/gpu/GrGpuResource.cpp b/src/gpu/GrGpuResource.cpp
index c3a9556..dcc4e62 100644
--- a/src/gpu/GrGpuResource.cpp
+++ b/src/gpu/GrGpuResource.cpp
@@ -43,6 +43,17 @@
     get_resource_cache(fGpu)->resourceAccess().insertResource(this);
 }
 
+void GrGpuResource::detachFromCache() {
+    if (this->wasDestroyed()) {
+        return;
+    }
+    if (fUniqueKey.isValid()) {
+        this->removeUniqueKey();
+    }
+    this->removeScratchKey();
+    this->makeUnbudgeted();
+}
+
 GrGpuResource::~GrGpuResource() {
     // The cache should have released or destroyed this resource.
     SkASSERT(this->wasDestroyed());
diff --git a/src/gpu/GrTexturePriv.h b/src/gpu/GrTexturePriv.h
index 0420611..cc0a05e 100644
--- a/src/gpu/GrTexturePriv.h
+++ b/src/gpu/GrTexturePriv.h
@@ -8,6 +8,7 @@
 #ifndef GrTexturePriv_DEFINED
 #define GrTexturePriv_DEFINED
 
+#include "GrExternalTextureData.h"
 #include "GrTexture.h"
 
 /** Class that adds methods to GrTexture that are only intended for use internal to Skia.
@@ -67,6 +68,15 @@
     }
     SkDestinationSurfaceColorMode mipColorMode() const { return fTexture->fMipColorMode; }
 
+    /**
+     *  Return the native bookkeeping data for this texture, and detach the backend object from
+     *  this GrTexture. It's lifetime will no longer be managed by Ganesh, and this GrTexture will
+     *  no longer refer to it. Leaves this GrTexture in an orphan state.
+     */
+    std::unique_ptr<GrExternalTextureData> detachBackendTexture() {
+        return fTexture->detachBackendTexture();
+    }
+
     static void ComputeScratchKey(const GrSurfaceDesc&, GrScratchKey*);
 
 private:
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index ad00b0a..5dce7d1 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -4741,3 +4741,7 @@
 void GrGLGpu::deleteFence(GrFence fence) const {
     GL_CALL(DeleteSync((GrGLsync)fence));
 }
+
+void GrGLGpu::flush() {
+    GL_CALL(Flush());
+}
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index fdc2ebb..b6ca4f6 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -150,6 +150,8 @@
     bool waitFence(GrFence, uint64_t timeout) const override;
     void deleteFence(GrFence) const override;
 
+    void flush() override;
+
 private:
     GrGLGpu(GrGLContext* ctx, GrContext* context);
 
diff --git a/src/gpu/gl/GrGLTexture.cpp b/src/gpu/gl/GrGLTexture.cpp
index edce7b1..a560988 100644
--- a/src/gpu/gl/GrGLTexture.cpp
+++ b/src/gpu/gl/GrGLTexture.cpp
@@ -5,9 +5,11 @@
  * found in the LICENSE file.
  */
 
+#include "GrContext.h"
 #include "GrGLTexture.h"
 #include "GrGLGpu.h"
 #include "GrShaderCaps.h"
+#include "SkMakeUnique.h"
 #include "SkTraceMemoryDump.h"
 
 #define GPUGL static_cast<GrGLGpu*>(this->getGpu())
@@ -111,6 +113,23 @@
     return reinterpret_cast<GrBackendObject>(&fInfo);
 }
 
+std::unique_ptr<GrExternalTextureData> GrGLTexture::detachBackendTexture() {
+    // Flush any pending writes to this texture, as well GL itself
+    GrFence fence = this->getContext()->prepareSurfaceForExternalIOAndFlush(this);
+
+    // Make a copy of our GL-specific information
+    auto data = skstd::make_unique<GrGLExternalTextureData>(fInfo, fence);
+
+    // Ensure the cache can't reach this texture anymore
+    this->detachFromCache();
+
+    // Detach from the GL object, so we don't use it (or try to delete it when we're freed)
+    fInfo.fTarget = 0;
+    fInfo.fID = 0;
+
+    return std::move(data);
+}
+
 void GrGLTexture::setMemoryBacking(SkTraceMemoryDump* traceMemoryDump,
                                    const SkString& dumpName) const {
     SkString texture_id;
diff --git a/src/gpu/gl/GrGLTexture.h b/src/gpu/gl/GrGLTexture.h
index 029fd87..16b47f1 100644
--- a/src/gpu/gl/GrGLTexture.h
+++ b/src/gpu/gl/GrGLTexture.h
@@ -71,6 +71,7 @@
     void onRelease() override;
     void setMemoryBacking(SkTraceMemoryDump* traceMemoryDump,
                           const SkString& dumpName) const override;
+    std::unique_ptr<GrExternalTextureData> detachBackendTexture() override;
 
 private:
     TexParams                       fTexParams;
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index 915ac97..8569c93 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -1860,3 +1860,6 @@
     GR_VK_CALL(this->vkInterface(), DestroyFence(this->device(), (VkFence)fence, nullptr));
 }
 
+void GrVkGpu::flush() {
+    // We submit the command buffer to the queue whenever Ganesh is flushed, so nothing is needed
+}
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index 18059b9..c935945 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -133,6 +133,8 @@
     bool waitFence(GrFence, uint64_t timeout) const override;
     void deleteFence(GrFence) const override;
 
+    void flush() override;
+
     void generateMipmap(GrVkTexture* tex);
 
     bool updateBuffer(GrVkBuffer* buffer, const void* src, VkDeviceSize offset, VkDeviceSize size);
diff --git a/src/gpu/vk/GrVkTexture.cpp b/src/gpu/vk/GrVkTexture.cpp
index 7f0cf8d..a8f1bf0 100644
--- a/src/gpu/vk/GrVkTexture.cpp
+++ b/src/gpu/vk/GrVkTexture.cpp
@@ -144,6 +144,12 @@
     return (GrBackendObject)&fInfo;
 }
 
+std::unique_ptr<GrExternalTextureData> GrVkTexture::detachBackendTexture() {
+    // Not supported on Vulkan yet
+    // TODO: Add thread-safe memory pools, and implement this.
+    return nullptr;
+}
+
 GrVkGpu* GrVkTexture::getVkGpu() const {
     SkASSERT(!this->wasDestroyed());
     return static_cast<GrVkGpu*>(this->getGpu());
diff --git a/src/gpu/vk/GrVkTexture.h b/src/gpu/vk/GrVkTexture.h
index 70db548..db7124e 100644
--- a/src/gpu/vk/GrVkTexture.h
+++ b/src/gpu/vk/GrVkTexture.h
@@ -42,6 +42,7 @@
 
     void onAbandon() override;
     void onRelease() override;
+    std::unique_ptr<GrExternalTextureData> detachBackendTexture() override;
 
 private:
     enum Wrapped { kWrapped };
diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp
index 48d9ad3..638b0c2 100644
--- a/src/image/SkImage.cpp
+++ b/src/image/SkImage.cpp
@@ -8,6 +8,7 @@
 #include "SkBitmap.h"
 #include "SkBitmapCache.h"
 #include "SkCanvas.h"
+#include "SkCrossContextImageData.h"
 #include "SkData.h"
 #include "SkImageEncoder.h"
 #include "SkImageFilter.h"
@@ -357,6 +358,21 @@
     return nullptr;
 }
 
+std::unique_ptr<SkCrossContextImageData> SkCrossContextImageData::MakeFromEncoded(
+        GrContext*, sk_sp<SkData> encoded, SkColorSpace* dstColorSpace) {
+    sk_sp<SkImage> image = SkImage::MakeFromEncoded(std::move(encoded));
+    if (!image) {
+        return nullptr;
+    }
+    // TODO: Force decode to raster here?
+    return std::unique_ptr<SkCrossContextImageData>(new SkCrossContextImageData(std::move(image)));
+}
+
+sk_sp<SkImage> SkImage::MakeFromCrossContextImageData(
+        GrContext*, std::unique_ptr<SkCrossContextImageData> ccid) {
+    return ccid->fImage;
+}
+
 sk_sp<SkImage> SkImage::makeNonTextureImage() const {
     return sk_ref_sp(const_cast<SkImage*>(this));
 }
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 05caf86..ddb62d7 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -14,6 +14,7 @@
 #include "GrCaps.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
+#include "GrGpu.h"
 #include "GrImageTextureMaker.h"
 #include "GrRenderTargetContext.h"
 #include "GrTextureAdjuster.h"
@@ -21,6 +22,7 @@
 #include "GrTextureProxy.h"
 #include "effects/GrYUVEffect.h"
 #include "SkCanvas.h"
+#include "SkCrossContextImageData.h"
 #include "SkBitmapCache.h"
 #include "SkGrPriv.h"
 #include "SkImage_Gpu.h"
@@ -357,7 +359,7 @@
                                    std::move(texColorSpace), SkBudgeted::kNo);
 }
 
-sk_sp<SkImage> SkImage::makeTextureImage(GrContext *context, SkColorSpace* dstColorSpace) const {
+sk_sp<SkImage> SkImage::makeTextureImage(GrContext* context, SkColorSpace* dstColorSpace) const {
     if (!context) {
         return nullptr;
     }
@@ -377,6 +379,70 @@
     return nullptr;
 }
 
+std::unique_ptr<SkCrossContextImageData> SkCrossContextImageData::MakeFromEncoded(
+        GrContext* context, sk_sp<SkData> encoded, SkColorSpace* dstColorSpace) {
+    sk_sp<SkImage> codecImage = SkImage::MakeFromEncoded(std::move(encoded));
+    if (!codecImage) {
+        return nullptr;
+    }
+
+    // If we don't have the ability to use fences, we can't safely transfer a texture between
+    // threads, so just hand off the codec image
+    if (!context->caps()->fenceSyncSupport()) {
+        return std::unique_ptr<SkCrossContextImageData>(
+            new SkCrossContextImageData(std::move(codecImage)));
+    }
+
+    sk_sp<SkImage> textureImage = codecImage->makeTextureImage(context, dstColorSpace);
+    if (!textureImage) {
+        // TODO: Force decode to raster here? Do mip-mapping, like getDeferredTextureImageData?
+        return std::unique_ptr<SkCrossContextImageData>(
+            new SkCrossContextImageData(std::move(codecImage)));
+    }
+
+    // Crack open the gpu image, extract the backend data, stick it in the SkCCID
+    GrTexture* texture = as_IB(textureImage)->peekTexture();
+    SkASSERT(texture);
+
+    GrBackendTextureDesc desc;
+    desc.fFlags = kNone_GrBackendTextureFlag;
+    desc.fOrigin = texture->origin();
+    desc.fWidth = texture->width();
+    desc.fHeight = texture->height();
+    desc.fConfig = texture->config();
+    desc.fSampleCnt = 0;
+
+    auto textureData = texture->texturePriv().detachBackendTexture();
+    if (!textureData) {
+        // Handles backends that don't support this feature (currently Vulkan). Do a raster decode
+        // here?
+        return std::unique_ptr<SkCrossContextImageData>(
+            new SkCrossContextImageData(std::move(codecImage)));
+    }
+
+    SkImageInfo info = as_IB(textureImage)->onImageInfo();
+    return std::unique_ptr<SkCrossContextImageData>(new SkCrossContextImageData(
+        desc, std::move(textureData), info.alphaType(), info.refColorSpace()));
+}
+
+sk_sp<SkImage> SkImage::MakeFromCrossContextImageData(
+        GrContext* context, std::unique_ptr<SkCrossContextImageData> ccid) {
+    if (ccid->fImage) {
+        // No pre-existing GPU resource. We could upload it now (with makeTextureImage),
+        // but we'd need a dstColorSpace.
+        return ccid->fImage;
+    }
+
+    if (ccid->fTextureData) {
+        GrFence fence = ccid->fTextureData->getFence();
+        context->getGpu()->waitFence(fence);
+        context->getGpu()->deleteFence(fence);
+    }
+
+    return MakeFromAdoptedTexture(context, ccid->fDesc, ccid->fAlphaType,
+                                  std::move(ccid->fColorSpace));
+}
+
 sk_sp<SkImage> SkImage::makeNonTextureImage() const {
     if (!this->isTextureBacked()) {
         return sk_ref_sp(const_cast<SkImage*>(this));
diff --git a/tests/CrossContextImageTest.cpp b/tests/CrossContextImageTest.cpp
new file mode 100644
index 0000000..ceb414d
--- /dev/null
+++ b/tests/CrossContextImageTest.cpp
@@ -0,0 +1,218 @@
+/*
+ * 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*/) {
+    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;
+            }
+
+            // 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);
+        }
+    }
+}
+
+#endif
diff --git a/tools/gpu/GrContextFactory.cpp b/tools/gpu/GrContextFactory.cpp
index 637c569..8cfa3f2d 100644
--- a/tools/gpu/GrContextFactory.cpp
+++ b/tools/gpu/GrContextFactory.cpp
@@ -273,4 +273,16 @@
     return ContextInfo(context.fBackend, context.fTestContext, context.fGrContext);
 }
 
+ContextInfo GrContextFactory::getSharedContextInfo(GrContext* shareContext, uint32_t shareIndex) {
+    SkASSERT(shareContext);
+    for (int i = 0; i < fContexts.count(); ++i) {
+        if (!fContexts[i].fAbandoned && fContexts[i].fGrContext == shareContext) {
+            return this->getContextInfo(fContexts[i].fType, fContexts[i].fOverrides,
+                                        shareContext, shareIndex);
+        }
+    }
+
+    return ContextInfo();
+}
+
 }  // namespace sk_gpu_test
diff --git a/tools/gpu/GrContextFactory.h b/tools/gpu/GrContextFactory.h
index e42f34c..c26729d 100644
--- a/tools/gpu/GrContextFactory.h
+++ b/tools/gpu/GrContextFactory.h
@@ -152,6 +152,11 @@
                                ContextOverrides overrides = ContextOverrides::kNone,
                                GrContext* shareContext = nullptr, uint32_t shareIndex = 0);
     /**
+     * Get a context in the same share group as the passed in GrContext, with the same type and
+     * overrides.
+     */
+    ContextInfo getSharedContextInfo(GrContext* shareContext, uint32_t shareIndex = 0);
+    /**
      * Get a GrContext initialized with a type of GL context. It also makes the GL context current.
      */
     GrContext* get(ContextType type, ContextOverrides overrides = ContextOverrides::kNone) {
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index a206339..204119c 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -313,6 +313,7 @@
     GrFence SK_WARN_UNUSED_RESULT insertFence() const override { return 0; }
     bool waitFence(GrFence, uint64_t) const override { return true; }
     void deleteFence(GrFence) const override {}
+    void flush() override {}
 
 private:
     void onResetContext(uint32_t resetBits) override {}
diff --git a/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp b/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp
index af35b7b..49d7743 100644
--- a/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp
+++ b/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp
@@ -85,7 +85,11 @@
         kGLES_GrGLStandard == forcedGpuAPI ?
         kGLES_SkWGLContextRequest : kGLPreferCompatibilityProfile_SkWGLContextRequest;
 
-    HGLRC winShareContext = shareContext ? shareContext->fGlRenderContext : nullptr;
+    HGLRC winShareContext = nullptr;
+    if (shareContext) {
+        winShareContext = shareContext->fPbufferContext ? shareContext->fPbufferContext->getGLRC()
+                                                        : shareContext->fGlRenderContext;
+    }
     fPbufferContext = SkWGLPbufferContext::Create(fDeviceContext, 0, contextType, winShareContext);
 
     HDC dc;