Implement morphology as a custom effect

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



git-svn-id: http://skia.googlecode.com/svn/trunk@4102 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 80dd25e..b7ada61 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -9,9 +9,11 @@
 
 #include "GrContext.h"
 
+#include "effects/GrMorphologyEffect.h"
+#include "effects/GrConvolutionEffect.h"
+
 #include "GrBufferAllocPool.h"
 #include "GrClipIterator.h"
-#include "effects/GrConvolutionEffect.h"
 #include "GrGpu.h"
 #include "GrIndexBuffer.h"
 #include "GrInOrderDrawBuffer.h"
@@ -242,23 +244,6 @@
                            sb->numSamples(), v);
 }
 
-void build_kernel(float sigma, float* kernel, int kernelWidth) {
-    int halfWidth = (kernelWidth - 1) / 2;
-    float sum = 0.0f;
-    float denom = 1.0f / (2.0f * sigma * sigma);
-    for (int i = 0; i < kernelWidth; ++i) {
-        float x = static_cast<float>(i - halfWidth);
-        // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
-        // is dropped here, since we renormalize the kernel below.
-        kernel[i] = sk_float_exp(- x * x * denom);
-        sum += kernel[i];
-    }
-    // Normalize the kernel
-    float scale = 1.0f / sum;
-    for (int i = 0; i < kernelWidth; ++i)
-        kernel[i] *= scale;
-}
-
 void scale_rect(SkRect* rect, float xScale, float yScale) {
     rect->fLeft = SkScalarMul(rect->fLeft, SkFloatToScalar(xScale));
     rect->fTop = SkScalarMul(rect->fTop, SkFloatToScalar(yScale));
@@ -266,15 +251,14 @@
     rect->fBottom = SkScalarMul(rect->fBottom, SkFloatToScalar(yScale));
 }
 
-float adjust_sigma(float sigma, int *scaleFactor, int *halfWidth,
-                          int *kernelWidth) {
+float adjust_sigma(float sigma, int *scaleFactor, int *radius) {
     *scaleFactor = 1;
     while (sigma > MAX_BLUR_SIGMA) {
         *scaleFactor *= 2;
         sigma *= 0.5f;
     }
-    *halfWidth = static_cast<int>(ceilf(sigma * 3.0f));
-    *kernelWidth = *halfWidth * 2 + 1;
+    *radius = static_cast<int>(ceilf(sigma * 3.0f));
+    GrAssert(*radius <= GrConvolutionEffect::kMaxKernelRadius);
     return sigma;
 }
 
@@ -282,10 +266,8 @@
                       GrTexture* texture,
                       const SkRect& rect,
                       int radius,
-                      GrSamplerState::Filter filter,
-                      GrSamplerState::FilterDirection direction) {
-    GrAssert(filter == GrSamplerState::kErode_Filter ||
-             filter == GrSamplerState::kDilate_Filter);
+                      GrContext::MorphologyType morphType,
+                      Gr1DKernelEffect::Direction direction) {
 
     GrRenderTarget* target = gpu->drawState()->getRenderTarget();
     GrDrawTarget::AutoStateRestore asr(gpu, GrDrawTarget::kReset_ASRInit);
@@ -293,31 +275,31 @@
     drawState->setRenderTarget(target);
     GrMatrix sampleM;
     sampleM.setIDiv(texture->width(), texture->height());
-    drawState->sampler(0)->reset(GrSamplerState::kClamp_WrapMode, filter,
-                                 sampleM);
-    drawState->sampler(0)->setMorphologyRadius(radius);
-    drawState->sampler(0)->setFilterDirection(direction);
+    drawState->sampler(0)->reset(sampleM);
+    SkAutoTUnref<GrCustomStage> morph(
+        new GrMorphologyEffect(direction, radius, morphType));
+    drawState->sampler(0)->setCustomStage(morph);
     drawState->setTexture(0, texture);
     gpu->drawSimpleRect(rect, NULL, 1 << 0);
 }
 
-void convolve(GrGpu* gpu,
-              GrTexture* texture,
-              const SkRect& rect,
-              const float* kernel,
-              int kernelWidth,
-              GrSamplerState::FilterDirection direction) {
+void convolve_gaussian(GrGpu* gpu,
+                       GrTexture* texture,
+                       const SkRect& rect,
+                       float sigma,
+                       int radius,
+                       Gr1DKernelEffect::Direction direction) {
     GrRenderTarget* target = gpu->drawState()->getRenderTarget();
     GrDrawTarget::AutoStateRestore asr(gpu, GrDrawTarget::kReset_ASRInit);
     GrDrawState* drawState = gpu->drawState();
     drawState->setRenderTarget(target);
     GrMatrix sampleM;
     sampleM.setIDiv(texture->width(), texture->height());
-    drawState->sampler(0)->reset(GrSamplerState::kClamp_WrapMode,
-                                 GrSamplerState::kConvolution_Filter,
-                                 sampleM);
-    drawState->sampler(0)->setCustomStage(
-        new GrConvolutionEffect(direction, kernelWidth, kernel));
+    drawState->sampler(0)->reset(sampleM);
+    SkAutoTUnref<GrConvolutionEffect> conv(new
+        GrConvolutionEffect(direction, radius));
+    conv->setGaussianKernel(sigma);
+    drawState->sampler(0)->setCustomStage(conv);
     drawState->setTexture(0, texture);
     gpu->drawSimpleRect(rect, NULL, 1 << 0);
 }
@@ -2085,10 +2067,10 @@
     GrTexture* origTexture = srcTexture;
     GrAutoMatrix avm(this, GrMatrix::I());
     SkIRect clearRect;
-    int scaleFactorX, halfWidthX, kernelWidthX;
-    int scaleFactorY, halfWidthY, kernelWidthY;
-    sigmaX = adjust_sigma(sigmaX, &scaleFactorX, &halfWidthX, &kernelWidthX);
-    sigmaY = adjust_sigma(sigmaY, &scaleFactorY, &halfWidthY, &kernelWidthY);
+    int scaleFactorX, radiusX;
+    int scaleFactorY, radiusY;
+    sigmaX = adjust_sigma(sigmaX, &scaleFactorX, &radiusX);
+    sigmaY = adjust_sigma(sigmaY, &scaleFactorY, &radiusY);
 
     SkRect srcRect(rect);
     scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
@@ -2138,21 +2120,17 @@
     srcRect.roundOut(&srcIRect);
 
     if (sigmaX > 0.0f) {
-        SkAutoTMalloc<float> kernelStorageX(kernelWidthX);
-        float* kernelX = kernelStorageX.get();
-        build_kernel(sigmaX, kernelX, kernelWidthX);
-
         if (scaleFactorX > 1) {
-            // Clear out a halfWidth to the right of the srcRect to prevent the
+            // Clear out a radius to the right of the srcRect to prevent the
             // X convolution from reading garbage.
             clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, 
-                                          halfWidthX, srcIRect.height());
+                                          radiusX, srcIRect.height());
             this->clear(&clearRect, 0x0);
         }
 
         this->setRenderTarget(dstTexture->asRenderTarget());
