Roll external/skia b3d2760e2..34dafd547 (7 commits)

https://skia.googlesource.com/skia.git/+log/b3d2760e2..34dafd547

2018-11-16 mtklein@google.com I think the _DXDY_ code may all be dead.
2018-11-16 michaelludwig@google.com Update threshold for degenerate gradients
2018-11-16 brianosman@google.com Use GrVertexWriter for GrRegionOp, add writeQuad()
2018-11-16 brianosman@google.com Do CCPR hairline coverage scaling in floats, rather than bytes
2018-11-16 skia-autoroll@skia-public.iam.gserviceaccount.com Roll third_party/externals/swiftshader 44994a88c9cc..000df8b42041 (1 commits)
2018-11-16 herb@google.com Fun with flags
2018-11-16 csmartdalton@google.com sksl: Polyfill fma() when not present in GLSL

The AutoRoll server is located here: https://autoroll-internal.skia.org/r/android-master-autoroll

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+/master/autoroll/README.md

If the roll is causing failures, please contact the current sheriff, who should
be CC'd on the roll, and stop the roller if necessary.

Test: Presubmit checks will test this change.
Change-Id: Id0616e8fca2bf1ead872fbd8221e19669152da58
Exempt-From-Owner-Approval: The autoroll bot does not require owner approval.
diff --git a/DEPS b/DEPS
index eb19786..996c1c3 100644
--- a/DEPS
+++ b/DEPS
@@ -30,7 +30,7 @@
   "third_party/externals/sfntly"          : "https://chromium.googlesource.com/external/github.com/googlei18n/sfntly.git@b18b09b6114b9b7fe6fc2f96d8b15e8a72f66916",
   "third_party/externals/spirv-headers"   : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@661ad91124e6af2272afd00f804d8aa276e17107",
   "third_party/externals/spirv-tools"     : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Tools.git@e9e4393b1c5aad7553c05782acefbe32b42644bd",
-  "third_party/externals/swiftshader"     : "https://swiftshader.googlesource.com/SwiftShader@44994a88c9cc899c44a55376ace7445bd4ce3896",
+  "third_party/externals/swiftshader"     : "https://swiftshader.googlesource.com/SwiftShader@000df8b42041ddf416f6b296636b40b4ede6ce35",
   #"third_party/externals/v8"              : "https://chromium.googlesource.com/v8/v8.git@5f1ae66d5634e43563b2d25ea652dfb94c31a3b4",
   "third_party/externals/wuffs"           : "https://github.com/google/wuffs.git@b5c47e273f7f8862bcf04976453d0ec81e6e6650",
   "third_party/externals/zlib"            : "https://chromium.googlesource.com/chromium/src/third_party/zlib@ea3ba903faac98b64b2bf8de5e98cd97b335a474",
diff --git a/include/private/GrColor.h b/include/private/GrColor.h
index f2f449f..61b25ff 100644
--- a/include/private/GrColor.h
+++ b/include/private/GrColor.h
@@ -76,14 +76,6 @@
 
 #define GrColor_WHITE 0xFFFFFFFF
 
