added SkSL saturate() function

Bug: skia:8220
Change-Id: Ib2e58ac77345a2aa53302c6c1484d52533556f93
Reviewed-on: https://skia-review.googlesource.com/145371
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
diff --git a/src/core/SkColorMatrixFilterRowMajor255.cpp b/src/core/SkColorMatrixFilterRowMajor255.cpp
index 9a8256e..ad36006 100644
--- a/src/core/SkColorMatrixFilterRowMajor255.cpp
+++ b/src/core/SkColorMatrixFilterRowMajor255.cpp
@@ -221,7 +221,7 @@
                                      uniformHandler->getUniformCStr(fMatrixHandle),
                                      args.fInputColor,
                                      uniformHandler->getUniformCStr(fVectorHandle));
-            fragBuilder->codeAppendf("\t%s = clamp(%s, 0.0, 1.0);\n",
+            fragBuilder->codeAppendf("\t%s = saturate(%s);\n",
                                      args.fOutputColor, args.fOutputColor);
             fragBuilder->codeAppendf("\t%s.rgb *= %s.a;\n", args.fOutputColor, args.fOutputColor);
         }
diff --git a/src/effects/SkHighContrastFilter.cpp b/src/effects/SkHighContrastFilter.cpp
index 48bcb40..de0eebf 100644
--- a/src/effects/SkHighContrastFilter.cpp
+++ b/src/effects/SkHighContrastFilter.cpp
@@ -343,7 +343,7 @@
     fragBuilder->codeAppendf("}");
 
     // Clamp.
-    fragBuilder->codeAppendf("color = clamp(color, 0, 1);");
+    fragBuilder->codeAppendf("color = saturate(color);");
 
     if (hcfe.linearize()) {
         fragBuilder->codeAppend("color.rgb = sqrt(color.rgb);");
diff --git a/src/effects/imagefilters/SkDisplacementMapEffect.cpp b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
index 64db1cd..92ac807 100644
--- a/src/effects/imagefilters/SkDisplacementMapEffect.cpp
+++ b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
@@ -551,7 +551,7 @@
 
     // Unpremultiply the displacement
     fragBuilder->codeAppendf(
-        "\t\t%s.rgb = (%s.a < %s) ? half3(0.0) : clamp(%s.rgb / %s.a, 0.0, 1.0);",
+        "\t\t%s.rgb = (%s.a < %s) ? half3(0.0) : saturate(%s.rgb / %s.a);",
         dColor, dColor, nearZero, dColor, dColor);
     SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[1]);
     fragBuilder->codeAppendf("\t\tfloat2 %s = %s + %s*(%s.",
diff --git a/src/effects/imagefilters/SkLightingImageFilter.cpp b/src/effects/imagefilters/SkLightingImageFilter.cpp
index dc7cd74..de86718 100644
--- a/src/effects/imagefilters/SkLightingImageFilter.cpp
+++ b/src/effects/imagefilters/SkLightingImageFilter.cpp
@@ -1945,7 +1945,7 @@
     };
     SkString lightBody;
     lightBody.appendf("\thalf colorScale = %s * dot(normal, surfaceToLight);\n", kd);
