Improve modulation detection logic in ProcessorOptimizationValidationTest

Bug: skia:
Change-Id: I6896df2f46d530afbda752894a3fc414ade661df
Reviewed-on: https://skia-review.googlesource.com/c/159521
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
Michael Ludwig 2018-10-08 16:43:58 -04:00 committed by Skia Commit-Bot
parent 733b265854
commit 7034f15b28

View File

@ -227,13 +227,20 @@ DEFINE_uint32(processorSeed, 0, "Use specific seed for processor tests. Overridd
#if GR_TEST_UTILS
static GrColor input_texel_color(int i, int j) {
GrColor color = GrColorPackRGBA((uint8_t)j, (uint8_t)(i + j), (uint8_t)(2 * j - i), (uint8_t)i);
return GrPremulColor(color);
}
static GrColor input_texel_color(int i, int j, SkScalar delta) {
// Delta must be less than 0.5 to prevent over/underflow issues with the input color
SkASSERT(delta <= 0.5);
static SkPMColor4f input_texel_color4f(int i, int j) {
return GrColor4f::FromGrColor(input_texel_color(i, j)).asRGBA4f<kPremul_SkAlphaType>();
GrColor color = GrColorPackRGBA((uint8_t)j, (uint8_t)(i + j), (uint8_t)(2 * j - i), (uint8_t)i);
GrColor4f color4f = GrColor4f::FromGrColor(color);
for (int i = 0; i < 4; i++) {
if (color4f.fRGBA[i] > 0.5) {
color4f.fRGBA[i] -= delta;
} else {
color4f.fRGBA[i] += delta;
}
}
return color4f.premul().toGrColor();
}
void test_draw_op(GrContext* context,
@ -251,6 +258,20 @@ void test_draw_op(GrContext* context,
rtc->addDrawOp(GrNoClip(), std::move(op));
}
// This assumes that the output buffer will be the same size as inputDataProxy
void render_fp(GrContext* context, GrRenderTargetContext* rtc, GrFragmentProcessor* fp,
sk_sp<GrTextureProxy> inputDataProxy, GrColor* buffer) {
int width = inputDataProxy->width();
int height = inputDataProxy->height();
// test_draw_op needs to take ownership of an FP, so give it a clone that it can own
test_draw_op(context, rtc, fp->clone(), inputDataProxy);
memset(buffer, 0x0, sizeof(GrColor) * width * height);
rtc->readPixels(SkImageInfo::Make(width, height, kRGBA_8888_SkColorType,
kPremul_SkAlphaType),
buffer, 0, 0, 0);
}
/** Initializes the two test texture proxies that are available to the FP test factories. */
bool init_test_textures(GrProxyProvider* proxyProvider, SkRandom* random,
sk_sp<GrTextureProxy> proxies[2]) {
@ -261,8 +282,8 @@ bool init_test_textures(GrProxyProvider* proxyProvider, SkRandom* random,
std::unique_ptr<GrColor[]> rgbaData(new GrColor[kTestTextureSize * kTestTextureSize]);
for (int y = 0; y < kTestTextureSize; ++y) {
for (int x = 0; x < kTestTextureSize; ++x) {
rgbaData[kTestTextureSize * y + x] =
input_texel_color(random->nextULessThan(256), random->nextULessThan(256));
rgbaData[kTestTextureSize * y + x] = input_texel_color(
random->nextULessThan(256), random->nextULessThan(256), 0.0f);
}
}
@ -296,11 +317,12 @@ bool init_test_textures(GrProxyProvider* proxyProvider, SkRandom* random,
// Creates a texture of premul colors used as the output of the fragment processor that precedes
// the fragment processor under test. Color values are those provided by input_texel_color().
sk_sp<GrTextureProxy> make_input_texture(GrProxyProvider* proxyProvider, int width, int height) {
sk_sp<GrTextureProxy> make_input_texture(GrProxyProvider* proxyProvider, int width, int height,
SkScalar delta) {
std::unique_ptr<GrColor[]> data(new GrColor[width * height]);
for (int y = 0; y < width; ++y) {
for (int x = 0; x < height; ++x) {
data.get()[width * y + x] = input_texel_color(x, y);
data.get()[width * y + x] = input_texel_color(x, y, delta);
}
}
@ -326,6 +348,75 @@ bool log_surface_proxy(GrContext* context, sk_sp<GrSurfaceProxy> src, SkString*
return log_surface_context(sContext, dst);
}
bool fuzzy_color_equals(const SkPMColor4f& c1, const SkPMColor4f& c2) {
// With the loss of precision of rendering into 32-bit color, then estimating the FP's output
// from that, it is not uncommon for a valid output to differ from estimate by up to 0.01
// (really 1/128 ~ .0078, but frequently floating point issues make that tolerance a little
// too unforgiving).
static constexpr SkScalar kTolerance = 0.01f;
for (int i = 0; i < 4; i++) {
if (!SkScalarNearlyEqual(c1[i], c2[i], kTolerance)) {
return false;
}
}
return true;
}
int modulation_index(int channelIndex, bool alphaModulation) {
return alphaModulation ? 3 : channelIndex;
}
// Given three input colors (color preceding the FP being tested), and the output of the FP, this
// ensures that the out1 = fp * in1.a, out2 = fp * in2.a, and out3 = fp * in3.a, where fp is the
// pre-modulated color that should not be changing across frames (FP's state doesn't change).
//
// When alphaModulation is false, this tests the very similar conditions that out1 = fp * in1,
// etc. using per-channel modulation instead of modulation by just the input alpha channel.
// - This estimates the pre-modulated fp color from one of the input/output pairs and confirms the
// conditions hold for the other two pairs.
bool legal_modulation(const GrColor& in1, const GrColor& in2, const GrColor& in3,
const GrColor& out1, const GrColor& out2, const GrColor& out3,
bool alphaModulation) {
// Convert to floating point, which is the number space the FP operates in (more or less)
SkPMColor4f in1f = GrColor4f::FromGrColor(in1).asRGBA4f<kPremul_SkAlphaType>();
SkPMColor4f in2f = GrColor4f::FromGrColor(in2).asRGBA4f<kPremul_SkAlphaType>();
SkPMColor4f in3f = GrColor4f::FromGrColor(in3).asRGBA4f<kPremul_SkAlphaType>();
SkPMColor4f out1f = GrColor4f::FromGrColor(out1).asRGBA4f<kPremul_SkAlphaType>();
SkPMColor4f out2f = GrColor4f::FromGrColor(out2).asRGBA4f<kPremul_SkAlphaType>();
SkPMColor4f out3f = GrColor4f::FromGrColor(out3).asRGBA4f<kPremul_SkAlphaType>();
// Reconstruct the output of the FP before the shader modulated its color with the input value.
// When the original input is very small, it may cause the final output color to round
// to 0, in which case we estimate the pre-modulated color using one of the stepped frames that
// will then have a guaranteed larger channel value (since the offset will be added to it).
SkPMColor4f fpPreModulation;
for (int i = 0; i < 4; i++) {
int modulationIndex = modulation_index(i, alphaModulation);
if (in1f[modulationIndex] < 0.2f) {
// Use the stepped frame
fpPreModulation[i] = out2f[i] / in2f[modulationIndex];
} else {
fpPreModulation[i] = out1f[i] / in1f[modulationIndex];
}
}
// With reconstructed pre-modulated FP output, derive the expected value of fp * input for each
// of the transformed input colors.
int mR = modulation_index(0, alphaModulation);
int mG = modulation_index(1, alphaModulation);
int mB = modulation_index(2, alphaModulation);
GrColor4f expected1 = GrColor4f(fpPreModulation.fR * in1f[mR], fpPreModulation.fG * in1f[mG],
fpPreModulation.fB * in1f[mB], fpPreModulation.fA * in1f.fA);
GrColor4f expected2 = GrColor4f(fpPreModulation.fR * in2f[mR], fpPreModulation.fG * in2f[mG],
fpPreModulation.fB * in2f[mB], fpPreModulation.fA * in2f.fA);
GrColor4f expected3 = GrColor4f(fpPreModulation.fR * in3f[mR], fpPreModulation.fG * in3f[mG],
fpPreModulation.fB * in3f[mB], fpPreModulation.fA * in3f.fA);
return fuzzy_color_equals(out1f, expected1.asRGBA4f<kPremul_SkAlphaType>()) &&
fuzzy_color_equals(out2f, expected2.asRGBA4f<kPremul_SkAlphaType>()) &&
fuzzy_color_equals(out3f, expected3.asRGBA4f<kPremul_SkAlphaType>());
}
DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, reporter, ctxInfo) {
GrContext* context = ctxInfo.grContext();
GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
@ -353,13 +444,26 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
}
GrProcessorTestData testData(&random, context, rtc.get(), proxies);
auto inputTexture = make_input_texture(proxyProvider, kRenderSize, kRenderSize);
// Coverage optimization uses three frames with a linearly transformed input texture. The first
// frame has no offset, second frames add .2 and .4, which should then be present as a fixed
// difference between the frame outputs if the FP is properly following the modulation
// requirements of the coverage optimization.
static constexpr SkScalar kInputDelta = 0.2f;
auto inputTexture1 = make_input_texture(proxyProvider, kRenderSize, kRenderSize, 0.0f);
auto inputTexture2 = make_input_texture(proxyProvider, kRenderSize, kRenderSize, kInputDelta);
auto inputTexture3 = make_input_texture(proxyProvider, kRenderSize, kRenderSize, 2*kInputDelta);
std::unique_ptr<GrColor[]> readData(new GrColor[kRenderSize * kRenderSize]);
// Encoded images are very verbose and this tests many potential images, so only export the
// first failure (subsequent failures have a reasonable chance of being related).
bool loggedFirstFailure = false;
bool loggedFirstWarning = false;
// Storage for the three frames required for coverage compatibility optimization. Each frame
// uses the correspondingly numbered inputTextureX.
std::unique_ptr<GrColor[]> readData1(new GrColor[kRenderSize * kRenderSize]);
std::unique_ptr<GrColor[]> readData2(new GrColor[kRenderSize * kRenderSize]);
std::unique_ptr<GrColor[]> readData3(new GrColor[kRenderSize * kRenderSize]);
// Because processor factories configure themselves in random ways, this is not exhaustive.
for (int i = 0; i < FPFactory::Count(); ++i) {
int timesToInvokeFactory = 5;
@ -382,24 +486,25 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
continue;
}
// Since we transfer away ownership of the original FP, we make a clone.
auto clone = fp->clone();
if (fp->compatibleWithCoverageAsAlpha()) {
// 2nd and 3rd frames are only used when checking coverage optimization
render_fp(context, rtc.get(), fp.get(), inputTexture2, readData2.get());
render_fp(context, rtc.get(), fp.get(), inputTexture3, readData3.get());
}
// Draw base frame last so that rtc holds the original FP behavior if we need to
// dump the image to the log.
render_fp(context, rtc.get(), fp.get(), inputTexture1, readData1.get());
test_draw_op(context, rtc.get(), std::move(fp), inputTexture);
memset(readData.get(), 0x0, sizeof(GrColor) * kRenderSize * kRenderSize);
rtc->readPixels(SkImageInfo::Make(kRenderSize, kRenderSize, kRGBA_8888_SkColorType,
kPremul_SkAlphaType),
readData.get(), 0, 0, 0);
if (0) { // Useful to see what FPs are being tested.
SkString children;
for (int c = 0; c < clone->numChildProcessors(); ++c) {
for (int c = 0; c < fp->numChildProcessors(); ++c) {
if (!c) {
children.append("(");
}
children.append(clone->name());
children.append(c == clone->numChildProcessors() - 1 ? ")" : ", ");
children.append(fp->childProcessor(c).name());
children.append(c == fp->numChildProcessors() - 1 ? ")" : ", ");
}
SkDebugf("%s %s\n", clone->name(), children.c_str());
SkDebugf("%s %s\n", fp->name(), children.c_str());
}
// This test has a history of being flaky on a number of devices. If an FP is logically
@ -421,35 +526,40 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
for (int y = 0; y < kRenderSize; ++y) {
for (int x = 0; x < kRenderSize; ++x) {
bool passing = true;
GrColor input = input_texel_color(x, y);
GrColor output = readData.get()[y * kRenderSize + x];
if (clone->compatibleWithCoverageAsAlpha()) {
// A modulating processor is allowed to modulate either the input color or
GrColor input = input_texel_color(x, y, 0.0f);
GrColor output = readData1.get()[y * kRenderSize + x];
if (fp->compatibleWithCoverageAsAlpha()) {
GrColor i2 = input_texel_color(x, y, kInputDelta);
GrColor i3 = input_texel_color(x, y, 2 * kInputDelta);
GrColor o2 = readData2.get()[y * kRenderSize + x];
GrColor o3 = readData3.get()[y * kRenderSize + x];
// A compatible processor is allowed to modulate either the input color or
// just the input alpha.
bool legalColorModulation =
GrColorUnpackA(output) <= GrColorUnpackA(input) &&
GrColorUnpackR(output) <= GrColorUnpackR(input) &&
GrColorUnpackG(output) <= GrColorUnpackG(input) &&
GrColorUnpackB(output) <= GrColorUnpackB(input);
bool legalAlphaModulation =
GrColorUnpackA(output) <= GrColorUnpackA(input) &&
GrColorUnpackR(output) <= GrColorUnpackA(input) &&
GrColorUnpackG(output) <= GrColorUnpackA(input) &&
GrColorUnpackB(output) <= GrColorUnpackA(input);
bool legalAlphaModulation = legal_modulation(input, i2, i3, output, o2, o3,
/* alpha */ true);
bool legalColorModulation = legal_modulation(input, i2, i3, output, o2, o3,
/* alpha */ false);
if (!legalColorModulation && !legalAlphaModulation) {
passing = false;
if (coverageMessage.isEmpty()) {
coverageMessage.printf("\"Modulating\" processor %s made color/"
"alpha value larger. Input: 0x%08x, Output: 0x%08x, pixel "
"(%d, %d).", clone->name(), input, output, x, y);
coverageMessage.printf("\"Modulating\" processor %s did not match "
"alpha-modulation nor color-modulation rules. "
"Input: 0x%08x, Output: 0x%08x, pixel (%d, %d).",
fp->name(), input, output, x, y);
}
}
}
SkPMColor4f input4f = input_texel_color4f(x, y);
SkPMColor4f input4f =
GrColor4f::FromGrColor(input).asRGBA4f<kPremul_SkAlphaType>();
GrColor4f output4f = GrColor4f::FromGrColor(output);
SkPMColor4f expected4f;
if (clone->hasConstantOutputForConstantInput(input4f, &expected4f)) {
if (fp->hasConstantOutputForConstantInput(input4f, &expected4f)) {
float rDiff = fabsf(output4f.fRGBA[0] - expected4f.fR);
float gDiff = fabsf(output4f.fRGBA[1] - expected4f.fG);
float bDiff = fabsf(output4f.fRGBA[2] - expected4f.fB);
@ -462,7 +572,7 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
constMessage.printf("Processor %s claimed output for const input "
"doesn't match actual output. Error: %f, Tolerance: %f, "
"input: (%f, %f, %f, %f), actual: (%f, %f, %f, %f), "
"expected(%f, %f, %f, %f)", clone->name(),
"expected(%f, %f, %f, %f)", fp->name(),
SkTMax(rDiff, SkTMax(gDiff, SkTMax(bDiff, aDiff))), kTol,
input4f.fR, input4f.fG, input4f.fB, input4f.fA,
output4f.fRGBA[0], output4f.fRGBA[1], output4f.fRGBA[2],
@ -471,14 +581,14 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
}
}
}
if (GrColorIsOpaque(input) && clone->preservesOpaqueInput() &&
if (GrColorIsOpaque(input) && fp->preservesOpaqueInput() &&
!GrColorIsOpaque(output)) {
passing = false;
if (opaqueMessage.isEmpty()) {
opaqueMessage.printf("Processor %s claimed opaqueness is preserved but "
"it is not. Input: 0x%08x, Output: 0x%08x.",
clone->name(), input, output);
fp->name(), input, output);
}
}
@ -496,7 +606,7 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
ERRORF(reporter, "Processor violated %d of %d pixels, seed: 0x%08x, processor: %s"
", first failing pixel details are below:",
failedPixelCount, kRenderSize * kRenderSize, seed,
clone->dumpInfo().c_str());
fp->dumpInfo().c_str());
// Print first failing pixel's details.
if (!coverageMessage.isEmpty()) {
@ -512,7 +622,7 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
if (!loggedFirstFailure) {
// Print with ERRORF to make sure the encoded image is output
SkString input;
log_surface_proxy(context, inputTexture, &input);
log_surface_proxy(context, inputTexture1, &input);
SkString output;
log_surface_context(rtc, &output);
ERRORF(reporter, "Input image: %s\n\n"
@ -520,11 +630,11 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
"Output image: %s\n", input.c_str(), output.c_str());
loggedFirstFailure = true;
}
} else {
} else if(failedPixelCount > 0) {
// Don't trigger an error, but don't just hide the failures either.
INFOF(reporter, "Processor violated %d of %d pixels (below error threshold), seed: "
"0x%08x, processor: %s", failedPixelCount, kRenderSize * kRenderSize,
seed, clone->dumpInfo().c_str());
seed, fp->dumpInfo().c_str());
if (!coverageMessage.isEmpty()) {
INFOF(reporter, coverageMessage.c_str());
}
@ -536,7 +646,7 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
}
if (!loggedFirstWarning) {
SkString input;
log_surface_proxy(context, inputTexture, &input);
log_surface_proxy(context, inputTexture1, &input);
SkString output;
log_surface_context(rtc, &output);
INFOF(reporter, "Input image: %s\n\n"
@ -570,7 +680,7 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorCloneTest, reporter, ctxInfo) {
}
GrProcessorTestData testData(&random, context, rtc.get(), proxies);
auto inputTexture = make_input_texture(proxyProvider, kRenderSize, kRenderSize);
auto inputTexture = make_input_texture(proxyProvider, kRenderSize, kRenderSize, 0.0f);
std::unique_ptr<GrColor[]> readData1(new GrColor[kRenderSize * kRenderSize]);
std::unique_ptr<GrColor[]> readData2(new GrColor[kRenderSize * kRenderSize]);
auto readInfo = SkImageInfo::Make(kRenderSize, kRenderSize, kRGBA_8888_SkColorType,
@ -600,14 +710,10 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorCloneTest, reporter, ctxInfo) {
REPORTER_ASSERT(reporter, fp->numChildProcessors() == clone->numChildProcessors());
REPORTER_ASSERT(reporter, fp->usesLocalCoords() == clone->usesLocalCoords());
// Draw with original and read back the results.
test_draw_op(context, rtc.get(), std::move(fp), inputTexture);
memset(readData1.get(), 0x0, sizeof(GrColor) * kRenderSize * kRenderSize);
rtc->readPixels(readInfo, readData1.get(), 0, 0, 0);
render_fp(context, rtc.get(), fp.get(), inputTexture, readData1.get());
// Draw with clone and read back the results.
test_draw_op(context, rtc.get(), std::move(clone), inputTexture);
memset(readData2.get(), 0x0, sizeof(GrColor) * kRenderSize * kRenderSize);
rtc->readPixels(readInfo, readData2.get(), 0, 0, 0);
render_fp(context, rtc.get(), clone.get(), inputTexture, readData2.get());
// Check that the results are the same.
bool passing = true;
@ -619,7 +725,7 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorCloneTest, reporter, ctxInfo) {
"Processor %s made clone produced different output. "
"Input color: 0x%08x, Original Output Color: 0x%08x, "
"Clone Output Color: 0x%08x..",
name, input_texel_color(x, y), readData1[idx], readData2[idx]);
name, input_texel_color(x, y, 0.0f), readData1[idx], readData2[idx]);
passing = false;
}
}