Make GrPaint have a variable sized array of color and coverage stages rather than a fixed size.

R=robertphillips@google.com, jvanverth@google.com

Author: bsalomon@google.com

Review URL: https://chromiumcodereview.appspot.com/18686007

git-svn-id: http://skia.googlecode.com/svn/trunk@10062 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/texdata.cpp b/gm/texdata.cpp
index 6281825..cfb27f6 100644
--- a/gm/texdata.cpp
+++ b/gm/texdata.cpp
@@ -113,7 +113,7 @@
                 SkMatrix tm;
                 tm = vm;
                 tm.postIDiv(2*S, 2*S);
-                paint.colorStage(0)->setEffect(GrSimpleTextureEffect::Create(texture, tm))->unref();
+                paint.addColorTextureEffect(texture, tm);
 
                 ctx->drawRect(paint, GrRect::MakeWH(2*S, 2*S));
 
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi
index 2191ff2..fff7922 100644
--- a/gyp/gpu.gypi
+++ b/gyp/gpu.gypi
@@ -84,6 +84,7 @@
       '<(skia_src_path)/gpu/GrMemoryPool.cpp',
       '<(skia_src_path)/gpu/GrMemoryPool.h',
       '<(skia_src_path)/gpu/GrOvalRenderer.cpp',
+      '<(skia_src_path)/gpu/GrPaint.cpp',
       '<(skia_src_path)/gpu/GrPath.cpp',
       '<(skia_src_path)/gpu/GrPath.h',
       '<(skia_src_path)/gpu/GrPathRendererChain.cpp',
diff --git a/include/gpu/GrPaint.h b/include/gpu/GrPaint.h
index 25ec699..9e326f0 100644
--- a/include/gpu/GrPaint.h
+++ b/include/gpu/GrPaint.h
@@ -43,11 +43,6 @@
  */
 class GrPaint {
 public:
-    enum {
-        kMaxColorStages     = 3,
-        kMaxCoverageStages  = 1,
-    };
-
     GrPaint() { this->reset(); }
 
     GrPaint(const GrPaint& paint) { *this = paint; }
@@ -111,63 +106,43 @@
     }
 
     /**
-     * Specifies a stage of the color pipeline. Usually the texture matrices of color stages apply
-     * to the primitive's positions. Some GrContext calls take explicit coords as an array or a
-     * rect. In this case these are the pre-matrix coords to colorStage(0).
+     * Appends an additional color effect to the color computation.
      */