-    lightBody.appendf("\treturn half4(lightColor * clamp(colorScale, 0.0, 1.0), 1.0);\n");
+    lightBody.appendf("\treturn half4(lightColor * saturate(colorScale), 1.0);\n");
     fragBuilder->emitFunction(kHalf4_GrSLType,
                               "light",
                               SK_ARRAY_COUNT(gLightArgs),
@@ -2043,7 +2043,7 @@
     lightBody.appendf("\thalf3 halfDir = half3(normalize(surfaceToLight + half3(0, 0, 1)));\n");
     lightBody.appendf("\tfloat colorScale = %s * pow(dot(normal, halfDir), %s);\n",
                       ks, shininess);
-    lightBody.appendf("\thalf3 color = lightColor * clamp(colorScale, 0.0, 1.0);\n");
+    lightBody.appendf("\thalf3 color = lightColor * saturate(colorScale);\n");
     lightBody.appendf("\treturn half4(color, max(max(color.r, color.g), color.b));\n");
     fragBuilder->emitFunction(kHalf4_GrSLType,
                               "light",
diff --git a/src/gpu/effects/GrArithmeticFP.fp b/src/gpu/effects/GrArithmeticFP.fp
index 9aa6549..ce16b91 100644
--- a/src/gpu/effects/GrArithmeticFP.fp
+++ b/src/gpu/effects/GrArithmeticFP.fp
@@ -16,7 +16,7 @@
 
 void main() {
     half4 dst = process(child);
-    sk_OutColor = clamp(k.x * sk_InColor * dst + k.y * sk_InColor + k.z * dst + k.w, 0, 1);
+    sk_OutColor = saturate(k.x * sk_InColor * dst + k.y * sk_InColor + k.z * dst + k.w);
     if (enforcePMColor) {
         sk_OutColor.rgb = min(sk_OutColor.rgb, sk_OutColor.a);
     }
diff --git a/src/gpu/effects/GrBezierEffect.cpp b/src/gpu/effects/GrBezierEffect.cpp
index d68b1bf..e6d6413 100644
--- a/src/gpu/effects/GrBezierEffect.cpp
+++ b/src/gpu/effects/GrBezierEffect.cpp
@@ -175,7 +175,7 @@
                                      func.c_str(), v.fsIn(), v.fsIn(), v.fsIn(), v.fsIn());
             fragBuilder->codeAppendf("%s = %s / %s;",
                                      edgeAlpha.c_str(), func.c_str(), gFM.c_str());
-            fragBuilder->codeAppendf("%s = clamp(0.5 - %s, 0.0, 1.0);",
+            fragBuilder->codeAppendf("%s = saturate(0.5 - %s);",
                                      edgeAlpha.c_str(), edgeAlpha.c_str());
             // Add line below for smooth cubic ramp
             // fragBuilder->codeAppend("edgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);");
@@ -380,7 +380,7 @@
             fragBuilder->codeAppendf("edgeAlpha = (%s.x * %s.x - %s.y);",
                                      v.fsIn(), v.fsIn(), v.fsIn());
             fragBuilder->codeAppend("edgeAlpha = edgeAlpha / sqrt(dot(gF, gF));");
-            fragBuilder->codeAppend("edgeAlpha = clamp(0.5 - edgeAlpha, 0.0, 1.0);");
+            fragBuilder->codeAppend("edgeAlpha = saturate(0.5 - edgeAlpha);");
             // Add line below for smooth cubic ramp
             // fragBuilder->codeAppend("edgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);");
             break;
@@ -615,7 +615,7 @@
                                      v.fsIn(), v.fsIn(), v.fsIn(), v.fsIn(), v.fsIn());
             fragBuilder->codeAppendf("%s = %s * inversesqrt(dot(%s, %s));",
                                      edgeAlpha.c_str(), func.c_str(), gF.c_str(), gF.c_str());
-            fragBuilder->codeAppendf("%s = clamp(0.5 - %s, 0.0, 1.0);",
+            fragBuilder->codeAppendf("%s = saturate(0.5 - %s);",
                                      edgeAlpha.c_str(), edgeAlpha.c_str());
             // Add line below for smooth cubic ramp
             // fragBuilder->codeAppendf("%s = %s * %s * (3.0 - 2.0 * %s);",
diff --git a/src/gpu/effects/GrCircleEffect.fp b/src/gpu/effects/GrCircleEffect.fp
index 0faf7d7..64932da 100644
--- a/src/gpu/effects/GrCircleEffect.fp
+++ b/src/gpu/effects/GrCircleEffect.fp
@@ -60,7 +60,7 @@
     @if (edgeType == GrClipEdgeType::kFillAA ||
          edgeType == GrClipEdgeType::kInverseFillAA ||
          edgeType == GrClipEdgeType::kHairlineAA) {
-        d = clamp(d, 0.0, 1.0);
+        d = saturate(d);
     } else {
         d = d > 0.5 ? 1.0 : 0.0;
     }
diff --git a/src/gpu/effects/GrConvexPolyEffect.cpp b/src/gpu/effects/GrConvexPolyEffect.cpp
index f98ed47..251f127 100644
--- a/src/gpu/effects/GrConvexPolyEffect.cpp
+++ b/src/gpu/effects/GrConvexPolyEffect.cpp
@@ -54,7 +54,7 @@
                                                              "1));\n",
                                  edgeArrayName, i);
         if (GrProcessorEdgeTypeIsAA(cpe.getEdgeType())) {
-            fragBuilder->codeAppend("\t\tedge = clamp(edge, 0.0, 1.0);\n");
+            fragBuilder->codeAppend("\t\tedge = saturate(edge);\n");
         } else {
             fragBuilder->codeAppend("\t\tedge = edge >= 0.5 ? 1.0 : 0.0;\n");
         }
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 59ec5da..099cc7e 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -152,7 +152,7 @@
             // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want
             // distance mapped linearly to coverage, so use a linear step:
             fragBuilder->codeAppend(
-                "half val = clamp((distance + afwidth) / (2.0 * afwidth), 0.0, 1.0);");
+                "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
         } else {
             fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
         }
@@ -447,7 +447,7 @@
         // mapped linearly to coverage, so use a linear step:
         if (isGammaCorrect) {
             fragBuilder->codeAppend(
-                "half val = clamp((distance + afwidth) / (2.0 * afwidth), 0.0, 1.0);");
+                "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
         } else {
             fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
         }
@@ -766,7 +766,7 @@
         // mapped linearly to coverage, so use a linear step:
         if (isGammaCorrect) {
             fragBuilder->codeAppendf("%s = "
-                "half4(clamp((distance + half3(afwidth)) / half3(2.0 * afwidth), 0.0, 1.0), 1.0);",
+                "half4(saturate((distance + half3(afwidth)) / half3(2.0 * afwidth)), 1.0);",
                 args.fOutputCoverage);
         } else {
             fragBuilder->codeAppendf(
diff --git a/src/gpu/effects/GrEllipseEffect.fp b/src/gpu/effects/GrEllipseEffect.fp
index 24cbec2..00a4c7d 100644
--- a/src/gpu/effects/GrEllipseEffect.fp
+++ b/src/gpu/effects/GrEllipseEffect.fp
@@ -91,13 +91,13 @@
             alpha = approx_dist > 0.0 ? 0.0 : 1.0;
             break;
         case GrClipEdgeType::kFillAA:
-            alpha = clamp(0.5 - approx_dist, 0.0, 1.0);
+            alpha = saturate(0.5 - approx_dist);
             break;
         case GrClipEdgeType::kInverseFillBW:
             alpha = approx_dist > 0.0 ? 1.0 : 0.0;
             break;
         case GrClipEdgeType::kInverseFillAA:
-            alpha = clamp(0.5 + approx_dist, 0.0, 1.0);
+            alpha = saturate(0.5 + approx_dist);
             break;
         default:
             // hairline not supported
diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
index 123728f..b3a3261 100644
--- a/src/gpu/effects/GrMatrixConvolutionEffect.cpp
+++ b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
@@ -87,14 +87,14 @@
                                   args.fTexSamplers[0]);
             if (!mce.convolveAlpha()) {
                 fragBuilder->codeAppend("c.rgb /= c.a;");
-                fragBuilder->codeAppend("c.rgb = clamp(c.rgb, 0.0, 1.0);");
+                fragBuilder->codeAppend("c.rgb = saturate(c.rgb);");
             }
             fragBuilder->codeAppend("sum += c * k;");
         }
     }
     if (mce.convolveAlpha()) {
         fragBuilder->codeAppendf("%s = sum * %s + %s;", args.fOutputColor, gain, bias);
-        fragBuilder->codeAppendf("%s.a = clamp(%s.a, 0, 1);", args.fOutputColor, args.fOutputColor);
+        fragBuilder->codeAppendf("%s.a = saturate(%s.a);", args.fOutputColor, args.fOutputColor);
         fragBuilder->codeAppendf("%s.rgb = clamp(%s.rgb, 0.0, %s.a);",
                                  args.fOutputColor, args.fOutputColor, args.fOutputColor);
     } else {
@@ -106,7 +106,7 @@
                               coords2D,
                               args.fTexSamplers[0]);
         fragBuilder->codeAppendf("%s.a = c.a;", args.fOutputColor);