-static inline GrColor GrColorMul(GrColor c0, GrColor c1) {
-    U8CPU r = SkMulDiv255Round(GrColorUnpackR(c0), GrColorUnpackR(c1));
-    U8CPU g = SkMulDiv255Round(GrColorUnpackG(c0), GrColorUnpackG(c1));
-    U8CPU b = SkMulDiv255Round(GrColorUnpackB(c0), GrColorUnpackB(c1));
-    U8CPU a = SkMulDiv255Round(GrColorUnpackA(c0), GrColorUnpackA(c1));
-    return GrColorPackRGBA(r, g, b, a);
-}
-
 /** Normalizes and coverts an uint8_t to a float. [0, 255] -> [0.0, 1.0] */
 static inline float GrNormalizeByteToFloat(uint8_t value) {
     static const float ONE_OVER_255 = 1.f / 255.f;
diff --git a/src/core/SkBitmapProcState.cpp b/src/core/SkBitmapProcState.cpp
index 26e5ac9..6e72908 100644
--- a/src/core/SkBitmapProcState.cpp
+++ b/src/core/SkBitmapProcState.cpp
@@ -227,6 +227,7 @@
 
 bool SkBitmapProcState::chooseScanlineProcs(bool trivialMatrix, bool clampClamp) {
     SkASSERT(fPixmap.colorType() == kN32_SkColorType);
+    SkASSERT(fInvType <= (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask));
 
     fMatrixProc = this->chooseMatrixProc(trivialMatrix);
     // TODO(dominikg): SkASSERT(fMatrixProc) instead? chooseMatrixProc never returns nullptr.
diff --git a/src/core/SkPointPriv.h b/src/core/SkPointPriv.h
index 06c83e6..338905d 100644
--- a/src/core/SkPointPriv.h
+++ b/src/core/SkPointPriv.h
@@ -126,22 +126,6 @@
     static void SetRectTriStrip(SkPoint v[], const SkRect& rect, size_t stride) {
         SetRectTriStrip(v, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, stride);
     }
-
-    // Get the idx'th point in a TriStrip (as above). Meant for use with GrVertexWriter
-    static SkPoint TriStripPoint(int idx, const float* ltrb) {
-        switch (idx) {
-            case 0: return { ltrb[0], ltrb[1] };
-            case 1: return { ltrb[0], ltrb[3] };
-            case 2: return { ltrb[2], ltrb[1] };
-            case 3: return { ltrb[2], ltrb[3] };
-        }
-        SkDEBUGFAIL("Invalid index");
-        return { ltrb[0], ltrb[1] };
-    }
-
-    static SkPoint TriStripPoint(int idx, const SkRect& r) {
-        return TriStripPoint(idx, r.asScalars());
-    }
 };
 
 #endif
diff --git a/src/gpu/GrShaderCaps.cpp b/src/gpu/GrShaderCaps.cpp
index c0b9eb5..43a194d 100644
--- a/src/gpu/GrShaderCaps.cpp
+++ b/src/gpu/GrShaderCaps.cpp
@@ -51,6 +51,8 @@
     fFPManipulationSupport = false;
     fFloatIs32Bits = true;
     fHalfIs32Bits = false;
+    fUnsignedSupport = false;
+    fBuiltinFMASupport = false;
 
     fVersionDeclString = nullptr;
     fShaderDerivativeExtensionString = nullptr;
@@ -120,6 +122,7 @@
     writer->appendBool("Floating point manipulation support", fFPManipulationSupport);
     writer->appendBool("float == fp32", fFloatIs32Bits);
     writer->appendBool("half == fp32", fHalfIs32Bits);
+    writer->appendBool("Builtin fma() support", fBuiltinFMASupport);
 
     writer->appendS32("Max FS Samplers", fMaxFragmentSamplers);
     writer->appendString("Advanced blend equation interaction",
diff --git a/src/gpu/GrShaderCaps.h b/src/gpu/GrShaderCaps.h
index b0836c4..fb1b17f 100644
--- a/src/gpu/GrShaderCaps.h
+++ b/src/gpu/GrShaderCaps.h
@@ -86,6 +86,9 @@
 
     bool unsignedSupport() const { return fUnsignedSupport; }
 
+    // SkSL only.
+    bool builtinFMASupport() const { return fBuiltinFMASupport; }
+
     AdvBlendEqInteraction advBlendEqInteraction() const { return fAdvBlendEqInteraction; }
 
     bool mustEnableAdvBlendEqs() const {
@@ -254,6 +257,9 @@
     bool fHalfIs32Bits                      : 1;
     bool fUnsignedSupport                   : 1;
 
+    // Used by SkSL to know when to generate polyfills.
+    bool fBuiltinFMASupport : 1;
+
     // Used for specific driver bug work arounds
     bool fCanUseAnyFunctionInShader                   : 1;
     bool fCanUseMinAndAbsTogether                     : 1;
diff --git a/src/gpu/GrVertexWriter.h b/src/gpu/GrVertexWriter.h
index 8bfcef7..6026348 100644
--- a/src/gpu/GrVertexWriter.h
+++ b/src/gpu/GrVertexWriter.h
@@ -21,6 +21,8 @@
  * thereof.
  */
 struct GrVertexWriter {
+    void* fPtr;
+
     template <typename T, typename... Args>
     void write(const T& val, const Args&... remainder) {
         static_assert(std::is_pod<T>::value, "");
@@ -41,7 +43,51 @@
 
     void write() {}
 
-    void* fPtr;
+    /**
+     * Specialized utility for writing a four-vertices, with some data being replicated at each
+     * vertex, and other data being the appropriate 2-components from an SkRect to construct a
+     * triangle strip.
+     *
+     * writeQuad(A, B, C, ...) is similar to write(A, B, C, ...), except that:
+     *
+     * - Four sets of data will be written
+     * - For any arguments of type TriStrip, a unique SkPoint will be written at each vertex,
+     *   in this order: left-top, left-bottom, right-top, right-bottom.
+     */
+    struct TriStrip { const SkRect& fRect; };
+
+    template <typename... Args>
+    void writeQuad(const Args&... remainder) {
+        this->writeQuadVert<0>(remainder...);
+        this->writeQuadVert<1>(remainder...);
+        this->writeQuadVert<2>(remainder...);
+        this->writeQuadVert<3>(remainder...);
+    }
+
+private:
+    template <int corner, typename T, typename... Args>
+    void writeQuadVert(const T& val, const Args&... remainder) {
+        this->writeQuadValue<corner>(val);
+        this->writeQuadVert<corner>(remainder...);
+    }
+
+    template <int corner>
+    void writeQuadVert() {}
+
+    template <int corner, typename T>
+    void writeQuadValue(const T& val) {
+        this->write(val);
+    }
+
+    template <int corner>
+    void writeQuadValue(const TriStrip& r) {
+        switch (corner) {
+            case 0: this->write(r.fRect.fLeft , r.fRect.fTop);    break;
+            case 1: this->write(r.fRect.fLeft , r.fRect.fBottom); break;
+            case 2: this->write(r.fRect.fRight, r.fRect.fTop);    break;
+            case 3: this->write(r.fRect.fRight, r.fRect.fBottom); break;
+        }
+    }
 };
 
 #endif
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index b1a70268..c1384fe 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -192,15 +192,14 @@
         hairlineStroke.setStrokeStyle(0);
 
         // How transparent does a 1px stroke have to be in order to appear as thin as the real one?
-        GrColor coverageAsAlpha = GrColorPackA4(SkScalarFloorToInt(draw->fStrokeDevWidth * 255));
+        float coverage = draw->fStrokeDevWidth;
 
         draw->fShape = GrShape(path, GrStyle(hairlineStroke, nullptr));
         draw->fStrokeDevWidth = 1;
 
         // TODO4F: Preserve float colors
         // fShapeConservativeIBounds already accounted for this possibility of inflating the stroke.
-        draw->fColor = SkPMColor4f::FromBytes_RGBA(
-                GrColorMul(draw->fColor.toBytes_RGBA(), coverageAsAlpha));
+        draw->fColor = draw->fColor * coverage;
     }
 
     return RequiresDstTexture(analysis.requiresDstTexture());
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 9dc76b6..74772ed 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -798,6 +798,12 @@
 
     // Unsigned integers only supported in and after GLSL 1.30.
     shaderCaps->fUnsignedSupport = ctxInfo.glslGeneration() >= k130_GrGLSLGeneration;
+
+    if (kGL_GrGLStandard == standard) {
+        shaderCaps->fBuiltinFMASupport = ctxInfo.glslGeneration() >= k400_GrGLSLGeneration;
+    } else {
+        shaderCaps->fBuiltinFMASupport = ctxInfo.glslGeneration() >= k320es_GrGLSLGeneration;
+    }
 }
 
 bool GrGLCaps::hasPathRenderingSupport(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli) {
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index 7db5463..4cc766b 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -16,7 +16,6 @@
 #include "SkBitmap.h"
 #include "SkLatticeIter.h"
 #include "SkMatrixPriv.h"
-#include "SkPointPriv.h"
 #include "SkRect.h"
 #include "glsl/GrGLSLColorSpaceXformHelper.h"
 #include "glsl/GrGLSLGeometryProcessor.h"
@@ -251,17 +250,15 @@
                     coords = kFlipMuls * coords + kFlipOffsets;
                     domain = SkNx_shuffle<0, 3, 2, 1>(kFlipMuls * domain + kFlipOffsets);
                 }
-                float texDomain[4];
-                float texCoords[4];
-                domain.store(texDomain);
-                coords.store(texCoords);
+                SkRect texDomain;
+                SkRect texCoords;
+                domain.store(&texDomain);
+                coords.store(&texCoords);
 
-                for (int j = 0; j < kVertsPerRect; ++j) {
-                    vertices.write(SkPointPriv::TriStripPoint(j, dstR),
-                                   SkPointPriv::TriStripPoint(j, texCoords),
+                vertices.writeQuad(GrVertexWriter::TriStrip{ dstR },
+                                   GrVertexWriter::TriStrip{ texCoords },
                                    texDomain,
                                    patchColor);
-                }
             }
 
             // If we didn't handle it above, apply the matrix here.
diff --git a/src/gpu/ops/GrRegionOp.cpp b/src/gpu/ops/GrRegionOp.cpp
index 5268582..d824609 100644
--- a/src/gpu/ops/GrRegionOp.cpp
+++ b/src/gpu/ops/GrRegionOp.cpp
@@ -12,8 +12,8 @@
 #include "GrOpFlushState.h"
 #include "GrResourceProvider.h"
 #include "GrSimpleMeshDrawOpHelper.h"
+#include "GrVertexWriter.h"
 #include "SkMatrixPriv.h"
-#include "SkPointPriv.h"
 #include "SkRegion.h"
 
 static const int kVertsPerInstance = 4;
@@ -27,30 +27,6 @@
                                          viewMatrix);
 }
 
