skia2/tests/SRGBReadWritePixelsTest.cpp
Brian Osman c7ad40f76f Remove SK_SUPPORT_GPU checks in tool-only code
Most of this is (obviously) not necessary to do, but once
I started, I figured I'd just get it all. Tools (nanobench,
DM, skiaserve), all GMs, benches, and unit tests, plus support
code (command line parsing and config stuff).

This is almost entirely mechanical.

Bug: skia:
Change-Id: I209500f8df8c5bd43f8298ff26440d1c4d7425fb
Reviewed-on: https://skia-review.googlesource.com/131153
Reviewed-by: Mike Klein <mtklein@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2018-05-31 18:59:44 +00:00

388 lines
17 KiB
C++

/*
* 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 "Test.h"
#include "GrCaps.h"
#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrSurfaceContext.h"
#include "SkCanvas.h"
#include "SkGr.h"
#include "SkSurface.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 = SkTMax(0.f, (float) inputComponent - error);
float upper = SkTMin(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 = SkTMax(0.f, (float) inputComponent - error);
float upper = SkTMin(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 = SkTMax(0.f, (float) lowerComponent - error);
upper = SkTMin(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 GrPixelConfig encoding_as_pixel_config(Encoding encoding) {
switch (encoding) {
case Encoding::kUntagged: return kRGBA_8888_GrPixelConfig;
case Encoding::kLinear: return kRGBA_8888_GrPixelConfig;
case Encoding::kSRGB: return kSRGBA_8888_GrPixelConfig;
}
return kUnknown_GrPixelConfig;
}
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] = (j << 24) | (i << 16) | (i << 8) | i;
}
}
return data;
}
static sk_sp<GrSurfaceContext> make_surface_context(Encoding contextEncoding, GrContext* context,
skiatest::Reporter* reporter) {
GrSurfaceDesc desc;
desc.fFlags = kRenderTarget_GrSurfaceFlag;
desc.fWidth = kW;
desc.fHeight = kH;
desc.fConfig = encoding_as_pixel_config(contextEncoding);
auto surfaceContext = context->contextPriv().makeDeferredSurfaceContext(
desc, kBottomLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact,
SkBudgeted::kNo, encoding_as_color_space(contextEncoding));
if (!surfaceContext) {
ERRORF(reporter, "Could not create %s surface context.", encoding_as_str(contextEncoding));
}
return surfaceContext;
}
#ifndef SK_LEGACY_GPU_PIXEL_OPS
static void text_write_fails(Encoding contextEncoding, Encoding writeEncoding, 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, "Expected %s write to %s surface context to fail.",
encoding_as_str(writeEncoding), encoding_as_str(contextEncoding));
}
}
#endif
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->contextPriv().caps()->isConfigRenderable(kSRGBA_8888_GrPixelConfig) &&
!context->contextPriv().caps()->isConfigTexturable(kSRGBA_8888_GrPixelConfig)) {
return;
}
// We allow more error on GPUs with lower precision shader variables.
float error = context->contextPriv().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->contextPriv().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);
#ifdef SK_LEGACY_GPU_PIXEL_OPS
// Untagged read from sRGB is treated as a conversion back to linear. TODO: Fail or don't
// convert?
test_write_read(Encoding::kSRGB, Encoding::kSRGB, Encoding::kUntagged, error,
check_srgb_to_linear_conversion, context, reporter);
#else
// 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);
#endif
// Converts back to linear
test_write_read(Encoding::kSRGB, Encoding::kSRGB, Encoding::kLinear, error,
check_srgb_to_linear_conversion, context, reporter);
#ifdef SK_LEGACY_GPU_PIXEL_OPS
///////////////////////////////////////////////////////////////////////////////////////////////
// Write untagged data to a sRGB context - Currently this treats the untagged data as
// linear and converts to sRGB during the write. TODO: Fail or passthrough?
// read back to srgb, no additional conversion
test_write_read(Encoding::kSRGB, Encoding::kUntagged, Encoding::kSRGB, error,
check_linear_to_srgb_conversion, context, reporter);
// read back to untagged. Currently converts back to linear. TODO: Fail or don't convert?
test_write_read(Encoding::kSRGB, Encoding::kUntagged, Encoding::kUntagged, error,
check_linear_to_srgb_to_linear_conversion, context, reporter);
// Converts back to linear.
test_write_read(Encoding::kSRGB, Encoding::kUntagged, Encoding::kLinear, error,
check_linear_to_srgb_to_linear_conversion, context, reporter);
#else
// Currently writing untagged data to kSRGB fails because SkImageInfoValidConversion fails.
text_write_fails(Encoding::kSRGB, Encoding::kUntagged, context, reporter);
#endif
///////////////////////////////////////////////////////////////////////////////////////////////
// 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);
#ifdef SK_LEGACY_GPU_PIXEL_OPS
// TODO: Fail or don't convert?
test_write_read(Encoding::kSRGB, Encoding::kLinear, Encoding::kUntagged, error,
check_linear_to_srgb_to_linear_conversion, context, reporter);
#else
// 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);
#endif
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 no conversion.
test_write_read(Encoding::kUntagged, writeEncoding, Encoding::kLinear, error,
check_no_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);
#ifdef SK_LEGACY_GPU_PIXEL_OPS
///////////////////////////////////////////////////////////////////////////////////////////////
// Write untagged data to a linear context. Currently does no conversion. TODO: Should this
// fail?
// Reading to sRGB does a conversion.
test_write_read(Encoding::kLinear, Encoding::kUntagged, Encoding::kSRGB, error,
check_linear_to_srgb_conversion, context, reporter);
// Reading to untagged does no conversion. TODO: Should it fail?
test_write_read(Encoding::kLinear, Encoding::kUntagged, Encoding::kUntagged, error,
check_no_conversion, context, reporter);
// Stays linear when read.
test_write_read(Encoding::kLinear, Encoding::kUntagged, Encoding::kLinear, error,
check_no_conversion, context, reporter);
#else
// Currently writing untagged data to kLinear fails because SkImageInfoValidConversion fails.
text_write_fails(Encoding::kSRGB, Encoding::kUntagged, context, reporter);
#endif
///////////////////////////////////////////////////////////////////////////////////////////////
// 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);
}