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/gn/core.gni b/gn/core.gni
index bd2a66e..3b71c45 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 d0fd0da..d60570d 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 0df16d7..1e9fb0e 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/GrCaps.h b/include/gpu/GrCaps.h
index 16a4ca9..23f870d 100644
--- a/include/gpu/GrCaps.h
+++ b/include/gpu/GrCaps.h
@@ -183,6 +183,7 @@
bool sampleShadingSupport() const { return fSampleShadingSupport; }
bool fenceSyncSupport() const { return fFenceSyncSupport; }
+ bool crossContextTextureSupport() const { return fCrossContextTextureSupport; }
protected:
/** Subclasses must call this at the end of their constructors in order to apply caps
@@ -225,6 +226,9 @@
// TODO: this may need to be an enum to support different fence types
bool fFenceSyncSupport : 1;
+ // Vulkan doesn't support this (yet) and some drivers have issues, too
+ bool fCrossContextTextureSupport : 1;
+
InstancedSupport fInstancedSupport;
BlendEquationSupport fBlendEquationSupport;
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/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));
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
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 {}