Registry-based unit test for custom effects

Review URL: http://codereview.appspot.com/6447085/



git-svn-id: http://skia.googlecode.com/svn/trunk@4946 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/effects/gradients/SkGradientShader.cpp b/src/effects/gradients/SkGradientShader.cpp
index e415c62..f61f8d5 100644
--- a/src/effects/gradients/SkGradientShader.cpp
+++ b/src/effects/gradients/SkGradientShader.cpp
@@ -732,4 +732,28 @@
     return fTexture;
 }
 
+int GrGradientEffect::RandomGradientParams(SkRandom* random,
+                                           SkColor colors[],
+                                           SkScalar** stops,
+                                           SkShader::TileMode* tm) {
+    int outColors = random->nextRangeU(1, kMaxRandomGradientColors);
+
+    // if one color, omit stops, otherwise randomly decide whether or not to
+    if (outColors == 1 || (outColors >= 2 && random->nextBool())) {
+        *stops = NULL;
+    }
+
+    GrScalar stop = 0.f;
+    for (int i = 0; i < outColors; ++i) {
+        colors[i] = random->nextU();
+        if (NULL != *stops) {
+            (*stops)[i] = stop;
+            stop = i < outColors - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
+        }
+    }
+    *tm = static_cast<SkShader::TileMode>(random->nextULessThan(SkShader::kTileModeCount));
+
+    return outColors;
+}
+
 #endif
diff --git a/src/effects/gradients/SkGradientShaderPriv.h b/src/effects/gradients/SkGradientShaderPriv.h
index 9e80fd3..807bf32 100644
--- a/src/effects/gradients/SkGradientShaderPriv.h
+++ b/src/effects/gradients/SkGradientShaderPriv.h
@@ -233,8 +233,22 @@
 
     bool useTexture() const { return fUseTexture; }
 
-private:
+protected:
 
+    /** Populates a pair of arrays with colors and stop info to construct a random gradient.
+        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 NULL, indicating that NULL should be
+        passed to the gradient factory rather than the array.
+    */
+    static const int kMaxRandomGradientColors = 4;
+    static int RandomGradientParams(SkRandom* r,
+                                    SkColor colors[kMaxRandomGradientColors],
+                                    SkScalar** stops,
+                                    SkShader::TileMode* tm);
+
+private:
     GrTexture* fTexture;
     bool fUseTexture;
 
diff --git a/src/effects/gradients/SkLinearGradient.cpp b/src/effects/gradients/SkLinearGradient.cpp
index d88540e..ef39768 100644
--- a/src/effects/gradients/SkLinearGradient.cpp
+++ b/src/effects/gradients/SkLinearGradient.cpp
@@ -516,12 +516,37 @@
     typedef GrGLLinearGradient GLProgramStage;
 
 private:
+    GR_DECLARE_CUSTOM_STAGE_TEST;
 
     typedef GrGradientEffect INHERITED;
 };
 
 /////////////////////////////////////////////////////////////////////
 
+GR_DEFINE_CUSTOM_STAGE_TEST(GrLinearGradient);
+
+GrCustomStage* GrLinearGradient::TestCreate(SkRandom* random,
+                                            GrContext* context,
+                                            GrTexture**) {
+    SkPoint points[] = {{random->nextUScalar1(), random->nextUScalar1()},
+                        {random->nextUScalar1(), random->nextUScalar1()}};
+
+    SkColor colors[kMaxRandomGradientColors];
+    SkScalar stopsArray[kMaxRandomGradientColors];
+    SkScalar* stops = stopsArray;
+    SkShader::TileMode tm;
+    int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+    SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear(points,
+                                                                 colors, stops, colorCount,
+                                                                 tm));
+    GrSamplerState sampler;
+    GrCustomStage* stage = shader->asNewCustomStage(context, &sampler);
+    GrAssert(NULL != stage);
+    return stage;
+}
+
+/////////////////////////////////////////////////////////////////////
+
 void GrGLLinearGradient::emitFS(GrGLShaderBuilder* builder,
                                 const char* outputColor,
                                 const char* inputColor,
diff --git a/src/effects/gradients/SkRadialGradient.cpp b/src/effects/gradients/SkRadialGradient.cpp
index f3978a5..dbde95a 100644
--- a/src/effects/gradients/SkRadialGradient.cpp
+++ b/src/effects/gradients/SkRadialGradient.cpp
@@ -516,12 +516,37 @@
     typedef GrGLRadialGradient GLProgramStage;
 
 private:
+    GR_DECLARE_CUSTOM_STAGE_TEST;
 
     typedef GrGradientEffect INHERITED;
 };
 
 /////////////////////////////////////////////////////////////////////
 
