Flutter Engine
The Flutter Engine
MutableImagesTest.cpp
Go to the documentation of this file.
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
25#include "tests/TestUtils.h"
26#include "tools/ToolUtils.h"
27
28using namespace skgpu::graphite;
30
31namespace {
32
33// We draw the larger image into the smaller surface to force mipmapping
34const SkISize kImageSize = { 32, 32 };
35SkDEBUGCODE(constexpr int kNumMipLevels = 6;)
36const SkISize kSurfaceSize = { 16, 16 };
37
38constexpr int kNumMutations = 2;
39constexpr SkColor4f kInitialColor = SkColors::kRed;
40constexpr SkColor4f kMutationColors[kNumMutations] = {
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
67class Mutator {
68public:
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
80 SkImage* getMutatingImage() {
81 return fMutatingImg.get();
82 }
83
84protected:
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
95class Redrawer {
96public:
97 Redrawer(skiatest::Reporter* reporter, Recorder* recorder)
98 : fReporter(reporter)
99 , fRecorder(recorder) {
100 SkImageInfo ii = SkImageInfo::Make(kSurfaceSize,
103 fReadbackPM.alloc(ii);
104 }
105
106 void init(SkImage* imageToDraw) {
107 SkImageInfo ii = SkImageInfo::Make(kSurfaceSize,
110 fImgDrawSurface = SkSurfaces::RenderTarget(fRecorder, ii, Mipmapped::kNo);
111 REPORTER_ASSERT(fReporter, fImgDrawSurface);
112
113 fImgDrawRecording = MakeRedrawRecording(fRecorder, fImgDrawSurface.get(), imageToDraw);
114 }
115
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.).
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
149private:
150 static std::unique_ptr<Recording> MakeRedrawRecording(Recorder* recorder,
151 SkSurface* surfaceToDrawTo,
152 SkImage* imageToDraw) {
155
156 SkCanvas* canvas = surfaceToDrawTo->getCanvas();
157
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
175void update_backend_texture(skiatest::Reporter* reporter,
176 Recorder* recorder,
177 const BackendTexture& backendTex,
178 SkColorType ct,
179 bool withMips,
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.
197class UpdateBackendTextureMutator : public Mutator {
198public:
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
205 UpdateBackendTextureMutator(skiatest::Reporter* reporter, Recorder* recorder, bool withMips)
206 : Mutator(reporter, recorder, withMips) {
207 }
208 ~UpdateBackendTextureMutator() override {
209 fRecorder->deleteBackendTexture(fBETexture);
210 }
211
212 std::unique_ptr<Recording> init(const Caps* caps) override {
214
215 // Note: not renderable
217 fWithMips ? Mipmapped::kYes
219 isProtected,
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,
233 /* colorSpace= */ nullptr);
234 REPORTER_ASSERT(fReporter, fMutatingImg);
235
236 return fRecorder->snap();
237 }
238
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
245 int getCase() const override { return 1; }
246
247private:
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.
255class VolatilePromiseImageMutator : public Mutator {
256public:
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
263 VolatilePromiseImageMutator(skiatest::Reporter* reporter, Recorder* recorder, bool withMips)
264 : Mutator(reporter, recorder, withMips) {
265 }
266
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
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
287 static void imageRelease(void* ctx) {
288 VolatilePromiseImageMutator* mutator = reinterpret_cast<VolatilePromiseImageMutator*>(ctx);
289
290 mutator->fCallbackTracker.onImageReleaseCB();
291 }
292
293 static void textureRelease(void* ctx) {
294 CallbackTracker* callbackTracker = reinterpret_cast<CallbackTracker*>(ctx);
295
296 callbackTracker->onTextureReleaseCB();
297 }
298
299 std::unique_ptr<Recording> init(const Caps* caps) override {
301
302 // Note: not renderable
304 fWithMips ? Mipmapped::kYes
306 isProtected,
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,
326 info,
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
340 std::unique_ptr<Recording> mutate(int mutationIndex) override {
341 fCallbackTracker.onMutation();
342 return nullptr;
343 }
344
345 int getCase() const override { return 2; }
346
347private:
348 class CallbackTracker {
349 public:
350 CallbackTracker() {
351 for (int i = 0; i < kNumMutations+1; ++i) {
352 fFulfilled[i] = false;
353 fReleased[i] = false;
354 }
355 }
356
357 void onMutation() {
358 // In this use case, the active mutation occurs in the volatile promise image callbacks.
359 ++fMutationCount;
360 }
361
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
373 void onImageReleaseCB() {
374 SkASSERT(!fImageReleased);
375 fImageReleased = true;
376 }
377
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
387 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.
414class SurfaceMutator : public Mutator {
415public:
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
422 SurfaceMutator(skiatest::Reporter* reporter, Recorder* recorder, bool withMips)
423 : Mutator(reporter, recorder, withMips) {
424 }
425
426 std::unique_ptr<Recording> init(const Caps* /* caps */) override {
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
441 std::unique_ptr<Recording> mutate(int mutationIndex) override {
442 fMutatingSurface->getCanvas()->clear(kMutationColors[mutationIndex]);
443 return fRecorder->snap();
444 }
445
446 int getCase() const override { return 3; }
447
448private:
449 sk_sp<SkSurface> fMutatingSurface;
450};
451
452using MutatorFactoryT = std::unique_ptr<Mutator> (*)(skiatest::Reporter*, Recorder*, bool withMips);
453
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) {
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
508
509 for (bool useTwoRecorders : { false, true }) {
510 for (bool withMips : { false, true }) {
511 // case 1 (AHBs)
512 run_test(reporter, context, useTwoRecorders, withMips,
514
515 // case 2 (Volatile Promise Images)
516 run_test(reporter, context, useTwoRecorders, withMips,
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,
524 }
525 }
526 }
527}
static void run_test(GrDirectContext *dContext, skiatest::Reporter *reporter, BulkRectTest test)
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
reporter
Definition: FontMgrTest.cpp:39
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(MutableImagesTest, reporter, context, CtsEnforcement::kNextRelease)
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition: SkAlphaType.h:29
#define SkASSERT(cond)
Definition: SkAssert.h:116
SkColorType
Definition: SkColorType.h:19
@ kRGBA_8888_SkColorType
pixel with 8 bits for red, green, blue, alpha; in 32-bit word
Definition: SkColorType.h:24
SkDEBUGCODE(SK_SPI) SkThreadID SkGetThreadID()
bool CheckSolidPixels(const SkColor4f &col, const SkPixmap &pixmap, const float tolRGBA[4], std::function< ComparePixmapsErrorReporter > &error)
Definition: TestUtils.cpp:191
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
#define ERRORF(r,...)
Definition: Test.h:293
void clear(SkColor color)
Definition: SkCanvas.h:1199
void drawImageRect(const SkImage *, const SkRect &src, const SkRect &dst, const SkSamplingOptions &, const SkPaint *, SrcRectConstraint)
Definition: SkCanvas.cpp:2333
SkCanvas * getCanvas()
Definition: SkSurface.cpp:82
bool protectedSupport() const
Definition: Caps.h:226
virtual TextureInfo getDefaultSampledTextureInfo(SkColorType, Mipmapped mipmapped, Protected, Renderable) const =0
const Caps * caps() const
Definition: ContextPriv.h:32
std::unique_ptr< Recorder > makeRecorder(const RecorderOptions &={})
Definition: Context.cpp:132
bool insertRecording(const InsertRecordingInfo &)
Definition: Context.cpp:156
std::unique_ptr< Recording > snap()
Definition: Recorder.cpp:159
bool updateBackendTexture(const BackendTexture &, const SkPixmap srcData[], int numLevels)
Definition: Recorder.cpp:314
DlColor color
const uint8_t uint32_t uint32_t GError ** error
static const int kImageSize
Definition: flippity.cpp:44
double y
double x
constexpr SkColor4f kGreen
Definition: SkColor.h:441
constexpr SkColor4f kRed
Definition: SkColor.h:440
constexpr SkColor4f kTransparent
Definition: SkColor.h:434
constexpr SkColor4f kBlue
Definition: SkColor.h:442
SK_API sk_sp< SkImage > PromiseTextureFrom(skgpu::graphite::Recorder *, SkISize dimensions, const skgpu::graphite::TextureInfo &, const SkColorInfo &, skgpu::Origin origin, skgpu::graphite::Volatile, GraphitePromiseTextureFulfillProc, GraphitePromiseImageReleaseProc, GraphitePromiseTextureReleaseProc, GraphitePromiseImageContext, std::string_view label={})
SK_API sk_sp< SkImage > WrapTexture(skgpu::graphite::Recorder *, const skgpu::graphite::BackendTexture &, SkColorType colorType, SkAlphaType alphaType, sk_sp< SkColorSpace > colorSpace, skgpu::Origin origin, GenerateMipmapsFromBase generateMipmapsFromBase, TextureReleaseProc=nullptr, ReleaseContext=nullptr, std::string_view label={})
SK_API sk_sp< SkDocument > Make(SkWStream *dst, const SkSerialProcs *=nullptr, std::function< void(const SkPicture *)> onEndPage=nullptr)
static bool init()
PODArray< SkColor > colors
Definition: SkRecords.h:276
SkSamplingOptions sampling
Definition: SkRecords.h:337
SK_API sk_sp< SkImage > AsImage(sk_sp< const SkSurface >)
SK_API sk_sp< SkSurface > RenderTarget(GrRecordingContext *context, skgpu::Budgeted budgeted, const SkImageInfo &imageInfo, int sampleCount, GrSurfaceOrigin surfaceOrigin, const SkSurfaceProps *surfaceProps, bool shouldCreateWithMips=false, bool isProtected=false)
int make_pixmaps(SkColorType ct, SkAlphaType at, bool withMips, const SkColor4f colors[6], SkPixmap pixmaps[6], std::unique_ptr< char[]> *mem)
Definition: ToolUtils.cpp:181
Mipmapped
Definition: GpuTypes.h:53
Protected
Definition: GpuTypes.h:61
SkSamplingOptions(SkFilterMode::kLinear))
Definition: SkSize.h:16
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
static constexpr SkRect MakeWH(float w, float h)
Definition: SkRect.h:609