xref: /aosp_15_r20/external/skia/tests/GrSurfaceResolveTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2022 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/core/SkAlphaType.h"
9 #include "include/core/SkBitmap.h"
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkColor.h"
12 #include "include/core/SkColorSpace.h"
13 #include "include/core/SkColorType.h"
14 #include "include/core/SkImageInfo.h"
15 #include "include/core/SkMatrix.h"
16 #include "include/core/SkRect.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/core/SkSamplingOptions.h"
19 #include "include/core/SkSurface.h"
20 #include "include/core/SkSurfaceProps.h"
21 #include "include/core/SkTypes.h"
22 #include "include/gpu/GpuTypes.h"
23 #include "include/gpu/ganesh/GrBackendSurface.h"
24 #include "include/gpu/ganesh/GrDirectContext.h"
25 #include "include/gpu/ganesh/GrTypes.h"
26 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
27 #include "include/private/SkColorData.h"
28 #include "include/private/gpu/ganesh/GrTypesPriv.h"
29 #include "src/gpu/SkBackingFit.h"
30 #include "src/gpu/Swizzle.h"
31 #include "src/gpu/ganesh/GrCaps.h"
32 #include "src/gpu/ganesh/GrDirectContextPriv.h"
33 #include "src/gpu/ganesh/GrFragmentProcessor.h"
34 #include "src/gpu/ganesh/GrPaint.h"
35 #include "src/gpu/ganesh/GrProxyProvider.h"
36 #include "src/gpu/ganesh/GrSamplerState.h"
37 #include "src/gpu/ganesh/GrSurfaceProxy.h"
38 #include "src/gpu/ganesh/GrSurfaceProxyView.h"
39 #include "src/gpu/ganesh/GrTextureProxy.h"
40 #include "src/gpu/ganesh/GrTextureResolveRenderTask.h"
41 #include "src/gpu/ganesh/SurfaceContext.h"
42 #include "src/gpu/ganesh/SurfaceDrawContext.h"
43 #include "src/gpu/ganesh/effects/GrTextureEffect.h"
44 #include "src/gpu/ganesh/ops/OpsTask.h"
45 #include "tests/CtsEnforcement.h"
46 #include "tests/Test.h"
47 #include "tests/TestUtils.h"
48 #include "tools/gpu/FenceSync.h"
49 #include "tools/gpu/ManagedBackendTexture.h"
50 
51 #include <functional>
52 #include <initializer_list>
53 #include <memory>
54 #include <utility>
55 
56 struct GrContextOptions;
57 
58 using namespace sk_gpu_test;
59 
check_pixels(skiatest::Reporter * reporter,GrDirectContext * dContext,const GrBackendTexture & tex,const SkImageInfo & info,SkColor expectedColor)60 bool check_pixels(skiatest::Reporter* reporter,
61                   GrDirectContext* dContext,
62                   const GrBackendTexture& tex,
63                   const SkImageInfo& info,
64                   SkColor expectedColor) {
65     // We have to do the readback of the backend texture wrapped in a different Skia surface than
66     // the one used in the main body of the test or else the readPixels call will trigger resolves
67     // itself.
68     sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext,
69                                                               tex,
70                                                               kTopLeft_GrSurfaceOrigin,
71                                                               /*sampleCnt=*/4,
72                                                               kRGBA_8888_SkColorType,
73                                                               nullptr,
74                                                               nullptr);
75     SkBitmap actual;
76     actual.allocPixels(info);
77     if (!surface->readPixels(actual, 0, 0)) {
78         return false;
79     }
80 
81     SkBitmap expected;
82     expected.allocPixels(info);
83     SkCanvas tmp(expected);
84     tmp.clear(expectedColor);
85     expected.setImmutable();
86 
87     const float tols[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
88 
89     auto error = std::function<ComparePixmapsErrorReporter>(
90         [reporter](int x, int y, const float diffs[4]) {
91             SkASSERT(x >= 0 && y >= 0);
92             ERRORF(reporter, "mismatch at %d, %d (%f, %f, %f %f)",
93                    x, y, diffs[0], diffs[1], diffs[2], diffs[3]);
94         });
95 
96     return ComparePixels(expected.pixmap(), actual.pixmap(), tols, error);
97 }
98 
DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SurfaceResolveTest,reporter,ctxInfo,CtsEnforcement::kApiLevel_T)99 DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SurfaceResolveTest,
100                                        reporter,
101                                        ctxInfo,
102                                        CtsEnforcement::kApiLevel_T) {
103     auto dContext = ctxInfo.directContext();
104 
105     SkImageInfo info = SkImageInfo::Make(8, 8, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
106 
107     auto managedTex = ManagedBackendTexture::MakeFromInfo(
108             dContext, info, skgpu::Mipmapped::kNo, GrRenderable::kYes);
109     if (!managedTex) {
110         return;
111     }
112     auto tex = managedTex->texture();
113     // Wrap the backend surface but tell it rendering with MSAA so that the wrapped texture is the
114     // resolve.
115     sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext,
116                                                               tex,
117                                                               kTopLeft_GrSurfaceOrigin,
118                                                               /*sampleCnt=*/4,
119                                                               kRGBA_8888_SkColorType,
120                                                               nullptr,
121                                                               nullptr);
122 
123     if (!surface) {
124         return;
125     }
126 
127     const GrCaps* caps = dContext->priv().caps();
128     // In metal and vulkan if we prefer discardable msaa attachments we will also auto resolve. The
129     // GrBackendTexture and SkSurface are set up in a way that is compatible with discardable msaa
130     // for both backends.
131     bool autoResolves = caps->msaaResolvesAutomatically() ||
132                         caps->preferDiscardableMSAAAttachment();
133 
134     // First do a simple test where we clear the surface than flush with SkSurface::flush. This
135     // should trigger the resolve and the texture should have the correct data.
136     surface->getCanvas()->clear(SK_ColorRED);
137     dContext->flush(surface.get());
138     dContext->submit();
139     REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED));
140 
141     // Next try doing a GrDirectContext::flush without the surface which will not trigger a resolve
142     // on gpus without automatic msaa resolves.
143     surface->getCanvas()->clear(SK_ColorBLUE);
144     dContext->flush();
145     dContext->submit();
146     if (autoResolves) {
147         REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorBLUE));
148     } else {
149         REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED));
150     }
151 
152     // Now doing a surface flush (even without any queued up normal work) should still resolve the
153     // surface.
154     dContext->flush(surface.get());
155     dContext->submit();
156     REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorBLUE));
157 
158     // Test using SkSurface::resolve with a GrDirectContext::flush
159     surface->getCanvas()->clear(SK_ColorRED);
160     SkSurfaces::ResolveMSAA(surface);
161     dContext->flush();
162     dContext->submit();
163     REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED));
164 
165     // Calling resolve again should cause no issues as it is a no-op (there is an assert in the
166     // resolve op that the surface's msaa is dirty, we shouldn't hit that assert).
167     SkSurfaces::ResolveMSAA(surface);
168     dContext->flush();
169     dContext->submit();
170     REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorRED));
171 
172     // Try resolving in the middle of draw calls. Non automatic resolve gpus should only see the
173     // results of the first draw.
174     surface->getCanvas()->clear(SK_ColorGREEN);
175     SkSurfaces::ResolveMSAA(surface);
176     surface->getCanvas()->clear(SK_ColorBLUE);
177     dContext->flush();
178     dContext->submit();
179     if (autoResolves) {
180         REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorBLUE));
181     } else {
182         REPORTER_ASSERT(reporter, check_pixels(reporter, dContext, tex, info, SK_ColorGREEN));
183     }
184 
185     // Test that a resolve between draws to a different surface doesn't cause the OpsTasks for that
186     // surface to be split. Fails if we hit validation asserts in GrDrawingManager.
187     // First clear out dirty msaa from previous test
188     dContext->flush(surface.get());
189 
190     auto otherSurface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kYes, info);
191     REPORTER_ASSERT(reporter, otherSurface);
192     otherSurface->getCanvas()->clear(SK_ColorRED);
193     SkSurfaces::ResolveMSAA(surface);
194     otherSurface->getCanvas()->clear(SK_ColorBLUE);
195     dContext->flush();
196     dContext->submit();
197 
198     // Make sure resolving a non-msaa surface doesn't trigger a resolve call. We'll hit an assert
199     // that the msaa is not dirty if it does.
200     REPORTER_ASSERT(reporter, otherSurface);
201     otherSurface->getCanvas()->clear(SK_ColorRED);
202     SkSurfaces::ResolveMSAA(otherSurface);
203     dContext->flush();
204     dContext->submit();
205 }
206 
207 // This test comes from crbug.com/1355807 and crbug.com/1365578. The underlying issue was:
208 //  * We would do a non-mipmapped draw of a proxy. This proxy would add a dependency from the ops
209 //    task to the proxy's last render task, which was a copy task targetting the proxy.
210 //  * We would do a mipmapped draw of the same proxy to the same ops task.
211 //    GrRenderTask::addDependency would detect the pre-existing dependency and early out before
212 //    adding the proxy to a resolve task.
213 // We also test the case where the first draw should add a MSAA resolve and the second draw should
214 // add a mipmap resolve.
DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(NonmippedDrawBeforeMippedDraw,reporter,ctxInfo,CtsEnforcement::kNever)215 DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(NonmippedDrawBeforeMippedDraw,
216                                        reporter,
217                                        ctxInfo,
218                                        CtsEnforcement::kNever) {
219     using ResolveFlags = GrSurfaceProxy::ResolveFlags;
220     auto dc = ctxInfo.directContext();
221 
222     if (!dc->priv().caps()->mipmapSupport()) {
223         return;
224     }
225 
226     for (int sampleCount : {1, 4}) {
227         GrRenderable renderable = sampleCount > 1 ? GrRenderable::kYes : GrRenderable::kNo;
228 
229         auto bef = dc->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888, renderable);
230         if (sampleCount > 1) {
231             if (dc->priv().caps()->msaaResolvesAutomatically()) {
232                 // MSAA won't add a resolve task.
233                 continue;
234             }
235             sampleCount = dc->priv().caps()->getRenderTargetSampleCount(sampleCount, bef);
236             if (!sampleCount) {
237                 continue;
238             }
239         }
240 
241         // Create a mipmapped proxy
242         auto mmProxy = dc->priv().proxyProvider()->createProxy(bef,
243                                                                {64, 64},
244                                                                renderable,
245                                                                sampleCount,
246                                                                skgpu::Mipmapped::kYes,
247                                                                SkBackingFit::kExact,
248                                                                skgpu::Budgeted::kYes,
249                                                                GrProtected::kNo,
250                                                                "test MM Proxy");
251         GrSurfaceProxyView mmProxyView{mmProxy,
252                                        kBottomLeft_GrSurfaceOrigin,
253                                        skgpu::Swizzle::RGBA()};
254 
255         if (sampleCount > 1) {
256             // Make sure MSAA surface needs a resolve by drawing to it. This also adds a last
257             // render task to the proxy.
258             auto drawContext = skgpu::ganesh::SurfaceDrawContext::Make(dc,
259                                                                        GrColorType::kRGBA_8888,
260                                                                        mmProxy,
261                                                                        nullptr,
262                                                                        kBottomLeft_GrSurfaceOrigin,
263                                                                        SkSurfaceProps{});
264             drawContext->fillWithFP(GrFragmentProcessor::MakeColor(SK_PMColor4fWHITE));
265         } else {
266             // Use a copy, as in the original bug, to dirty the mipmap status and also install
267             // a last render task on the proxy.
268             auto src = dc->priv().proxyProvider()->createProxy(bef,
269                                                                {64, 64},
270                                                                GrRenderable::kNo,
271                                                                1,
272                                                                skgpu::Mipmapped::kNo,
273                                                                SkBackingFit::kExact,
274                                                                skgpu::Budgeted::kYes,
275                                                                GrProtected::kNo,
276                                                                "testSrc");
277             skgpu::ganesh::SurfaceContext mmSC(
278                     dc, mmProxyView, {GrColorType::kRGBA_8888, kPremul_SkAlphaType, nullptr});
279             mmSC.testCopy(src);
280         }
281 
282         auto drawDst = skgpu::ganesh::SurfaceDrawContext::Make(dc,
283                                                                GrColorType::kRGBA_8888,
284                                                                nullptr,
285                                                                SkBackingFit::kExact,
286                                                                {8, 8},
287                                                                SkSurfaceProps{},
288                                                                "testDrawDst");
289 
290         // Do a non-mipmapped draw from the mipmapped texture. This should add a dependency on the
291         // copy task recorded above. If the src texture is also multisampled this should record a
292         // msaa-only resolve.
293         {
294             auto te = GrTextureEffect::Make(
295                     mmProxyView,
296                     kPremul_SkAlphaType,
297                     SkMatrix::I(),
298                     GrSamplerState{SkFilterMode::kLinear, SkMipmapMode::kNone},
299                     *dc->priv().caps());
300 
301             GrPaint paint;
302             paint.setColorFragmentProcessor(std::move(te));
303 
304             drawDst->drawRect(nullptr,
305                               std::move(paint),
306                               GrAA::kNo,
307                               SkMatrix::Scale(1/8.f, 1/8.f),
308                               SkRect::Make(mmProxy->dimensions()));
309             if (sampleCount > 1) {
310                 const GrTextureResolveRenderTask* resolveTask =
311                         drawDst->getOpsTask()->resolveTask();
312                 if (!resolveTask) {
313                     ERRORF(reporter, "No resolve task after drawing MSAA proxy");
314                     return;
315                 }
316                 if (resolveTask->flagsForProxy(mmProxy) != ResolveFlags::kMSAA) {
317                     ERRORF(reporter, "Expected resolve flags to be kMSAA");
318                     return;
319                 }
320             }
321         }
322 
323         // Now do a mipmapped draw from the same texture. Ensure that even though we have a
324         // dependency on the copy task we still ensure that a resolve is recorded.
325         {
326             auto te = GrTextureEffect::Make(
327                     mmProxyView,
328                     kPremul_SkAlphaType,
329                     SkMatrix::I(),
330                     GrSamplerState{SkFilterMode::kLinear, SkMipmapMode::kLinear},
331                     *dc->priv().caps());
332 
333             GrPaint paint;
334             paint.setColorFragmentProcessor(std::move(te));
335 
336             drawDst->drawRect(nullptr,
337                               std::move(paint),
338                               GrAA::kNo,
339                               SkMatrix::Scale(1/8.f, 1/8.f),
340                               SkRect::Make(mmProxy->dimensions()));
341         }
342         const GrTextureResolveRenderTask* resolveTask = drawDst->getOpsTask()->resolveTask();
343         if (!resolveTask) {
344             ERRORF(reporter, "No resolve task after drawing mip mapped proxy");
345             return;
346         }
347 
348         ResolveFlags expectedFlags = GrSurfaceProxy::ResolveFlags::kMipMaps;
349         const char* expectedStr = "kMipMaps";
350         if (sampleCount > 1) {
351             expectedFlags |= GrSurfaceProxy::ResolveFlags::kMSAA;
352             expectedStr = "kMipMaps|kMSAA";
353         }
354         if (resolveTask->flagsForProxy(mmProxy) != expectedFlags) {
355             ERRORF(reporter, "Expected resolve flags to be %s", expectedStr);
356             return;
357         }
358     }
359 }
360