/* * Copyright 2022 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "tests/Test.h" #include "include/core/SkBitmap.h" #include "include/core/SkColorSpace.h" #include "include/core/SkImageGenerator.h" #include "include/core/SkPicture.h" #include "include/core/SkPictureRecorder.h" #include "include/core/SkSpan.h" #include "include/gpu/graphite/Context.h" #include "include/gpu/graphite/Image.h" #include "include/gpu/graphite/Recording.h" #include "include/gpu/graphite/Surface.h" #include "include/private/base/SkTo.h" #include "src/core/SkMipmapBuilder.h" #include "src/gpu/graphite/Caps.h" #include "src/gpu/graphite/RecorderPriv.h" #include "src/gpu/graphite/Surface_Graphite.h" #include "src/image/SkImage_Base.h" #include "tests/TestUtils.h" #include "tools/ToolUtils.h" #include "tools/graphite/GraphiteToolUtils.h" using namespace skgpu::graphite; using Mipmapped = skgpu::Mipmapped; namespace { const SkISize kSurfaceSize = { 16, 16 }; const SkISize kImageSize = { 32, 32 }; constexpr SkColor4f kBaseImageColor = SkColors::kYellow; constexpr SkColor4f kFirstMipLevelColor = SkColors::kRed; constexpr SkColor4f kBackgroundColor = SkColors::kBlue; sk_sp create_and_attach_mipmaps(sk_sp img) { constexpr SkColor4f mipLevelColors[] = { kFirstMipLevelColor, SkColors::kGreen, SkColors::kMagenta, SkColors::kCyan, SkColors::kWhite, }; SkMipmapBuilder builder(img->imageInfo()); int count = builder.countLevels(); SkASSERT_RELEASE(count == SkToInt(std::size(mipLevelColors))); for (int i = 0; i < count; ++i) { SkPixmap pm = builder.level(i); pm.erase(mipLevelColors[i]); } return builder.attachTo(img); } sk_sp create_raster(Mipmapped mipmapped) { SkImageInfo ii = SkImageInfo::Make(kImageSize.width(), kImageSize.height(), kRGBA_8888_SkColorType, kPremul_SkAlphaType); SkBitmap bm; if (!bm.tryAllocPixels(ii)) { return nullptr; } bm.eraseColor(kBaseImageColor); sk_sp img = SkImages::RasterFromBitmap(bm); if (mipmapped == Mipmapped::kYes) { img = create_and_attach_mipmaps(std::move(img)); } return img; } /* 0 */ sk_sp create_raster_backed_image_no_mipmaps(Recorder*) { return create_raster(Mipmapped::kNo); } /* 1 */ sk_sp create_raster_backed_image_with_mipmaps(Recorder*) { return create_raster(Mipmapped::kYes); } /* 2 */ sk_sp create_gpu_backed_image_no_mipmaps(Recorder* recorder) { sk_sp raster = create_raster(Mipmapped::kNo); return SkImages::TextureFromImage(recorder, raster, {false}); } /* 3 */ sk_sp create_gpu_backed_image_with_mipmaps(Recorder* recorder) { sk_sp raster = create_raster(Mipmapped::kYes); return SkImages::TextureFromImage(recorder, raster, {true}); } /* 4 */ sk_sp create_picture_backed_image(Recorder*) { SkIRect r = SkIRect::MakeWH(kImageSize.width(), kImageSize.height()); SkPaint paint; paint.setColor(kBaseImageColor); SkPictureRecorder recorder; SkCanvas* canvas = recorder.beginRecording(SkRect::Make(r)); canvas->drawIRect(r, paint); sk_sp picture = recorder.finishRecordingAsPicture(); return SkImages::DeferredFromPicture(std::move(picture), r.size(), /* matrix= */ nullptr, /* paint= */ nullptr, SkImages::BitDepth::kU8, SkColorSpace::MakeSRGB()); } /* 5 */ sk_sp create_bitmap_generator_backed_image(Recorder*) { class BitmapBackedGenerator final : public SkImageGenerator { public: BitmapBackedGenerator() : SkImageGenerator(SkImageInfo::Make(kImageSize.width(), kImageSize.height(), kRGBA_8888_SkColorType, kPremul_SkAlphaType)) { } bool onGetPixels(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, const Options&) override { if (dstInfo.dimensions() != kImageSize) { return false; } SkBitmap bm; if (!bm.tryAllocPixels(dstInfo)) { return false; } bm.eraseColor(kBaseImageColor); return bm.readPixels(dstInfo, pixels, rowBytes, 0, 0); } }; std::unique_ptr gen(new BitmapBackedGenerator()); return SkImages::DeferredFromGenerator(std::move(gen)); } bool check_img(skiatest::Reporter* reporter, Context* context, Recorder* recorder, SkImage* imageToDraw, Mipmapped mipmapped, const char* testcase, const SkColor4f& expectedColor) { SkImageInfo ii = SkImageInfo::Make(kSurfaceSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType); SkBitmap result; result.allocPixels(ii); SkPixmap pm; SkAssertResult(result.peekPixels(&pm)); { sk_sp surface = SkSurfaces::RenderTarget(recorder, ii); if (!surface) { ERRORF(reporter, "Surface creation failed"); return false; } SkCanvas* canvas = surface->getCanvas(); canvas->clear(kBackgroundColor); SkSamplingOptions sampling = (mipmapped == Mipmapped::kYes) ? SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNearest) : SkSamplingOptions(SkFilterMode::kLinear); canvas->drawImageRect(imageToDraw, SkRect::MakeWH(kSurfaceSize.width(), kSurfaceSize.height()), sampling); if (!surface->readPixels(pm, 0, 0)) { ERRORF(reporter, "readPixels failed"); return false; } } auto error = std::function( [&](int x, int y, const float diffs[4]) { ERRORF(reporter, "case %s %s: expected (%.1f %.1f %.1f %.1f) got (%.1f, %.1f, %.1f, %.1f)", testcase, (mipmapped == Mipmapped::kYes) ? "w/ mipmaps" : "w/o mipmaps", expectedColor.fR, expectedColor.fG, expectedColor.fB, expectedColor.fA, expectedColor.fR-diffs[0], expectedColor.fG-diffs[1], expectedColor.fB-diffs[2], expectedColor.fA-diffs[3]); }); static constexpr float kTol[] = {0, 0, 0, 0}; CheckSolidPixels(expectedColor, pm, kTol, error); return true; } using FactoryT = sk_sp (*)(Recorder*); struct TestCase { const char* fTestCase; FactoryT fFactory; SkColor4f fExpectedColors[2]; /* [ w/o mipmaps, w/ mipmaps ] */ }; void run_test(skiatest::Reporter* reporter, Context* context, Recorder* recorder, SkSpan testcases) { for (auto t : testcases) { for (auto mm : { Mipmapped::kNo, Mipmapped::kYes }) { sk_sp image = t.fFactory(recorder); check_img(reporter, context, recorder, image.get(), mm, t.fTestCase, t.fExpectedColors[static_cast(mm)]); } } } } // anonymous namespace // This test creates a bunch of solid yellow images in different ways and then draws them into a // smaller surface (w/ src mode) that has been initialized to solid blue. When mipmap levels // are possible to be specified the first mipmap level is made red. Thus, when mipmapping // is allowed and it is specified as the sample mode, the drawn image will be red. // For the Default ImageProvider (which does _no_ caching and conversion) the expectations are: // // 0) raster-backed image w/o mipmaps // drawn w/o mipmapping --> dropped draw (blue) // drawn w/ mipmapping --> dropped draw (blue) // // 1) raster-backed image w/ mipmaps // drawn w/o mipmapping --> dropped draw (blue) // drawn w/ mipmapping --> dropped draw (blue) // // 2) Graphite-backed w/o mipmaps // drawn w/o mipmapping --> drawn (yellow) // drawn w/ mipmapping --> drawn (yellow) - mipmap filtering is dropped // // 3) Graphite-backed w/ mipmaps // drawn w/o mipmapping --> drawn (yellow) // drawn w/ mipmapping --> drawn (red) // // 4) picture-backed image // drawn w/o mipmapping --> dropped draw (blue) // drawn w/ mipmapping --> dropped draw (blue) // // 5) bitmap-backed-generator based image // drawn w/o mipmapping --> dropped draw (blue) // drawn w/ mipmapping --> dropped draw (blue) // DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(ImageProviderTest_Graphite_Default, reporter, context, CtsEnforcement::kApiLevel_V) { TestCase testcases[] = { { "0", create_raster_backed_image_no_mipmaps, { kBackgroundColor, kBackgroundColor } }, { "1", create_raster_backed_image_with_mipmaps, { kBackgroundColor, kBackgroundColor } }, { "2", create_gpu_backed_image_no_mipmaps, { kBaseImageColor, kBaseImageColor } }, { "3", create_gpu_backed_image_with_mipmaps, { kBaseImageColor, kFirstMipLevelColor } }, { "4", create_picture_backed_image, { kBackgroundColor, kBackgroundColor } }, { "5", create_bitmap_generator_backed_image, { kBackgroundColor, kBackgroundColor } }, }; std::unique_ptr recorder = context->makeRecorder(); run_test(reporter, context, recorder.get(), testcases); } // For the Testing ImageProvider (which does some caching and conversion) the expectations are: // // 0) raster-backed image w/o mipmaps // drawn w/o mipmapping --> drawn (yellow) - auto-converted // drawn w/ mipmapping --> drawn (yellow) - auto-converted // // 1) raster-backed image w/ mipmaps // drawn w/o mipmapping --> drawn (yellow) - auto-converted // drawn w/ mipmapping --> drawn (red) - auto-converted // // 2) Graphite-backed w/o mipmaps // drawn w/o mipmapping --> drawn (yellow) // drawn w/ mipmapping --> drawn (yellow) - mipmap filtering is dropped // // 3) Graphite-backed w/ mipmaps // drawn w/o mipmapping --> drawn (yellow) // drawn w/ mipmapping --> drawn (red) // // 4) picture-backed image // drawn w/o mipmapping --> drawn (yellow) - auto-converted // drawn w/ mipmapping --> drawn (yellow) - mipmaps auto generated // // 5) bitmap-backed-generator based image // drawn w/o mipmapping --> drawn (yellow) - auto-converted // drawn w/ mipmapping --> drawn (yellow) - auto-converted // DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(ImageProviderTest_Graphite_Testing, reporter, context, CtsEnforcement::kApiLevel_V) { static const TestCase testcases[] = { { "0", create_raster_backed_image_no_mipmaps, { kBaseImageColor, kBaseImageColor } }, { "1", create_raster_backed_image_with_mipmaps, { kBaseImageColor, kFirstMipLevelColor } }, { "2", create_gpu_backed_image_no_mipmaps, { kBaseImageColor, kBaseImageColor } }, { "3", create_gpu_backed_image_with_mipmaps, { kBaseImageColor, kFirstMipLevelColor } }, { "4", create_picture_backed_image, { kBaseImageColor, kBaseImageColor } }, { "5", create_bitmap_generator_backed_image, { kBaseImageColor, kBaseImageColor } }, }; RecorderOptions options = ToolUtils::CreateTestingRecorderOptions(); std::unique_ptr recorder = context->makeRecorder(options); run_test(reporter, context, recorder.get(), testcases); } // Here we're testing that the RequiredProperties parameter to makeTextureImage and makeSubset // works as expected. DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(Make_TextureImage_Subset_Test, reporter, context, CtsEnforcement::kApiLevel_V) { static const struct { std::string name; FactoryT fFactory; } testcases[] = { { "raster_no_mips", create_raster_backed_image_no_mipmaps }, { "raster_with_mips", create_raster_backed_image_with_mipmaps }, { "texture_no_mips", create_gpu_backed_image_no_mipmaps }, { "texture_with_mips", create_gpu_backed_image_with_mipmaps }, { "picture_backed", create_picture_backed_image }, { "image_generator", create_bitmap_generator_backed_image }, }; const SkIRect kFakeSubset = SkIRect::MakeWH(kImageSize.width(), kImageSize.height()); const SkIRect kTrueSubset = kFakeSubset.makeInset(4, 4); std::unique_ptr recorderUP = context->makeRecorder(); auto recorder = recorderUP.get(); for (const auto& test : testcases) { sk_sp orig = test.fFactory(recorder); skiatest::ReporterContext subtest(reporter, test.name); for (bool mipmapped : {false, true}) { skiatest::ReporterContext subtest2(reporter, SkStringPrintf("mipmaps: %d", (int)mipmapped)); sk_sp i = SkImages::TextureFromImage(recorder, orig, {mipmapped}); // makeTextureImage has an optimization which allows Mipmaps on an Image if it // would take extra work to remove them. bool mipmapOptAllowed = orig->hasMipmaps() && !mipmapped; REPORTER_ASSERT(reporter, i->isTextureBacked()); REPORTER_ASSERT( reporter, (i->hasMipmaps() == mipmapped) || (i->hasMipmaps() && mipmapOptAllowed)); // SkImage::makeSubset should "leave an image where it is", that is, return a // texture backed image iff the original image was texture backed. Otherwise, // it will return a raster image. i = orig->makeSubset(recorder, kTrueSubset, {mipmapped}); REPORTER_ASSERT(reporter, orig->isTextureBacked() == i->isTextureBacked(), "orig texture status %d != subset texture status %d", orig->isTextureBacked(), i->isTextureBacked()); if (i->isTextureBacked()) { REPORTER_ASSERT(reporter, i->dimensions() == kTrueSubset.size()); REPORTER_ASSERT(reporter, i->hasMipmaps() == mipmapped); } i = orig->makeSubset(recorder, kFakeSubset, {mipmapped}); REPORTER_ASSERT(reporter, orig->isTextureBacked() == i->isTextureBacked(), "orig texture status %d != subset texture status %d", orig->isTextureBacked(), i->isTextureBacked()); if (i->isTextureBacked()) { REPORTER_ASSERT(reporter, i->dimensions() == kFakeSubset.size()); REPORTER_ASSERT( reporter, i->hasMipmaps() == mipmapped || (i->hasMipmaps() && mipmapOptAllowed)); } // SubsetTextureFrom should always return a texture-backed image i = SkImages::SubsetTextureFrom(recorder, orig.get(), kTrueSubset, {mipmapped}); REPORTER_ASSERT(reporter, i->isTextureBacked()); REPORTER_ASSERT(reporter, i->dimensions() == kTrueSubset.size()); REPORTER_ASSERT(reporter, i->hasMipmaps() == mipmapped); if (!orig->isTextureBacked()) { i = SkImages::TextureFromImage(nullptr, orig, {mipmapped}); REPORTER_ASSERT(reporter, !i); // Make sure makeSubset w/o a recorder works as expected i = orig->makeSubset(nullptr, kTrueSubset, {mipmapped}); REPORTER_ASSERT(reporter, !i->isTextureBacked()); REPORTER_ASSERT(reporter, i->dimensions() == kTrueSubset.size()); // Picture-backed images don't support mipmaps but check the other types. if (as_IB(i)->type() != SkImage_Base::Type::kLazyPicture) { REPORTER_ASSERT(reporter, i->hasMipmaps() == mipmapped); } i = orig->makeSubset(nullptr, kFakeSubset, {mipmapped}); REPORTER_ASSERT(reporter, !i->isTextureBacked()); REPORTER_ASSERT(reporter, i->dimensions() == kFakeSubset.size()); // Picture-backed images don't support mipmaps but check the other types. if (as_IB(i)->type() != SkImage_Base::Type::kLazyPicture) { REPORTER_ASSERT(reporter, i->hasMipmaps() == mipmapped); } } } } } namespace { SkColorType pick_colortype(const Caps* caps, bool mipmapped) { auto mm = mipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; TextureInfo info = caps->getDefaultSampledTextureInfo( kRGB_565_SkColorType, mm, skgpu::Protected::kNo, skgpu::Renderable::kYes); if (info.isValid()) { return kRGB_565_SkColorType; } info = caps->getDefaultSampledTextureInfo( kRGBA_F16_SkColorType, mm, skgpu::Protected::kNo, skgpu::Renderable::kYes); if (info.isValid()) { return kRGBA_F16_SkColorType; } return kUnknown_SkColorType; } } // anonymous namespace // Here we're testing that the RequiredProperties parameter of: // SkImage::makeColorSpace and // SkImage::makeColorTypeAndColorSpace // works as expected. DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(MakeColorSpace_Test, reporter, context, CtsEnforcement::kApiLevel_V) { static const struct { std::string name; FactoryT fFactory; bool fTextureBacked; } testcases[] = { { "raster_no_mips", create_raster_backed_image_no_mipmaps, false }, { "raster_with_mips", create_raster_backed_image_with_mipmaps, false }, { "texture_no_mips", create_gpu_backed_image_no_mipmaps, true }, { "texture_with_mips", create_gpu_backed_image_with_mipmaps, true }, { "picture_backed", create_picture_backed_image, false }, { "image_generator", create_bitmap_generator_backed_image, false }, }; sk_sp spin = SkColorSpace::MakeSRGB()->makeColorSpin(); std::unique_ptr recorder = context->makeRecorder(); const Caps* caps = recorder->priv().caps(); for (const auto& testcase : testcases) { skiatest::ReporterContext subtest(reporter, testcase.name); sk_sp orig = testcase.fFactory(recorder.get()); SkASSERT(orig->colorType() == kRGBA_8888_SkColorType || orig->colorType() == kBGRA_8888_SkColorType); SkASSERT(!orig->colorSpace() || orig->colorSpace() == SkColorSpace::MakeSRGB().get()); for (bool mipmapped : {false, true}) { skiatest::ReporterContext subtest2(reporter, SkStringPrintf("mipmaps: %d", (int)mipmapped)); sk_sp i = orig->makeColorSpace(recorder.get(), spin, {mipmapped}); REPORTER_ASSERT(reporter, i != nullptr); REPORTER_ASSERT(reporter, i->isTextureBacked() == testcase.fTextureBacked); REPORTER_ASSERT(reporter, i->colorSpace() == spin.get()); if (testcase.fTextureBacked) { REPORTER_ASSERT(reporter, i->hasMipmaps() == mipmapped); } else { REPORTER_ASSERT(reporter, !i->hasMipmaps()); } SkColorType altCT = pick_colortype(caps, mipmapped); i = orig->makeColorTypeAndColorSpace(recorder.get(), altCT, spin, {mipmapped}); REPORTER_ASSERT(reporter, i != nullptr); REPORTER_ASSERT(reporter, i->isTextureBacked() == testcase.fTextureBacked); REPORTER_ASSERT(reporter, i->colorType() == altCT); REPORTER_ASSERT(reporter, i->colorSpace() == spin.get()); if (testcase.fTextureBacked) { REPORTER_ASSERT(reporter, i->hasMipmaps() == mipmapped); } else { REPORTER_ASSERT(reporter, !i->hasMipmaps()); } } } }