Make ProcessorOptimizationTest for canTweakAlphaForCoverage more robust

Change-Id: Ic43bebee6a0036eff5b718cc505d85bf839cb8a3
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/260898
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/tests/ProcessorTest.cpp b/tests/ProcessorTest.cpp
index 0dd5c8f..aa3d1cf 100644
--- a/tests/ProcessorTest.cpp
+++ b/tests/ProcessorTest.cpp
@@ -217,7 +217,10 @@
                                    (uint8_t)((i + j) & 0xFF),
                                    (uint8_t)((2 * j - i) & 0xFF));
     SkColor4f color4f = SkColor4f::FromColor(color);
-    for (int i = 0; i < 4; i++) {
+    // We only apply delta to the r,g, and b channels. This is because we're using this
+    // to test the canTweakAlphaForCoverage() optimization. A processor is allowed
+    // to use the input color's alpha in its calculation and report this optimization.
+    for (int i = 0; i < 3; i++) {
         if (color4f[i] > 0.5) {
             color4f[i] -= delta;
         } else {
@@ -361,56 +364,101 @@
     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) {
+// Given three input colors (color preceding the FP being tested) provided to the FP at the same
+// local coord and the three corresponding FP outputs, this ensures that either:
+//   out[0] = fp * in[0].a, out[1] = fp * in[1].a, and out[2] = fp * in[2].a
+// where fp is the pre-modulated color that should not be changing across frames (FP's state doesn't
+// change), OR:
+//   out[0] = fp * in[0], out[1] = fp * in[1], and out[2] = fp * in[2]
+// (per-channel modulation instead of modulation by just the alpha channel)
+// It does this by estimating the pre-modulated fp color from one of the input/output pairs and
+// confirms the conditions hold for the other two pairs.
+// It is required that the three input colors have the same alpha as fp is allowed to be a function
+// of the input alpha (but not r, g, or b).
+bool legal_modulation(const GrColor in[3], const GrColor out[3]) {
     // Convert to floating point, which is the number space the FP operates in (more or less)
-    SkPMColor4f in1f = SkPMColor4f::FromBytes_RGBA(in1);
-    SkPMColor4f in2f = SkPMColor4f::FromBytes_RGBA(in2);
-    SkPMColor4f in3f = SkPMColor4f::FromBytes_RGBA(in3);
-    SkPMColor4f out1f = SkPMColor4f::FromBytes_RGBA(out1);
-    SkPMColor4f out2f = SkPMColor4f::FromBytes_RGBA(out2);
-    SkPMColor4f out3f = SkPMColor4f::FromBytes_RGBA(out3);
+    SkPMColor4f inf[3], outf[3];
+    for (int i = 0; i < 3; ++i) {
+        inf[i]  = SkPMColor4f::FromBytes_RGBA(in[i]);
+        outf[i] = SkPMColor4f::FromBytes_RGBA(out[i]);
+    }
+    // This test is only valid if all the input alphas are the same.
+    SkASSERT(inf[0].fA == inf[1].fA && inf[1].fA == inf[2].fA);
 
     // 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;
+    SkPMColor4f fpPreColorModulation;
+    SkPMColor4f fpPreAlphaModulation;
     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];
-        }
+        // Use the most stepped up frame
+        int maxInIdx = inf[0][i] > inf[1][i] ? 0 : 1;
+        maxInIdx = inf[maxInIdx][i] > inf[2][i] ? maxInIdx : 2;
+        const auto& in = inf[maxInIdx];
+        const auto& out = outf[maxInIdx];
+        fpPreColorModulation[i] = out[i] / in[i];
+        fpPreAlphaModulation[i] = out[i] / in[3];
     }
 
     // With reconstructed pre-modulated FP output, derive the expected value of fp * input for each
     // of the transformed input colors.
-    SkPMColor4f expected1 = alphaModulation ? (fpPreModulation * in1f.fA)
-                                            : (fpPreModulation * in1f);
-    SkPMColor4f expected2 = alphaModulation ? (fpPreModulation * in2f.fA)
-                                            : (fpPreModulation * in2f);
-    SkPMColor4f expected3 = alphaModulation ? (fpPreModulation * in3f.fA)
-                                            : (fpPreModulation * in3f);
+    SkPMColor4f expectedForAlphaModulation[3];
+    SkPMColor4f expectedForColorModulation[3];
+    for (int i = 0; i < 3; ++i) {
+        expectedForAlphaModulation[i] = fpPreAlphaModulation * inf[i].fA;
+        expectedForColorModulation[i] = fpPreColorModulation * inf[i];
+        // If the input alpha is 0 then the other channels should also be zero
+        // since the color is assumed to be premul. Modulating zeros by anything
+        // should produce zeros.
+        if (inf[i].fA == 0) {
+            SkASSERT(inf[i].fR == 0 && inf[i].fG == 0 && inf[i].fB == 0);
+            expectedForColorModulation[i] = expectedForAlphaModulation[i] = {0, 0, 0, 0};
+        }
+    }
 
-    return fuzzy_color_equals(out1f, expected1) &&
-           fuzzy_color_equals(out2f, expected2) &&
-           fuzzy_color_equals(out3f, expected3);
+    bool isLegalColorModulation = fuzzy_color_equals(outf[0], expectedForColorModulation[0]) &&
+                                  fuzzy_color_equals(outf[1], expectedForColorModulation[1]) &&
+                                  fuzzy_color_equals(outf[2], expectedForColorModulation[2]);
+
+    bool isLegalAlphaModulation = fuzzy_color_equals(outf[0], expectedForAlphaModulation[0]) &&
+                                  fuzzy_color_equals(outf[1], expectedForAlphaModulation[1]) &&
+                                  fuzzy_color_equals(outf[2], expectedForAlphaModulation[2]);
+
+    // This can be enabled to print the values that caused this check to fail.
+    if (0 && !isLegalColorModulation && !isLegalAlphaModulation) {
+        SkDebugf("Color modulation test\n\timplied mod color: (%.03f, %.03f, %.03f, %.03f)\n",
+                 fpPreColorModulation[0],
+                 fpPreColorModulation[1],
+                 fpPreColorModulation[2],
+                 fpPreColorModulation[3]);
+        for (int i = 0; i < 3; ++i) {
+            SkDebugf("\t(%.03f, %.03f, %.03f, %.03f) -> "
+                     "(%.03f, %.03f, %.03f, %.03f) | "
+                     "(%.03f, %.03f, %.03f, %.03f), ok: %d\n",
+                     inf[i].fR, inf[i].fG, inf[i].fB, inf[i].fA,
+                     outf[i].fR, outf[i].fG, outf[i].fB, outf[i].fA,
+                     expectedForColorModulation[i].fR, expectedForColorModulation[i].fG,
+                     expectedForColorModulation[i].fB, expectedForColorModulation[i].fA,
+                     fuzzy_color_equals(outf[i], expectedForColorModulation[i]));
+        }
+        SkDebugf("Alpha modulation test\n\timplied mod color: (%.03f, %.03f, %.03f, %.03f)\n",
+                 fpPreAlphaModulation[0],
+                 fpPreAlphaModulation[1],
+                 fpPreAlphaModulation[2],
+                 fpPreAlphaModulation[3]);
+        for (int i = 0; i < 3; ++i) {
+            SkDebugf("\t(%.03f, %.03f, %.03f, %.03f) -> "
+                     "(%.03f, %.03f, %.03f, %.03f) | "
+                     "(%.03f, %.03f, %.03f, %.03f), ok: %d\n",
+                     inf[i].fR, inf[i].fG, inf[i].fB, inf[i].fA,
+                     outf[i].fR, outf[i].fG, outf[i].fB, outf[i].fA,
+                     expectedForAlphaModulation[i].fR, expectedForAlphaModulation[i].fG,
+                     expectedForAlphaModulation[i].fB, expectedForAlphaModulation[i].fA,
+                     fuzzy_color_equals(outf[i], expectedForAlphaModulation[i]));
+        }
+    }
+    return isLegalColorModulation || isLegalAlphaModulation;
 }
 
 DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, reporter, ctxInfo) {
@@ -530,24 +578,21 @@
                     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 ins[3];
+                        ins[0] = input;
+                        ins[1] = input_texel_color(x, y, kInputDelta);
+                        ins[2] = input_texel_color(x, y, 2 * kInputDelta);
 
-                        GrColor o2 = readData2.get()[y * kRenderSize + x];
-                        GrColor o3 = readData3.get()[y * kRenderSize + x];
+                        GrColor outs[3];
+                        outs[0] = output;
+                        outs[1] = readData2.get()[y * kRenderSize + x];
+                        outs[2] = readData3.get()[y * kRenderSize + x];
 
-                        // A compatible processor is allowed to modulate either the input color or
-                        // just the input alpha.
-                        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) {
+                        if (!legal_modulation(ins, outs)) {
                             passing = false;
-
                             if (coverageMessage.isEmpty()) {
-                                coverageMessage.printf("\"Modulating\" processor %s did not match "
+                                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);