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