+GR_DEFINE_CUSTOM_STAGE_TEST(GrRadialGradient);
+
+GrCustomStage* GrRadialGradient::TestCreate(SkRandom* random,
+                                            GrContext* context,
+                                            GrTexture**) {
+    SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
+    SkScalar radius = random->nextUScalar1();
+
+    SkColor colors[kMaxRandomGradientColors];
+    SkScalar stopsArray[kMaxRandomGradientColors];
+    SkScalar* stops = stopsArray;
+    SkShader::TileMode tm;
+    int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+    SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius,
+                                                                 colors, stops, colorCount,
+                                                                 tm));
+    GrSamplerState sampler;
+    GrCustomStage* stage = shader->asNewCustomStage(context, &sampler);
+    GrAssert(NULL != stage);
+    return stage;
+}
+
+/////////////////////////////////////////////////////////////////////
+
 void GrGLRadialGradient::emitFS(GrGLShaderBuilder* builder,
                                 const char* outputColor,
                                 const char* inputColor,
diff --git a/src/effects/gradients/SkSweepGradient.cpp b/src/effects/gradients/SkSweepGradient.cpp
index 0dee9b5..89ebba2 100644
--- a/src/effects/gradients/SkSweepGradient.cpp
+++ b/src/effects/gradients/SkSweepGradient.cpp
@@ -422,13 +422,36 @@
 
     typedef GrGLSweepGradient GLProgramStage;
 
-protected:
+private:
+    GR_DECLARE_CUSTOM_STAGE_TEST;
 
     typedef GrGradientEffect INHERITED;
 };
 
 /////////////////////////////////////////////////////////////////////
 
+GR_DEFINE_CUSTOM_STAGE_TEST(GrSweepGradient);
+
+GrCustomStage* GrSweepGradient::TestCreate(SkRandom* random,
+                                           GrContext* context,
+                                           GrTexture**) {
+    SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
+
+    SkColor colors[kMaxRandomGradientColors];
+    SkScalar stopsArray[kMaxRandomGradientColors];
+    SkScalar* stops = stopsArray;
+    SkShader::TileMode tmIgnored;
+    int colorCount = RandomGradientParams(random, colors, &stops, &tmIgnored);
+    SkAutoTUnref<SkShader> shader(SkGradientShader::CreateSweep(center.fX, center.fY,
+                                                                colors, stops, colorCount));
+    GrSamplerState sampler;
+    GrCustomStage* stage = shader->asNewCustomStage(context, &sampler);
+    GrAssert(NULL != stage);
+    return stage;
+}
+
+/////////////////////////////////////////////////////////////////////
+
 void GrGLSweepGradient::emitFS(GrGLShaderBuilder* builder,
                               const char* outputColor,
                               const char* inputColor,
diff --git a/src/effects/gradients/SkTwoPointConicalGradient.cpp b/src/effects/gradients/SkTwoPointConicalGradient.cpp
index 8902573..528291b 100644
--- a/src/effects/gradients/SkTwoPointConicalGradient.cpp
+++ b/src/effects/gradients/SkTwoPointConicalGradient.cpp
@@ -402,6 +402,7 @@
     typedef GrGLConical2Gradient GLProgramStage;
 
 private:
+    GR_DECLARE_CUSTOM_STAGE_TEST;
 
     // @{
     // Cache of values - these can change arbitrarily, EXCEPT
@@ -416,6 +417,37 @@
     typedef GrGradientEffect INHERITED;
 };
 
