/* * 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/ganesh/GrBackendSurface.h" #include "include/gpu/ganesh/GrDirectContext.h" #include "include/gpu/ganesh/GrTypes.h" #include "include/gpu/ganesh/SkSurfaceGanesh.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 #include #include #include 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 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( [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 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; } } }