Add specialized code for hard stop gradients on GPU
authorfmenozzi <fmenozzi@google.com>
Mon, 15 Aug 2016 14:03:47 +0000 (07:03 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 15 Aug 2016 14:03:47 +0000 (07:03 -0700)
BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2223203003

Committed: https://skia.googlesource.com/skia/+/0c63006b88a16e3418d92852a62771615799839d
Committed: https://skia.googlesource.com/skia/+/99818d69372d29a139935cfe5c379e491432931b
Committed: https://skia.googlesource.com/skia/+/2a4959181fc98d5d7ee862e7cd1c7993b3343be6
Review-Url: https://codereview.chromium.org/2223203003

src/effects/gradients/SkGradientShader.cpp
src/effects/gradients/SkGradientShaderPriv.h
src/effects/gradients/SkSweepGradient.cpp

index 820c5b2..457b89d 100644 (file)
@@ -228,23 +228,6 @@ void SkGradientShaderBase::flatten(SkWriteBuffer& buffer) const {
     desc.flatten(buffer);
 }
 
-SkGradientShaderBase::GpuColorType SkGradientShaderBase::getGpuColorType(SkColor colors[3]) const {
-    if (fColorCount <= 3) {
-        memcpy(colors, fOrigColors, fColorCount * sizeof(SkColor));
-    }
-
-    if (SkShader::kClamp_TileMode == fTileMode) {
-        if (2 == fColorCount) {
-            return kTwo_GpuColorType;
-        } else if (3 == fColorCount &&
-                   (SkScalarAbs(
-                    SkFixedToScalar(fRecs[1].fPos) - SK_ScalarHalf) < SK_Scalar1 / 1000)) {
-            return kThree_GpuColorType;
-        }
-    }
-    return kTexture_GpuColorType;
-}
-
 void SkGradientShaderBase::FlipGradientColors(SkColor* colorDst, Rec* recDst,
                                               SkColor* colorSrc, Rec* recSrc,
                                               int count) {
@@ -911,113 +894,190 @@ SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
 #include "glsl/GrGLSLUniformHandler.h"
 #include "SkGr.h"
 
-GrGradientEffect::GLSLProcessor::GLSLProcessor()
-    : fCachedYCoord(SK_ScalarMax) {
+static inline bool close_to_one_half(const SkFixed& val) {
+    return SkScalarNearlyEqual(SkFixedToScalar(val), SK_ScalarHalf);
+}
+
+static inline int color_type_to_color_count(GrGradientEffect::ColorType colorType) {
+    switch (colorType) {
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+        case GrGradientEffect::kHardStopCentered_ColorType:
+            return 4;
+        case GrGradientEffect::kHardStopLeftEdged_ColorType:
+        case GrGradientEffect::kHardStopRightEdged_ColorType:
+            return 3;
+#endif
+        case GrGradientEffect::kTwo_ColorType:
+            return 2;
+        case GrGradientEffect::kThree_ColorType:
+            return 3;
+        case GrGradientEffect::kTexture_ColorType:
+            return 0;
+    }
+
+    SkDEBUGFAIL("Unhandled ColorType in color_type_to_color_count()");
+    return -1;
+}
+
+GrGradientEffect::ColorType GrGradientEffect::determineColorType(
+        const SkGradientShaderBase& shader) {
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+    if (shader.fOrigPos) {
+        if (4 == shader.fColorCount) {
+            if (SkScalarNearlyEqual(shader.fOrigPos[0], 0.0f) &&
+                SkScalarNearlyEqual(shader.fOrigPos[1], 0.5f) &&
+                SkScalarNearlyEqual(shader.fOrigPos[2], 0.5f) &&
+                SkScalarNearlyEqual(shader.fOrigPos[3], 1.0f)) {
+
+                return kHardStopCentered_ColorType;
+            }
+        } else if (3 == shader.fColorCount) {
+            if (SkScalarNearlyEqual(shader.fOrigPos[0], 0.0f) &&
+                SkScalarNearlyEqual(shader.fOrigPos[1], 0.0f) &&
+                SkScalarNearlyEqual(shader.fOrigPos[2], 1.0f)) {
+
+                return kHardStopLeftEdged_ColorType;
+            } else if (SkScalarNearlyEqual(shader.fOrigPos[0], 0.0f) &&
+                       SkScalarNearlyEqual(shader.fOrigPos[1], 1.0f) &&
+                       SkScalarNearlyEqual(shader.fOrigPos[2], 1.0f)) {
+                
+                return kHardStopRightEdged_ColorType;
+            }
+        }
+    }
+#endif
+
+    if (SkShader::kClamp_TileMode == shader.getTileMode()) {
+        if (2 == shader.fColorCount) {
+            return kTwo_ColorType;
+        } else if (3 == shader.fColorCount &&
+                   close_to_one_half(shader.getRecs()[1].fPos)) {
+            return kThree_ColorType;
+        }
+    }
+
+    return kTexture_ColorType;
 }
 
 void GrGradientEffect::GLSLProcessor::emitUniforms(GrGLSLUniformHandler* uniformHandler,
                                                    const GrGradientEffect& ge) {
-
-    if (SkGradientShaderBase::kTwo_GpuColorType == ge.getColorType()) { // 2 Color case
-        fColorStartUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                    kVec4f_GrSLType, kDefault_GrSLPrecision,
-                                                    "GradientStartColor");
-        fColorEndUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                  kVec4f_GrSLType, kDefault_GrSLPrecision,
-                                                  "GradientEndColor");
-
-    } else if (SkGradientShaderBase::kThree_GpuColorType == ge.getColorType()) { // 3 Color Case
-        fColorStartUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                    kVec4f_GrSLType,  kDefault_GrSLPrecision,
-                                                    "GradientStartColor");
-        fColorMidUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                  kVec4f_GrSLType, kDefault_GrSLPrecision,
-                                                  "GradientMidColor");
-        fColorEndUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                  kVec4f_GrSLType, kDefault_GrSLPrecision,
-                                                  "GradientEndColor");
-
-    } else { // if not a fast case
+    if (int colorCount = color_type_to_color_count(ge.getColorType())) {
+        fColorsUni = uniformHandler->addUniformArray(kFragment_GrShaderFlag,
+                                                     kVec4f_GrSLType,
+                                                     kDefault_GrSLPrecision,
+                                                     "Colors",
+                                                     colorCount);
+    } else {
         fFSYUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
                                              kFloat_GrSLType, kDefault_GrSLPrecision,
                                              "GradientYCoordFS");
     }
 }
 
