Implement 2-pt conical gradient layout

Add an FP that provides the two-point conical gradient effect. The
majority of the new FP's shader code is a straight forward port from
SkTwoPointConicalGradient_gpu.cpp. This FP is bulkier because of the
extensive calculations that go on in its overridden Make function.

To support 2-pt conical gradient's behavior of writing transparent
black in invalid areas of the conical interpolation, the contract of
gradient layout FPs has been updated to provide a flag in the y
component as to whether or not the fragment should be rejected.

A separate channel was used since negative values and large values are
perfectly reasonable for the untiled gradient layout to return (before
the value is then constrained into [0, 1]). It also seemed better to
avoid returning a problematic value like infinity or NaN.

Bug: skia:
Change-Id: I37373bb5aebd89cac8905602e699ad19f0f5ac82
Reviewed-on: https://skia-review.googlesource.com/148988
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/gradients/GrTwoPointConicalGradientLayout.cpp b/src/gpu/gradients/GrTwoPointConicalGradientLayout.cpp
new file mode 100644
index 0000000..cd67661
--- /dev/null
+++ b/src/gpu/gradients/GrTwoPointConicalGradientLayout.cpp
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrTwoPointConicalGradientLayout.fp; do not modify.
+ **************************************************************************************************/
+#include "GrTwoPointConicalGradientLayout.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLTwoPointConicalGradientLayout : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLTwoPointConicalGradientLayout() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrTwoPointConicalGradientLayout& _outer =
+                args.fFp.cast<GrTwoPointConicalGradientLayout>();
+        (void)_outer;
+        auto gradientMatrix = _outer.gradientMatrix();
+        (void)gradientMatrix;
+        auto type = _outer.type();
+        (void)type;
+        auto isRadiusIncreasing = _outer.isRadiusIncreasing();
+        (void)isRadiusIncreasing;
+        auto isFocalOnCircle = _outer.isFocalOnCircle();
+        (void)isFocalOnCircle;
+        auto isWellBehaved = _outer.isWellBehaved();
+        (void)isWellBehaved;
+        auto isSwapped = _outer.isSwapped();
+        (void)isSwapped;
+        auto isNativelyFocal = _outer.isNativelyFocal();
+        (void)isNativelyFocal;
+        auto focalParams = _outer.focalParams();
+        (void)focalParams;
+        fFocalParamsVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
+                                                           kDefault_GrSLPrecision, "focalParams");
+        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        fragBuilder->codeAppendf(
+                "half2 p = half2(%s);\nhalf t = -1.0;\nhalf v = 1.0;\n@switch (%d) {\n    case "
+                "1:\n        {\n            half r0_2 = %s.y;\n            t = r0_2 - p.y * p.y;\n "
+                "           if (t >= 0.0) {\n                t = float(p.x) + sqrt(float(t));\n    "
+                "        } else {\n                v = -1.0;\n            }\n        }\n        "
+                "break;\n    case 0:\n        {\n            half r0 = %s.x;\n            @if (%s) "
+                "{\n                t = length(p) - r0;\n            } else {\n                t = "
+                "-length(p) - r0;\n         ",
+                sk_TransformedCoords2D_0.c_str(), (int)_outer.type(),
+                args.fUniformHandler->getUniformCStr(fFocalParamsVar),
+                args.fUniformHandler->getUniformCStr(fFocalParamsVar),
+                (_outer.isRadiusIncreasing() ? "true" : "false"));
+        fragBuilder->codeAppendf(
+                "   }\n        }\n        break;\n    case 2:\n        {\n            half invR1 = "
+                "%s.x;\n            half fx = %s.y;\n            half x_t = -1.0;\n            @if "
+                "(%s) {\n                x_t = dot(p, p) / p.x;\n            } else if (%s) {\n    "
+                "            x_t = length(p) - p.x * invR1;\n            } else {\n                "
+                "half temp = p.x * p.x - p.y * p.y;\n                if (temp >= 0.0) {\n          "
+                "          @if (%s || !%s) {\n                        x_t = "
+                "half(-sqrt(float(temp)) - float(p.x * invR1",
+                args.fUniformHandler->getUniformCStr(fFocalParamsVar),
+                args.fUniformHandler->getUniformCStr(fFocalParamsVar),
+                (_outer.isFocalOnCircle() ? "true" : "false"),
+                (_outer.isWellBehaved() ? "true" : "false"),
+                (_outer.isSwapped() ? "true" : "false"),
+                (_outer.isRadiusIncreasing() ? "true" : "false"));
+        fragBuilder->codeAppendf(
+                "));\n                    } else {\n                        x_t = "
+                "half(sqrt(float(temp)) - float(p.x * invR1));\n                    }\n            "
+                "    }\n            }\n            @if (!%s) {\n                if (float(x_t) <= "
+                "0.0) {\n                    v = -1.0;\n                }\n            }\n         "
+                "   @if (%s) {\n                @if (%s) {\n                    t = x_t;\n         "
+                "       } else {\n                    t = x_t + fx;\n                }\n           "
+                " } else {\n                @if (%s) {",
+                (_outer.isWellBehaved() ? "true" : "false"),
+                (_outer.isRadiusIncreasing() ? "true" : "false"),
+                (_outer.isNativelyFocal() ? "true" : "false"),
+                (_outer.isNativelyFocal() ? "true" : "false"));
+        fragBuilder->codeAppendf(
+                "\n                    t = -x_t;\n                } else {\n                    t "
+                "= -x_t + fx;\n                }\n            }\n            @if (%s) {\n          "
+                "      t = 1.0 - t;\n            }\n        }\n        break;\n}\n%s = half4(t, v, "
+                "0.0, 0.0);\n",
+                (_outer.isSwapped() ? "true" : "false"), args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrTwoPointConicalGradientLayout& _outer =
+                _proc.cast<GrTwoPointConicalGradientLayout>();
+        {
+            const SkPoint& focalParamsValue = _outer.focalParams();
+            if (fFocalParamsPrev != focalParamsValue) {
+                fFocalParamsPrev = focalParamsValue;
+                pdman.set2f(fFocalParamsVar, focalParamsValue.fX, focalParamsValue.fY);
+            }
+        }
+    }
+    SkPoint fFocalParamsPrev = SkPoint::Make(SK_FloatNaN, SK_FloatNaN);
+    UniformHandle fFocalParamsVar;
+};
+GrGLSLFragmentProcessor* GrTwoPointConicalGradientLayout::onCreateGLSLInstance() const {
+    return new GrGLSLTwoPointConicalGradientLayout();
+}
+void GrTwoPointConicalGradientLayout::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                                            GrProcessorKeyBuilder* b) const {
+    b->add32((int32_t)fType);
+    b->add32((int32_t)fIsRadiusIncreasing);
+    b->add32((int32_t)fIsFocalOnCircle);
+    b->add32((int32_t)fIsWellBehaved);
+    b->add32((int32_t)fIsSwapped);
+    b->add32((int32_t)fIsNativelyFocal);
+}
+bool GrTwoPointConicalGradientLayout::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrTwoPointConicalGradientLayout& that = other.cast<GrTwoPointConicalGradientLayout>();
+    (void)that;
+    if (fGradientMatrix != that.fGradientMatrix) return false;
+    if (fType != that.fType) return false;
+    if (fIsRadiusIncreasing != that.fIsRadiusIncreasing) return false;
+    if (fIsFocalOnCircle != that.fIsFocalOnCircle) return false;
+    if (fIsWellBehaved != that.fIsWellBehaved) return false;
+    if (fIsSwapped != that.fIsSwapped) return false;
+    if (fIsNativelyFocal != that.fIsNativelyFocal) return false;
+    if (fFocalParams != that.fFocalParams) return false;
+    return true;
+}
+GrTwoPointConicalGradientLayout::GrTwoPointConicalGradientLayout(
+        const GrTwoPointConicalGradientLayout& src)
+        : INHERITED(kGrTwoPointConicalGradientLayout_ClassID, src.optimizationFlags())
+        , fGradientMatrix(src.fGradientMatrix)
+        , fType(src.fType)
+        , fIsRadiusIncreasing(src.fIsRadiusIncreasing)
+        , fIsFocalOnCircle(src.fIsFocalOnCircle)
+        , fIsWellBehaved(src.fIsWellBehaved)
+        , fIsSwapped(src.fIsSwapped)
+        , fIsNativelyFocal(src.fIsNativelyFocal)
+        , fFocalParams(src.fFocalParams)
+        , fCoordTransform0(src.fCoordTransform0) {
+    this->addCoordTransform(&fCoordTransform0);
+}
+std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrTwoPointConicalGradientLayout(*this));
+}
+
+// .fp files do not let you reference outside enum definitions, so we have to explicitly map
+// between the two compatible enum defs
+GrTwoPointConicalGradientLayout::Type convert_type(SkTwoPointConicalGradient::Type type) {
+    switch (type) {
+        case SkTwoPointConicalGradient::Type::kRadial:
+            return GrTwoPointConicalGradientLayout::Type::kRadial;
+        case SkTwoPointConicalGradient::Type::kStrip:
+            return GrTwoPointConicalGradientLayout::Type::kStrip;
+        case SkTwoPointConicalGradient::Type::kFocal:
+            return GrTwoPointConicalGradientLayout::Type::kFocal;
+    }
+    SkDEBUGFAIL("Should not be reachable");
+    return GrTwoPointConicalGradientLayout::Type::kRadial;
+}
+
+std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::Make(
+        const SkTwoPointConicalGradient& grad, const GrFPArgs& args) {
+    GrTwoPointConicalGradientLayout::Type grType = convert_type(grad.getType());
+
+    // The focalData struct is only valid if isFocal is true
+    const SkTwoPointConicalGradient::FocalData& focalData = grad.getFocalData();
+    bool isFocal = grType == Type::kFocal;
+
+    // Calculate optimization switches from gradient specification
+    bool isFocalOnCircle = isFocal && focalData.isFocalOnCircle();
+    bool isWellBehaved = isFocal && focalData.isWellBehaved();
+    bool isSwapped = isFocal && focalData.isSwapped();
+    bool isNativelyFocal = isFocal && focalData.isNativelyFocal();
+
+    // Type-specific calculations: isRadiusIncreasing, focalParams, and the gradient matrix.
+    // However, all types start with the total inverse local matrix calculated from the shader
+    // and args
+    bool isRadiusIncreasing;
+    SkPoint focalParams;  // really just a 2D tuple
+    SkMatrix matrix;
+
+    // Initialize the base matrix
+    if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) {
+        return nullptr;
+    }
+
+    if (isFocal) {
+        isRadiusIncreasing = (1 - focalData.fFocalX) > 0;
+
+        focalParams.set(1.0 / focalData.fR1, focalData.fFocalX);
+
+        matrix.postConcat(grad.getGradientMatrix());
+    } else if (grType == Type::kRadial) {
+        SkScalar dr = grad.getDiffRadius();
+        isRadiusIncreasing = dr >= 0;
+
+        SkScalar r0 = grad.getStartRadius() / dr;
+        focalParams.set(r0, r0 * r0);
+
+        // GPU radial matrix is different from the original matrix, since we map the diff radius
+        // to have |dr| = 1, so manually compute the final gradient matrix here.
+
+        // Map center to (0, 0)
+        matrix.postTranslate(-grad.getStartCenter().fX, -grad.getStartCenter().fY);
+
+        // scale |diffRadius| to 1
+        matrix.postScale(1 / dr, 1 / dr);
+    } else {                         // kStrip
+        isRadiusIncreasing = false;  // kStrip doesn't use this flag
+
+        SkScalar r0 = grad.getStartRadius() / grad.getCenterX1();
+        focalParams.set(r0, r0 * r0);
+
+        matrix.postConcat(grad.getGradientMatrix());
+    }
+
+    return std::unique_ptr<GrFragmentProcessor>(new GrTwoPointConicalGradientLayout(
+            matrix, grType, isRadiusIncreasing, isFocalOnCircle, isWellBehaved, isSwapped,
+            isNativelyFocal, focalParams));
+}