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/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index 1efea17..ade64d6 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -53,6 +53,7 @@
     fMustClearUploadedBufferData = false;
     fSampleShadingSupport = false;
     fFenceSyncSupport = false;
+    fCrossContextTextureSupport = false;
 
     fUseDrawInsteadOfClear = false;
 
@@ -141,6 +142,7 @@
     r.appendf("Must clear buffer memory           : %s\n", gNY[fMustClearUploadedBufferData]);
     r.appendf("Sample shading support             : %s\n", gNY[fSampleShadingSupport]);
     r.appendf("Fence sync support                 : %s\n", gNY[fFenceSyncSupport]);
+    r.appendf("Cross context texture support      : %s\n", gNY[fCrossContextTextureSupport]);
 
     r.appendf("Draw Instead of Clear [workaround] : %s\n", gNY[fUseDrawInsteadOfClear]);
     r.appendf("Draw Instead of TexSubImage [workaround] : %s\n",
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..f710810 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/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index ddf2252..0bcb7e8 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -603,6 +603,15 @@
         fFenceSyncSupport = true;
     }
 
+    // Safely moving textures between contexts requires fences. The Windows Intel driver has a
+    // bug with deleting and reusing texture IDs across contexts, so disallow this feature.
+    fCrossContextTextureSupport = fFenceSyncSupport;
+#ifdef SK_BUILD_FOR_WIN
+    if (kIntel_GrGLVendor == ctxInfo.vendor()) {
+        fCrossContextTextureSupport = false;
+    }
+#endif
+
     // We support manual mip-map generation (via iterative downsampling draw calls). This fixes
     // bugs on some cards/drivers that produce incorrect mip-maps for sRGB textures when using
     // glGenerateMipmap. Our implementation requires mip-level sampling control. Additionally,
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/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp
index 94c1a50..fd454ad 100644
--- a/src/gpu/vk/GrVkCaps.cpp
+++ b/src/gpu/vk/GrVkCaps.cpp
@@ -37,6 +37,7 @@
 
     fUseDrawInsteadOfClear = false;
     fFenceSyncSupport = true;   // always available in Vulkan
+    fCrossContextTextureSupport = false; // TODO: Add thread-safe memory pools so we can enable this
 
     fMapBufferFlags = kNone_MapFlags; //TODO: figure this out
     fBufferMapThreshold = SK_MaxS32;  //TODO: figure this out
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..33d17bc 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,64 @@
     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;
+    }
+
+    // Some backends or drivers don't support (safely) moving resources between contexts
+    if (!context->caps()->crossContextTextureSupport()) {
+        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();
+    SkASSERT(textureData);
+
+    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));