-static inline void set_color_uni(const GrGLSLProgramDataManager& pdman,
-                                 const GrGLSLProgramDataManager::UniformHandle uni,
-                                 const SkColor* color) {
-       pdman.set4f(uni,
-                   SkColorGetR(*color) / 255.f,
-                   SkColorGetG(*color) / 255.f,
-                   SkColorGetB(*color) / 255.f,
-                   SkColorGetA(*color) / 255.f);
-}
+static inline void set_after_interp_color_uni_array(const GrGLSLProgramDataManager& pdman,
+                                       const GrGLSLProgramDataManager::UniformHandle uni,
+                                       const SkTDArray<SkColor>& colors) {
+    int count = colors.count();
+    constexpr int kSmallCount = 10;
+
+    SkAutoSTArray<4*kSmallCount, float> vals(4*count);
+
+    for (int i = 0; i < colors.count(); i++) {
+        // RGBA
+        vals[4*i + 0] = SkColorGetR(colors[i]) / 255.f;
+        vals[4*i + 1] = SkColorGetG(colors[i]) / 255.f;
+        vals[4*i + 2] = SkColorGetB(colors[i]) / 255.f;
+        vals[4*i + 3] = SkColorGetA(colors[i]) / 255.f;
+    }
 
-static inline void set_mul_color_uni(const GrGLSLProgramDataManager& pdman,
-                                     const GrGLSLProgramDataManager::UniformHandle uni,
-                                     const SkColor* color){
-       float a = SkColorGetA(*color) / 255.f;
-       float aDiv255 = a / 255.f;
-       pdman.set4f(uni,
-                   SkColorGetR(*color) * aDiv255,
-                   SkColorGetG(*color) * aDiv255,
-                   SkColorGetB(*color) * aDiv255,
-                   a);
+    pdman.set4fv(uni, colors.count(), vals.get());
 }
 
-void GrGradientEffect::GLSLProcessor::onSetData(const GrGLSLProgramDataManager& pdman,
-                                                const GrProcessor& processor) {
+static inline void set_before_interp_color_uni_array(const GrGLSLProgramDataManager& pdman,
+                                              const GrGLSLProgramDataManager::UniformHandle uni,
+                                              const SkTDArray<SkColor>& colors) {
+    int count = colors.count();
+    constexpr int kSmallCount = 10;
 
-    const GrGradientEffect& e = processor.cast<GrGradientEffect>();
+    SkAutoSTArray<4*kSmallCount, float> vals(4*count);
 
+    for (int i = 0; i < count; i++) {
+        float a = SkColorGetA(colors[i]) / 255.f;
+        float aDiv255 = a / 255.f;
 
-    if (SkGradientShaderBase::kTwo_GpuColorType == e.getColorType()){
+        // RGBA
+        vals[4*i + 0] = SkColorGetR(colors[i]) * aDiv255;
+        vals[4*i + 1] = SkColorGetG(colors[i]) * aDiv255;
+        vals[4*i + 2] = SkColorGetB(colors[i]) * aDiv255;
+        vals[4*i + 3] = a;
+    }
 
-        if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) {
-            set_mul_color_uni(pdman, fColorStartUni, e.getColors(0));
-            set_mul_color_uni(pdman, fColorEndUni,   e.getColors(1));
-        } else {
-            set_color_uni(pdman, fColorStartUni, e.getColors(0));
-            set_color_uni(pdman, fColorEndUni,   e.getColors(1));
-        }
+    pdman.set4fv(uni, count, vals.get());
+}
 
-    } else if (SkGradientShaderBase::kThree_GpuColorType == e.getColorType()){
+void GrGradientEffect::GLSLProcessor::onSetData(const GrGLSLProgramDataManager& pdman,
+                                                const GrProcessor& processor) {
+    const GrGradientEffect& e = processor.cast<GrGradientEffect>();
 
-        if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) {
-            set_mul_color_uni(pdman, fColorStartUni, e.getColors(0));
-            set_mul_color_uni(pdman, fColorMidUni,   e.getColors(1));
-            set_mul_color_uni(pdman, fColorEndUni,   e.getColors(2));
-        } else {
-            set_color_uni(pdman, fColorStartUni, e.getColors(0));
-            set_color_uni(pdman, fColorMidUni,   e.getColors(1));
-            set_color_uni(pdman, fColorEndUni,   e.getColors(2));
+    switch (e.getColorType()) {
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+        case GrGradientEffect::kHardStopCentered_ColorType:
+        case GrGradientEffect::kHardStopLeftEdged_ColorType:
+        case GrGradientEffect::kHardStopRightEdged_ColorType:
+#endif
+        case GrGradientEffect::kTwo_ColorType:
+        case GrGradientEffect::kThree_ColorType: {
+            if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) {
+                set_before_interp_color_uni_array(pdman, fColorsUni, e.fColors);
+            } else {
+                set_after_interp_color_uni_array(pdman, fColorsUni, e.fColors);
+            }
+
+            break;
         }
-    } else {
 
-        SkScalar yCoord = e.getYCoord();
-        if (yCoord != fCachedYCoord) {
-            pdman.set1f(fFSYUni, yCoord);
-            fCachedYCoord = yCoord;
+        case GrGradientEffect::kTexture_ColorType: {
+            SkScalar yCoord = e.getYCoord();
+            if (yCoord != fCachedYCoord) {
+                pdman.set1f(fFSYUni, yCoord);
+                fCachedYCoord = yCoord;
+            }
+            break;
         }
     }
 }
 
-
 uint32_t GrGradientEffect::GLSLProcessor::GenBaseGradientKey(const GrProcessor& processor) {
     const GrGradientEffect& e = processor.cast<GrGradientEffect>();
 
     uint32_t key = 0;
 
-    if (SkGradientShaderBase::kTwo_GpuColorType == e.getColorType()) {
+    if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) {
+        key |= kPremulBeforeInterpKey;
+    }
+
+    if (GrGradientEffect::kTwo_ColorType == e.getColorType()) {
         key |= kTwoColorKey;
-    } else if (SkGradientShaderBase::kThree_GpuColorType == e.getColorType()) {
+    } else if (GrGradientEffect::kThree_ColorType == e.getColorType()) {
         key |= kThreeColorKey;
     }
-
-    if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) {
-        key |= kPremulBeforeInterpKey;
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+    else if (GrGradientEffect::kHardStopCentered_ColorType == e.getColorType()) {
+        key |= kHardStopCenteredKey;
+    } else if (GrGradientEffect::kHardStopLeftEdged_ColorType == e.getColorType()) {
+        key |= kHardStopZeroZeroOneKey;
+    } else if (GrGradientEffect::kHardStopRightEdged_ColorType == e.getColorType()) {
+        key |= kHardStopZeroOneOneKey;
+    }
+   
+    if (SkShader::TileMode::kClamp_TileMode == e.fTileMode) {
+        key |= kClampTileMode;
+    } else if (SkShader::TileMode::kRepeat_TileMode == e.fTileMode) {
+        key |= kRepeatTileMode;
+    } else {
+        key |= kMirrorTileMode;
     }
+#endif
 
     return key;
 }
@@ -1030,56 +1090,183 @@ void GrGradientEffect::GLSLProcessor::emitColor(GrGLSLFPFragmentBuilder* fragBui
                                                 const char* outputColor,
                                                 const char* inputColor,
                                                 const SamplerHandle* texSamplers) {
-    if (SkGradientShaderBase::kTwo_GpuColorType == ge.getColorType()){
-        fragBuilder->codeAppendf("\tvec4 colorTemp = mix(%s, %s, clamp(%s, 0.0, 1.0));\n",
-                                 uniformHandler->getUniformVariable(fColorStartUni).c_str(),
-                                 uniformHandler->getUniformVariable(fColorEndUni).c_str(),
-                                 gradientTValue);
-        // Note that we could skip this step if both colors are known to be opaque. Two
-        // considerations:
-        // The gradient SkShader reporting opaque is more restrictive than necessary in the two pt
-        // case. Make sure the key reflects this optimization (and note that it can use the same
-        // shader as thekBeforeIterp case). This same optimization applies to the 3 color case
-        // below.
-        if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) {
-            fragBuilder->codeAppend("\tcolorTemp.rgb *= colorTemp.a;\n");
+    switch (ge.getColorType()) {
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+        case kHardStopCentered_ColorType: {
+            const char* t      = gradientTValue;
+            const char* colors = uniformHandler->getUniformCStr(fColorsUni);
+
+            fragBuilder->codeAppendf("float clamp_t = clamp(%s, 0.0, 1.0);", t);
+
+            // Account for tile mode
+            if (SkShader::kRepeat_TileMode == ge.fTileMode) {
+                fragBuilder->codeAppendf("clamp_t = fract(%s);", t);
+            } else if (SkShader::kMirror_TileMode == ge.fTileMode) {
+                fragBuilder->codeAppendf("if (%s < 0.0 || %s > 1.0) {", t, t);
+                fragBuilder->codeAppendf("    if (mod(floor(%s), 2.0) == 0.0) {", t);
+                fragBuilder->codeAppendf("        clamp_t = fract(%s);", t);
+                fragBuilder->codeAppendf("    } else {");
+                fragBuilder->codeAppendf("        clamp_t = 1.0 - fract(%s);", t);
+                fragBuilder->codeAppendf("    }");
+                fragBuilder->codeAppendf("}");
+            }
+
+            // Calculate color
+            fragBuilder->codeAppendf("float relative_t = fract(2.0 * clamp_t);");
+            if (SkShader::kClamp_TileMode == ge.fTileMode) {
+                fragBuilder->codeAppendf("relative_t += step(1.0, %s);", t);
+            }
+            fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[0], %s[1], relative_t);", colors,
+                                     colors);
+            fragBuilder->codeAppendf("if (clamp_t >= 0.5) {");
+            fragBuilder->codeAppendf("    colorTemp = mix(%s[2], %s[3], relative_t);", colors,
+                                     colors);
+            fragBuilder->codeAppendf("}");
+
+            if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) {
+                fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;");
+            }
+            fragBuilder->codeAppendf("%s = %s;", outputColor,
+                                     (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str());
+
+            break;
         }
 
-        fragBuilder->codeAppendf("\t%s = %s;\n", outputColor,
-                                 (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str());
-    } else if (SkGradientShaderBase::kThree_GpuColorType == ge.getColorType()) {
-        fragBuilder->codeAppendf("\tfloat oneMinus2t = 1.0 - (2.0 * (%s));\n",
-                                 gradientTValue);
-        fragBuilder->codeAppendf("\tvec4 colorTemp = clamp(oneMinus2t, 0.0, 1.0) * %s;\n",
-                                 uniformHandler->getUniformVariable(fColorStartUni).c_str());
-        if (!glslCaps->canUseMinAndAbsTogether()) {
-            // The Tegra3 compiler will sometimes never return if we have
-            // min(abs(oneMinus2t), 1.0), or do the abs first in a separate expression.
-            fragBuilder->codeAppend("\tfloat minAbs = abs(oneMinus2t);\n");
-            fragBuilder->codeAppend("\tminAbs = minAbs > 1.0 ? 1.0 : minAbs;\n");
-            fragBuilder->codeAppendf("\tcolorTemp += (1.0 - minAbs) * %s;\n",
-                                     uniformHandler->getUniformVariable(fColorMidUni).c_str());
-        } else {
-            fragBuilder->codeAppendf("\tcolorTemp += (1.0 - min(abs(oneMinus2t), 1.0)) * %s;\n",
-                                     uniformHandler->getUniformVariable(fColorMidUni).c_str());
+        case kHardStopLeftEdged_ColorType: {
+            const char* t      = gradientTValue;
+            const char* colors = uniformHandler->getUniformCStr(fColorsUni);
+
+            fragBuilder->codeAppendf("float clamp_t = clamp(%s, 0.0, 1.0);", t);
+
+            // Account for tile mode
+            if (SkShader::kRepeat_TileMode == ge.fTileMode) {
+                fragBuilder->codeAppendf("clamp_t = fract(%s);", t);
+            } else if (SkShader::kMirror_TileMode == ge.fTileMode) {
+                fragBuilder->codeAppendf("if (%s < 0.0 || %s > 1.0) {", t, t);
+                fragBuilder->codeAppendf("    if (mod(floor(%s), 2.0) == 0.0) {", t);
+                fragBuilder->codeAppendf("        clamp_t = fract(%s);", t);
+                fragBuilder->codeAppendf("    } else {");
+                fragBuilder->codeAppendf("        clamp_t = 1.0 - fract(%s);", t);
+                fragBuilder->codeAppendf("    }");
+                fragBuilder->codeAppendf("}");
+            }
+
+            fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[1], %s[2], clamp_t);", colors,
+                                     colors);
+            if (SkShader::kClamp_TileMode == ge.fTileMode) {
+                fragBuilder->codeAppendf("if (%s < 0.0) {", t);
+                fragBuilder->codeAppendf("    colorTemp = %s[0];", colors);
+                fragBuilder->codeAppendf("}");
+            }
+
+            if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) {
+                fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;");
+            }
+            fragBuilder->codeAppendf("%s = %s;", outputColor,
+                                     (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str());
+
+            break;
         }
-        fragBuilder->codeAppendf("\tcolorTemp += clamp(-oneMinus2t, 0.0, 1.0) * %s;\n",
-                                 uniformHandler->getUniformVariable(fColorEndUni).c_str());
-        if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) {
-            fragBuilder->codeAppend("\tcolorTemp.rgb *= colorTemp.a;\n");
+
+        case kHardStopRightEdged_ColorType: {
+            const char* t      = gradientTValue;
+            const char* colors = uniformHandler->getUniformCStr(fColorsUni);
+
+            fragBuilder->codeAppendf("float clamp_t = clamp(%s, 0.0, 1.0);", t);
+
+            // Account for tile mode
+            if (SkShader::kRepeat_TileMode == ge.fTileMode) {
+                fragBuilder->codeAppendf("clamp_t = fract(%s);", t);
+            } else if (SkShader::kMirror_TileMode == ge.fTileMode) {
+                fragBuilder->codeAppendf("if (%s < 0.0 || %s > 1.0) {", t, t);
+                fragBuilder->codeAppendf("    if (mod(floor(%s), 2.0) == 0.0) {", t);
+                fragBuilder->codeAppendf("        clamp_t = fract(%s);", t);
+                fragBuilder->codeAppendf("    } else {");
+                fragBuilder->codeAppendf("        clamp_t = 1.0 - fract(%s);", t);
+                fragBuilder->codeAppendf("    }");
+                fragBuilder->codeAppendf("}");
+            }
+
+            fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[0], %s[1], clamp_t);", colors,
+                                     colors);
+            if (SkShader::kClamp_TileMode == ge.fTileMode) {
+                fragBuilder->codeAppendf("if (%s > 1.0) {", t);
+                fragBuilder->codeAppendf("    colorTemp = %s[2];", colors);
+                fragBuilder->codeAppendf("}");
+            }
+
+            if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) {
+                fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;");
+            }
+            fragBuilder->codeAppendf("%s = %s;", outputColor,
+                                     (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str());
+
+            break;
         }
+#endif
 
-        fragBuilder->codeAppendf("\t%s = %s;\n", outputColor,
-                                 (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str());
-    } else {
-        fragBuilder->codeAppendf("\tvec2 coord = vec2(%s, %s);\n",
-                                 gradientTValue,
-                                 uniformHandler->getUniformVariable(fFSYUni).c_str());
-        fragBuilder->codeAppendf("\t%s = ", outputColor);
-        fragBuilder->appendTextureLookupAndModulate(inputColor,
-                                                    texSamplers[0],
-                                                    "coord");
-        fragBuilder->codeAppend(";\n");
+        case kTwo_ColorType: {
+            const char* t      = gradientTValue;
+            const char* colors = uniformHandler->getUniformCStr(fColorsUni);
+
+            fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[0], %s[1], clamp(%s, 0.0, 1.0));",
+                                     colors, colors, t);
+
+            // We could skip this step if both colors are known to be opaque. Two
+            // considerations:
+            // The gradient SkShader reporting opaque is more restrictive than necessary in the two
+            // pt case. Make sure the key reflects this optimization (and note that it can use the
+            // same shader as thekBeforeIterp case). This same optimization applies to the 3 color
+            // case below.
+            if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) {
+                fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;");
+            }
+
+            fragBuilder->codeAppendf("%s = %s;", outputColor,
+                                     (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str());
+
+            break;
+        }
+
+        case kThree_ColorType: {
+            const char* t      = gradientTValue;
+            const char* colors = uniformHandler->getUniformCStr(fColorsUni);
+
+            fragBuilder->codeAppendf("float oneMinus2t = 1.0 - (2.0 * %s);", t);
+            fragBuilder->codeAppendf("vec4 colorTemp = clamp(oneMinus2t, 0.0, 1.0) * %s[0];",
+                                     colors);
+            if (!glslCaps->canUseMinAndAbsTogether()) {
+                // The Tegra3 compiler will sometimes never return if we have
+                // min(abs(oneMinus2t), 1.0), or do the abs first in a separate expression.
+                fragBuilder->codeAppendf("float minAbs = abs(oneMinus2t);");
+                fragBuilder->codeAppendf("minAbs = minAbs > 1.0 ? 1.0 : minAbs;");
+                fragBuilder->codeAppendf("colorTemp += (1.0 - minAbs) * %s[1];", colors);
+            } else {
+                fragBuilder->codeAppendf("colorTemp += (1.0 - min(abs(oneMinus2t), 1.0)) * %s[1];",
+                                         colors);
+            }
+            fragBuilder->codeAppendf("colorTemp += clamp(-oneMinus2t, 0.0, 1.0) * %s[2];", colors);
+
+            if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) {
+                fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;");
+            }
+
+            fragBuilder->codeAppendf("%s = %s;", outputColor,
+                                     (GrGLSLExpr4(inputColor) * GrGLSLExpr4("colorTemp")).c_str());
+
+            break;
+        }
+
+        case kTexture_ColorType: {
+            const char* fsyuni = uniformHandler->getUniformCStr(fFSYUni);
+
+            fragBuilder->codeAppendf("vec2 coord = vec2(%s, %s);", gradientTValue, fsyuni);
+            fragBuilder->codeAppendf("%s = ", outputColor);
+            fragBuilder->appendTextureLookupAndModulate(inputColor, texSamplers[0], "coord");
+            fragBuilder->codeAppend(";");
+
+            break;
+        }
     }
 }
 