-static void tesselate_region(intptr_t vertices,
-                             size_t vertexStride,
-                             GrColor color,
-                             const SkRegion& region) {
-    SkRegion::Iterator iter(region);
-
-    intptr_t verts = vertices;
-    while (!iter.done()) {
-        SkRect rect = SkRect::Make(iter.rect());
-        SkPoint* position = (SkPoint*)verts;
-        SkPointPriv::SetRectTriStrip(position, rect, vertexStride);
-
-        static const int kColorOffset = sizeof(SkPoint);
-        GrColor* vertColor = reinterpret_cast<GrColor*>(verts + kColorOffset);
-        for (int i = 0; i < kVertsPerInstance; i++) {
-            *vertColor = color;
-            vertColor = (GrColor*)((intptr_t)vertColor + vertexStride);
-        }
-
-        verts += vertexStride * kVertsPerInstance;
-        iter.next();
-    }
-}
-
 namespace {
 
 class RegionOp final : public GrMeshDrawOp {
@@ -119,7 +95,6 @@
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
         }
-        size_t kVertexStride = gp->vertexStride();
 
         int numRegions = fRegions.count();
         int numRects = 0;
@@ -131,21 +106,24 @@
             return;
         }
         sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
-        PatternHelper helper(target, GrPrimitiveType::kTriangles, kVertexStride, indexBuffer.get(),
-                             kVertsPerInstance, kIndicesPerInstance, numRects);
-        void* vertices = helper.vertices();
-        if (!vertices || !indexBuffer) {
+        PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(),
+                             indexBuffer.get(), kVertsPerInstance, kIndicesPerInstance, numRects);
+        GrVertexWriter vertices{helper.vertices()};
+        if (!vertices.fPtr || !indexBuffer) {
             SkDebugf("Could not allocate vertices\n");
             return;
         }
 
