diff options
-rw-r--r-- | dm/DMSrcSink.cpp | 5 | ||||
-rw-r--r-- | include/core/SkDeferredDisplayListRecorder.h | 9 | ||||
-rw-r--r-- | src/core/SkDeferredDisplayListRecorder.cpp | 3 | ||||
-rw-r--r-- | src/image/SkImage_Gpu.cpp | 59 | ||||
-rw-r--r-- | src/image/SkImage_Gpu.h | 8 | ||||
-rw-r--r-- | tests/DeferredDisplayListTest.cpp | 2 | ||||
-rw-r--r-- | tests/PromiseImageTest.cpp | 257 |
7 files changed, 230 insertions, 113 deletions
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index 9b68dc99cf..43992a8ff8 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -1248,6 +1248,10 @@ static void promise_image_release_proc(void* textureContext) { // Do nothing. We free all the backend textures at the end. } +static void promise_image_done_proc(void* textureContext) { + // Do nothing. +} + class PromiseImageCallbackContext { public: const SkTArray<PromiseImageInfo>* fImageInfo; @@ -1283,6 +1287,7 @@ static sk_sp<SkImage> promise_image_creator(const void* rawData, size_t length, curImage.fBitmap.refColorSpace(), promise_image_fulfill_proc, promise_image_release_proc, + promise_image_done_proc, (void*) &curImage); SkASSERT(image); return image; diff --git a/include/core/SkDeferredDisplayListRecorder.h b/include/core/SkDeferredDisplayListRecorder.h index d7743e6b8c..0acf598f96 100644 --- a/include/core/SkDeferredDisplayListRecorder.h +++ b/include/core/SkDeferredDisplayListRecorder.h @@ -55,6 +55,7 @@ public: typedef void* TextureContext; typedef void (*TextureReleaseProc)(TextureContext textureContext); typedef void (*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture); + typedef void (*PromiseDoneProc)(TextureContext textureContext); /** Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The main @@ -75,6 +76,12 @@ public: In other words we will never call textureFulfillProc or textureReleaseProc multiple times for the same textureContext before calling the other. + We we call the promiseDoneProc when we will no longer call the textureFulfillProc again. We + pass in the textureContext as a parameter to the promiseDoneProc. We also guarantee that + there will be no outstanding textureReleaseProcs that still need to be called when we call + the textureDoneProc. Thus when the textureDoneProc gets called the client is able to cleanup + all GPU objects and meta data needed for the textureFulfill call. + This call is only valid if the SkDeferredDisplayListRecorder is backed by a gpu context. @param backendFormat format of promised gpu texture @@ -91,6 +98,7 @@ public: @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 promiseDoneProc function called when we will no longer call textureFulfillProc @param textureContext state passed to textureFulfillProc and textureReleaseProc @return created SkImage, or nullptr */ @@ -104,6 +112,7 @@ public: sk_sp<SkColorSpace> colorSpace, TextureFulfillProc textureFulfillProc, TextureReleaseProc textureReleaseProc, + PromiseDoneProc promiseDoneProc, TextureContext textureContext); private: diff --git a/src/core/SkDeferredDisplayListRecorder.cpp b/src/core/SkDeferredDisplayListRecorder.cpp index 3d84aeb7bb..a0f6c01563 100644 --- a/src/core/SkDeferredDisplayListRecorder.cpp +++ b/src/core/SkDeferredDisplayListRecorder.cpp @@ -33,6 +33,7 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture( sk_sp<SkColorSpace> colorSpace, TextureFulfillProc textureFulfillProc, TextureReleaseProc textureReleaseProc, + PromiseDoneProc promiseDoneProc, TextureContext textureContext) { return nullptr; } @@ -155,6 +156,7 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture( sk_sp<SkColorSpace> colorSpace, TextureFulfillProc textureFulfillProc, TextureReleaseProc textureReleaseProc, + PromiseDoneProc promiseDoneProc, TextureContext textureContext) { if (!fContext) { return nullptr; @@ -171,6 +173,7 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture( std::move(colorSpace), textureFulfillProc, textureReleaseProc, + promiseDoneProc, textureContext); } diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp index 6ab2ddc0b8..5a2b25106f 100644 --- a/src/image/SkImage_Gpu.cpp +++ b/src/image/SkImage_Gpu.cpp @@ -529,17 +529,58 @@ sk_sp<SkImage> SkImage::makeTextureImage(GrContext* context, SkColorSpace* dstCo return nullptr; } +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This helper holds the normal hard ref for the Release proc as well as a hard ref on the DoneProc. + * Thus when a GrTexture is being released, it will unref both the ReleaseProc and DoneProc. + */ +class PromiseReleaseProcHelper : public GrReleaseProcHelper { +public: + PromiseReleaseProcHelper(SkImage_Gpu::TextureReleaseProc releaseProc, + SkImage_Gpu::TextureContext context, + sk_sp<GrReleaseProcHelper> doneHelper) + : INHERITED(releaseProc, context) + , fDoneProcHelper(std::move(doneHelper)) {} + + void weak_dispose() const override { + // Call the inherited weak_dispose first so that we call the ReleaseProc before the DoneProc + // if we hold the last ref to the DoneProc. + INHERITED::weak_dispose(); + fDoneProcHelper.reset(); + } + +private: + mutable sk_sp<GrReleaseProcHelper> fDoneProcHelper; + + typedef GrReleaseProcHelper INHERITED; +}; + +/** + * This helper class manages the ref counting for the the ReleaseProc and DoneProc for promise + * images. It holds a weak ref on the ReleaseProc (hard refs are owned by GrTextures). The weak ref + * allows us to reuse an outstanding ReleaseProc (because we dropped our GrTexture but the GrTexture + * isn't done on the GPU) without needing to call FulfillProc again. It also holds a hard ref on the + * DoneProc. The idea is that after every flush we may call the ReleaseProc so that the client can + * free up their GPU memory if they want to. The life time of the DoneProc matches that of any + * outstanding ReleaseProc as well as the PromiseImageHelper. Thus we won't call the DoneProc until + * all ReleaseProcs are finished and we are finished with the PromiseImageHelper (i.e. won't call + * FulfillProc again). + */ class PromiseImageHelper { public: PromiseImageHelper(SkImage_Gpu::TextureFulfillProc fulFillProc, SkImage_Gpu::TextureReleaseProc releaseProc, + SkImage_Gpu::PromiseDoneProc doneProc, SkImage_Gpu::TextureContext context) : fFulfillProc(fulFillProc) , fReleaseProc(releaseProc) - , fContext(context) {} + , fContext(context) + , fDoneHelper(new GrReleaseProcHelper(doneProc, context)) {} void reset() { this->resetReleaseHelper(); + fDoneHelper.reset(); } sk_sp<GrTexture> getTexture(GrResourceProvider* resourceProvider, GrPixelConfig config) { @@ -567,7 +608,7 @@ public: fReleaseProc(fContext); return sk_sp<GrTexture>(); } - fReleaseHelper = new GrReleaseProcHelper(fReleaseProc, fContext); + fReleaseHelper = new PromiseReleaseProcHelper(fReleaseProc, fContext, fDoneHelper); // Take a weak ref fReleaseHelper->weak_ref(); } else { @@ -608,7 +649,11 @@ private: 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; + PromiseReleaseProcHelper* fReleaseHelper = nullptr; + // We don't want to call the fDoneHelper until we are done with the PromiseImageHelper and all + // ReleaseHelpers are finished. Thus we hold a hard ref here and we will pass a hard ref to each + // fReleaseHelper we make. + sk_sp<GrReleaseProcHelper> fDoneHelper; }; sk_sp<SkImage> SkImage_Gpu::MakePromiseTexture(GrContext* context, @@ -622,6 +667,7 @@ sk_sp<SkImage> SkImage_Gpu::MakePromiseTexture(GrContext* context, sk_sp<SkColorSpace> colorSpace, TextureFulfillProc textureFulfillProc, TextureReleaseProc textureReleaseProc, + PromiseDoneProc promiseDoneProc, TextureContext textureContext) { if (!context) { return nullptr; @@ -631,7 +677,7 @@ sk_sp<SkImage> SkImage_Gpu::MakePromiseTexture(GrContext* context, return nullptr; } - if (!textureFulfillProc || !textureReleaseProc) { + if (!textureFulfillProc || !textureReleaseProc || !promiseDoneProc) { return nullptr; } @@ -651,7 +697,8 @@ sk_sp<SkImage> SkImage_Gpu::MakePromiseTexture(GrContext* context, desc.fHeight = height; desc.fConfig = config; - PromiseImageHelper promiseHelper(textureFulfillProc, textureReleaseProc, textureContext); + PromiseImageHelper promiseHelper(textureFulfillProc, textureReleaseProc, promiseDoneProc, + textureContext); sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy( [promiseHelper, config] (GrResourceProvider* resourceProvider) mutable { @@ -672,6 +719,8 @@ sk_sp<SkImage> SkImage_Gpu::MakePromiseTexture(GrContext* context, 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 e1fd35a766..7a4fa9751a 100644 --- a/src/image/SkImage_Gpu.h +++ b/src/image/SkImage_Gpu.h @@ -63,6 +63,7 @@ public: typedef ReleaseContext TextureContext; typedef void (*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture); + typedef void (*PromiseDoneProc)(TextureContext textureContext); /** Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The main @@ -83,6 +84,11 @@ public: In other words we will never call textureFulfillProc or textureReleaseProc multiple times for the same textureContext before calling the other. + We we call the promiseDoneProc when we will no longer call the textureFulfillProc again. We + also guarantee that there will be no outstanding textureReleaseProcs that still need to be + called when we call the textureDoneProc. Thus when the textureDoneProc gets called the + client is able to cleanup all GPU objects and meta data needed for the textureFulfill call. + @param context Gpu context @param backendFormat format of promised gpu texture @param width width of promised gpu texture @@ -98,6 +104,7 @@ public: @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 promiseDoneProc function called when we will no longer call textureFulfillProc @param textureContext state passed to textureFulfillProc and textureReleaseProc @return created SkImage, or nullptr */ @@ -112,6 +119,7 @@ public: sk_sp<SkColorSpace> colorSpace, TextureFulfillProc textureFulfillProc, TextureReleaseProc textureReleaseProc, + PromiseDoneProc promiseDoneProc, TextureContext textureContext); /** Implementation of MakeFromYUVTexturesCopy and MakeFromNV12TexturesCopy */ diff --git a/tests/DeferredDisplayListTest.cpp b/tests/DeferredDisplayListTest.cpp index f17957c9c8..9abe5f9ff4 100644 --- a/tests/DeferredDisplayListTest.cpp +++ b/tests/DeferredDisplayListTest.cpp @@ -406,6 +406,7 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DDLWrapBackendTest, reporter, ctxInfo) { static void dummy_fulfill_proc(void*, GrBackendTexture*) { SkASSERT(0); } static void dummy_release_proc(void*) { SkASSERT(0); } +static void dummy_done_proc(void*) { SkASSERT(0); } // Test out the behavior of an invalid DDLRecorder DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DDLInvalidRecorder, reporter, ctxInfo) { @@ -439,6 +440,7 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DDLInvalidRecorder, reporter, ctxInfo) { kPremul_SkAlphaType, nullptr, dummy_fulfill_proc, dummy_release_proc, + dummy_done_proc, nullptr); REPORTER_ASSERT(reporter, !image); } diff --git a/tests/PromiseImageTest.cpp b/tests/PromiseImageTest.cpp index c5bd9cd268..ef4a299850 100644 --- a/tests/PromiseImageTest.cpp +++ b/tests/PromiseImageTest.cpp @@ -22,10 +22,12 @@ struct PromiseTextureChecker { explicit PromiseTextureChecker(const GrBackendTexture& tex) : fTexture(tex) , fFulfillCount(0) - , fReleaseCount(0) {} + , fReleaseCount(0) + , fDoneCount(0) {} GrBackendTexture fTexture; int fFulfillCount; int fReleaseCount; + int fDoneCount; static void Fulfill(void* self, GrBackendTexture* outTexture) { static_cast<PromiseTextureChecker*>(self)->fFulfillCount++; *outTexture = static_cast<PromiseTextureChecker*>(self)->fTexture; @@ -33,6 +35,9 @@ struct PromiseTextureChecker { static void Release(void* self) { static_cast<PromiseTextureChecker*>(self)->fReleaseCount++; } + static void Done(void* self) { + static_cast<PromiseTextureChecker*>(self)->fDoneCount++; + } }; // Because Vulkan may delay when it actually calls the ReleaseProcs depending on when command @@ -43,6 +48,7 @@ static bool check_fulfill_and_release_cnts(const PromiseTextureChecker& promiseC int expectedFulfillCnt, int expectedReleaseCnt, bool expectedRequired, + int expectedDoneCnt, skiatest::Reporter* reporter) { bool result = true; int countDiff = promiseChecker.fFulfillCount - promiseChecker.fReleaseCount; @@ -81,123 +87,158 @@ static bool check_fulfill_and_release_cnts(const PromiseTextureChecker& promiseC } } + if (expectedDoneCnt != promiseChecker.fDoneCount) { + result = false; + REPORTER_ASSERT(reporter, expectedDoneCnt == promiseChecker.fDoneCount); + } + 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 = backendTex.format(); - 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); + for (bool releaseImageEarly : {true, false}) { + GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture( + nullptr, kWidth, kHeight, kRGBA_8888_GrPixelConfig, true, GrMipMapped::kNo); + REPORTER_ASSERT(reporter, backendTex.isValid()); + + GrBackendFormat backendFormat = backendTex.format(); + 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, + PromiseTextureChecker::Done, + &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; + int expectedDoneCnt = 0; + + canvas->drawImage(refImg, 0, 0); + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + true, + expectedFulfillCnt, + expectedReleaseCnt, + true, + expectedDoneCnt, + 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, + expectedDoneCnt, + reporter)); + + gpu->testingOnly_flushGpuAndSync(); + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + true, + expectedFulfillCnt, + expectedReleaseCnt, + true, + expectedDoneCnt, + 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, + expectedDoneCnt, + 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, + expectedDoneCnt, + reporter)); + + canvas->drawImage(refImg, 0, 0); + + if (releaseImageEarly) { + refImg.reset(); + } + + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + !isVulkan, + expectedFulfillCnt, + expectedReleaseCnt, + !isVulkan, + expectedDoneCnt, + reporter)); + + canvas->flush(); + expectedFulfillCnt++; + + gpu->testingOnly_flushGpuAndSync(); + expectedReleaseCnt++; + if (releaseImageEarly) { + expectedDoneCnt++; + } + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + true, + expectedFulfillCnt, + expectedReleaseCnt, + !isVulkan, + expectedDoneCnt, + reporter)); + expectedFulfillCnt = promiseChecker.fFulfillCount; + expectedReleaseCnt = promiseChecker.fReleaseCount; + + if (!releaseImageEarly) { + refImg.reset(); + expectedDoneCnt++; + } + + REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, + true, + expectedFulfillCnt, + expectedReleaseCnt, + true, + expectedDoneCnt, + reporter)); + + gpu->deleteTestingOnlyBackendTexture(backendTex); + } } #endif |