| /* |
| * 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 "SkExchange.h" |
| #include "Test.h" |
| |
| #include "GrBackendSurface.h" |
| #include "GrContextPriv.h" |
| #include "GrGpu.h" |
| #include "GrTexture.h" |
| #include "SkImage_Gpu.h" |
| #include "SkPromiseImageTexture.h" |
| |
| using namespace sk_gpu_test; |
| |
| struct PromiseTextureChecker { |
| // shared indicates whether the backend texture is used to fulfill more than one promise |
| // image. |
| explicit PromiseTextureChecker(const GrBackendTexture& tex, skiatest::Reporter* reporter, |
| bool shared) |
| : fTexture(SkPromiseImageTexture::Make(tex)) |
| , fReporter(reporter) |
| , fShared(shared) |
| , fFulfillCount(0) |
| , fReleaseCount(0) |
| , fDoneCount(0) {} |
| sk_sp<SkPromiseImageTexture> fTexture; |
| skiatest::Reporter* fReporter; |
| bool fShared; |
| int fFulfillCount; |
| int fReleaseCount; |
| int fDoneCount; |
| GrBackendTexture fLastFulfilledTexture; |
| |
| /** |
| * Replaces the backend texture that this checker will return from fulfill. Also, transfers |
| * ownership of the previous PromiseImageTexture to the caller, if they want to control when |
| * it is deleted. The default argument will remove the existing texture without installing a |
| * valid replacement. |
| */ |
| sk_sp<const SkPromiseImageTexture> replaceTexture( |
| const GrBackendTexture& tex = GrBackendTexture()) { |
| return skstd::exchange(fTexture, SkPromiseImageTexture::Make(tex)); |
| } |
| |
| SkTArray<GrUniqueKey> uniqueKeys() const { |
| return fTexture->testingOnly_uniqueKeysToInvalidate(); |
| } |
| |
| static sk_sp<SkPromiseImageTexture> Fulfill(void* self) { |
| auto checker = static_cast<PromiseTextureChecker*>(self); |
| checker->fFulfillCount++; |
| checker->fLastFulfilledTexture = checker->fTexture->backendTexture(); |
| return checker->fTexture; |
| } |
| static void Release(void* self) { |
| auto checker = static_cast<PromiseTextureChecker*>(self); |
| checker->fReleaseCount++; |
| if (!checker->fShared) { |
| // This is only used in a single threaded fashion with a single promise image. So |
| // every fulfill should be balanced by a release before the next fulfill. |
| REPORTER_ASSERT(checker->fReporter, checker->fReleaseCount == checker->fFulfillCount); |
| } |
| } |
| static void Done(void* self) { |
| static_cast<PromiseTextureChecker*>(self)->fDoneCount++; |
| } |
| }; |
| |
| enum class ReleaseBalanceExpecation { |
| kBalanced, |
| kBalancedOrPlusOne, |
| kAny |
| }; |
| |
| static bool check_fulfill_and_release_cnts(const PromiseTextureChecker& promiseChecker, |
| ReleaseBalanceExpecation balanceExpecation, |
| int expectedFulfillCnt, |
| int expectedReleaseCnt, |
| bool expectedRequired, |
| int expectedDoneCnt, |
| 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 (balanceExpecation == ReleaseBalanceExpecation::kBalanced) { |
| result = false; |
| REPORTER_ASSERT(reporter, 0 == countDiff); |
| } else if (countDiff != 1 && |
| balanceExpecation == ReleaseBalanceExpecation::kBalancedOrPlusOne) { |
| result = false; |
| REPORTER_ASSERT(reporter, 0 == countDiff || 1 == countDiff); |
| } else if (countDiff < 0) { |
| result = false; |
| REPORTER_ASSERT(reporter, countDiff >= 0); |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| if (expectedDoneCnt != promiseChecker.fDoneCount) { |
| result = false; |
| REPORTER_ASSERT(reporter, expectedDoneCnt == promiseChecker.fDoneCount); |
| } |
| |
| return result; |
| } |
| |
| DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTestNoDelayedRelease, reporter, ctxInfo) { |
| const int kWidth = 10; |
| const int kHeight = 10; |
| |
| GrContext* ctx = ctxInfo.grContext(); |
| GrGpu* gpu = ctx->priv().getGpu(); |
| |
| for (bool releaseImageEarly : {true, false}) { |
| GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture( |
| nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, true, GrMipMapped::kNo); |
| REPORTER_ASSERT(reporter, backendTex.isValid()); |
| |
| GrBackendFormat backendFormat = backendTex.getBackendFormat(); |
| REPORTER_ASSERT(reporter, backendFormat.isValid()); |
| |
| PromiseTextureChecker promiseChecker(backendTex, reporter, false); |
| 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, |
| SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo)); |
| |
| 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; |
| ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| |
| canvas->drawImage(refImg, 0, 0); |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| bool isVulkan = GrBackendApi::kVulkan == ctx->backend(); |
| canvas->flush(); |
| expectedFulfillCnt++; |
| expectedReleaseCnt++; |
| if (isVulkan) { |
| balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne; |
| } |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| !isVulkan, |
| expectedDoneCnt, |
| reporter)); |
| |
| gpu->testingOnly_flushGpuAndSync(); |
| balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| 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, |
| balanceExpecation, |
| 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++; |
| if (isVulkan) { |
| balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne; |
| } |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| !isVulkan, |
| expectedDoneCnt, |
| reporter)); |
| |
| canvas->drawImage(refImg, 0, 0); |
| |
| if (releaseImageEarly) { |
| refImg.reset(); |
| } |
| |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| !isVulkan, |
| expectedDoneCnt, |
| reporter)); |
| |
| canvas->flush(); |
| expectedFulfillCnt++; |
| |
| gpu->testingOnly_flushGpuAndSync(); |
| expectedReleaseCnt++; |
| if (releaseImageEarly) { |
| expectedDoneCnt++; |
| } |
| balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| 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, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| gpu->deleteTestingOnlyBackendTexture(backendTex); |
| } |
| } |
| |
| DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTestDelayedRelease, reporter, ctxInfo) { |
| const int kWidth = 10; |
| const int kHeight = 10; |
| |
| GrContext* ctx = ctxInfo.grContext(); |
| GrGpu* gpu = ctx->priv().getGpu(); |
| |
| GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture( |
| nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, true, GrMipMapped::kNo); |
| REPORTER_ASSERT(reporter, backendTex.isValid()); |
| |
| GrBackendFormat backendFormat = backendTex.getBackendFormat(); |
| REPORTER_ASSERT(reporter, backendFormat.isValid()); |
| |
| PromiseTextureChecker promiseChecker(backendTex, reporter, false); |
| 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, |
| SkDeferredDisplayListRecorder::DelayReleaseCallback::kYes)); |
| |
| 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; |
| ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| |
| canvas->drawImage(refImg, 0, 0); |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| bool isVulkan = GrBackendApi::kVulkan == ctx->backend(); |
| canvas->flush(); |
| expectedFulfillCnt++; |
| // Because we've delayed release, we expect a +1 balance. |
| balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| !isVulkan, |
| expectedDoneCnt, |
| reporter)); |
| |
| gpu->testingOnly_flushGpuAndSync(); |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| canvas->drawImage(refImg, 0, 0); |
| canvas->drawImage(refImg, 0, 0); |
| |
| canvas->flush(); |
| |
| gpu->testingOnly_flushGpuAndSync(); |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| canvas->drawImage(refImg, 0, 0); |
| canvas->flush(); |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| !isVulkan, |
| expectedDoneCnt, |
| reporter)); |
| |
| canvas->drawImage(refImg, 0, 0); |
| |
| refImg.reset(); |
| |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| !isVulkan, |
| expectedDoneCnt, |
| reporter)); |
| |
| canvas->flush(); |
| gpu->testingOnly_flushGpuAndSync(); |
| // We released the image already and we flushed and synced. |
| balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| expectedReleaseCnt++; |
| expectedDoneCnt++; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| !isVulkan, |
| expectedDoneCnt, |
| reporter)); |
| |
| gpu->deleteTestingOnlyBackendTexture(backendTex); |
| } |
| |
| // Tests replacing the backing texture for a promise image after a release and then refulfilling in |
| // the SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo case. |
| DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTextureReuse, reporter, ctxInfo) { |
| const int kWidth = 10; |
| const int kHeight = 10; |
| |
| GrContext* ctx = ctxInfo.grContext(); |
| GrGpu* gpu = ctx->priv().getGpu(); |
| |
| GrBackendTexture backendTex1 = gpu->createTestingOnlyBackendTexture( |
| nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, false, GrMipMapped::kNo); |
| GrBackendTexture backendTex2 = gpu->createTestingOnlyBackendTexture( |
| nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, false, GrMipMapped::kNo); |
| GrBackendTexture backendTex3 = gpu->createTestingOnlyBackendTexture( |
| nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, false, GrMipMapped::kNo); |
| REPORTER_ASSERT(reporter, backendTex1.isValid()); |
| REPORTER_ASSERT(reporter, backendTex2.isValid()); |
| REPORTER_ASSERT(reporter, backendTex3.isValid()); |
| |
| GrBackendFormat backendFormat = backendTex1.getBackendFormat(); |
| REPORTER_ASSERT(reporter, backendFormat.isValid()); |
| REPORTER_ASSERT(reporter, backendFormat == backendTex2.getBackendFormat()); |
| REPORTER_ASSERT(reporter, backendFormat == backendTex3.getBackendFormat()); |
| |
| PromiseTextureChecker promiseChecker(backendTex1, reporter, true); |
| 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, |
| SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo)); |
| |
| SkImageInfo info = |
| SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType); |
| 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); |
| canvas->drawImage(refImg, 5, 5); |
| ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| bool isVulkan = GrBackendApi::kVulkan == ctx->backend(); |
| canvas->flush(); |
| expectedFulfillCnt++; |
| expectedReleaseCnt++; |
| if (isVulkan) { |
| balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne; |
| } |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| !isVulkan, |
| expectedDoneCnt, |
| reporter)); |
| REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals( |
| promiseChecker.fLastFulfilledTexture, backendTex1)); |
| // We should have put a GrTexture for this fulfillment into the cache. |
| auto keys = promiseChecker.uniqueKeys(); |
| REPORTER_ASSERT(reporter, keys.count() == 1); |
| GrUniqueKey texKey1; |
| if (keys.count()) { |
| texKey1 = keys[0]; |
| } |
| REPORTER_ASSERT(reporter, texKey1.isValid()); |
| REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey1)); |
| |
| gpu->testingOnly_flushGpuAndSync(); |
| balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| REPORTER_ASSERT(reporter, |
| GrBackendTexture::TestingOnly_Equals( |
| promiseChecker.replaceTexture()->backendTexture(), backendTex1)); |
| gpu->deleteTestingOnlyBackendTexture(backendTex1); |
| |
| ctx->priv().getResourceCache()->purgeAsNeeded(); |
| // We should have invalidated the key on the previously cached texture (after ensuring |
| // invalidation messages have been processed by calling purgeAsNeeded.) |
| REPORTER_ASSERT(reporter, !ctx->priv().resourceProvider()->findByUniqueKey<>(texKey1)); |
| |
| promiseChecker.replaceTexture(backendTex2); |
| |
| canvas->drawImage(refImg, 0, 0); |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| canvas->flush(); |
| expectedFulfillCnt++; |
| expectedReleaseCnt++; |
| if (isVulkan) { |
| balanceExpecation = ReleaseBalanceExpecation::kBalancedOrPlusOne; |
| } |
| // Second texture should be in the cache. |
| keys = promiseChecker.uniqueKeys(); |
| REPORTER_ASSERT(reporter, keys.count() == 1); |
| GrUniqueKey texKey2; |
| if (keys.count()) { |
| texKey2 = keys[0]; |
| } |
| REPORTER_ASSERT(reporter, texKey2.isValid() && texKey2 != texKey1); |
| REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey2)); |
| |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| !isVulkan, |
| expectedDoneCnt, |
| reporter)); |
| REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals( |
| promiseChecker.fLastFulfilledTexture, backendTex2)); |
| |
| gpu->testingOnly_flushGpuAndSync(); |
| balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| // Because we have kept the SkPromiseImageTexture alive, we should be able to use it again and |
| // hit the cache. |
| ctx->priv().getResourceCache()->purgeAsNeeded(); |
| REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey2)); |
| |
| canvas->drawImage(refImg, 0, 0); |
| |
| canvas->flush(); |
| gpu->testingOnly_flushGpuAndSync(); |
| expectedFulfillCnt++; |
| expectedReleaseCnt++; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| // Make sure we didn't add another key and that the second texture is still alive in the cache. |
| keys = promiseChecker.uniqueKeys(); |
| REPORTER_ASSERT(reporter, keys.count() == 1); |
| if (keys.count()) { |
| REPORTER_ASSERT(reporter, texKey2 == keys[0]); |
| } |
| ctx->priv().getResourceCache()->purgeAsNeeded(); |
| REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey2)); |
| |
| // Now we test keeping tex2 alive but fulfilling with a new texture. |
| sk_sp<const SkPromiseImageTexture> promiseImageTexture2 = |
| promiseChecker.replaceTexture(backendTex3); |
| REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals( |
| promiseImageTexture2->backendTexture(), backendTex2)); |
| |
| canvas->drawImage(refImg, 0, 0); |
| |
| canvas->flush(); |
| gpu->testingOnly_flushGpuAndSync(); |
| expectedFulfillCnt++; |
| expectedReleaseCnt++; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| keys = promiseChecker.uniqueKeys(); |
| REPORTER_ASSERT(reporter, keys.count() == 1); |
| GrUniqueKey texKey3; |
| if (keys.count()) { |
| texKey3 = keys[0]; |
| } |
| ctx->priv().getResourceCache()->purgeAsNeeded(); |
| REPORTER_ASSERT(reporter, !ctx->priv().resourceProvider()->findByUniqueKey<>(texKey2)); |
| REPORTER_ASSERT(reporter, ctx->priv().resourceProvider()->findByUniqueKey<>(texKey3)); |
| gpu->deleteTestingOnlyBackendTexture(promiseImageTexture2->backendTexture()); |
| |
| // Make a new promise image also backed by texture 3. |
| sk_sp<SkImage> refImg2( |
| SkImage_Gpu::MakePromiseTexture( |
| ctx, backendFormat, kWidth, kHeight, |
| GrMipMapped::kNo, texOrigin, |
| kRGBA_8888_SkColorType, kPremul_SkAlphaType, |
| nullptr, |
| PromiseTextureChecker::Fulfill, |
| PromiseTextureChecker::Release, |
| PromiseTextureChecker::Done, |
| &promiseChecker, |
| SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo)); |
| canvas->drawImage(refImg, 0, 0); |
| canvas->drawImage(refImg2, 1, 1); |
| |
| canvas->flush(); |
| gpu->testingOnly_flushGpuAndSync(); |
| expectedFulfillCnt += 2; |
| expectedReleaseCnt += 2; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| // The two images should share a single GrTexture by using the same key. The key is only |
| // dependent on the pixel config and the PromiseImageTexture key. |
| keys = promiseChecker.uniqueKeys(); |
| REPORTER_ASSERT(reporter, keys.count() == 1); |
| if (keys.count() > 0) { |
| REPORTER_ASSERT(reporter, texKey3 == keys[0]); |
| } |
| ctx->priv().getResourceCache()->purgeAsNeeded(); |
| |
| // If we delete the SkPromiseImageTexture we should trigger both key removals. |
| REPORTER_ASSERT(reporter, |
| GrBackendTexture::TestingOnly_Equals( |
| promiseChecker.replaceTexture()->backendTexture(), backendTex3)); |
| |
| ctx->priv().getResourceCache()->purgeAsNeeded(); |
| REPORTER_ASSERT(reporter, !ctx->priv().resourceProvider()->findByUniqueKey<>(texKey3)); |
| gpu->deleteTestingOnlyBackendTexture(backendTex3); |
| |
| // After deleting each image we should get a done call. |
| refImg.reset(); |
| ++expectedDoneCnt; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| refImg2.reset(); |
| ++expectedDoneCnt; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| } |
| |
| DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTextureReuseDifferentConfig, reporter, ctxInfo) { |
| // Try making two promise SkImages backed by the same texture but with different configs. |
| // This will only be testable on backends where a single texture format (8bit red unorm) can |
| // be used for alpha and gray image color types. |
| |
| const int kWidth = 10; |
| const int kHeight = 10; |
| |
| GrContext* ctx = ctxInfo.grContext(); |
| GrGpu* gpu = ctx->priv().getGpu(); |
| |
| GrBackendTexture backendTex1 = gpu->createTestingOnlyBackendTexture( |
| nullptr, kWidth, kHeight, GrColorType::kGray_8, false, GrMipMapped::kNo); |
| REPORTER_ASSERT(reporter, backendTex1.isValid()); |
| |
| GrBackendTexture backendTex2 = gpu->createTestingOnlyBackendTexture( |
| nullptr, kWidth, kHeight, GrColorType::kAlpha_8, false, GrMipMapped::kNo); |
| REPORTER_ASSERT(reporter, backendTex2.isValid()); |
| if (backendTex1.getBackendFormat() != backendTex2.getBackendFormat()) { |
| gpu->deleteTestingOnlyBackendTexture(backendTex1); |
| return; |
| } |
| // We only needed this texture to check that alpha and gray color types use the same format. |
| gpu->deleteTestingOnlyBackendTexture(backendTex2); |
| |
| SkImageInfo info = |
| SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType); |
| sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info); |
| SkCanvas* canvas = surface->getCanvas(); |
| |
| for (auto delayRelease : {SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo, |
| SkDeferredDisplayListRecorder::DelayReleaseCallback::kYes}) { |
| PromiseTextureChecker promiseChecker(backendTex1, reporter, true); |
| sk_sp<SkImage> alphaImg(SkImage_Gpu::MakePromiseTexture( |
| ctx, backendTex1.getBackendFormat(), kWidth, kHeight, GrMipMapped::kNo, |
| kTopLeft_GrSurfaceOrigin, kAlpha_8_SkColorType, kPremul_SkAlphaType, nullptr, |
| PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release, |
| PromiseTextureChecker::Done, &promiseChecker, delayRelease)); |
| REPORTER_ASSERT(reporter, alphaImg); |
| |
| sk_sp<SkImage> grayImg(SkImage_Gpu::MakePromiseTexture( |
| ctx, backendTex1.getBackendFormat(), kWidth, kHeight, GrMipMapped::kNo, |
| kBottomLeft_GrSurfaceOrigin, kGray_8_SkColorType, kOpaque_SkAlphaType, nullptr, |
| PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release, |
| PromiseTextureChecker::Done, &promiseChecker, delayRelease)); |
| REPORTER_ASSERT(reporter, grayImg); |
| |
| canvas->drawImage(alphaImg, 0, 0); |
| canvas->drawImage(grayImg, 1, 1); |
| canvas->flush(); |
| gpu->testingOnly_flushGpuAndSync(); |
| |
| int expectedFulfillCnt = 2; |
| int expectedReleaseCnt = 0; |
| int expectedDoneCnt = 0; |
| ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kAny; |
| if (delayRelease == SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo) { |
| expectedReleaseCnt = 2; |
| balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| } |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| |
| // Because they use different configs, each image should have created a different GrTexture |
| // and they both should still be cached. |
| ctx->priv().getResourceCache()->purgeAsNeeded(); |
| |
| auto keys = promiseChecker.uniqueKeys(); |
| REPORTER_ASSERT(reporter, keys.count() == 2); |
| for (const auto& key : keys) { |
| auto surf = ctx->priv().resourceProvider()->findByUniqueKey<GrSurface>(key); |
| REPORTER_ASSERT(reporter, surf && surf->asTexture()); |
| if (surf && surf->asTexture()) { |
| REPORTER_ASSERT(reporter, |
| !GrBackendTexture::TestingOnly_Equals( |
| backendTex1, surf->asTexture()->getBackendTexture())); |
| } |
| } |
| |
| // Change the backing texture, this should invalidate the keys. |
| promiseChecker.replaceTexture(); |
| ctx->priv().getResourceCache()->purgeAsNeeded(); |
| |
| for (const auto& key : keys) { |
| auto surf = ctx->priv().resourceProvider()->findByUniqueKey<GrSurface>(key); |
| REPORTER_ASSERT(reporter, !surf); |
| } |
| } |
| gpu->deleteTestingOnlyBackendTexture(backendTex1); |
| } |
| |
| DEF_GPUTEST(PromiseImageTextureShutdown, reporter, ctxInfo) { |
| const int kWidth = 10; |
| const int kHeight = 10; |
| |
| // Different ways of killing contexts. |
| using DeathFn = std::function<void(sk_gpu_test::GrContextFactory*, GrContext*)>; |
| DeathFn destroy = [](sk_gpu_test::GrContextFactory* factory, GrContext* context) { |
| factory->destroyContexts(); |
| }; |
| DeathFn abandon = [](sk_gpu_test::GrContextFactory* factory, GrContext* context) { |
| context->abandonContext(); |
| }; |
| DeathFn releaseResourcesAndAbandon = [](sk_gpu_test::GrContextFactory* factory, |
| GrContext* context) { |
| context->releaseResourcesAndAbandonContext(); |
| }; |
| |
| for (int type = 0; type < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++type) { |
| auto contextType = static_cast<sk_gpu_test::GrContextFactory::ContextType>(type); |
| // These tests are difficult to get working with Vulkan. See http://skbug.com/8705 |
| // and http://skbug.com/8275 |
| GrBackendApi api = sk_gpu_test::GrContextFactory::ContextTypeBackend(contextType); |
| if (api == GrBackendApi::kVulkan) { |
| continue; |
| } |
| DeathFn contextKillers[] = {destroy, abandon, releaseResourcesAndAbandon}; |
| for (auto contextDeath : contextKillers) { |
| sk_gpu_test::GrContextFactory factory; |
| auto ctx = factory.get(contextType); |
| if (!ctx) { |
| continue; |
| } |
| GrGpu* gpu = ctx->priv().getGpu(); |
| |
| GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture( |
| nullptr, kWidth, kHeight, GrColorType::kAlpha_8, false, GrMipMapped::kNo); |
| REPORTER_ASSERT(reporter, backendTex.isValid()); |
| |
| SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, |
| kPremul_SkAlphaType); |
| sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info); |
| SkCanvas* canvas = surface->getCanvas(); |
| |
| PromiseTextureChecker promiseChecker(backendTex, reporter, false); |
| sk_sp<SkImage> image(SkImage_Gpu::MakePromiseTexture( |
| ctx, backendTex.getBackendFormat(), kWidth, kHeight, GrMipMapped::kNo, |
| kTopLeft_GrSurfaceOrigin, kAlpha_8_SkColorType, kPremul_SkAlphaType, nullptr, |
| PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release, |
| PromiseTextureChecker::Done, &promiseChecker, |
| SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo)); |
| REPORTER_ASSERT(reporter, image); |
| |
| canvas->drawImage(image, 0, 0); |
| image.reset(); |
| // If the surface still holds a ref to the context then the factory will not be able |
| // to destroy the context (and instead will release-all-and-abandon). |
| surface.reset(); |
| |
| ctx->flush(); |
| contextDeath(&factory, ctx); |
| |
| int expectedFulfillCnt = 1; |
| int expectedReleaseCnt = 1; |
| int expectedDoneCnt = 1; |
| ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kBalanced; |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, |
| balanceExpecation, |
| expectedFulfillCnt, |
| expectedReleaseCnt, |
| true, |
| expectedDoneCnt, |
| reporter)); |
| } |
| } |
| } |
| |
| DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTextureFullCache, reporter, ctxInfo) { |
| const int kWidth = 10; |
| const int kHeight = 10; |
| |
| GrContext* ctx = ctxInfo.grContext(); |
| GrGpu* gpu = ctx->priv().getGpu(); |
| |
| GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture( |
| nullptr, kWidth, kHeight, GrColorType::kAlpha_8, false, GrMipMapped::kNo); |
| REPORTER_ASSERT(reporter, backendTex.isValid()); |
| |
| SkImageInfo info = |
| SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType); |
| sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info); |
| SkCanvas* canvas = surface->getCanvas(); |
| |
| PromiseTextureChecker promiseChecker(backendTex, reporter, false); |
| sk_sp<SkImage> image(SkImage_Gpu::MakePromiseTexture( |
| ctx, backendTex.getBackendFormat(), kWidth, kHeight, GrMipMapped::kNo, |
| kTopLeft_GrSurfaceOrigin, kAlpha_8_SkColorType, kPremul_SkAlphaType, nullptr, |
| PromiseTextureChecker::Fulfill, PromiseTextureChecker::Release, |
| PromiseTextureChecker::Done, &promiseChecker, |
| SkDeferredDisplayListRecorder::DelayReleaseCallback::kNo)); |
| REPORTER_ASSERT(reporter, image); |
| |
| // Make the cache full. This tests that we don't preemptively purge cached textures for |
| // fulfillment due to cache pressure. |
| static constexpr int kMaxResources = 10; |
| static constexpr int kMaxBytes = 100; |
| ctx->setResourceCacheLimits(kMaxResources, kMaxBytes); |
| sk_sp<GrTexture> textures[2 * kMaxResources]; |
| for (int i = 0; i < 2 * kMaxResources; ++i) { |
| GrSurfaceDesc desc; |
| desc.fConfig = kRGBA_8888_GrPixelConfig; |
| desc.fWidth = desc.fHeight = 100; |
| textures[i] = ctx->priv().resourceProvider()->createTexture(desc, SkBudgeted::kYes); |
| REPORTER_ASSERT(reporter, textures[i]); |
| } |
| |
| // Relying on the asserts in the promiseImageChecker to ensure that fulfills and releases are |
| // properly ordered. |
| canvas->drawImage(image, 0, 0); |
| canvas->flush(); |
| canvas->drawImage(image, 1, 0); |
| canvas->flush(); |
| canvas->drawImage(image, 2, 0); |
| canvas->flush(); |
| canvas->drawImage(image, 3, 0); |
| canvas->flush(); |
| canvas->drawImage(image, 4, 0); |
| canvas->flush(); |
| canvas->drawImage(image, 5, 0); |
| canvas->flush(); |
| // Must call this to ensure that all callbacks are performed before the checker is destroyed. |
| gpu->testingOnly_flushGpuAndSync(); |
| gpu->deleteTestingOnlyBackendTexture(backendTex); |
| } |