Add GrAASmallPathRenderer.

Uses cached signed distance fields to render scaled and rotated versions
of small paths.

BUG=skia:2935

Review URL: https://codereview.chromium.org/589103004
diff --git a/src/gpu/GrAADistanceFieldPathRenderer.cpp b/src/gpu/GrAADistanceFieldPathRenderer.cpp
new file mode 100755
index 0000000..364217c
--- /dev/null
+++ b/src/gpu/GrAADistanceFieldPathRenderer.cpp
@@ -0,0 +1,340 @@
+
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrAADistanceFieldPathRenderer.h"
+
+#include "GrAtlas.h"
+#include "GrContext.h"
+#include "GrDrawState.h"
+#include "GrSurfacePriv.h"
+#include "GrSWMaskHelper.h"
+#include "GrTexturePriv.h"
+#include "effects/GrDistanceFieldTextureEffect.h"
+
+#include "SkDistanceFieldGen.h"
+#include "SkRTConf.h"
+
+#define ATLAS_TEXTURE_WIDTH 1024
+#define ATLAS_TEXTURE_HEIGHT 1024
+
+#define PLOT_WIDTH  256
+#define PLOT_HEIGHT 256
+
+#define NUM_PLOTS_X   (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH)
+#define NUM_PLOTS_Y   (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT)
+
+SK_CONF_DECLARE(bool, c_DumpPathCache, "gpu.dumpPathCache", false,
+                "Dump the contents of the path cache before every purge.");
+
+////////////////////////////////////////////////////////////////////////////////
+GrAADistanceFieldPathRenderer::~GrAADistanceFieldPathRenderer() {
+    PathDataList::Iter iter;
+    iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
+    PathData* pathData;
+    while ((pathData = iter.get())) {
+        iter.next();
+        fPathList.remove(pathData);
+        SkDELETE(pathData);
+    }
+    
+    SkDELETE(fAtlas);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool GrAADistanceFieldPathRenderer::canDrawPath(const SkPath& path,
+                                                const SkStrokeRec& stroke,
+                                                const GrDrawTarget* target,
+                                                bool antiAlias) const {
+    // TODO: Support inverse fill
+    // TODO: Support strokes
+    if (!target->caps()->shaderDerivativeSupport() || !antiAlias || path.isInverseFillType()
+        || SkStrokeRec::kFill_Style != stroke.getStyle()) {
+        return false;
+    }
+
+    // currently don't support perspective or scaling more than 3x
+    const GrDrawState& drawState = target->getDrawState();
+    const SkMatrix& vm = drawState.getViewMatrix();
+    if (vm.hasPerspective() || vm.getMaxScale() > 3.0f) {
+        return false;
+    }
+    
+    // only support paths smaller than 64 x 64
+    const SkRect& bounds = path.getBounds();
+    return bounds.width() < 64.f && bounds.height() < 64.f;
+}
+
+
+GrPathRenderer::StencilSupport GrAADistanceFieldPathRenderer::onGetStencilSupport(
+                                                                       const SkPath&,
+                                                                       const SkStrokeRec&,
+                                                                       const GrDrawTarget*) const {
+    return GrPathRenderer::kNoSupport_StencilSupport;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// position + texture coord
+extern const GrVertexAttrib gSDFPathVertexAttribs[] = {
+    { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding },
+    { kVec2f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexAttribBinding }
+};
+static const size_t kSDFPathVASize = 2 * sizeof(SkPoint);
+
+bool GrAADistanceFieldPathRenderer::onDrawPath(const SkPath& path,
+                                               const SkStrokeRec& stroke,
+                                               GrDrawTarget* target,
+                                               bool antiAlias) {
+    // we've already bailed on inverse filled paths, so this is safe
+    if (path.isEmpty()) {
+        return true;
+    }
+
+    SkASSERT(fContext);
+
+    // check to see if path is cached
+    // TODO: handle stroked vs. filled version of same path
+    PathData* pathData = fPathCache.find(path.getGenerationID());
+    if (NULL == pathData) {
+        pathData = this->addPathToAtlas(path, stroke, antiAlias);
+        if (NULL == pathData) {
+            return false;
+        }
+    }
+
+    // use signed distance field to render
+    return this->internalDrawPath(path, pathData, target);
+}
+
+// factor used to scale the path prior to building distance field
+const SkScalar kScaleFactor = 2.0f;
+// padding around path bounds to allow for antialiased pixels
+const SkScalar kAntiAliasPad = 1.0f;
+
+GrAADistanceFieldPathRenderer::PathData* GrAADistanceFieldPathRenderer::addPathToAtlas(
+                                                                        const SkPath& path,
+                                                                        const SkStrokeRec& stroke,
+                                                                        bool antiAlias) {
+
+    // generate distance field and add to atlas
+    if (NULL == fAtlas) {
+        SkISize textureSize = SkISize::Make(ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT);
+        fAtlas = SkNEW_ARGS(GrAtlas, (fContext->getGpu(), kAlpha_8_GrPixelConfig,
+                                      kNone_GrTextureFlags, textureSize,
+                                      NUM_PLOTS_X, NUM_PLOTS_Y, false));
+        if (NULL == fAtlas) {
+            return NULL;
+        }
+    }
+    
+    const SkRect& bounds = path.getBounds();
+    
+    // generate bounding rect for bitmap draw
+    SkRect scaledBounds = bounds;
+    // scale up to improve maxification range
+    scaledBounds.fLeft *= kScaleFactor;
+    scaledBounds.fTop *= kScaleFactor;
+    scaledBounds.fRight *= kScaleFactor;
+    scaledBounds.fBottom *= kScaleFactor;
+    // move the origin to an integer boundary (gives better results)
+    SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
+    SkScalar dy = SkScalarFraction(scaledBounds.fTop);
+    scaledBounds.offset(-dx, -dy);
+    // get integer boundary
+    SkIRect devPathBounds;
+    scaledBounds.roundOut(&devPathBounds);
+    // pad to allow room for antialiasing
+    devPathBounds.outset(SkScalarCeilToInt(kAntiAliasPad), SkScalarCeilToInt(kAntiAliasPad));
+    // move origin to upper left corner
+    devPathBounds.offsetTo(0,0);
+    
+    // draw path to bitmap
+    SkMatrix drawMatrix;
+    drawMatrix.setTranslate(-bounds.left(), -bounds.top());
+    drawMatrix.postScale(kScaleFactor, kScaleFactor);
+    drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);
+    GrSWMaskHelper helper(fContext);
+    
+    if (!helper.init(devPathBounds, &drawMatrix)) {
+        return NULL;
+    }
+    helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF);
+    
+    // generate signed distance field
+    devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
+    int width = devPathBounds.width();
+    int height = devPathBounds.height();
+    SkAutoSMalloc<1024> dfStorage(width*height*sizeof(unsigned char));
+    helper.toSDF((unsigned char*) dfStorage.get());
+    
+    // add to atlas
+    SkIPoint16  atlasLocation;
+    GrPlot* plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage.get(),
+                                      &atlasLocation);
+    
+    // if atlas full
+    if (NULL == plot) {
+        if (this->freeUnusedPlot()) {
+            plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage.get(),
+                                      &atlasLocation);
+            if (plot) {
+                goto HAS_ATLAS;
+            }
+        }
+    
+        if (c_DumpPathCache) {
+#ifdef SK_DEVELOPER
+            GrTexture* texture = fAtlas->getTexture();
+            texture->surfacePriv().savePixels("pathcache.png");
+#endif
+        }
+        
+        // before we purge the cache, we must flush any accumulated draws
+        fContext->flush();
+        
+        if (this->freeUnusedPlot()) {
+            plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage.get(),
+                                      &atlasLocation);
+            if (plot) {
+                goto HAS_ATLAS;
+            }
+        }
+            
+        return NULL;
+    }
+    
+HAS_ATLAS:
+    // add to cache
+    PathData* pathData = SkNEW(PathData);
+    pathData->fGenID = path.getGenerationID();
+    pathData->fPlot = plot;
+    // change the scaled rect to match the size of the inset distance field
+    scaledBounds.fRight = scaledBounds.fLeft +
+        SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
+    scaledBounds.fBottom = scaledBounds.fTop +
+        SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
+    // shift the origin to the correct place relative to the distance field
+    // need to also restore the fractional translation
+    scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
+                        -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
+    pathData->fBounds = scaledBounds;
+    // origin we render from is inset from distance field edge
+    atlasLocation.fX += SK_DistanceFieldInset;
+    atlasLocation.fY += SK_DistanceFieldInset;
+    pathData->fAtlasLocation = atlasLocation;
+    
+    fPathCache.add(pathData);
+    fPathList.addToTail(pathData);
+    
+    return pathData;
+}
+
+bool GrAADistanceFieldPathRenderer::freeUnusedPlot() {
+    // find an unused plot
+    GrPlot* plot = fAtlas->getUnusedPlot();
+    if (NULL == plot) {
+        return false;
+    }
+    plot->resetRects();
+
+    // remove any paths that use this plot
+    PathDataList::Iter iter;
+    iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
+    PathData* pathData;
+    while ((pathData = iter.get())) {
+        iter.next();
+        if (plot == pathData->fPlot) {
+            fPathCache.remove(pathData->fGenID);
+            fPathList.remove(pathData);
+            SkDELETE(pathData);
+        }
+    }
+        
+    // tell the atlas to free the plot
+    GrAtlas::RemovePlot(&fPlotUsage, plot);
+    
+    return true;
+}
+
+bool GrAADistanceFieldPathRenderer::internalDrawPath(const SkPath& path,
+                                                     const PathData* pathData,
+                                                     GrDrawTarget* target) {
+    
+    GrTexture* texture = fAtlas->getTexture();
+    GrDrawState* drawState = target->drawState();
+    
+    SkASSERT(pathData->fPlot);
+    GrDrawTarget::DrawToken drawToken = target->getCurrentDrawToken();
+    pathData->fPlot->setDrawToken(drawToken);
+    
+    // make me some vertices
+    drawState->setVertexAttribs<gSDFPathVertexAttribs>(SK_ARRAY_COUNT(gSDFPathVertexAttribs),
+                                                       kSDFPathVASize);
+    void* vertices = NULL;
+    void* indices = NULL;
+    bool success = target->reserveVertexAndIndexSpace(4, 6, &vertices, &indices);
+    GrAlwaysAssert(success);
+    
+    SkScalar dx = pathData->fBounds.fLeft;
+    SkScalar dy = pathData->fBounds.fTop;
+    SkScalar width = pathData->fBounds.width();
+    SkScalar height = pathData->fBounds.height();
+    
+    SkScalar invScale = 1.0f/kScaleFactor;
+    dx *= invScale;
+    dy *= invScale;
+    width *= invScale;
+    height *= invScale;
+    
+    SkFixed tx = SkIntToFixed(pathData->fAtlasLocation.fX);
+    SkFixed ty = SkIntToFixed(pathData->fAtlasLocation.fY);
+    SkFixed tw = SkScalarToFixed(pathData->fBounds.width());
+    SkFixed th = SkScalarToFixed(pathData->fBounds.height());
+    
+    // vertex positions
+    SkRect r = SkRect::MakeXYWH(dx, dy, width, height);    
+    size_t vertSize = 2 * sizeof(SkPoint);
+    SkPoint* positions = reinterpret_cast<SkPoint*>(vertices);
+    positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertSize);
+    
+    // vertex texture coords
+    intptr_t intPtr = reinterpret_cast<intptr_t>(positions);
+    SkPoint* textureCoords = reinterpret_cast<SkPoint*>(intPtr + vertSize - sizeof(SkPoint));
+    textureCoords->setRectFan(SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx)),
+                              SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty)),
+                              SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx + tw)),
+                              SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty + th)),
+                              vertSize);
+    
+    uint16_t* indexPtr = reinterpret_cast<uint16_t*>(indices);
+    *indexPtr++ = 0;
+    *indexPtr++ = 1;
+    *indexPtr++ = 2;
+    *indexPtr++ = 0;
+    *indexPtr++ = 2;
+    *indexPtr++ = 3;
+    
+    // set up any flags
+    uint32_t flags = 0;
+    const SkMatrix& vm = drawState->getViewMatrix();
+    flags |= vm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
+    
+    GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
+    drawState->setGeometryProcessor(GrDistanceFieldNoGammaTextureEffect::Create(texture,
+                                                                                params,
+                                                                                flags))->unref();
+    
+
+    vm.mapRect(&r);
+    target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6, &r);
+    target->resetVertexSource();
+    target->resetIndexSource();
+    
+    return true;
+}
+
diff --git a/src/gpu/GrAADistanceFieldPathRenderer.h b/src/gpu/GrAADistanceFieldPathRenderer.h
new file mode 100755
index 0000000..4c09f12
--- /dev/null
+++ b/src/gpu/GrAADistanceFieldPathRenderer.h
@@ -0,0 +1,77 @@
+
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrAADistanceFieldPathRenderer_DEFINED
+#define GrAADistanceFieldPathRenderer_DEFINED
+
+#include "GrAllocPool.h"
+#include "GrAtlas.h"
+#include "GrPathRenderer.h"
+#include "GrRect.h"
+
+#include "SkChecksum.h"
+
+class GrContext;
+class GrPlot;
+
+class GrAADistanceFieldPathRenderer : public GrPathRenderer {
+public:
+    GrAADistanceFieldPathRenderer(GrContext* context)
+        : fContext(context)
+        , fAtlas(NULL) {
+    }
+    
+    virtual ~GrAADistanceFieldPathRenderer();
+    
+    virtual bool canDrawPath(const SkPath& path,
+                             const SkStrokeRec& stroke,
+                             const GrDrawTarget* target,
+                             bool antiAlias) const SK_OVERRIDE;
+
+protected:
+    virtual StencilSupport onGetStencilSupport(const SkPath&,
+                                               const SkStrokeRec&,
+                                               const GrDrawTarget*) const SK_OVERRIDE;
+    
+    virtual bool onDrawPath(const SkPath& path,
+                            const SkStrokeRec& stroke,
+                            GrDrawTarget* target,
+                            bool antiAlias) SK_OVERRIDE;
+
+private:
+    struct PathData {
+        uint32_t   fGenID;
+        GrPlot*    fPlot;
+        SkRect     fBounds;
+        SkIPoint16 fAtlasLocation;
+        SK_DECLARE_INTERNAL_LLIST_INTERFACE(PathData);
+        
+        static inline const uint32_t& GetKey(const PathData& data) {
+            return data.fGenID;
+        }
+        
+        static inline uint32_t Hash(uint32_t key) {
+            return SkChecksum::Murmur3(&key, sizeof(key));
+        }
+    };
+    typedef SkTInternalLList<PathData> PathDataList;
+    
+    GrContext*                         fContext;
+    GrAtlas*                           fAtlas;
+    GrAtlas::ClientPlotUsage           fPlotUsage;
+    SkTDynamicHash<PathData, uint32_t> fPathCache;
+    PathDataList                       fPathList;
+    
+    bool internalDrawPath(const SkPath& path, const PathData* pathData, GrDrawTarget* target);
+    PathData* addPathToAtlas(const SkPath& path, const SkStrokeRec& stroke, bool antiAlias);
+    bool freeUnusedPlot();
+    
+    typedef GrPathRenderer INHERITED;
+};
+
+#endif
diff --git a/src/gpu/GrAddPathRenderers_default.cpp b/src/gpu/GrAddPathRenderers_default.cpp
index 4f17243..1b5f085 100644
--- a/src/gpu/GrAddPathRenderers_default.cpp
+++ b/src/gpu/GrAddPathRenderers_default.cpp
@@ -10,6 +10,7 @@
 #include "GrStencilAndCoverPathRenderer.h"
 #include "GrAAHairLinePathRenderer.h"
 #include "GrAAConvexPathRenderer.h"