-        convolve(fGpu, srcTexture, srcRect, kernelX, kernelWidthX,
-                 GrSamplerState::kX_FilterDirection);
+        convolve_gaussian(fGpu, srcTexture, srcRect, sigmaX, radiusX,
+                          Gr1DKernelEffect::kX_Direction);
         SkTSwap(srcTexture, dstTexture);
         if (temp2 && dstTexture == origTexture) {
             dstTexture = temp2->texture();
@@ -2160,21 +2138,17 @@
     }
 
     if (sigmaY > 0.0f) {
-        SkAutoTMalloc<float> kernelStorageY(kernelWidthY);
-        float* kernelY = kernelStorageY.get();
-        build_kernel(sigmaY, kernelY, kernelWidthY);
-
         if (scaleFactorY > 1 || sigmaX > 0.0f) {
-            // Clear out a halfWidth below the srcRect to prevent the Y
+            // Clear out a radius below the srcRect to prevent the Y
             // convolution from reading garbage.
             clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, 
-                                          srcIRect.width(), halfWidthY);
+                                          srcIRect.width(), radiusY);
             this->clear(&clearRect, 0x0);
         }
 
         this->setRenderTarget(dstTexture->asRenderTarget());
-        convolve(fGpu, srcTexture, srcRect, kernelY, kernelWidthY,
-                 GrSamplerState::kY_FilterDirection);
+        convolve_gaussian(fGpu, srcTexture, srcRect, sigmaY, radiusY,
+                          Gr1DKernelEffect::kY_Direction);
         SkTSwap(srcTexture, dstTexture);
         if (temp2 && dstTexture == origTexture) {
             dstTexture = temp2->texture();
@@ -2210,7 +2184,7 @@
 GrTexture* GrContext::applyMorphology(GrTexture* srcTexture,
                                       const GrRect& rect,
                                       GrTexture* temp1, GrTexture* temp2,
-                                      GrSamplerState::Filter filter,
+                                      MorphologyType morphType,
                                       SkISize radius) {
     ASSERT_OWNED_RESOURCE(srcTexture);
     GrRenderTarget* oldRenderTarget = this->getRenderTarget();
@@ -2220,8 +2194,8 @@
                                  SkIntToScalar(srcTexture->height())));
     if (radius.fWidth > 0) {
         this->setRenderTarget(temp1->asRenderTarget());
-        apply_morphology(fGpu, srcTexture, rect, radius.fWidth, filter,
-                         GrSamplerState::kX_FilterDirection);
+        apply_morphology(fGpu, srcTexture, rect, radius.fWidth, morphType,
+                         Gr1DKernelEffect::kX_Direction);
         SkIRect clearRect = SkIRect::MakeXYWH(
                     SkScalarFloorToInt(rect.fLeft), 
                     SkScalarFloorToInt(rect.fBottom),
@@ -2232,8 +2206,8 @@
     }
     if (radius.fHeight > 0) {
         this->setRenderTarget(temp2->asRenderTarget());
-        apply_morphology(fGpu, srcTexture, rect, radius.fHeight, filter,
-                         GrSamplerState::kY_FilterDirection);
+        apply_morphology(fGpu, srcTexture, rect, radius.fHeight, morphType,
+                         Gr1DKernelEffect::kY_Direction);
         srcTexture = temp2;
     }
     this->setRenderTarget(oldRenderTarget);
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 411b2c5..871dbb3 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1451,14 +1451,14 @@
         GrAutoScratchTexture temp1(context, desc), temp2(context, desc);
         texture = context->applyMorphology(texture, rect,
                                            temp1.texture(), temp2.texture(),
-                                           GrSamplerState::kDilate_Filter,
+                                           GrContext::kDilate_MorphologyType,
                                            radius);
         texture->ref();
     } else if (filter->asAnErode(&radius)) {
         GrAutoScratchTexture temp1(context, desc), temp2(context, desc);
         texture = context->applyMorphology(texture, rect,
                                            temp1.texture(), temp2.texture(),
-                                           GrSamplerState::kErode_Filter,
+                                           GrContext::kErode_MorphologyType,
                                            radius);
         texture->ref();
     }
diff --git a/src/gpu/effects/Gr1DKernelEffect.h b/src/gpu/effects/Gr1DKernelEffect.h
new file mode 100644
index 0000000..b6e116f
--- /dev/null
+++ b/src/gpu/effects/Gr1DKernelEffect.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef Gr1DKernelEffect_DEFINED
+#define Gr1DKernelEffect_DEFINED
+
+#include "GrCustomStage.h"
+
+/**
+ * Base class for 1D kernel effects. The kernel operates either in X or Y and
+ * has a pixel radius. The kernel is specified in the src texture's space
+ * and the kernel center is pinned to a texel's center. The radius specifies
+ * the number of texels on either side of the center texel in X or Y that are
+ * read. Since the center pixel is also read, the total width is one larger than
+ * two times the radius.
+ */
+class Gr1DKernelEffect : public GrCustomStage {
+
+public:
+    enum Direction {
+        kX_Direction,
+        kY_Direction,
+    };
+
+    Gr1DKernelEffect(Direction direction,
+                     int radius)
+        : fDirection(direction)
+        , fRadius(radius) {}
+
+    virtual ~Gr1DKernelEffect() {};
+
+    static int WidthFromRadius(int radius) { return 2 * radius + 1; }
+
+    int radius() const { return fRadius; }
+    int width() const { return WidthFromRadius(fRadius); }
+    Direction direction() const { return fDirection; }
+
+private:
+
+    Direction       fDirection;
+    int             fRadius;
+
+    typedef GrCustomStage INHERITED;
+};
+
+#endif
diff --git a/src/gpu/effects/GrConvolutionEffect.cpp b/src/gpu/effects/GrConvolutionEffect.cpp
index 0dd1606c..9296ecc 100644
--- a/src/gpu/effects/GrConvolutionEffect.cpp
+++ b/src/gpu/effects/GrConvolutionEffect.cpp
@@ -11,14 +11,11 @@
 #include "gl/GrGLTexture.h"
 #include "GrProgramStageFactory.h"
 
-/////////////////////////////////////////////////////////////////////
-
 class GrGLConvolutionEffect : public GrGLProgramStage {
-
 public:
-
     GrGLConvolutionEffect(const GrProgramStageFactory& factory,
-                          const GrCustomStage* stage);
+                          const GrCustomStage& stage);
+
     virtual void setupVariables(GrGLShaderBuilder* state,
                                 int stage) SK_OVERRIDE;
     virtual void emitVS(GrGLShaderBuilder* state,
@@ -27,38 +24,38 @@
                         const char* outputColor,
                         const char* inputColor,
                         const char* samplerName) SK_OVERRIDE;
+
     virtual void initUniforms(const GrGLInterface*, int programID) SK_OVERRIDE;
 
