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