-        fragBuilder->codeAppendf("%s.rgb = clamp(sum.rgb * %s + %s, 0, 1);", args.fOutputColor, gain, bias);
+        fragBuilder->codeAppendf("%s.rgb = saturate(sum.rgb * %s + %s);", args.fOutputColor, gain, bias);
         fragBuilder->codeAppendf("%s.rgb *= %s.a;", args.fOutputColor, args.fOutputColor);
     }
     fragBuilder->codeAppendf("%s *= %s;\n", args.fOutputColor, args.fInputColor);
diff --git a/src/gpu/effects/GrRRectEffect.cpp b/src/gpu/effects/GrRRectEffect.cpp
index 2fb28fd..6478306 100644
--- a/src/gpu/effects/GrRRectEffect.cpp
+++ b/src/gpu/effects/GrRRectEffect.cpp
@@ -166,10 +166,10 @@
     // If we're on a device where float != fp32 then the length calculation could overflow.
     SkString clampedCircleDistance;
     if (!args.fShaderCaps->floatIs32Bits()) {
-        clampedCircleDistance.printf("clamp(%s.x * (1.0 - length(dxy * %s.y)), 0.0, 1.0);",
+        clampedCircleDistance.printf("saturate(%s.x * (1.0 - length(dxy * %s.y)));",
                                      radiusPlusHalfName, radiusPlusHalfName);
     } else {
-        clampedCircleDistance.printf("clamp(%s.x - length(dxy), 0.0, 1.0);", radiusPlusHalfName);
+        clampedCircleDistance.printf("saturate(%s.x - length(dxy));", radiusPlusHalfName);
     }
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
@@ -198,9 +198,9 @@
         case CircularRRectEffect::kTopLeft_CornerFlag:
             fragBuilder->codeAppendf("float2 dxy = max(%s.xy - sk_FragCoord.xy, 0.0);",
                                      rectName);
-            fragBuilder->codeAppendf("half rightAlpha = clamp(%s.z - sk_FragCoord.x, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half rightAlpha = saturate(%s.z - sk_FragCoord.x);",
                                      rectName);
-            fragBuilder->codeAppendf("half bottomAlpha = clamp(%s.w - sk_FragCoord.y, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half bottomAlpha = saturate(%s.w - sk_FragCoord.y);",
                                      rectName);
             fragBuilder->codeAppendf("half alpha = bottomAlpha * rightAlpha * %s;",
                                      clampedCircleDistance.c_str());
@@ -209,9 +209,9 @@
             fragBuilder->codeAppendf("float2 dxy = max(float2(sk_FragCoord.x - %s.z, "
                                                              "%s.y - sk_FragCoord.y), 0.0);",
                                      rectName, rectName);
-            fragBuilder->codeAppendf("half leftAlpha = clamp(sk_FragCoord.x - %s.x, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half leftAlpha = saturate(sk_FragCoord.x - %s.x);",
                                      rectName);
-            fragBuilder->codeAppendf("half bottomAlpha = clamp(%s.w - sk_FragCoord.y, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half bottomAlpha = saturate(%s.w - sk_FragCoord.y);",
                                      rectName);
             fragBuilder->codeAppendf("half alpha = bottomAlpha * leftAlpha * %s;",
                                      clampedCircleDistance.c_str());
@@ -219,9 +219,9 @@
         case CircularRRectEffect::kBottomRight_CornerFlag:
             fragBuilder->codeAppendf("float2 dxy = max(sk_FragCoord.xy - %s.zw, 0.0);",
                                      rectName);
-            fragBuilder->codeAppendf("half leftAlpha = clamp(sk_FragCoord.x - %s.x, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half leftAlpha = saturate(sk_FragCoord.x - %s.x);",
                                      rectName);
-            fragBuilder->codeAppendf("half topAlpha = clamp(sk_FragCoord.y - %s.y, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half topAlpha = saturate(sk_FragCoord.y - %s.y);",
                                      rectName);
             fragBuilder->codeAppendf("half alpha = topAlpha * leftAlpha * %s;",
                                      clampedCircleDistance.c_str());
@@ -230,9 +230,9 @@
             fragBuilder->codeAppendf("float2 dxy = max(float2(%s.x - sk_FragCoord.x, "
                                                              "sk_FragCoord.y - %s.w), 0.0);",
                                      rectName, rectName);
-            fragBuilder->codeAppendf("half rightAlpha = clamp(%s.z - sk_FragCoord.x, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half rightAlpha = saturate(%s.z - sk_FragCoord.x);",
                                      rectName);
-            fragBuilder->codeAppendf("half topAlpha = clamp(sk_FragCoord.y - %s.y, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half topAlpha = saturate(sk_FragCoord.y - %s.y);",
                                      rectName);
             fragBuilder->codeAppendf("half alpha = topAlpha * rightAlpha * %s;",
                                      clampedCircleDistance.c_str());
@@ -241,7 +241,7 @@
             fragBuilder->codeAppendf("float2 dxy0 = %s.xy - sk_FragCoord.xy;", rectName);
             fragBuilder->codeAppendf("float dy1 = sk_FragCoord.y - %s.w;", rectName);
             fragBuilder->codeAppend("float2 dxy = max(float2(dxy0.x, max(dxy0.y, dy1)), 0.0);");
-            fragBuilder->codeAppendf("half rightAlpha = clamp(%s.z - sk_FragCoord.x, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half rightAlpha = saturate(%s.z - sk_FragCoord.x);",
                                      rectName);
             fragBuilder->codeAppendf("half alpha = rightAlpha * %s;",
                                      clampedCircleDistance.c_str());
@@ -250,7 +250,7 @@
             fragBuilder->codeAppendf("float2 dxy0 = %s.xy - sk_FragCoord.xy;", rectName);
             fragBuilder->codeAppendf("float dx1 = sk_FragCoord.x - %s.z;", rectName);
             fragBuilder->codeAppend("float2 dxy = max(float2(max(dxy0.x, dx1), dxy0.y), 0.0);");
-            fragBuilder->codeAppendf("half bottomAlpha = clamp(%s.w - sk_FragCoord.y, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half bottomAlpha = saturate(%s.w - sk_FragCoord.y);",
                                      rectName);
             fragBuilder->codeAppendf("half alpha = bottomAlpha * %s;",
                                      clampedCircleDistance.c_str());
@@ -259,7 +259,7 @@
             fragBuilder->codeAppendf("float dy0 = %s.y - sk_FragCoord.y;", rectName);
             fragBuilder->codeAppendf("float2 dxy1 = sk_FragCoord.xy - %s.zw;", rectName);
             fragBuilder->codeAppend("float2 dxy = max(float2(dxy1.x, max(dy0, dxy1.y)), 0.0);");
-            fragBuilder->codeAppendf("half leftAlpha = clamp(sk_FragCoord.x - %s.x, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half leftAlpha = saturate(sk_FragCoord.x - %s.x);",
                                      rectName);
             fragBuilder->codeAppendf("half alpha = leftAlpha * %s;",
                                      clampedCircleDistance.c_str());
@@ -268,7 +268,7 @@
             fragBuilder->codeAppendf("float dx0 = %s.x - sk_FragCoord.x;", rectName);
             fragBuilder->codeAppendf("float2 dxy1 = sk_FragCoord.xy - %s.zw;", rectName);
             fragBuilder->codeAppend("float2 dxy = max(float2(max(dx0, dxy1.x), dxy1.y), 0.0);");
-            fragBuilder->codeAppendf("half topAlpha = clamp(sk_FragCoord.y - %s.y, 0.0, 1.0);",
+            fragBuilder->codeAppendf("half topAlpha = saturate(sk_FragCoord.y - %s.y);",
                                      rectName);
             fragBuilder->codeAppendf("half alpha = topAlpha * %s;",
                                      clampedCircleDistance.c_str());
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index 9831a11..6505944 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -611,7 +611,7 @@
             fragBuilder->codeAppendf("edgeAlpha = (%s.x*%s.x - %s.y);", v.fsIn(), v.fsIn(),
                                      v.fsIn());
             fragBuilder->codeAppendf("edgeAlpha = "
-                                     "clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);}");
+                                     "saturate(0.5 - edgeAlpha / length(gF));}");
 
             fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
         }
