skia2/tests/CompressedBackendAllocationTest.cpp
Adlai Holler bcfc554fde Add GrDirectContext arg to SkImage::readPixels
Note: The polarity of the staging flag is inverted from usual because
a G3 dependency with no SkUserConfig.h relies on the legacy API.

Once this lands, we will migrate them and others, then remove the
staging API. The inverted staging flag is kind of nice, actually - I may
use that pattern in the future. It means less total CLs and it's just as
easy to flip the bit on or off during debugging.

Bug: skia:104662
Change-Id: I48cba1eeae3e2e6f79918c6d243e0666e68ec71b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/310656
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Adlai Holler <adlai@google.com>
2020-08-27 19:26:29 +00:00

334 lines
14 KiB
C++

/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkCanvas.h"
#include "include/gpu/GrDirectContext.h"
#include "include/gpu/GrRecordingContext.h"
#include "src/core/SkAutoPixmapStorage.h"
#include "src/core/SkCompressedDataUtils.h"
#include "src/core/SkMipmap.h"
#include "src/gpu/GrBackendUtils.h"
#include "src/gpu/GrContextPriv.h"
#include "src/image/SkImage_Base.h"
#include "tests/Test.h"
#include "tests/TestUtils.h"
#include "tools/ToolUtils.h"
// Just verify that 'actual' is entirely 'expected'
static void check_solid_pixmap(skiatest::Reporter* reporter,
const SkColor4f& expected, const SkPixmap& actual,
const char* label0, const char* label1, const char* label2) {
const float tols[4] = { 0.01f, 0.01f, 0.01f, 0.01f };
auto error = std::function<ComparePixmapsErrorReporter>(
[reporter, label0, label1, label2](int x, int y, const float diffs[4]) {
SkASSERT(x >= 0 && y >= 0);
ERRORF(reporter, "%s %s %s - mismatch at %d, %d (%f, %f, %f %f)",
label0, label1, label2, x, y,
diffs[0], diffs[1], diffs[2], diffs[3]);
});
CheckSolidPixels(expected, actual, tols, error);
}
// Create an SkImage to wrap 'backendTex'
sk_sp<SkImage> create_image(GrDirectContext* dContext, const GrBackendTexture& backendTex) {
SkImage::CompressionType compression =
GrBackendFormatToCompressionType(backendTex.getBackendFormat());
SkAlphaType at = SkCompressionTypeIsOpaque(compression) ? kOpaque_SkAlphaType
: kPremul_SkAlphaType;
return SkImage::MakeFromCompressedTexture(dContext,
backendTex,
kTopLeft_GrSurfaceOrigin,
at,
nullptr);
}
// Draw the compressed backend texture (wrapped in an SkImage) into an RGBA surface, attempting
// to access all the mipMap levels.
static void check_compressed_mipmaps(GrRecordingContext* rContext, sk_sp<SkImage> img,
SkImage::CompressionType compressionType,
const SkColor4f expectedColors[6],
GrMipmapped mipMapped,
skiatest::Reporter* reporter, const char* label) {
SkImageInfo readbackSurfaceII = SkImageInfo::Make(32, 32, kRGBA_8888_SkColorType,
kPremul_SkAlphaType);
sk_sp<SkSurface> surf = SkSurface::MakeRenderTarget(rContext,
SkBudgeted::kNo,
readbackSurfaceII, 1,
kTopLeft_GrSurfaceOrigin,
nullptr);
if (!surf) {
return;
}
SkCanvas* canvas = surf->getCanvas();
SkPaint p;
p.setFilterQuality(kHigh_SkFilterQuality); // to force mipMapping
p.setBlendMode(SkBlendMode::kSrc);
int numMipLevels = 1;
if (mipMapped == GrMipmapped::kYes) {
numMipLevels = SkMipmap::ComputeLevelCount(32, 32)+1;
}
for (int i = 0, rectSize = 32; i < numMipLevels; ++i, rectSize /= 2) {
SkASSERT(rectSize >= 1);
canvas->clear(SK_ColorTRANSPARENT);
SkRect r = SkRect::MakeWH(rectSize, rectSize);
canvas->drawImageRect(img, r, &p);
SkImageInfo readbackII = SkImageInfo::Make(rectSize, rectSize,
kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType);
SkAutoPixmapStorage actual2;
SkAssertResult(actual2.tryAlloc(readbackII));
actual2.erase(SkColors::kTransparent);
bool result = surf->readPixels(actual2, 0, 0);
REPORTER_ASSERT(reporter, result);
SkString str;
str.appendf("mip-level %d", i);
check_solid_pixmap(reporter, expectedColors[i], actual2,
GrCompressionTypeToStr(compressionType), label, str.c_str());
}
}
// Verify that we can readback from a compressed texture
static void check_readback(GrDirectContext* dContext, sk_sp<SkImage> img,
SkImage::CompressionType compressionType,
const SkColor4f& expectedColor,
skiatest::Reporter* reporter, const char* label) {
#ifdef SK_BUILD_FOR_IOS
// reading back ETC2 is broken on Metal/iOS (skbug.com/9839)
if (dContext->backend() == GrBackendApi::kMetal) {
return;
}
#endif
SkAutoPixmapStorage actual;
SkImageInfo readBackII = SkImageInfo::Make(img->width(), img->height(),
kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType);
SkAssertResult(actual.tryAlloc(readBackII));
actual.erase(SkColors::kTransparent);
bool result = img->readPixels(dContext, actual, 0, 0);
REPORTER_ASSERT(reporter, result);
check_solid_pixmap(reporter, expectedColor, actual,
GrCompressionTypeToStr(compressionType), label, "");
}
// Test initialization of compressed GrBackendTextures to a specific color
static void test_compressed_color_init(GrDirectContext* dContext,
skiatest::Reporter* reporter,
std::function<GrBackendTexture (GrDirectContext*,
const SkColor4f&,
GrMipmapped)> create,
const SkColor4f& color,
SkImage::CompressionType compression,
GrMipmapped mipMapped) {
GrBackendTexture backendTex = create(dContext, color, mipMapped);
if (!backendTex.isValid()) {
return;
}
sk_sp<SkImage> img = create_image(dContext, backendTex);
if (!img) {
return;
}
SkColor4f expectedColors[6] = { color, color, color, color, color, color };
check_compressed_mipmaps(dContext, img, compression, expectedColors, mipMapped,
reporter, "colorinit");
check_readback(dContext, img, compression, color, reporter, "solid readback");
SkColor4f newColor;
newColor.fR = color.fB;
newColor.fG = color.fR;
newColor.fB = color.fG;
newColor.fA = color.fA;
bool result = dContext->updateCompressedBackendTexture(backendTex, newColor, nullptr, nullptr);
// Since we were able to create the compressed texture we should be able to update it.
REPORTER_ASSERT(reporter, result);
SkColor4f expectedNewColors[6] = {newColor, newColor, newColor, newColor, newColor, newColor};
check_compressed_mipmaps(dContext, img, compression, expectedNewColors, mipMapped, reporter,
"colorinit");
check_readback(dContext, std::move(img), compression, newColor, reporter, "solid readback");
dContext->deleteBackendTexture(backendTex);
}
// Create compressed data pulling the color for each mipmap level from 'levelColors'.
static std::unique_ptr<const char[]> make_compressed_data(SkImage::CompressionType compression,
SkColor4f levelColors[6],
GrMipmapped mipMapped) {
SkISize dimensions { 32, 32 };
int numMipLevels = 1;
if (mipMapped == GrMipmapped::kYes) {
numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
}
SkTArray<size_t> mipMapOffsets(numMipLevels);
size_t dataSize = SkCompressedDataSize(compression, dimensions, &mipMapOffsets,
mipMapped == GrMipmapped::kYes);
char* data = new char[dataSize];
for (int level = 0; level < numMipLevels; ++level) {
// We have to do this a level at a time bc we might have a different color for
// each level
GrFillInCompressedData(compression, dimensions,
GrMipmapped::kNo, &data[mipMapOffsets[level]], levelColors[level]);
dimensions = {std::max(1, dimensions.width() /2), std::max(1, dimensions.height()/2)};
}
return std::unique_ptr<const char[]>(data);
}
// Verify that we can initialize a compressed backend texture with data (esp.
// the mipmap levels).
static void test_compressed_data_init(GrDirectContext* dContext,
skiatest::Reporter* reporter,
std::function<GrBackendTexture (GrDirectContext*,
const char* data,
size_t dataSize,
GrMipmapped)> create,
SkImage::CompressionType compression,
GrMipmapped mipMapped) {
SkColor4f expectedColors[6] = {
{ 1.0f, 0.0f, 0.0f, 1.0f }, // R
{ 0.0f, 1.0f, 0.0f, 1.0f }, // G
{ 0.0f, 0.0f, 1.0f, 1.0f }, // B
{ 0.0f, 1.0f, 1.0f, 1.0f }, // C
{ 1.0f, 0.0f, 1.0f, 1.0f }, // M
{ 1.0f, 1.0f, 0.0f, 1.0f }, // Y
};
std::unique_ptr<const char[]> data(make_compressed_data(compression, expectedColors,
mipMapped));
size_t dataSize = SkCompressedDataSize(compression, { 32, 32 }, nullptr,
mipMapped == GrMipmapped::kYes);
GrBackendTexture backendTex = create(dContext, data.get(), dataSize, mipMapped);
if (!backendTex.isValid()) {
return;
}
sk_sp<SkImage> img = create_image(dContext, backendTex);
if (!img) {
return;
}
check_compressed_mipmaps(dContext, img, compression, expectedColors,
mipMapped, reporter, "pixmap");
check_readback(dContext, img, compression, expectedColors[0], reporter, "data readback");
SkColor4f expectedColorsNew[6] = {
{1.0f, 1.0f, 0.0f, 1.0f}, // Y
{1.0f, 0.0f, 0.0f, 1.0f}, // R
{0.0f, 1.0f, 0.0f, 1.0f}, // G
{0.0f, 0.0f, 1.0f, 1.0f}, // B
{0.0f, 1.0f, 1.0f, 1.0f}, // C
{1.0f, 0.0f, 1.0f, 1.0f}, // M
};
std::unique_ptr<const char[]> dataNew(
make_compressed_data(compression, expectedColorsNew, mipMapped));
size_t dataNewSize =
SkCompressedDataSize(compression, {32, 32}, nullptr, mipMapped == GrMipMapped::kYes);
bool result = dContext->updateCompressedBackendTexture(backendTex, dataNew.get(), dataNewSize,
nullptr, nullptr);
// Since we were able to create the compressed texture we should be able to update it.
REPORTER_ASSERT(reporter, result);
check_compressed_mipmaps(dContext, img, compression, expectedColorsNew, mipMapped, reporter,
"pixmap");
check_readback(dContext, std::move(img), compression, expectedColorsNew[0], reporter,
"data readback");
dContext->deleteBackendTexture(backendTex);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(CompressedBackendAllocationTest, reporter, ctxInfo) {
auto dContext = ctxInfo.directContext();
const GrCaps* caps = dContext->priv().caps();
struct {
SkImage::CompressionType fCompression;
SkColor4f fColor;
} combinations[] = {
{ SkImage::CompressionType::kETC2_RGB8_UNORM, SkColors::kRed },
{ SkImage::CompressionType::kBC1_RGB8_UNORM, SkColors::kBlue },
{ SkImage::CompressionType::kBC1_RGBA8_UNORM, SkColors::kTransparent },
};
for (auto combo : combinations) {
GrBackendFormat format = dContext->compressedBackendFormat(combo.fCompression);
if (!format.isValid()) {
continue;
}
if (!caps->isFormatTexturable(format)) {
continue;
}
for (auto mipMapped : { GrMipmapped::kNo, GrMipmapped::kYes }) {
if (GrMipmapped::kYes == mipMapped && !caps->mipmapSupport()) {
continue;
}
// color initialized
{
auto createWithColorMtd = [format](GrDirectContext* dContext,
const SkColor4f& color,
GrMipmapped mipMapped) {
return dContext->createCompressedBackendTexture(32, 32, format, color,
mipMapped, GrProtected::kNo);
};
test_compressed_color_init(dContext, reporter, createWithColorMtd,
combo.fColor, combo.fCompression, mipMapped);
}
// data initialized
{
auto createWithDataMtd = [format](GrDirectContext* dContext,
const char* data, size_t dataSize,
GrMipmapped mipMapped) {
return dContext->createCompressedBackendTexture(32, 32, format, data, dataSize,
mipMapped, GrProtected::kNo);
};
test_compressed_data_init(dContext, reporter, createWithDataMtd,
combo.fCompression, mipMapped);
}
}
}
}