+GR_DEFINE_CUSTOM_STAGE_TEST(GrConical2Gradient);
+
+GrCustomStage* GrConical2Gradient::TestCreate(SkRandom* random,
+                                              GrContext* context,
+                                              GrTexture**) {
+    SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
+    SkScalar radius1 = random->nextUScalar1();
+    SkPoint center2;
+    SkScalar radius2;
+    do {
+        center1.set(random->nextUScalar1(), random->nextUScalar1());
+        radius2 = random->nextUScalar1 ();
+        // If the circles are identical the factory will give us an empty shader.
+    } while (radius1 == radius2 && center1 == center2);
+    
+    SkColor colors[kMaxRandomGradientColors];
+    SkScalar stopsArray[kMaxRandomGradientColors];
+    SkScalar* stops = stopsArray;
+    SkShader::TileMode tm;
+    int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+    SkAutoTUnref<SkShader> shader(SkGradientShader::CreateTwoPointConical(center1, radius1,
+                                                                          center2, radius2,
+                                                                          colors, stops, colorCount,
+                                                                          tm));
+    GrSamplerState sampler;
+    GrCustomStage* stage = shader->asNewCustomStage(context, &sampler);
+    GrAssert(NULL != stage);
+    return stage;
+}
+
+
 /////////////////////////////////////////////////////////////////////
 
 GrGLConical2Gradient::GrGLConical2Gradient(
diff --git a/src/effects/gradients/SkTwoPointRadialGradient.cpp b/src/effects/gradients/SkTwoPointRadialGradient.cpp
index 84ae91d..441f5a8 100644
--- a/src/effects/gradients/SkTwoPointRadialGradient.cpp
+++ b/src/effects/gradients/SkTwoPointRadialGradient.cpp
@@ -434,6 +434,7 @@
     typedef GrGLRadial2Gradient GLProgramStage;
 
 private:
+    GR_DECLARE_CUSTOM_STAGE_TEST;
 
     // @{
     // Cache of values - these can change arbitrarily, EXCEPT
@@ -450,6 +451,38 @@
 
 /////////////////////////////////////////////////////////////////////
 
+GR_DEFINE_CUSTOM_STAGE_TEST(GrRadial2Gradient);
+
+GrCustomStage* GrRadial2Gradient::TestCreate(SkRandom* random,
+                                             GrContext* context,
+                                             GrTexture**) {
+    SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
+    SkScalar radius1 = random->nextUScalar1();
+    SkPoint center2;
+    SkScalar radius2;
+    do {
+        center1.set(random->nextUScalar1(), random->nextUScalar1());
+        radius2 = random->nextUScalar1 ();
+        // There is a bug in two point radial gradients with idenitical radii
+    } while (radius1 == radius2);
+
+    SkColor colors[kMaxRandomGradientColors];
+    SkScalar stopsArray[kMaxRandomGradientColors];
+    SkScalar* stops = stopsArray;
+    SkShader::TileMode tm;
+    int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+    SkAutoTUnref<SkShader> shader(SkGradientShader::CreateTwoPointRadial(center1, radius1,
+                                                                         center2, radius2,
+                                                                         colors, stops, colorCount,
+                                                                         tm));
+    GrSamplerState sampler;
+    GrCustomStage* stage = shader->asNewCustomStage(context, &sampler);
+    GrAssert(NULL != stage);
+    return stage;
+}
+
+/////////////////////////////////////////////////////////////////////
+
 GrGLRadial2Gradient::GrGLRadial2Gradient(
         const GrProgramStageFactory& factory,
         const GrCustomStage& baseData)
diff --git a/src/gpu/GrCustomStage.cpp b/src/gpu/GrCustomStage.cpp
index bea07cf..6d7bfad 100644
--- a/src/gpu/GrCustomStage.cpp
+++ b/src/gpu/GrCustomStage.cpp
@@ -13,6 +13,13 @@
 
 SK_DEFINE_INST_COUNT(GrCustomStage)
 
+#if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
+SkTArray<GrCustomStageTestFactory*, true>* GrCustomStageTestFactory::GetFactories() {
+    static SkTArray<GrCustomStageTestFactory*, true> gFactories;
+    return &gFactories;
+}
+#endif
+
 class GrCustomStage_Globals {
 public:
     static GrMemoryPool* GetTLS() {
diff --git a/src/gpu/effects/GrColorTableEffect.cpp b/src/gpu/effects/GrColorTableEffect.cpp
index f1ce664..94eb8fd 100644
--- a/src/gpu/effects/GrColorTableEffect.cpp
+++ b/src/gpu/effects/GrColorTableEffect.cpp
@@ -52,11 +52,20 @@
     static const float kColorScaleFactor = 255.0f / 256.0f;
     static const float kColorOffsetFactor = 1.0f / 512.0f;
     SkString* code = &builder->fFSCode;
-    code->appendf("\t\tvec4 coord = vec4(%s.rgb / %s.a, %s.a);\n",
-                  inputColor, inputColor, inputColor);
-    code->appendf("\t\tcoord = coord * %f + vec4(%f, %f, %f, %f);\n",
-                  kColorScaleFactor,
-                  kColorOffsetFactor, kColorOffsetFactor, kColorOffsetFactor, kColorOffsetFactor);
+    if (NULL == inputColor) {
+        // the input color is solid white (all ones).
+        static const float kMaxValue = kColorScaleFactor + kColorOffsetFactor;
+        code->appendf("\t\tvec4 coord = vec4(%f, %f, %f, %f);\n",
+                      kMaxValue, kMaxValue, kMaxValue, kMaxValue);
+
+    } else {
+        code->appendf("\t\tvec4 coord = vec4(%s.rgb / %s.a, %s.a);\n",
+                      inputColor, inputColor, inputColor);
+        code->appendf("\t\tcoord = coord * %f + vec4(%f, %f, %f, %f);\n",
+                      kColorScaleFactor,
+                      kColorOffsetFactor, kColorOffsetFactor,
+                      kColorOffsetFactor, kColorOffsetFactor);
+    }
 
     const GrTextureAccess& access = *fCustomStage.textureAccess(0);
     code->appendf("\t\t%s.a = ", outputColor);
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index ebbd267..b04d924 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -301,7 +301,7 @@
     add_helper(outputVar, colorStr.c_str(), constStr.c_str(), fsCode);
 }
 
-void GrGLProgram::genEdgeCoverage(SkString* coverageVar,
+bool GrGLProgram::genEdgeCoverage(SkString* coverageVar,
                                   GrGLShaderBuilder* segments) const {
     if (fDesc.fVertexLayout & GrDrawTarget::kEdge_VertexLayoutBit) {
         const char *vsName, *fsName;
@@ -358,8 +358,10 @@
             break;
         }
         *coverageVar = "edgeAlpha";
+        return true;
     } else {
         coverageVar->reset();
+        return false;
     }
 }
 
@@ -770,7 +772,7 @@
     if (!wroteFragColorZero || Desc::kNone_DualSrcOutput != fDesc.fDualSrcOutput) {
 
         if (!coverageIsZero) {
-            this->genEdgeCoverage(&inCoverage, &builder);
+            bool inCoverageIsScalar  = this->genEdgeCoverage(&inCoverage, &builder);
 
             switch (fDesc.fCoverageInput) {
                 case Desc::kSolidWhite_ColorInput:
@@ -778,9 +780,11 @@
                     break;
                 case Desc::kAttribute_ColorInput:
                     gen_attribute_coverage(&builder, &inCoverage);
+                    inCoverageIsScalar = false;
                     break;
                 case Desc::kUniform_ColorInput:
                     this->genUniformCoverage(&builder, &inCoverage);
+                    inCoverageIsScalar = false;
                     break;
                 default:
                     GrCrash("Unexpected input coverage.");
@@ -793,8 +797,7 @@
                     // create var to hold stage output
                     outCoverage = "coverage";
                     outCoverage.appendS32(s);
-                    builder.fFSCode.appendf("\tvec4 %s;\n",
-                                            outCoverage.c_str());
+                    builder.fFSCode.appendf("\tvec4 %s;\n", outCoverage.c_str());
 
                     const char* inCoords;
                     // figure out what our input coords are
@@ -813,6 +816,13 @@
                         const GrProgramStageFactory& factory = customStages[s]->getFactory();
                         fProgramStage[s] = factory.createGLInstance(*customStages[s]);
                     }
+                    // stages don't know how to deal with a scalar input. (Maybe they should. We 
+                    // could pass a GrGLShaderVar)
+                    if (inCoverageIsScalar) {
+                        builder.fFSCode.appendf("\tvec4 %s4 = vec4(%s);\n", 
+                                                inCoverage.c_str(), inCoverage.c_str());
+                        inCoverage.append("4");
+                    }
                     this->genStageCode(s,
                                        inCoverage.size() ? inCoverage.c_str() : NULL,
                                        outCoverage.c_str(),
diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h
index 62c70e5..989b7c6 100644
--- a/src/gpu/gl/GrGLProgram.h
+++ b/src/gpu/gl/GrGLProgram.h
@@ -259,8 +259,10 @@
 
     void genUniformCoverage(GrGLShaderBuilder* segments, SkString* inOutCoverage);
 
-    // generates code to compute coverage based on edge AA.
-    void genEdgeCoverage(SkString* coverageVar, GrGLShaderBuilder* builder) const;
+    // generates code to compute coverage based on edge AA. Returns true if edge coverage was
+    // inserted in which case coverageVar will be updated to refer to a scalar. Otherwise,
+    // coverageVar is set to an empty string.
+    bool genEdgeCoverage(SkString* coverageVar, GrGLShaderBuilder* builder) const;
 
     // Creates a GL program ID, binds shader attributes to GL vertex attrs, and links the program
     bool bindOutputsAttribsAndLinkProgram(SkString texCoordAttrNames[GrDrawState::kMaxTexCoords],
diff --git a/src/gpu/gl/GrGLProgramStage.h b/src/gpu/gl/GrGLProgramStage.h
index 8e3a4b1..69095b6 100644
--- a/src/gpu/gl/GrGLProgramStage.h
+++ b/src/gpu/gl/GrGLProgramStage.h
@@ -57,7 +57,12 @@
         on the state.
         The code will be inside an otherwise-empty block.
         Fragment shader inputs are a vec2 of coordinates, one texture,
-        and a color; output is a color. */
+        and a color; output is a color. The input color may be NULL which
+        indicates that the input color is solid white. TODO: Better system
+        for communicating optimization info (e.g. input color is solid white,
+        trans black, known to be opaque, etc.) that allows the custom stage
+        to communicate back similar known info about its output.
+        */
     /* TODO: don't give them the samplerName, just a handle; then give
        a function here for them to call into that'll apply any texture
        domain - but do we force them to be honest about texture domain
diff --git a/src/gpu/gl/GrGLShaderBuilder.cpp b/src/gpu/gl/GrGLShaderBuilder.cpp
index fb70556..3ca4086 100644
--- a/src/gpu/gl/GrGLShaderBuilder.cpp
+++ b/src/gpu/gl/GrGLShaderBuilder.cpp
@@ -184,11 +184,7 @@
 GrCustomStage::StageKey GrGLShaderBuilder::KeyForTextureAccess(const GrTextureAccess& access,
                                                                const GrGLCaps& caps) {
     GrCustomStage::StageKey key = 0;
-    
-    if (!access.getTexture()) {
-        return key;
-    }    
-    
+
     // Assume that swizzle support implies that we never have to modify a shader to adjust
     // for texture format/swizzle settings.
     if (caps.textureSwizzleSupport()) {
@@ -277,7 +273,11 @@
         fGSOutputs.push_back();
         fGSOutputs.back().setType(type);
         fGSOutputs.back().setTypeModifier(GrGLShaderVar::kOut_TypeModifier);
-        fGSOutputs.back().accessName()->printf("g%s", name);
+        if (kNonStageIdx == fCurrentStage) {
+            fGSOutputs.back().accessName()->printf("g%s", name);
+        } else {
+            fGSOutputs.back().accessName()->printf("g%s%d", name, fCurrentStage);
+        }
         fsName = fGSOutputs.back().accessName();
     } else {
         fsName = fVSOutputs.back().accessName();