skia2/tests/SRGBReadWritePixelsTest.cpp

318 lines
14 KiB
C++
Raw Normal View History

/*
* Copyright 2015 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/core/SkSurface.h"
#include "include/gpu/GrContext.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrImageInfo.h"
#include "src/gpu/GrRenderTargetContext.h"
#include "src/gpu/GrSurfaceContext.h"
#include "src/gpu/SkGr.h"
#include "tests/Test.h"
// using anonymous namespace because these functions are used as template params.
namespace {
/** convert 0..1 srgb value to 0..1 linear */
float srgb_to_linear(float srgb) {
if (srgb <= 0.04045f) {
return srgb / 12.92f;
} else {
return powf((srgb + 0.055f) / 1.055f, 2.4f);
}
}
/** convert 0..1 linear value to 0..1 srgb */
float linear_to_srgb(float linear) {
if (linear <= 0.0031308) {
return linear * 12.92f;
} else {
return 1.055f * powf(linear, 1.f / 2.4f) - 0.055f;
}
}
}
/** tests a conversion with an error tolerance */
template <float (*CONVERT)(float)> static bool check_conversion(uint32_t input, uint32_t output,
float error) {
// alpha should always be exactly preserved.
if ((input & 0xff000000) != (output & 0xff000000)) {
return false;
}
for (int c = 0; c < 3; ++c) {
uint8_t inputComponent = (uint8_t) ((input & (0xff << (c*8))) >> (c*8));
float lower = std::max(0.f, (float) inputComponent - error);
float upper = std::min(255.f, (float) inputComponent + error);
lower = CONVERT(lower / 255.f);
upper = CONVERT(upper / 255.f);
SkASSERT(lower >= 0.f && lower <= 255.f);
SkASSERT(upper >= 0.f && upper <= 255.f);
uint8_t outputComponent = (output & (0xff << (c*8))) >> (c*8);
if (outputComponent < SkScalarFloorToInt(lower * 255.f) ||
outputComponent > SkScalarCeilToInt(upper * 255.f)) {
return false;
}
}
return true;
}
/** tests a forward and backward conversion with an error tolerance */
template <float (*FORWARD)(float), float (*BACKWARD)(float)>
static bool check_double_conversion(uint32_t input, uint32_t output, float error) {
// alpha should always be exactly preserved.
if ((input & 0xff000000) != (output & 0xff000000)) {
return false;
}
for (int c = 0; c < 3; ++c) {
uint8_t inputComponent = (uint8_t) ((input & (0xff << (c*8))) >> (c*8));
float lower = std::max(0.f, (float) inputComponent - error);
float upper = std::min(255.f, (float) inputComponent + error);
lower = FORWARD(lower / 255.f);
upper = FORWARD(upper / 255.f);
SkASSERT(lower >= 0.f && lower <= 255.f);
SkASSERT(upper >= 0.f && upper <= 255.f);
uint8_t upperComponent = SkScalarCeilToInt(upper * 255.f);
uint8_t lowerComponent = SkScalarFloorToInt(lower * 255.f);
lower = std::max(0.f, (float) lowerComponent - error);
upper = std::min(255.f, (float) upperComponent + error);
lower = BACKWARD(lowerComponent / 255.f);
upper = BACKWARD(upperComponent / 255.f);
SkASSERT(lower >= 0.f && lower <= 255.f);
SkASSERT(upper >= 0.f && upper <= 255.f);
upperComponent = SkScalarCeilToInt(upper * 255.f);
lowerComponent = SkScalarFloorToInt(lower * 255.f);
uint8_t outputComponent = (output & (0xff << (c*8))) >> (c*8);
if (outputComponent < lowerComponent || outputComponent > upperComponent) {
return false;
}
}
return true;
}
static bool check_srgb_to_linear_conversion(uint32_t srgb, uint32_t linear, float error) {
return check_conversion<srgb_to_linear>(srgb, linear, error);
}
static bool check_linear_to_srgb_conversion(uint32_t linear, uint32_t srgb, float error) {
return check_conversion<linear_to_srgb>(linear, srgb, error);
}
static bool check_linear_to_srgb_to_linear_conversion(uint32_t input, uint32_t output, float error) {
return check_double_conversion<linear_to_srgb, srgb_to_linear>(input, output, error);
}
static bool check_srgb_to_linear_to_srgb_conversion(uint32_t input, uint32_t output, float error) {
return check_double_conversion<srgb_to_linear, linear_to_srgb>(input, output, error);
}
static bool check_no_conversion(uint32_t input, uint32_t output, float error) {
// This is a bit of a hack to check identity transformations that may lose precision.
return check_srgb_to_linear_to_srgb_conversion(input, output, error);
}
typedef bool (*CheckFn) (uint32_t orig, uint32_t actual, float error);
void read_and_check_pixels(skiatest::Reporter* reporter, GrSurfaceContext* context,
uint32_t* origData,
const SkImageInfo& dstInfo, CheckFn checker, float error,
const char* subtestName) {
int w = dstInfo.width();
int h = dstInfo.height();
SkAutoTMalloc<uint32_t> readData(w * h);
memset(readData.get(), 0, sizeof(uint32_t) * w * h);
if (!context->readPixels(dstInfo, readData.get(), 0, {0, 0})) {
ERRORF(reporter, "Could not read pixels for %s.", subtestName);
return;
}
for (int j = 0; j < h; ++j) {
for (int i = 0; i < w; ++i) {
uint32_t orig = origData[j * w + i];
uint32_t read = readData[j * w + i];
if (!checker(orig, read, error)) {
ERRORF(reporter, "Original 0x%08x, read back as 0x%08x in %s at %d, %d).", orig,
read, subtestName, i, j);
return;
}
}
}
}
namespace {
enum class Encoding {
kUntagged,
kLinear,
kSRGB,
};
}
static sk_sp<SkColorSpace> encoding_as_color_space(Encoding encoding) {
switch (encoding) {
case Encoding::kUntagged: return nullptr;
case Encoding::kLinear: return SkColorSpace::MakeSRGBLinear();
case Encoding::kSRGB: return SkColorSpace::MakeSRGB();
}
return nullptr;
}
static const char* encoding_as_str(Encoding encoding) {
switch (encoding) {
case Encoding::kUntagged: return "untagged";
case Encoding::kLinear: return "linear";
case Encoding::kSRGB: return "sRGB";
}
return nullptr;
}
static constexpr int kW = 255;
static constexpr int kH = 255;
static std::unique_ptr<uint32_t[]> make_data() {
std::unique_ptr<uint32_t[]> data(new uint32_t[kW * kH]);
for (int j = 0; j < kH; ++j) {
for (int i = 0; i < kW; ++i) {
data[j * kW + i] = (0xFF << 24) | (i << 16) | (i << 8) | i;
}
}
return data;
}
static std::unique_ptr<GrSurfaceContext> make_surface_context(Encoding contextEncoding,
GrContext* context,
skiatest::Reporter* reporter) {
auto surfaceContext = GrRenderTargetContext::Make(
context, GrColorType::kRGBA_8888, encoding_as_color_space(contextEncoding),
SkBackingFit::kExact, {kW, kH}, 1, GrMipMapped::kNo, GrProtected::kNo,
kBottomLeft_GrSurfaceOrigin, SkBudgeted::kNo);
if (!surfaceContext) {
ERRORF(reporter, "Could not create %s surface context.", encoding_as_str(contextEncoding));
}
turn on -Wreturn-std-move-in-c++11 This CL has a complicated back story, but it's concrete change is simple, just turning the warning on and converting a bunch of return foo; to return std::move(foo); These changes are exclusively in places where RVO and NRVO do not apply, so it should not conflict with warnings like -Wpessimizing-move. Since C++11, when you return a named local and its type doesn't match the declared return type exactly, there's an implicit std::move() wrapped around the value (what I'm making explicit here) so the move constructor gets an opportunity to take precedence over the copy constructor. You can read about this implicit move here under the section "automatic move from local variables and parameters": https://en.cppreference.com/w/cpp/language/return#Notes. This situation comes up for us with smart pointers: a function declares its return type as std::unique_ptr<Base> or sk_sp<Base>, and we return a std::unique_ptr<Impl> or sk_sp<Impl>. Those types don't match exactly, so RVO and NRVO don't come into play. They've always been going through move constructors, and that's not changed here, just made explicit. There was apparently once a bug in the C++11 standard and compilers implementing that which made these copy instead of move, and then this sort of code would do a little unnecessary ref/unref dance for sk_sp, and would entirely fail to compile for uncopyable std::unique_ptr. These explicit moves ostensibly will make our code more compatible with those older compilers. That compatibility alone is, I think, a terrible reason to land this CL. Like, actively bad. But... to balance that out, I think the explicit std::move()s here actually help remind us that RVO/NRVO are not in play, and remind us we're going to call the move constructor. So that C++11 standard bug becomes kind of useful for us, in that Clang added this warning to catch it, and its fix improves readability. So really read this all as, "warn about implicit std::move() on return". In the end I think it's just about readability. I don't really hold any hope out that we'll become compatible with those older compilers. Bug: skia:9909 Change-Id: Id596e9261188b6f10e759906af6c95fe303f6ffe Reviewed-on: https://skia-review.googlesource.com/c/skia/+/271601 Reviewed-by: Brian Salomon <bsalomon@google.com> Reviewed-by: Ben Wagner <bungeman@google.com> Commit-Queue: Mike Klein <mtklein@google.com>
2020-02-18 19:33:23 +00:00
return std::move(surfaceContext);
}
static void test_write_read(Encoding contextEncoding, Encoding writeEncoding, Encoding readEncoding,
float error, CheckFn check, GrContext* context,
skiatest::Reporter* reporter) {
auto surfaceContext = make_surface_context(contextEncoding, context, reporter);
if (!surfaceContext) {
return;
}
auto writeII = SkImageInfo::Make(kW, kH, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
encoding_as_color_space(writeEncoding));
auto data = make_data();
if (!surfaceContext->writePixels(writeII, data.get(), 0, {0, 0})) {
ERRORF(reporter, "Could not write %s to %s surface context.",
encoding_as_str(writeEncoding), encoding_as_str(contextEncoding));
return;
}
auto readII = SkImageInfo::Make(kW, kH, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
encoding_as_color_space(readEncoding));
SkString testName;
testName.printf("write %s data to a %s context and read as %s.", encoding_as_str(writeEncoding),
encoding_as_str(contextEncoding), encoding_as_str(readEncoding));
read_and_check_pixels(reporter, surfaceContext.get(), data.get(), readII, check, error,
testName.c_str());
}
// Test all combinations of writePixels/readPixels where the surface context/write source/read dst
// are sRGB, linear, or untagged RGBA_8888.
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SRGBReadWritePixels, reporter, ctxInfo) {
GrContext* context = ctxInfo.grContext();
if (!context->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888_SRGB,
GrRenderable::kNo).isValid()) {
return;
}
// We allow more error on GPUs with lower precision shader variables.
float error = context->priv().caps()->shaderCaps()->halfIs32Bits() ? 0.5f : 1.2f;
// For the all-sRGB case, we allow a small error only for devices that have
// precision variation because the sRGB data gets converted to linear and back in
// the shader.
float smallError = context->priv().caps()->shaderCaps()->halfIs32Bits() ? 0.0f : 1.f;
///////////////////////////////////////////////////////////////////////////////////////////////
// Write sRGB data to a sRGB context - no conversion on the write.
// back to sRGB - no conversion.
test_write_read(Encoding::kSRGB, Encoding::kSRGB, Encoding::kSRGB, smallError,
check_no_conversion, context, reporter);
// Reading back to untagged should be a pass through with no conversion.
test_write_read(Encoding::kSRGB, Encoding::kSRGB, Encoding::kUntagged, error,
check_no_conversion, context, reporter);
// Converts back to linear
test_write_read(Encoding::kSRGB, Encoding::kSRGB, Encoding::kLinear, error,
check_srgb_to_linear_conversion, context, reporter);
// Untagged source data should be interpreted as sRGB.
test_write_read(Encoding::kSRGB, Encoding::kUntagged, Encoding::kSRGB, smallError,
check_no_conversion, context, reporter);
///////////////////////////////////////////////////////////////////////////////////////////////
// Write linear data to a sRGB context. It gets converted to sRGB on write. The reads
// are all the same as the above cases where the original data was untagged.
test_write_read(Encoding::kSRGB, Encoding::kLinear, Encoding::kSRGB, error,
check_linear_to_srgb_conversion, context, reporter);
// When the dst buffer is untagged there should be no conversion on the read.
test_write_read(Encoding::kSRGB, Encoding::kLinear, Encoding::kUntagged, error,
check_linear_to_srgb_conversion, context, reporter);
test_write_read(Encoding::kSRGB, Encoding::kLinear, Encoding::kLinear, error,
check_linear_to_srgb_to_linear_conversion, context, reporter);
///////////////////////////////////////////////////////////////////////////////////////////////
// Write data to an untagged context. The write does no conversion no matter what encoding the
// src data has.
for (auto writeEncoding : {Encoding::kSRGB, Encoding::kUntagged, Encoding::kLinear}) {
// The read from untagged to sRGB also does no conversion.
test_write_read(Encoding::kUntagged, writeEncoding, Encoding::kSRGB, error,
check_no_conversion, context, reporter);
// Reading untagged back as untagged should do no conversion.
test_write_read(Encoding::kUntagged, writeEncoding, Encoding::kUntagged, error,
check_no_conversion, context, reporter);
// Reading untagged back as linear does convert (context is source, so treated as sRGB),
// dst is tagged.
test_write_read(Encoding::kUntagged, writeEncoding, Encoding::kLinear, error,
check_srgb_to_linear_conversion, context, reporter);
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Write sRGB data to a linear context - converts to sRGB on the write.
// converts back to sRGB on read.
test_write_read(Encoding::kLinear, Encoding::kSRGB, Encoding::kSRGB, error,
check_srgb_to_linear_to_srgb_conversion, context, reporter);
// Reading untagged data from linear currently does no conversion.
test_write_read(Encoding::kLinear, Encoding::kSRGB, Encoding::kUntagged, error,
check_srgb_to_linear_conversion, context, reporter);
// Stays linear when read.
test_write_read(Encoding::kLinear, Encoding::kSRGB, Encoding::kLinear, error,
check_srgb_to_linear_conversion, context, reporter);
// Untagged source data should be interpreted as sRGB.
test_write_read(Encoding::kLinear, Encoding::kUntagged, Encoding::kSRGB, error,
check_srgb_to_linear_to_srgb_conversion, context, reporter);
///////////////////////////////////////////////////////////////////////////////////////////////
// Write linear data to a linear context. Does no conversion.
// Reading to sRGB does a conversion.
test_write_read(Encoding::kLinear, Encoding::kLinear, Encoding::kSRGB, error,
check_linear_to_srgb_conversion, context, reporter);
// Reading to untagged does no conversion.
test_write_read(Encoding::kLinear, Encoding::kLinear, Encoding::kUntagged, error,
check_no_conversion, context, reporter);
// Stays linear when read.
test_write_read(Encoding::kLinear, Encoding::kLinear, Encoding::kLinear, error,
check_no_conversion, context, reporter);
}