-    virtual void setData(const GrGLInterface*, 
+    virtual void setData(const GrGLInterface*,
                          const GrGLTexture&,
-                         GrCustomStage*,
+                         const GrCustomStage&,
                          int stageNum) SK_OVERRIDE;
 
-    static inline StageKey GenKey(const GrCustomStage* s);
-    
-protected:
-
-    unsigned int         fKernelWidth;
-    const GrGLShaderVar* fKernelVar;
-    const GrGLShaderVar* fImageIncrementVar;
- 
-    GrGLint              fKernelLocation;
-    GrGLint              fImageIncrementLocation;
+    static inline StageKey GenKey(const GrCustomStage&);
 
 private:
+    int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); }
+
+    int                   fRadius;
+    const GrGLShaderVar*  fKernelVar;
+    GrGLint               fKernelLocation;
+    const GrGLShaderVar*  fImageIncrementVar;
+    GrGLint               fImageIncrementLocation;
 
     typedef GrGLProgramStage INHERITED;
 };
 
-GrGLConvolutionEffect::GrGLConvolutionEffect(
-                                    const GrProgramStageFactory& factory,
-                                    const GrCustomStage* data)
+GrGLConvolutionEffect::GrGLConvolutionEffect(const GrProgramStageFactory& factory,
+                                             const GrCustomStage& stage)
     : GrGLProgramStage(factory)
     , fKernelVar(NULL)
-    , fImageIncrementVar(NULL)
     , fKernelLocation(0)
