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/SkBitmap.h"
11 #include "include/core/SkImageGenerator.h"
12 #include "include/core/SkPictureRecorder.h"
13 #include "include/core/SkSpan.h"
14 #include "include/gpu/graphite/Context.h"
15 #include "include/gpu/graphite/Recording.h"
16 #include "src/core/SkMipmapBuilder.h"
17 #include "src/gpu/graphite/Caps.h"
18 #include "src/gpu/graphite/RecorderPriv.h"
19 #include "src/gpu/graphite/Surface_Graphite.h"
20 #include "tests/TestUtils.h"
21 #include "tools/ToolUtils.h"
22 
23 using namespace skgpu::graphite;
24 using Mipmapped = skgpu::Mipmapped;
25 
26 namespace {
27 
28 const SkISize kSurfaceSize = { 16, 16 };
29 const SkISize kImageSize = { 32, 32 };
30 
31 constexpr SkColor4f kBaseImageColor = SkColors::kYellow;
32 constexpr SkColor4f kFirstMipLevelColor = SkColors::kRed;
33 constexpr SkColor4f kBackgroundColor = SkColors::kBlue;
34 
create_and_attach_mipmaps(sk_sp<SkImage> img)35 sk_sp<SkImage> create_and_attach_mipmaps(sk_sp<SkImage> img) {
36     constexpr SkColor4f mipLevelColors[] = {
37             kFirstMipLevelColor,
38             SkColors::kGreen,
39             SkColors::kMagenta,
40             SkColors::kCyan,
41             SkColors::kWhite,
42     };
43 
44     SkMipmapBuilder builder(img->imageInfo());
45 
46     int count = builder.countLevels();
47 
48     SkASSERT_RELEASE(count == std::size(mipLevelColors));
49 
50     for (int i = 0; i < count; ++i) {
51         SkPixmap pm = builder.level(i);
52         pm.erase(mipLevelColors[i]);
53     }
54 
55     return builder.attachTo(img);
56 }
57 
create_raster(Mipmapped mipmapped)58 sk_sp<SkImage> create_raster(Mipmapped mipmapped) {
59     SkImageInfo ii = SkImageInfo::Make(kImageSize.width(),
60                                        kImageSize.height(),
61                                        kRGBA_8888_SkColorType,
62                                        kPremul_SkAlphaType);
63     SkBitmap bm;
64     if (!bm.tryAllocPixels(ii)) {
65         return nullptr;
66     }
67 
68     bm.eraseColor(kBaseImageColor);
69 
70     sk_sp<SkImage> img = SkImage::MakeFromBitmap(bm);
71 
72     if (mipmapped == Mipmapped::kYes) {
73         img = create_and_attach_mipmaps(std::move(img));
74     }
75 
76     return img;
77 }
78 
79 /* 0 */
create_raster_backed_image_no_mipmaps(Recorder *)80 sk_sp<SkImage> create_raster_backed_image_no_mipmaps(Recorder*) {
81     return create_raster(Mipmapped::kNo);
82 }
83 
84 /* 1 */
create_raster_backed_image_with_mipmaps(Recorder *)85 sk_sp<SkImage> create_raster_backed_image_with_mipmaps(Recorder*) {
86     return create_raster(Mipmapped::kYes);
87 }
88 
89 /* 2 */
create_gpu_backed_image_no_mipmaps(Recorder * recorder)90 sk_sp<SkImage> create_gpu_backed_image_no_mipmaps(Recorder* recorder) {
91     sk_sp<SkImage> raster = create_raster(Mipmapped::kNo);
92     return raster->makeTextureImage(recorder, { Mipmapped::kNo });
93 }
94 
95 /* 3 */
create_gpu_backed_image_with_mipmaps(Recorder * recorder)96 sk_sp<SkImage> create_gpu_backed_image_with_mipmaps(Recorder* recorder) {
97     sk_sp<SkImage> raster = create_raster(Mipmapped::kYes);
98     return raster->makeTextureImage(recorder, { Mipmapped::kYes });
99 }
100 
101 /* 4 */
create_picture_backed_image(Recorder *)102 sk_sp<SkImage> create_picture_backed_image(Recorder*) {
103     SkIRect r = SkIRect::MakeWH(kImageSize.width(), kImageSize.height());
104     SkPaint paint;
105     paint.setColor(kBaseImageColor);
106 
107     SkPictureRecorder recorder;
108     SkCanvas* canvas = recorder.beginRecording(SkRect::Make(r));
109     canvas->drawIRect(r, paint);
110     sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
111 
112     return SkImage::MakeFromPicture(std::move(picture),
113                                     r.size(),
114                                     /* matrix= */ nullptr,
115                                     /* paint= */ nullptr,
116                                     SkImage::BitDepth::kU8,
117                                     SkColorSpace::MakeSRGB());
118 }
119 
120 /* 5 */
create_bitmap_generator_backed_image(Recorder *)121 sk_sp<SkImage> create_bitmap_generator_backed_image(Recorder*) {
122 
123     class BitmapBackedGenerator final : public SkImageGenerator {
124     public:
125         BitmapBackedGenerator()
126                 : SkImageGenerator(SkImageInfo::Make(kImageSize.width(),
127                                                      kImageSize.height(),
128                                                      kRGBA_8888_SkColorType,
129                                                      kPremul_SkAlphaType)) {
130         }
131 
132         bool onGetPixels(const SkImageInfo& dstInfo,
133                          void* pixels,
134                          size_t rowBytes,
135                          const Options&) override {
136 
137             if (dstInfo.dimensions() != kImageSize) {
138                 return false;
139             }
140 
141             SkBitmap bm;
142             if (!bm.tryAllocPixels(dstInfo)) {
143                 return false;
144             }
145 
146             bm.eraseColor(kBaseImageColor);
147 
148             return bm.readPixels(dstInfo, pixels, rowBytes, 0, 0);
149         }
150     };
151 
152     std::unique_ptr<SkImageGenerator> gen(new BitmapBackedGenerator());
153 
154     return SkImage::MakeFromGenerator(std::move(gen));
155 }
156 
check_img(skiatest::Reporter * reporter,Context * context,Recorder * recorder,SkImage * imageToDraw,Mipmapped mipmapped,const char * testcase,const SkColor4f & expectedColor)157 bool check_img(skiatest::Reporter* reporter,
158                Context* context,
159                Recorder* recorder,
160                SkImage* imageToDraw,
161                Mipmapped mipmapped,
162                const char* testcase,
163                const SkColor4f& expectedColor) {
164     SkImageInfo ii = SkImageInfo::Make(kSurfaceSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
165 
166     SkBitmap result;
167     result.allocPixels(ii);
168     SkPixmap pm;
169 
170     SkAssertResult(result.peekPixels(&pm));
171 
172     {
173         sk_sp<SkSurface> surface = SkSurface::MakeGraphite(recorder, ii);
174         if (!surface) {
175             ERRORF(reporter, "Surface creation failed");
176             return false;
177         }
178 
179         SkCanvas* canvas = surface->getCanvas();
180 
181         canvas->clear(kBackgroundColor);
182 
183         SkSamplingOptions sampling = (mipmapped == Mipmapped::kYes)
184                 ? SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNearest)
185                 : SkSamplingOptions(SkFilterMode::kLinear);
186 
187         canvas->drawImageRect(imageToDraw,
188                               SkRect::MakeWH(kSurfaceSize.width(), kSurfaceSize.height()),
189                               sampling);
190 
191         if (!surface->readPixels(pm, 0, 0)) {
192             ERRORF(reporter, "readPixels failed");
193             return false;
194         }
195     }
196 
197     auto error = std::function<ComparePixmapsErrorReporter>(
198             [&](int x, int y, const float diffs[4]) {
199                 ERRORF(reporter,
200                        "case %s %s: expected (%.1f %.1f %.1f %.1f) got (%.1f, %.1f, %.1f, %.1f)",
201                        testcase,
202                        (mipmapped == Mipmapped::kYes) ? "w/ mipmaps" : "w/o mipmaps",
203                        expectedColor.fR, expectedColor.fG, expectedColor.fB, expectedColor.fA,
204                        expectedColor.fR-diffs[0], expectedColor.fG-diffs[1],
205                        expectedColor.fB-diffs[2], expectedColor.fA-diffs[3]);
206             });
207     static constexpr float kTol[] = {0, 0, 0, 0};
208     CheckSolidPixels(expectedColor, pm, kTol, error);
209 
210     return true;
211 }
212 
213 using FactoryT = sk_sp<SkImage> (*)(Recorder*);
214 
215 struct TestCase {
216     const char* fTestCase;
217     FactoryT    fFactory;
218     SkColor4f   fExpectedColors[2];   /* [ w/o mipmaps, w/ mipmaps ] */
219 };
220 
run_test(skiatest::Reporter * reporter,Context * context,Recorder * recorder,SkSpan<const TestCase> testcases)221 void run_test(skiatest::Reporter* reporter,
222               Context* context,
223               Recorder* recorder,
224               SkSpan<const TestCase> testcases) {
225 
226     for (auto t : testcases) {
227         for (auto mm : { Mipmapped::kNo, Mipmapped::kYes }) {
228             sk_sp<SkImage> image = t.fFactory(recorder);
229 
230             check_img(reporter, context, recorder, image.get(), mm,
231                       t.fTestCase, t.fExpectedColors[static_cast<int>(mm)]);
232         }
233     }
234 }
235 
236 } // anonymous namespace
237 
238 // This test creates a bunch of solid yellow images in different ways and then draws them into a
239 // smaller surface (w/ src mode) that has been initialized to solid blue. When mipmap levels
240 // are possible to be specified the first mipmap level is made red. Thus, when mipmapping
241 // is allowed and it is specified as the sample mode, the drawn image will be red.
242 
243 // For the Default ImageProvider (which does _no_ caching and conversion) the expectations are:
244 //
245 //    0) raster-backed image w/o mipmaps
246 //                    drawn w/o mipmapping    --> dropped draw (blue)
247 //                    drawn w/ mipmapping     --> dropped draw (blue)
248 //
249 //    1) raster-backed image w/ mipmaps
250 //                    drawn w/o mipmapping    --> dropped draw (blue)
251 //                    drawn w/ mipmapping     --> dropped draw (blue)
252 //
253 //    2) Graphite-backed w/o mipmaps
254 //                    drawn w/o mipmapping    --> drawn (yellow)
255 //                    drawn w/ mipmapping     --> drawn (yellow) - mipmap filtering is dropped
256 //
257 //    3) Graphite-backed w/ mipmaps
258 //                    drawn w/o mipmapping    --> drawn (yellow)
259 //                    drawn w/ mipmapping     --> drawn (red)
260 //
261 //    4) picture-backed image
262 //                    drawn w/o mipmapping    --> dropped draw (blue)
263 //                    drawn w/ mipmapping     --> dropped draw (blue)
264 //
265 //    5) bitmap-backed-generator based image
266 //                    drawn w/o mipmapping    --> dropped draw (blue)
267 //                    drawn w/ mipmapping     --> dropped draw (blue)
268 //
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(ImageProviderTest_Graphite_Default,reporter,context)269 DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(ImageProviderTest_Graphite_Default, reporter, context) {
270     TestCase testcases[] = {
271         { "0", create_raster_backed_image_no_mipmaps,   { kBackgroundColor, kBackgroundColor } },
272         { "1", create_raster_backed_image_with_mipmaps, { kBackgroundColor, kBackgroundColor } },
273         { "2", create_gpu_backed_image_no_mipmaps,      { kBaseImageColor,  kBaseImageColor } },
274         { "3", create_gpu_backed_image_with_mipmaps,    { kBaseImageColor,  kFirstMipLevelColor } },
275         { "4", create_picture_backed_image,             { kBackgroundColor, kBackgroundColor } },
276         { "5", create_bitmap_generator_backed_image,    { kBackgroundColor, kBackgroundColor }  },
277     };
278 
279     std::unique_ptr<Recorder> recorder = context->makeRecorder();
280 
281     run_test(reporter, context, recorder.get(), testcases);
282 }
283 
284 // For the Testing ImageProvider (which does some caching and conversion) the expectations are:
285 //
286 //    0) raster-backed image w/o mipmaps
287 //                    drawn w/o mipmapping    --> drawn (yellow) - auto-converted
288 //                    drawn w/ mipmapping     --> drawn (yellow) - auto-converted
289 //
290 //    1) raster-backed image w/ mipmaps
291 //                    drawn w/o mipmapping    --> drawn (yellow) - auto-converted
292 //                    drawn w/ mipmapping     --> drawn (red) - auto-converted
293 //
294 //    2) Graphite-backed w/o mipmaps
295 //                    drawn w/o mipmapping    --> drawn (yellow)
296 //                    drawn w/ mipmapping     --> drawn (yellow) - mipmap filtering is dropped
297 //
298 //    3) Graphite-backed w/ mipmaps
299 //                    drawn w/o mipmapping    --> drawn (yellow)
300 //                    drawn w/ mipmapping     --> drawn (red)
301 //
302 //    4) picture-backed image
303 //                    drawn w/o mipmapping    --> drawn (yellow) - auto-converted
304 //                    drawn w/ mipmapping     --> drawn (yellow) - mipmap filtering is dropped
305 //                                                                 due to no mipmap regen
306 //
307 //    5) bitmap-backed-generator based image
308 //                    drawn w/o mipmapping    --> drawn (yellow) - auto-converted
309 //                    drawn w/ mipmapping     --> drawn (yellow) - auto-converted
310 //
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(ImageProviderTest_Graphite_Testing,reporter,context)311 DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(ImageProviderTest_Graphite_Testing, reporter, context) {
312     static const TestCase testcases[] = {
313         { "0", create_raster_backed_image_no_mipmaps,   { kBaseImageColor, kBaseImageColor } },
314         { "1", create_raster_backed_image_with_mipmaps, { kBaseImageColor, kFirstMipLevelColor } },
315         { "2", create_gpu_backed_image_no_mipmaps,      { kBaseImageColor, kBaseImageColor } },
316         { "3", create_gpu_backed_image_with_mipmaps,    { kBaseImageColor, kFirstMipLevelColor } },
317         { "4", create_picture_backed_image,             { kBaseImageColor, kBaseImageColor } },
318         { "5", create_bitmap_generator_backed_image,    { kBaseImageColor, kBaseImageColor } },
319     };
320 
321     RecorderOptions options = ToolUtils::CreateTestingRecorderOptions();
322     std::unique_ptr<skgpu::graphite::Recorder> recorder = context->makeRecorder(options);
323 
324     run_test(reporter, context, recorder.get(), testcases);
325 }
326 
327 // Here we're testing that the RequiredProperties parameter to makeTextureImage and makeSubset
328 // works as expected.
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(Make_TextureImage_Subset_Test,reporter,context)329 DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(Make_TextureImage_Subset_Test, reporter, context) {
330     static const struct {
331         // Some of the factories don't correctly create mipmaps through makeTextureImage
332         // bc Graphite's mipmap regeneration isn't implemented yet (b/238754357).
333         bool     fMipmapsBlockedByBug;
334         FactoryT fFactory;
335     } testcases[] = {
336         { false, create_raster_backed_image_no_mipmaps   },
337         { false, create_raster_backed_image_with_mipmaps },
338         { false, create_gpu_backed_image_no_mipmaps      },
339         { false, create_gpu_backed_image_with_mipmaps    },
340         { true,  create_picture_backed_image             },
341         { true,  create_bitmap_generator_backed_image    },
342     };
343 
344     const SkIRect kFakeSubset = SkIRect::MakeWH(kImageSize.width(), kImageSize.height());
345     const SkIRect kTrueSubset = kFakeSubset.makeInset(4, 4);
346 
347     std::unique_ptr<Recorder> recorder = context->makeRecorder();
348 
349     for (auto test : testcases) {
350         sk_sp<SkImage> orig = test.fFactory(recorder.get());
351 
352         for (Mipmapped mm : { Mipmapped::kNo, Mipmapped::kYes }) {
353             sk_sp<SkImage> i = orig->makeTextureImage(recorder.get(), { mm });
354 
355             // makeTextureImage has an optimization which allows Mipmaps on an Image if it
356             // would take extra work to remove them.
357             bool mipmapOptAllowed = orig->hasMipmaps() && mm == Mipmapped::kNo;
358 
359             REPORTER_ASSERT(reporter, i->isTextureBacked());
360             if (!test.fMipmapsBlockedByBug || mm == Mipmapped::kNo) {
361                 REPORTER_ASSERT(reporter, (i->hasMipmaps() == (mm == Mipmapped::kYes)) ||
362                                           (i->hasMipmaps() && mipmapOptAllowed));
363             }
364 
365             i = orig->makeSubset(kTrueSubset, recorder.get(), { mm });
366             REPORTER_ASSERT(reporter, i->isTextureBacked());
367             REPORTER_ASSERT(reporter, i->dimensions() == kTrueSubset.size());
368             REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
369 
370             i = orig->makeSubset(kFakeSubset, recorder.get(), { mm });
371             REPORTER_ASSERT(reporter, i->isTextureBacked());
372             REPORTER_ASSERT(reporter, i->dimensions() == kFakeSubset.size());
373             REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes) ||
374                                       (i->hasMipmaps() && mipmapOptAllowed));
375 
376             if (!orig->isTextureBacked()) {
377                 i = orig->makeTextureImage(nullptr, mm);
378                 REPORTER_ASSERT(reporter, !i);
379 
380                 // Make sure makeSubset w/o a recorder works as expected
381                 i = orig->makeSubset(kTrueSubset, nullptr, { mm });
382                 REPORTER_ASSERT(reporter, !i->isTextureBacked());
383                 REPORTER_ASSERT(reporter, i->dimensions() == kTrueSubset.size());
384                 REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
385 
386                 i = orig->makeSubset(kFakeSubset, nullptr, { mm });
387                 REPORTER_ASSERT(reporter, !i->isTextureBacked());
388                 REPORTER_ASSERT(reporter, i->dimensions() == kFakeSubset.size());
389                 REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
390             }
391         }
392     }
393 }
394 
395 namespace {
396 
pick_colortype(const Caps * caps,Mipmapped mipmapped)397 SkColorType pick_colortype(const Caps* caps, Mipmapped mipmapped) {
398     TextureInfo info = caps->getDefaultSampledTextureInfo(kRGB_565_SkColorType,
399                                                           mipmapped,
400                                                           skgpu::Protected::kNo,
401                                                           Renderable::kYes);
402     if (info.isValid()) {
403         return kRGB_565_SkColorType;
404     }
405 
406     info = caps->getDefaultSampledTextureInfo(kRGBA_F16_SkColorType,
407                                               mipmapped,
408                                               skgpu::Protected::kNo,
409                                               Renderable::kYes);
410     if (info.isValid()) {
411         return kRGBA_F16_SkColorType;
412     }
413 
414     return kUnknown_SkColorType;
415 }
416 
417 } // anonymous namespace
418 
419 // Here we're testing that the RequiredProperties parameter of:
420 //    SkImage::makeColorSpace and
421 //    SkImage::makeColorTypeAndColorSpace
422 // works as expected.
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(MakeColorSpace_Test,reporter,context)423 DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(MakeColorSpace_Test, reporter, context) {
424     static const struct {
425         // Some of the factories don't correctly create mipmaps through makeTextureImage
426         // bc Graphite's mipmap regeneration isn't implemented yet (b/238754357).
427         bool     fMipmapsBlockedByBug;
428         FactoryT fFactory;
429     } testcases[] = {
430             { false, create_raster_backed_image_no_mipmaps   },
431             { false, create_raster_backed_image_with_mipmaps },
432             { false, create_gpu_backed_image_no_mipmaps      },
433             { false, create_gpu_backed_image_with_mipmaps    },
434             { true,  create_picture_backed_image             },
435             { true,  create_bitmap_generator_backed_image    },
436     };
437 
438     sk_sp<SkColorSpace> spin = SkColorSpace::MakeSRGB()->makeColorSpin();
439 
440     std::unique_ptr<Recorder> recorder = context->makeRecorder();
441 
442     const Caps* caps = recorder->priv().caps();
443 
444     for (auto testcase : testcases) {
445         sk_sp<SkImage> orig = testcase.fFactory(recorder.get());
446 
447         SkASSERT(orig->colorType() == kRGBA_8888_SkColorType);
448         SkASSERT(!orig->colorSpace() || orig->colorSpace() == SkColorSpace::MakeSRGB().get());
449 
450         for (Mipmapped mm : { Mipmapped::kNo, Mipmapped::kYes }) {
451             sk_sp<SkImage> i = orig->makeColorSpace(spin, recorder.get(), { mm });
452 
453             REPORTER_ASSERT(reporter, i->isTextureBacked());
454             REPORTER_ASSERT(reporter, i->colorSpace() == spin.get());
455             if (!testcase.fMipmapsBlockedByBug || mm == Mipmapped::kNo) {
456                 REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
457             }
458 
459             SkColorType altCT = pick_colortype(caps, mm);
460             i = orig->makeColorTypeAndColorSpace(altCT, spin, recorder.get(), { mm });
461 
462             REPORTER_ASSERT(reporter, i->isTextureBacked());
463             REPORTER_ASSERT(reporter, i->colorType() == altCT);
464             REPORTER_ASSERT(reporter, i->colorSpace() == spin.get());
465             if (!testcase.fMipmapsBlockedByBug || mm == Mipmapped::kNo) {
466                 REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
467             }
468         }
469     }
470 }
471