diff options
author | Greg Daniel <egdaniel@google.com> | 2018-03-09 12:05:04 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-03-09 18:02:00 +0000 |
commit | a8d9211bc3f34fcb3cf331d8fd76e4ea5803fe20 (patch) | |
tree | 5e13626cb2fc91f839ecf624953921ee7bf31b1b | |
parent | 33cb22cb2b60255a2755cc88072490fe8d05b0b9 (diff) |
Add promise images for deferred instantiation of wrapped gpu textures
This will allow a client to make an SkImage that "wraps" a gpu texture,
however the client does need to supply the actual gpu texture at Image
creation time. Instead it is retrieve at flush time via a callback.
Bug: skia:
Change-Id: I6267a55ab7102101a7bd80a6f547b6a870d2df08
Reviewed-on: https://skia-review.googlesource.com/109021
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Cary Clark <caryclark@google.com>
-rw-r--r-- | gn/tests.gni | 1 | ||||
-rw-r--r-- | include/core/SkDeferredDisplayListRecorder.h | 59 | ||||
-rw-r--r-- | include/gpu/GrBackendSurface.h | 2 | ||||
-rw-r--r-- | src/core/SkDeferredDisplayListRecorder.cpp | 31 | ||||
-rw-r--r-- | src/gpu/GrProxyProvider.cpp | 19 | ||||
-rw-r--r-- | src/gpu/GrProxyProvider.h | 5 | ||||
-rw-r--r-- | src/gpu/GrSurfaceProxyPriv.h | 4 | ||||
-rw-r--r-- | src/image/SkImage_Gpu.cpp | 142 | ||||
-rw-r--r-- | src/image/SkImage_Gpu.h | 53 | ||||
-rw-r--r-- | tests/DeferredDisplayListTest.cpp | 4 | ||||
-rw-r--r-- | tests/LazyProxyTest.cpp | 15 | ||||
-rw-r--r-- | tests/PromiseImageTest.cpp | 203 | ||||
-rw-r--r-- | tools/gpu/GrTest.cpp | 24 | ||||
-rw-r--r-- | tools/gpu/GrTest.h | 2 |
14 files changed, 543 insertions, 21 deletions
diff --git a/gn/tests.gni b/gn/tests.gni index cde24b0ea3..0c34a6cfd3 100644 --- a/gn/tests.gni +++ b/gn/tests.gni @@ -173,6 +173,7 @@ tests_sources = [ "$_tests/PremulAlphaRoundTripTest.cpp", "$_tests/PrimitiveProcessorTest.cpp", "$_tests/ProcessorTest.cpp", + "$_tests/PromiseImageTest.cpp", "$_tests/ProxyConversionTest.cpp", "$_tests/ProxyRefTest.cpp", "$_tests/ProxyTest.cpp", diff --git a/include/core/SkDeferredDisplayListRecorder.h b/include/core/SkDeferredDisplayListRecorder.h index c985e7377d..601cd533b5 100644 --- a/include/core/SkDeferredDisplayListRecorder.h +++ b/include/core/SkDeferredDisplayListRecorder.h @@ -8,11 +8,15 @@ #ifndef SkDeferredDisplayListMaker_DEFINED #define SkDeferredDisplayListMaker_DEFINED +#include "SkImageInfo.h" #include "SkRefCnt.h" #include "SkSurfaceCharacterization.h" +#include "SkTypes.h" #include "../private/SkDeferredDisplayList.h" +class GrBackendFormat; +class GrBackendTexture; class GrContext; class SkCanvas; @@ -46,6 +50,61 @@ public: std::unique_ptr<SkDeferredDisplayList> detach(); + // Matches the defines in SkImage_Gpu.h + typedef void* TextureContext; + typedef void (*TextureReleaseProc)(TextureContext textureContext); + typedef void (*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture); + + /** + Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The main + difference is that the client doesn't have the backend texture on the gpu yet but they know + all the properties of the texture. So instead of passing in a GrBackendTexture the client + supplies a GrBackendFormat, width, height, and GrMipMapped state. + + When we actually send the draw calls to the GPU, we will call the textureFulfillProc and + the client will return a GrBackendTexture to us. The properties of the GrBackendTexture must + match those set during the SkImage creation, and it must have a valid backend gpu texture. + The gpu texture supplied by the client must stay valid until we call the textureReleaseProc. + + When we are done with the texture returned by the textureFulfillProc we will call the + textureReleaseProc passing in the textureContext. This is a signal to the client that they + are free to delete the underlying gpu texture. If future draws also use the same promise + image we will call the textureFulfillProc again if we've already called the + textureReleaseProc. We will always call textureFulfillProc and textureReleaseProc in pairs. + In other words we will never call textureFulfillProc or textureReleaseProc multiple times + for the same textureContext before calling the other. + + This call is only valid if the SkDeferredDisplayListRecorder is backed by a gpu context. + + @param backendFormat format of promised gpu texture + @param width width of promised gpu texture + @param height height of promised gpu texture + @param mipMapped mip mapped state of promised gpu texture + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param colorType one of: kUnknown_SkColorType, kAlpha_8_SkColorType, + kRGB_565_SkColorType, kARGB_4444_SkColorType, + kRGBA_8888_SkColorType, kBGRA_8888_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType + @param alphaType one of: kUnknown_SkAlphaType, kOpaque_SkAlphaType, + kPremul_SkAlphaType, kUnpremul_SkAlphaType + @param colorSpace range of colors; may be nullptr + @param textureFulfillProc function called to get actual gpu texture + @param textureReleaseProc function called when texture can be released + @param textureContext state passed to textureFulfillProc and textureReleaseProc + @return created SkImage, or nullptr + */ + sk_sp<SkImage> makePromiseTexture(const GrBackendFormat& backendFormat, + int width, + int height, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp<SkColorSpace> colorSpace, + TextureFulfillProc textureFulfillProc, + TextureReleaseProc textureReleaseProc, + TextureContext textureContext); + private: bool init(); diff --git a/include/gpu/GrBackendSurface.h b/include/gpu/GrBackendSurface.h index a2df90cd0d..94ab39aca2 100644 --- a/include/gpu/GrBackendSurface.h +++ b/include/gpu/GrBackendSurface.h @@ -26,7 +26,7 @@ public: } #ifdef SK_VULKAN - static GrBackendFormat MakeVK(VkFormat format) { + static GrBackendFormat MakeVk(VkFormat format) { return GrBackendFormat(format); } #endif diff --git a/src/core/SkDeferredDisplayListRecorder.cpp b/src/core/SkDeferredDisplayListRecorder.cpp index 96bb575924..45992d8c6f 100644 --- a/src/core/SkDeferredDisplayListRecorder.cpp +++ b/src/core/SkDeferredDisplayListRecorder.cpp @@ -7,6 +7,7 @@ #include "SkDeferredDisplayListRecorder.h" + #if SK_SUPPORT_GPU #include "GrContextPriv.h" #include "GrProxyProvider.h" @@ -14,6 +15,7 @@ #include "SkGpuDevice.h" #include "SkGr.h" +#include "SkImage_Gpu.h" #include "SkSurface_Gpu.h" #endif @@ -150,3 +152,32 @@ std::unique_ptr<SkDeferredDisplayList> SkDeferredDisplayListRecorder::detach() { } +sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture( + const GrBackendFormat& backendFormat, + int width, + int height, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp<SkColorSpace> colorSpace, + TextureFulfillProc textureFulfillProc, + TextureReleaseProc textureReleaseProc, + TextureContext textureContext) { +#if !defined(SK_RASTER_RECORDER_IMPLEMENTATION) && defined(SK_SUPPORT_GPU) + return SkImage_Gpu::MakePromiseTexture(fContext.get(), + backendFormat, + width, + height, + mipMapped, + origin, + colorType, + alphaType, + colorSpace, + textureFulfillProc, + textureReleaseProc, + textureContext); +#else + return nullptr; +#endif +} diff --git a/src/gpu/GrProxyProvider.cpp b/src/gpu/GrProxyProvider.cpp index dbd49a7ac3..7c82544cb1 100644 --- a/src/gpu/GrProxyProvider.cpp +++ b/src/gpu/GrProxyProvider.cpp @@ -524,6 +524,20 @@ sk_sp<GrTextureProxy> GrProxyProvider::createLazyProxy(LazyInstantiateCallback&& GrMipMapped mipMapped, GrRenderTargetFlags renderTargetFlags, SkBackingFit fit, SkBudgeted budgeted) { + // For non-ddl draws always make lazy proxy's single use. + LazyInstantiationType lazyType = fResourceProvider ? LazyInstantiationType::kSingleUse + : LazyInstantiationType::kMultipleUse; + return this->createLazyProxy(std::move(callback), desc, origin, mipMapped, renderTargetFlags, + fit, budgeted, lazyType); +} + +sk_sp<GrTextureProxy> GrProxyProvider::createLazyProxy(LazyInstantiateCallback&& callback, + const GrSurfaceDesc& desc, + GrSurfaceOrigin origin, + GrMipMapped mipMapped, + GrRenderTargetFlags renderTargetFlags, + SkBackingFit fit, SkBudgeted budgeted, + LazyInstantiationType lazyType) { SkASSERT((desc.fWidth <= 0 && desc.fHeight <= 0) || (desc.fWidth > 0 && desc.fHeight > 0)); uint32_t flags = GrResourceProvider::kNoPendingIO_Flag; @@ -539,11 +553,6 @@ sk_sp<GrTextureProxy> GrProxyProvider::createLazyProxy(LazyInstantiateCallback&& } #endif - using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType; - // For non-ddl draws always make lazy proxy's single use. - LazyInstantiationType lazyType = fResourceProvider ? LazyInstantiationType::kSingleUse - : LazyInstantiationType::kMultipleUse; - return sk_sp<GrTextureProxy>( SkToBool(kRenderTarget_GrSurfaceFlag & desc.fFlags) ? new GrTextureRenderTargetProxy(std::move(callback), lazyType, desc, origin, diff --git a/src/gpu/GrProxyProvider.h b/src/gpu/GrProxyProvider.h index 2f2eeb1113..45a3c1a1b7 100644 --- a/src/gpu/GrProxyProvider.h +++ b/src/gpu/GrProxyProvider.h @@ -155,6 +155,7 @@ public: kYes = true }; + using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType; /** * Creates a texture proxy that will be instantiated by a user-supplied callback during flush. * (Stencil is not supported by this method.) The width and height must either both be greater @@ -167,6 +168,10 @@ public: */ sk_sp<GrTextureProxy> createLazyProxy(LazyInstantiateCallback&&, const GrSurfaceDesc&, GrSurfaceOrigin, GrMipMapped, GrRenderTargetFlags, + SkBackingFit, SkBudgeted, LazyInstantiationType); + + sk_sp<GrTextureProxy> createLazyProxy(LazyInstantiateCallback&&, const GrSurfaceDesc&, + GrSurfaceOrigin, GrMipMapped, GrRenderTargetFlags, SkBackingFit, SkBudgeted); sk_sp<GrTextureProxy> createLazyProxy(LazyInstantiateCallback&&, const GrSurfaceDesc&, diff --git a/src/gpu/GrSurfaceProxyPriv.h b/src/gpu/GrSurfaceProxyPriv.h index 10203b71d0..00b38684d8 100644 --- a/src/gpu/GrSurfaceProxyPriv.h +++ b/src/gpu/GrSurfaceProxyPriv.h @@ -80,10 +80,6 @@ public: GrSurfaceProxy::LazyInstantiationType::kUninstantiate == lazyInstantiationType(); } - void testingOnly_setLazyInstantiationType(GrSurfaceProxy::LazyInstantiationType lazyType) { - fProxy->fLazyInstantiationType = lazyType; - } - static bool AttachStencilIfNeeded(GrResourceProvider*, GrSurface*, bool needsStencil); private: diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp index 6ed3e15bb2..d49835ea90 100644 --- a/src/image/SkImage_Gpu.cpp +++ b/src/image/SkImage_Gpu.cpp @@ -576,6 +576,148 @@ sk_sp<SkImage> SkImage::makeTextureImage(GrContext* context, SkColorSpace* dstCo return nullptr; } +class PromiseImageHelper { +public: + PromiseImageHelper(SkImage_Gpu::TextureFulfillProc fulFillProc, + SkImage_Gpu::TextureReleaseProc releaseProc, + SkImage_Gpu::TextureContext context) + : fFulfillProc(fulFillProc) + , fReleaseProc(releaseProc) + , fContext(context) {} + + void reset() { + this->resetReleaseHelper(); + } + + sk_sp<GrTexture> getTexture(GrResourceProvider* resourceProvider) { + // Releases the promise helper if there are no outstanding hard refs. This means that we + // don't have any ReleaseProcs waiting to be called so we will need to do a fulfill. + if (fReleaseHelper && fReleaseHelper->weak_expired()) { + this->resetReleaseHelper(); + } + + sk_sp<GrTexture> tex; + if (!fReleaseHelper) { + fFulfillProc(fContext, &fBackendTex); + if (!fBackendTex.isValid()) { + // Even though the GrBackendTexture is not valid, we must call the release + // proc to keep our contract of always calling Fulfill and Release in pairs. + fReleaseProc(fContext); + return sk_sp<GrTexture>(); + } + + tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership); + if (!tex) { + // Even though the GrBackendTexture is not valid, we must call the release + // proc to keep our contract of always calling Fulfill and Release in pairs. + fReleaseProc(fContext); + return sk_sp<GrTexture>(); + } + fReleaseHelper = new GrReleaseProcHelper(fReleaseProc, fContext); + // Take a weak ref + fReleaseHelper->weak_ref(); + } else { + SkASSERT(fBackendTex.isValid()); + tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership); + if (!tex) { + // We weren't able to make a texture here, but since we are in this branch + // of the calls (promiseHelper.fReleaseHelper is valid) there is already a + // texture out there which will call the release proc so we don't need to + // call it here. + return sk_sp<GrTexture>(); + } + + SkAssertResult(fReleaseHelper->try_ref()); + } + SkASSERT(tex); + // Pass the hard ref off to the texture + tex->setRelease(sk_sp<GrReleaseProcHelper>(fReleaseHelper)); + return tex; + } + +private: + // Weak unrefs fReleaseHelper and sets it to null + void resetReleaseHelper() { + if (fReleaseHelper) { + fReleaseHelper->weak_unref(); + fReleaseHelper = nullptr; + } + } + + SkImage_Gpu::TextureFulfillProc fFulfillProc; + SkImage_Gpu::TextureReleaseProc fReleaseProc; + SkImage_Gpu::TextureContext fContext; + + // We cache the GrBackendTexture so that if we deleted the GrTexture but the the release proc + // has yet not been called (this can happen on Vulkan), then we can create a new texture without + // needing to call the fulfill proc again. + GrBackendTexture fBackendTex; + // The fReleaseHelper is used to track a weak ref on the release proc. This helps us make sure + // we are always pairing fulfill and release proc calls correctly. + GrReleaseProcHelper* fReleaseHelper = nullptr; +}; + +sk_sp<SkImage> SkImage_Gpu::MakePromiseTexture(GrContext* context, + const GrBackendFormat& backendFormat, + int width, + int height, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp<SkColorSpace> colorSpace, + TextureFulfillProc textureFulfillProc, + TextureReleaseProc textureReleaseProc, + TextureContext textureContext) { + if (!context) { + return nullptr; + } + + if (width <= 0 || height <= 0) { + return nullptr; + } + + if (!textureFulfillProc || !textureReleaseProc) { + return nullptr; + } + + SkImageInfo info = SkImageInfo::Make(width, height, colorType, alphaType, colorSpace); + if (!SkImageInfoIsValidAllowNumericalCS(info)) { + return nullptr; + } + GrPixelConfig config = kUnknown_GrPixelConfig; + if (!context->caps()->getConfigFromBackendFormat(backendFormat, colorType, &config)) { + return nullptr; + } + + GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider(); + + GrSurfaceDesc desc; + desc.fWidth = width; + desc.fHeight = height; + desc.fConfig = config; + + PromiseImageHelper promiseHelper(textureFulfillProc, textureReleaseProc, textureContext); + + sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy( + [promiseHelper] (GrResourceProvider* resourceProvider) mutable { + if (!resourceProvider) { + promiseHelper.reset(); + return sk_sp<GrTexture>(); + } + + return promiseHelper.getTexture(resourceProvider); + }, desc, origin, mipMapped, GrRenderTargetFlags::kNone, SkBackingFit::kExact, + SkBudgeted::kNo, GrSurfaceProxy::LazyInstantiationType::kUninstantiate); + + if (!proxy) { + return nullptr; + } + + return sk_make_sp<SkImage_Gpu>(context, kNeedNewImageUniqueID, alphaType, std::move(proxy), + std::move(colorSpace), SkBudgeted::kNo); +} + sk_sp<SkImage> SkImage::MakeCrossContextFromEncoded(GrContext* context, sk_sp<SkData> encoded, bool buildMips, SkColorSpace* dstColorSpace) { sk_sp<SkImage> codecImage = SkImage::MakeFromEncoded(std::move(encoded)); diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h index 9cc28c317a..24a8c78869 100644 --- a/src/image/SkImage_Gpu.h +++ b/src/image/SkImage_Gpu.h @@ -60,6 +60,59 @@ public: sk_sp<SkImage> onMakeColorSpace(sk_sp<SkColorSpace>, SkColorType, SkTransferFunctionBehavior) const override; + typedef ReleaseContext TextureContext; + typedef void (*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture); + + /** + Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The main + difference is that the client doesn't have the backend texture on the gpu yet but they know + all the properties of the texture. So instead of passing in a GrBackendTexture the client + supplies a GrBackendFormat, width, height, and GrMipMapped state. + + When we actually send the draw calls to the GPU, we will call the textureFulfillProc and + the client will return a GrBackendTexture to us. The properties of the GrBackendTexture must + match those set during the SkImage creation, and it must have a valid backend gpu texture. + The gpu texture supplied by the client must stay valid until we call the textureReleaseProc. + + When we are done with the texture returned by the textureFulfillProc we will call the + textureReleaseProc passing in the textureContext. This is a signal to the client that they + are free to delete the underlying gpu texture. If future draws also use the same promise + image we will call the textureFulfillProc again if we've already called the + textureReleaseProc. We will always call textureFulfillProc and textureReleaseProc in pairs. + In other words we will never call textureFulfillProc or textureReleaseProc multiple times + for the same textureContext before calling the other. + + @param context Gpu context + @param backendFormat format of promised gpu texture + @param width width of promised gpu texture + @param height height of promised gpu texture + @param mipMapped mip mapped state of promised gpu texture + @param origin one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin + @param colorType one of: kUnknown_SkColorType, kAlpha_8_SkColorType, + kRGB_565_SkColorType, kARGB_4444_SkColorType, + kRGBA_8888_SkColorType, kBGRA_8888_SkColorType, + kGray_8_SkColorType, kRGBA_F16_SkColorType + @param alphaType one of: kUnknown_SkAlphaType, kOpaque_SkAlphaType, + kPremul_SkAlphaType, kUnpremul_SkAlphaType + @param colorSpace range of colors; may be nullptr + @param textureFulfillProc function called to get actual gpu texture + @param textureReleaseProc function called when texture can be released + @param textureContext state passed to textureFulfillProc and textureReleaseProc + @return created SkImage, or nullptr + */ + static sk_sp<SkImage> MakePromiseTexture(GrContext* context, + const GrBackendFormat& backendFormat, + int width, + int height, + GrMipMapped mipMapped, + GrSurfaceOrigin origin, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp<SkColorSpace> colorSpace, + TextureFulfillProc textureFulfillProc, + TextureReleaseProc textureReleaseProc, + TextureContext textureContext); + bool onIsValid(GrContext*) const override; private: diff --git a/tests/DeferredDisplayListTest.cpp b/tests/DeferredDisplayListTest.cpp index b0c80c32ef..6700ba5570 100644 --- a/tests/DeferredDisplayListTest.cpp +++ b/tests/DeferredDisplayListTest.cpp @@ -42,9 +42,9 @@ static GrBackendFormat create_backend_format(GrContext* context, SkColorType col if (kRGBA_8888_SkColorType == colorType) { VkFormat format = caps->srgbSupport() ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; - return GrBackendFormat::MakeVK(format); + return GrBackendFormat::MakeVk(format); } else if (kRGBA_F16_SkColorType == colorType) { - return GrBackendFormat::MakeVK(VK_FORMAT_R16G16B16A16_SFLOAT); + return GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_SFLOAT); } break; #endif diff --git a/tests/LazyProxyTest.cpp b/tests/LazyProxyTest.cpp index 88cbd01649..b7c4a9c43c 100644 --- a/tests/LazyProxyTest.cpp +++ b/tests/LazyProxyTest.cpp @@ -217,7 +217,8 @@ DEF_GPUTEST(LazyProxyReleaseTest, reporter, /* options */) { using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType; for (bool doInstantiate : {true, false}) { for (auto lazyType : {LazyInstantiationType::kSingleUse, - LazyInstantiationType::kMultipleUse}) { + LazyInstantiationType::kMultipleUse, + LazyInstantiationType::kUninstantiate}) { int testCount = 0; int* testCountPtr = &testCount; sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy( @@ -229,10 +230,8 @@ DEF_GPUTEST(LazyProxyReleaseTest, reporter, /* options */) { *testCountPtr = 1; return sk_sp<GrTexture>(); }, - desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact, - SkBudgeted::kNo); - - proxy->priv().testingOnly_setLazyInstantiationType(lazyType); + desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, GrRenderTargetFlags::kNone, + SkBackingFit::kExact, SkBudgeted::kNo, lazyType); REPORTER_ASSERT(reporter, 0 == testCount); @@ -423,10 +422,8 @@ DEF_GPUTEST(LazyProxyUninstantiateTest, reporter, /* options */) { texture->setRelease(UninstantiateReleaseProc, releasePtr); return texture; }, - desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact, - SkBudgeted::kNo); - - lazyProxy->priv().testingOnly_setLazyInstantiationType(lazyType); + desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, GrRenderTargetFlags::kNone, + SkBackingFit::kExact, SkBudgeted::kNo, lazyType); rtc->priv().testingOnly_addDrawOp(skstd::make_unique<LazyUninstantiateTestOp>(lazyProxy)); diff --git a/tests/PromiseImageTest.cpp b/tests/PromiseImageTest.cpp new file mode 100644 index 0000000000..b475147c78 --- /dev/null +++ b/tests/PromiseImageTest.cpp @@ -0,0 +1,203 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Test.h" + +#if SK_SUPPORT_GPU + +#include "GrBackendSurface.h" +#include "GrContextPriv.h" +#include "GrGpu.h" +#include "GrTest.h" +#include "SkDeferredDisplayListRecorder.h" +#include "SkImage_Gpu.h" + +using namespace sk_gpu_test; + +struct PromiseTextureChecker { + explicit PromiseTextureChecker(const GrBackendTexture& tex) + : fTexture(tex) + , fFulfillCount(0) + , fReleaseCount(0) {} + GrBackendTexture fTexture; + int fFulfillCount; + int fReleaseCount; + static void Fulfill(void* self, GrBackendTexture* outTexture) { + static_cast<PromiseTextureChecker*>(self)->fFulfillCount++; + *outTexture = static_cast<PromiseTextureChecker*>(self)->fTexture; + } + static void Release(void* self) { + static_cast<PromiseTextureChecker*>(self)->fReleaseCount++; + } +}; + +// Because Vulkan may delay when it actually calls the ReleaseProcs depending on when command +// buffers finish their work, we need some slight wiggle room in what values we expect for fulfill +// and release counts. +static bool check_fulfill_and_release_cnts(const PromiseTextureChecker& promiseChecker, + bool countsMustBeEqual, + int expectedFulfillCnt, + int expectedReleaseCnt, + bool expectedRequired, + skiatest::Reporter* reporter) { + bool result = true; + int countDiff = promiseChecker.fFulfillCount - promiseChecker.fReleaseCount; + // FulfillCount should always equal ReleaseCount or be at most one higher + if (countDiff != 0) { + if (countsMustBeEqual) { + result = false; + REPORTER_ASSERT(reporter, 0 == countDiff); + } else if (countDiff != 1) { + result = false; + REPORTER_ASSERT(reporter, 0 == countDiff || 1 == countDiff); + } + } + + int fulfillDiff = expectedFulfillCnt - promiseChecker.fFulfillCount; + REPORTER_ASSERT(reporter, fulfillDiff >= 0); + if (fulfillDiff != 0) { + if (expectedRequired) { + result = false; + REPORTER_ASSERT(reporter, expectedFulfillCnt == promiseChecker.fFulfillCount); + } else if (fulfillDiff > 1) { + result = false; + REPORTER_ASSERT(reporter, fulfillDiff <= 1); + } + } + + int releaseDiff = expectedReleaseCnt - promiseChecker.fReleaseCount; + REPORTER_ASSERT(reporter, releaseDiff >= 0); + if (releaseDiff != 0) { + if (expectedRequired) { + result = false; + REPORTER_ASSERT(reporter, expectedReleaseCnt == promiseChecker.fReleaseCount); + } else if (releaseDiff > 1) { + result = false; + REPORTER_ASSERT(reporter, releaseDiff <= 1); + } + } + + return result; +} + +DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTest, reporter, ctxInfo) { + const int kWidth = 10; + const int kHeight = 10; + std::unique_ptr<uint32_t[]> pixels(new uint32_t[kWidth * kHeight]); + + GrContext* ctx = ctxInfo.grContext(); + GrGpu* gpu = ctx->contextPriv().getGpu(); + + GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture( + pixels.get(), kWidth, kHeight, kRGBA_8888_GrPixelConfig, true, GrMipMapped::kNo); + REPORTER_ASSERT(reporter, backendTex.isValid()); + + GrBackendFormat backendFormat = GrTest::CreateBackendFormatFromTexture(backendTex); + REPORTER_ASSERT(reporter, backendFormat.isValid()); + + PromiseTextureChecker promiseChecker(backendTex); + GrSurfaceOrigin texOrigin = kTopLeft_GrSurfaceOrigin; + sk_sp<SkImage> refImg( + SkImage_Gpu::MakePromiseTexture(ctx, backendFormat, kWidth, kHeight, GrMipMapped::kNo, + texOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType, + nullptr, + PromiseTextureChecker::Fulfill, + PromiseTextureChecker::Release, + &promiseChecker)); + + SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight); + sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info); + SkCanvas* canvas = surface->getCanvas(); + + int expectedFulfillCnt = 0; + int expectedReleaseCnt = 0; + + canvas->drawImage(refImg, 0, 0); + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + true, + expectedFulfillCnt, + expectedReleaseCnt, + true, + reporter)); + + bool isVulkan = kVulkan_GrBackend == ctx->contextPriv().getBackend(); + canvas->flush(); + expectedFulfillCnt++; + expectedReleaseCnt++; + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + !isVulkan, + expectedFulfillCnt, + expectedReleaseCnt, + !isVulkan, + reporter)); + + gpu->testingOnly_flushGpuAndSync(); + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + true, + expectedFulfillCnt, + expectedReleaseCnt, + true, + reporter)); + + canvas->drawImage(refImg, 0, 0); + canvas->drawImage(refImg, 0, 0); + + canvas->flush(); + expectedFulfillCnt++; + expectedReleaseCnt++; + + gpu->testingOnly_flushGpuAndSync(); + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + true, + expectedFulfillCnt, + expectedReleaseCnt, + true, + reporter)); + + // Now test code path on Vulkan where we released the texture, but the GPU isn't done with + // resource yet and we do another draw. We should only call fulfill on the first draw and + // use the cached GrBackendTexture on the second. Release should only be called after the second + // draw is finished. + canvas->drawImage(refImg, 0, 0); + canvas->flush(); + expectedFulfillCnt++; + expectedReleaseCnt++; + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + !isVulkan, + expectedFulfillCnt, + expectedReleaseCnt, + !isVulkan, + reporter)); + + canvas->drawImage(refImg, 0, 0); + canvas->flush(); + expectedFulfillCnt++; + + gpu->testingOnly_flushGpuAndSync(); + expectedReleaseCnt++; + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + true, + expectedFulfillCnt, + expectedReleaseCnt, + !isVulkan, + reporter)); + expectedFulfillCnt = promiseChecker.fFulfillCount; + expectedReleaseCnt = promiseChecker.fReleaseCount; + + refImg.reset(); + + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + true, + expectedFulfillCnt, + expectedReleaseCnt, + true, + reporter)); + + gpu->deleteTestingOnlyBackendTexture(&backendTex); +} + +#endif diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp index 2160c7f47e..ae86aaed82 100644 --- a/tools/gpu/GrTest.cpp +++ b/tools/gpu/GrTest.cpp @@ -78,6 +78,30 @@ GrBackendTexture CreateBackendTexture(GrBackend backend, int width, int height, } } +GrBackendFormat CreateBackendFormatFromTexture(const GrBackendTexture& tex) { + switch (tex.backend()) { +#ifdef SK_VULKAN + case kVulkan_GrBackend: { + const GrVkImageInfo* vkInfo = tex.getVkImageInfo(); + SkASSERT(vkInfo); + return GrBackendFormat::MakeVk(vkInfo->fFormat); + } +#endif + case kOpenGL_GrBackend: { + const GrGLTextureInfo* glInfo = tex.getGLTextureInfo(); + SkASSERT(glInfo); + return GrBackendFormat::MakeGL(glInfo->fFormat, glInfo->fTarget); + } + case kMock_GrBackend: { + const GrMockTextureInfo* mockInfo = tex.getMockTextureInfo(); + SkASSERT(mockInfo); + return GrBackendFormat::MakeMock(mockInfo->fConfig); + } + default: + return GrBackendFormat(); + } +} + } // namespace GrTest bool GrSurfaceProxy::isWrapped_ForTesting() const { diff --git a/tools/gpu/GrTest.h b/tools/gpu/GrTest.h index 6666ab1a20..76e5e93e03 100644 --- a/tools/gpu/GrTest.h +++ b/tools/gpu/GrTest.h @@ -22,6 +22,8 @@ namespace GrTest { // TODO: remove this. It is only used in the SurfaceSemaphores Test. GrBackendTexture CreateBackendTexture(GrBackend, int width, int height, GrPixelConfig, GrMipMapped, GrBackendObject); + + GrBackendFormat CreateBackendFormatFromTexture(const GrBackendTexture& tex); }; #endif |