| /* |
| * 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; |
| } |
| } |
| } |