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);