+    , fImageIncrementVar(NULL)
     , fImageIncrementLocation(0) {
-    fKernelWidth = static_cast<const GrConvolutionEffect*>(data)->width();
+    const GrConvolutionEffect& c =
+        static_cast<const GrConvolutionEffect&>(stage);
+    fRadius = c.radius();
 }
 
 void GrGLConvolutionEffect::setupVariables(GrGLShaderBuilder* state,
@@ -68,124 +65,132 @@
         kVec2f_GrSLType, "uImageIncrement", stage);
     fKernelVar = &state->addUniform(
         GrGLShaderBuilder::kFragment_VariableLifetime,
-        kFloat_GrSLType, "uKernel", stage, fKernelWidth);
+        kFloat_GrSLType, "uKernel", stage, this->width());
 
     fImageIncrementLocation = kUseUniform;
     fKernelLocation = kUseUniform;
 }
 
 void GrGLConvolutionEffect::emitVS(GrGLShaderBuilder* state,
-                        const char* vertexCoords) {
+                                   const char* vertexCoords) {
     GrStringBuilder* code = &state->fVSCode;
-    float scale = (fKernelWidth - 1) * 0.5f;
-    code->appendf("\t\t%s -= vec2(%g, %g) * %s;\n",
-                  vertexCoords, scale, scale,
+    code->appendf("\t\t%s -= vec2(%d, %d) * %s;\n",
+                  vertexCoords, fRadius, fRadius,
                   fImageIncrementVar->getName().c_str());
-
 }
 
 void GrGLConvolutionEffect::emitFS(GrGLShaderBuilder* state,
-                        const char* outputColor,
-                        const char* inputColor,
-                        const char* samplerName) {
+                                   const char* outputColor,
+                                   const char* inputColor,
+                                   const char* samplerName) {
     GrStringBuilder* code = &state->fFSCode;
     const char* texFunc = "texture2D";
     bool complexCoord = false;
 
-    GrStringBuilder modulate;
-    if (NULL != inputColor) {
-        modulate.printf(" * %s", inputColor);
-    }
+    state->fFSCode.appendf("\t\tvec4 sum = vec4(0, 0, 0, 0);\n");
+
+    code->appendf("\t\tvec2 coord = %s;\n", state->fSampleCoords.c_str());
+    code->appendf("\t\tfor (int i = 0; i < %d; i++) {\n", this->width());
 
     // Creates the string "kernel[i]" with workarounds for
     // possible driver bugs
     GrStringBuilder kernelIndex;
     fKernelVar->appendArrayAccess("i", &kernelIndex);
-
-    code->appendf("\t\tvec4 sum = vec4(0, 0, 0, 0);\n");
-    code->appendf("\t\tvec2 coord = %s;\n", state->fSampleCoords.c_str());
-    code->appendf("\t\tfor (int i = 0; i < %d; i++) {\n",
-                  fKernelWidth);
-
-    code->appendf("\t\t\tsum += ");
+    state->fFSCode.appendf("\t\t\tsum += ");
     state->emitTextureLookup(samplerName, "coord");
-    code->appendf(" * %s;\n", kernelIndex.c_str());
+    state->fFSCode.appendf(" * %s;\n", kernelIndex.c_str());
 
     code->appendf("\t\t\tcoord += %s;\n",
                   fImageIncrementVar->getName().c_str());
     code->appendf("\t\t}\n");
-    code->appendf("\t\t%s = sum%s;\n", outputColor, modulate.c_str());
+
+    state->fFSCode.appendf("\t\t%s = sum%s;\n",
+                           outputColor,
+                           state->fModulate.c_str());
 }
 
 void GrGLConvolutionEffect::initUniforms(const GrGLInterface* gl,
                                          int programID) {
-    GR_GL_CALL_RET(gl, fKernelLocation,
-        GetUniformLocation(programID, fKernelVar->getName().c_str()));
     GR_GL_CALL_RET(gl, fImageIncrementLocation,
         GetUniformLocation(programID,
             fImageIncrementVar->getName().c_str()));
+    GR_GL_CALL_RET(gl, fKernelLocation,
+        GetUniformLocation(programID, fKernelVar->getName().c_str()));
 }
 
 void GrGLConvolutionEffect::setData(const GrGLInterface* gl,
                                     const GrGLTexture& texture,
-                                    GrCustomStage* data,
+                                    const GrCustomStage& data,
                                     int stageNum) {
-    const GrConvolutionEffect* conv =
-        static_cast<const GrConvolutionEffect*>(data);
-    // the code we generated was for a specific kernel width
-    GrAssert(conv->width() == fKernelWidth);
-    GR_GL_CALL(gl, Uniform1fv(fKernelLocation,
-                              fKernelWidth,
-                              conv->kernel()));
+    const GrConvolutionEffect& conv =
+        static_cast<const GrConvolutionEffect&>(data);
+    // the code we generated was for a specific kernel radius
+    GrAssert(conv.radius() == fRadius);
     float imageIncrement[2] = { 0 };
-    switch (conv->direction()) {
-        case GrSamplerState::kX_FilterDirection:
+    switch (conv.direction()) {
+        case Gr1DKernelEffect::kX_Direction:
             imageIncrement[0] = 1.0f / texture.width();
             break;
-        case GrSamplerState::kY_FilterDirection:
-            imageIncrement[1] = 1.0f / texture.width();
+        case Gr1DKernelEffect::kY_Direction:
+            imageIncrement[1] = 1.0f / texture.height();
             break;
         default:
             GrCrash("Unknown filter direction.");
     }
     GR_GL_CALL(gl, Uniform2fv(fImageIncrementLocation, 1, imageIncrement));
+
+    GR_GL_CALL(gl, Uniform1fv(fKernelLocation, this->width(), conv.kernel()));
 }
 
 GrGLProgramStage::StageKey GrGLConvolutionEffect::GenKey(
-                                                    const GrCustomStage* s) {
-    return static_cast<const GrConvolutionEffect*>(s)->width();
+                                                    const GrCustomStage& s) {
+    return static_cast<const GrConvolutionEffect&>(s).radius();
 }
 
-/////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
 
-GrConvolutionEffect::GrConvolutionEffect(
-        GrSamplerState::FilterDirection direction,
-        unsigned int kernelWidth,
-        const float* kernel)
-    : fDirection (direction)
-    , fKernelWidth (kernelWidth) {
-    GrAssert(kernelWidth <= MAX_KERNEL_WIDTH);
-    for (unsigned int i = 0; i < kernelWidth; i++) {
-        fKernel[i] = kernel[i];
+GrConvolutionEffect::GrConvolutionEffect(Direction direction,
+                                         int radius,
+                                         const float* kernel)
+    : Gr1DKernelEffect(direction, radius) {
+    GrAssert(radius <= kMaxKernelRadius);
+    int width = this->width();
+    if (NULL != kernel) {
+        for (int i = 0; i < width; i++) {
+            fKernel[i] = kernel[i];
+        }
     }
 }
 
 GrConvolutionEffect::~GrConvolutionEffect() {
-
 }
 
 const GrProgramStageFactory& GrConvolutionEffect::getFactory() const {
     return GrTProgramStageFactory<GrConvolutionEffect>::getInstance();
 }
 
-bool GrConvolutionEffect::isEqual(const GrCustomStage * sBase) const {
-    const GrConvolutionEffect* s =
-        static_cast<const GrConvolutionEffect*>(sBase);
-
-    return (fKernelWidth == s->fKernelWidth &&
-            fDirection == s->fDirection &&
-            0 == memcmp(fKernel, s->fKernel, fKernelWidth * sizeof(float)));
+bool GrConvolutionEffect::isEqual(const GrCustomStage& sBase) const {
+     const GrConvolutionEffect& s =
+        static_cast<const GrConvolutionEffect&>(sBase);
+    return (this->radius() == s.radius() &&
+             this->direction() == s.direction() &&
+             0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
 }
 
-
-
+void GrConvolutionEffect::setGaussianKernel(float sigma) {
+    int width = this->width();
+    float sum = 0.0f;
+    float denom = 1.0f / (2.0f * sigma * sigma);
+    for (int i = 0; i < width; ++i) {
+        float x = static_cast<float>(i - this->radius());
+        // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
+        // is dropped here, since we renormalize the kernel below.
+        fKernel[i] = sk_float_exp(- x * x * denom);
+        sum += fKernel[i];
+    }
+    // Normalize the kernel
+    float scale = 1.0f / sum;
+    for (int i = 0; i < width; ++i) {
+        fKernel[i] *= scale;
+    }
+}
diff --git a/src/gpu/effects/GrConvolutionEffect.h b/src/gpu/effects/GrConvolutionEffect.h
index df164d5..fd6883b 100644
--- a/src/gpu/effects/GrConvolutionEffect.h
+++ b/src/gpu/effects/GrConvolutionEffect.h
@@ -8,40 +8,58 @@
 #ifndef GrConvolutionEffect_DEFINED
 #define GrConvolutionEffect_DEFINED
 
-#include "GrCustomStage.h"
-#include "GrSamplerState.h" // for MAX_KENEL_WIDTH, FilterDirection
+#include "Gr1DKernelEffect.h"
 
 class GrGLConvolutionEffect;
 
-class GrConvolutionEffect : public GrCustomStage {
+/**
+ * A convolution effect. The kernel is specified as an array of 2 * half-width 
+ * + 1 weights. Each texel is multiplied by it's weight and summed to determine
+ * the output color. The output color is modulated by the input color.
+ */
+class GrConvolutionEffect : public Gr1DKernelEffect {
 
 public:
 
-    GrConvolutionEffect(GrSamplerState::FilterDirection direction,
-                        unsigned int kernelWidth, const float* kernel);
+    GrConvolutionEffect(Direction, int halfWidth, const float* kernel = NULL);
     virtual ~GrConvolutionEffect();
 
-    unsigned int width() const { return fKernelWidth; }
+    void setKernel(const float* kernel) {
+        memcpy(fKernel, kernel, this->width());
+    }
+
+    /**
+     * Helper to set the kernel to a Gaussian. Replaces the existing kernel.
+     */
+    void setGaussianKernel(float sigma);
+
     const float* kernel() const { return fKernel; }
-    GrSamplerState::FilterDirection direction() const { return fDirection; }
 
     static const char* Name() { return "Convolution"; }
 
     typedef GrGLConvolutionEffect GLProgramStage;
 
     virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
-    virtual bool isEqual(const GrCustomStage *) const SK_OVERRIDE;
+    virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+
+    enum {
+        // This was decided based on the min allowed value for the max texture
+        // samples per fragment program run in DX9SM2 (32). A sigma param of 4.0
+        // on a blur filter gives a kernel width of 25 while a sigma of 5.0
+        // would exceed a 32 wide kernel.
+        kMaxKernelRadius = 12,
+        // With a C++11 we could have a constexpr version of WidthFromRadius()
+        // and not have to duplicate this calculation.
+        kMaxKernelWidth = 2 * kMaxKernelRadius + 1,
+    };
 
 protected:
 
-    GrSamplerState::FilterDirection fDirection;
-    unsigned int fKernelWidth;
-    float fKernel[MAX_KERNEL_WIDTH];
-
+    float fKernel[kMaxKernelWidth];
 
 private:
 
-    typedef GrCustomStage INHERITED;
+    typedef Gr1DKernelEffect INHERITED;
 };
 
 #endif
diff --git a/src/gpu/effects/GrMorphologyEffect.cpp b/src/gpu/effects/GrMorphologyEffect.cpp
new file mode 100644
index 0000000..614ef32
--- /dev/null
+++ b/src/gpu/effects/GrMorphologyEffect.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrMorphologyEffect.h"
+#include "gl/GrGLProgramStage.h"
+#include "gl/GrGLSL.h"
+#include "gl/GrGLTexture.h"
+#include "GrProgramStageFactory.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLMorphologyEffect  : public GrGLProgramStage {
+public:
+    GrGLMorphologyEffect (const GrProgramStageFactory& factory,
+                          const GrCustomStage& stage);
+
+    virtual void setupVariables(GrGLShaderBuilder* state,
+                                int stage) SK_OVERRIDE;
+    virtual void emitVS(GrGLShaderBuilder* state,
+                        const char* vertexCoords) SK_OVERRIDE;
+    virtual void emitFS(GrGLShaderBuilder* state,
+                        const char* outputColor,
+                        const char* inputColor,
+                        const char* samplerName) SK_OVERRIDE;
+
+    static inline StageKey GenKey(const GrCustomStage& s);
+
+    virtual void initUniforms(const GrGLInterface*, int programID) SK_OVERRIDE;
+    virtual void setData(const GrGLInterface*, 
+                         const GrGLTexture&,
+                         const GrCustomStage&,
+                         int stageNum) SK_OVERRIDE;
+
+private:
+    int width() const { return GrMorphologyEffect::WidthFromRadius(fRadius); }
+
+    int                                fRadius;
+    GrMorphologyEffect::MorphologyType fType;
+    const GrGLShaderVar*               fImageIncrementVar;
+    GrGLint                            fImageIncrementLocation;
+
+    typedef GrGLProgramStage INHERITED;
+};
+
+GrGLMorphologyEffect ::GrGLMorphologyEffect(const GrProgramStageFactory& factory,
+                                            const GrCustomStage& stage)
+    : GrGLProgramStage(factory)
+    , fImageIncrementVar(NULL)
+    , fImageIncrementLocation(0) {
+    const GrMorphologyEffect& m = static_cast<const GrMorphologyEffect&>(stage);
+    fRadius = m.radius();
+    fType = m.type();
+}
+
+void GrGLMorphologyEffect::setupVariables(GrGLShaderBuilder* state, int stage) {
+    fImageIncrementVar = &state->addUniform(
+        GrGLShaderBuilder::kBoth_VariableLifetime,
+        kVec2f_GrSLType, "uImageIncrement", stage);
+}
+
+void GrGLMorphologyEffect::emitVS(GrGLShaderBuilder* state,
+                                  const char* vertexCoords) {
+    GrStringBuilder* code = &state->fVSCode;
+    code->appendf("\t\t%s -= vec2(%d, %d) * %s;\n",
+                  vertexCoords, fRadius, fRadius,
+                  fImageIncrementVar->getName().c_str());
+}
+
+void GrGLMorphologyEffect::initUniforms(const GrGLInterface* gl,
+                                        int programID) {
+    GR_GL_CALL_RET(gl, fImageIncrementLocation,
+        GetUniformLocation(programID,
+            fImageIncrementVar->getName().c_str()));
+}
+
+void GrGLMorphologyEffect ::emitFS(GrGLShaderBuilder* state,
+                                   const char* outputColor,
+                                   const char* inputColor,
+                                   const char* samplerName) {
+    GrStringBuilder* code = &state->fFSCode;
+    const char* texFunc = "texture2D";
+    bool complexCoord = false;
+
+    const char* func;
+    switch (fType) {
+        case GrContext::kErode_MorphologyType:
+            state->fFSCode.appendf("\t\tvec4 value = vec4(1, 1, 1, 1);\n");
+            func = "min";
+            break;
+        case GrContext::kDilate_MorphologyType:
+            state->fFSCode.appendf("\t\tvec4 value = vec4(0, 0, 0, 0);\n");
+            func = "max";
+            break;
+        default:
+            GrCrash("Unexpected type");
+            break;
+    }
+
+    code->appendf("\t\tvec2 coord = %s;\n", state->fSampleCoords.c_str());
+    code->appendf("\t\tfor (int i = 0; i < %d; i++) {\n", this->width());
+    state->fFSCode.appendf("\t\t\tvalue = %s(value, ", func);
+    state->emitTextureLookup(samplerName, "coord");
+    state->fFSCode.appendf(");\n");
+    code->appendf("\t\t\tcoord += %s;\n",
+                  fImageIncrementVar->getName().c_str());
+    code->appendf("\t\t}\n");
+
+    state->fFSCode.appendf("\t\t%s = value%s;\n",
+                           outputColor,
+                           state->fModulate.c_str());
+}
+
+GrGLProgramStage::StageKey GrGLMorphologyEffect::GenKey(
+                                                    const GrCustomStage& s) {
+    const GrMorphologyEffect& m = static_cast<const GrMorphologyEffect&>(s);
+    StageKey key = static_cast<StageKey>(m.radius());
+    key |= (m.type() << 8);
+    return key;
+}
+
+void GrGLMorphologyEffect ::setData(const GrGLInterface* gl,
+                                    const GrGLTexture& texture,
+                                    const GrCustomStage& data,
+                                    int stageNum) {
+    const Gr1DKernelEffect& kern =
+        static_cast<const Gr1DKernelEffect&>(data);
+    // the code we generated was for a specific kernel radius
+    GrAssert(kern.radius() == fRadius);
+    float imageIncrement[2] = { 0 };
+    switch (kern.direction()) {
+        case Gr1DKernelEffect::kX_Direction:
+            imageIncrement[0] = 1.0f / texture.width();
+            break;
+        case Gr1DKernelEffect::kY_Direction:
+            imageIncrement[1] = 1.0f / texture.height();
+            break;
+        default:
+            GrCrash("Unknown filter direction.");
+    }
+    GR_GL_CALL(gl, Uniform2fv(fImageIncrementLocation, 1, imageIncrement));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrMorphologyEffect::GrMorphologyEffect(Direction direction,
+                                       int radius,
+                                       MorphologyType type)
+    : Gr1DKernelEffect(direction, radius)
+    , fType(type) {
+}
+
+GrMorphologyEffect::~GrMorphologyEffect() {
+}
+
+const GrProgramStageFactory& GrMorphologyEffect::getFactory() const {
+    return GrTProgramStageFactory<GrMorphologyEffect>::getInstance();
+}
+
+bool GrMorphologyEffect::isEqual(const GrCustomStage& sBase) const {
+    const GrMorphologyEffect& s =
+        static_cast<const GrMorphologyEffect&>(sBase);
+    return (this->radius() == s.radius() &&
+            this->direction() == s.direction() &&
+            this->type() == s.type());
+}
diff --git a/src/gpu/effects/GrMorphologyEffect.h b/src/gpu/effects/GrMorphologyEffect.h
new file mode 100644
index 0000000..c784460
--- /dev/null
+++ b/src/gpu/effects/GrMorphologyEffect.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrMorphologyEffect_DEFINED
+#define GrMorphologyEffect_DEFINED
+
+#include "GrContext.h"
+#include "Gr1DKernelEffect.h"
+
+class GrGLMorphologyEffect;
+
+/**
+ * Morphology effects. Depending upon the type of morphology, either the
+ * component-wise min (Erode_Type) or max (Dilate_Type) of all pixels in the
+ * kernel is selected as the new color. The new color is modulated by the input
+ * color.
+ */
+class GrMorphologyEffect : public Gr1DKernelEffect {
+
+public:
+
+    typedef GrContext::MorphologyType MorphologyType;
+
+    GrMorphologyEffect(Direction, int radius, MorphologyType);
+    virtual ~GrMorphologyEffect();
+
+    MorphologyType type() const { return fType; }
+
+    static const char* Name() { return "Morphology"; }
+
+    typedef GrGLMorphologyEffect GLProgramStage;
+
+    virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
+    virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+
+protected:
+
+    MorphologyType fType;
+
+private:
+
+    typedef Gr1DKernelEffect INHERITED;
+};
+#endif
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index 9a78a6e..a49aa61 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -95,18 +95,6 @@
     s->appendS32(stage);
 }
 
-inline void convolve_param_names(int stage, GrStringBuilder* k, GrStringBuilder* i) {
-    *k = "uKernel";
-    k->appendS32(stage);
-    *i = "uImageIncrement";
-    i->appendS32(stage);
-}
-
-inline void image_increment_param_name(int stage, GrStringBuilder* i) {
-    *i = "uImageIncrement";
-    i->appendS32(stage);
-}
-
 inline void tex_domain_name(int stage, GrStringBuilder* s) {
     *s = "uTexDom";
     s->appendS32(stage);
@@ -629,7 +617,7 @@
                     const GrProgramStageFactory& factory =
                         customStages[s]->getFactory();
                     programData->fCustomStage[s] =
-                        factory.createGLInstance(customStages[s]);
+                        factory.createGLInstance(*customStages[s]);
                 }
                 this->genStageCode(gl,
                                    s,
@@ -752,7 +740,7 @@
                         const GrProgramStageFactory& factory =
                             customStages[s]->getFactory();
                         programData->fCustomStage[s] =
-                            factory.createGLInstance(customStages[s]);
+                            factory.createGLInstance(*customStages[s]);
                     }
                     this->genStageCode(gl, s,
                         fProgramDesc.fStages[s],
@@ -1186,21 +1174,6 @@
                 GrAssert(kUnusedUniform != locations.fTexDomUni);
             }
 
-            GrStringBuilder kernelName, imageIncrementName;
-            convolve_param_names(s, &kernelName, &imageIncrementName);
-            if (kUseUniform == locations.fKernelUni) {
-                GL_CALL_RET(locations.fKernelUni,
-                            GetUniformLocation(progID, kernelName.c_str()));
-                GrAssert(kUnusedUniform != locations.fKernelUni);
-            }
-
-            if (kUseUniform == locations.fImageIncrementUni) {
-                GL_CALL_RET(locations.fImageIncrementUni, 
-                            GetUniformLocation(progID, 
-                                               imageIncrementName.c_str()));
-                GrAssert(kUnusedUniform != locations.fImageIncrementUni);
-            }
-
             if (NULL != programData->fCustomStage[s]) {
                 programData->fCustomStage[s]->
                     initUniforms(gl.interface(), progID);
@@ -1411,65 +1384,6 @@
 
 }
 
-void genMorphologyVS(int stageNum,
-                     const StageDesc& desc,
-                     GrGLShaderBuilder* segments,
-                     GrGLProgram::StageUniLocations* locations,
-                     const char** imageIncrementName,
-                     const char* varyingVSName) {
-
-    GrStringBuilder iiName;
-    image_increment_param_name(stageNum, &iiName);
-    const GrGLShaderVar* imgInc =
-        &segments->addUniform(
-            GrGLShaderBuilder::kBoth_VariableLifetime, kVec2f_GrSLType, 
-            iiName.c_str());
-    *imageIncrementName = imgInc->getName().c_str();
-
-    locations->fImageIncrementUni = kUseUniform;
-    segments->fVSCode.appendf("\t%s -= vec2(%d, %d) * %s;\n",
-                                  varyingVSName, desc.fKernelWidth,
-                                  desc.fKernelWidth, *imageIncrementName);
-}
- 
-void genMorphologyFS(int stageNum,
-                     const StageDesc& desc,
-                     GrGLShaderBuilder* segments,
-                     const char* samplerName,
-                     const char* imageIncrementName,
-                     const char* fsOutColor,
-                     GrStringBuilder& texFunc) {
-    GrStringBuilder valueVar("value");
-    valueVar.appendS32(stageNum);
-    GrStringBuilder coordVar("coord");
-    coordVar.appendS32(stageNum);
-    bool isDilate = StageDesc::kDilate_FetchMode == desc.fFetchMode;
-
-   if (isDilate) {
-        segments->fFSCode.appendf("\tvec4 %s = vec4(0, 0, 0, 0);\n",
-                                  valueVar.c_str());
-    } else {
-        segments->fFSCode.appendf("\tvec4 %s = vec4(1, 1, 1, 1);\n",
-                                  valueVar.c_str());
-    }
-    segments->fFSCode.appendf("\tvec2 %s = %s;\n", 
-                              coordVar.c_str(),
-                              segments->fSampleCoords.c_str());
-    segments->fFSCode.appendf("\tfor (int i = 0; i < %d; i++) {\n",
-                              desc.fKernelWidth * 2 + 1);
-    segments->fFSCode.appendf("\t\t%s = %s(%s, %s(%s, %s)%s);\n",
-                              valueVar.c_str(), isDilate ? "max" : "min",
-                              valueVar.c_str(), texFunc.c_str(),
-                              samplerName, coordVar.c_str(),
-                              segments->fSwizzle.c_str());
-    segments->fFSCode.appendf("\t\t%s += %s;\n",
-                              coordVar.c_str(),
-                              imageIncrementName);
-    segments->fFSCode.appendf("\t}\n");
-    segments->fFSCode.appendf("\t%s = %s%s;\n", fsOutColor,
-                              valueVar.c_str(), segments->fModulate.c_str());
-}
-
 }
 
 void GrGLProgram::genStageCode(const GrGLContextInfo& gl,
@@ -1562,12 +1476,6 @@
 
     GrGLShaderVar* kernel = NULL;
     const char* imageIncrementName = NULL;
-    if (StageDesc::kDilate_FetchMode == desc.fFetchMode ||
-               StageDesc::kErode_FetchMode == desc.fFetchMode) {
-        genMorphologyVS(stageNum, desc, segments, locations,
-                        &imageIncrementName, varyingVSName);
-    }
-
     if (NULL != customStage) {
         segments->fVSCode.appendf("\t{ // stage %d %s\n",
                                   stageNum, customStage->name());
@@ -1677,15 +1585,6 @@
             gen2x2FS(stageNum, segments, locations,
                 samplerName.c_str(), texelSizeName, fsOutColor, texFunc);
             break;
-        case StageDesc::kConvolution_FetchMode:
-            GrAssert(!(desc.fInConfigFlags & kMulByAlphaMask));
-            break;
-        case StageDesc::kDilate_FetchMode:
-        case StageDesc::kErode_FetchMode:
-            GrAssert(!(desc.fInConfigFlags & kMulByAlphaMask));
-            genMorphologyFS(stageNum, desc, segments,
-                samplerName.c_str(), imageIncrementName, fsOutColor, texFunc);
-            break;
         default:
             if (desc.fInConfigFlags & kMulByAlphaMask) {
                 // only one of the mul by alpha flags should be set
diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h
index 189e876..bfb1f1c 100644
--- a/src/gpu/gl/GrGLProgram.h
+++ b/src/gpu/gl/GrGLProgram.h
@@ -106,13 +106,10 @@
                 kCustomTextureDomain_OptFlagBit = 1 << 2,
                 kIsEnabled_OptFlagBit           = 1 << 7
             };
-            // Convolution is obsolete; left in for testing only
+
             enum FetchMode {
                 kSingle_FetchMode,
                 k2x2_FetchMode,
-                kConvolution_FetchMode,
-                kErode_FetchMode,
-                kDilate_FetchMode,
 
                 kFetchModeCnt,
             };
@@ -179,7 +176,6 @@
             uint8_t fInConfigFlags; // bitfield of InConfigFlags values
             uint8_t fFetchMode;     // casts to enum FetchMode
             uint8_t fCoordMapping;  // casts to enum CoordMapping
-            uint8_t fKernelWidth;
 
             /** Non-zero if user-supplied code will write the stage's
                 contribution to the fragment shader. */
@@ -268,16 +264,12 @@
         GrGLint fSamplerUni;
         GrGLint fRadial2Uni;
         GrGLint fTexDomUni;
-        GrGLint fKernelUni;
-        GrGLint fImageIncrementUni;
         void reset() {
             fTextureMatrixUni = kUnusedUniform;
             fNormalizedTexelSizeUni = kUnusedUniform;
             fSamplerUni = kUnusedUniform;
             fRadial2Uni = kUnusedUniform;
             fTexDomUni = kUnusedUniform;
-            fKernelUni = kUnusedUniform;
-            fImageIncrementUni = kUnusedUniform;
         }
     };
 
diff --git a/src/gpu/gl/GrGLProgramStage.cpp b/src/gpu/gl/GrGLProgramStage.cpp
index 70d1186..487b468 100644
--- a/src/gpu/gl/GrGLProgramStage.cpp
+++ b/src/gpu/gl/GrGLProgramStage.cpp
@@ -25,11 +25,9 @@
 
 }
 
-void GrGLProgramStage::setData(const GrGLInterface*, 
-                               const GrGLTexture&, 
-                               GrCustomStage*,
+void GrGLProgramStage::setData(const GrGLInterface*,
+                               const GrGLTexture&,
+                               const GrCustomStage&,
                                int stageNum) {
-
-
 }
 
diff --git a/src/gpu/gl/GrGLProgramStage.h b/src/gpu/gl/GrGLProgramStage.h
index 52d7200..e10d13b 100644
--- a/src/gpu/gl/GrGLProgramStage.h
+++ b/src/gpu/gl/GrGLProgramStage.h
@@ -32,7 +32,12 @@
 class GrGLProgramStage {
 
 public:
-    typedef GrCustomStage::StageKey StageKey ;
+    typedef GrCustomStage::StageKey StageKey;
+    enum {
+        // the number of bits in StageKey available to GenKey
+        kProgramStageKeyBits = GrProgramStageFactory::kProgramStageKeyBits,
+    };
+
     // TODO: redundant with GrGLProgram.cpp
     enum {
         kUnusedUniform = -1,
@@ -79,7 +84,7 @@
         created in emit*(). */
     virtual void setData(const GrGLInterface* gl,
                          const GrGLTexture& texture,
-                         GrCustomStage* stage,
+                         const GrCustomStage& stage,
                          int stageNum);
 
     const char* name() const { return fFactory.name(); }
diff --git a/src/gpu/gl/GrGpuGL.cpp b/src/gpu/gl/GrGpuGL.cpp
index 8b28c9f..29c7900 100644
--- a/src/gpu/gl/GrGpuGL.cpp
+++ b/src/gpu/gl/GrGpuGL.cpp
@@ -2102,9 +2102,6 @@
         case GrSamplerState::k4x4Downsample_Filter:
             return GR_GL_LINEAR;
         case GrSamplerState::kNearest_Filter:
-        case GrSamplerState::kConvolution_Filter:
-        case GrSamplerState::kErode_Filter:
-        case GrSamplerState::kDilate_Filter:
             return GR_GL_NEAREST;
         default:
             GrAssert(!"Unknown filter type");
diff --git a/src/gpu/gl/GrGpuGL_program.cpp b/src/gpu/gl/GrGpuGL_program.cpp
index 2ac26d5..2448fa4 100644
--- a/src/gpu/gl/GrGpuGL_program.cpp
+++ b/src/gpu/gl/GrGpuGL_program.cpp
@@ -7,8 +7,10 @@
 
 #include "GrGpuGL.h"
 
-#include "GrBinHashKey.h"
 #include "effects/GrConvolutionEffect.h"
+#include "effects/GrMorphologyEffect.h"
+
+#include "GrBinHashKey.h"
 #include "GrCustomStage.h"
 #include "GrGLProgramStage.h"
 #include "GrGLSL.h"
@@ -176,6 +178,75 @@
     return r->nextF() > .5f;
 }
 
+typedef GrGLProgram::StageDesc StageDesc;
+// TODO: Effects should be able to register themselves for inclusion in the
+// randomly generated shaders. They should be able to configure themselves
+// randomly.
+GrCustomStage* create_random_effect(StageDesc* stageDesc,
+                                    GrRandom* random) {
+    enum EffectType {
+        kConvolution_EffectType,
+        kErode_EffectType,
+        kDilate_EffectType,
+
+        kEffectCount
+    };
+
+    // TODO: Remove this when generator doesn't apply this non-custom-stage
+    // notion to custom stages automatically.
+    static const uint32_t kMulByAlphaMask =
+        StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag |
+        StageDesc::kMulRGBByAlpha_RoundDown_InConfigFlag;
+
+    static const Gr1DKernelEffect::Direction gKernelDirections[] = {
+        Gr1DKernelEffect::kX_Direction,
+        Gr1DKernelEffect::kY_Direction
+    };
+
+    // TODO: When matrices are property of the custom-stage then remove the
+    // no-persp flag code below.
+    int effect = random_int(random, kEffectCount);
+    switch (effect) {
+        case kConvolution_EffectType: {
+            int direction = random_int(random, 2);
+            int kernelRadius = random_int(random, 1, 4);
+            float kernel[GrConvolutionEffect::kMaxKernelWidth];
+            for (int i = 0; i < GrConvolutionEffect::kMaxKernelWidth; i++) {
+                kernel[i] = random->nextF();
+            }
+            // does not work with perspective or mul-by-alpha-mask
+            stageDesc->fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
+            stageDesc->fInConfigFlags &= ~kMulByAlphaMask;
+            return new GrConvolutionEffect(gKernelDirections[direction],
+                                           kernelRadius,
+                                           kernel);
+            }
+        case kErode_EffectType: {
+            int direction = random_int(random, 2);
+            int kernelRadius = random_int(random, 1, 4);
+            // does not work with perspective or mul-by-alpha-mask
+            stageDesc->fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
+            stageDesc->fInConfigFlags &= ~kMulByAlphaMask;
+            return new GrMorphologyEffect(gKernelDirections[direction],
+                                          kernelRadius,
+                                          GrContext::kErode_MorphologyType);
+            }
+        case kDilate_EffectType: {
+            int direction = random_int(random, 2);
+            int kernelRadius = random_int(random, 1, 4);
+            // does not work with perspective or mul-by-alpha-mask
+            stageDesc->fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
+            stageDesc->fInConfigFlags &= ~kMulByAlphaMask;
+            return new GrMorphologyEffect(gKernelDirections[direction],
+                                          kernelRadius,
+                                          GrContext::kDilate_MorphologyType);
+            }
+        default:
+            GrCrash("Unexpected custom effect type");
+    }
+    return NULL;
+}
+
 }
 
 bool GrGpuGL::programUnitTest() {
@@ -250,7 +321,7 @@
             pdesc.fDualSrcOutput = ProgramDesc::kNone_DualSrcOutput;
         }
 
-        GrCustomStage* customStages[GrDrawState::kNumStages];
+        SkAutoTUnref<GrCustomStage> customStages[GrDrawState::kNumStages];
 
         for (int s = 0; s < GrDrawState::kNumStages; ++s) {
             // enable the stage?
@@ -270,56 +341,34 @@
             StageDesc& stage = pdesc.fStages[s];
 
             stage.fCustomStageKey = 0;
-            customStages[s] = NULL;
 
             stage.fOptFlags = STAGE_OPTS[random_int(&random, GR_ARRAY_COUNT(STAGE_OPTS))];
             stage.fInConfigFlags = IN_CONFIG_FLAGS[random_int(&random, GR_ARRAY_COUNT(IN_CONFIG_FLAGS))];
             stage.fCoordMapping =  random_int(&random, StageDesc::kCoordMappingCnt);
             stage.fFetchMode = random_int(&random, StageDesc::kFetchModeCnt);
-            // convolution shaders don't work with persp tex matrix
-            if (stage.fFetchMode == StageDesc::kConvolution_FetchMode ||
-                stage.fFetchMode == StageDesc::kDilate_FetchMode ||
-                stage.fFetchMode == StageDesc::kErode_FetchMode) {
-                stage.fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
-            }
             stage.setEnabled(VertexUsesStage(s, pdesc.fVertexLayout));
             static const uint32_t kMulByAlphaMask =
                 StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag |
                 StageDesc::kMulRGBByAlpha_RoundDown_InConfigFlag;
 
-            switch (stage.fFetchMode) {
-                case StageDesc::kSingle_FetchMode:
-                    stage.fKernelWidth = 0;
-                    break;
-                case StageDesc::kConvolution_FetchMode:
-                case StageDesc::kDilate_FetchMode:
-                case StageDesc::kErode_FetchMode:
-                    stage.fKernelWidth = random_int(&random, 2, 8);
-                    stage.fInConfigFlags &= ~kMulByAlphaMask;
-                    break;
-                case StageDesc::k2x2_FetchMode:
-                    stage.fKernelWidth = 0;
-                    stage.fInConfigFlags &= ~kMulByAlphaMask;
-                    break;
+            if (StageDesc::k2x2_FetchMode == stage.fFetchMode) {
+                stage.fInConfigFlags &= ~kMulByAlphaMask;
             }
 
-            // TODO: is there a more elegant way to express this?
-            if (stage.fFetchMode == StageDesc::kConvolution_FetchMode) {
-                int direction = random_int(&random, 2);
-                float kernel[MAX_KERNEL_WIDTH];
-                for (int i = 0; i < stage.fKernelWidth; i++) {
-                    kernel[i] = random.nextF();
+            bool useCustomEffect = random_bool(&random);
+            if (useCustomEffect) {
+                customStages[s].reset(create_random_effect(&stage, &random));
+                if (NULL != customStages[s]) {
+                    stage.fCustomStageKey =
+                        customStages[s]->getFactory().glStageKey(*customStages[s]);
                 }
-                customStages[s] = new GrConvolutionEffect(
-                    (GrSamplerState::FilterDirection)direction,
-                    stage.fKernelWidth, kernel);
-                stage.fCustomStageKey =
-                    customStages[s]->getFactory().glStageKey(customStages[s]);
             }
         }
         CachedData cachedData;
-        if (!program.genProgram(this->glContextInfo(), customStages,
-                                &cachedData)) {
+        GR_STATIC_ASSERT(sizeof(customStages) ==
+                         GrDrawState::kNumStages * sizeof(GrCustomStage*));
+        GrCustomStage** stages = reinterpret_cast<GrCustomStage**>(&customStages);
+        if (!program.genProgram(this->glContextInfo(), stages, &cachedData)) {
             return false;
         }
         DeleteProgram(this->glInterface(), &cachedData);
@@ -469,32 +518,6 @@
     }
 }
 
-void GrGpuGL::flushConvolution(int s) {
-    const GrSamplerState& sampler = this->getDrawState().getSampler(s);
-    int kernelUni = fProgramData->fUniLocations.fStages[s].fKernelUni;
-    if (GrGLProgram::kUnusedUniform != kernelUni) {
-        GL_CALL(Uniform1fv(kernelUni, sampler.getKernelWidth(),
-                           sampler.getKernel()));
-    }
-    int imageIncrementUni = fProgramData->fUniLocations.fStages[s].fImageIncrementUni;
-    if (GrGLProgram::kUnusedUniform != imageIncrementUni) {
-        const GrGLTexture* texture =
-            static_cast<const GrGLTexture*>(this->getDrawState().getTexture(s));
-        float imageIncrement[2] = { 0 };
-        switch (sampler.getFilterDirection()) {
-            case GrSamplerState::kX_FilterDirection:
-                imageIncrement[0] = 1.0f / texture->width();
-                break;
-            case GrSamplerState::kY_FilterDirection:
-                imageIncrement[1] = 1.0f / texture->height();
-                break;
-            default:
-                GrCrash("Unknown filter direction.");
-        }
-        GL_CALL(Uniform2fv(imageIncrementUni, 1, imageIncrement));
-    }
-}
-
 void GrGpuGL::flushTexelSize(int s) {
     const int& uni = fProgramData->fUniLocations.fStages[s].fNormalizedTexelSizeUni;
     if (GrGLProgram::kUnusedUniform != uni) {
@@ -692,8 +715,6 @@
 
             this->flushRadial2(s);
 
-            this->flushConvolution(s);
-
             this->flushTexelSize(s);
 
             this->flushTextureDomain(s);
@@ -706,7 +727,7 @@
                         this->getDrawState().getTexture(s));
                 fProgramData->fCustomStage[s]->setData(
                     this->glInterface(), *texture,
-                    sampler.getCustomStage(), s);
+                    *sampler.getCustomStage(), s);
             }
         }
     }
@@ -875,7 +896,7 @@
     GrCustomStage* customStage = sampler.getCustomStage();
     if (customStage) {
         const GrProgramStageFactory& factory = customStage->getFactory();
-        stage->fCustomStageKey = factory.glStageKey(customStage);
+        stage->fCustomStageKey = factory.glStageKey(*customStage);
         customStages[index] = customStage;
     } else {
         stage->fCustomStageKey = 0;
@@ -1027,16 +1048,6 @@
                 case GrSamplerState::k4x4Downsample_Filter:
                     stage.fFetchMode = StageDesc::k2x2_FetchMode;
                     break;
-                // performs fKernelWidth texture2D()s
-                case GrSamplerState::kConvolution_Filter:
-                    stage.fFetchMode = StageDesc::kConvolution_FetchMode;
-                    break;
-                case GrSamplerState::kDilate_Filter:
-                    stage.fFetchMode = StageDesc::kDilate_FetchMode;
-                    break;
-                case GrSamplerState::kErode_Filter:
-                    stage.fFetchMode = StageDesc::kErode_FetchMode;
-                    break;
                 default:
                     GrCrash("Unexpected filter!");
                     break;
@@ -1083,13 +1094,6 @@
                 }
             }
 
-            if (sampler.getFilter() == GrSamplerState::kDilate_Filter ||
-                sampler.getFilter() == GrSamplerState::kErode_Filter) {
-                stage.fKernelWidth = sampler.getKernelWidth();
-            } else {
-                stage.fKernelWidth = 0;
-            }
-
             setup_custom_stage(&stage, sampler, customStages,
                                &fCurrentProgram, s);
 
@@ -1098,7 +1102,6 @@
             stage.fCoordMapping     = (StageDesc::CoordMapping) 0;
             stage.fInConfigFlags    = 0;
             stage.fFetchMode        = (StageDesc::FetchMode) 0;
-            stage.fKernelWidth      = 0;
             stage.fCustomStageKey   = 0;
             customStages[s] = NULL;
         }