| /* | 
 |  * Copyright 2022 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/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/GrBackendSurface.h" | 
 | #include "include/gpu/GrDirectContext.h" | 
 | #include "include/gpu/GrTypes.h" | 
 | #include "include/private/SkColorData.h" | 
 | #include "include/private/gpu/ganesh/GrTypesPriv.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/GrFragmentProcessor.h" | 
 | #include "src/gpu/ganesh/GrPaint.h" | 
 | #include "src/gpu/ganesh/GrProxyProvider.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/CtsEnforcement.h" | 
 | #include "tests/Test.h" | 
 | #include "tests/TestUtils.h" | 
 | #include "tools/gpu/FenceSync.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 = SkSurface::MakeFromBackendTexture(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, | 
 |                                                           GrMipmapped::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 = SkSurface::MakeFromBackendTexture(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); | 
 |     surface->flush(); | 
 |     dContext->submit(); | 
 |     REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED)); | 
 |  | 
 |     // Next try doing a GrDirectContext::flush 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. | 
 |     surface->flush(); | 
 |     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); | 
 |     surface->resolveMSAA(); | 
 |     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). | 
 |     surface->resolveMSAA(); | 
 |     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); | 
 |     surface->resolveMSAA(); | 
 |     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 | 
 |     surface->flush(); | 
 |  | 
 |     auto otherSurface = SkSurface::MakeRenderTarget(dContext, skgpu::Budgeted::kYes, info); | 
 |     REPORTER_ASSERT(reporter, otherSurface); | 
 |     otherSurface->getCanvas()->clear(SK_ColorRED); | 
 |     surface->resolveMSAA(); | 
 |     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); | 
 |     otherSurface->resolveMSAA(); | 
 |     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, | 
 |                                                                GrMipmapped::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::v1::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, | 
 |                                                                GrMipmapped::kNo, | 
 |                                                                SkBackingFit::kExact, | 
 |                                                                skgpu::Budgeted::kYes, | 
 |                                                                GrProtected::kNo, | 
 |                                                                "testSrc"); | 
 |             skgpu::v1::SurfaceContext mmSC(dc, | 
 |                                            mmProxyView, | 
 |                                            {GrColorType::kRGBA_8888, kPremul_SkAlphaType, nullptr}); | 
 |             mmSC.testCopy(src); | 
 |         } | 
 |  | 
 |         auto drawDst = skgpu::v1::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; | 
 |         } | 
 |     } | 
 | } |