| /* | |
| * 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" | |
| #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; | |
| /** | |
| * Releases the SkPromiseImageTexture. Used to test that cached GrTexture representations | |
| * in the cache are freed. | |
| */ | |
| void releaseTexture() { fTexture.reset(); } | |
| SkTArray<GrUniqueKey> uniqueKeys() const { | |
| return fTexture->testingOnly_uniqueKeysToInvalidate(); | |
| } | |
| static sk_sp<SkPromiseImageTexture> Fulfill(void* self) { | |
| auto checker = static_cast<PromiseTextureChecker*>(self); | |
| checker->fFulfillCount++; | |
| 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(PromiseImageTest, 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)); | |
| 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(); | |
| surface->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); | |
| surface->flush(); | |
| gpu->testingOnly_flushGpuAndSync(); | |
| REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker, | |
| balanceExpecation, | |
| expectedFulfillCnt, | |
| expectedReleaseCnt, | |
| true, | |
| expectedDoneCnt, | |
| reporter)); | |
| canvas->drawImage(refImg, 0, 0); | |
| surface->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)); | |
| surface->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); | |
| } | |
| 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(); | |
| 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)); | |
| 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)); | |
| REPORTER_ASSERT(reporter, grayImg); | |
| canvas->drawImage(alphaImg, 0, 0); | |
| canvas->drawImage(grayImg, 1, 1); | |
| surface->flush(); | |
| gpu->testingOnly_flushGpuAndSync(); | |
| int expectedFulfillCnt = 2; | |
| int expectedReleaseCnt = 0; | |
| int expectedDoneCnt = 0; | |
| ReleaseBalanceExpecation balanceExpecation = ReleaseBalanceExpecation::kAny; | |
| 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.releaseTexture(); | |
| ctx->priv().getResourceCache()->purgeAsNeeded(); | |
| for (const auto& key : keys) { | |
| auto surf = ctx->priv().resourceProvider()->findByUniqueKey<GrSurface>(key); | |
| REPORTER_ASSERT(reporter, !surf); | |
| } | |
| // Must do this to ensure all callbacks occur before the PromiseImageChecker goes out of scope. | |
| alphaImg.reset(); | |
| grayImg.reset(); | |
| ctx->flush(); | |
| ctx->priv().getGpu()->testingOnly_flushGpuAndSync(); | |
| 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)); | |
| 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)); | |
| 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); | |
| surface->flush(); | |
| canvas->drawImage(image, 1, 0); | |
| surface->flush(); | |
| canvas->drawImage(image, 2, 0); | |
| surface->flush(); | |
| canvas->drawImage(image, 3, 0); | |
| surface->flush(); | |
| canvas->drawImage(image, 4, 0); | |
| surface->flush(); | |
| canvas->drawImage(image, 5, 0); | |
| surface->flush(); | |
| // Must call these to ensure that all callbacks are performed before the checker is destroyed. | |
| image.reset(); | |
| ctx->flush(); | |
| gpu->testingOnly_flushGpuAndSync(); | |
| gpu->deleteTestingOnlyBackendTexture(backendTex); | |
| } |