diff --git a/src/gpu/ops/GrDashOp.cpp b/src/gpu/ops/GrDashOp.cpp
index 29d9dcb..5393fe0 100644
--- a/src/gpu/ops/GrDashOp.cpp
+++ b/src/gpu/ops/GrDashOp.cpp
@@ -951,7 +951,7 @@
     if (dce.aaMode() != AAMode::kNone) {
         fragBuilder->codeAppendf("half diff = dist - %s.x;", circleParams.fsIn());
         fragBuilder->codeAppend("diff = 1.0 - diff;");
-        fragBuilder->codeAppend("half alpha = clamp(diff, 0.0, 1.0);");
+        fragBuilder->codeAppend("half alpha = saturate(diff);");
     } else {
         fragBuilder->codeAppendf("half alpha = 1.0;");
         fragBuilder->codeAppendf("alpha *=  dist < %s.x + 0.5 ? 1.0 : 0.0;", circleParams.fsIn());
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index 622fb7f..436b67a 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -166,27 +166,27 @@
 
             fragBuilder->codeAppend("float d = length(circleEdge.xy);");
             fragBuilder->codeAppend("half distanceToOuterEdge = circleEdge.z * (1.0 - d);");
-            fragBuilder->codeAppend("half edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);");
+            fragBuilder->codeAppend("half edgeAlpha = saturate(distanceToOuterEdge);");
             if (cgp.fStroke) {
                 fragBuilder->codeAppend(
                         "half distanceToInnerEdge = circleEdge.z * (d - circleEdge.w);");
-                fragBuilder->codeAppend("half innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);");
+                fragBuilder->codeAppend("half innerAlpha = saturate(distanceToInnerEdge);");
                 fragBuilder->codeAppend("edgeAlpha *= innerAlpha;");
             }
 
             if (cgp.fInClipPlane.isInitialized()) {
                 fragBuilder->codeAppend(
-                        "half clip = clamp(circleEdge.z * dot(circleEdge.xy, clipPlane.xy) + "
-                        "clipPlane.z, 0.0, 1.0);");
+                        "half clip = saturate(circleEdge.z * dot(circleEdge.xy, clipPlane.xy) + "
+                        "clipPlane.z);");
                 if (cgp.fInIsectPlane.isInitialized()) {
                     fragBuilder->codeAppend(
-                            "clip *= clamp(circleEdge.z * dot(circleEdge.xy, isectPlane.xy) + "
-                            "isectPlane.z, 0.0, 1.0);");
+                            "clip *= saturate(circleEdge.z * dot(circleEdge.xy, isectPlane.xy) + "
+                            "isectPlane.z);");
                 }
                 if (cgp.fInUnionPlane.isInitialized()) {
                     fragBuilder->codeAppend(
-                            "clip = clamp(clip + clamp(circleEdge.z * dot(circleEdge.xy, "
-                            "unionPlane.xy) + unionPlane.z, 0.0, 1.0), 0.0, 1.0);");
+                            "clip = saturate(clip + saturate(circleEdge.z * dot(circleEdge.xy, "
+                            "unionPlane.xy) + unionPlane.z));");
                 }
                 fragBuilder->codeAppend("edgeAlpha *= clip;");
                 if (cgp.fInRoundCapCenters.isInitialized()) {
@@ -403,7 +403,7 @@
                     float linearDist;
                     angleToEdge = clamp(angleToEdge, -3.1415, 3.1415);
                     linearDist = diameter * sin(angleToEdge / 2);
-                    return clamp(linearDist + 0.5, 0, 1);
+                    return saturate(linearDist + 0.5);
             )",
                                       &fnName);
             fragBuilder->codeAppend(R"(
@@ -411,9 +411,9 @@
 
                     // Compute coverage from outer/inner edges of the stroke.
                     half distanceToOuterEdge = circleEdge.z - d;
-                    half edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
+                    half edgeAlpha = saturate(distanceToOuterEdge);
                     half distanceToInnerEdge = d - circleEdge.z * circleEdge.w;
-                    half innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);
+                    half innerAlpha = saturate(distanceToInnerEdge);
                     edgeAlpha *= innerAlpha;
 
                     half angleFromStart = atan(circleEdge.y, circleEdge.x) - dashParams.z;