+#include "GrAADistanceFieldPathRenderer.h"
 #if GR_STROKE_PATH_RENDERING
 #include "../../experimental/StrokePathRenderer/GrStrokePathRenderer.h"
 #endif
@@ -31,4 +32,7 @@
         chain->addPathRenderer(pr)->unref();
     }
     chain->addPathRenderer(SkNEW(GrAAConvexPathRenderer))->unref();
+#ifndef SK_LEGACY_NO_DISTANCE_FIELD_PATHS
+    chain->addPathRenderer(SkNEW_ARGS(GrAADistanceFieldPathRenderer, (ctx)))->unref();
+#endif
 }
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index c80a13c..90aab90 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -12,6 +12,7 @@
 #include "GrGpu.h"
 
 #include "SkData.h"
+#include "SkDistanceFieldGen.h"
 #include "SkStrokeRec.h"
 
 // TODO: try to remove this #include
@@ -306,6 +307,16 @@
     }
 }
 
+/**
+ * Convert mask generation results to a signed distance field
+ */
+void GrSWMaskHelper::toSDF(unsigned char* sdf) {
+    SkAutoLockPixels alp(fBM);
+    
+    SkGenerateDistanceFieldFromA8Image(sdf, (const unsigned char*)fBM.getPixels(),
+                                       fBM.width(), fBM.height(), fBM.rowBytes());
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 /**
  * Software rasterizes path to A8 mask (possibly using the context's matrix)
diff --git a/src/gpu/GrSWMaskHelper.h b/src/gpu/GrSWMaskHelper.h
index f8cce8b..b23ee2c 100644
--- a/src/gpu/GrSWMaskHelper.h
+++ b/src/gpu/GrSWMaskHelper.h
@@ -68,13 +68,16 @@
     // Move the mask generation results from the internal bitmap to the gpu.
     void toTexture(GrTexture* texture);
 
+    // Convert mask generation results to a signed distance field
+    void toSDF(unsigned char* sdf);
+    
     // Reset the internal bitmap
     void clear(uint8_t alpha) {
         fBM.eraseColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
     }
 
     // Canonical usage utility that draws a single path and uploads it
-    // to the GPU. The result is returned in "result".
+    // to the GPU. The result is returned.
     static GrTexture* DrawPathMaskToTexture(GrContext* context,
                                             const SkPath& path,
                                             const SkStrokeRec& stroke,
diff --git a/src/gpu/effects/GrDistanceFieldTextureEffect.cpp b/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
index 245a035..265f112 100755
--- a/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
+++ b/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
@@ -86,7 +86,7 @@
         fsBuilder->codeAppend("\tfloat afwidth;\n");
         if (dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag) {
             // this gives us a smooth step across approximately one fragment
-            fsBuilder->codeAppend("\tafwidth = " SK_DistanceFieldAAFactor "*dFdx(st.x);\n");
+            fsBuilder->codeAppend("\tafwidth = abs(" SK_DistanceFieldAAFactor "*dFdx(st.x));\n");
         } else {
             fsBuilder->codeAppend("\tvec2 Jdx = dFdx(st);\n");
             fsBuilder->codeAppend("\tvec2 Jdy = dFdy(st);\n");
@@ -206,8 +206,7 @@
            fFlags == cte.fFlags;
 }
 
-void GrDistanceFieldTextureEffect::onComputeInvariantOutput(
-    InvariantOutput* inout) const {
+void GrDistanceFieldTextureEffect::onComputeInvariantOutput(InvariantOutput* inout) const {
     if (inout->isOpaque() && GrPixelConfigIsOpaque(this->texture(0)->config())) {
         inout->fValidFlags = kA_GrColorComponentFlag;
     } else {
@@ -261,6 +260,177 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+class GrGLDistanceFieldNoGammaTextureEffect : public GrGLGeometryProcessor {
+public:
+    GrGLDistanceFieldNoGammaTextureEffect(const GrBackendProcessorFactory& factory,
+                                          const GrProcessor& effect)
+        : INHERITED(factory)
+        , fTextureSize(SkISize::Make(-1, -1)) {}
+
+    virtual void emitCode(GrGLFullProgramBuilder* builder,
+                          const GrGeometryProcessor& effect,
+                          const GrProcessorKey& key,
+                          const char* outputColor,
+                          const char* inputColor,
+                          const TransformedCoordsArray&,
+                          const TextureSamplerArray& samplers) SK_OVERRIDE {
+        const GrDistanceFieldNoGammaTextureEffect& dfTexEffect =
+                                                effect.cast<GrDistanceFieldNoGammaTextureEffect>();
+        SkASSERT(1 == dfTexEffect.getVertexAttribs().count());
+
+        GrGLProcessorFragmentShaderBuilder* fsBuilder = builder->getFragmentShaderBuilder();
+        SkAssertResult(fsBuilder->enableFeature(
+                                     GrGLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
+
+        SkString fsCoordName;
+        const char* vsCoordName;
+        const char* fsCoordNamePtr;
+        builder->addVarying(kVec2f_GrSLType, "textureCoords", &vsCoordName, &fsCoordNamePtr);
+        fsCoordName = fsCoordNamePtr;
+
+        GrGLVertexShaderBuilder* vsBuilder = builder->getVertexShaderBuilder();
+        vsBuilder->codeAppendf("%s = %s;", vsCoordName, dfTexEffect.inTextureCoords().c_str());
+
+        const char* textureSizeUniName = NULL;
+        fTextureSizeUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
+                                              kVec2f_GrSLType, "TextureSize",
+                                              &textureSizeUniName);
+
+        fsBuilder->codeAppend("vec4 texColor = ");
+        fsBuilder->appendTextureLookup(samplers[0],
+                                       fsCoordName.c_str(),
+                                       kVec2f_GrSLType);
+        fsBuilder->codeAppend(";");
+        fsBuilder->codeAppend("float distance = "
+            SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
+
+        // we adjust for the effect of the transformation on the distance by using
+        // the length of the gradient of the texture coordinates. We use st coordinates
+        // to ensure we're mapping 1:1 from texel space to pixel space.
+        fsBuilder->codeAppendf("vec2 uv = %s;", fsCoordName.c_str());
+        fsBuilder->codeAppendf("vec2 st = uv*%s;", textureSizeUniName);
+        fsBuilder->codeAppend("float afwidth;");
+        if (dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag) {
+            // this gives us a smooth step across approximately one fragment
+            fsBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*dFdx(st.x));");
+        } else {
+            fsBuilder->codeAppend("vec2 Jdx = dFdx(st);");
+            fsBuilder->codeAppend("vec2 Jdy = dFdy(st);");
+
+            fsBuilder->codeAppend("vec2 uv_grad;");
+            if (builder->ctxInfo().caps()->dropsTileOnZeroDivide()) {
+                // this is to compensate for the Adreno, which likes to drop tiles on division by 0
+                fsBuilder->codeAppend("float uv_len2 = dot(uv, uv);");
+                fsBuilder->codeAppend("if (uv_len2 < 0.0001) {");
+                fsBuilder->codeAppend("uv_grad = vec2(0.7071, 0.7071);");
+                fsBuilder->codeAppend("} else {");
+                fsBuilder->codeAppend("uv_grad = uv*inversesqrt(uv_len2);");
+                fsBuilder->codeAppend("}");
+            } else {
+                fsBuilder->codeAppend("uv_grad = normalize(uv);");
+            }
+            fsBuilder->codeAppend("vec2 grad = vec2(uv_grad.x*Jdx.x + uv_grad.y*Jdy.x,");
+            fsBuilder->codeAppend("                 uv_grad.x*Jdx.y + uv_grad.y*Jdy.y);");
+
+            // this gives us a smooth step across approximately one fragment
+            fsBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
+        }
+        fsBuilder->codeAppend("float val = smoothstep(-afwidth, afwidth, distance);");
+
+        fsBuilder->codeAppendf("%s = %s;", outputColor,
+            (GrGLSLExpr4(inputColor) * GrGLSLExpr1("val")).c_str());
+    }
+
+    virtual void setData(const GrGLProgramDataManager& pdman,
+                         const GrProcessor& effect) SK_OVERRIDE {
+        SkASSERT(fTextureSizeUni.isValid());
+
+        GrTexture* texture = effect.texture(0);
+        if (texture->width() != fTextureSize.width() || 
+            texture->height() != fTextureSize.height()) {
+            fTextureSize = SkISize::Make(texture->width(), texture->height());
+            pdman.set2f(fTextureSizeUni,
+                        SkIntToScalar(fTextureSize.width()),
+                        SkIntToScalar(fTextureSize.height()));
+        }
+    }
+
+    static inline void GenKey(const GrProcessor& effect, const GrGLCaps&,
+                              GrProcessorKeyBuilder* b) {
+        const GrDistanceFieldNoGammaTextureEffect& dfTexEffect =
+            effect.cast<GrDistanceFieldNoGammaTextureEffect>();
+
+        b->add32(dfTexEffect.getFlags());
+    }
+
+private:
+    GrGLProgramDataManager::UniformHandle fTextureSizeUni;
+    SkISize                               fTextureSize;
+
+    typedef GrGLGeometryProcessor INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrDistanceFieldNoGammaTextureEffect::GrDistanceFieldNoGammaTextureEffect(GrTexture* texture,
+                                                                    const GrTextureParams& params,
+                                                                    uint32_t flags)
+    : fTextureAccess(texture, params)
+    , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
+    , fInTextureCoords(this->addVertexAttrib(GrShaderVar("inTextureCoords",
+                       kVec2f_GrSLType,
+                       GrShaderVar::kAttribute_TypeModifier))) {
+    SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
+    this->addTextureAccess(&fTextureAccess);
+}
+
+bool GrDistanceFieldNoGammaTextureEffect::onIsEqual(const GrProcessor& other) const {
+    const GrDistanceFieldNoGammaTextureEffect& cte = 
+                                                 other.cast<GrDistanceFieldNoGammaTextureEffect>();
+    return fTextureAccess == cte.fTextureAccess && fFlags == cte.fFlags;
+}
+
+void GrDistanceFieldNoGammaTextureEffect::onComputeInvariantOutput(InvariantOutput* inout) const {
+    if (inout->isOpaque() && GrPixelConfigIsOpaque(this->texture(0)->config())) {
+        inout->fValidFlags = kA_GrColorComponentFlag;
+    } else {
+        inout->fValidFlags = 0;
+    }
+    inout->fIsSingleComponent = false;
+}
+
+const GrBackendGeometryProcessorFactory& GrDistanceFieldNoGammaTextureEffect::getFactory() const {
+    return GrTBackendGeometryProcessorFactory<GrDistanceFieldNoGammaTextureEffect>::getInstance();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldNoGammaTextureEffect);
+
+GrGeometryProcessor* GrDistanceFieldNoGammaTextureEffect::TestCreate(SkRandom* random,
+                                                                     GrContext*,
+                                                                     const GrDrawTargetCaps&,
+                                                                     GrTexture* textures[]) {
+    int texIdx = random->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx 
+                                    : GrProcessorUnitTest::kAlphaTextureIdx;
+    static const SkShader::TileMode kTileModes[] = {
+        SkShader::kClamp_TileMode,
+        SkShader::kRepeat_TileMode,
+        SkShader::kMirror_TileMode,
+    };
+    SkShader::TileMode tileModes[] = {
+        kTileModes[random->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
+        kTileModes[random->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
+    };
+    GrTextureParams params(tileModes, random->nextBool() ? GrTextureParams::kBilerp_FilterMode 
+                                                         : GrTextureParams::kNone_FilterMode);
+
+    return GrDistanceFieldNoGammaTextureEffect::Create(textures[texIdx], params,
+        random->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
 class GrGLDistanceFieldLCDTextureEffect : public GrGLGeometryProcessor {
 public:
     GrGLDistanceFieldLCDTextureEffect(const GrBackendProcessorFactory& factory,
@@ -347,7 +517,7 @@
         fsBuilder->codeAppend("\tfloat afwidth;\n");
         if (isUniformScale) {
             // this gives us a smooth step across approximately one fragment
-            fsBuilder->codeAppend("\tafwidth = " SK_DistanceFieldAAFactor "*dx;\n");
+            fsBuilder->codeAppend("\tafwidth = abs(" SK_DistanceFieldAAFactor "*dx);\n");
         } else {
             fsBuilder->codeAppend("\tvec2 uv_grad;\n");
             if (builder->ctxInfo().caps()->dropsTileOnZeroDivide()) {
@@ -476,8 +646,7 @@
             fFlags == cte.fFlags);
 }
 
-void GrDistanceFieldLCDTextureEffect::onComputeInvariantOutput(
-    InvariantOutput* inout) const {
+void GrDistanceFieldLCDTextureEffect::onComputeInvariantOutput(InvariantOutput* inout) const {
     if (inout->isOpaque() && GrPixelConfigIsOpaque(this->texture(0)->config())) {
         inout->fValidFlags = kA_GrColorComponentFlag;
     } else {
diff --git a/src/gpu/effects/GrDistanceFieldTextureEffect.h b/src/gpu/effects/GrDistanceFieldTextureEffect.h
index efd622e..a49102d 100644
--- a/src/gpu/effects/GrDistanceFieldTextureEffect.h
+++ b/src/gpu/effects/GrDistanceFieldTextureEffect.h
@@ -12,6 +12,7 @@
 #include "GrGeometryProcessor.h"
 
 class GrGLDistanceFieldTextureEffect;
+class GrGLDistanceFieldNoGammaTextureEffect;
 class GrGLDistanceFieldLCDTextureEffect;
 
 enum GrDistanceFieldEffectFlags {
@@ -89,7 +90,49 @@
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
 
-    typedef GrFragmentProcessor INHERITED;
+    typedef GrGeometryProcessor INHERITED;
+};
+
+
+/**
+* The output color of this effect is a modulation of the input color and a sample from a
+* distance field texture (using a smoothed step function near 0.5).
+* It allows explicit specification of the filtering and wrap modes (GrTextureParams). The input
+* coords are a custom attribute. No gamma correct blending is applied.
+*/
+class GrDistanceFieldNoGammaTextureEffect : public GrGeometryProcessor {
+public:
+    static GrGeometryProcessor* Create(GrTexture* tex, const GrTextureParams& params,
+                                       uint32_t flags) {
+        return SkNEW_ARGS(GrDistanceFieldNoGammaTextureEffect, (tex, params, flags));
+    }
+
+    virtual ~GrDistanceFieldNoGammaTextureEffect() {}
+
+    static const char* Name() { return "DistanceFieldTexture"; }
+
+    const GrShaderVar& inTextureCoords() const { return fInTextureCoords; }
+    uint32_t getFlags() const { return fFlags; }
+
+    typedef GrGLDistanceFieldNoGammaTextureEffect GLProcessor;
+
+    virtual const GrBackendGeometryProcessorFactory& getFactory() const SK_OVERRIDE;
+
+private:
+    GrDistanceFieldNoGammaTextureEffect(GrTexture* texture, const GrTextureParams& params,
+                                        uint32_t flags);
+
+    virtual bool onIsEqual(const GrProcessor& other) const SK_OVERRIDE;
+
+    virtual void onComputeInvariantOutput(InvariantOutput* inout) const SK_OVERRIDE;
+
+    GrTextureAccess    fTextureAccess;
+    uint32_t           fFlags;
+    const GrShaderVar& fInTextureCoords;
+
+    GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
+
+    typedef GrGeometryProcessor INHERITED;
 };
 
 /**
@@ -137,7 +180,7 @@
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
 
-    typedef GrFragmentProcessor INHERITED;
+    typedef GrGeometryProcessor INHERITED;
 };
 
 #endif