xref: /aosp_15_r20/external/skia/tests/graphite/MutableImagesTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2022 Google LLC
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 "tests/Test.h"
9 
10 #include "include/core/SkColorSpace.h"
11 #include "include/core/SkImage.h"
12 #include "include/gpu/GpuTypes.h"
13 #include "include/gpu/graphite/BackendTexture.h"
14 #include "include/gpu/graphite/Context.h"
15 #include "include/gpu/graphite/Image.h"
16 #include "include/gpu/graphite/Recorder.h"
17 #include "include/gpu/graphite/Recording.h"
18 #include "include/gpu/graphite/Surface.h"
19 #include "src/core/SkAutoPixmapStorage.h"
20 #include "src/gpu/graphite/Caps.h"
21 #include "src/gpu/graphite/ContextPriv.h"
22 #include "src/gpu/graphite/Surface_Graphite.h"
23 #include "src/gpu/graphite/Texture.h"
24 #include "src/gpu/graphite/TextureProxy.h"
25 #include "tests/TestUtils.h"
26 #include "tools/ToolUtils.h"
27 
28 using namespace skgpu::graphite;
29 using Mipmapped = skgpu::Mipmapped;
30 
31 namespace {
32 
33 // We draw the larger image into the smaller surface to force mipmapping
34 const SkISize kImageSize = { 32, 32 };
35 SkDEBUGCODE(constexpr int kNumMipLevels = 6;)
36 const SkISize kSurfaceSize = { 16, 16 };
37 
38 constexpr int kNumMutations = 2;
39 constexpr SkColor4f kInitialColor = SkColors::kRed;
40 constexpr SkColor4f kMutationColors[kNumMutations] = {
41     SkColors::kGreen,
42     SkColors::kBlue
43 };
44 
45 /*
46  * We have 3 use cases. In each case there is a mutating task which changes the contents of an
47  * image (somehow) and a shared redraw task which just creates a single Recording which draws the
48  * image that is being mutated. The mutator's image must start off being 'kInitialColor' and
49  * then cycle through 'kMutationColors'. The mutation tasks are:
50 
51  *  1) (AHBs) The client has wrapped a backend texture in an image and is changing the backend
52  *     texture's contents.
53  *  2) (Volatile Promise Images) The client has a pool of backend textures and updates both the
54  *     contents of the backend textures and which one backs the image every frame
55  *  3) (Surface/Image pair) The client has a surface and has snapped an image w/o a copy but
56  *     keeps drawing to the surface
57  *
58  * There are also two scenarios for the mutation and redrawing tasks:
59  *  a) Both use the same recorder
60  *  b) They use separate recorders
61  * The latter, obviously, requires more synchronization.
62  */
63 
64 // Base class for the 3 mutation methods.
65 //    init   - should create the SkImage that is going to be changing
66 //    mutate - should change the contents of the SkImage
67 class Mutator {
68 public:
Mutator(skiatest::Reporter * reporter,Recorder * recorder,bool withMips)69     Mutator(skiatest::Reporter* reporter, Recorder* recorder, bool withMips)
70             : fReporter(reporter)
71             , fRecorder(recorder)
72             , fWithMips(withMips) {
73     }
74     virtual ~Mutator() = default;
75 
76     virtual std::unique_ptr<Recording> init(const Caps*) = 0;
77     virtual std::unique_ptr<Recording> mutate(int mutationIndex) = 0;
78     virtual int getCase() const = 0;
79 
getMutatingImage()80     SkImage* getMutatingImage() {
81         return fMutatingImg.get();
82     }
83 
84 protected:
85     skiatest::Reporter* fReporter;
86     Recorder* fRecorder;
87     bool fWithMips;
88 
89     sk_sp<SkImage> fMutatingImg; // needs to be created in the 'init' method
90 };
91 
92 // This class puts the 3 mutation use cases through their paces.
93 //    init - creates the single Recording that draws the mutator's image
94 //    checkResult - verifies that replaying the Recording results in the expected/mutated color
95 class Redrawer {
96 public:
Redrawer(skiatest::Reporter * reporter,Recorder * recorder)97     Redrawer(skiatest::Reporter* reporter, Recorder* recorder)
98             : fReporter(reporter)
99             , fRecorder(recorder) {
100         SkImageInfo ii = SkImageInfo::Make(kSurfaceSize,
101                                            kRGBA_8888_SkColorType,
102                                            kPremul_SkAlphaType);
103         fReadbackPM.alloc(ii);
104     }
105 
init(SkImage * imageToDraw)106     void init(SkImage* imageToDraw) {
107         SkImageInfo ii = SkImageInfo::Make(kSurfaceSize,
108                                            kRGBA_8888_SkColorType,
109                                            kPremul_SkAlphaType);
110         fImgDrawSurface = SkSurfaces::RenderTarget(fRecorder, ii, Mipmapped::kNo);
111         REPORTER_ASSERT(fReporter, fImgDrawSurface);
112 
113         fImgDrawRecording = MakeRedrawRecording(fRecorder, fImgDrawSurface.get(), imageToDraw);
114     }
115 
imgDrawRecording()116     Recording* imgDrawRecording() {
117         return fImgDrawRecording.get();
118     }
119 
120     // This is here bc it uses a lot from the Redrawer (i.e., its recorder, its surface, etc.).
checkResult(Context * context,int testcaseID,bool useTwoRecorders,bool withMips,const SkColor4f & expectedColor)121     void checkResult(Context* context,
122                      int testcaseID,
123                      bool useTwoRecorders,
124                      bool withMips,
125                      const SkColor4f& expectedColor) {
126 
127         fReadbackPM.erase(SkColors::kTransparent);
128 
129         if (!fImgDrawSurface->readPixels(fReadbackPM, 0, 0)) {
130             ERRORF(fReporter, "readPixels failed");
131         }
132 
133         auto error = std::function<ComparePixmapsErrorReporter>(
134                 [&](int x, int y, const float diffs[4]) {
135                     ERRORF(fReporter,
136                            "case %d%c - %s: "
137                            "expected (%.1f %.1f %.1f %.1f) "
138                            "- diffs (%.1f, %.1f, %.1f, %.1f)",
139                            testcaseID, useTwoRecorders ? 'b' : 'a',
140                            withMips ? "mipmapped" : "not-mipmapped",
141                            expectedColor.fR, expectedColor.fG, expectedColor.fB, expectedColor.fA,
142                            diffs[0], diffs[1], diffs[2], diffs[3]);
143                 });
144 
145         static constexpr float kTol[] = {0, 0, 0, 0};
146         CheckSolidPixels(expectedColor, fReadbackPM, kTol, error);
147     }
148 
149 private:
MakeRedrawRecording(Recorder * recorder,SkSurface * surfaceToDrawTo,SkImage * imageToDraw)150     static std::unique_ptr<Recording> MakeRedrawRecording(Recorder* recorder,
151                                                           SkSurface* surfaceToDrawTo,
152                                                           SkImage* imageToDraw) {
153         SkSamplingOptions sampling = SkSamplingOptions(SkFilterMode::kLinear,
154                                                        SkMipmapMode::kNearest);
155 
156         SkCanvas* canvas = surfaceToDrawTo->getCanvas();
157 
158         canvas->clear(SkColors::kTransparent);
159         canvas->drawImageRect(imageToDraw,
160                               SkRect::MakeWH(kSurfaceSize.width(), kSurfaceSize.height()),
161                               sampling);
162 
163         return recorder->snap();
164     }
165 
166     skiatest::Reporter* fReporter;
167     Recorder* fRecorder;
168 
169     sk_sp<SkSurface> fImgDrawSurface;
170     std::unique_ptr<Recording> fImgDrawRecording;
171 
172     SkAutoPixmapStorage fReadbackPM;
173 };
174 
update_backend_texture(skiatest::Reporter * reporter,Recorder * recorder,const BackendTexture & backendTex,SkColorType ct,bool withMips,SkColor4f color)175 void update_backend_texture(skiatest::Reporter* reporter,
176                             Recorder* recorder,
177                             const BackendTexture& backendTex,
178                             SkColorType ct,
179                             bool withMips,
180                             SkColor4f color) {
181     SkPixmap pixmaps[6];
182     std::unique_ptr<char[]> memForPixmaps;
183 
184     const SkColor4f colors[6] = { color, color, color, color, color, color };
185 
186     int numMipLevels = ToolUtils::make_pixmaps(ct, kPremul_SkAlphaType, withMips, colors, pixmaps,
187                                                &memForPixmaps);
188     SkASSERT(numMipLevels == 1 || numMipLevels == kNumMipLevels);
189     SkASSERT(kImageSize == pixmaps[0].dimensions());
190 
191     REPORTER_ASSERT(reporter, recorder->updateBackendTexture(backendTex, pixmaps, numMipLevels));
192 }
193 
194 // case 1 (AHBs)
195 // To simulate the AHB use case this Mutator creates a BackendTexture and an SkImage that wraps
196 // it. To mutate the SkImage it simply updates the BackendTexture.
197 class UpdateBackendTextureMutator : public Mutator {
198 public:
Make(skiatest::Reporter * reporter,Recorder * recorder,bool withMips)199     static std::unique_ptr<Mutator> Make(skiatest::Reporter* reporter,
200                                          Recorder* recorder,
201                                          bool withMips) {
202         return std::make_unique<UpdateBackendTextureMutator>(reporter, recorder, withMips);
203     }
204 
UpdateBackendTextureMutator(skiatest::Reporter * reporter,Recorder * recorder,bool withMips)205     UpdateBackendTextureMutator(skiatest::Reporter* reporter, Recorder* recorder, bool withMips)
206             : Mutator(reporter, recorder, withMips) {
207     }
~UpdateBackendTextureMutator()208     ~UpdateBackendTextureMutator() override {
209         fRecorder->deleteBackendTexture(fBETexture);
210     }
211 
init(const Caps * caps)212     std::unique_ptr<Recording> init(const Caps* caps) override {
213         skgpu::Protected isProtected = skgpu::Protected(caps->protectedSupport());
214 
215         // Note: not renderable
216         TextureInfo info = caps->getDefaultSampledTextureInfo(kRGBA_8888_SkColorType,
217                                                               fWithMips ? Mipmapped::kYes
218                                                                         : Mipmapped::kNo,
219                                                               isProtected,
220                                                               skgpu::Renderable::kNo);
221         REPORTER_ASSERT(fReporter, info.isValid());
222 
223         fBETexture = fRecorder->createBackendTexture(kImageSize, info);
224         REPORTER_ASSERT(fReporter, fBETexture.isValid());
225 
226         update_backend_texture(fReporter, fRecorder, fBETexture, kRGBA_8888_SkColorType,
227                                fWithMips, kInitialColor);
228 
229         fMutatingImg = SkImages::WrapTexture(fRecorder,
230                                              fBETexture,
231                                              kRGBA_8888_SkColorType,
232                                              kPremul_SkAlphaType,
233                                              /* colorSpace= */ nullptr);
234         REPORTER_ASSERT(fReporter, fMutatingImg);
235 
236         return fRecorder->snap();
237     }
238 
mutate(int mutationIndex)239     std::unique_ptr<Recording> mutate(int mutationIndex) override {
240         update_backend_texture(fReporter, fRecorder, fBETexture, kRGBA_8888_SkColorType,
241                                fWithMips, kMutationColors[mutationIndex]);
242         return fRecorder->snap();
243     }
244 
getCase() const245     int getCase() const override { return 1; }
246 
247 private:
248     BackendTexture fBETexture;
249 };
250 
251 // case 2 (Volatile Promise Images)
252 // To simulate the hardware video decoder use case this Mutator creates a set of BackendTextures
253 // and fills them w/ different colors. A single volatile Promise Image is created and is
254 // fulfilled by the different BackendTextures.
255 class VolatilePromiseImageMutator : public Mutator {
256 public:
Make(skiatest::Reporter * reporter,Recorder * recorder,bool withMips)257     static std::unique_ptr<Mutator> Make(skiatest::Reporter* reporter,
258                                          Recorder* recorder,
259                                          bool withMips) {
260         return std::make_unique<VolatilePromiseImageMutator>(reporter, recorder, withMips);
261     }
262 
VolatilePromiseImageMutator(skiatest::Reporter * reporter,Recorder * recorder,bool withMips)263     VolatilePromiseImageMutator(skiatest::Reporter* reporter, Recorder* recorder, bool withMips)
264             : Mutator(reporter, recorder, withMips) {
265     }
266 
~VolatilePromiseImageMutator()267     ~VolatilePromiseImageMutator() override {
268         // We need to delete the mutating image first since it holds onto the backend texture
269         // that was last used to fulfill the volatile promise image.
270         fMutatingImg.reset();
271 
272         fCallbackTracker.finishedTest();
273 
274         for (int i = 0; i < kNumMutations+1; ++i) {
275             fRecorder->deleteBackendTexture(fBETextures[i]);
276         }
277     }
278 
fulfill(void * ctx)279     static std::tuple<BackendTexture, void*> fulfill(void* ctx) {
280         VolatilePromiseImageMutator* mutator = reinterpret_cast<VolatilePromiseImageMutator*>(ctx);
281 
282         int index = mutator->fCallbackTracker.onFulfillCB();
283 
284         return { mutator->fBETextures[index], &mutator->fCallbackTracker };
285     }
286 
imageRelease(void * ctx)287     static void imageRelease(void* ctx) {
288         VolatilePromiseImageMutator* mutator = reinterpret_cast<VolatilePromiseImageMutator*>(ctx);
289 
290         mutator->fCallbackTracker.onImageReleaseCB();
291     }
292 
textureRelease(void * ctx)293     static void textureRelease(void* ctx) {
294         CallbackTracker* callbackTracker = reinterpret_cast<CallbackTracker*>(ctx);
295 
296         callbackTracker->onTextureReleaseCB();
297     }
298 
init(const Caps * caps)299     std::unique_ptr<Recording> init(const Caps* caps) override {
300         skgpu::Protected isProtected = skgpu::Protected(caps->protectedSupport());
301 
302         // Note: not renderable
303         TextureInfo info = caps->getDefaultSampledTextureInfo(kRGBA_8888_SkColorType,
304                                                               fWithMips ? Mipmapped::kYes
305                                                                         : Mipmapped::kNo,
306                                                               isProtected,
307                                                               skgpu::Renderable::kNo);
308         REPORTER_ASSERT(fReporter, info.isValid());
309 
310         fBETextures[0] = fRecorder->createBackendTexture(kImageSize, info);
311         REPORTER_ASSERT(fReporter, fBETextures[0].isValid());
312 
313         update_backend_texture(fReporter, fRecorder, fBETextures[0], kRGBA_8888_SkColorType,
314                                fWithMips, kInitialColor);
315 
316         for (int i = 0; i < kNumMutations; ++i) {
317             fBETextures[i+1] = fRecorder->createBackendTexture(kImageSize, info);
318             REPORTER_ASSERT(fReporter, fBETextures[i+1].isValid());
319 
320             update_backend_texture(fReporter, fRecorder, fBETextures[i+1], kRGBA_8888_SkColorType,
321                                    fWithMips, kMutationColors[i]);
322         }
323 
324         fMutatingImg = SkImages::PromiseTextureFrom(fRecorder,
325                                                     kImageSize,
326                                                     info,
327                                                     SkColorInfo(kRGBA_8888_SkColorType,
328                                                                 kPremul_SkAlphaType,
329                                                                 /* cs= */ nullptr),
330                                                     Volatile::kYes,
331                                                     fulfill,
332                                                     imageRelease,
333                                                     textureRelease,
334                                                     this);
335         REPORTER_ASSERT(fReporter, fMutatingImg);
336 
337         return fRecorder->snap();
338     }
339 
mutate(int mutationIndex)340     std::unique_ptr<Recording> mutate(int mutationIndex) override {
341         fCallbackTracker.onMutation();
342         return nullptr;
343     }
344 
getCase() const345     int getCase() const override { return 2; }
346 
347 private:
348     class CallbackTracker {
349     public:
CallbackTracker()350         CallbackTracker() {
351             for (int i = 0; i < kNumMutations+1; ++i) {
352                 fFulfilled[i] = false;
353                 fReleased[i] = false;
354             }
355         }
356 
onMutation()357         void onMutation() {
358             // In this use case, the active mutation occurs in the volatile promise image callbacks.
359             ++fMutationCount;
360         }
361 
onFulfillCB()362         int onFulfillCB() {
363             SkASSERT(fMutationCount < kNumMutations+1);
364             SkASSERT(fFulfilledCount == fMutationCount);
365             // For this unit test we should only be fulfilling with each backend texture only once
366             SkASSERT(!fFulfilled[fFulfilledCount]);
367             SkASSERT(!fReleased[fFulfilledCount]);
368 
369             fFulfilled[fFulfilledCount] = true;
370             return fFulfilledCount++;
371         }
372 
onImageReleaseCB()373         void onImageReleaseCB() {
374             SkASSERT(!fImageReleased);
375             fImageReleased = true;
376         }
377 
onTextureReleaseCB()378         void onTextureReleaseCB() {
379             SkASSERT(fReleasedCount >= 0 && fReleasedCount < kNumMutations+1);
380 
381             SkASSERT(fFulfilled[fReleasedCount]);
382             SkASSERT(!fReleased[fReleasedCount]);
383             fReleased[fReleasedCount] = true;
384             fReleasedCount++;
385         }
386 
finishedTest() const387         void finishedTest() const {
388             SkASSERT(fMutationCount == kNumMutations);
389             SkASSERT(fImageReleased);
390 
391             for (int i = 0; i < kNumMutations+1; ++i) {
392                 SkASSERT(fFulfilled[i]);
393                 SkASSERT(fReleased[i]);
394             }
395         }
396 
397     private:
398         int fMutationCount = 0;
399         int fFulfilledCount = 0;
400         bool fImageReleased = false;
401         int fReleasedCount = 0;
402         bool fFulfilled[kNumMutations+1];
403         bool fReleased[kNumMutations+1];
404     };
405 
406     CallbackTracker fCallbackTracker;
407 
408     BackendTexture fBETextures[kNumMutations+1];
409 };
410 
411 // case 3 (Surface/Image pair)
412 // This mutator creates an SkSurface/SkImage pair that share the same backend object.
413 // Mutation is accomplished by simply drawing to the SkSurface.
414 class SurfaceMutator : public Mutator {
415 public:
Make(skiatest::Reporter * reporter,Recorder * recorder,bool withMips)416     static std::unique_ptr<Mutator> Make(skiatest::Reporter* reporter,
417                                          Recorder* recorder,
418                                          bool withMips) {
419         return std::make_unique<SurfaceMutator>(reporter, recorder, withMips);
420     }
421 
SurfaceMutator(skiatest::Reporter * reporter,Recorder * recorder,bool withMips)422     SurfaceMutator(skiatest::Reporter* reporter, Recorder* recorder, bool withMips)
423             : Mutator(reporter, recorder, withMips) {
424     }
425 
init(const Caps *)426     std::unique_ptr<Recording> init(const Caps* /* caps */) override {
427         SkImageInfo ii = SkImageInfo::Make(kImageSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
428 
429         fMutatingSurface = SkSurfaces::RenderTarget(
430                 fRecorder, ii, fWithMips ? Mipmapped::kYes : Mipmapped::kNo);
431         REPORTER_ASSERT(fReporter, fMutatingSurface);
432 
433         fMutatingSurface->getCanvas()->clear(kInitialColor);
434 
435         fMutatingImg = SkSurfaces::AsImage(fMutatingSurface);
436         REPORTER_ASSERT(fReporter, fMutatingImg);
437 
438         return fRecorder->snap();
439     }
440 
mutate(int mutationIndex)441     std::unique_ptr<Recording> mutate(int mutationIndex) override {
442         fMutatingSurface->getCanvas()->clear(kMutationColors[mutationIndex]);
443         return fRecorder->snap();
444     }
445 
getCase() const446     int getCase() const override { return 3; }
447 
448 private:
449     sk_sp<SkSurface> fMutatingSurface;
450 };
451 
452 using MutatorFactoryT = std::unique_ptr<Mutator> (*)(skiatest::Reporter*, Recorder*, bool withMips);
453 
run_test(skiatest::Reporter * reporter,Context * context,bool useTwoRecorders,bool withMips,MutatorFactoryT createMutator)454 void run_test(skiatest::Reporter* reporter,
455               Context* context,
456               bool useTwoRecorders,
457               bool withMips,
458               MutatorFactoryT createMutator) {
459     const Caps* caps = context->priv().caps();
460 
461     std::unique_ptr<Recorder> recorders[2];
462     recorders[0] = context->makeRecorder();
463 
464     Recorder* mutatorRecorder = recorders[0].get();
465     Recorder* redrawerRecorder = recorders[0].get();
466 
467     if (useTwoRecorders) {
468         recorders[1] = context->makeRecorder();
469         redrawerRecorder = recorders[1].get();
470     }
471 
472     std::unique_ptr<Mutator> mutator = createMutator(reporter, mutatorRecorder, withMips);
473 
474     {
475         std::unique_ptr<Recording> imgCreationRecording = mutator->init(caps);
476         REPORTER_ASSERT(reporter, context->insertRecording({ imgCreationRecording.get() }));
477     }
478 
479     {
480         Redrawer redrawer(reporter, redrawerRecorder);
481 
482         redrawer.init(mutator->getMutatingImage());
483 
484         REPORTER_ASSERT(reporter, context->insertRecording({ redrawer.imgDrawRecording() }));
485         redrawer.checkResult(context, mutator->getCase(),
486                              useTwoRecorders, withMips, kInitialColor);
487 
488         for (int i = 0; i < kNumMutations; ++i) {
489             {
490                 std::unique_ptr<Recording> imgMutationRecording = mutator->mutate(i);
491                 if (imgMutationRecording) {
492                     REPORTER_ASSERT(reporter,
493                                     context->insertRecording({imgMutationRecording.get()}));
494                 }
495             }
496 
497             REPORTER_ASSERT(reporter, context->insertRecording({ redrawer.imgDrawRecording() }));
498             redrawer.checkResult(context, mutator->getCase(),
499                                  useTwoRecorders, withMips, kMutationColors[i]);
500         }
501     }
502 }
503 
504 } // anonymous namespace
505 
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(MutableImagesTest,reporter,context,CtsEnforcement::kApiLevel_V)506 DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(MutableImagesTest, reporter, context,
507                                          CtsEnforcement::kApiLevel_V) {
508 
509     for (bool useTwoRecorders : { false, true }) {
510         for (bool withMips : { false, true }) {
511             // case 1 (AHBs)
512             run_test(reporter, context, useTwoRecorders, withMips,
513                      UpdateBackendTextureMutator::Make);
514 
515             // case 2 (Volatile Promise Images)
516             run_test(reporter, context, useTwoRecorders, withMips,
517                      VolatilePromiseImageMutator::Make);
518 
519             // case 3 (Surface/Image pair)
520             if (!withMips) {
521                 // TODO: allow the mipmapped version when we can automatically regenerate mipmaps
522                 run_test(reporter, context, useTwoRecorders, withMips,
523                          SurfaceMutator::Make);
524             }
525         }
526     }
527 }
528