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>
This commit is contained in:
parent
585dba831c
commit
2c2bc11aea
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
68
include/core/SkCrossContextImageData.h
Normal file
68
include/core/SkCrossContextImageData.h
Normal file
@ -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
|
@ -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<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.
|
||||
|
@ -183,6 +183,7 @@ public:
|
||||
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 @@ protected:
|
||||
// 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;
|
||||
|
@ -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.
|
||||
*/
|
||||
|
30
include/gpu/GrExternalTextureData.h
Normal file
30
include/gpu/GrExternalTextureData.h
Normal file
@ -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
|
@ -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();
|
||||
|
||||
|
@ -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<GrExternalTextureData> detachBackendTexture() = 0;
|
||||
|
||||
private:
|
||||
void computeScratchKey(GrScratchKey*) const override;
|
||||
|
@ -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<GrBackendObject>(&fInfo);
|
||||
}
|
||||
|
||||
GrGLTextureInfo fInfo;
|
||||
|
||||
typedef GrExternalTextureData INHERITED;
|
||||
};
|
||||
|
||||
GR_STATIC_ASSERT(sizeof(GrBackendObject) >= sizeof(const GrGLTextureInfo*));
|
||||
|
||||
#endif
|
||||
|
@ -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<GrBackendObject>(&fInfo);
|
||||
}
|
||||
|
||||
GrVkImageInfo fInfo;
|
||||
|
||||
typedef GrExternalTextureData INHERITED;
|
||||
};
|
||||
|
||||
GR_STATIC_ASSERT(sizeof(GrBackendObject) >= sizeof(const GrVkImageInfo*));
|
||||
|
||||
#endif
|
||||
|
@ -53,6 +53,7 @@ GrCaps::GrCaps(const GrContextOptions& options) {
|
||||
fMustClearUploadedBufferData = false;
|
||||
fSampleShadingSupport = false;
|
||||
fFenceSyncSupport = false;
|
||||
fCrossContextTextureSupport = false;
|
||||
|
||||
fUseDrawInsteadOfClear = false;
|
||||
|
||||
@ -141,6 +142,7 @@ SkString GrCaps::dump() const {
|
||||
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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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<GrExternalTextureData> detachBackendTexture() {
|
||||
return fTexture->detachBackendTexture();
|
||||
}
|
||||
|
||||
static void ComputeScratchKey(const GrSurfaceDesc&, GrScratchKey*);
|
||||
|
||||
private:
|
||||
|
@ -603,6 +603,15 @@ void GrGLCaps::init(const GrContextOptions& contextOptions,
|
||||
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,
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 @@ GrBackendObject GrGLTexture::getTextureHandle() const {
|
||||
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;
|
||||
|
@ -71,6 +71,7 @@ protected:
|
||||
void onRelease() override;
|
||||
void setMemoryBacking(SkTraceMemoryDump* traceMemoryDump,
|
||||
const SkString& dumpName) const override;
|
||||
std::unique_ptr<GrExternalTextureData> detachBackendTexture() override;
|
||||
|
||||
private:
|
||||
TexParams fTexParams;
|
||||
|
@ -37,6 +37,7 @@ GrVkCaps::GrVkCaps(const GrContextOptions& contextOptions, const GrVkInterface*
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -144,6 +144,12 @@ GrBackendObject GrVkTexture::getTextureHandle() const {
|
||||
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());
|
||||
|
@ -42,6 +42,7 @@ protected:
|
||||
|
||||
void onAbandon() override;
|
||||
void onRelease() override;
|
||||
std::unique_ptr<GrExternalTextureData> detachBackendTexture() override;
|
||||
|
||||
private:
|
||||
enum Wrapped { kWrapped };
|
||||
|
@ -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> SkImage::makeTextureImage(GrContext*, SkColorSpace* dstColorSpace
|
||||
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));
|
||||
}
|
||||
|
@ -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<SkImage> create_image_from_maker(GrTextureMaker* maker, SkAlphaType
|
||||
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 @@ sk_sp<SkImage> SkImage::makeTextureImage(GrContext *context, SkColorSpace* dstCo
|
||||
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));
|
||||
|
223
tests/CrossContextImageTest.cpp
Normal file
223
tests/CrossContextImageTest.cpp
Normal file
@ -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
|
@ -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 {}
|
||||
|
Loading…
Reference in New Issue
Block a user