| /* | |
| * Copyright 2023 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/gpu/graphite/BackendTexture.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColorFilter.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/gpu/graphite/Context.h" | |
| #include "include/gpu/graphite/Image.h" | |
| #include "include/gpu/graphite/Recording.h" | |
| #include "include/gpu/graphite/Surface.h" | |
| #include "include/gpu/graphite/YUVABackendTextures.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/ContextPriv.h" | |
| #include "src/gpu/graphite/RecordingPriv.h" | |
| #include "tests/Test.h" | |
| #include "tools/graphite/GraphiteTestContext.h" | |
| using namespace skgpu::graphite; | |
| namespace { | |
| struct PromiseImageChecker { | |
| PromiseImageChecker() = default; | |
| void checkImageReleased(skiatest::Reporter* reporter, int expectedReleaseCnt) { | |
| REPORTER_ASSERT(reporter, expectedReleaseCnt == fImageReleaseCount); | |
| } | |
| int fImageReleaseCount = 0; | |
| static void ImageRelease(void* self) { | |
| auto checker = reinterpret_cast<PromiseImageChecker*>(self); | |
| checker->fImageReleaseCount++; | |
| } | |
| }; | |
| struct PromiseTextureChecker { | |
| PromiseTextureChecker() = default; | |
| explicit PromiseTextureChecker(const BackendTexture& backendTex) { | |
| fBackendTextures[0] = backendTex; | |
| } | |
| explicit PromiseTextureChecker(const BackendTexture& backendTex0, | |
| const BackendTexture& backendTex1) | |
| : fHasTwoBackendTextures(true) { | |
| fBackendTextures[0] = backendTex0; | |
| fBackendTextures[1] = backendTex1; | |
| } | |
| int totalReleaseCount() const { return fTextureReleaseCounts[0] + fTextureReleaseCounts[1]; } | |
| bool fHasTwoBackendTextures = false; | |
| BackendTexture fBackendTextures[2]; | |
| int fFulfillCount = 0; | |
| int fTextureReleaseCounts[2] = { 0, 0 }; | |
| static std::tuple<BackendTexture, void*> Fulfill(void* self) { | |
| auto checker = reinterpret_cast<PromiseTextureChecker*>(self); | |
| checker->fFulfillCount++; | |
| if (checker->fHasTwoBackendTextures) { | |
| int whichToUse = checker->fFulfillCount % 2; | |
| return { checker->fBackendTextures[whichToUse], | |
| &checker->fTextureReleaseCounts[whichToUse] }; | |
| } else { | |
| return { checker->fBackendTextures[0], &checker->fTextureReleaseCounts[0] }; | |
| } | |
| } | |
| static void TextureRelease(void* context) { | |
| int* releaseCount = reinterpret_cast<int*>(context); | |
| (*releaseCount)++; | |
| } | |
| }; | |
| enum class ReleaseBalanceExpectation { | |
| kBalanced, | |
| kOffByOne, // fulfill calls ahead of release calls by 1 | |
| kOffByTwo, // fulfill calls ahead of release calls by 2 | |
| kFulfillsOnly, // 'n' fulfill calls, 0 release calls | |
| }; | |
| void check_fulfill_and_release_cnts(skiatest::Reporter* reporter, | |
| const PromiseImageChecker& promiseImageChecker, | |
| const PromiseTextureChecker* promiseTextureCheckers, | |
| int expectedFulfillCnt, | |
| ReleaseBalanceExpectation releaseBalanceExpectation) { | |
| // If one fulfill fails the following ones won't be called, so we check the max value | |
| int maxFulfillCnt = 0; | |
| for (int i = 0; i < 4; ++i) { | |
| maxFulfillCnt = std::max(promiseTextureCheckers[i].fFulfillCount, maxFulfillCnt); | |
| if (!expectedFulfillCnt) { | |
| // Release should only ever be called after Fulfill. | |
| REPORTER_ASSERT(reporter, promiseImageChecker.fImageReleaseCount == 0); | |
| REPORTER_ASSERT(reporter, promiseTextureCheckers[i].totalReleaseCount() == 0); | |
| return; | |
| } | |
| if (promiseTextureCheckers[i].fFulfillCount) { | |
| int releaseDiff = promiseTextureCheckers[i].fFulfillCount - | |
| promiseTextureCheckers[i].totalReleaseCount(); | |
| switch (releaseBalanceExpectation) { | |
| case ReleaseBalanceExpectation::kBalanced: | |
| SkASSERT(!releaseDiff); | |
| REPORTER_ASSERT(reporter, !releaseDiff); | |
| break; | |
| case ReleaseBalanceExpectation::kOffByOne: | |
| SkASSERT(releaseDiff == 1); | |
| REPORTER_ASSERT(reporter, releaseDiff == 1); | |
| break; | |
| case ReleaseBalanceExpectation::kOffByTwo: | |
| SkASSERT(releaseDiff == 2); | |
| REPORTER_ASSERT(reporter, releaseDiff == 2); | |
| break; | |
| case ReleaseBalanceExpectation::kFulfillsOnly: | |
| REPORTER_ASSERT(reporter, promiseTextureCheckers[i].totalReleaseCount() == 0); | |
| break; | |
| } | |
| } | |
| } | |
| SkASSERT(maxFulfillCnt == expectedFulfillCnt); | |
| REPORTER_ASSERT(reporter, maxFulfillCnt == expectedFulfillCnt); | |
| } | |
| void check_unfulfilled(const PromiseImageChecker& promiseImageChecker, | |
| const PromiseTextureChecker* promiseTextureCheckers, | |
| skiatest::Reporter* reporter) { | |
| check_fulfill_and_release_cnts(reporter, promiseImageChecker, promiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 0, | |
| ReleaseBalanceExpectation::kBalanced); | |
| } | |
| void check_fulfilled_ahead_by_one(skiatest::Reporter* reporter, | |
| const PromiseImageChecker& promiseImageChecker, | |
| const PromiseTextureChecker* promiseTextureCheckers, | |
| int expectedFulfillCnt) { | |
| check_fulfill_and_release_cnts(reporter, promiseImageChecker, promiseTextureCheckers, | |
| expectedFulfillCnt, ReleaseBalanceExpectation::kOffByOne); | |
| } | |
| void check_fulfilled_ahead_by_two(skiatest::Reporter* reporter, | |
| const PromiseImageChecker& promiseImageChecker, | |
| const PromiseTextureChecker* promiseTextureCheckers, | |
| int expectedFulfillCnt) { | |
| check_fulfill_and_release_cnts(reporter, promiseImageChecker, promiseTextureCheckers, | |
| expectedFulfillCnt, ReleaseBalanceExpectation::kOffByTwo); | |
| } | |
| void check_all_done(skiatest::Reporter* reporter, | |
| const PromiseImageChecker& promiseImageChecker, | |
| const PromiseTextureChecker* promiseTextureCheckers, | |
| int expectedFulfillCnt) { | |
| check_fulfill_and_release_cnts(reporter, promiseImageChecker, promiseTextureCheckers, | |
| expectedFulfillCnt, ReleaseBalanceExpectation::kBalanced); | |
| } | |
| void check_fulfills_only(skiatest::Reporter* reporter, | |
| const PromiseImageChecker& promiseImageChecker, | |
| const PromiseTextureChecker* promiseTextureCheckers, | |
| int expectedFulfillCnt) { | |
| check_fulfill_and_release_cnts(reporter, promiseImageChecker, promiseTextureCheckers, | |
| expectedFulfillCnt, ReleaseBalanceExpectation::kFulfillsOnly); | |
| } | |
| struct TestCtx { | |
| TestCtx() {} | |
| ~TestCtx() { | |
| for (int i = 0; i < 8; ++i) { | |
| if (fBackendTextures[i].isValid()) { | |
| fContext->deleteBackendTexture(fBackendTextures[i]); | |
| } | |
| } | |
| } | |
| Context* fContext; | |
| std::unique_ptr<Recorder> fRecorder; | |
| BackendTexture fBackendTextures[8]; | |
| PromiseImageChecker fPromiseImageChecker; | |
| SkImages::GraphitePromiseImageContext fImageContext = &fPromiseImageChecker; | |
| PromiseTextureChecker fPromiseTextureCheckers[4]; | |
| SkImages::GraphitePromiseTextureFulfillContext fTextureContexts[4] = { | |
| &fPromiseTextureCheckers[0], | |
| &fPromiseTextureCheckers[1], | |
| &fPromiseTextureCheckers[2], | |
| &fPromiseTextureCheckers[3], | |
| }; | |
| sk_sp<SkImage> fImg; | |
| sk_sp<SkSurface> fSurface; | |
| }; | |
| void setup_test_context(Context* context, | |
| skiatest::Reporter* reporter, | |
| TestCtx* testCtx, | |
| SkISize dimensions, | |
| Volatile isVolatile, | |
| bool invalidBackendTex) { | |
| testCtx->fContext = context; | |
| const Caps* caps = context->priv().caps(); | |
| skgpu::Protected isProtected = skgpu::Protected(caps->protectedSupport()); | |
| testCtx->fRecorder = context->makeRecorder(); | |
| testCtx->fPromiseImageChecker = PromiseImageChecker(); | |
| TextureInfo textureInfo[4]; | |
| for (int i = 0; i < 4; ++i) { | |
| textureInfo[i] = caps->getDefaultSampledTextureInfo(kAlpha_8_SkColorType, | |
| skgpu::Mipmapped::kNo, | |
| isProtected, | |
| skgpu::Renderable::kYes); | |
| if (invalidBackendTex) { | |
| // Having invalid backend textures will invalidate all the fulfill calls | |
| REPORTER_ASSERT(reporter, !testCtx->fBackendTextures[i].isValid()); | |
| REPORTER_ASSERT(reporter, !testCtx->fBackendTextures[i+4].isValid()); | |
| } else { | |
| testCtx->fBackendTextures[i] = | |
| testCtx->fRecorder->createBackendTexture(dimensions, textureInfo[i]); | |
| REPORTER_ASSERT(reporter, testCtx->fBackendTextures[i].isValid()); | |
| if (isVolatile == Volatile::kYes) { | |
| testCtx->fBackendTextures[i+4] = | |
| testCtx->fRecorder->createBackendTexture(dimensions, textureInfo[i]); | |
| REPORTER_ASSERT(reporter, testCtx->fBackendTextures[i+4].isValid()); | |
| } | |
| } | |
| if (isVolatile == Volatile::kYes) { | |
| testCtx->fPromiseTextureCheckers[i] = | |
| PromiseTextureChecker(testCtx->fBackendTextures[i], | |
| testCtx->fBackendTextures[i+4]); | |
| } else { | |
| testCtx->fPromiseTextureCheckers[i] = | |
| PromiseTextureChecker(testCtx->fBackendTextures[i]); | |
| } | |
| } | |
| SkYUVAInfo yuvaInfo(dimensions, | |
| SkYUVAInfo::PlaneConfig::kY_U_V_A, | |
| SkYUVAInfo::Subsampling::k444, | |
| kJPEG_Full_SkYUVColorSpace); | |
| YUVABackendTextureInfo yuvaBackendInfo(yuvaInfo, | |
| textureInfo, | |
| skgpu::Mipmapped::kNo); | |
| testCtx->fImg = SkImages::PromiseTextureFromYUVA(testCtx->fRecorder.get(), | |
| yuvaBackendInfo, | |
| SkColorSpace::MakeSRGBLinear(), | |
| isVolatile, | |
| PromiseTextureChecker::Fulfill, | |
| PromiseImageChecker::ImageRelease, | |
| PromiseTextureChecker::TextureRelease, | |
| testCtx->fImageContext, | |
| testCtx->fTextureContexts); | |
| SkImageInfo ii = SkImageInfo::Make(dimensions.fWidth, | |
| dimensions.fHeight, | |
| kRGBA_8888_SkColorType, | |
| kPremul_SkAlphaType); | |
| testCtx->fSurface = SkSurfaces::RenderTarget(testCtx->fRecorder.get(), ii); | |
| } | |
| } // anonymous namespace | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(NonVolatileGraphiteYUVAPromiseImageTest, | |
| reporter, | |
| context, | |
| testGpuContext, | |
| true, | |
| CtsEnforcement::kApiLevel_202404) { | |
| constexpr SkISize kDimensions { 16, 16 }; | |
| TestCtx testContext; | |
| setup_test_context(context, reporter, &testContext, | |
| kDimensions, Volatile::kNo, /* invalidBackendTex= */ false); | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); // NVPIs not fulfilled at snap | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); // NVPIs fulfilled at insert | |
| } | |
| context->submit(SyncToCpu::kNo); | |
| // testContext.fImg still has a ref so we should not have called TextureRelease. | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); | |
| testGpuContext->syncedSubmit(context); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); | |
| // Test that more draws and insertions don't refulfill the NVPI | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); // No new fulfill | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| // testContext.fImg should still be fulfilled from the first time we inserted a Recording. | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); | |
| } | |
| testGpuContext->syncedSubmit(context); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 1); | |
| // Test that dropping the SkImage's ref doesn't change anything | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| testContext.fImg.reset(); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); | |
| } | |
| // fImg's proxy is reffed by the recording so, despite fImg being reset earlier, | |
| // the imageRelease callback doesn't occur until the recording is deleted. | |
| testContext.fPromiseImageChecker.checkImageReleased(reporter, /* expectedReleaseCnt= */ 1); | |
| // testContext.fImg no longer holds a ref but the last recording is still not submitted. | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 1); | |
| testGpuContext->syncedSubmit(context); | |
| // Now TextureRelease should definitely have been called. | |
| check_all_done(reporter, testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); | |
| } | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS( | |
| NonVolatileGraphiteYUVAPromiseImageFulfillFailureTest, | |
| reporter, | |
| context, | |
| testGpuContext, | |
| true, | |
| CtsEnforcement::kApiLevel_202404) { | |
| constexpr SkISize kDimensions { 16, 16 }; | |
| TestCtx testContext; | |
| setup_test_context(context, reporter, &testContext, | |
| kDimensions, Volatile::kNo, /* invalidBackendTex= */ true); | |
| // Draw the image a few different ways. | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); | |
| // Test that reinserting gives uninstantiated PromiseImages a second chance | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 2); | |
| } | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| SkPaint paint; | |
| paint.setColorFilter(SkColorFilters::LinearToSRGBGamma()); | |
| canvas->drawImage(testContext.fImg, 0, 0, SkSamplingOptions(), &paint); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 2); | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 3); | |
| } | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| sk_sp<SkShader> shader = testContext.fImg->makeShader(SkSamplingOptions()); | |
| REPORTER_ASSERT(reporter, shader); | |
| SkPaint paint; | |
| paint.setShader(std::move(shader)); | |
| canvas->drawRect(SkRect::MakeWH(1, 1), paint); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 3); | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 4); | |
| } | |
| testContext.fSurface.reset(); | |
| testContext.fImg.reset(); | |
| // Despite fulfill failing 4x, the imageRelease callback still fires | |
| testContext.fPromiseImageChecker.checkImageReleased(reporter, /* expectedReleaseCnt= */ 1); | |
| testGpuContext->syncedSubmit(context); | |
| // fulfill should've been called 4x while release should never have been called | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 4); | |
| } | |
| DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(NonVolatileGraphiteYUVAPromiseImageCreationFailureTest, | |
| reporter, | |
| context, | |
| CtsEnforcement::kApiLevel_202404) { | |
| // Note: these dimensions are invalid and will cause MakeGraphitePromiseTexture to fail | |
| constexpr SkISize kDimensions { 0, 0 }; | |
| TestCtx testContext; | |
| setup_test_context(context, reporter, &testContext, | |
| kDimensions, Volatile::kNo, /* invalidBackendTex= */ true); | |
| SkASSERT(!testContext.fImg); | |
| // Despite MakeGraphitePromiseTexture failing, ImageRelease is called | |
| REPORTER_ASSERT(reporter, testContext.fPromiseImageChecker.fImageReleaseCount == 1); | |
| for (int i = 0; i < 4; ++i) { | |
| REPORTER_ASSERT(reporter, testContext.fPromiseTextureCheckers[i].fFulfillCount == 0); | |
| REPORTER_ASSERT(reporter, testContext.fPromiseTextureCheckers[i].totalReleaseCount() == 0); | |
| } | |
| } | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(VolatileGraphiteYUVAPromiseImageTest, | |
| reporter, | |
| context, | |
| testGpuContext, | |
| true, | |
| CtsEnforcement::kApiLevel_202404) { | |
| constexpr SkISize kDimensions { 16, 16 }; | |
| TestCtx testContext; | |
| setup_test_context(context, reporter, &testContext, | |
| kDimensions, Volatile::kYes, /* invalidBackendTex= */ false); | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| // Nothing happens at snap time for VPIs | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); // VPIs fulfilled on insert | |
| // Test that multiple insertions will clobber prior fulfills | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| check_fulfilled_ahead_by_two(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 2); | |
| } | |
| testGpuContext->syncedSubmit(context); | |
| check_all_done(reporter, testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 2); | |
| for (int i = 0; i < 4; ++i) { | |
| REPORTER_ASSERT(reporter, | |
| testContext.fPromiseTextureCheckers[i].fTextureReleaseCounts[0] == 1); | |
| REPORTER_ASSERT(reporter, | |
| testContext.fPromiseTextureCheckers[i].fTextureReleaseCounts[1] == 1); | |
| } | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| // Nothing happens at snap time for volatile images | |
| check_all_done(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 2); | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 3); | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| check_fulfilled_ahead_by_two(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 4); | |
| } | |
| testGpuContext->syncedSubmit(context); | |
| check_all_done(reporter, testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 4); | |
| for (int i = 0; i < 4; ++i) { | |
| REPORTER_ASSERT(reporter, | |
| testContext.fPromiseTextureCheckers[i].fTextureReleaseCounts[0] == 2); | |
| REPORTER_ASSERT(reporter, | |
| testContext.fPromiseTextureCheckers[i].fTextureReleaseCounts[1] == 2); | |
| } | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| testContext.fImg.reset(); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| // Nothing happens at snap time for volatile images | |
| check_all_done(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 4); | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| check_fulfilled_ahead_by_one(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 5); | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| check_fulfilled_ahead_by_two(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 6); | |
| } | |
| // testContext.fImg no longer holds a ref but the last recordings are still not submitted. | |
| check_fulfilled_ahead_by_two(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 6); | |
| testGpuContext->syncedSubmit(context); | |
| // Now all Releases should definitely have been called. | |
| check_all_done(reporter, testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 6); | |
| for (int i = 0; i < 4; ++i) { | |
| REPORTER_ASSERT(reporter, | |
| testContext.fPromiseTextureCheckers[i].fTextureReleaseCounts[0] == 3); | |
| REPORTER_ASSERT(reporter, | |
| testContext.fPromiseTextureCheckers[i].fTextureReleaseCounts[1] == 3); | |
| } | |
| } | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS( | |
| VolatileGraphiteYUVAPromiseImageFulfillFailureTest, | |
| reporter, | |
| context, | |
| testGpuContext, | |
| true, | |
| CtsEnforcement::kApiLevel_202404) { | |
| constexpr SkISize kDimensions { 16, 16 }; | |
| TestCtx testContext; | |
| setup_test_context(context, reporter, &testContext, | |
| kDimensions, Volatile::kYes, /* invalidBackendTex= */ true); | |
| // Draw the image a few different ways. | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 1); | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 2); | |
| } | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| SkPaint paint; | |
| paint.setColorFilter(SkColorFilters::LinearToSRGBGamma()); | |
| canvas->drawImage(testContext.fImg, 0, 0, SkSamplingOptions(), &paint); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 2); | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 3); | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 4); | |
| } | |
| { | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| sk_sp<SkShader> shader = testContext.fImg->makeShader(SkSamplingOptions()); | |
| REPORTER_ASSERT(reporter, shader); | |
| SkPaint paint; | |
| paint.setShader(std::move(shader)); | |
| canvas->drawRect(SkRect::MakeWH(1, 1), paint); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 4); | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 5); | |
| REPORTER_ASSERT(reporter, !context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 6); | |
| } | |
| testContext.fSurface.reset(); | |
| testContext.fImg.reset(); | |
| testGpuContext->syncedSubmit(context); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 6); | |
| } | |
| // Test out dropping the Recorder prior to inserting the Recording | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(GraphiteYUVAPromiseImageRecorderLoss, | |
| reporter, | |
| context, | |
| testGpuContext, | |
| true, | |
| CtsEnforcement::kApiLevel_202404) { | |
| constexpr SkISize kDimensions{ 16, 16 }; | |
| for (Volatile isVolatile : { Volatile::kNo, Volatile::kYes }) { | |
| TestCtx testContext; | |
| setup_test_context(context, reporter, &testContext, | |
| kDimensions, isVolatile, /* invalidBackendTex= */ false); | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); | |
| std::unique_ptr<Recording> recording = testContext.fRecorder->snap(); | |
| check_unfulfilled(testContext.fPromiseImageChecker, testContext.fPromiseTextureCheckers, | |
| reporter); | |
| testContext.fRecorder.reset(); // Recorder drop | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recording.get() })); | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 1); | |
| testGpuContext->syncedSubmit(context); | |
| testContext.fSurface.reset(); | |
| testContext.fImg.reset(); | |
| recording.reset(); | |
| check_all_done(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 1); | |
| } | |
| } | |
| // Test out PromiseImages appearing in multiple Recordings. In particular, test that | |
| // previous instantiations don't impact the Recording's collection of PromiseImages. | |
| DEF_CONDITIONAL_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(GraphiteYUVAPromiseImageMultipleImgUses, | |
| reporter, | |
| context, | |
| testGpuContext, | |
| true, | |
| CtsEnforcement::kApiLevel_202404) { | |
| constexpr SkISize kDimensions{ 16, 16 }; | |
| static constexpr int kNumRecordings = 3; | |
| for (Volatile isVolatile : { Volatile::kNo, Volatile::kYes }) { | |
| int expectedVolatile = (isVolatile == Volatile::kYes) ? 4 : 0; | |
| int expectedNonVolatile = 4 - expectedVolatile; | |
| TestCtx testContext; | |
| setup_test_context(context, reporter, &testContext, | |
| kDimensions, isVolatile, /* invalidBackendTex= */ false); | |
| std::unique_ptr<Recording> recordings[kNumRecordings]; | |
| SkCanvas* canvas = testContext.fSurface->getCanvas(); | |
| for (int i = 0; i < kNumRecordings; ++i) { | |
| canvas->drawImage(testContext.fImg, 0, 0); | |
| recordings[i] = testContext.fRecorder->snap(); | |
| if (isVolatile == Volatile::kYes) { | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ i); | |
| } else { | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ i > 0 ? 1 : 0); | |
| } | |
| REPORTER_ASSERT(reporter, | |
| recordings[i]->priv().numVolatilePromiseImages() == expectedVolatile); | |
| REPORTER_ASSERT(reporter, | |
| recordings[i]->priv().numNonVolatilePromiseImages() == | |
| expectedNonVolatile); | |
| REPORTER_ASSERT(reporter, context->insertRecording({ recordings[i].get() })); | |
| if (isVolatile == Volatile::kYes) { | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ i+1); | |
| } else { | |
| check_fulfills_only(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ 1); | |
| } | |
| // Non-volatiles are cleared out after a successful insertion | |
| REPORTER_ASSERT(reporter, recordings[i]->priv().numNonVolatilePromiseImages() == 0); | |
| } | |
| testGpuContext->syncedSubmit(context); | |
| testContext.fSurface.reset(); | |
| testContext.fImg.reset(); | |
| for (int i = 0; i < kNumRecordings; ++i) { | |
| recordings[i].reset(); | |
| } | |
| if (isVolatile == Volatile::kYes) { | |
| check_all_done(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, | |
| /* expectedFulfillCnt= */ kNumRecordings); | |
| } else { | |
| check_all_done(reporter, testContext.fPromiseImageChecker, | |
| testContext.fPromiseTextureCheckers, /* expectedFulfillCnt= */ 1); | |
| } | |
| } | |
| } |