| /* | |
| * Copyright 2022 Google LLC | |
| * | |
| * 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/SkBitmap.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkSamplingOptions.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/core/SkSurfaceProps.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/gpu/GpuTypes.h" | |
| #include "include/gpu/ganesh/GrBackendSurface.h" | |
| #include "include/gpu/ganesh/GrContextOptions.h" | |
| #include "include/gpu/ganesh/GrDirectContext.h" | |
| #include "include/gpu/ganesh/GrTypes.h" | |
| #include "include/gpu/ganesh/SkSurfaceGanesh.h" | |
| #include "include/gpu/ganesh/mock/GrMockTypes.h" | |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" | |
| #include "src/core/SkColorData.h" | |
| #include "src/gpu/SkBackingFit.h" | |
| #include "src/gpu/Swizzle.h" | |
| #include "src/gpu/ganesh/GrCaps.h" | |
| #include "src/gpu/ganesh/GrDirectContextPriv.h" | |
| #include "src/gpu/ganesh/GrDrawingManager.h" | |
| #include "src/gpu/ganesh/GrFragmentProcessor.h" | |
| #include "src/gpu/ganesh/GrOnFlushResourceProvider.h" | |
| #include "src/gpu/ganesh/GrPaint.h" | |
| #include "src/gpu/ganesh/GrProxyProvider.h" | |
| #include "src/gpu/ganesh/GrRenderTargetProxy.h" | |
| #include "src/gpu/ganesh/GrSamplerState.h" | |
| #include "src/gpu/ganesh/GrSurfaceProxy.h" | |
| #include "src/gpu/ganesh/GrSurfaceProxyView.h" | |
| #include "src/gpu/ganesh/GrTextureProxy.h" | |
| #include "src/gpu/ganesh/GrTextureResolveRenderTask.h" | |
| #include "src/gpu/ganesh/SurfaceContext.h" | |
| #include "src/gpu/ganesh/SurfaceDrawContext.h" | |
| #include "src/gpu/ganesh/effects/GrTextureEffect.h" | |
| #include "src/gpu/ganesh/ops/OpsTask.h" | |
| #include "tests/ComparePixels.h" | |
| #include "tests/CtsEnforcement.h" | |
| #include "tests/Test.h" | |
| #include "tools/gpu/ManagedBackendTexture.h" | |
| #include <functional> | |
| #include <initializer_list> | |
| #include <memory> | |
| #include <utility> | |
| struct GrContextOptions; | |
| using namespace sk_gpu_test; | |
| bool check_pixels(skiatest::Reporter* reporter, | |
| GrDirectContext* dContext, | |
| const GrBackendTexture& tex, | |
| const SkImageInfo& info, | |
| SkColor expectedColor) { | |
| // We have to do the readback of the backend texture wrapped in a different Skia surface than | |
| // the one used in the main body of the test or else the readPixels call will trigger resolves | |
| // itself. | |
| sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext, | |
| tex, | |
| kTopLeft_GrSurfaceOrigin, | |
| /*sampleCnt=*/4, | |
| kRGBA_8888_SkColorType, | |
| nullptr, | |
| nullptr); | |
| SkBitmap actual; | |
| actual.allocPixels(info); | |
| if (!surface->readPixels(actual, 0, 0)) { | |
| return false; | |
| } | |
| SkBitmap expected; | |
| expected.allocPixels(info); | |
| SkCanvas tmp(expected); | |
| tmp.clear(expectedColor); | |
| expected.setImmutable(); | |
| const float tols[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; | |
| auto error = std::function<ComparePixmapsErrorReporter>( | |
| [reporter](int x, int y, const float diffs[4]) { | |
| SkASSERT(x >= 0 && y >= 0); | |
| ERRORF(reporter, "mismatch at %d, %d (%f, %f, %f %f)", | |
| x, y, diffs[0], diffs[1], diffs[2], diffs[3]); | |
| }); | |
| return ComparePixels(expected.pixmap(), actual.pixmap(), tols, error); | |
| } | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SurfaceResolveTest, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kApiLevel_T) { | |
| auto dContext = ctxInfo.directContext(); | |
| SkImageInfo info = SkImageInfo::Make(8, 8, kRGBA_8888_SkColorType, kPremul_SkAlphaType); | |
| auto managedTex = ManagedBackendTexture::MakeFromInfo( | |
| dContext, info, skgpu::Mipmapped::kNo, GrRenderable::kYes); | |
| if (!managedTex) { | |
| return; | |
| } | |
| auto tex = managedTex->texture(); | |
| // Wrap the backend surface but tell it rendering with MSAA so that the wrapped texture is the | |
| // resolve. | |
| sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext, | |
| tex, | |
| kTopLeft_GrSurfaceOrigin, | |
| /*sampleCnt=*/4, | |
| kRGBA_8888_SkColorType, | |
| nullptr, | |
| nullptr); | |
| if (!surface) { | |
| return; | |
| } | |
| const GrCaps* caps = dContext->priv().caps(); | |
| // In metal and vulkan if we prefer discardable msaa attachments we will also auto resolve. The | |
| // GrBackendTexture and SkSurface are set up in a way that is compatible with discardable msaa | |
| // for both backends. | |
| bool autoResolves = caps->msaaResolvesAutomatically() || | |
| caps->preferDiscardableMSAAAttachment(); | |
| // First do a simple test where we clear the surface than flush with SkSurface::flush. This | |
| // should trigger the resolve and the texture should have the correct data. | |
| surface->getCanvas()->clear(SK_ColorRED); | |
| dContext->flush(surface.get()); | |
| dContext->submit(); | |
| REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED)); | |
| // Next try doing a GrDirectContext::flush without the surface which will not trigger a resolve | |
| // on gpus without automatic msaa resolves. | |
| surface->getCanvas()->clear(SK_ColorBLUE); | |
| dContext->flush(); | |
| dContext->submit(); | |
| if (autoResolves) { | |
| REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorBLUE)); | |
| } else { | |
| REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED)); | |
| } | |
| // Now doing a surface flush (even without any queued up normal work) should still resolve the | |
| // surface. | |
| dContext->flush(surface.get()); | |
| dContext->submit(); | |
| REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorBLUE)); | |
| // Test using SkSurface::resolve with a GrDirectContext::flush | |
| surface->getCanvas()->clear(SK_ColorRED); | |
| SkSurfaces::ResolveMSAA(surface); | |
| dContext->flush(); | |
| dContext->submit(); | |
| REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED)); | |
| // Calling resolve again should cause no issues as it is a no-op (there is an assert in the | |
| // resolve op that the surface's msaa is dirty, we shouldn't hit that assert). | |
| SkSurfaces::ResolveMSAA(surface); | |
| dContext->flush(); | |
| dContext->submit(); | |
| REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED)); | |
| // Try resolving in the middle of draw calls. Non automatic resolve gpus should only see the | |
| // results of the first draw. | |
| surface->getCanvas()->clear(SK_ColorGREEN); | |
| SkSurfaces::ResolveMSAA(surface); | |
| surface->getCanvas()->clear(SK_ColorBLUE); | |
| dContext->flush(); | |
| dContext->submit(); | |
| if (autoResolves) { | |
| REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorBLUE)); | |
| } else { | |
| REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorGREEN)); | |
| } | |
| // Test that a resolve between draws to a different surface doesn't cause the OpsTasks for that | |
| // surface to be split. Fails if we hit validation asserts in GrDrawingManager. | |
| // First clear out dirty msaa from previous test | |
| dContext->flush(surface.get()); | |
| auto otherSurface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kYes, info); | |
| REPORTER_ASSERT(reporter, otherSurface); | |
| otherSurface->getCanvas()->clear(SK_ColorRED); | |
| SkSurfaces::ResolveMSAA(surface); | |
| otherSurface->getCanvas()->clear(SK_ColorBLUE); | |
| dContext->flush(); | |
| dContext->submit(); | |
| // Make sure resolving a non-msaa surface doesn't trigger a resolve call. We'll hit an assert | |
| // that the msaa is not dirty if it does. | |
| REPORTER_ASSERT(reporter, otherSurface); | |
| otherSurface->getCanvas()->clear(SK_ColorRED); | |
| SkSurfaces::ResolveMSAA(otherSurface); | |
| dContext->flush(); | |
| dContext->submit(); | |
| } | |
| // This test comes from crbug.com/1355807 and crbug.com/1365578. The underlying issue was: | |
| // * We would do a non-mipmapped draw of a proxy. This proxy would add a dependency from the ops | |
| // task to the proxy's last render task, which was a copy task targetting the proxy. | |
| // * We would do a mipmapped draw of the same proxy to the same ops task. | |
| // GrRenderTask::addDependency would detect the pre-existing dependency and early out before | |
| // adding the proxy to a resolve task. | |
| // We also test the case where the first draw should add a MSAA resolve and the second draw should | |
| // add a mipmap resolve. | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(NonmippedDrawBeforeMippedDraw, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| using ResolveFlags = GrSurfaceProxy::ResolveFlags; | |
| auto dc = ctxInfo.directContext(); | |
| if (!dc->priv().caps()->mipmapSupport()) { | |
| return; | |
| } | |
| for (int sampleCount : {1, 4}) { | |
| GrRenderable renderable = sampleCount > 1 ? GrRenderable::kYes : GrRenderable::kNo; | |
| auto bef = dc->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888, renderable); | |
| if (sampleCount > 1) { | |
| if (dc->priv().caps()->msaaResolvesAutomatically()) { | |
| // MSAA won't add a resolve task. | |
| continue; | |
| } | |
| sampleCount = dc->priv().caps()->getRenderTargetSampleCount(sampleCount, bef); | |
| if (!sampleCount) { | |
| continue; | |
| } | |
| } | |
| // Create a mipmapped proxy | |
| auto mmProxy = dc->priv().proxyProvider()->createProxy(bef, | |
| {64, 64}, | |
| renderable, | |
| sampleCount, | |
| skgpu::Mipmapped::kYes, | |
| SkBackingFit::kExact, | |
| skgpu::Budgeted::kYes, | |
| GrProtected::kNo, | |
| "test MM Proxy"); | |
| GrSurfaceProxyView mmProxyView{mmProxy, | |
| kBottomLeft_GrSurfaceOrigin, | |
| skgpu::Swizzle::RGBA()}; | |
| if (sampleCount > 1) { | |
| // Make sure MSAA surface needs a resolve by drawing to it. This also adds a last | |
| // render task to the proxy. | |
| auto drawContext = skgpu::ganesh::SurfaceDrawContext::Make(dc, | |
| GrColorType::kRGBA_8888, | |
| mmProxy, | |
| nullptr, | |
| kBottomLeft_GrSurfaceOrigin, | |
| SkSurfaceProps{}); | |
| drawContext->fillWithFP(GrFragmentProcessor::MakeColor(SK_PMColor4fWHITE)); | |
| } else { | |
| // Use a copy, as in the original bug, to dirty the mipmap status and also install | |
| // a last render task on the proxy. | |
| auto src = dc->priv().proxyProvider()->createProxy(bef, | |
| {64, 64}, | |
| GrRenderable::kNo, | |
| 1, | |
| skgpu::Mipmapped::kNo, | |
| SkBackingFit::kExact, | |
| skgpu::Budgeted::kYes, | |
| GrProtected::kNo, | |
| "testSrc"); | |
| skgpu::ganesh::SurfaceContext mmSC( | |
| dc, mmProxyView, {GrColorType::kRGBA_8888, kPremul_SkAlphaType, nullptr}); | |
| mmSC.testCopy(src); | |
| } | |
| auto drawDst = skgpu::ganesh::SurfaceDrawContext::Make(dc, | |
| GrColorType::kRGBA_8888, | |
| nullptr, | |
| SkBackingFit::kExact, | |
| {8, 8}, | |
| SkSurfaceProps{}, | |
| "testDrawDst"); | |
| // Do a non-mipmapped draw from the mipmapped texture. This should add a dependency on the | |
| // copy task recorded above. If the src texture is also multisampled this should record a | |
| // msaa-only resolve. | |
| { | |
| auto te = GrTextureEffect::Make( | |
| mmProxyView, | |
| kPremul_SkAlphaType, | |
| SkMatrix::I(), | |
| GrSamplerState{SkFilterMode::kLinear, SkMipmapMode::kNone}, | |
| *dc->priv().caps()); | |
| GrPaint paint; | |
| paint.setColorFragmentProcessor(std::move(te)); | |
| drawDst->drawRect(nullptr, | |
| std::move(paint), | |
| GrAA::kNo, | |
| SkMatrix::Scale(1/8.f, 1/8.f), | |
| SkRect::Make(mmProxy->dimensions())); | |
| if (sampleCount > 1) { | |
| const GrTextureResolveRenderTask* resolveTask = | |
| drawDst->getOpsTask()->resolveTask(); | |
| if (!resolveTask) { | |
| ERRORF(reporter, "No resolve task after drawing MSAA proxy"); | |
| return; | |
| } | |
| if (resolveTask->flagsForProxy(mmProxy) != ResolveFlags::kMSAA) { | |
| ERRORF(reporter, "Expected resolve flags to be kMSAA"); | |
| return; | |
| } | |
| } | |
| } | |
| // Now do a mipmapped draw from the same texture. Ensure that even though we have a | |
| // dependency on the copy task we still ensure that a resolve is recorded. | |
| { | |
| auto te = GrTextureEffect::Make( | |
| mmProxyView, | |
| kPremul_SkAlphaType, | |
| SkMatrix::I(), | |
| GrSamplerState{SkFilterMode::kLinear, SkMipmapMode::kLinear}, | |
| *dc->priv().caps()); | |
| GrPaint paint; | |
| paint.setColorFragmentProcessor(std::move(te)); | |
| drawDst->drawRect(nullptr, | |
| std::move(paint), | |
| GrAA::kNo, | |
| SkMatrix::Scale(1/8.f, 1/8.f), | |
| SkRect::Make(mmProxy->dimensions())); | |
| } | |
| const GrTextureResolveRenderTask* resolveTask = drawDst->getOpsTask()->resolveTask(); | |
| if (!resolveTask) { | |
| ERRORF(reporter, "No resolve task after drawing mip mapped proxy"); | |
| return; | |
| } | |
| ResolveFlags expectedFlags = GrSurfaceProxy::ResolveFlags::kMipMaps; | |
| const char* expectedStr = "kMipMaps"; | |
| if (sampleCount > 1) { | |
| expectedFlags |= GrSurfaceProxy::ResolveFlags::kMSAA; | |
| expectedStr = "kMipMaps|kMSAA"; | |
| } | |
| if (resolveTask->flagsForProxy(mmProxy) != expectedFlags) { | |
| ERRORF(reporter, "Expected resolve flags to be %s", expectedStr); | |
| return; | |
| } | |
| } | |
| } | |
| namespace { | |
| // A preFlush callback that fails the first N flushes, simulating an allocation failure in an | |
| // onFlush callback (e.g. atlas instantiation under memory pressure). | |
| class FailingPreFlushCallback : public GrOnFlushCallbackObject { | |
| public: | |
| explicit FailingPreFlushCallback(int failCount) : fRemainingFailures(failCount) {} | |
| bool preFlush(GrOnFlushResourceProvider*) override { | |
| if (fRemainingFailures > 0) { | |
| --fRemainingFailures; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool retainOnFreeGpuResources() override { return true; } | |
| private: | |
| int fRemainingFailures; | |
| }; | |
| } // namespace | |
| // GrTextureResolveRenderTask::addProxy() optimistically marks a proxy's mipmaps clean / MSAA | |
| // resolved at recording time. If the owning flush is dropped without executing render tasks | |
| // (e.g., a preFlush callback or the resource allocator fails) the proxy must be re-dirtied so a | |
| // later flush will record a new resolve. | |
| DEF_GANESH_TEST(SurfaceResolveProxyStateAfterFailedFlush, | |
| reporter, | |
| /* options */, | |
| CtsEnforcement::kNever) { | |
| using ResolveFlags = GrSurfaceProxy::ResolveFlags; | |
| using Enable = GrContextOptions::Enable; | |
| using MipmapMode = GrSamplerState::MipmapMode; | |
| for (auto reduceOpsTaskSplitting : {Enable::kYes, Enable::kNo}) { | |
| for (int sampleCount : {1, 4}) { | |
| GrMockOptions mockOptions; | |
| mockOptions.fMipmapSupport = true; | |
| mockOptions.fConfigOptions[(int)GrColorType::kRGBA_8888].fRenderability = | |
| GrMockOptions::ConfigOptions::Renderability::kMSAA; | |
| GrContextOptions ctxOptions; | |
| ctxOptions.fReduceOpsTaskSplitting = reduceOpsTaskSplitting; | |
| sk_sp<GrDirectContext> dContext = GrDirectContext::MakeMock(&mockOptions, ctxOptions); | |
| if (!dContext) { | |
| ERRORF(reporter, "could not create mock context"); | |
| continue; | |
| } | |
| GrDrawingManager* drawingManager = dContext->priv().drawingManager(); | |
| auto bef = dContext->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888, | |
| GrRenderable::kYes); | |
| sk_sp<GrSurfaceProxy> proxy = | |
| dContext->priv().proxyProvider()->createProxy(bef, | |
| {64, 64}, | |
| GrRenderable::kYes, | |
| sampleCount, | |
| skgpu::Mipmapped::kYes, | |
| SkBackingFit::kExact, | |
| skgpu::Budgeted::kYes, | |
| GrProtected::kNo, | |
| "ResolveProxyStateTest"); | |
| if (!proxy) { | |
| ERRORF(reporter, "could not create proxy"); | |
| continue; | |
| } | |
| GrTextureProxy* texProxy = proxy->asTextureProxy(); | |
| GrRenderTargetProxy* rtProxy = proxy->asRenderTargetProxy(); | |
| GrSurfaceProxyView proxyView{proxy, | |
| kTopLeft_GrSurfaceOrigin, | |
| skgpu::Swizzle::RGBA()}; | |
| const bool testMSAA = sampleCount > 1; | |
| REPORTER_ASSERT(reporter, !testMSAA || proxy->requiresManualMSAAResolve()); | |
| auto recordDirtyingDrawAndMipmappedDependency = [&]() { | |
| auto srcSDC = | |
| skgpu::ganesh::SurfaceDrawContext::Make(dContext.get(), | |
| GrColorType::kRGBA_8888, | |
| proxy, | |
| nullptr, | |
| kTopLeft_GrSurfaceOrigin, | |
| SkSurfaceProps{}); | |
| srcSDC->fillWithFP(GrFragmentProcessor::MakeColor(SK_PMColor4fWHITE)); | |
| auto dstSDC = | |
| skgpu::ganesh::SurfaceDrawContext::Make(dContext.get(), | |
| GrColorType::kRGBA_8888, | |
| nullptr, | |
| SkBackingFit::kExact, | |
| {8, 8}, | |
| SkSurfaceProps{}, | |
| "ResolveProxyStateTestDst"); | |
| auto te = GrTextureEffect::Make( | |
| proxyView, | |
| kPremul_SkAlphaType, | |
| SkMatrix::I(), | |
| GrSamplerState{SkFilterMode::kLinear, MipmapMode::kLinear}, | |
| *dContext->priv().caps()); | |
| GrPaint paint; | |
| paint.setColorFragmentProcessor(std::move(te)); | |
| dstSDC->drawRect(nullptr, | |
| std::move(paint), | |
| GrAA::kNo, | |
| SkMatrix::Scale(1/8.f, 1/8.f), | |
| SkRect::Make(proxy->dimensions())); | |
| return dstSDC; | |
| }; | |
| // Record a flush whose preFlush callback fails. The resolve task records the proxy | |
| // as resolved/clean and is then discarded without executing. | |
| { | |
| FailingPreFlushCallback failCB(1); | |
| dContext->priv().addOnFlushCallbackObject(&failCB); | |
| auto dstSDC = recordDirtyingDrawAndMipmappedDependency(); | |
| ResolveFlags expectedFlags = ResolveFlags::kMipMaps; | |
| if (testMSAA) { | |
| expectedFlags |= ResolveFlags::kMSAA; | |
| } | |
| const GrTextureResolveRenderTask* resolveTask = | |
| dstSDC->getOpsTask()->resolveTask(); | |
| REPORTER_ASSERT(reporter, | |
| resolveTask && | |
| resolveTask->flagsForProxy(proxy) == expectedFlags); | |
| REPORTER_ASSERT(reporter, !texProxy->mipmapsAreDirty()); | |
| REPORTER_ASSERT(reporter, !testMSAA || !rtProxy->isMSAADirty()); | |
| dContext->flush(); | |
| drawingManager->testingOnly_removeOnFlushCallbackObject(&failCB); | |
| } | |
| // The resolve never executed so the proxy must once again report dirty mips/MSAA. | |
| REPORTER_ASSERT(reporter, texProxy->mipmapsAreDirty()); | |
| if (testMSAA) { | |
| REPORTER_ASSERT(reporter, rtProxy->isMSAADirty()); | |
| REPORTER_ASSERT(reporter, | |
| rtProxy->msaaDirtyRect() == SkIRect::MakeSize(proxy->dimensions())); | |
| } | |
| // A subsequent dependency must therefore record a new resolve task and the proxy | |
| // should be clean once that flush succeeds. | |
| { | |
| auto dstSDC = recordDirtyingDrawAndMipmappedDependency(); | |
| REPORTER_ASSERT(reporter, dstSDC->getOpsTask()->resolveTask()); | |
| dContext->flush(); | |
| } | |
| REPORTER_ASSERT(reporter, !texProxy->mipmapsAreDirty()); | |
| REPORTER_ASSERT(reporter, !testMSAA || !rtProxy->isMSAADirty()); | |
| } | |
| } | |
| } | |
| // Directly read back 'tex' and return the first pixel color. | |
| static bool read_backing_pixel(GrDirectContext* dContext, | |
| const GrBackendTexture& tex, | |
| const SkImageInfo& info, | |
| SkColor* outPixel) { | |
| sk_sp<SkSurface> reader = SkSurfaces::WrapBackendTexture(dContext, | |
| tex, | |
| kTopLeft_GrSurfaceOrigin, | |
| /*sampleCnt=*/1, | |
| kRGBA_8888_SkColorType, | |
| nullptr, | |
| nullptr); | |
| if (!reader) { | |
| return false; | |
| } | |
| SkBitmap bm; | |
| bm.allocPixels(info); | |
| if (!reader->readPixels(bm, 0, 0)) { | |
| return false; | |
| } | |
| *outPixel = bm.getColor(0, 0); | |
| return true; | |
| } | |
| // This test wraps a backend texture as an MSAA render target twice. The first wrap establishes a | |
| // known baseline color in the single-sample texture via a *successful* flush. The second wrap | |
| // creates a *fresh, never-written* MSAA color attachment (on GL a new renderbuffer, on Vulkan a | |
| // scratch/new GrVkImage), records a draw, forces the flush to fail via a failing preFlush | |
| // callback, and then reads back the single-sample texture. The read-back must not show the | |
| // uninitialized MSAA attachment contents; if it does, resolve_and_mipmap() ran despite the failed | |
| // flush and copied uninitialized GPU memory into the client-visible texture. | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SurfaceResolveAfterFailedFlush, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kNever) { | |
| auto dContext = ctxInfo.directContext(); | |
| const GrCaps* caps = dContext->priv().caps(); | |
| // Only meaningful on backends that require an explicit resolve of a persistent MSAA attachment. | |
| if (caps->msaaResolvesAutomatically() || caps->preferDiscardableMSAAAttachment()) { | |
| return; | |
| } | |
| SkImageInfo info = SkImageInfo::Make(8, 8, kRGBA_8888_SkColorType, kPremul_SkAlphaType); | |
| auto managedTex = ManagedBackendTexture::MakeFromInfo( | |
| dContext, info, skgpu::Mipmapped::kNo, GrRenderable::kYes); | |
| if (!managedTex) { | |
| return; | |
| } | |
| const GrBackendTexture& tex = managedTex->texture(); | |
| constexpr SkColor kBaseline = SK_ColorBLUE; // known content of the single-sample texture | |
| constexpr SkColor kIntended = SK_ColorGREEN; // what the failed flush *would* have drawn | |
| // 1. Wrap once and successfully draw kBaseline so the single-sample texture holds known bytes. | |
| { | |
| sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext, | |
| tex, | |
| kTopLeft_GrSurfaceOrigin, | |
| /*sampleCnt=*/4, | |
| kRGBA_8888_SkColorType, | |
| nullptr, | |
| nullptr); | |
| if (!surface) { | |
| return; // MSAA=4 unsupported on this config. | |
| } | |
| surface->getCanvas()->clear(kBaseline); | |
| dContext->flush(surface.get()); | |
| dContext->submit(GrSyncCpu::kYes); | |
| } | |
| // Ensure the second wrap below allocates a *fresh* MSAA attachment rather than recycling the | |
| // one from the wrap above (whose contents would coincidentally match kBaseline). | |
| dContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources); | |
| SkColor pixel = 0; | |
| REPORTER_ASSERT(reporter, read_backing_pixel(dContext, tex, info, &pixel)); | |
| REPORTER_ASSERT(reporter, pixel == kBaseline, | |
| "baseline flush failed to resolve, got 0x%x", pixel); | |
| // 2. Re-wrap: this creates a *new* persistent MSAA color attachment that has never been | |
| // written by any render pass in this test. | |
| sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext, | |
| tex, | |
| kTopLeft_GrSurfaceOrigin, | |
| /*sampleCnt=*/4, | |
| kRGBA_8888_SkColorType, | |
| nullptr, | |
| nullptr); | |
| if (!surface) { | |
| return; | |
| } | |
| // 3. Record a full-surface draw so OpsTask::onMakeClosed() returns kTargetDirty and | |
| // markMSAADirty() is called during closeAllTasks(). Force the flush to fail. | |
| FailingPreFlushCallback failCB(1); | |
| dContext->priv().addOnFlushCallbackObject(&failCB); | |
| surface->getCanvas()->clear(kIntended); | |
| GrSemaphoresSubmitted result = dContext->flush(surface.get(), GrFlushInfo{}); | |
| dContext->submit(GrSyncCpu::kYes); | |
| GrDrawingManager* drawingManager = dContext->priv().drawingManager(); | |
| drawingManager->testingOnly_removeOnFlushCallbackObject(&failCB); | |
| REPORTER_ASSERT(reporter, result == GrSemaphoresSubmitted::kNo, | |
| "expected flush to report semaphores not submitted"); | |
| // 4. Read back the single-sample backing texture. | |
| REPORTER_ASSERT(reporter, read_backing_pixel(dContext, tex, info, &pixel)); | |
| // The recorded draw never executed, so kIntended should not appear. | |
| REPORTER_ASSERT(reporter, pixel != kIntended, | |
| "flush failed but draw, somehow, occurred (got 0x%x)", pixel); | |
| // Since the flush failed the MSAA resolve step should not have occurred and the contents | |
| // of the backend texture should have remained at 'kBaseline'. | |
| REPORTER_ASSERT(reporter, pixel == kBaseline, | |
| "Invalid resolve after a failed flush: expected 0x%x, actual 0x%x", | |
| kBaseline, pixel); | |
| } |