Add a gradient edge optimization test

R=reed@google.com
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2267593002

Review-Url: https://codereview.chromium.org/2267593002
diff --git a/tests/GradientTest.cpp b/tests/GradientTest.cpp
index 3ed2f51..1e8f393 100644
--- a/tests/GradientTest.cpp
+++ b/tests/GradientTest.cpp
@@ -67,12 +67,12 @@
 };
 
 
-static void none_gradproc(skiatest::Reporter* reporter, const GradRec&) {
+static void none_gradproc(skiatest::Reporter* reporter, const GradRec&, const GradRec&) {
     sk_sp<SkShader> s(SkShader::MakeEmptyShader());
     REPORTER_ASSERT(reporter, SkShader::kNone_GradientType == s->asAGradient(nullptr));
 }
 
-static void color_gradproc(skiatest::Reporter* reporter, const GradRec& rec) {
+static void color_gradproc(skiatest::Reporter* reporter, const GradRec& rec, const GradRec&) {
     SkAutoTUnref<SkShader> s(new SkColorShader(rec.fColors[0]));
     REPORTER_ASSERT(reporter, SkShader::kColor_GradientType == s->asAGradient(nullptr));
 
@@ -83,48 +83,54 @@
     REPORTER_ASSERT(reporter, 1 == info.fColorCount);
 }
 
-static void linear_gradproc(skiatest::Reporter* reporter, const GradRec& rec) {
-    sk_sp<SkShader> s(SkGradientShader::MakeLinear(rec.fPoint, rec.fColors, rec.fPos,
-                                                   rec.fColorCount, rec.fTileMode));
+static void linear_gradproc(skiatest::Reporter* reporter, const GradRec& buildRec,
+                            const GradRec& checkRec) {
+    sk_sp<SkShader> s(SkGradientShader::MakeLinear(buildRec.fPoint, buildRec.fColors, buildRec.fPos,
+                                                   buildRec.fColorCount, buildRec.fTileMode));
 
     SkShader::GradientInfo info;
-    rec.gradCheck(reporter, s, &info, SkShader::kLinear_GradientType);
-    REPORTER_ASSERT(reporter, !memcmp(info.fPoint, rec.fPoint, 2 * sizeof(SkPoint)));
+    checkRec.gradCheck(reporter, s, &info, SkShader::kLinear_GradientType);
+    REPORTER_ASSERT(reporter, !memcmp(info.fPoint, checkRec.fPoint, 2 * sizeof(SkPoint)));
 }
 
-static void radial_gradproc(skiatest::Reporter* reporter, const GradRec& rec) {
-    sk_sp<SkShader> s(SkGradientShader::MakeRadial(rec.fPoint[0], rec.fRadius[0], rec.fColors,
-                                                   rec.fPos, rec.fColorCount, rec.fTileMode));
+static void radial_gradproc(skiatest::Reporter* reporter, const GradRec& buildRec,
+                            const GradRec& checkRec) {
+    sk_sp<SkShader> s(SkGradientShader::MakeRadial(buildRec.fPoint[0], buildRec.fRadius[0],
+                                                   buildRec.fColors, buildRec.fPos,
+                                                   buildRec.fColorCount, buildRec.fTileMode));
 
     SkShader::GradientInfo info;
-    rec.gradCheck(reporter, s, &info, SkShader::kRadial_GradientType);
-    REPORTER_ASSERT(reporter, info.fPoint[0] == rec.fPoint[0]);
-    REPORTER_ASSERT(reporter, info.fRadius[0] == rec.fRadius[0]);
+    checkRec.gradCheck(reporter, s, &info, SkShader::kRadial_GradientType);
+    REPORTER_ASSERT(reporter, info.fPoint[0] == checkRec.fPoint[0]);
+    REPORTER_ASSERT(reporter, info.fRadius[0] == checkRec.fRadius[0]);
 }
 
-static void sweep_gradproc(skiatest::Reporter* reporter, const GradRec& rec) {
-    sk_sp<SkShader> s(SkGradientShader::MakeSweep(rec.fPoint[0].fX, rec.fPoint[0].fY, rec.fColors,
-                                                  rec.fPos, rec.fColorCount));
+static void sweep_gradproc(skiatest::Reporter* reporter, const GradRec& buildRec,
+                           const GradRec& checkRec) {
+    sk_sp<SkShader> s(SkGradientShader::MakeSweep(buildRec.fPoint[0].fX, buildRec.fPoint[0].fY,
+                                                  buildRec.fColors, buildRec.fPos,
+                                                  buildRec.fColorCount));
 
     SkShader::GradientInfo info;
-    rec.gradCheck(reporter, s, &info, SkShader::kSweep_GradientType);
-    REPORTER_ASSERT(reporter, info.fPoint[0] == rec.fPoint[0]);
+    checkRec.gradCheck(reporter, s, &info, SkShader::kSweep_GradientType);
+    REPORTER_ASSERT(reporter, info.fPoint[0] == checkRec.fPoint[0]);
 }
 
-static void conical_gradproc(skiatest::Reporter* reporter, const GradRec& rec) {
-    sk_sp<SkShader> s(SkGradientShader::MakeTwoPointConical(rec.fPoint[0],
-                                                            rec.fRadius[0],
-                                                            rec.fPoint[1],
-                                                            rec.fRadius[1],
-                                                            rec.fColors,
-                                                            rec.fPos,
-                                                            rec.fColorCount,
-                                                            rec.fTileMode));
+static void conical_gradproc(skiatest::Reporter* reporter, const GradRec& buildRec,
+                             const GradRec& checkRec) {
+    sk_sp<SkShader> s(SkGradientShader::MakeTwoPointConical(buildRec.fPoint[0],
+                                                            buildRec.fRadius[0],
+                                                            buildRec.fPoint[1],
+                                                            buildRec.fRadius[1],
+                                                            buildRec.fColors,
+                                                            buildRec.fPos,
+                                                            buildRec.fColorCount,
+                                                            buildRec.fTileMode));
 
     SkShader::GradientInfo info;
-    rec.gradCheck(reporter, s, &info, SkShader::kConical_GradientType);
-    REPORTER_ASSERT(reporter, !memcmp(info.fPoint, rec.fPoint, 2 * sizeof(SkPoint)));
-    REPORTER_ASSERT(reporter, !memcmp(info.fRadius, rec.fRadius, 2 * sizeof(SkScalar)));
+    checkRec.gradCheck(reporter, s, &info, SkShader::kConical_GradientType);
+    REPORTER_ASSERT(reporter, !memcmp(info.fPoint, checkRec.fPoint, 2 * sizeof(SkPoint)));
+    REPORTER_ASSERT(reporter, !memcmp(info.fRadius, checkRec.fRadius, 2 * sizeof(SkScalar)));
 }
 
 // Ensure that repeated color gradients behave like drawing a single color
@@ -150,7 +156,7 @@
     }
 }
 
-typedef void (*GradProc)(skiatest::Reporter* reporter, const GradRec&);
+typedef void (*GradProc)(skiatest::Reporter* reporter, const GradRec&, const GradRec&);
 
 static void TestGradientShaders(skiatest::Reporter* reporter) {
     static const SkColor gColors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
@@ -179,7 +185,85 @@
     };
 
     for (size_t i = 0; i < SK_ARRAY_COUNT(gProcs); ++i) {
-        gProcs[i](reporter, rec);
+        gProcs[i](reporter, rec, rec);
+    }
+}
+
+static void TestGradientOptimization(skiatest::Reporter* reporter) {
+    static const struct {
+        GradProc fProc;
+        bool     fIsClampRestricted;
+    } gProcInfo[] = {
+        { linear_gradproc , false },
+        { radial_gradproc , false },
+        { sweep_gradproc  , true  }, // sweep is funky in that it always pretends to be kClamp.
+        { conical_gradproc, false },
+    };
+
+    static const SkColor   gC_00[] = { 0xff000000, 0xff000000 };
+    static const SkColor   gC_01[] = { 0xff000000, 0xffffffff };
+    static const SkColor   gC_11[] = { 0xffffffff, 0xffffffff };
+    static const SkColor  gC_001[] = { 0xff000000, 0xff000000, 0xffffffff };
+    static const SkColor  gC_011[] = { 0xff000000, 0xffffffff, 0xffffffff };
+    static const SkColor gC_0011[] = { 0xff000000, 0xff000000, 0xffffffff, 0xffffffff };
+
+    static const SkScalar   gP_01[] = { 0, 1 };
+    static const SkScalar  gP_001[] = { 0,   0, 1 };
+    static const SkScalar  gP_011[] = { 0,   1, 1 };
+    static const SkScalar  gP_0x1[] = { 0, .5f, 1 };
+    static const SkScalar gP_0011[] = { 0, 0, 1, 1 };
+
+    static const SkPoint    gPts[] = { {0, 0}, {1, 1} };
+    static const SkScalar gRadii[] = { 1, 2 };
+
+    static const struct {
+        const SkColor*  fCol;
+        const SkScalar* fPos;
+        int             fCount;
+
+        const SkColor*  fExpectedCol;
+        const SkScalar* fExpectedPos;
+        int             fExpectedCount;
+        bool            fRequiresNonClamp;
+    } gTests[] = {
+        { gC_001,  gP_001, 3,  gC_01,  gP_01, 2, false },
+        { gC_001,  gP_011, 3,  gC_00,  gP_01, 2, true  },
+        { gC_001,  gP_0x1, 3, gC_001, gP_0x1, 3, false },
+        { gC_001, nullptr, 3, gC_001, gP_0x1, 3, false },
+
+        { gC_011,  gP_001, 3,  gC_11,  gP_01, 2, true  },
+        { gC_011,  gP_011, 3,  gC_01,  gP_01, 2, false },
+        { gC_011,  gP_0x1, 3, gC_011, gP_0x1, 3, false },
+        { gC_011, nullptr, 3, gC_011, gP_0x1, 3, false },
+
+        { gC_0011, gP_0011, 4, gC_0011, gP_0011, 4, false },
+    };
+
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gProcInfo); ++i) {
+        for (int mode = 0; mode < SkShader::kTileModeCount; ++mode) {
+            if (gProcInfo[i].fIsClampRestricted && mode != SkShader::kClamp_TileMode) {
+                continue;
+            }
+
+            for (size_t t = 0; t < SK_ARRAY_COUNT(gTests); ++t) {
+                GradRec rec;
+                rec.fColorCount = gTests[t].fCount;
+                rec.fColors     = gTests[t].fCol;
+                rec.fPos        = gTests[t].fPos;
+                rec.fTileMode   = static_cast<SkShader::TileMode>(mode);
+                rec.fPoint      = gPts;
+                rec.fRadius     = gRadii;
+
+                GradRec expected = rec;
+                if (!gTests[t].fRequiresNonClamp || mode != SkShader::kClamp_TileMode) {
+                    expected.fColorCount = gTests[t].fExpectedCount;
+                    expected.fColors     = gTests[t].fExpectedCol;
+                    expected.fPos        = gTests[t].fExpectedPos;
+                }
+
+                gProcInfo[i].fProc(reporter, rec, expected);
+            }
+        }
     }
 }
 
@@ -271,6 +355,7 @@
 
 DEF_TEST(Gradient, reporter) {
     TestGradientShaders(reporter);
+    TestGradientOptimization(reporter);
     TestConstantGradient(reporter);
     test_big_grad(reporter);
     test_nearly_vertical(reporter);