@@ -597,7 +597,7 @@
             // avoid calling inversesqrt on zero.
             fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.0e-4);");
             fragBuilder->codeAppend("half invlen = inversesqrt(grad_dot);");
-            fragBuilder->codeAppend("half edgeAlpha = clamp(0.5-test*invlen, 0.0, 1.0);");
+            fragBuilder->codeAppend("half edgeAlpha = saturate(0.5-test*invlen);");
 
             // for inner curve
             if (egp.fStroke) {
@@ -606,7 +606,7 @@
                 fragBuilder->codeAppend("test = dot(offset, offset) - 1.0;");
                 fragBuilder->codeAppendf("grad = 2.0*offset*%s.zw;", ellipseRadii.fsIn());
                 fragBuilder->codeAppend("invlen = inversesqrt(dot(grad, grad));");
-                fragBuilder->codeAppend("edgeAlpha *= clamp(0.5+test*invlen, 0.0, 1.0);");
+                fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
             }
 
             fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
@@ -751,10 +751,10 @@
             fragBuilder->codeAppend("half invlen = inversesqrt(grad_dot);");
             if (DIEllipseStyle::kHairline == diegp.fStyle) {
                 // can probably do this with one step
-                fragBuilder->codeAppend("half edgeAlpha = clamp(1.0-test*invlen, 0.0, 1.0);");
-                fragBuilder->codeAppend("edgeAlpha *= clamp(1.0+test*invlen, 0.0, 1.0);");
+                fragBuilder->codeAppend("half edgeAlpha = saturate(1.0-test*invlen);");
+                fragBuilder->codeAppend("edgeAlpha *= saturate(1.0+test*invlen);");
             } else {
-                fragBuilder->codeAppend("half edgeAlpha = clamp(0.5-test*invlen, 0.0, 1.0);");
+                fragBuilder->codeAppend("half edgeAlpha = saturate(0.5-test*invlen);");
             }
 
             // for inner curve
@@ -768,7 +768,7 @@
                         "             2.0*%s.x*duvdy.x + 2.0*%s.y*duvdy.y);",
                         offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn());
                 fragBuilder->codeAppend("invlen = inversesqrt(dot(grad, grad));");
-                fragBuilder->codeAppend("edgeAlpha *= clamp(0.5+test*invlen, 0.0, 1.0);");
+                fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);");
             }
 
             fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index d9a4f71..77f194c 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -255,7 +255,7 @@
                     if (mulByFragCoordW) {
                         args.fFragBuilder->codeAppend("mindist *= sk_FragCoord.w;");
                     }
