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