| /* | |
| * 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 "include/core/SkAlphaType.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkColorFilter.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/core/SkImage.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkSamplingOptions.h" | |
| #include "include/core/SkShader.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/gpu/GpuTypes.h" | |
| #include "include/gpu/GrBackendSurface.h" | |
| #include "include/gpu/GrDirectContext.h" | |
| #include "include/gpu/GrTypes.h" | |
| #include "include/gpu/ganesh/SkSurfaceGanesh.h" | |
| #include "include/private/base/SkTArray.h" | |
| #include "include/private/chromium/GrDeferredDisplayListRecorder.h" | |
| #include "include/private/chromium/GrPromiseImageTexture.h" | |
| #include "include/private/chromium/SkImageChromium.h" | |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" | |
| #include "src/gpu/ganesh/GrCaps.h" | |
| #include "src/gpu/ganesh/GrDirectContextPriv.h" | |
| #include "src/gpu/ganesh/GrResourceProvider.h" | |
| #include "src/gpu/ganesh/GrTexture.h" | |
| #include "tests/CtsEnforcement.h" | |
| #include "tests/Test.h" | |
| #include "tools/gpu/ContextType.h" | |
| #include "tools/gpu/FenceSync.h" | |
| #include "tools/gpu/ManagedBackendTexture.h" | |
| #include <cstddef> | |
| #include <functional> | |
| #include <utility> | |
| using namespace skia_private; | |
| struct GrContextOptions; | |
| 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(GrPromiseImageTexture::Make(tex)), fReporter(reporter), fShared(shared) {} | |
| sk_sp<GrPromiseImageTexture> fTexture; | |
| skiatest::Reporter* fReporter; | |
| bool fShared; | |
| int fFulfillCount = 0; | |
| int fReleaseCount = 0; | |
| static sk_sp<GrPromiseImageTexture> Fulfill(void* self) { | |
| auto checker = static_cast<PromiseTextureChecker*>(self); | |
| checker->fFulfillCount++; | |
| return checker->fTexture; | |
| } | |
| static void Release(void* self) { static_cast<PromiseTextureChecker*>(self)->fReleaseCount++; } | |
| }; | |
| enum class ReleaseBalanceExpectation { | |
| kBalanced, | |
| kAllUnbalanced, | |
| kUnknown, | |
| kUnbalancedByOne, | |
| kBalancedOrOffByOne, | |
| }; | |
| static void check_fulfill_and_release_cnts(skiatest::Reporter* reporter, | |
| const PromiseTextureChecker& promiseChecker, | |
| int expectedFulfillCnt, | |
| ReleaseBalanceExpectation releaseBalanceExpecation) { | |
| REPORTER_ASSERT(reporter, promiseChecker.fFulfillCount == expectedFulfillCnt); | |
| if (!expectedFulfillCnt) { | |
| // Release and Done should only ever be called after Fulfill. | |
| REPORTER_ASSERT(reporter, !promiseChecker.fReleaseCount); | |
| return; | |
| } | |
| int releaseDiff = promiseChecker.fFulfillCount - promiseChecker.fReleaseCount; | |
| switch (releaseBalanceExpecation) { | |
| case ReleaseBalanceExpectation::kBalanced: | |
| REPORTER_ASSERT(reporter, !releaseDiff); | |
| break; | |
| case ReleaseBalanceExpectation::kAllUnbalanced: | |
| REPORTER_ASSERT(reporter, releaseDiff == promiseChecker.fFulfillCount); | |
| break; | |
| case ReleaseBalanceExpectation::kUnknown: | |
| REPORTER_ASSERT(reporter, | |
| releaseDiff >= 0 && releaseDiff <= promiseChecker.fFulfillCount); | |
| break; | |
| case ReleaseBalanceExpectation::kUnbalancedByOne: | |
| REPORTER_ASSERT(reporter, releaseDiff == 1); | |
| break; | |
| case ReleaseBalanceExpectation::kBalancedOrOffByOne: | |
| REPORTER_ASSERT(reporter, releaseDiff == 0 || releaseDiff == 1); | |
| break; | |
| } | |
| } | |
| static void check_unfulfilled(const PromiseTextureChecker& promiseChecker, | |
| skiatest::Reporter* reporter) { | |
| check_fulfill_and_release_cnts(reporter, promiseChecker, 0, | |
| ReleaseBalanceExpectation::kBalanced); | |
| } | |
| static void check_only_fulfilled(skiatest::Reporter* reporter, | |
| const PromiseTextureChecker& promiseChecker, | |
| int expectedFulfillCnt = 1) { | |
| check_fulfill_and_release_cnts(reporter, promiseChecker, expectedFulfillCnt, | |
| ReleaseBalanceExpectation::kAllUnbalanced); | |
| } | |
| static void check_all_flushed_but_not_synced(skiatest::Reporter* reporter, | |
| const PromiseTextureChecker& promiseChecker, | |
| GrBackendApi api, | |
| int expectedFulfillCnt = 1) { | |
| ReleaseBalanceExpectation releaseBalanceExpectation = ReleaseBalanceExpectation::kBalanced; | |
| // On Vulkan and D3D Done isn't guaranteed to be called until a sync has occurred. | |
| if (api == GrBackendApi::kVulkan || api == GrBackendApi::kDirect3D) { | |
| releaseBalanceExpectation = expectedFulfillCnt == 1 | |
| ? ReleaseBalanceExpectation::kBalancedOrOffByOne | |
| : ReleaseBalanceExpectation::kUnknown; | |
| } | |
| check_fulfill_and_release_cnts(reporter, promiseChecker, expectedFulfillCnt, | |
| releaseBalanceExpectation); | |
| } | |
| static void check_all_done(skiatest::Reporter* reporter, | |
| const PromiseTextureChecker& promiseChecker, | |
| int expectedFulfillCnt = 1) { | |
| check_fulfill_and_release_cnts(reporter, promiseChecker, expectedFulfillCnt, | |
| ReleaseBalanceExpectation::kBalanced); | |
| } | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(PromiseImageTest, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| const int kWidth = 10; | |
| const int kHeight = 10; | |
| auto ctx = ctxInfo.directContext(); | |
| GrBackendTexture backendTex = ctx->createBackendTexture(kWidth, | |
| kHeight, | |
| kRGBA_8888_SkColorType, | |
| SkColors::kTransparent, | |
| skgpu::Mipmapped::kNo, | |
| GrRenderable::kYes, | |
| GrProtected::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(SkImages::PromiseTextureFrom(ctx->threadSafeProxy(), | |
| backendFormat, | |
| {kWidth, kHeight}, | |
| skgpu::Mipmapped::kNo, | |
| texOrigin, | |
| kRGBA_8888_SkColorType, | |
| kPremul_SkAlphaType, | |
| nullptr, | |
| PromiseTextureChecker::Fulfill, | |
| PromiseTextureChecker::Release, | |
| &promiseChecker)); | |
| SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight); | |
| sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(ctx, skgpu::Budgeted::kNo, info); | |
| SkCanvas* canvas = surface->getCanvas(); | |
| canvas->drawImage(refImg, 0, 0); | |
| check_unfulfilled(promiseChecker, reporter); | |
| ctx->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| // We still own the image so we should not have called Release or Done. | |
| check_only_fulfilled(reporter, promiseChecker); | |
| ctx->submit(GrSyncCpu::kYes); | |
| check_only_fulfilled(reporter, promiseChecker); | |
| canvas->drawImage(refImg, 0, 0); | |
| canvas->drawImage(refImg, 0, 0); | |
| ctx->flushAndSubmit(surface.get(), GrSyncCpu::kYes); | |
| // Image should still be fulfilled from the first time we drew/flushed it. | |
| check_only_fulfilled(reporter, promiseChecker); | |
| canvas->drawImage(refImg, 0, 0); | |
| ctx->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| check_only_fulfilled(reporter, promiseChecker); | |
| canvas->drawImage(refImg, 0, 0); | |
| refImg.reset(); | |
| // We no longer own the image but the last draw is still unflushed. | |
| check_only_fulfilled(reporter, promiseChecker); | |
| ctx->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| // Flushing should have called Release. Depending on the backend and timing it may have called | |
| // done. | |
| check_all_flushed_but_not_synced(reporter, promiseChecker, ctx->backend()); | |
| ctx->submit(GrSyncCpu::kYes); | |
| // Now Done should definitely have been called. | |
| check_all_done(reporter, promiseChecker); | |
| ctx->deleteBackendTexture(backendTex); | |
| } | |
| DEF_GANESH_TEST(PromiseImageTextureShutdown, reporter, ctxInfo, CtsEnforcement::kNever) { | |
| const int kWidth = 10; | |
| const int kHeight = 10; | |
| // Different ways of killing contexts. | |
| using DeathFn = std::function<void(sk_gpu_test::GrContextFactory*, GrDirectContext*)>; | |
| DeathFn destroy = [](sk_gpu_test::GrContextFactory* factory, GrDirectContext*) { | |
| factory->destroyContexts(); | |
| }; | |
| DeathFn abandon = [](sk_gpu_test::GrContextFactory* factory, GrDirectContext* dContext) { | |
| dContext->abandonContext(); | |
| }; | |
| DeathFn releaseResourcesAndAbandon = [](sk_gpu_test::GrContextFactory* factory, | |
| GrDirectContext* dContext) { | |
| dContext->releaseResourcesAndAbandonContext(); | |
| }; | |
| for (int type = 0; type < skgpu::kContextTypeCount; ++type) { | |
| auto contextType = static_cast<skgpu::ContextType>(type); | |
| // These tests are difficult to get working with Vulkan. See http://skbug.com/8705 | |
| // and http://skbug.com/8275 | |
| // And Direct3D, for similar reasons. | |
| GrBackendApi api = skgpu::ganesh::ContextTypeBackend(contextType); | |
| if (api == GrBackendApi::kUnsupported || api == GrBackendApi::kVulkan || | |
| api == GrBackendApi::kDirect3D) { | |
| continue; | |
| } | |
| DeathFn contextKillers[] = {destroy, abandon, releaseResourcesAndAbandon}; | |
| for (const DeathFn& contextDeath : contextKillers) { | |
| sk_gpu_test::GrContextFactory factory; | |
| auto ctx = factory.get(contextType); | |
| if (!ctx) { | |
| continue; | |
| } | |
| auto mbet = sk_gpu_test::ManagedBackendTexture::MakeWithoutData(ctx, | |
| kWidth, | |
| kHeight, | |
| kAlpha_8_SkColorType, | |
| skgpu::Mipmapped::kNo, | |
| GrRenderable::kNo); | |
| if (!mbet) { | |
| ERRORF(reporter, "Could not create texture alpha texture."); | |
| continue; | |
| } | |
| SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, | |
| kPremul_SkAlphaType); | |
| sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(ctx, skgpu::Budgeted::kNo, info); | |
| SkCanvas* canvas = surface->getCanvas(); | |
| PromiseTextureChecker promiseChecker(mbet->texture(), reporter, false); | |
| sk_sp<SkImage> image(SkImages::PromiseTextureFrom(ctx->threadSafeProxy(), | |
| mbet->texture().getBackendFormat(), | |
| {kWidth, kHeight}, | |
| skgpu::Mipmapped::kNo, | |
| kTopLeft_GrSurfaceOrigin, | |
| kAlpha_8_SkColorType, | |
| kPremul_SkAlphaType, | |
| /*color space*/ nullptr, | |
| PromiseTextureChecker::Fulfill, | |
| PromiseTextureChecker::Release, | |
| &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->flushAndSubmit(); | |
| contextDeath(&factory, ctx); | |
| check_all_done(reporter, promiseChecker); | |
| } | |
| } | |
| } | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(PromiseImageTextureFullCache, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| const int kWidth = 10; | |
| const int kHeight = 10; | |
| auto dContext = ctxInfo.directContext(); | |
| GrBackendTexture backendTex = dContext->createBackendTexture(kWidth, | |
| kHeight, | |
| kAlpha_8_SkColorType, | |
| SkColors::kTransparent, | |
| skgpu::Mipmapped::kNo, | |
| GrRenderable::kNo, | |
| GrProtected::kNo); | |
| REPORTER_ASSERT(reporter, backendTex.isValid()); | |
| SkImageInfo info = | |
| SkImageInfo::Make(kWidth, kHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType); | |
| sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info); | |
| SkCanvas* canvas = surface->getCanvas(); | |
| PromiseTextureChecker promiseChecker(backendTex, reporter, false); | |
| sk_sp<SkImage> image(SkImages::PromiseTextureFrom(dContext->threadSafeProxy(), | |
| backendTex.getBackendFormat(), | |
| {kWidth, kHeight}, | |
| skgpu::Mipmapped::kNo, | |
| kTopLeft_GrSurfaceOrigin, | |
| kAlpha_8_SkColorType, | |
| kPremul_SkAlphaType, | |
| nullptr, | |
| PromiseTextureChecker::Fulfill, | |
| PromiseTextureChecker::Release, | |
| &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 kMaxBytes = 1; | |
| dContext->setResourceCacheLimit(kMaxBytes); | |
| TArray<sk_sp<GrTexture>> textures; | |
| for (int i = 0; i < 5; ++i) { | |
| auto format = dContext->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888, | |
| GrRenderable::kNo); | |
| textures.emplace_back(dContext->priv().resourceProvider()->createTexture( | |
| {100, 100}, | |
| format, | |
| GrTextureType::k2D, | |
| GrRenderable::kNo, | |
| 1, | |
| skgpu::Mipmapped::kNo, | |
| skgpu::Budgeted::kYes, | |
| GrProtected::kNo, | |
| /*label=*/"PromiseImageTextureFullCacheTest")); | |
| REPORTER_ASSERT(reporter, textures[i]); | |
| } | |
| size_t bytesUsed; | |
| dContext->getResourceCacheUsage(nullptr, &bytesUsed); | |
| REPORTER_ASSERT(reporter, bytesUsed > kMaxBytes); | |
| // Relying on the asserts in the promiseImageChecker to ensure that fulfills and releases are | |
| // properly ordered. | |
| canvas->drawImage(image, 0, 0); | |
| dContext->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| canvas->drawImage(image, 1, 0); | |
| dContext->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| canvas->drawImage(image, 2, 0); | |
| dContext->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| canvas->drawImage(image, 3, 0); | |
| dContext->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| canvas->drawImage(image, 4, 0); | |
| dContext->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| canvas->drawImage(image, 5, 0); | |
| dContext->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| // Must call these to ensure that all callbacks are performed before the checker is destroyed. | |
| image.reset(); | |
| dContext->flushAndSubmit(GrSyncCpu::kYes); | |
| dContext->deleteBackendTexture(backendTex); | |
| } | |
| // Test case where promise image fulfill returns nullptr. | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(PromiseImageNullFulfill, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| const int kWidth = 10; | |
| const int kHeight = 10; | |
| auto dContext = ctxInfo.directContext(); | |
| GrBackendFormat backendFormat = | |
| dContext->defaultBackendFormat(kRGBA_8888_SkColorType, GrRenderable::kYes); | |
| if (!backendFormat.isValid()) { | |
| ERRORF(reporter, "No valid default kRGBA_8888 texture format."); | |
| return; | |
| } | |
| struct Counts { | |
| int fFulfillCount = 0; | |
| int fReleaseCount = 0; | |
| } counts; | |
| auto fulfill = [](GrDeferredDisplayListRecorder::PromiseImageTextureContext ctx) { | |
| ++static_cast<Counts*>(ctx)->fFulfillCount; | |
| return sk_sp<GrPromiseImageTexture>(); | |
| }; | |
| auto release = [](GrDeferredDisplayListRecorder::PromiseImageTextureContext ctx) { | |
| ++static_cast<Counts*>(ctx)->fReleaseCount; | |
| }; | |
| GrSurfaceOrigin texOrigin = kTopLeft_GrSurfaceOrigin; | |
| sk_sp<SkImage> refImg(SkImages::PromiseTextureFrom(dContext->threadSafeProxy(), | |
| backendFormat, | |
| {kWidth, kHeight}, | |
| skgpu::Mipmapped::kNo, | |
| texOrigin, | |
| kRGBA_8888_SkColorType, | |
| kPremul_SkAlphaType, | |
| nullptr, | |
| fulfill, | |
| release, | |
| &counts)); | |
| SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight); | |
| sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info); | |
| SkCanvas* canvas = surface->getCanvas(); | |
| // Draw the image a few different ways. | |
| canvas->drawImage(refImg, 0, 0); | |
| SkPaint paint; | |
| paint.setColorFilter(SkColorFilters::LinearToSRGBGamma()); | |
| canvas->drawImage(refImg, 0, 0, SkSamplingOptions(), &paint); | |
| auto shader = refImg->makeShader(SkSamplingOptions()); | |
| REPORTER_ASSERT(reporter, shader); | |
| paint.setShader(std::move(shader)); | |
| canvas->drawRect(SkRect::MakeWH(1,1), paint); | |
| paint.setShader(nullptr); | |
| refImg.reset(); | |
| dContext->flushAndSubmit(surface.get(), GrSyncCpu::kNo); | |
| // We should only call each callback once and we should have made all the calls by this point. | |
| REPORTER_ASSERT(reporter, counts.fFulfillCount == 1); | |
| REPORTER_ASSERT(reporter, counts.fReleaseCount == 1); | |
| } |