add support for high quality image filtering on the GPU

R=bsalomon@google.com, reed@google.com

Review URL: https://codereview.chromium.org/23779003

git-svn-id: http://skia.googlecode.com/svn/trunk@11087 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/tilemodes_scaled.cpp b/gm/tilemodes_scaled.cpp
new file mode 100644
index 0000000..63a4ef7
--- /dev/null
+++ b/gm/tilemodes_scaled.cpp
@@ -0,0 +1,282 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "gm.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkColorPriv.h"
+#include "SkColorFilter.h"
+#include "SkTypeface.h"
+
+// effects
+#include "SkGradientShader.h"
+#include "SkUnitMappers.h"
+#include "SkBlurDrawLooper.h"
+
+static void makebm(SkBitmap* bm, SkBitmap::Config config, int w, int h) {
+    bm->setConfig(config, w, h);
+    bm->allocPixels();
+    bm->eraseColor(SK_ColorTRANSPARENT);
+
+    SkCanvas    canvas(*bm);
+    SkPoint     pts[] = { { 0, 0 }, { SkIntToScalar(w), SkIntToScalar(h)} };
+    SkColor     colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
+    SkScalar    pos[] = { 0, SK_Scalar1/2, SK_Scalar1 };
+    SkPaint     paint;
+
+    SkUnitMapper*   um = NULL;
+
+    um = new SkCosineMapper;
+//    um = new SkDiscreteMapper(12);
+
+    SkAutoUnref au(um);
+
+    paint.setDither(true);
+    paint.setShader(SkGradientShader::CreateLinear(pts, colors, pos,
+                SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode, um))->unref();
+    canvas.drawPaint(paint);
+}
+
+static void setup(SkPaint* paint, const SkBitmap& bm, SkPaint::FilterLevel filter_level,
+                  SkShader::TileMode tmx, SkShader::TileMode tmy) {
+    SkShader* shader = SkShader::CreateBitmapShader(bm, tmx, tmy);
+    paint->setShader(shader)->unref();
+    paint->setFilterLevel(filter_level);
+}
+
+static const SkBitmap::Config gConfigs[] = {
+    SkBitmap::kARGB_8888_Config,
+    SkBitmap::kRGB_565_Config,
+};
+
+class ScaledTilingGM : public skiagm::GM {
+    SkBlurDrawLooper    fLooper;
+public:
+    ScaledTilingGM(bool powerOfTwoSize)
+            : fLooper(SkIntToScalar(1), SkIntToScalar(2), SkIntToScalar(2), 0x88000000)
+            , fPowerOfTwoSize(powerOfTwoSize) {
+    }
+
+    SkBitmap    fTexture[SK_ARRAY_COUNT(gConfigs)];
+
+protected:
+
+    enum {
+        kPOTSize = 4,
+        kNPOTSize = 3,
+    };
+
+    SkString onShortName() {
+        SkString name("scaled_tilemodes");
+        if (!fPowerOfTwoSize) {
+            name.append("_npot");
+        }
+        return name;
+    }
+
+    SkISize onISize() { return SkISize::Make(880, 760); }
+
+    virtual void onOnceBeforeDraw() SK_OVERRIDE {
+        int size = fPowerOfTwoSize ? kPOTSize : kNPOTSize;
+        for (size_t i = 0; i < SK_ARRAY_COUNT(gConfigs); i++) {
+            makebm(&fTexture[i], gConfigs[i], size, size);
+        }
+    }
+
+    virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+        
+        float scale = 32.f/kPOTSize;
+
+        int size = fPowerOfTwoSize ? kPOTSize : kNPOTSize;
+
+        SkRect r = { 0, 0, SkIntToScalar(size*2), SkIntToScalar(size*2) };
+
+        static const char* gConfigNames[] = { "8888" , "565", "4444" };
+
+        static const SkPaint::FilterLevel           gFilterLevels[] = 
+            { SkPaint::kNone_FilterLevel, 
+              SkPaint::kLow_FilterLevel,
+              SkPaint::kMedium_FilterLevel,
+              SkPaint::kHigh_FilterLevel };
+        static const char*          gFilterNames[] = { "None", "Low", "Medium", "High" };
+
+        static const SkShader::TileMode gModes[] = { SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode };
+        static const char*          gModeNames[] = {    "C",                    "R",                   "M" };
+
+        SkScalar y = SkIntToScalar(24);
+        SkScalar x = SkIntToScalar(10)/scale;
+
+        for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
+            for (size_t ky = 0; ky < SK_ARRAY_COUNT(gModes); ky++) {
+                SkPaint p;
+                SkString str;
+                p.setAntiAlias(true);
+                p.setDither(true);
+                p.setLooper(&fLooper);
+                str.printf("[%s,%s]", gModeNames[kx], gModeNames[ky]);
+
+                p.setTextAlign(SkPaint::kCenter_Align);
+                canvas->drawText(str.c_str(), str.size(), scale*(x + r.width()/2), y, p);
+
+                x += r.width() * 4 / 3;
+            }
+        }
+
+        y = SkIntToScalar(40) / scale;
+
+        for (size_t i = 0; i < SK_ARRAY_COUNT(gConfigs); i++) {
+            for (size_t j = 0; j < SK_ARRAY_COUNT(gFilterLevels); j++) {
+                x = SkIntToScalar(10)/scale;
+                for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
+                    for (size_t ky = 0; ky < SK_ARRAY_COUNT(gModes); ky++) {
+                        SkPaint paint;
+#if 1 // Temporary change to regen bitmap before each draw. This may help tracking down an issue
+      // on SGX where resizing NPOT textures to POT textures exhibits a driver bug.
+                        if (!fPowerOfTwoSize) {
+                            makebm(&fTexture[i], gConfigs[i], size, size);
+                        }
+#endif
+                        setup(&paint, fTexture[i], gFilterLevels[j], gModes[kx], gModes[ky]);
+                        paint.setDither(true);
+
+                        canvas->save();
+                        canvas->scale(scale,scale);
+                        canvas->translate(x, y);
+                        canvas->drawRect(r, paint);
+                        canvas->restore();
+
+                        x += r.width() * 4 / 3;
+                    }
+                }
+                {
+                    SkPaint p;
+                    SkString str;
+                    p.setAntiAlias(true);
+                    p.setLooper(&fLooper);
+                    str.printf("%s, %s", gConfigNames[i], gFilterNames[j]);
+                    canvas->drawText(str.c_str(), str.size(), scale*x, scale*(y + r.height() * 2 / 3), p);
+                }
+
+                y += r.height() * 4 / 3;
+            }
+        }
+    }
+
+private:
+    bool fPowerOfTwoSize;
+    typedef skiagm::GM INHERITED;
+};
+
+static const int gWidth = 32;
+static const int gHeight = 32;
+
+static SkShader* make_bm(SkShader::TileMode tx, SkShader::TileMode ty) {
+    SkBitmap bm;
+    makebm(&bm, SkBitmap::kARGB_8888_Config, gWidth, gHeight);
+    return SkShader::CreateBitmapShader(bm, tx, ty);
+}
+
+static SkShader* make_grad(SkShader::TileMode tx, SkShader::TileMode ty) {
+    SkPoint pts[] = { { 0, 0 }, { SkIntToScalar(gWidth), SkIntToScalar(gHeight)} };
+    SkPoint center = { SkIntToScalar(gWidth)/2, SkIntToScalar(gHeight)/2 };
+    SkScalar rad = SkIntToScalar(gWidth)/2;
+    SkColor colors[] = { 0xFFFF0000, 0xFF0044FF };
+
+    int index = (int)ty;
+    switch (index % 3) {
+        case 0:
+            return SkGradientShader::CreateLinear(pts, colors, NULL, SK_ARRAY_COUNT(colors), tx);
+        case 1:
+            return SkGradientShader::CreateRadial(center, rad, colors, NULL, SK_ARRAY_COUNT(colors), tx);
+        case 2:
+            return SkGradientShader::CreateSweep(center.fX, center.fY, colors, NULL, SK_ARRAY_COUNT(colors));
+    }
+
+    return NULL;
+}
+
+typedef SkShader* (*ShaderProc)(SkShader::TileMode, SkShader::TileMode);
+
+class ScaledTiling2GM : public skiagm::GM {
+    ShaderProc fProc;
+    SkString   fName;
+public:
+    ScaledTiling2GM(ShaderProc proc, const char name[]) : fProc(proc) {
+        fName.printf("scaled_tilemode_%s", name);
+    }
+
+protected:
+    SkString onShortName() {
+        return fName;
+    }
+
+    SkISize onISize() { return SkISize::Make(880, 560); }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        canvas->scale(SkIntToScalar(3)/2, SkIntToScalar(3)/2);
+
+        const SkScalar w = SkIntToScalar(gWidth);
+        const SkScalar h = SkIntToScalar(gHeight);
+        SkRect r = { -w, -h, w*2, h*2 };
+
+        static const SkShader::TileMode gModes[] = {
+            SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode
+        };
+        static const char* gModeNames[] = {
+            "Clamp", "Repeat", "Mirror"
+        };
+
+        SkScalar y = SkIntToScalar(24);
+        SkScalar x = SkIntToScalar(66);
+
+        SkPaint p;
+        p.setAntiAlias(true);
+        p.setTextAlign(SkPaint::kCenter_Align);
+
+        for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
+            SkString str(gModeNames[kx]);
+            canvas->drawText(str.c_str(), str.size(), x + r.width()/2, y, p);
+            x += r.width() * 4 / 3;
+        }
+
+        y += SkIntToScalar(16) + h;
+        p.setTextAlign(SkPaint::kRight_Align);
+
+        for (size_t ky = 0; ky < SK_ARRAY_COUNT(gModes); ky++) {
+            x = SkIntToScalar(16) + w;
+
+            SkString str(gModeNames[ky]);
+            canvas->drawText(str.c_str(), str.size(), x, y + h/2, p);
+
+            x += SkIntToScalar(50);
+            for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
+                SkPaint paint;
+                paint.setShader(fProc(gModes[kx], gModes[ky]))->unref();
+
+                canvas->save();
+                canvas->translate(x, y);
+                canvas->drawRect(r, paint);
+                canvas->restore();
+
+                x += r.width() * 4 / 3;
+            }
+            y += r.height() * 4 / 3;
+        }
+    }
+
+private:
+    typedef skiagm::GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM( return new ScaledTilingGM(true); )
+DEF_GM( return new ScaledTilingGM(false); )
+DEF_GM( return new ScaledTiling2GM(make_bm, "bitmap"); )
+DEF_GM( return new ScaledTiling2GM(make_grad, "gradient"); )
diff --git a/gyp/effects.gyp b/gyp/effects.gyp
index d0b8b4d..d22debb 100644
--- a/gyp/effects.gyp
+++ b/gyp/effects.gyp
@@ -16,6 +16,7 @@
       ],
       'include_dirs': [
         '../include/effects',
+        '../src/effects',
         '../src/core',
       ],
       'direct_dependent_settings': {
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index b2afc5e..52faae0 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -122,6 +122,7 @@
     '../gm/thinrects.cpp',
     '../gm/thinstrokedrects.cpp',
     '../gm/tilemodes.cpp',
+    '../gm/tilemodes_scaled.cpp',
     '../gm/tinybitmap.cpp',
     '../gm/twopointradial.cpp',
     '../gm/typeface.cpp',
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi
index 33fbfae..d50d245 100644
--- a/gyp/gpu.gypi
+++ b/gyp/gpu.gypi
@@ -134,6 +134,8 @@
       '<(skia_src_path)/gpu/effects/GrBezierEffect.h',
       '<(skia_src_path)/gpu/effects/GrConvolutionEffect.cpp',
       '<(skia_src_path)/gpu/effects/GrConvolutionEffect.h',
+      '<(skia_src_path)/gpu/effects/GrBicubicEffect.cpp',
+      '<(skia_src_path)/gpu/effects/GrBicubicEffect.h',
       '<(skia_src_path)/gpu/effects/GrSimpleTextureEffect.cpp',
       '<(skia_src_path)/gpu/effects/GrSimpleTextureEffect.h',
       '<(skia_src_path)/gpu/effects/GrSingleTextureEffect.cpp',
diff --git a/include/core/SkError.h b/include/core/SkError.h
index c5af460..f3f22e9 100644
--- a/include/core/SkError.h
+++ b/include/core/SkError.h
@@ -47,7 +47,12 @@
 
     /** Skia failed while trying to consume some external resource.
      */
-    kParseError_SkError
+    kParseError_SkError,
+    
+    /** Something went wrong internally; could be resource exhaustion but
+      * will often be a bug.
+     */
+    kInternalError_SkError
 };
 
 /** Return the current per-thread error code.  Error codes are "sticky"; they
diff --git a/src/core/SkBitmapProcShader.cpp b/src/core/SkBitmapProcShader.cpp
index 0e7ac6a..70b5212 100644
--- a/src/core/SkBitmapProcShader.cpp
+++ b/src/core/SkBitmapProcShader.cpp
@@ -5,11 +5,14 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-#include "SkBitmapProcShader.h"
 #include "SkColorPriv.h"
 #include "SkFlattenableBuffers.h"
 #include "SkPixelRef.h"
 #include "SkErrorInternals.h"
+#include "SkBitmapProcShader.h"
+
+#include "effects/GrSimpleTextureEffect.h"
+#include "effects/GrBicubicEffect.h"
 
 bool SkBitmapProcShader::CanDo(const SkBitmap& bm, TileMode tx, TileMode ty) {
     switch (bm.config()) {
@@ -367,11 +370,9 @@
             textureFilterMode = GrTextureParams::kMipMap_FilterMode;
             break;
         case SkPaint::kHigh_FilterLevel:
-            SkErrorInternals::SetError( kInvalidPaint_SkError,
-                                        "Sorry, I don't yet support high quality "
-                                        "filtering on the GPU; falling back to "
-                                        "MIPMaps.");
-            textureFilterMode = GrTextureParams::kMipMap_FilterMode;
+            // fall back to no filtering here; we will install another
+            // shader that will do the HQ filtering.
+            textureFilterMode = GrTextureParams::kNone_FilterMode;
             break;
         default:
             SkErrorInternals::SetError( kInvalidPaint_SkError,
@@ -386,11 +387,17 @@
     GrTexture* texture = GrLockAndRefCachedBitmapTexture(context, fRawBitmap, &params);
 
     if (NULL == texture) {
-        SkDebugf("Couldn't convert bitmap to texture.\n");
+        SkErrorInternals::SetError( kInternalError_SkError,
+                                    "Couldn't convert bitmap to texture.");
         return NULL;
     }
 
-    GrEffectRef* effect = GrSimpleTextureEffect::Create(texture, matrix, params);
+    GrEffectRef* effect = NULL;
+    if (paintFilterLevel == SkPaint::kHigh_FilterLevel) {
+        effect = GrBicubicEffect::Create(texture, matrix, params);
+    } else {
+        effect = GrSimpleTextureEffect::Create(texture, matrix, params);
+    }
     GrUnlockAndUnrefCachedBitmapTexture(texture);
     return effect;
 }
diff --git a/src/effects/SkBicubicImageFilter.cpp b/src/effects/SkBicubicImageFilter.cpp
index 3b2dd4f..0ffcde6 100644
--- a/src/effects/SkBicubicImageFilter.cpp
+++ b/src/effects/SkBicubicImageFilter.cpp
@@ -14,31 +14,30 @@
 #include "SkUnPreMultiply.h"
 
 #if SK_SUPPORT_GPU
-#include "gl/GrGLEffectMatrix.h"
-#include "effects/GrSingleTextureEffect.h"
-#include "GrTBackendEffectFactory.h"
+#include "effects/GrBicubicEffect.h"
 #include "GrContext.h"
 #include "GrTexture.h"
 #include "SkImageFilterUtils.h"
 #endif
 
+#define DS(x) SkDoubleToScalar(x)
+
+static const SkScalar gMitchellCoefficients[16] = {
+    DS( 1.0 / 18.0), DS(-9.0 / 18.0), DS( 15.0 / 18.0), DS( -7.0 / 18.0),
+    DS(16.0 / 18.0), DS( 0.0 / 18.0), DS(-36.0 / 18.0), DS( 21.0 / 18.0),
+    DS( 1.0 / 18.0), DS( 9.0 / 18.0), DS( 27.0 / 18.0), DS(-21.0 / 18.0),
+    DS( 0.0 / 18.0), DS( 0.0 / 18.0), DS( -6.0 / 18.0), DS(  7.0 / 18.0),
+};
+
 SkBicubicImageFilter::SkBicubicImageFilter(const SkSize& scale, const SkScalar coefficients[16], SkImageFilter* input)
   : INHERITED(input),
     fScale(scale) {
     memcpy(fCoefficients, coefficients, sizeof(fCoefficients));
 }
 
-#define DS(x) SkDoubleToScalar(x)
-
 SkBicubicImageFilter* SkBicubicImageFilter::CreateMitchell(const SkSize& scale,
                                                            SkImageFilter* input) {
-    static const SkScalar coefficients[16] = {
-        DS( 1.0 / 18.0), DS(-9.0 / 18.0), DS( 15.0 / 18.0), DS( -7.0 / 18.0),
-        DS(16.0 / 18.0), DS( 0.0 / 18.0), DS(-36.0 / 18.0), DS( 21.0 / 18.0),
-        DS( 1.0 / 18.0), DS( 9.0 / 18.0), DS( 27.0 / 18.0), DS(-21.0 / 18.0),
-        DS( 0.0 / 18.0), DS( 0.0 / 18.0), DS( -6.0 / 18.0), DS(  7.0 / 18.0),
-    };
-    return SkNEW_ARGS(SkBicubicImageFilter, (scale, coefficients, input));
+    return SkNEW_ARGS(SkBicubicImageFilter, (scale, gMitchellCoefficients, input));
 }
 
 SkBicubicImageFilter::SkBicubicImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
@@ -157,184 +156,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 #if SK_SUPPORT_GPU
-class GrGLBicubicEffect;
-
-class GrBicubicEffect : public GrSingleTextureEffect {
-public:
-    virtual ~GrBicubicEffect();
-
-    static const char* Name() { return "Bicubic"; }
-    const float* coefficients() const { return fCoefficients; }
-
-    typedef GrGLBicubicEffect GLEffect;
-
-    virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
-    virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
-
-    static GrEffectRef* Create(GrTexture* tex, const SkScalar coefficients[16]) {
-        AutoEffectUnref effect(SkNEW_ARGS(GrBicubicEffect, (tex, coefficients)));
-        return CreateEffectRef(effect);
-    }
-
-private:
-    GrBicubicEffect(GrTexture*, const SkScalar coefficients[16]);
-    virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
-    float    fCoefficients[16];
-
-    GR_DECLARE_EFFECT_TEST;
-
-    typedef GrSingleTextureEffect INHERITED;
-};
-
-class GrGLBicubicEffect : public GrGLEffect {
-public:
-    GrGLBicubicEffect(const GrBackendEffectFactory& factory,
-                      const GrDrawEffect&);
-    virtual void emitCode(GrGLShaderBuilder*,
-                          const GrDrawEffect&,
-                          EffectKey,
-                          const char* outputColor,
-                          const char* inputColor,
-                          const TextureSamplerArray&) SK_OVERRIDE;
-
-    static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
-
-    virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
-
-private:
-    typedef GrGLUniformManager::UniformHandle        UniformHandle;
-
-    UniformHandle       fCoefficientsUni;
-    UniformHandle       fImageIncrementUni;
-
-    GrGLEffectMatrix    fEffectMatrix;
-
-    typedef GrGLEffect INHERITED;
-};
-
-GrGLBicubicEffect::GrGLBicubicEffect(const GrBackendEffectFactory& factory,
-                                     const GrDrawEffect& drawEffect)
-    : INHERITED(factory)
-    , fEffectMatrix(drawEffect.castEffect<GrBicubicEffect>().coordsType()) {
-}
-
-void GrGLBicubicEffect::emitCode(GrGLShaderBuilder* builder,
-                                 const GrDrawEffect&,
-                                 EffectKey key,
-                                 const char* outputColor,
-                                 const char* inputColor,
-                                 const TextureSamplerArray& samplers) {
-    SkString coords;
-    fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
-    fCoefficientsUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
-                                           kMat44f_GrSLType, "Coefficients");
-    fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
-                                             kVec2f_GrSLType, "ImageIncrement");
-
-    const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
-    const char* coeff = builder->getUniformCStr(fCoefficientsUni);
-
-    SkString cubicBlendName;
-
-    static const GrGLShaderVar gCubicBlendArgs[] = {
-        GrGLShaderVar("coefficients",  kMat44f_GrSLType),
-        GrGLShaderVar("t",             kFloat_GrSLType),
-        GrGLShaderVar("c0",            kVec4f_GrSLType),
-        GrGLShaderVar("c1",            kVec4f_GrSLType),
-        GrGLShaderVar("c2",            kVec4f_GrSLType),
-        GrGLShaderVar("c3",            kVec4f_GrSLType),
-    };
-    builder->fsEmitFunction(kVec4f_GrSLType,
-                            "cubicBlend",
-                            SK_ARRAY_COUNT(gCubicBlendArgs),
-                            gCubicBlendArgs,
-                            "\tvec4 ts = vec4(1.0, t, t * t, t * t * t);\n"
-                            "\tvec4 c = coefficients * ts;\n"
-                            "\treturn c.x * c0 + c.y * c1 + c.z * c2 + c.w * c3;\n",
-                            &cubicBlendName);
-    builder->fsCodeAppendf("\tvec2 coord = %s - %s * vec2(0.5, 0.5);\n", coords.c_str(), imgInc);
-    builder->fsCodeAppendf("\tvec2 f = fract(coord / %s);\n", imgInc);
-    for (int y = 0; y < 4; ++y) {
-        for (int x = 0; x < 4; ++x) {
-            SkString coord;
-            coord.printf("coord + %s * vec2(%d, %d)", imgInc, x - 1, y - 1);
-            builder->fsCodeAppendf("\tvec4 s%d%d = ", x, y);
-            builder->fsAppendTextureLookup(samplers[0], coord.c_str());
-            builder->fsCodeAppend(";\n");
-        }
-        builder->fsCodeAppendf("\tvec4 s%d = %s(%s, f.x, s0%d, s1%d, s2%d, s3%d);\n", y, cubicBlendName.c_str(), coeff, y, y, y, y);
-    }
-    builder->fsCodeAppendf("\t%s = %s(%s, f.y, s0, s1, s2, s3);\n", outputColor, cubicBlendName.c_str(), coeff);
-}
-
-GrGLEffect::EffectKey GrGLBicubicEffect::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
-    const GrBicubicEffect& bicubic = drawEffect.castEffect<GrBicubicEffect>();
-    EffectKey matrixKey = GrGLEffectMatrix::GenKey(bicubic.getMatrix(),
-                                                   drawEffect,
-                                                   bicubic.coordsType(),
-                                                   bicubic.texture(0));
-    return matrixKey;
-}
-
-void GrGLBicubicEffect::setData(const GrGLUniformManager& uman,
-                                const GrDrawEffect& drawEffect) {
-    const GrBicubicEffect& effect = drawEffect.castEffect<GrBicubicEffect>();
-    GrTexture& texture = *effect.texture(0);
-    float imageIncrement[2];
-    imageIncrement[0] = 1.0f / texture.width();
-    imageIncrement[1] = 1.0f / texture.height();
-    uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
-    uman.setMatrix4f(fCoefficientsUni, effect.coefficients());
-    fEffectMatrix.setData(uman,
-                          effect.getMatrix(),
-                          drawEffect,
-                          effect.texture(0));
-}
-
-GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
-                                 const SkScalar coefficients[16])
-  : INHERITED(texture, MakeDivByTextureWHMatrix(texture)) {
-    for (int y = 0; y < 4; y++) {
-        for (int x = 0; x < 4; x++) {
-            // Convert from row-major scalars to column-major floats.
-            fCoefficients[x * 4 + y] = SkScalarToFloat(coefficients[y * 4 + x]);
-        }
-    }
-}
-
-GrBicubicEffect::~GrBicubicEffect() {
-}
-
-const GrBackendEffectFactory& GrBicubicEffect::getFactory() const {
-    return GrTBackendEffectFactory<GrBicubicEffect>::getInstance();
-}
-
-bool GrBicubicEffect::onIsEqual(const GrEffect& sBase) const {
-    const GrBicubicEffect& s = CastEffect<GrBicubicEffect>(sBase);
-    return this->texture(0) == s.texture(0) &&
-           !memcmp(fCoefficients, s.coefficients(), 16);
-}
-
-void GrBicubicEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
-    // FIXME:  Perhaps we can do better.
-    *validFlags = 0;
-    return;
-}
-
-GR_DEFINE_EFFECT_TEST(GrBicubicEffect);
-
-GrEffectRef* GrBicubicEffect::TestCreate(SkMWCRandom* random,
-                                         GrContext* context,
-                                         const GrDrawTargetCaps&,
-                                         GrTexture* textures[]) {
-    int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
-                                      GrEffectUnitTest::kAlphaTextureIdx;
-    SkScalar coefficients[16];
-    for (int i = 0; i < 16; i++) {
-        coefficients[i] = random->nextSScalar1();
-    }
-    return GrBicubicEffect::Create(textures[texIdx], coefficients);
-}
 
 bool SkBicubicImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
                                           SkBitmap* result, SkIPoint* offset) {
diff --git a/src/gpu/effects/GrBicubicEffect.cpp b/src/gpu/effects/GrBicubicEffect.cpp
new file mode 100644
index 0000000..9a38f67
--- /dev/null
+++ b/src/gpu/effects/GrBicubicEffect.cpp
@@ -0,0 +1,175 @@
+#include "GrBicubicEffect.h"
+
+#define DS(x) SkDoubleToScalar(x)
+
+const SkScalar GrBicubicEffect::gMitchellCoefficients[16] = {
+    DS( 1.0 / 18.0), DS(-9.0 / 18.0), DS( 15.0 / 18.0), DS( -7.0 / 18.0),
+    DS(16.0 / 18.0), DS( 0.0 / 18.0), DS(-36.0 / 18.0), DS( 21.0 / 18.0),
+    DS( 1.0 / 18.0), DS( 9.0 / 18.0), DS( 27.0 / 18.0), DS(-21.0 / 18.0),
+    DS( 0.0 / 18.0), DS( 0.0 / 18.0), DS( -6.0 / 18.0), DS(  7.0 / 18.0),
+};
+
+
+class GrGLBicubicEffect : public GrGLEffect {
+public:
+    GrGLBicubicEffect(const GrBackendEffectFactory& factory,
+                      const GrDrawEffect&);
+    virtual void emitCode(GrGLShaderBuilder*,
+                          const GrDrawEffect&,
+                          EffectKey,
+                          const char* outputColor,
+                          const char* inputColor,
+                          const TextureSamplerArray&) SK_OVERRIDE;
+
+    static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+    virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+    typedef GrGLUniformManager::UniformHandle        UniformHandle;
+
+    UniformHandle       fCoefficientsUni;
+    UniformHandle       fImageIncrementUni;
+
+    GrGLEffectMatrix    fEffectMatrix;
+
+    typedef GrGLEffect INHERITED;
+};
+
+GrGLBicubicEffect::GrGLBicubicEffect(const GrBackendEffectFactory& factory,
+                                     const GrDrawEffect& drawEffect)
+    : INHERITED(factory)
+    , fEffectMatrix(drawEffect.castEffect<GrBicubicEffect>().coordsType()) {
+}
+
+void GrGLBicubicEffect::emitCode(GrGLShaderBuilder* builder,
+                                 const GrDrawEffect&,
+                                 EffectKey key,
+                                 const char* outputColor,
+                                 const char* inputColor,
+                                 const TextureSamplerArray& samplers) {
+    SkString coords;
+    fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+    fCoefficientsUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
+                                           kMat44f_GrSLType, "Coefficients");
+    fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
+                                             kVec2f_GrSLType, "ImageIncrement");
+
+    const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+    const char* coeff = builder->getUniformCStr(fCoefficientsUni);
+
+    SkString cubicBlendName;
+
+    static const GrGLShaderVar gCubicBlendArgs[] = {
+        GrGLShaderVar("coefficients",  kMat44f_GrSLType),
+        GrGLShaderVar("t",             kFloat_GrSLType),
+        GrGLShaderVar("c0",            kVec4f_GrSLType),
+        GrGLShaderVar("c1",            kVec4f_GrSLType),
+        GrGLShaderVar("c2",            kVec4f_GrSLType),
+        GrGLShaderVar("c3",            kVec4f_GrSLType),
+    };
+    builder->fsEmitFunction(kVec4f_GrSLType,
+                            "cubicBlend",
+                            SK_ARRAY_COUNT(gCubicBlendArgs),
+                            gCubicBlendArgs,
+                            "\tvec4 ts = vec4(1.0, t, t * t, t * t * t);\n"
+                            "\tvec4 c = coefficients * ts;\n"
+                            "\treturn c.x * c0 + c.y * c1 + c.z * c2 + c.w * c3;\n",
+                            &cubicBlendName);
+    builder->fsCodeAppendf("\tvec2 coord = %s - %s * vec2(0.5, 0.5);\n", coords.c_str(), imgInc);
+    builder->fsCodeAppendf("\tvec2 f = fract(coord / %s);\n", imgInc);
+    for (int y = 0; y < 4; ++y) {
+        for (int x = 0; x < 4; ++x) {
+            SkString coord;
+            coord.printf("coord + %s * vec2(%d, %d)", imgInc, x - 1, y - 1);
+            builder->fsCodeAppendf("\tvec4 s%d%d = ", x, y);
+            builder->fsAppendTextureLookup(samplers[0], coord.c_str());
+            builder->fsCodeAppend(";\n");
+        }
+        builder->fsCodeAppendf("\tvec4 s%d = %s(%s, f.x, s0%d, s1%d, s2%d, s3%d);\n", y, cubicBlendName.c_str(), coeff, y, y, y, y);
+    }
+    builder->fsCodeAppendf("\t%s = %s(%s, f.y, s0, s1, s2, s3);\n", outputColor, cubicBlendName.c_str(), coeff);
+}
+
+GrGLEffect::EffectKey GrGLBicubicEffect::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+    const GrBicubicEffect& bicubic = drawEffect.castEffect<GrBicubicEffect>();
+    EffectKey matrixKey = GrGLEffectMatrix::GenKey(bicubic.getMatrix(),
+                                                   drawEffect,
+                                                   bicubic.coordsType(),
+                                                   bicubic.texture(0));
+    return matrixKey;
+}
+
+void GrGLBicubicEffect::setData(const GrGLUniformManager& uman,
+                                const GrDrawEffect& drawEffect) {
+    const GrBicubicEffect& effect = drawEffect.castEffect<GrBicubicEffect>();
+    GrTexture& texture = *effect.texture(0);
+    float imageIncrement[2];
+    imageIncrement[0] = 1.0f / texture.width();
+    imageIncrement[1] = 1.0f / texture.height();
+    uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+    uman.setMatrix4f(fCoefficientsUni, effect.coefficients());
+    fEffectMatrix.setData(uman,
+                          effect.getMatrix(),
+                          drawEffect,
+                          effect.texture(0));
+}
+
+GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
+                                 const SkScalar coefficients[16])
+  : INHERITED(texture, MakeDivByTextureWHMatrix(texture)) {
+    for (int y = 0; y < 4; y++) {
+        for (int x = 0; x < 4; x++) {
+            // Convert from row-major scalars to column-major floats.
+            fCoefficients[x * 4 + y] = SkScalarToFloat(coefficients[y * 4 + x]);
+        }
+    }
+}
+
+GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
+                                 const SkScalar coefficients[16],
+                                 const SkMatrix &matrix,
+                                 const GrTextureParams &params,
+                                 CoordsType coordsType)
+  : INHERITED(texture, MakeDivByTextureWHMatrix(texture), params, coordsType) {
+    for (int y = 0; y < 4; y++) {
+        for (int x = 0; x < 4; x++) {
+            // Convert from row-major scalars to column-major floats.
+            fCoefficients[x * 4 + y] = SkScalarToFloat(coefficients[y * 4 + x]);
+        }
+    }
+}
+
+GrBicubicEffect::~GrBicubicEffect() {
+}
+
+const GrBackendEffectFactory& GrBicubicEffect::getFactory() const {
+    return GrTBackendEffectFactory<GrBicubicEffect>::getInstance();
+}
+
+bool GrBicubicEffect::onIsEqual(const GrEffect& sBase) const {
+    const GrBicubicEffect& s = CastEffect<GrBicubicEffect>(sBase);
+    return this->texture(0) == s.texture(0) &&
+           !memcmp(fCoefficients, s.coefficients(), 16);
+}
+
+void GrBicubicEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+    // FIXME:  Perhaps we can do better.
+    *validFlags = 0;
+    return;
+}
+
+GR_DEFINE_EFFECT_TEST(GrBicubicEffect);
+
+GrEffectRef* GrBicubicEffect::TestCreate(SkMWCRandom* random,
+                                         GrContext* context,
+                                         const GrDrawTargetCaps&,
+                                         GrTexture* textures[]) {
+    int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+                                      GrEffectUnitTest::kAlphaTextureIdx;
+    SkScalar coefficients[16];
+    for (int i = 0; i < 16; i++) {
+        coefficients[i] = random->nextSScalar1();
+    }
+    return GrBicubicEffect::Create(textures[texIdx], coefficients);
+}
diff --git a/src/gpu/effects/GrBicubicEffect.h b/src/gpu/effects/GrBicubicEffect.h
new file mode 100644
index 0000000..618ef1a
--- /dev/null
+++ b/src/gpu/effects/GrBicubicEffect.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrBicubicTextureEffect_DEFINED
+#define GrBicubicTextureEffect_DEFINED
+
+#include "GrSingleTextureEffect.h"
+#include "GrDrawEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrTBackendEffectFactory.h"
+
+class GrGLBicubicEffect;
+
+class GrBicubicEffect : public GrSingleTextureEffect {
+public:
+    virtual ~GrBicubicEffect();
+
+    static const char* Name() { return "Bicubic"; }
+    const float* coefficients() const { return fCoefficients; }
+
+    typedef GrGLBicubicEffect GLEffect;
+
+    virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+    virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+    static GrEffectRef* Create(GrTexture* tex, const SkScalar coefficients[16]) {
+        AutoEffectUnref effect(SkNEW_ARGS(GrBicubicEffect, (tex, coefficients)));
+        return CreateEffectRef(effect);
+    }
+
+    static GrEffectRef* Create(GrTexture* tex, const SkScalar coefficients[16], 
+                               const SkMatrix& matrix,
+                               const GrTextureParams& p,
+                               CoordsType coordsType = kLocal_CoordsType) {
+        AutoEffectUnref effect(SkNEW_ARGS(GrBicubicEffect, (tex, coefficients, matrix, p, coordsType)));
+        return CreateEffectRef(effect);
+    }
+
+    static GrEffectRef* Create(GrTexture* tex) {
+        return Create(tex, gMitchellCoefficients);
+    }
+
+    static GrEffectRef* Create(GrTexture* tex, 
+                               const SkMatrix& matrix,
+                               const GrTextureParams& p,
+                               CoordsType coordsType = kLocal_CoordsType) {
+        return Create(tex, gMitchellCoefficients, matrix, p, coordsType);
+    }
+
+private:
+    GrBicubicEffect(GrTexture*, const SkScalar coefficients[16]);
+    GrBicubicEffect(GrTexture*, const SkScalar coefficients[16], 
+                    const SkMatrix &matrix, const GrTextureParams &p, CoordsType coordsType);
+    virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+    float    fCoefficients[16];
+
+    GR_DECLARE_EFFECT_TEST;
+    
+    static const SkScalar gMitchellCoefficients[16];
+
+    typedef GrSingleTextureEffect INHERITED;
+};
+
+#endif