-                    args.fFragBuilder->codeAppendf("%s = float4(clamp(mindist, 0, 1));",
+                    args.fFragBuilder->codeAppendf("%s = float4(saturate(mindist));",
                                                    args.fOutputCoverage);
                 } else {
                     args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
diff --git a/src/shaders/SkLightingShader.cpp b/src/shaders/SkLightingShader.cpp
index 1c3ee54..dbc443e 100644
--- a/src/shaders/SkLightingShader.cpp
+++ b/src/shaders/SkLightingShader.cpp
@@ -180,7 +180,7 @@
                 fragBuilder->codeAppendf("for (int i = 0; i < %d; i++) {",
                                          lightingFP.fDirectionalLights.count());
                 // TODO: modulate the contribution from each light based on the shadow map
-                fragBuilder->codeAppendf("    float NdotL = clamp(dot(normal, %s[i]), 0.0, 1.0);",
+                fragBuilder->codeAppendf("    float NdotL = saturate(dot(normal, %s[i]));",
                                          lightDirsUniName);
                 fragBuilder->codeAppendf("    result += %s[i]*diffuseColor.rgb*NdotL;",
                                          lightColorsUniName);
diff --git a/src/shaders/SkPerlinNoiseShader.cpp b/src/shaders/SkPerlinNoiseShader.cpp
index 0fc11a3..e8b04d5 100644
--- a/src/shaders/SkPerlinNoiseShader.cpp
+++ b/src/shaders/SkPerlinNoiseShader.cpp
@@ -1067,7 +1067,7 @@
     }
 
     // Clamp values
-    fragBuilder->codeAppendf("\n\t\t%s = clamp(%s, 0.0, 1.0);", args.fOutputColor, args.fOutputColor);
+    fragBuilder->codeAppendf("\n\t\t%s = saturate(%s);", args.fOutputColor, args.fOutputColor);
 
     // Pre-multiply the result
     fragBuilder->codeAppendf("\n\t\t%s = half4(%s.rgb * %s.aaa, %s.a);\n",
@@ -1369,7 +1369,7 @@
     fragBuilder->codeAppendf("%s = half4(r, g, b, a);", args.fOutputColor);
 
     // Clamp values
-    fragBuilder->codeAppendf("%s = clamp(%s, 0.0, 1.0);", args.fOutputColor, args.fOutputColor);
+    fragBuilder->codeAppendf("%s = saturate(%s);", args.fOutputColor, args.fOutputColor);
 
     // Pre-multiply the result
     fragBuilder->codeAppendf("\n\t\t%s = half4(%s.rgb * %s.aaa, %s.a);\n",
diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp
index 536a7a8..073ac18 100644
--- a/src/shaders/gradients/SkGradientShader.cpp
+++ b/src/shaders/gradients/SkGradientShader.cpp
@@ -1025,7 +1025,7 @@
                     break;
                 default:
                     // regular [0, 1] clamping
-                    fragBuilder->codeAppendf("half tiled_t = clamp(%s, 0.0, 1.0);", t);
+                    fragBuilder->codeAppendf("half tiled_t = saturate(%s);", t);
             }
             break;
         case GrSamplerState::WrapMode::kRepeat:
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index 62cfbae..0e5aea3 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -15,6 +15,10 @@
 #include "ir/SkSLNop.h"
 #include "ir/SkSLVariableReference.h"
 
+#ifndef SKSL_STANDALONE
+#include "SkOnce.h"
+#endif
+
 namespace SkSL {
 
 void GLSLCodeGenerator::write(const char* s) {
@@ -446,143 +450,190 @@
     this->write(")");
 }
 
+std::unordered_map<StringFragment, GLSLCodeGenerator::FunctionClass>*
+                                                      GLSLCodeGenerator::fFunctionClasses = nullptr;
+
 void GLSLCodeGenerator::writeFunctionCall(const FunctionCall& c) {
-    if (!fProgram.fSettings.fCaps->canUseMinAndAbsTogether() && c.fFunction.fName == "min" &&
-        c.fFunction.fBuiltin) {
-        SkASSERT(c.fArguments.size() == 2);
-        if (is_abs(*c.fArguments[0])) {
-            this->writeMinAbsHack(*c.fArguments[0], *c.fArguments[1]);
-            return;
-        }
-        if (is_abs(*c.fArguments[1])) {
-            // note that this violates the GLSL left-to-right evaluation semantics. I doubt it will
-            // ever end up mattering, but it's worth calling out.
-            this->writeMinAbsHack(*c.fArguments[1], *c.fArguments[0]);
-            return;
-        }
+#ifdef SKSL_STANDALONE
+    if (!fFunctionClasses) {
+#else
+    static SkOnce once;
+    once([] {
+#endif
+        fFunctionClasses = new std::unordered_map<StringFragment, FunctionClass>();
+        (*fFunctionClasses)["atan"]        = FunctionClass::kAtan;
+        (*fFunctionClasses)["determinant"] = FunctionClass::kDeterminant;
+        (*fFunctionClasses)["dFdx"]        = FunctionClass::kDerivative;
+        (*fFunctionClasses)["dFdy"]        = FunctionClass::kDerivative;
+        (*fFunctionClasses)["fract"]       = FunctionClass::kFract;
+        (*fFunctionClasses)["inverse"]     = FunctionClass::kInverse;
+        (*fFunctionClasses)["inverseSqrt"] = FunctionClass::kInverseSqrt;
+        (*fFunctionClasses)["min"]         = FunctionClass::kMin;
+        (*fFunctionClasses)["saturate"]    = FunctionClass::kSaturate;
+        (*fFunctionClasses)["texture"]     = FunctionClass::kTexture;
+        (*fFunctionClasses)["transpose"]   = FunctionClass::kTranspose;
     }
-    if (!fProgram.fSettings.fCaps->canUseFractForNegativeValues() && c.fFunction.fName == "fract" &&
-        c.fFunction.fBuiltin) {
-        SkASSERT(c.fArguments.size() == 1);
-
-        this->write("(0.5 - sign(");
-        this->writeExpression(*c.fArguments[0], kSequence_Precedence);
-        this->write(") * (0.5 - fract(abs(");
-        this->writeExpression(*c.fArguments[0], kSequence_Precedence);
-        this->write("))))");
-
-        return;
-    }
-    if (fProgram.fSettings.fCaps->mustForceNegatedAtanParamToFloat() &&
-        c.fFunction.fName == "atan" &&
-        c.fFunction.fBuiltin && c.fArguments.size() == 2 &&
-        c.fArguments[1]->fKind == Expression::kPrefix_Kind) {
-        const PrefixExpression& p = (PrefixExpression&) *c.fArguments[1];
-        if (p.fOperator == Token::MINUS) {
-            this->write("atan(");
-            this->writeExpression(*c.fArguments[0], kSequence_Precedence);
-            this->write(", -1.0 * ");
-            this->writeExpression(*p.fOperand, kMultiplicative_Precedence);
-            this->write(")");
-            return;
-        }
-    }
-    if (c.fFunction.fBuiltin && c.fFunction.fName == "determinant" &&
-        fProgram.fSettings.fCaps->generation() < k150_GrGLSLGeneration) {
-        SkASSERT(c.fArguments.size() == 1);
-        this->writeDeterminantHack(*c.fArguments[0]);
-        return;
-    }
-    if (c.fFunction.fBuiltin && c.fFunction.fName == "inverse" &&
-        fProgram.fSettings.fCaps->generation() < k140_GrGLSLGeneration) {
-        SkASSERT(c.fArguments.size() == 1);
-        this->writeInverseHack(*c.fArguments[0]);
-        return;
-    }
-    if (c.fFunction.fBuiltin && c.fFunction.fName == "inverseSqrt" &&
-        fProgram.fSettings.fCaps->generation() < k130_GrGLSLGeneration) {
-        SkASSERT(c.fArguments.size() == 1);
-        this->writeInverseSqrtHack(*c.fArguments[0]);
-        return;
-    }
-    if (c.fFunction.fBuiltin && c.fFunction.fName == "transpose" &&
-        fProgram.fSettings.fCaps->generation() < k130_GrGLSLGeneration) {
-        SkASSERT(c.fArguments.size() == 1);
-        this->writeTransposeHack(*c.fArguments[0]);
-        return;
-    }
-    if (!fFoundDerivatives && (c.fFunction.fName == "dFdx" || c.fFunction.fName == "dFdy") &&
-        c.fFunction.fBuiltin && fProgram.fSettings.fCaps->shaderDerivativeExtensionString()) {
-        SkASSERT(fProgram.fSettings.fCaps->shaderDerivativeSupport());
-        this->writeExtension(fProgram.fSettings.fCaps->shaderDerivativeExtensionString());
-        fFoundDerivatives = true;
-    }
+#ifndef SKSL_STANDALONE
+    );
+#endif
+    const auto found = c.fFunction.fBuiltin ? fFunctionClasses->find(c.fFunction.fName) :
+                                              fFunctionClasses->end();
     bool isTextureFunctionWithBias = false;
-    if (c.fFunction.fName == "texture" && c.fFunction.fBuiltin) {
-        const char* dim = "";
-        bool proj = false;
-        switch (c.fArguments[0]->fType.dimensions()) {
-            case SpvDim1D:
-                dim = "1D";
-                isTextureFunctionWithBias = true;
-                if (c.fArguments[1]->fType == *fContext.fFloat_Type) {
-                    proj = false;
-                } else {
-                    SkASSERT(c.fArguments[1]->fType == *fContext.fFloat2_Type);
-                    proj = true;
+    bool nameWritten = false;
+    if (found != fFunctionClasses->end()) {
+        switch (found->second) {
+            case FunctionClass::kAtan:
+                if (fProgram.fSettings.fCaps->mustForceNegatedAtanParamToFloat() &&
+                    c.fArguments.size() == 2 &&
+                    c.fArguments[1]->fKind == Expression::kPrefix_Kind) {
+                    const PrefixExpression& p = (PrefixExpression&) *c.fArguments[1];
+                    if (p.fOperator == Token::MINUS) {
+                        this->write("atan(");
+                        this->writeExpression(*c.fArguments[0], kSequence_Precedence);
+                        this->write(", -1.0 * ");
+                        this->writeExpression(*p.fOperand, kMultiplicative_Precedence);
+                        this->write(")");
+                        return;
+                    }
                 }
                 break;
-            case SpvDim2D:
-                dim = "2D";
-                if (c.fArguments[0]->fType != *fContext.fSamplerExternalOES_Type) {
-                    isTextureFunctionWithBias = true;
-                }
-                if (c.fArguments[1]->fType == *fContext.fFloat2_Type) {
-                    proj = false;
-                } else {
-                    SkASSERT(c.fArguments[1]->fType == *fContext.fFloat3_Type);
-                    proj = true;
+            case FunctionClass::kDerivative:
+                if (!fFoundDerivatives &&
+                    fProgram.fSettings.fCaps->shaderDerivativeExtensionString()) {
+                    SkASSERT(fProgram.fSettings.fCaps->shaderDerivativeSupport());
+                    this->writeExtension(fProgram.fSettings.fCaps->shaderDerivativeExtensionString());
+                    fFoundDerivatives = true;
                 }
                 break;
-            case SpvDim3D:
-                dim = "3D";
-                isTextureFunctionWithBias = true;
-                if (c.fArguments[1]->fType == *fContext.fFloat3_Type) {
-                    proj = false;
-                } else {
-                    SkASSERT(c.fArguments[1]->fType == *fContext.fFloat4_Type);
-                    proj = true;
+            case FunctionClass::kDeterminant:
+                if (fProgram.fSettings.fCaps->generation() < k150_GrGLSLGeneration) {
+                    SkASSERT(c.fArguments.size() == 1);
+                    this->writeDeterminantHack(*c.fArguments[0]);
+                    return;
                 }
                 break;
-            case SpvDimCube:
-                dim = "Cube";
-                isTextureFunctionWithBias = true;
-                proj = false;
+            case FunctionClass::kFract:
+                if (!fProgram.fSettings.fCaps->canUseFractForNegativeValues()) {
+                    SkASSERT(c.fArguments.size() == 1);
+                    this->write("(0.5 - sign(");
+                    this->writeExpression(*c.fArguments[0], kSequence_Precedence);
+                    this->write(") * (0.5 - fract(abs(");
+                    this->writeExpression(*c.fArguments[0], kSequence_Precedence);
+                    this->write("))))");
+                    return;
+                }
                 break;
-            case SpvDimRect:
-                dim = "Rect";
-                proj = false;
+            case FunctionClass::kInverse:
+                if (fProgram.fSettings.fCaps->generation() < k140_GrGLSLGeneration) {
+                    SkASSERT(c.fArguments.size() == 1);
+                    this->writeInverseHack(*c.fArguments[0]);
+                    return;
+                }
                 break;
-            case SpvDimBuffer:
-                SkASSERT(false); // doesn't exist
-                dim = "Buffer";
-                proj = false;
+            case FunctionClass::kInverseSqrt:
+                if (fProgram.fSettings.fCaps->generation() < k130_GrGLSLGeneration) {
+                    SkASSERT(c.fArguments.size() == 1);
+                    this->writeInverseSqrtHack(*c.fArguments[0]);
+                    return;
+                }
                 break;
-            case SpvDimSubpassData:
-                SkASSERT(false); // doesn't exist
-                dim = "SubpassData";
-                proj = false;
+            case FunctionClass::kMin:
+                if (!fProgram.fSettings.fCaps->canUseMinAndAbsTogether()) {
+                    SkASSERT(c.fArguments.size() == 2);
+                    if (is_abs(*c.fArguments[0])) {
+                        this->writeMinAbsHack(*c.fArguments[0], *c.fArguments[1]);
+                        return;
+                    }
+                    if (is_abs(*c.fArguments[1])) {
+                        // note that this violates the GLSL left-to-right evaluation semantics.
+                        // I doubt it will ever end up mattering, but it's worth calling out.
+                        this->writeMinAbsHack(*c.fArguments[1], *c.fArguments[0]);
+                        return;
+                    }
+                }
+                break;
+            case FunctionClass::kSaturate:
+                SkASSERT(c.fArguments.size() == 1);
+                this->write("clamp(");
+                this->writeExpression(*c.fArguments[0], kSequence_Precedence);
+                this->write(", 0.0, 1.0)");
+                return;
+            case FunctionClass::kTexture: {
+                const char* dim = "";
+                bool proj = false;
+                switch (c.fArguments[0]->fType.dimensions()) {
+                    case SpvDim1D:
+                        dim = "1D";
+                        isTextureFunctionWithBias = true;
+                        if (c.fArguments[1]->fType == *fContext.fFloat_Type) {
+                            proj = false;
+                        } else {
+                            SkASSERT(c.fArguments[1]->fType == *fContext.fFloat2_Type);
+                            proj = true;
+                        }
+                        break;
+                    case SpvDim2D:
+                        dim = "2D";
+                        if (c.fArguments[0]->fType != *fContext.fSamplerExternalOES_Type) {
+                            isTextureFunctionWithBias = true;
+                        }
+                        if (c.fArguments[1]->fType == *fContext.fFloat2_Type) {
+                            proj = false;
+                        } else {
+                            SkASSERT(c.fArguments[1]->fType == *fContext.fFloat3_Type);
+                            proj = true;
+                        }
+                        break;
+                    case SpvDim3D:
+                        dim = "3D";
+                        isTextureFunctionWithBias = true;
+                        if (c.fArguments[1]->fType == *fContext.fFloat3_Type) {
+                            proj = false;
+                        } else {
+                            SkASSERT(c.fArguments[1]->fType == *fContext.fFloat4_Type);
+                            proj = true;
+                        }
+                        break;
+                    case SpvDimCube:
+                        dim = "Cube";
+                        isTextureFunctionWithBias = true;
+                        proj = false;
+                        break;
+                    case SpvDimRect:
+                        dim = "Rect";
+                        proj = false;
+                        break;
+                    case SpvDimBuffer:
+                        SkASSERT(false); // doesn't exist
+                        dim = "Buffer";
+                        proj = false;
+                        break;
+                    case SpvDimSubpassData:
+                        SkASSERT(false); // doesn't exist
+                        dim = "SubpassData";
+                        proj = false;
+                        break;
+                }
+                this->write("texture");
+                if (fProgram.fSettings.fCaps->generation() < k130_GrGLSLGeneration) {
+                    this->write(dim);
+                }
+                if (proj) {
+                    this->write("Proj");
+                }
+                nameWritten = true;
+                break;
+            }
+            case FunctionClass::kTranspose:
+                if (fProgram.fSettings.fCaps->generation() < k130_GrGLSLGeneration) {
+                    SkASSERT(c.fArguments.size() == 1);
+                    this->writeTransposeHack(*c.fArguments[0]);
+                    return;
+                }
                 break;
         }
-        this->write("texture");
-        if (fProgram.fSettings.fCaps->generation() < k130_GrGLSLGeneration) {
-            this->write(dim);
-        }
-        if (proj) {
-            this->write("Proj");
-        }
-
-    } else {
+    }
+    if (!nameWritten) {
         this->write(c.fFunction.fName);
     }
     this->write("(");
diff --git a/src/sksl/SkSLGLSLCodeGenerator.h b/src/sksl/SkSLGLSLCodeGenerator.h
index e19444d..6346ac6 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.h
+++ b/src/sksl/SkSLGLSLCodeGenerator.h
@@ -218,6 +218,22 @@
     bool fSetupFragPositionLocal = false;
     bool fSetupFragCoordWorkaround = false;
 
+    // We map function names to function class so we can quickly deal with function calls that need
+    // extra processing
+    enum class FunctionClass {
+        kAtan,
+        kDeterminant,
+        kDerivative,
+        kFract,
+        kInverse,
+        kInverseSqrt,
+        kMin,
+        kSaturate,
+        kTexture,
+        kTranspose
+    };
+    static std::unordered_map<StringFragment, FunctionClass>* fFunctionClasses;
+
     typedef CodeGenerator INHERITED;
 };
 
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
index 7f6b3a0..7986c40 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -69,6 +69,7 @@
     fIntrinsicMap[String("min")]           = SPECIAL(Min);
     fIntrinsicMap[String("max")]           = SPECIAL(Max);
     fIntrinsicMap[String("clamp")]         = SPECIAL(Clamp);
+    fIntrinsicMap[String("saturate")]      = SPECIAL(Saturate);
     fIntrinsicMap[String("dot")]           = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpDot,
                                                              SpvOpUndef, SpvOpUndef, SpvOpUndef);
     fIntrinsicMap[String("mix")]           = SPECIAL(Mix);
@@ -942,6 +943,17 @@
                                                SpvOpUndef, args, out);
             break;
         }
+        case kSaturate_SpecialIntrinsic: {
+            SkASSERT(c.fArguments.size() == 1);
+            std::vector<std::unique_ptr<Expression>> finalArgs;
+            finalArgs.push_back(c.fArguments[0]->clone());
+            finalArgs.emplace_back(new FloatLiteral(fContext, -1, 0));
+            finalArgs.emplace_back(new FloatLiteral(fContext, -1, 1));
+            std::vector<SpvId> spvArgs = this->vectorize(finalArgs, out);
+            this->writeGLSLExtendedInstruction(c.fType, result, GLSLstd450FClamp, GLSLstd450SClamp,
+                                               GLSLstd450UClamp, spvArgs, out);
+            break;
+        }
     }
     return result;
 }
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.h b/src/sksl/SkSLSPIRVCodeGenerator.h
index 7010c84..8b88c75 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.h
+++ b/src/sksl/SkSLSPIRVCodeGenerator.h
@@ -95,6 +95,7 @@
         kMin_SpecialIntrinsic,
         kMix_SpecialIntrinsic,
         kMod_SpecialIntrinsic,
+        kSaturate_SpecialIntrinsic,
         kSubpassLoad_SpecialIntrinsic,
         kTexture_SpecialIntrinsic,
     };
diff --git a/src/sksl/sksl.inc b/src/sksl/sksl.inc
index 3d7b53a..88d6be4 100644
--- a/src/sksl/sksl.inc
+++ b/src/sksl/sksl.inc
@@ -73,6 +73,7 @@
 $genIType clamp($genIType x, int minVal, int maxVal);
 //$genUType clamp($genUType x, $genUType minVal, $genUType maxVal);
 //$genUType clamp($genUType x, uint minVal, uint maxVal);
+$genType saturate($genType x);
 $genType mix($genType x, $genType y, $genType a);
 $genType mix($genType x, $genType y, float a);
 //$genDType mix($genDType x, $genDType y, $genDType a);