-    GrEffectStage* colorStage(int i) {
-        GrAssert((unsigned)i < kMaxColorStages);
-        return fColorStages + i;
-    }
-
-    const GrEffectStage& getColorStage(int i) const {
-        GrAssert((unsigned)i < kMaxColorStages);
-        return fColorStages[i];
-    }
-
-    bool isColorStageEnabled(int i) const {
-        GrAssert((unsigned)i < kMaxColorStages);
-        return (NULL != fColorStages[i].getEffect());
+    const GrEffectRef* addColorEffect(const GrEffectRef* effect, int attr0 = -1, int attr1 = -1) {
+        GrAssert(NULL != effect);
+        SkNEW_APPEND_TO_TARRAY(&fColorStages, GrEffectStage, (effect, attr0, attr1));
+        return effect;
     }
 
     /**
-     * Specifies a stage of the coverage pipeline. Coverage stages' texture matrices are always
-     * applied to the primitive's position, never to explicit texture coords.
+     * Appends an additional coverage effect to the coverage computation.
      */
-    GrEffectStage* coverageStage(int i) {
-        GrAssert((unsigned)i < kMaxCoverageStages);
-        return fCoverageStages + i;
+    const GrEffectRef* addCoverageEffect(const GrEffectRef* effect, int attr0 = -1, int attr1 = -1) {
+        GrAssert(NULL != effect);
+        SkNEW_APPEND_TO_TARRAY(&fCoverageStages, GrEffectStage, (effect, attr0, attr1));
+        return effect;
     }
 
-    const GrEffectStage& getCoverageStage(int i) const {
-        GrAssert((unsigned)i < kMaxCoverageStages);
-        return fCoverageStages[i];
-    }
+    /**
+     * Helpers for adding color or coverage effects that sample a texture. The matrix is applied
+     * to the src space position to compute texture coordinates.
+     */
+    void addColorTextureEffect(GrTexture* texture, const SkMatrix& matrix);
+    void addCoverageTextureEffect(GrTexture* texture, const SkMatrix& matrix);
 
-    bool isCoverageStageEnabled(int i) const {
-        GrAssert((unsigned)i < kMaxCoverageStages);
-        return (NULL != fCoverageStages[i].getEffect());
-    }
+    void addColorTextureEffect(GrTexture* texture,
+                               const SkMatrix& matrix,
+                               const GrTextureParams& params);
+    void addCoverageTextureEffect(GrTexture* texture,
+                                  const SkMatrix& matrix,
+                                  const GrTextureParams& params);
 
-    bool hasCoverageStage() const {
-        for (int i = 0; i < kMaxCoverageStages; ++i) {
-            if (this->isCoverageStageEnabled(i)) {
-                return true;
-            }
-        }
-        return false;
-    }
+    int numColorStages() const { return fColorStages.count(); }
+    int numCoverageStages() const { return fCoverageStages.count(); }
+    int numTotalStages() const { return this->numColorStages() + this->numCoverageStages(); }
 
-    bool hasColorStage() const {
-        for (int i = 0; i < kMaxColorStages; ++i) {
-            if (this->isColorStageEnabled(i)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    bool hasStage() const { return this->hasColorStage() || this->hasCoverageStage(); }
+    const GrEffectStage& getColorStage(int s) const { return fColorStages[s]; }
+    const GrEffectStage& getCoverageStage(int s) const { return fCoverageStages[s]; }
 
     GrPaint& operator=(const GrPaint& paint) {
         fSrcBlendCoeff = paint.fSrcBlendCoeff;
@@ -181,16 +156,9 @@
         fColorFilterColor = paint.fColorFilterColor;
         fColorFilterXfermode = paint.fColorFilterXfermode;
 
-        for (int i = 0; i < kMaxColorStages; ++i) {
-            if (paint.isColorStageEnabled(i)) {
-                fColorStages[i] = paint.fColorStages[i];
-            }
-        }
-        for (int i = 0; i < kMaxCoverageStages; ++i) {
-            if (paint.isCoverageStageEnabled(i)) {
-                fCoverageStages[i] = paint.fCoverageStages[i];
-            }
-        }
+        fColorStages = paint.fColorStages;
+        fCoverageStages = paint.fCoverageStages;
+
         return *this;
     }
 
@@ -206,15 +174,6 @@
         this->resetColorFilter();
     }
 
-    // internal use
-    // GrPaint's textures and masks map to the first N stages
-    // of GrDrawTarget in that order (textures followed by masks)
-    enum {
-        kFirstColorStage = 0,
-        kFirstCoverageStage = kMaxColorStages,
-        kTotalStages = kFirstColorStage + kMaxColorStages + kMaxCoverageStages,
-    };
-
 private:
     /**
      * Called when the source coord system from which geometry is rendered changes. It ensures that
@@ -222,48 +181,40 @@
      * from the previous coord system to the new coord system.
      */
     void localCoordChange(const SkMatrix& oldToNew) {
-        for (int i = 0; i < kMaxColorStages; ++i) {
-            if (this->isColorStageEnabled(i)) {
-                fColorStages[i].localCoordChange(oldToNew);
-            }
+        for (int i = 0; i < fColorStages.count(); ++i) {
+            fColorStages[i].localCoordChange(oldToNew);
         }
-        for (int i = 0; i < kMaxCoverageStages; ++i) {
-            if (this->isCoverageStageEnabled(i)) {
-                fCoverageStages[i].localCoordChange(oldToNew);
-            }
+        for (int i = 0; i < fCoverageStages.count(); ++i) {
+            fCoverageStages[i].localCoordChange(oldToNew);
         }
     }
 
     bool localCoordChangeInverse(const SkMatrix& newToOld) {
         SkMatrix oldToNew;
         bool computed = false;
-        for (int i = 0; i < kMaxColorStages; ++i) {
-            if (this->isColorStageEnabled(i)) {
-                if (!computed && !newToOld.invert(&oldToNew)) {
-                    return false;
-                } else {
-                    computed = true;
-                }
-                fColorStages[i].localCoordChange(oldToNew);
+        for (int i = 0; i < fColorStages.count(); ++i) {
+            if (!computed && !newToOld.invert(&oldToNew)) {
+                return false;
+            } else {
+                computed = true;
             }
+            fColorStages[i].localCoordChange(oldToNew);
         }
-        for (int i = 0; i < kMaxCoverageStages; ++i) {
-            if (this->isCoverageStageEnabled(i)) {
-                if (!computed && !newToOld.invert(&oldToNew)) {
-                    return false;
-                } else {
-                    computed = true;
-                }
-                fCoverageStages[i].localCoordChange(oldToNew);
+        for (int i = 0; i < fCoverageStages.count(); ++i) {
+            if (!computed && !newToOld.invert(&oldToNew)) {
+                return false;
+            } else {
+                computed = true;
             }
+            fCoverageStages[i].localCoordChange(oldToNew);
         }
         return true;
     }
 
     friend class GrContext; // To access above two functions
 
-    GrEffectStage               fColorStages[kMaxColorStages];
-    GrEffectStage               fCoverageStages[kMaxCoverageStages];
+    SkSTArray<4, GrEffectStage> fColorStages;
+    SkSTArray<2, GrEffectStage> fCoverageStages;
 
     GrBlendCoeff                fSrcBlendCoeff;
     GrBlendCoeff                fDstBlendCoeff;
@@ -295,12 +246,8 @@
     }
 
     void resetStages() {
-        for (int i = 0; i < kMaxColorStages; ++i) {
-            fColorStages[i].reset();
-        }
-        for (int i = 0; i < kMaxCoverageStages; ++i) {
-            fCoverageStages[i].reset();
-        }
+        fColorStages.reset();
+        fCoverageStages.reset();
     }
 };
 
diff --git a/include/gpu/SkGpuDevice.h b/include/gpu/SkGpuDevice.h
index 0dd363f..b0b1559 100644
--- a/include/gpu/SkGpuDevice.h
+++ b/include/gpu/SkGpuDevice.h
@@ -152,7 +152,6 @@
                                                Usage usage) SK_OVERRIDE;
 
     SkDrawProcs* initDrawForText(GrTextContext*);
-    bool bindDeviceAsTexture(GrPaint* paint);
 
     // sets the render target, clip, and matrix on GrContext. Use forceIdenity to override
     // SkDraw's matrix and draw in device coords.
@@ -178,12 +177,12 @@
                             const SkRect&,
                             const SkMatrix&,
                             const GrTextureParams& params,
-                            GrPaint* grPaint);
+                            const SkPaint& paint);
     void drawTiledBitmap(const SkBitmap& bitmap,
                          const SkRect& srcRect,
                          const SkMatrix& m,
                          const GrTextureParams& params,
-                         GrPaint* grPaint);
+                         const SkPaint& paint);
 
     /**
      * Returns non-initialized instance.
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 33caf75..ddd6449 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -127,7 +127,7 @@
     SkASSERT(effect);
     SkAutoUnref effectRef(effect);
     GrPaint paint;
-    paint.colorStage(0)->setEffect(effect);
+    paint.addColorEffect(effect);
     context->drawRect(paint, rect);
     SkAutoTUnref<GrTexture> resultTex(dst.detach());
     SkImageFilterUtils::WrapTexture(resultTex, input.width(), input.height(), result);
diff --git a/src/effects/SkBicubicImageFilter.cpp b/src/effects/SkBicubicImageFilter.cpp
index a97a768..5161f6d 100644
--- a/src/effects/SkBicubicImageFilter.cpp
+++ b/src/effects/SkBicubicImageFilter.cpp
@@ -365,7 +365,7 @@
     }
     GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
     GrPaint paint;
-    paint.colorStage(0)->setEffect(GrBicubicEffect::Create(srcTexture, fCoefficients))->unref();
+    paint.addColorEffect(GrBicubicEffect::Create(srcTexture, fCoefficients))->unref();
     SkRect srcRect;
     srcBM.getBounds(&srcRect);
     context->drawRectToRect(paint, dstRect, srcRect);
diff --git a/src/effects/SkBlendImageFilter.cpp b/src/effects/SkBlendImageFilter.cpp
index 1c40ca8..9aae35a 100644
--- a/src/effects/SkBlendImageFilter.cpp
+++ b/src/effects/SkBlendImageFilter.cpp
@@ -194,8 +194,9 @@
     GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
 
     GrPaint paint;
-    paint.colorStage(0)->setEffect(
-        GrBlendEffect::Create(fMode, foreground, foregroundOffset, background, backgroundOffset))->unref();
+    paint.addColorEffect(GrBlendEffect::Create(fMode,
+                                               foreground, foregroundOffset,
+                                               background, backgroundOffset))->unref();
     SkRect srcRect;
     src.getBounds(&srcRect);
     context->drawRect(paint, srcRect);
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index 85176d6..66a07c4 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -423,8 +423,7 @@
         matrix.setIDiv(src->width(), src->height());
         // Blend pathTexture over blurTexture.
         GrContext::AutoRenderTarget art(context, (*result)->asRenderTarget());
-        paint.colorStage(0)->setEffect(
-            GrSimpleTextureEffect::Create(src, matrix))->unref();
+        paint.addColorEffect(GrSimpleTextureEffect::Create(src, matrix))->unref();
         if (SkBlurMaskFilter::kInner_BlurStyle == fBlurStyle) {
             // inner:  dst = dst * src
             paint.setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff);
diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp
index 8d287b2..7b64d1d 100644
--- a/src/effects/SkDisplacementMapEffect.cpp
+++ b/src/effects/SkDisplacementMapEffect.cpp
@@ -305,7 +305,7 @@
     GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
 
     GrPaint paint;
-    paint.colorStage(0)->setEffect(
+    paint.addColorEffect(
         GrDisplacementMapEffect::Create(fXChannelSelector,
                                         fYChannelSelector,
                                         fScale,
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index cb17c90..4783abd 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -452,10 +452,10 @@
                            GrMorphologyEffect::MorphologyType morphType,
                            Gr1DKernelEffect::Direction direction) {
     GrPaint paint;
-    paint.colorStage(0)->setEffect(GrMorphologyEffect::Create(texture,
-                                                              direction,
-                                                              radius,
-                                                              morphType))->unref();
+    paint.addColorEffect(GrMorphologyEffect::Create(texture,
+                                                    direction,
+                                                    radius,
+                                                    morphType))->unref();
     context->drawRect(paint, SkRect::MakeFromIRect(rect));
 }
 
diff --git a/src/effects/SkXfermodeImageFilter.cpp b/src/effects/SkXfermodeImageFilter.cpp
index a318ceb..3df59a8 100644
--- a/src/effects/SkXfermodeImageFilter.cpp
+++ b/src/effects/SkXfermodeImageFilter.cpp
@@ -119,23 +119,23 @@
                                   SkIntToScalar(backgroundOffset.fY-foregroundOffset.fY));
 
 
-    GrPaint paint;
     SkRect srcRect;
     src.getBounds(&srcRect);
     if (NULL != xferEffect) {
-        paint.colorStage(0)->setEffect(
-            GrSimpleTextureEffect::Create(foregroundTex, foregroundMatrix))->unref();
-        paint.colorStage(1)->setEffect(xferEffect)->unref();
+        GrPaint paint;
+        paint.addColorTextureEffect(foregroundTex, foregroundMatrix);
+        paint.addColorEffect(xferEffect)->unref();
         context->drawRect(paint, srcRect);
     } else {
+        GrPaint backgroundPaint;
         SkMatrix backgroundMatrix = GrEffect::MakeDivByTextureWHMatrix(backgroundTex);
-        paint.colorStage(0)->setEffect(
-            GrSimpleTextureEffect::Create(backgroundTex, backgroundMatrix))->unref();
-        context->drawRect(paint, srcRect);
-        paint.setBlendFunc(sk_blend_to_grblend(sm), sk_blend_to_grblend(dm));
-        paint.colorStage(0)->setEffect(
-            GrSimpleTextureEffect::Create(foregroundTex, foregroundMatrix))->unref();
-        context->drawRect(paint, srcRect);
+        backgroundPaint.addColorTextureEffect(backgroundTex, backgroundMatrix);
+        context->drawRect(backgroundPaint, srcRect);
+
+        GrPaint foregroundPaint;
+        foregroundPaint.setBlendFunc(sk_blend_to_grblend(sm), sk_blend_to_grblend(dm));
+        foregroundPaint.addColorTextureEffect(foregroundTex, foregroundMatrix);
+        context->drawRect(foregroundPaint, srcRect);
     }
     offset->fX += backgroundOffset.fX;
     offset->fY += backgroundOffset.fY;
diff --git a/src/gpu/GrAAConvexPathRenderer.cpp b/src/gpu/GrAAConvexPathRenderer.cpp
index 5d461ab..408fcb5 100644
--- a/src/gpu/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/GrAAConvexPathRenderer.cpp
@@ -637,12 +637,6 @@
 
     drawState->setVertexAttribs<gPathAttribs>(SK_ARRAY_COUNT(gPathAttribs));
 
-    enum {
-        // the edge effects share this stage with glyph rendering
-        // (kGlyphMaskStage in GrTextContext) && SW path rendering
-        // (kPathMaskStage in GrSWMaskHelper)
-        kEdgeEffectStage = GrPaint::kTotalStages,
-    };
     static const int kEdgeAttrIndex = 1;
     GrEffectRef* quadEffect = QuadEdgeEffect::Create();
     drawState->addCoverageEffect(quadEffect, kEdgeAttrIndex)->unref();
diff --git a/src/gpu/GrAAHairLinePathRenderer.cpp b/src/gpu/GrAAHairLinePathRenderer.cpp
index 332c6bb..85121bb 100644
--- a/src/gpu/GrAAHairLinePathRenderer.cpp
+++ b/src/gpu/GrAAHairLinePathRenderer.cpp
@@ -1100,12 +1100,6 @@
     // TODO: See whether rendering lines as degenerate quads improves perf
     // when we have a mix
 
-    enum {
-        // the edge effects share this stage with glyph rendering
-        // (kGlyphMaskStage in GrTextContext) && SW path rendering
-        // (kPathMaskStage in GrSWMaskHelper)
-        kEdgeEffectStage = GrPaint::kTotalStages,
-    };
     static const int kEdgeAttrIndex = 1;
 
     GrEffectRef* hairLineEffect = HairLineEdgeEffect::Create();
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index d9924bf..31d5ff1 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -1766,20 +1766,16 @@
         return NULL;
     }
 
-    GrPaint paint;
-    paint.reset();
-
     for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
+        GrPaint paint;
         SkMatrix matrix;
         matrix.setIDiv(srcTexture->width(), srcTexture->height());
         this->setRenderTarget(dstTexture->asRenderTarget());
         SkRect dstRect(srcRect);
         scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
                              i < scaleFactorY ? 0.5f : 1.0f);
-
-        paint.colorStage(0)->setEffect(GrSimpleTextureEffect::Create(srcTexture,
-                                                                     matrix,
-                                                                     true))->unref();
+        GrTextureParams params(SkShader::kClamp_TileMode, true);
+        paint.addColorTextureEffect(srcTexture, matrix, params);
         this->drawRectToRect(paint, dstRect, srcRect);
         srcRect = dstRect;
         srcTexture = dstTexture;
@@ -1797,7 +1793,7 @@
                                           radiusX, srcIRect.height());
             this->clear(&clearRect, 0x0);
         }
-
+        GrPaint paint;
         this->setRenderTarget(dstTexture->asRenderTarget());
         AutoRestoreEffects are;
         GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW, &are);
@@ -1817,6 +1813,7 @@
         }
 
         this->setRenderTarget(dstTexture->asRenderTarget());
+        GrPaint paint;
         AutoRestoreEffects are;
         GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW, &are);
         convolve_gaussian(target, srcTexture, srcRect, sigmaY, radiusY,
@@ -1835,12 +1832,14 @@
                                       1, srcIRect.height());
         this->clear(&clearRect, 0x0);
         SkMatrix matrix;
-        // FIXME:  This should be mitchell, not bilinear.
         matrix.setIDiv(srcTexture->width(), srcTexture->height());
         this->setRenderTarget(dstTexture->asRenderTarget());
-        paint.colorStage(0)->setEffect(GrSimpleTextureEffect::Create(srcTexture,
-                                                                     matrix,
-                                                                     true))->unref();
+
+        GrPaint paint;
+        // FIXME:  This should be mitchell, not bilinear.
+        GrTextureParams params(SkShader::kClamp_TileMode, true);
+        paint.addColorTextureEffect(srcTexture, matrix, params);
+
         SkRect dstRect(srcRect);
         scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
         this->drawRectToRect(paint, dstRect, srcRect);
diff --git a/src/gpu/GrDrawState.cpp b/src/gpu/GrDrawState.cpp
index 7c68543..ab800d2 100644
--- a/src/gpu/GrDrawState.cpp
+++ b/src/gpu/GrDrawState.cpp
@@ -32,16 +32,12 @@
     fColorStages.reset();
     fCoverageStages.reset();
 
-    for (int i = 0; i < GrPaint::kMaxColorStages; ++i) {
-        if (paint.isColorStageEnabled(i)) {
-            fColorStages.push_back(paint.getColorStage(i));
-        }
+    for (int i = 0; i < paint.numColorStages(); ++i) {
+        fColorStages.push_back(paint.getColorStage(i));
     }
 
-    for (int i = 0; i < GrPaint::kMaxCoverageStages; ++i) {
-        if (paint.isCoverageStageEnabled(i)) {
-            fCoverageStages.push_back(paint.getCoverageStage(i));
-        }
+    for (int i = 0; i < paint.numCoverageStages(); ++i) {
+        fCoverageStages.push_back(paint.getCoverageStage(i));
     }
 
     this->setRenderTarget(rt);
diff --git a/src/gpu/GrPaint.cpp b/src/gpu/GrPaint.cpp
new file mode 100644
index 0000000..b05c3c3
--- /dev/null
+++ b/src/gpu/GrPaint.cpp
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrPaint.h"
+
+#include "effects/GrSimpleTextureEffect.h"
+
+void GrPaint::addColorTextureEffect(GrTexture* texture, const SkMatrix& matrix) {
+    GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix);
+    this->addColorEffect(effect)->unref();
+}
+
+void GrPaint::addCoverageTextureEffect(GrTexture* texture, const SkMatrix& matrix) {
+    GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix);
+    this->addCoverageEffect(effect)->unref();
+}
+
+void GrPaint::addColorTextureEffect(GrTexture* texture,
+                                    const SkMatrix& matrix,
+                                    const GrTextureParams& params) {
+    GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix, params);
+    this->addColorEffect(effect)->unref();
+}
+
+void GrPaint::addCoverageTextureEffect(GrTexture* texture,
+                                       const SkMatrix& matrix,
+                                       const GrTextureParams& params) {
+    GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix, params);
+    this->addCoverageEffect(effect)->unref();
+}
+
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index 0fab55c..c9f3989 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -192,12 +192,6 @@
         return;
     }
     GrDrawState::AutoRestoreEffects are(drawState);
-    enum {
-        // the SW path renderer shares this stage with glyph
-        // rendering (kGlyphMaskStage in GrTextContext)
-        // && edge rendering (kEdgeEffectStage in GrContext)
-        kPathMaskStage = GrPaint::kTotalStages,
-    };
 
     GrRect dstRect = GrRect::MakeLTRB(
                             SK_Scalar1 * rect.fLeft,
diff --git a/src/gpu/GrTextContext.cpp b/src/gpu/GrTextContext.cpp
index 65c5c7a..c929d0c 100644
--- a/src/gpu/GrTextContext.cpp
+++ b/src/gpu/GrTextContext.cpp
@@ -43,7 +43,7 @@
         if (!GrPixelConfigIsAlphaOnly(fCurrTexture->config())) {
             if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
                 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
-                fPaint.hasColorStage()) {
+                fPaint.numColorStages()) {
                 GrPrintf("LCD Text will not draw correctly.\n");
             }
             // setup blend so that we get mask * paintColor + (1-mask)*dstColor
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 3b0d143..ab7693c 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -38,15 +38,6 @@
     #define CHECK_SHOULD_DRAW(draw, forceI) this->prepareDraw(draw, forceI)
 #endif
 
-// we use the same effect slot on GrPaint for bitmaps and shaders (since drawBitmap, drawSprite,
-// and drawDevice ignore SkShader)
-enum {
-    kShaderEffectIdx = 0,
-    kBitmapEffectIdx = 0,
-    kColorFilterEffectIdx = 1,
-    kXfermodeEffectIdx = 2,
-};
-
 // This constant represents the screen alignment criterion in texels for
 // requiring texture domain clamping to prevent color bleeding when drawing
 // a sub region of a larger source image.
@@ -442,16 +433,6 @@
     return fRenderTarget;
 }
 
-bool SkGpuDevice::bindDeviceAsTexture(GrPaint* paint) {
-    GrTexture* texture = fRenderTarget->asTexture();
-    if (NULL != texture) {
-        paint->colorStage(kBitmapEffectIdx)->setEffect(
-            GrSimpleTextureEffect::Create(texture, SkMatrix::I()))->unref();
-        return true;
-    }
-    return false;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 
 SK_COMPILE_ASSERT(SkShader::kNone_BitmapType == 0, shader_type_mismatch);
@@ -489,7 +470,7 @@
     GrEffectRef* xferEffect = NULL;
     if (SkXfermode::AsNewEffectOrCoeff(mode, dev->context(), &xferEffect, &sm, &dm)) {
         if (NULL != xferEffect) {
-            grPaint->colorStage(kXfermodeEffectIdx)->setEffect(xferEffect)->unref();
+            grPaint->addColorEffect(xferEffect)->unref();
             sm = SkXfermode::kOne_Coeff;
             dm = SkXfermode::kZero_Coeff;
         }
@@ -513,7 +494,6 @@
         GrAssert(!constantColor);
     } else {
         grPaint->setColor(SkColor2GrColor(skPaint.getColor()));
-        GrAssert(!grPaint->isColorStageEnabled(kShaderEffectIdx));
     }
 
     SkColorFilter* colorFilter = skPaint.getColorFilter();
@@ -526,7 +506,7 @@
         } else {
             SkAutoTUnref<GrEffectRef> effect(colorFilter->asNewEffect(dev->context()));
             if (NULL != effect.get()) {
-                grPaint->colorStage(kColorFilterEffectIdx)->setEffect(effect);
+                grPaint->addColorEffect(effect);
             } else {
                 // TODO: rewrite this using asNewEffect()
                 SkColor color;
@@ -552,32 +532,33 @@
     SkShader* shader = skPaint.getShader();
     if (NULL == shader) {
         return skPaint2GrPaintNoShader(dev, skPaint, false, constantColor, grPaint);
-    } else if (!skPaint2GrPaintNoShader(dev, skPaint, true, false, grPaint)) {
-        return false;
     }
 
+    // setup the shader as the first color effect on the paint
     SkAutoTUnref<GrEffectRef> effect(shader->asNewEffect(dev->context(), skPaint));
     if (NULL != effect.get()) {
-        grPaint->colorStage(kShaderEffectIdx)->setEffect(effect);
-        return true;
-    }
+        grPaint->addColorEffect(effect);
+        // Now setup the rest of the paint.
+        return skPaint2GrPaintNoShader(dev, skPaint, true, false, grPaint);
+    } else {
+        // We still don't have SkColorShader::asNewEffect() implemented.
+        SkShader::GradientInfo info;
+        SkColor                color;
 
-    // We still don't have SkColorShader::asNewEffect() implemented.
-    SkShader::GradientInfo info;
-    SkColor                color;
-
-    info.fColors = &color;
-    info.fColorOffsets = NULL;
-    info.fColorCount = 1;
-    if (SkShader::kColor_GradientType == shader->asAGradient(&info)) {
-        SkPaint copy(skPaint);
-        copy.setShader(NULL);
-        // modulate the paint alpha by the shader's solid color alpha
-        U8CPU newA = SkMulDiv255Round(SkColorGetA(color), copy.getAlpha());
-        copy.setColor(SkColorSetA(color, newA));
-        return skPaint2GrPaintNoShader(dev, copy, false, constantColor, grPaint);
+        info.fColors = &color;
+        info.fColorOffsets = NULL;
+        info.fColorCount = 1;
+        if (SkShader::kColor_GradientType == shader->asAGradient(&info)) {
+            SkPaint copy(skPaint);
+            copy.setShader(NULL);
+            // modulate the paint alpha by the shader's solid color alpha
+            U8CPU newA = SkMulDiv255Round(SkColorGetA(color), copy.getAlpha());
+            copy.setColor(SkColorSetA(color, newA));
+            return skPaint2GrPaintNoShader(dev, copy, false, constantColor, grPaint);
+        } else {
+            return false;
+        }
     }
-    return false;
 }
 }
 
@@ -769,22 +750,16 @@
 // Return true if the mask was successfully drawn.
 bool draw_mask(GrContext* context, const SkRect& maskRect,
                GrPaint* grp, GrTexture* mask) {
-
     GrContext::AutoMatrix am;
     if (!am.setIdentity(context, grp)) {
         return false;
     }
 
-    static const int MASK_IDX = GrPaint::kMaxCoverageStages - 1;
-    // we assume the last mask index is available for use
-    GrAssert(!grp->isCoverageStageEnabled(MASK_IDX));
-
     SkMatrix matrix;
     matrix.setTranslate(-maskRect.fLeft, -maskRect.fTop);
     matrix.postIDiv(mask->width(), mask->height());
 
-    grp->coverageStage(MASK_IDX)->reset();
-    grp->coverageStage(MASK_IDX)->setEffect(GrSimpleTextureEffect::Create(mask, matrix))->unref();
+    grp->addCoverageEffect(GrSimpleTextureEffect::Create(mask, matrix))->unref();
     context->drawRect(*grp, maskRect);
     return true;
 }
@@ -1169,20 +1144,14 @@
         return;
     }
 
-    GrPaint grPaint;
-
-    bool alphaOnly = !(SkBitmap::kA8_Config == bitmap.config());
-    if (!skPaint2GrPaintNoShader(this, paint, alphaOnly, false, &grPaint)) {
-        return;
-    }
     GrTextureParams params;
     params.setBilerp(paint.isFilterBitmap());
 
     if (!this->shouldTileBitmap(bitmap, params, srcRectPtr)) {
         // take the simple case
-        this->internalDrawBitmap(bitmap, srcRect, m, params, &grPaint);
+        this->internalDrawBitmap(bitmap, srcRect, m, params, paint);
     } else {
-        this->drawTiledBitmap(bitmap, srcRect, m, params, &grPaint);
+        this->drawTiledBitmap(bitmap, srcRect, m, params, paint);
     }
 }
 
@@ -1192,7 +1161,7 @@
                                   const SkRect& srcRect,
                                   const SkMatrix& m,
                                   const GrTextureParams& params,
-                                  GrPaint* grPaint) {
+                                  const SkPaint& paint) {
     const int maxTextureSize = fContext->getMaxTextureSize();
 
     int tileSize = determine_tile_size(bitmap, srcRect, maxTextureSize);
@@ -1241,7 +1210,7 @@
                 tmpM.preTranslate(SkIntToScalar(iTileR.fLeft),
                                   SkIntToScalar(iTileR.fTop));
 
-                this->internalDrawBitmap(tmpB, tileR, tmpM, params, grPaint);
+                this->internalDrawBitmap(tmpB, tileR, tmpM, params, paint);
             }
         }
     }
@@ -1301,7 +1270,7 @@
                                      const SkRect& srcRect,
                                      const SkMatrix& m,
                                      const GrTextureParams& params,
-                                     GrPaint* grPaint) {
+                                     const SkPaint& paint) {
     SkASSERT(bitmap.width() <= fContext->getMaxTextureSize() &&
              bitmap.height() <= fContext->getMaxTextureSize());
 
@@ -1371,8 +1340,17 @@
     } else {
         effect.reset(GrSimpleTextureEffect::Create(texture, SkMatrix::I(), params));
     }
-    grPaint->colorStage(kBitmapEffectIdx)->setEffect(effect);
-    fContext->drawRectToRect(*grPaint, dstRect, paintRect, &m);
+
+    // Construct a GrPaint by setting the bitmap texture as the first effect and then configuring
+    // the rest from the SkPaint.
+    GrPaint grPaint;
+    grPaint.addColorEffect(effect);
+    bool alphaOnly = !(SkBitmap::kA8_Config == bitmap.config());
+    if (!skPaint2GrPaintNoShader(this, paint, alphaOnly, false, &grPaint)) {
+        return;
+    }
+
+    fContext->drawRectToRect(grPaint, dstRect, paintRect, &m);
 }
 
 static bool filter_texture(SkDevice* device, GrContext* context,
@@ -1404,33 +1382,28 @@
     int w = bitmap.width();
     int h = bitmap.height();
 
-    GrPaint grPaint;
-    if(!skPaint2GrPaintNoShader(this, paint, true, false, &grPaint)) {
-        return;
-    }
-
-    GrEffectStage* stage = grPaint.colorStage(kBitmapEffectIdx);
-
     GrTexture* texture;
-    stage->reset();
     // draw sprite uses the default texture params
     SkAutoCachedTexture act(this, bitmap, NULL, &texture);
-    grPaint.colorStage(kBitmapEffectIdx)->setEffect(
-        GrSimpleTextureEffect::Create(texture, SkMatrix::I()))->unref();
 
     SkImageFilter* filter = paint.getImageFilter();
     SkIPoint offset = SkIPoint::Make(0, 0);
     if (NULL != filter) {
         SkBitmap filterBitmap;
         if (filter_texture(this, fContext, texture, filter, w, h, &filterBitmap, &offset)) {
-            grPaint.colorStage(kBitmapEffectIdx)->setEffect(
-                GrSimpleTextureEffect::Create(filterBitmap.getTexture(), SkMatrix::I()))->unref();
-            texture = filterBitmap.getTexture();
+            texture = (GrTexture*) filterBitmap.getTexture();
             w = filterBitmap.width();
             h = filterBitmap.height();
         }
     }
 
+    GrPaint grPaint;
+    grPaint.addColorTextureEffect(texture, SkMatrix::I());
+
+    if(!skPaint2GrPaintNoShader(this, paint, true, false, &grPaint)) {
+        return;
+    }
+
     fContext->drawRectToRect(grPaint,
                              GrRect::MakeXYWH(SkIntToScalar(left),
                                               SkIntToScalar(top),
@@ -1484,27 +1457,22 @@
     // drawDevice is defined to be in device coords.
     CHECK_SHOULD_DRAW(draw, true);
 
-    GrPaint grPaint;
-    grPaint.colorStage(kBitmapEffectIdx)->reset();
-    if (!dev->bindDeviceAsTexture(&grPaint) ||
-        !skPaint2GrPaintNoShader(this, paint, true, false, &grPaint)) {
+    GrRenderTarget* devRT = device->accessRenderTarget();
+    GrTexture* devTex;
+    if (NULL == (devTex = devRT->asTexture())) {
         return;
     }
 
-    GrTexture* devTex = (*grPaint.getColorStage(kBitmapEffectIdx).getEffect())->texture(0);
-    SkASSERT(NULL != devTex);
-
     const SkBitmap& bm = dev->accessBitmap(false);
     int w = bm.width();
     int h = bm.height();
 
     SkImageFilter* filter = paint.getImageFilter();
+
     if (NULL != filter) {
         SkBitmap filterBitmap;
         SkIPoint offset = SkIPoint::Make(0, 0);
         if (filter_texture(this, fContext, devTex, filter, w, h, &filterBitmap, &offset)) {
-            grPaint.colorStage(kBitmapEffectIdx)->setEffect(
-                GrSimpleTextureEffect::Create(filterBitmap.getTexture(), SkMatrix::I()))->unref();
             devTex = filterBitmap.getTexture();
             w = filterBitmap.width();
             h = filterBitmap.height();
@@ -1513,13 +1481,20 @@
         }
     }
 
+    GrPaint grPaint;
+    grPaint.addColorTextureEffect(devTex, SkMatrix::I());
+
+    if (!skPaint2GrPaintNoShader(this, paint, true, false, &grPaint)) {
+        return;
+    }
+
     GrRect dstRect = GrRect::MakeXYWH(SkIntToScalar(x),
                                       SkIntToScalar(y),
                                       SkIntToScalar(w),
                                       SkIntToScalar(h));
 
-    // The device being drawn may not fill up its texture (saveLayer uses
-    // the approximate ).
+    // The device being drawn may not fill up its texture (e.g. saveLayer uses approximate
+    // scratch texture).
     GrRect srcRect = GrRect::MakeWH(SK_Scalar1 * w / devTex->width(),
                                     SK_Scalar1 * h / devTex->height());
 
diff --git a/src/gpu/effects/GrConfigConversionEffect.cpp b/src/gpu/effects/GrConfigConversionEffect.cpp
index fd6264f..db6141f 100644
--- a/src/gpu/effects/GrConfigConversionEffect.cpp
+++ b/src/gpu/effects/GrConfigConversionEffect.cpp
@@ -219,7 +219,6 @@
         // from readTex to tempTex followed by a PM->UPM draw to readTex and finally read the data.
         // We then verify that two reads produced the same values.
 
-        GrPaint paint;
         AutoEffectUnref pmToUPM1(SkNEW_ARGS(GrConfigConversionEffect, (dataTex,
                                                                        false,
                                                                        *pmToUPMRule,
@@ -238,17 +237,21 @@
         SkAutoTUnref<GrEffectRef> pmToUPMEffect2(CreateEffectRef(pmToUPM2));
 
         context->setRenderTarget(readTex->asRenderTarget());
-        paint.colorStage(0)->setEffect(pmToUPMEffect1);
-        context->drawRectToRect(paint, kDstRect, kSrcRect);
+        GrPaint paint1;
+        paint1.addColorEffect(pmToUPMEffect1);
+        context->drawRectToRect(paint1, kDstRect, kSrcRect);
 
         readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, firstRead);
 
         context->setRenderTarget(tempTex->asRenderTarget());
-        paint.colorStage(0)->setEffect(upmToPMEffect);
-        context->drawRectToRect(paint, kDstRect, kSrcRect);
+        GrPaint paint2;
+        paint2.addColorEffect(upmToPMEffect);
+        context->drawRectToRect(paint2, kDstRect, kSrcRect);
         context->setRenderTarget(readTex->asRenderTarget());
-        paint.colorStage(0)->setEffect(pmToUPMEffect2);
-        context->drawRectToRect(paint, kDstRect, kSrcRect);
+
+        GrPaint paint3;
+        paint3.addColorEffect(pmToUPMEffect2);
+        context->drawRectToRect(paint3, kDstRect, kSrcRect);
 
         readTex->readPixels(0, 0, 256, 256, kRGBA_8888_GrPixelConfig, secondRead);