blob: 58b6ce0ea8825a58a8651585298b47d9c8f35bb4 [file] [log] [blame]
/*
* 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/SkImageGenerator.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkSpan.h"
#include "include/gpu/graphite/Context.h"
#include "include/gpu/graphite/Recording.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 "tests/TestUtils.h"
#include "tools/ToolUtils.h"
using namespace skgpu::graphite;
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<SkImage> create_and_attach_mipmaps(sk_sp<SkImage> img) {
constexpr SkColor4f mipLevelColors[] = {
kFirstMipLevelColor,
SkColors::kGreen,
SkColors::kMagenta,
SkColors::kCyan,
SkColors::kWhite,
};
SkMipmapBuilder builder(img->imageInfo());
int count = builder.countLevels();
SkASSERT_RELEASE(count == 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<SkImage> 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<SkImage> img = SkImage::MakeFromBitmap(bm);
if (mipmapped == Mipmapped::kYes) {
img = create_and_attach_mipmaps(std::move(img));
}
return img;
}
/* 0 */
sk_sp<SkImage> create_raster_backed_image_no_mipmaps(Recorder*) {
return create_raster(Mipmapped::kNo);
}
/* 1 */
sk_sp<SkImage> create_raster_backed_image_with_mipmaps(Recorder*) {
return create_raster(Mipmapped::kYes);
}
/* 2 */
sk_sp<SkImage> create_gpu_backed_image_no_mipmaps(Recorder* recorder) {
sk_sp<SkImage> raster = create_raster(Mipmapped::kNo);
return raster->makeTextureImage(recorder, { Mipmapped::kNo });
}
/* 3 */
sk_sp<SkImage> create_gpu_backed_image_with_mipmaps(Recorder* recorder) {
sk_sp<SkImage> raster = create_raster(Mipmapped::kYes);
return raster->makeTextureImage(recorder, { Mipmapped::kYes });
}
/* 4 */
sk_sp<SkImage> 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<SkPicture> picture = recorder.finishRecordingAsPicture();
return SkImage::MakeFromPicture(std::move(picture),
r.size(),
/* matrix= */ nullptr,
/* paint= */ nullptr,
SkImage::BitDepth::kU8,
SkColorSpace::MakeSRGB());
}
/* 5 */
sk_sp<SkImage> 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<SkImageGenerator> gen(new BitmapBackedGenerator());
return SkImage::MakeFromGenerator(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<SkSurface> surface = SkSurface::MakeGraphite(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<ComparePixmapsErrorReporter>(
[&](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<SkImage> (*)(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<const TestCase> testcases) {
for (auto t : testcases) {
for (auto mm : { Mipmapped::kNo, Mipmapped::kYes }) {
sk_sp<SkImage> image = t.fFactory(recorder);
check_img(reporter, context, recorder, image.get(), mm,
t.fTestCase, t.fExpectedColors[static_cast<int>(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) {
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> 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) - mipmap filtering is dropped
// due to no mipmap regen
//
// 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) {
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<skgpu::graphite::Recorder> 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) {
static const struct {
// Some of the factories don't correctly create mipmaps through makeTextureImage
// bc Graphite's mipmap regeneration isn't implemented yet (b/238754357).
bool fMipmapsBlockedByBug;
FactoryT fFactory;
} testcases[] = {
{ false, create_raster_backed_image_no_mipmaps },
{ false, create_raster_backed_image_with_mipmaps },
{ false, create_gpu_backed_image_no_mipmaps },
{ false, create_gpu_backed_image_with_mipmaps },
{ true, create_picture_backed_image },
{ true, create_bitmap_generator_backed_image },
};
const SkIRect kFakeSubset = SkIRect::MakeWH(kImageSize.width(), kImageSize.height());
const SkIRect kTrueSubset = kFakeSubset.makeInset(4, 4);
std::unique_ptr<Recorder> recorder = context->makeRecorder();
for (auto test : testcases) {
sk_sp<SkImage> orig = test.fFactory(recorder.get());
for (Mipmapped mm : { Mipmapped::kNo, Mipmapped::kYes }) {
sk_sp<SkImage> i = orig->makeTextureImage(recorder.get(), { mm });
// makeTextureImage has an optimization which allows Mipmaps on an Image if it
// would take extra work to remove them.
bool mipmapOptAllowed = orig->hasMipmaps() && mm == Mipmapped::kNo;
REPORTER_ASSERT(reporter, i->isTextureBacked());
if (!test.fMipmapsBlockedByBug || mm == Mipmapped::kNo) {
REPORTER_ASSERT(reporter, (i->hasMipmaps() == (mm == Mipmapped::kYes)) ||
(i->hasMipmaps() && mipmapOptAllowed));
}
i = orig->makeSubset(kTrueSubset, recorder.get(), { mm });
REPORTER_ASSERT(reporter, i->isTextureBacked());
REPORTER_ASSERT(reporter, i->dimensions() == kTrueSubset.size());
REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
i = orig->makeSubset(kFakeSubset, recorder.get(), { mm });
REPORTER_ASSERT(reporter, i->isTextureBacked());
REPORTER_ASSERT(reporter, i->dimensions() == kFakeSubset.size());
REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes) ||
(i->hasMipmaps() && mipmapOptAllowed));
if (!orig->isTextureBacked()) {
i = orig->makeTextureImage(nullptr, { mm });
REPORTER_ASSERT(reporter, !i);
// Make sure makeSubset w/o a recorder works as expected
i = orig->makeSubset(kTrueSubset, nullptr, { mm });
REPORTER_ASSERT(reporter, !i->isTextureBacked());
REPORTER_ASSERT(reporter, i->dimensions() == kTrueSubset.size());
REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
i = orig->makeSubset(kFakeSubset, nullptr, { mm });
REPORTER_ASSERT(reporter, !i->isTextureBacked());
REPORTER_ASSERT(reporter, i->dimensions() == kFakeSubset.size());
REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
}
}
}
}
namespace {
SkColorType pick_colortype(const Caps* caps, Mipmapped mipmapped) {
TextureInfo info = caps->getDefaultSampledTextureInfo(kRGB_565_SkColorType,
mipmapped,
skgpu::Protected::kNo,
Renderable::kYes);
if (info.isValid()) {
return kRGB_565_SkColorType;
}
info = caps->getDefaultSampledTextureInfo(kRGBA_F16_SkColorType,
mipmapped,
skgpu::Protected::kNo,
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) {
static const struct {
// Some of the factories don't correctly create mipmaps through makeTextureImage
// bc Graphite's mipmap regeneration isn't implemented yet (b/238754357).
bool fMipmapsBlockedByBug;
FactoryT fFactory;
} testcases[] = {
{ false, create_raster_backed_image_no_mipmaps },
{ false, create_raster_backed_image_with_mipmaps },
{ false, create_gpu_backed_image_no_mipmaps },
{ false, create_gpu_backed_image_with_mipmaps },
{ true, create_picture_backed_image },
{ true, create_bitmap_generator_backed_image },
};
sk_sp<SkColorSpace> spin = SkColorSpace::MakeSRGB()->makeColorSpin();
std::unique_ptr<Recorder> recorder = context->makeRecorder();
const Caps* caps = recorder->priv().caps();
for (auto testcase : testcases) {
sk_sp<SkImage> orig = testcase.fFactory(recorder.get());
SkASSERT(orig->colorType() == kRGBA_8888_SkColorType);
SkASSERT(!orig->colorSpace() || orig->colorSpace() == SkColorSpace::MakeSRGB().get());
for (Mipmapped mm : { Mipmapped::kNo, Mipmapped::kYes }) {
sk_sp<SkImage> i = orig->makeColorSpace(spin, recorder.get(), { mm });
REPORTER_ASSERT(reporter, i->isTextureBacked());
REPORTER_ASSERT(reporter, i->colorSpace() == spin.get());
if (!testcase.fMipmapsBlockedByBug || mm == Mipmapped::kNo) {
REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
}
SkColorType altCT = pick_colortype(caps, mm);
i = orig->makeColorTypeAndColorSpace(altCT, spin, recorder.get(), { mm });
REPORTER_ASSERT(reporter, i->isTextureBacked());
REPORTER_ASSERT(reporter, i->colorType() == altCT);
REPORTER_ASSERT(reporter, i->colorSpace() == spin.get());
if (!testcase.fMipmapsBlockedByBug || mm == Mipmapped::kNo) {
REPORTER_ASSERT(reporter, i->hasMipmaps() == (mm == Mipmapped::kYes));
}
}
}
}