From 9ad1f92e2fceea33215c0f13cee42a679fb88d44 Mon Sep 17 00:00:00 2001 From: Brian Osman Date: Thu, 23 Feb 2017 17:06:10 -0500 Subject: [PATCH] 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 Reviewed-by: Brian Salomon --- gn/core.gni | 1 + gn/gpu.gni | 1 + gn/tests.gni | 1 + include/core/SkCrossContextImageData.h | 68 ++++++ include/core/SkImage.h | 9 + include/gpu/GrContext.h | 6 + include/gpu/GrExternalTextureData.h | 30 +++ include/gpu/GrGpuResource.h | 5 + include/gpu/GrTexture.h | 2 + include/gpu/gl/GrGLTypes.h | 18 ++ include/gpu/vk/GrVkTypes.h | 18 ++ src/gpu/GrContext.cpp | 7 + src/gpu/GrGpu.h | 4 + src/gpu/GrGpuResource.cpp | 11 + src/gpu/GrTexturePriv.h | 10 + src/gpu/gl/GrGLGpu.cpp | 4 + src/gpu/gl/GrGLGpu.h | 2 + src/gpu/gl/GrGLTexture.cpp | 19 ++ src/gpu/gl/GrGLTexture.h | 1 + src/gpu/vk/GrVkGpu.cpp | 3 + src/gpu/vk/GrVkGpu.h | 2 + src/gpu/vk/GrVkTexture.cpp | 6 + src/gpu/vk/GrVkTexture.h | 1 + src/image/SkImage.cpp | 16 ++ src/image/SkImage_Gpu.cpp | 68 +++++- tests/CrossContextImageTest.cpp | 218 ++++++++++++++++++ tools/gpu/GrContextFactory.cpp | 12 + tools/gpu/GrContextFactory.h | 5 + tools/gpu/GrTest.cpp | 1 + .../win/CreatePlatformGLTestContext_win.cpp | 6 +- 30 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 include/core/SkCrossContextImageData.h create mode 100644 include/gpu/GrExternalTextureData.h create mode 100644 tests/CrossContextImageTest.cpp diff --git a/gn/core.gni b/gn/core.gni index b6ad6a9960..7dd997ed05 100644 --- a/gn/core.gni +++ b/gn/core.gni @@ -390,6 +390,7 @@ skia_core_sources = [ "$_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 ae05f2cb31..f3e479d1dc 100644 --- a/gn/gpu.gni +++ b/gn/gpu.gni @@ -18,6 +18,7 @@ skia_gpu_sources = [ "$_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 6646f3df5a..f8c95d79de 100644 --- a/gn/tests.gni +++ b/gn/tests.gni @@ -44,6 +44,7 @@ tests_sources = [ "$_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 0000000000..bbee6e742b --- /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 MakeFromEncoded( + GrContext*, sk_sp, SkColorSpace* dstColorSpace); + +private: + SkCrossContextImageData(sk_sp image) : fImage(std::move(image)) { + SkASSERT(!fImage->isTextureBacked()); + } + +#if SK_SUPPORT_GPU + SkCrossContextImageData(const GrBackendTextureDesc& desc, + std::unique_ptr textureData, + SkAlphaType alphaType, sk_sp 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 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 fColorSpace; + GrBackendTextureDesc fDesc; + std::unique_ptr fTextureData; +#endif + + friend class SkImage; +}; + +#endif diff --git a/include/core/SkImage.h b/include/core/SkImage.h index cbd1b29c8a..93d8564192 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; @@ -323,6 +324,14 @@ public: */ sk_sp 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 MakeFromCrossContextImageData(GrContext*, + std::unique_ptr); + /** * 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 f268b8817a..2c24050084 100644 --- a/include/gpu/GrContext.h +++ b/include/gpu/GrContext.h @@ -324,6 +324,12 @@ public: */ 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. */ diff --git a/include/gpu/GrExternalTextureData.h b/include/gpu/GrExternalTextureData.h new file mode 100644 index 0000000000..9ab819ce70 --- /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 cc7e7aaa20..e0a7903f1d 100644 --- a/include/gpu/GrGpuResource.h +++ b/include/gpu/GrGpuResource.h @@ -260,6 +260,11 @@ protected: // 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 c032cbea29..1f63958d28 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 @@ protected: GrSamplerParams::FilterMode highestFilterMode, bool wasMipMapDataProvided); void validateDesc() const; + virtual std::unique_ptr detachBackendTexture() = 0; private: void computeScratchKey(GrScratchKey*) const override; diff --git a/include/gpu/gl/GrGLTypes.h b/include/gpu/gl/GrGLTypes.h index 5b9e31de1a..d03363c75e 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 @@ struct GrGLTextureInfo { 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(&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 aa1334adca..e9a312160d 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 @@ struct GrVkImageInfo { 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(&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 4b1324e33e..e816687b0b 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -545,6 +545,13 @@ void GrContext::prepareSurfaceForExternalIO(GrSurface* surface) { 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 0aed3d2c74..f710810650 100644 --- a/src/gpu/GrGpu.h +++ b/src/gpu/GrGpu.h @@ -382,6 +382,10 @@ public: 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 c3a9556588..dcc4e62282 100644 --- a/src/gpu/GrGpuResource.cpp +++ b/src/gpu/GrGpuResource.cpp @@ -43,6 +43,17 @@ void GrGpuResource::registerWithCacheWrapped() { 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 042061129d..cc0a05edfd 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 @@ public: } 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 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 ad00b0a90c..5dce7d1e2b 100644 --- a/src/gpu/gl/GrGLGpu.cpp +++ b/src/gpu/gl/GrGLGpu.cpp @@ -4741,3 +4741,7 @@ bool GrGLGpu::waitFence(GrFence fence, uint64_t timeout) const { 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 fdc2ebbfa9..b6ca4f6fd6 100644 --- a/src/gpu/gl/GrGLGpu.h +++ b/src/gpu/gl/GrGLGpu.h @@ -150,6 +150,8 @@ public: 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 edce7b1f33..a5609887c0 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(this->getGpu()) @@ -111,6 +113,23 @@ GrBackendObject GrGLTexture::getTextureHandle() const { return reinterpret_cast(&fInfo); } +std::unique_ptr 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(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 029fd87a1a..16b47f1f07 100644 --- a/src/gpu/gl/GrGLTexture.h +++ b/src/gpu/gl/GrGLTexture.h @@ -71,6 +71,7 @@ protected: void onRelease() override; void setMemoryBacking(SkTraceMemoryDump* traceMemoryDump, const SkString& dumpName) const override; + std::unique_ptr detachBackendTexture() override; private: TexParams fTexParams; diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp index 915ac9747e..8569c9367f 100644 --- a/src/gpu/vk/GrVkGpu.cpp +++ b/src/gpu/vk/GrVkGpu.cpp @@ -1860,3 +1860,6 @@ void GrVkGpu::deleteFence(GrFence fence) const { 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 18059b9682..c935945bd1 100644 --- a/src/gpu/vk/GrVkGpu.h +++ b/src/gpu/vk/GrVkGpu.h @@ -133,6 +133,8 @@ public: 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 7f0cf8d1d8..a8f1bf00dc 100644 --- a/src/gpu/vk/GrVkTexture.cpp +++ b/src/gpu/vk/GrVkTexture.cpp @@ -144,6 +144,12 @@ GrBackendObject GrVkTexture::getTextureHandle() const { return (GrBackendObject)&fInfo; } +std::unique_ptr 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(this->getGpu()); diff --git a/src/gpu/vk/GrVkTexture.h b/src/gpu/vk/GrVkTexture.h index 70db54838d..db7124e2b8 100644 --- a/src/gpu/vk/GrVkTexture.h +++ b/src/gpu/vk/GrVkTexture.h @@ -42,6 +42,7 @@ protected: void onAbandon() override; void onRelease() override; + std::unique_ptr detachBackendTexture() override; private: enum Wrapped { kWrapped }; diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp index 48d9ad32ba..638b0c2955 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 @@ sk_sp SkImage::makeTextureImage(GrContext*, SkColorSpace* dstColorSpace return nullptr; } +std::unique_ptr SkCrossContextImageData::MakeFromEncoded( + GrContext*, sk_sp encoded, SkColorSpace* dstColorSpace) { + sk_sp image = SkImage::MakeFromEncoded(std::move(encoded)); + if (!image) { + return nullptr; + } + // TODO: Force decode to raster here? + return std::unique_ptr(new SkCrossContextImageData(std::move(image))); +} + +sk_sp SkImage::MakeFromCrossContextImageData( + GrContext*, std::unique_ptr ccid) { + return ccid->fImage; +} + sk_sp SkImage::makeNonTextureImage() const { return sk_ref_sp(const_cast(this)); } diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp index 05caf867a3..ddb62d7a89 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 @@ static sk_sp create_image_from_maker(GrTextureMaker* maker, SkAlphaType std::move(texColorSpace), SkBudgeted::kNo); } -sk_sp SkImage::makeTextureImage(GrContext *context, SkColorSpace* dstColorSpace) const { +sk_sp SkImage::makeTextureImage(GrContext* context, SkColorSpace* dstColorSpace) const { if (!context) { return nullptr; } @@ -377,6 +379,70 @@ sk_sp SkImage::makeTextureImage(GrContext *context, SkColorSpace* dstCo return nullptr; } +std::unique_ptr SkCrossContextImageData::MakeFromEncoded( + GrContext* context, sk_sp encoded, SkColorSpace* dstColorSpace) { + sk_sp 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( + new SkCrossContextImageData(std::move(codecImage))); + } + + sk_sp textureImage = codecImage->makeTextureImage(context, dstColorSpace); + if (!textureImage) { + // TODO: Force decode to raster here? Do mip-mapping, like getDeferredTextureImageData? + return std::unique_ptr( + 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( + new SkCrossContextImageData(std::move(codecImage))); + } + + SkImageInfo info = as_IB(textureImage)->onImageInfo(); + return std::unique_ptr(new SkCrossContextImageData( + desc, std::move(textureData), info.alphaType(), info.refColorSpace())); +} + +sk_sp SkImage::MakeFromCrossContextImageData( + GrContext* context, std::unique_ptr 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::makeNonTextureImage() const { if (!this->isTextureBacked()) { return sk_ref_sp(const_cast(this)); diff --git a/tests/CrossContextImageTest.cpp b/tests/CrossContextImageTest.cpp new file mode 100644 index 0000000000..ceb414d59f --- /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 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 create_test_data(SkEncodedImageFormat format) { + auto image = create_test_image(); + return sk_sp(image->encode(format, 100)); +} + +DEF_GPUTEST(CrossContextImage_SameContext, reporter, /*factory*/) { + GrContextFactory factory; + sk_sp testImage = create_test_image(); + + // Test both PNG and JPG, to exercise GPU YUV conversion + for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) { + sk_sp encoded = create_test_data(format); + + for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) { + GrContextFactory::ContextType ctxType = static_cast(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 testImage = create_test_image(); + + // Test both PNG and JPG, to exercise GPU YUV conversion + for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) { + sk_sp encoded = create_test_data(format); + + for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) { + GrContextFactory::ContextType ctxType = static_cast(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 fCCID; + sk_sp fEncoded; +}; +} + +static void upload_image_thread_proc(void* data) { + CrossContextImage_ThreadContext* ctx = static_cast(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 testImage = create_test_image(); + + // Test both PNG and JPG, to exercise GPU YUV conversion + for (auto format : { SkEncodedImageFormat::kPNG, SkEncodedImageFormat::kJPEG }) { + sk_sp encoded = create_test_data(format); + + for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) { + GrContextFactory::ContextType ctxType = static_cast(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 637c569f89..8cfa3f2db0 100644 --- a/tools/gpu/GrContextFactory.cpp +++ b/tools/gpu/GrContextFactory.cpp @@ -273,4 +273,16 @@ ContextInfo GrContextFactory::getContextInfo(ContextType type, ContextOverrides 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 e42f34c944..c26729d965 100644 --- a/tools/gpu/GrContextFactory.h +++ b/tools/gpu/GrContextFactory.h @@ -151,6 +151,11 @@ public: ContextInfo getContextInfo(ContextType type, 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. */ diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp index a206339418..204119c166 100644 --- a/tools/gpu/GrTest.cpp +++ b/tools/gpu/GrTest.cpp @@ -313,6 +313,7 @@ public: 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 af35b7b6bc..49d774315b 100644 --- a/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp +++ b/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp @@ -85,7 +85,11 @@ WinGLTestContext::WinGLTestContext(GrGLStandard forcedGpuAPI, WinGLTestContext* 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;