-        intptr_t verts = reinterpret_cast<intptr_t>(vertices);
         for (int i = 0; i < numRegions; i++) {
             // TODO4F: Preserve float colors
-            tesselate_region(verts, kVertexStride, fRegions[i].fColor.toBytes_RGBA(),
-                             fRegions[i].fRegion);
-            int numRectsInRegion = fRegions[i].fRegion.computeRegionComplexity();
-            verts += numRectsInRegion * kVertsPerInstance * kVertexStride;
+            GrColor color = fRegions[i].fColor.toBytes_RGBA();
+
+            SkRegion::Iterator iter(fRegions[i].fRegion);
+            while (!iter.done()) {
+                SkRect rect = SkRect::Make(iter.rect());
+                vertices.writeQuad(GrVertexWriter::TriStrip{ rect }, color);
+                iter.next();
+            }
         }
         auto pipe = fHelper.makePipeline(target);
         helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
diff --git a/src/gpu/text/GrTextBlob.h b/src/gpu/text/GrTextBlob.h
index 8a1e0ec..7d4450c 100644
--- a/src/gpu/text/GrTextBlob.h
+++ b/src/gpu/text/GrTextBlob.h
@@ -329,35 +329,18 @@
                                 SkScalar* transX, SkScalar* transY);
 
         // df properties