@@ -1092,56 +1279,89 @@ GrGradientEffect::GrGradientEffect(GrContext* ctx,
 
     fIsOpaque = shader.isOpaque();
 
-    fColorType = shader.getGpuColorType(&fColors[0]);
+    fColorType = this->determineColorType(shader);
 
-    // The two and three color specializations do not currently support tiling.
-    if (SkGradientShaderBase::kTwo_GpuColorType == fColorType ||
-        SkGradientShaderBase::kThree_GpuColorType == fColorType) {
-        fRow = -1;
+    if (kTexture_ColorType != fColorType) {
+        if (shader.fOrigColors) {
+            fColors = SkTDArray<SkColor>(shader.fOrigColors, shader.fColorCount);
+        }
 
-        if (SkGradientShader::kInterpolateColorsInPremul_Flag & shader.getGradFlags()) {
-            fPremulType = kBeforeInterp_PremulType;
-        } else {
-            fPremulType = kAfterInterp_PremulType;
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+        if (shader.fOrigPos) {
+            fPositions = SkTDArray<SkScalar>(shader.fOrigPos, shader.fColorCount);
         }
-        fCoordTransform.reset(kCoordSet, matrix);
-    } else {
-        // doesn't matter how this is set, just be consistent because it is part of the effect key.
-        fPremulType = kBeforeInterp_PremulType;
-        SkBitmap bitmap;
-        shader.getGradientTableBitmap(&bitmap);
-
-        GrTextureStripAtlas::Desc desc;
-        desc.fWidth  = bitmap.width();
-        desc.fHeight = 32;
-        desc.fRowHeight = bitmap.height();
-        desc.fContext = ctx;
-        desc.fConfig = SkImageInfo2GrPixelConfig(bitmap.info(), *ctx->caps());
-        fAtlas = GrTextureStripAtlas::GetAtlas(desc);
-        SkASSERT(fAtlas);
-
-        // We always filter the gradient table. Each table is one row of a texture, always y-clamp.
-        GrTextureParams params;
-        params.setFilterMode(GrTextureParams::kBilerp_FilterMode);
-        params.setTileModeX(tileMode);
-
-        fRow = fAtlas->lockRow(bitmap);
-        if (-1 != fRow) {
-            fYCoord = fAtlas->getYOffset(fRow) + SK_ScalarHalf * fAtlas->getNormalizedTexelHeight();
-            fCoordTransform.reset(kCoordSet, matrix, fAtlas->getTexture(), params.filterMode());
-            fTextureAccess.reset(fAtlas->getTexture(), params);
-        } else {
-            SkAutoTUnref<GrTexture> texture(
-                GrRefCachedBitmapTexture(ctx, bitmap, params, SkSourceGammaTreatment::kRespect));
-            if (!texture) {
-                return;
+#endif
+    }
+
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+    fTileMode = tileMode;
+#endif
+
+    switch (fColorType) {
+        // The two and three color specializations do not currently support tiling.
+        case kTwo_ColorType:
+        case kThree_ColorType:
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+        case kHardStopLeftEdged_ColorType:
+        case kHardStopRightEdged_ColorType:
+        case kHardStopCentered_ColorType:
+#endif
+            fRow = -1;
+
+            if (SkGradientShader::kInterpolateColorsInPremul_Flag & shader.getGradFlags()) {
+                fPremulType = kBeforeInterp_PremulType;
+            } else {
+                fPremulType = kAfterInterp_PremulType;
             }
-            fCoordTransform.reset(kCoordSet, matrix, texture, params.filterMode());
-            fTextureAccess.reset(texture, params);
-            fYCoord = SK_ScalarHalf;
-        }
-        this->addTextureAccess(&fTextureAccess);
+
+            fCoordTransform.reset(kCoordSet, matrix);
+
+            break;
+        case kTexture_ColorType:
+            // doesn't matter how this is set, just be consistent because it is part of the
+            // effect key.
+            fPremulType = kBeforeInterp_PremulType;
+
+            SkBitmap bitmap;
+            shader.getGradientTableBitmap(&bitmap);
+
+            GrTextureStripAtlas::Desc desc;
+            desc.fWidth  = bitmap.width();
+            desc.fHeight = 32;
+            desc.fRowHeight = bitmap.height();
+            desc.fContext = ctx;
+            desc.fConfig = SkImageInfo2GrPixelConfig(bitmap.info(), *ctx->caps());
+            fAtlas = GrTextureStripAtlas::GetAtlas(desc);
+            SkASSERT(fAtlas);
+
+            // We always filter the gradient table. Each table is one row of a texture, always
+            // y-clamp.
+            GrTextureParams params;
+            params.setFilterMode(GrTextureParams::kBilerp_FilterMode);
+            params.setTileModeX(tileMode);
+
+            fRow = fAtlas->lockRow(bitmap);
+            if (-1 != fRow) {
+                fYCoord = fAtlas->getYOffset(fRow)+SK_ScalarHalf*fAtlas->getNormalizedTexelHeight();
+                fCoordTransform.reset(kCoordSet, matrix, fAtlas->getTexture(), params.filterMode());
+                fTextureAccess.reset(fAtlas->getTexture(), params);
+            } else {
+                SkAutoTUnref<GrTexture> texture(
+                    GrRefCachedBitmapTexture(ctx, bitmap, params,
+                                             SkSourceGammaTreatment::kRespect));
+                if (!texture) {
+                    return;
+                }
+                fCoordTransform.reset(kCoordSet, matrix, texture, params.filterMode());
+                fTextureAccess.reset(texture, params);
+                fYCoord = SK_ScalarHalf;
+            }
+
+            this->addTextureAccess(&fTextureAccess);
+
+            break;
     }
+
     this->addCoordTransform(&fCoordTransform);
 }
 
@@ -1152,30 +1372,27 @@ GrGradientEffect::~GrGradientEffect() {
 }
 
 bool GrGradientEffect::onIsEqual(const GrFragmentProcessor& processor) const {
-    const GrGradientEffect& s = processor.cast<GrGradientEffect>();
-
-    if (this->fColorType == s.getColorType()){
+    const GrGradientEffect& ge = processor.cast<GrGradientEffect>();
 
-        if (SkGradientShaderBase::kTwo_GpuColorType == fColorType) {
-            if (this->getPremulType() != s.getPremulType() ||
-                *this->getColors(0) != *s.getColors(0) ||
-                *this->getColors(1) != *s.getColors(1)) {
-                return false;
-            }
-        } else if (SkGradientShaderBase::kThree_GpuColorType == fColorType) {
-            if (this->getPremulType() != s.getPremulType() ||
-                *this->getColors(0) != *s.getColors(0) ||
-                *this->getColors(1) != *s.getColors(1) ||
-                *this->getColors(2) != *s.getColors(2)) {
+    if (this->fColorType == ge.getColorType()) {
+        if (kTexture_ColorType == fColorType) {
+            if (fYCoord != ge.getYCoord()) {
                 return false;
             }
         } else {
-            if (fYCoord != s.getYCoord()) {
+            if (this->getPremulType() != ge.getPremulType() ||
+                this->fColors.count() != ge.fColors.count()) {
                 return false;
             }
+
+            for (int i = 0; i < this->fColors.count(); i++) {
+                if (*this->getColors(i) != *ge.getColors(i)) {
+                    return false;
+                }
+            }
         }
 
-        SkASSERT(this->useAtlas() == s.useAtlas());
+        SkASSERT(this->useAtlas() == ge.useAtlas());
         return true;
     }
 
index 50ad427..ee30c9b 100644 (file)
 #include "SkShader.h"
 #include "SkOnce.h"
 
+#if SK_SUPPORT_GPU
+    #define GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS 1
+#endif
+
 static inline void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
                                int count) {
     if (count > 0) {
@@ -128,7 +132,8 @@ public:
         bool getDither() const { return fCacheDither; }
 
     private:
-        // Working pointers. If either is nullptr, we need to recompute the corresponding cache values.
+        // Working pointers. If either is nullptr, we need to recompute the corresponding
+        // cache values.
         uint16_t*   fCache16;
         SkPMColor*  fCache32;
 
@@ -197,17 +202,6 @@ public:
         kDitherStride16 = kCache16Count,
     };
 
-    enum GpuColorType {
-        kTwo_GpuColorType,
-        kThree_GpuColorType, // Symmetric three color
-        kTexture_GpuColorType
-    };
-
-    // Determines and returns the gradient is a two color gradient, symmetric three color gradient
-    // or other (texture gradient). If it is two or symmetric three color, the colors array will
-    // also be filled with the gradient colors
-    GpuColorType getGpuColorType(SkColor colors[3]) const;
-
     uint32_t getGradFlags() const { return fGradFlags; }
 
 protected:
@@ -220,7 +214,6 @@ protected:
     const SkMatrix fPtsToUnit;
     TileMode    fTileMode;
     TileProc    fTileProc;
-    int         fColorCount;
     uint8_t     fGradFlags;
 
     struct Rec {
@@ -254,9 +247,13 @@ private:
 public:
     SkColor*    fOrigColors; // original colors, before modulation by paint in context.
     SkScalar*   fOrigPos;   // original positions
+    int         fColorCount;
 
     bool colorsAreOpaque() const { return fColorsAreOpaque; }
 
+    TileMode getTileMode() const { return fTileMode; }
+    Rec* getRecs() const { return fRecs; }
+
 private:
     bool        fColorsAreOpaque;
 
@@ -336,9 +333,30 @@ public:
     virtual ~GrGradientEffect();
 
     bool useAtlas() const { return SkToBool(-1 != fRow); }
-    SkScalar getYCoord() const { return fYCoord; };
+    SkScalar getYCoord() const { return fYCoord; }
 
-    SkGradientShaderBase::GpuColorType getColorType() const { return fColorType; }
+    enum ColorType {
+        kTwo_ColorType,
+        kThree_ColorType, // Symmetric three color
+        kTexture_ColorType,
+
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+        kHardStopCentered_ColorType,   // 0, 0.5, 0.5, 1
+        kHardStopLeftEdged_ColorType,  // 0, 0, 1
+        kHardStopRightEdged_ColorType, // 0, 1, 1
+#endif
+    };
+
+    ColorType getColorType() const { return fColorType; }
+
+    // Determines the type of gradient, one of:
+    //    - Two-color
+    //    - Symmetric three-color
+    //    - Texture
+    //    - Centered hard stop
+    //    - Left-edged hard stop
+    //    - Right-edged hard stop
+    ColorType determineColorType(const SkGradientShaderBase& shader);
 
     enum PremulType {
         kBeforeInterp_PremulType,
@@ -348,8 +366,8 @@ public:
     PremulType getPremulType() const { return fPremulType; }
 
     const SkColor* getColors(int pos) const {
-        SkASSERT(fColorType != SkGradientShaderBase::kTexture_GpuColorType);
-        SkASSERT((pos-1) <= fColorType);
+        SkASSERT(fColorType != kTexture_ColorType);
+        SkASSERT(pos < fColors.count());
         return &fColors[pos];
     }
 
@@ -358,8 +376,8 @@ protected:
         The function decides whether stop values should be used or not. The return value indicates
         the number of colors, which will be capped by kMaxRandomGradientColors. colors should be
         sized to be at least kMaxRandomGradientColors. stops is a pointer to an array of at least
-        size kMaxRandomGradientColors. It may be updated to nullptr, indicating that nullptr should be
-        passed to the gradient factory rather than the array.
+        size kMaxRandomGradientColors. It may be updated to nullptr, indicating that nullptr should
+        be passed to the gradient factory rather than the array.
     */
     static const int kMaxRandomGradientColors = 4;
     static int RandomGradientParams(SkRandom* r,
@@ -376,26 +394,31 @@ protected:
 private:
     static const GrCoordSet kCoordSet = kLocal_GrCoordSet;
 
+    SkTDArray<SkColor>  fColors;
+    SkTDArray<SkScalar> fPositions;
+    SkShader::TileMode  fTileMode;
+
     GrCoordTransform fCoordTransform;
     GrTextureAccess fTextureAccess;
     SkScalar fYCoord;
     GrTextureStripAtlas* fAtlas;
     int fRow;
     bool fIsOpaque;
-    SkGradientShaderBase::GpuColorType fColorType;
-    SkColor fColors[3]; // More than 3 colors we use texture
-    PremulType fPremulType; // This only changes behavior for two and three color special cases.
-                            // It is already baked into to the table for texture gradients.
+    ColorType fColorType;
+    PremulType fPremulType; // This is already baked into the table for texture gradients, and
+                            // only changes behavior for gradients that don't use a texture.
     typedef GrFragmentProcessor INHERITED;
 
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 
-// Base class for GLSL gradient effects
+// Base class for GL gradient effects
 class GrGradientEffect::GLSLProcessor : public GrGLSLFragmentProcessor {
 public:
-    GLSLProcessor();
+    GLSLProcessor() {
+        fCachedYCoord = SK_ScalarMax;
+    }
 
 protected:
     void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
@@ -412,10 +435,10 @@ protected:
     // should call this method from their emitCode().
     void emitUniforms(GrGLSLUniformHandler*, const GrGradientEffect&);
 
-
-    // emit code that gets a fragment's color from an expression for t; Has branches for 3 separate
-    // control flows inside -- 2 color gradients, 3 color symmetric gradients (both using
-    // native GLSL mix), and 4+ color gradients that use the traditional texture lookup.
+    // Emit code that gets a fragment's color from an expression for t; has branches for
+    // several control flows inside -- 2-color gradients, 3-color symmetric gradients, 4+
+    // color gradients that use the traditional texture lookup, as well as several varieties
+    // of hard stop gradients
     void emitColor(GrGLSLFPFragmentBuilder* fragBuilder,
                    GrGLSLUniformHandler* uniformHandler,
                    const GrGLSLCaps* caps,
@@ -428,18 +451,30 @@ protected:
 private:
     enum {
         // First bit for premul before/after interp
-        kPremulBeforeInterpKey = 1,
-
-        // Next two bits for 2/3 color type (neither means using texture atlas)
-        kTwoColorKey   = 4,
-        kThreeColorKey = 6,
+        kPremulBeforeInterpKey  =  1,
+
+        // Next three bits for 2/3 color type or different special
+        // hard stop cases (neither means using texture atlas)
+        kTwoColorKey            =  2,
+        kThreeColorKey          =  4,
+#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS
+        kHardStopCenteredKey    =  6,
+        kHardStopZeroZeroOneKey =  8,
+        kHardStopZeroOneOneKey  = 10,
+
+        // Next two bits for tile mode
+        kClampTileMode          = 16,
+        kRepeatTileMode         = 32,
+        kMirrorTileMode         = 48,
+
+        // Lower six bits for premul, 2/3 color type, and tile mode
+        kReservedBits           = 6,
+#endif
     };
 
     SkScalar fCachedYCoord;
+    GrGLSLProgramDataManager::UniformHandle fColorsUni;
     GrGLSLProgramDataManager::UniformHandle fFSYUni;
-    GrGLSLProgramDataManager::UniformHandle fColorStartUni;
-    GrGLSLProgramDataManager::UniformHandle fColorMidUni;
-    GrGLSLProgramDataManager::UniformHandle fColorEndUni;
 
     typedef GrGLSLFragmentProcessor INHERITED;
 };
index 6e32aa3..505cfd2 100644 (file)
@@ -218,10 +218,10 @@ void GrSweepGradient::GLSLSweepProcessor::emitCode(EmitArgs& args) {
     // On Intel GPU there is an issue where it reads the second arguement to atan "- %s.x" as an int
     // thus must us -1.0 * %s.x to work correctly
     if (args.fGLSLCaps->mustForceNegatedAtanParamToFloat()){
-        t.printf("atan(- %s.y, -1.0 * %s.x) * 0.1591549430918 + 0.5",
+        t.printf("(atan(- %s.y, -1.0 * %s.x) * 0.1591549430918 + 0.5)",
                  coords2D.c_str(), coords2D.c_str());
     } else {
-        t.printf("atan(- %s.y, - %s.x) * 0.1591549430918 + 0.5",
+        t.printf("(atan(- %s.y, - %s.x) * 0.1591549430918 + 0.5)",
                  coords2D.c_str(), coords2D.c_str());
     }
     this->emitColor(args.fFragBuilder,