-        void setDrawAsDistanceFields() { fFlags |= kDrawAsSDF_Flag; }
-        bool drawAsDistanceFields() const { return SkToBool(fFlags & kDrawAsSDF_Flag); }
-        void setUseLCDText(bool useLCDText) {
-            fFlags = useLCDText ? fFlags | kUseLCDText_Flag : fFlags & ~kUseLCDText_Flag;
-        }
-        bool hasUseLCDText() const { return SkToBool(fFlags & kUseLCDText_Flag); }
-        void setAntiAliased(bool antiAliased) {
-            fFlags = antiAliased ? fFlags | kAntiAliased_Flag : fFlags & ~kAntiAliased_Flag;
-        }
-        bool isAntiAliased() const { return SkToBool(fFlags & kAntiAliased_Flag); }
-        void setHasWCoord(bool hasW) {
-            fFlags  = hasW ? (fFlags | kHasWCoord_Flag) : fFlags & ~kHasWCoord_Flag;
-        }
-        bool hasWCoord() const { return SkToBool(fFlags & kHasWCoord_Flag); }
-        void setNeedsTransform(bool needsTransform) {
-            fFlags  = needsTransform ? (fFlags | kNeedsTransform_Flag)
-                                     : fFlags & ~kNeedsTransform_Flag;
-        }
-        bool needsTransform() const { return SkToBool(fFlags & kNeedsTransform_Flag); }
+        void setDrawAsDistanceFields() { fFlags.drawAsSdf = true; }
+        bool drawAsDistanceFields() const { return fFlags.drawAsSdf; }
+        void setUseLCDText(bool useLCDText) { fFlags.useLCDText = useLCDText; }
+        bool hasUseLCDText() const { return fFlags.useLCDText; }
+        void setAntiAliased(bool antiAliased) { fFlags.antiAliased = antiAliased; }
+        bool isAntiAliased() const { return fFlags.antiAliased; }
+        void setHasWCoord(bool hasW) { fFlags.hasWCoord = hasW; }
+        bool hasWCoord() const { return fFlags.hasWCoord; }
+        void setNeedsTransform(bool needsTransform) { fFlags.needsTransform = needsTransform; }
+        bool needsTransform() const { return fFlags.needsTransform; }
 
     private:
-        enum Flag {
-            kDrawAsSDF_Flag = 0x01,
-            kUseLCDText_Flag = 0x02,
-            kAntiAliased_Flag = 0x04,
-            kHasWCoord_Flag = 0x08,
-            kNeedsTransform_Flag = 0x10
-        };
-
         GrDrawOpAtlas::BulkUseTokenUpdater fBulkUseToken;
         sk_sp<GrTextStrike> fStrike;
         SkMatrix fCurrentViewMatrix;
@@ -371,7 +354,13 @@
         SkScalar fY;
         GrColor fColor{GrColor_ILLEGAL};
         GrMaskFormat fMaskFormat{kA8_GrMaskFormat};
-        uint32_t fFlags{0};
+        struct {
+            bool drawAsSdf:1;
+            bool useLCDText:1;
+            bool antiAliased:1;
+            bool hasWCoord:1;
+            bool needsTransform:1;
+        } fFlags{false, false, false, false, false};
     };  // SubRunInfo
 
 
diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp
index 4726f09..2a9bfdd 100644
--- a/src/shaders/gradients/SkGradientShader.cpp
+++ b/src/shaders/gradients/SkGradientShader.cpp
@@ -557,6 +557,10 @@
     return avg;
 }
 
+// The default SkScalarNearlyZero threshold of .0024 is too big and causes regressions for svg
+// gradients defined in the wild.
+static constexpr SkScalar kDegenerateThreshold = SK_Scalar1 / (1 << 15);
+
 // Except for special circumstances of clamped gradients, every gradient shape--when degenerate--
 // can be mapped to the same fallbacks. The specific shape factories must account for special
 // clamped conditions separately, this will always return the last color for clamped gradients.
@@ -686,7 +690,7 @@
         return nullptr;
     }
 
-    if (SkScalarNearlyZero((pts[1] - pts[0]).length())) {
+    if (SkScalarNearlyZero((pts[1] - pts[0]).length(), kDegenerateThreshold)) {
         // Degenerate gradient, the only tricky complication is when in clamp mode, the limit of
         // the gradient approaches two half planes of solid color (first and last). However, they
         // are divided by the line perpendicular to the start and end point, which becomes undefined
@@ -733,7 +737,7 @@
         return nullptr;
     }
 
-    if (SkScalarNearlyZero(radius)) {
+    if (SkScalarNearlyZero(radius, kDegenerateThreshold)) {
         // Degenerate gradient optimization, and no special logic needed for clamped radial gradient
         return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode);
     }
@@ -778,15 +782,15 @@
     if (!valid_grad(colors, pos, colorCount, mode)) {
         return nullptr;
     }
-    if (SkScalarNearlyZero((start - end).length())) {
+    if (SkScalarNearlyZero((start - end).length(), kDegenerateThreshold)) {
         // If the center positions are the same, then the gradient is the radial variant of a 2 pt
         // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate
         // (startRadius == endRadius).
-        if (SkScalarNearlyEqual(startRadius, endRadius)) {
+        if (SkScalarNearlyEqual(startRadius, endRadius, kDegenerateThreshold)) {
             // Degenerate case, where the interpolation region area approaches zero. The proper
             // behavior depends on the tile mode, which is consistent with the default degenerate
             // gradient behavior, except when mode = clamp and the radii > 0.
-            if (mode == SkShader::TileMode::kClamp_TileMode && endRadius > SK_ScalarNearlyZero) {
+            if (mode == SkShader::TileMode::kClamp_TileMode && endRadius > kDegenerateThreshold) {
                 // The interpolation region becomes an infinitely thin ring at the radius, so the
                 // final gradient will be the first color repeated from p=0 to 1, and then a hard
                 // stop switching to the last color at p=1.
@@ -799,7 +803,7 @@
                 return make_degenerate_gradient(
                         colors, pos, colorCount, std::move(colorSpace), mode);
             }
-        } else if (SkScalarNearlyZero(startRadius)) {
+        } else if (SkScalarNearlyZero(startRadius, kDegenerateThreshold)) {
             // We can treat this gradient as radial, which is faster. If we got here, we know
             // that endRadius is not equal to 0, so this produces a meaningful gradient
             return MakeRadial(start, endRadius, colors, std::move(colorSpace), pos, colorCount,
@@ -859,10 +863,10 @@
         return nullptr;
     }
 
-    if (SkScalarNearlyEqual(startAngle, endAngle)) {
+    if (SkScalarNearlyEqual(startAngle, endAngle, kDegenerateThreshold)) {
         // Degenerate gradient, which should follow default degenerate behavior unless it is
         // clamped and the angle is greater than 0.
-        if (mode == SkShader::kClamp_TileMode && endAngle > SK_ScalarNearlyZero) {
+        if (mode == SkShader::kClamp_TileMode && endAngle > kDegenerateThreshold) {
             // In this case, the first color is repeated from 0 to the angle, then a hardstop
             // switches to the last color (all other colors are compressed to the infinitely thin
             // interpolation region).
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index d56d611..26bda18 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -467,6 +467,7 @@
         (*fFunctionClasses)["dFdx"]        = FunctionClass::kDerivative;
         (*fFunctionClasses)["dFdy"]        = FunctionClass::kDerivative;
         (*fFunctionClasses)["fwidth"]      = FunctionClass::kDerivative;
+        (*fFunctionClasses)["fma"]         = FunctionClass::kFMA;
         (*fFunctionClasses)["fract"]       = FunctionClass::kFract;
         (*fFunctionClasses)["inverse"]     = FunctionClass::kInverse;
         (*fFunctionClasses)["inverseSqrt"] = FunctionClass::kInverseSqrt;
@@ -535,6 +536,19 @@
                     return;
                 }
                 break;
+            case FunctionClass::kFMA:
+                if (!fProgram.fSettings.fCaps->builtinFMASupport()) {
+                    SkASSERT(c.fArguments.size() == 3);
+                    this->write("((");
+                    this->writeExpression(*c.fArguments[0], kSequence_Precedence);
+                    this->write(") * (");
+                    this->writeExpression(*c.fArguments[1], kSequence_Precedence);
+                    this->write(") + (");
+                    this->writeExpression(*c.fArguments[2], kSequence_Precedence);
+                    this->write("))");
+                    return;
+                }
+                break;
             case FunctionClass::kFract:
                 if (!fProgram.fSettings.fCaps->canUseFractForNegativeValues()) {
                     SkASSERT(c.fArguments.size() == 1);
diff --git a/src/sksl/SkSLGLSLCodeGenerator.h b/src/sksl/SkSLGLSLCodeGenerator.h
index 906c1b0..99e7d47 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.h
+++ b/src/sksl/SkSLGLSLCodeGenerator.h
@@ -228,6 +228,7 @@
         kAtan,
         kDeterminant,
         kDerivative,
+        kFMA,
         kFract,
         kInverse,
         kInverseSqrt,