Add canFilterMaskGPU & filterMaskGPU to SkMaskFilter

https://codereview.chromium.org/18110012/



git-svn-id: http://skia.googlecode.com/svn/trunk@9888 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h
index aa499ec..40a03a5 100644
--- a/include/core/SkMaskFilter.h
+++ b/include/core/SkMaskFilter.h
@@ -14,6 +14,7 @@
 #include "SkMask.h"
 #include "SkPaint.h"
 
+class SkBitmap;
 class SkBlitter;
 class SkBounder;
 class SkMatrix;
@@ -58,27 +59,56 @@
     virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
                             SkIPoint* margin) const;
 
-    enum BlurType {
-        kNone_BlurType,    //!< this maskfilter is not a blur
-        kNormal_BlurType,  //!< fuzzy inside and outside
-        kSolid_BlurType,   //!< solid inside, fuzzy outside
-        kOuter_BlurType,   //!< nothing inside, fuzzy outside
-        kInner_BlurType    //!< fuzzy inside, nothing outside
-    };
-
-    struct BlurInfo {
-        SkScalar fRadius;
-        bool     fIgnoreTransform;
-        bool     fHighQuality;
-    };
+#if SK_SUPPORT_GPU
+    /**
+     *  Returns true if the filter can be expressed a single-pass
+     *  GrEffect, used to process this filter on the GPU, or false if
+     *  not.
+     *
+     *  If effect is non-NULL, a new GrEffect instance is stored
+     *  in it.  The caller assumes ownership of the stage, and it is up to the
+     *  caller to unref it.
+     */
+    virtual bool asNewEffect(GrEffectRef** effect, GrTexture*) const;
 
     /**
-     *  Optional method for maskfilters that can be described as a blur. If so,
-     *  they return the corresponding BlurType and set the fields in BlurInfo
-     *  (if not null). If they cannot be described as a blur, they return
-     *  kNone_BlurType and ignore the info parameter.
+     *  Returns true if the filter can be processed on the GPU.  This is most
+     *  often used for multi-pass effects, where intermediate results must be
+     *  rendered to textures.  For single-pass effects, use asNewEffect().
+     *
+     *  'maskRect' returns the device space portion of the mask the the filter
+     *  needs. The mask passed into 'filterMaskGPU' should have the same extent
+     *  as 'maskRect' but be translated to the upper-left corner of the mask
+     *  (i.e., (maskRect.fLeft, maskRect.fTop) appears at (0, 0) in the mask).
      */
-    virtual BlurType asABlur(BlurInfo*) const;
+    virtual bool canFilterMaskGPU(const SkRect& devBounds, 
+                                  const SkIRect& clipBounds,
+                                  const SkMatrix& ctm,
+                                  SkRect* maskRect) const;
+
+    /**
+     *  Perform this mask filter on the GPU.  This is most often used for
+     *  multi-pass effects, where intermediate results must be rendered to
+     *  textures.  For single-pass effects, use asNewEffect().  'src' is the
+     *  source image for processing, as a texture-backed bitmap.  'result' is
+     *  the destination bitmap, which should contain a texture-backed pixelref
+     *  on success. 'maskRect' should be the rect returned from canFilterMaskGPU.
+     */
+    bool filterMaskGPU(GrContext* context,
+                       const SkBitmap& src, 
+                       const SkRect& maskRect, 
+                       SkBitmap* result) const;
+
+    /**
+     *  This flavor of 'filterMaskGPU' provides a more direct means of accessing
+     *  the filtering capabilities. Setting 'canOverwriteSrc' can allow some
+     *  filters to skip the allocation of an additional texture.
+     */
+    virtual bool filterMaskGPU(GrTexture* src, 
+                               const SkRect& maskRect, 
+                               GrTexture** result,
+                               bool canOverwriteSrc) const;
+#endif
 
     /**
      * The fast bounds function is used to enable the paint to be culled early
diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp
index 0346ce0..fbee4dc 100644
--- a/src/core/SkMaskFilter.cpp
+++ b/src/core/SkMaskFilter.cpp
@@ -11,7 +11,11 @@
 #include "SkBlitter.h"
 #include "SkBounder.h"
 #include "SkDraw.h"
+#include "SkGr.h"
+#include "SkGrPixelRef.h"
 #include "SkRasterClip.h"
+#include "SkTypes.h"
+#include "GrTexture.h"
 
 
 SK_DEFINE_INST_COUNT(SkMaskFilter)
@@ -266,8 +270,69 @@
     return kUnimplemented_FilterReturn;
 }
 
-SkMaskFilter::BlurType SkMaskFilter::asABlur(BlurInfo*) const {
-    return kNone_BlurType;
+bool SkMaskFilter::asNewEffect(GrEffectRef** effect, GrTexture*) const {
+    return false;
+}
+
+bool SkMaskFilter::canFilterMaskGPU(const SkRect& devBounds, 
+                                    const SkIRect& clipBounds,
+                                    const SkMatrix& ctm,
+                                    SkRect* maskRect) const {
+    return false;
+}
+
+bool SkMaskFilter::filterMaskGPU(GrContext* context,
+                                 const SkBitmap& srcBM, 
+                                 const SkRect& maskRect, 
+                                 SkBitmap* resultBM) const {
+    SkAutoTUnref<GrTexture> src;
+    bool canOverwriteSrc = false;
+    if (NULL == srcBM.getTexture()) {
+        GrTextureDesc desc;
+        // Needs to be a render target to be overwritten in filterMaskGPU
+        desc.fFlags     = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+        desc.fConfig    = SkBitmapConfig2GrPixelConfig(srcBM.config());
+        desc.fWidth     = srcBM.width();
+        desc.fHeight    = srcBM.height();
+
+        // TODO: right now this is exact to guard against out of bounds reads
+        // by the filter code. More thought needs to be devoted to the 
+        // "filterMaskGPU" contract and then enforced (i.e., clamp the code
+        // in "filterMaskGPU" so it never samples beyond maskRect)
+        GrAutoScratchTexture ast(context, desc, GrContext::kExact_ScratchTexMatch);
+        if (NULL == ast.texture()) {
+            return false;
+        }
+
+        SkAutoLockPixels alp(srcBM);
+        ast.texture()->writePixels(0, 0, srcBM.width(), srcBM.height(),
+                                   desc.fConfig,
+                                   srcBM.getPixels(), srcBM.rowBytes());
+
+        src.reset(ast.detach());
+        canOverwriteSrc = true;
+    } else {
+        src.reset((GrTexture*) srcBM.getTexture());
+        src.get()->ref();
+    }
+    GrTexture* dst;
+
+    bool result = this->filterMaskGPU(src, maskRect, &dst, canOverwriteSrc);
+    if (!result) {
+        return false;
+    }
+
+    resultBM->setConfig(srcBM.config(), dst->width(), dst->height());
+    resultBM->setPixelRef(SkNEW_ARGS(SkGrPixelRef, (dst)))->unref();
+    dst->unref();
+    return true;
+}
+
+bool SkMaskFilter::filterMaskGPU(GrTexture* src, 
+                                 const SkRect& maskRect, 
+                                 GrTexture** result,
+                                 bool canOverwriteSrc) const {
+    return false;
 }
 
 void SkMaskFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
diff --git a/src/device/xps/SkXPSDevice.cpp b/src/device/xps/SkXPSDevice.cpp
index d7a5598..bfae6de 100644
--- a/src/device/xps/SkXPSDevice.cpp
+++ b/src/device/xps/SkXPSDevice.cpp
@@ -1542,21 +1542,6 @@
                                SkMatrix* matrix,
                                SkVector* ppuScale,
                                const SkIRect& clip, SkIRect* clipIRect) {
-    //TODO: currently ignoring the ppm if blur ignoring transform.
-    if (filter) {
-        SkMaskFilter::BlurInfo blurInfo;
-        SkMaskFilter::BlurType blurType = filter->asABlur(&blurInfo);
-
-        if (SkMaskFilter::kNone_BlurType != blurType
-            && blurInfo.fIgnoreTransform) {
-
-            ppuScale->fX = SK_Scalar1;
-            ppuScale->fY = SK_Scalar1;
-            *clipIRect = clip;
-            return;
-        }
-    }
-
     //This action is in unit space, but the ppm is specified in physical space.
     ppuScale->fX = SkScalarDiv(this->fCurrentPixelsPerMeter.fX,
                                this->fCurrentUnitsPerMeter.fX);
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index eaf7704..8c14bc8 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -10,10 +10,16 @@
 #include "SkBlurMask.h"
 #include "SkFlattenableBuffers.h"
 #include "SkMaskFilter.h"
-#include "SkBounder.h"
-#include "SkRasterClip.h"
 #include "SkRTConf.h"
 #include "SkStringUtils.h"
+#include "SkStrokeRec.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "effects/GrSimpleTextureEffect.h"
+#include "SkGrPixelRef.h"
+#endif
 
 class SkBlurMaskFilterImpl : public SkMaskFilter {
 public:
@@ -25,7 +31,17 @@
     virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
                             SkIPoint* margin) const SK_OVERRIDE;
 
-    virtual BlurType asABlur(BlurInfo*) const SK_OVERRIDE;
+#if SK_SUPPORT_GPU
+    virtual bool canFilterMaskGPU(const SkRect& devBounds, 
+                                  const SkIRect& clipBounds,
+                                  const SkMatrix& ctm,
+                                  SkRect* maskRect) const SK_OVERRIDE;
+    virtual bool filterMaskGPU(GrTexture* src, 
+                               const SkRect& maskRect, 
+                               GrTexture** result,
+                               bool canOverwriteSrc) const;
+#endif
+
     virtual void computeFastBounds(const SkRect&, SkRect*) const SK_OVERRIDE;
 
     SkDEVCODE(virtual void toString(SkString* str) const SK_OVERRIDE;)
@@ -40,16 +56,41 @@
                         SkIPoint* margin, SkMask::CreateMode createMode) const;
 
 private:
+    // To avoid unseemly allocation requests (esp. for finite platforms like
+    // handset) we limit the radius so something manageable. (as opposed to
+    // a request like 10,000)
+    static const SkScalar kMAX_RADIUS;
+    // This constant approximates the scaling done in the software path's
+    // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
+    // IMHO, it actually should be 1:  we blur "less" than we should do
+    // according to the CSS and canvas specs, simply because Safari does the same.
+    // Firefox used to do the same too, until 4.0 where they fixed it.  So at some
+    // point we should probably get rid of these scaling constants and rebaseline
+    // all the blur tests.
+    static const SkScalar kBLUR_SIGMA_SCALE;
+
     SkScalar                    fRadius;
     SkBlurMaskFilter::BlurStyle fBlurStyle;
     uint32_t                    fBlurFlags;
 
     SkBlurMaskFilterImpl(SkFlattenableReadBuffer&);
     virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+#if SK_SUPPORT_GPU
+    SkScalar computeXformedRadius(const SkMatrix& ctm) const {
+        bool ignoreTransform = SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag);
+
+        SkScalar xformedRadius = ignoreTransform ? fRadius
+                                                 : ctm.mapRadius(fRadius);
+        return SkMinScalar(xformedRadius, kMAX_RADIUS);
+    }
+#endif
 
     typedef SkMaskFilter INHERITED;
 };
 
+const SkScalar SkBlurMaskFilterImpl::kMAX_RADIUS = SkIntToScalar(128);
+const SkScalar SkBlurMaskFilterImpl::kBLUR_SIGMA_SCALE = 0.6f;
+
 SkMaskFilter* SkBlurMaskFilter::Create(SkScalar radius,
                                        SkBlurMaskFilter::BlurStyle style,
                                        uint32_t flags) {
@@ -97,11 +138,7 @@
         radius = matrix.mapRadius(fRadius);
     }
 
-    // To avoid unseemly allocation requests (esp. for finite platforms like
-    // handset) we limit the radius so something manageable. (as opposed to
-    // a request like 10,000)
-    static const SkScalar MAX_RADIUS = SkIntToScalar(128);
-    radius = SkMinScalar(radius, MAX_RADIUS);
+    radius = SkMinScalar(radius, kMAX_RADIUS);
     SkBlurMask::Quality blurQuality =
         (fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ?
             SkBlurMask::kHigh_Quality : SkBlurMask::kLow_Quality;
@@ -120,11 +157,7 @@
         radius = matrix.mapRadius(fRadius);
     }
 
-    // To avoid unseemly allocation requests (esp. for finite platforms like
-    // handset) we limit the radius so something manageable. (as opposed to
-    // a request like 10,000)
-    static const SkScalar MAX_RADIUS = SkIntToScalar(128);
-    radius = SkMinScalar(radius, MAX_RADIUS);
+    radius = SkMinScalar(radius, kMAX_RADIUS);
 
     return SkBlurMask::BlurRect(dst, r, radius, (SkBlurMask::Style)fBlurStyle,
                                 margin, createMode);
@@ -320,22 +353,99 @@
     buffer.writeUInt(fBlurFlags);
 }
 
-static const SkMaskFilter::BlurType gBlurStyle2BlurType[] = {
-    SkMaskFilter::kNormal_BlurType,
-    SkMaskFilter::kSolid_BlurType,
-    SkMaskFilter::kOuter_BlurType,
-    SkMaskFilter::kInner_BlurType,
-};
+#if SK_SUPPORT_GPU
 
-SkMaskFilter::BlurType SkBlurMaskFilterImpl::asABlur(BlurInfo* info) const {
-    if (info) {
-        info->fRadius = fRadius;
-        info->fIgnoreTransform = SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag);
-        info->fHighQuality = SkToBool(fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag);
+bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,
+                                            const SkIRect& clipBounds,
+                                            const SkMatrix& ctm,
+                                            SkRect* maskRect) const {
+    SkScalar xformedRadius = this->computeXformedRadius(ctm);
+    if (xformedRadius <= 0) {
+        return false;
     }
-    return gBlurStyle2BlurType[fBlurStyle];
+
+    static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64);
+    static const SkScalar kMIN_GPU_BLUR_RADIUS = SkIntToScalar(32);
+
+    if (srcBounds.width() <= kMIN_GPU_BLUR_SIZE &&
+        srcBounds.height() <= kMIN_GPU_BLUR_SIZE &&
+        xformedRadius <= kMIN_GPU_BLUR_RADIUS) {
+        // We prefer to blur small rect with small radius via CPU.
+        return false;
+    }
+
+    if (NULL == maskRect) {
+        // don't need to compute maskRect
+        return true;
+    }
+
+    float sigma3 = 3 * SkScalarToFloat(xformedRadius) * kBLUR_SIGMA_SCALE;
+
+    SkRect clipRect = SkRect::MakeFromIRect(clipBounds);
+    SkRect srcRect(srcBounds);
+
+    // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area.
+    srcRect.outset(SkFloatToScalar(sigma3), SkFloatToScalar(sigma3));
+    clipRect.outset(SkFloatToScalar(sigma3), SkFloatToScalar(sigma3));
+    srcRect.intersect(clipRect);
+    *maskRect = srcRect;
+    return true; 
 }
 
+bool SkBlurMaskFilterImpl::filterMaskGPU(GrTexture* src, 
+                                         const SkRect& maskRect, 
+                                         GrTexture** result,
+                                         bool canOverwriteSrc) const {
+    SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height());
+
+    GrContext* context = src->getContext();
+
+    GrContext::AutoWideOpenIdentityDraw awo(context, NULL);
+
+    SkScalar xformedRadius = this->computeXformedRadius(context->getMatrix());
+    SkASSERT(xformedRadius > 0);
+
+    float sigma = SkScalarToFloat(xformedRadius) * kBLUR_SIGMA_SCALE;
+
+    // If we're doing a normal blur, we can clobber the pathTexture in the
+    // gaussianBlur.  Otherwise, we need to save it for later compositing.
+    bool isNormalBlur = (SkBlurMaskFilter::kNormal_BlurStyle == fBlurStyle);
+    *result = context->gaussianBlur(src, isNormalBlur && canOverwriteSrc,
+                                    clipRect, sigma, sigma);
+    if (NULL == result) {
+        return false;
+    }
+
+    if (!isNormalBlur) {
+        context->setIdentityMatrix();
+        GrPaint paint;
+        SkMatrix matrix;
+        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();
+        if (SkBlurMaskFilter::kInner_BlurStyle == fBlurStyle) {
+            // inner:  dst = dst * src
+            paint.setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff);
+        } else if (SkBlurMaskFilter::kSolid_BlurStyle == fBlurStyle) {
+            // solid:  dst = src + dst - src * dst
+            //             = (1 - dst) * src + 1 * dst
+            paint.setBlendFunc(kIDC_GrBlendCoeff, kOne_GrBlendCoeff);
+        } else if (SkBlurMaskFilter::kOuter_BlurStyle == fBlurStyle) {
+            // outer:  dst = dst * (1 - src)
+            //             = 0 * src + (1 - src) * dst
+            paint.setBlendFunc(kZero_GrBlendCoeff, kISC_GrBlendCoeff);
+        }
+        context->drawRect(paint, clipRect);
+    }
+
+    return true;
+}
+
+#endif // SK_SUPPORT_GPU
+
+
 #ifdef SK_DEVELOPER
 void SkBlurMaskFilterImpl::toString(SkString* str) const {
     str->append("SkBlurMaskFilterImpl: (");
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index bfc3695..01b8021 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -47,18 +47,6 @@
     kXfermodeEffectIdx = 2,
 };
 
-#define MAX_BLUR_SIGMA 4.0f
-// FIXME:  This value comes from from SkBlurMaskFilter.cpp.
-// Should probably be put in a common header someplace.
-#define MAX_BLUR_RADIUS SkIntToScalar(128)
-// This constant approximates the scaling done in the software path's
-// "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
-// IMHO, it actually should be 1:  we blur "less" than we should do
-// according to the CSS and canvas specs, simply because Safari does the same.
-// Firefox used to do the same too, until 4.0 where they fixed it.  So at some
-// point we should probably get rid of these scaling constants and rebaseline
-// all the blur tests.
-#define BLUR_SIGMA_SCALE 0.6f
 // 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.
@@ -776,138 +764,11 @@
 // helpers for applying mask filters
 namespace {
 
-// We prefer to blur small rect with small radius via CPU.
-#define MIN_GPU_BLUR_SIZE SkIntToScalar(64)
-#define MIN_GPU_BLUR_RADIUS SkIntToScalar(32)
-inline bool shouldDrawBlurWithCPU(const SkRect& rect, SkScalar radius) {
-    if (rect.width() <= MIN_GPU_BLUR_SIZE &&
-        rect.height() <= MIN_GPU_BLUR_SIZE &&
-        radius <= MIN_GPU_BLUR_RADIUS) {
-        return true;
-    }
-    return false;
-}
-
-bool drawWithGPUMaskFilter(GrContext* context, const SkPath& devPath, const SkStrokeRec& stroke,
-                           SkMaskFilter* filter, const SkRegion& clip,
-                           SkBounder* bounder, GrPaint* grp) {
-    SkMaskFilter::BlurInfo info;
-    SkMaskFilter::BlurType blurType = filter->asABlur(&info);
-    if (SkMaskFilter::kNone_BlurType == blurType) {
-        return false;
-    }
-    SkScalar radius = info.fIgnoreTransform ? info.fRadius
-                                            : context->getMatrix().mapRadius(info.fRadius);
-    radius = SkMinScalar(radius, MAX_BLUR_RADIUS);
-    if (radius <= 0) {
-        return false;
-    }
-
-    SkRect srcRect = devPath.getBounds();
-    if (shouldDrawBlurWithCPU(srcRect, radius)) {
-        return false;
-    }
-
-    float sigma = SkScalarToFloat(radius) * BLUR_SIGMA_SCALE;
-    float sigma3 = sigma * 3.0f;
-
-    SkRect clipRect;
-    clipRect.set(clip.getBounds());
-
-    // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area.
-    srcRect.inset(SkFloatToScalar(-sigma3), SkFloatToScalar(-sigma3));
-    clipRect.inset(SkFloatToScalar(-sigma3), SkFloatToScalar(-sigma3));
-    srcRect.intersect(clipRect);
-    SkRect finalRect = srcRect;
-    SkIRect finalIRect;
-    finalRect.roundOut(&finalIRect);
-    if (clip.quickReject(finalIRect)) {
-        return true;
-    }
-    if (bounder && !bounder->doIRect(finalIRect)) {
-        return true;
-    }
-    GrPoint offset = GrPoint::Make(-srcRect.fLeft, -srcRect.fTop);
-    srcRect.offset(offset);
-    GrTextureDesc desc;
-    desc.fFlags = kRenderTarget_GrTextureFlagBit;
-    desc.fWidth = SkScalarCeilToInt(srcRect.width());
-    desc.fHeight = SkScalarCeilToInt(srcRect.height());
-    // We actually only need A8, but it often isn't supported as a
-    // render target so default to RGBA_8888
-    desc.fConfig = kRGBA_8888_GrPixelConfig;
-
-    if (context->isConfigRenderable(kAlpha_8_GrPixelConfig)) {
-        desc.fConfig = kAlpha_8_GrPixelConfig;
-    }
-
-    GrAutoScratchTexture pathEntry(context, desc);
-    GrTexture* pathTexture = pathEntry.texture();
-    if (NULL == pathTexture) {
-        return false;
-    }
-
-    SkAutoTUnref<GrTexture> blurTexture;
-
-    {
-        GrContext::AutoRenderTarget art(context, pathTexture->asRenderTarget());
-        GrContext::AutoClip ac(context, srcRect);
-
-        context->clear(NULL, 0);
-
-        GrPaint tempPaint;
-        if (grp->isAntiAlias()) {
-            tempPaint.setAntiAlias(true);
-            // AA uses the "coverage" stages on GrDrawTarget. Coverage with a dst
-            // blend coeff of zero requires dual source blending support in order
-            // to properly blend partially covered pixels. This means the AA
-            // code path may not be taken. So we use a dst blend coeff of ISA. We
-            // could special case AA draws to a dst surface with known alpha=0 to
-            // use a zero dst coeff when dual source blending isn't available.f
-            tempPaint.setBlendFunc(kOne_GrBlendCoeff, kISC_GrBlendCoeff);
-        }
-
-        GrContext::AutoMatrix am;
-
-        // Draw hard shadow to pathTexture with path top-left at origin using tempPaint.
-        SkMatrix translate;
-        translate.setTranslate(offset.fX, offset.fY);
-        am.set(context, translate);
-        context->drawPath(tempPaint, devPath, stroke);
-
-        // If we're doing a normal blur, we can clobber the pathTexture in the
-        // gaussianBlur.  Otherwise, we need to save it for later compositing.
-        bool isNormalBlur = blurType == SkMaskFilter::kNormal_BlurType;
-        blurTexture.reset(context->gaussianBlur(pathTexture, isNormalBlur,
-                                                srcRect, sigma, sigma));
-        if (NULL == blurTexture) {
-            return false;
-        }
-
-        if (!isNormalBlur) {
-            context->setIdentityMatrix();
-            GrPaint paint;
-            SkMatrix matrix;
-            matrix.setIDiv(pathTexture->width(), pathTexture->height());
-            // Blend pathTexture over blurTexture.
-            context->setRenderTarget(blurTexture->asRenderTarget());
-            paint.colorStage(0)->setEffect(
-                GrSimpleTextureEffect::Create(pathTexture, matrix))->unref();
-            if (SkMaskFilter::kInner_BlurType == blurType) {
-                // inner:  dst = dst * src
-                paint.setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff);
-            } else if (SkMaskFilter::kSolid_BlurType == blurType) {
-                // solid:  dst = src + dst - src * dst
-                //             = (1 - dst) * src + 1 * dst
-                paint.setBlendFunc(kIDC_GrBlendCoeff, kOne_GrBlendCoeff);
-            } else if (SkMaskFilter::kOuter_BlurType == blurType) {
-                // outer:  dst = dst * (1 - src)
-                //             = 0 * src + (1 - src) * dst
-                paint.setBlendFunc(kZero_GrBlendCoeff, kISC_GrBlendCoeff);
-            }
-            context->drawRect(paint, srcRect);
-        }
-    }
+// Draw a mask using the supplied paint. Since the coverage/geometry
+// is already burnt into the mask this boils down to a rect draw.
+// 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)) {
@@ -919,19 +780,18 @@
     GrAssert(!grp->isCoverageStageEnabled(MASK_IDX));
 
     SkMatrix matrix;
-    matrix.setTranslate(-finalRect.fLeft, -finalRect.fTop);
-    matrix.postIDiv(blurTexture->width(), blurTexture->height());
+    matrix.setTranslate(-maskRect.fLeft, -maskRect.fTop);
+    matrix.postIDiv(mask->width(), mask->height());
 
     grp->coverageStage(MASK_IDX)->reset();
-    grp->coverageStage(MASK_IDX)->setEffect(
-        GrSimpleTextureEffect::Create(blurTexture, matrix))->unref();
-    context->drawRect(*grp, finalRect);
+    grp->coverageStage(MASK_IDX)->setEffect(GrSimpleTextureEffect::Create(mask, matrix))->unref();
+    context->drawRect(*grp, maskRect);
     return true;
 }
 
-bool drawWithMaskFilter(GrContext* context, const SkPath& devPath,
-                        SkMaskFilter* filter, const SkRegion& clip, SkBounder* bounder,
-                        GrPaint* grp, SkPaint::Style style) {
+bool draw_with_mask_filter(GrContext* context, const SkPath& devPath,
+                           SkMaskFilter* filter, const SkRegion& clip, SkBounder* bounder,
+                           GrPaint* grp, SkPaint::Style style) {
     SkMask  srcM, dstM;
 
     if (!SkDraw::DrawToMask(devPath, &clip.getBounds(), filter, &context->getMatrix(), &srcM,
@@ -955,9 +815,6 @@
 
     // we now have a device-aligned 8bit mask in dstM, ready to be drawn using
     // the current clip (and identity matrix) and GrPaint settings
-    GrContext::AutoMatrix am;
-    am.setIdentity(context, grp);
-
     GrTextureDesc desc;
     desc.fWidth = dstM.fBounds.width();
     desc.fHeight = dstM.fBounds.height();
@@ -972,28 +829,75 @@
     texture->writePixels(0, 0, desc.fWidth, desc.fHeight, desc.fConfig,
                                dstM.fImage, dstM.fRowBytes);
 
-    static const int MASK_IDX = GrPaint::kMaxCoverageStages - 1;
-    // we assume the last mask index is available for use
-    GrAssert(!grp->isCoverageStageEnabled(MASK_IDX));
+    SkRect maskRect = SkRect::MakeFromIRect(dstM.fBounds);
 
-    SkMatrix m;
-    m.setTranslate(-dstM.fBounds.fLeft*SK_Scalar1, -dstM.fBounds.fTop*SK_Scalar1);
-    m.postIDiv(texture->width(), texture->height());
+    return draw_mask(context, maskRect, grp, texture);
+}
 
-    grp->coverageStage(MASK_IDX)->setEffect(GrSimpleTextureEffect::Create(texture, m))->unref();
-    GrRect d;
-    d.setLTRB(SkIntToScalar(dstM.fBounds.fLeft),
-              SkIntToScalar(dstM.fBounds.fTop),
-              SkIntToScalar(dstM.fBounds.fRight),
-              SkIntToScalar(dstM.fBounds.fBottom));
+// Create a mask of 'devPath' and place the result in 'mask'. Return true on
+// success; false otherwise.
+bool create_mask_GPU(GrContext* context,
+                     const SkRect& maskRect,
+                     const SkPath& devPath,
+                     const SkStrokeRec& stroke,
+                     bool doAA,
+                     GrAutoScratchTexture* mask) {
+    GrTextureDesc desc;
+    desc.fFlags = kRenderTarget_GrTextureFlagBit;
+    desc.fWidth = SkScalarCeilToInt(maskRect.width());
+    desc.fHeight = SkScalarCeilToInt(maskRect.height());
+    // We actually only need A8, but it often isn't supported as a
+    // render target so default to RGBA_8888
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+    if (context->isConfigRenderable(kAlpha_8_GrPixelConfig)) {
+        desc.fConfig = kAlpha_8_GrPixelConfig;
+    }
 
-    context->drawRect(*grp, d);
+    mask->set(context, desc);
+    if (NULL == mask->texture()) {
+        return false;
+    }
+
+    GrTexture* maskTexture = mask->texture();
+    SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height());
+
+    GrContext::AutoRenderTarget art(context, maskTexture->asRenderTarget());
+    GrContext::AutoClip ac(context, clipRect);
+
+    context->clear(NULL, 0x0);
+
+    GrPaint tempPaint;
+    if (doAA) {
+        tempPaint.setAntiAlias(true);
+        // AA uses the "coverage" stages on GrDrawTarget. Coverage with a dst
+        // blend coeff of zero requires dual source blending support in order
+        // to properly blend partially covered pixels. This means the AA
+        // code path may not be taken. So we use a dst blend coeff of ISA. We
+        // could special case AA draws to a dst surface with known alpha=0 to
+        // use a zero dst coeff when dual source blending isn't available.
+        tempPaint.setBlendFunc(kOne_GrBlendCoeff, kISC_GrBlendCoeff);
+    }
+
+    GrContext::AutoMatrix am;
+
+    // Draw the mask into maskTexture with the path's top-left at the origin using tempPaint.
+    SkMatrix translate;
+    translate.setTranslate(-maskRect.fLeft, -maskRect.fTop);
+    am.set(context, translate);
+    context->drawPath(tempPaint, devPath, stroke);
     return true;
 }
 
+SkBitmap wrap_texture(GrTexture* texture) {
+    SkBitmap result;
+    bool dummy;
+    SkBitmap::Config config = grConfig2skConfig(texture->config(), &dummy);
+    result.setConfig(config, texture->width(), texture->height());
+    result.setPixelRef(SkNEW_ARGS(SkGrPixelRef, (texture)))->unref();
+    return result;
 }
 
-///////////////////////////////////////////////////////////////////////////////
+};
 
 void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath,
                            const SkPaint& paint, const SkMatrix* prePathMatrix,
@@ -1051,6 +955,7 @@
         if (!stroke.isHairlineStyle()) {
             if (stroke.applyToPath(&tmpPath, *pathPtr)) {
                 pathPtr = &tmpPath;
+                pathIsMutable = true;
                 stroke.setFillStyle();
             }
         }
@@ -1060,13 +965,46 @@
 
         // transform the path into device space
         pathPtr->transform(fContext->getMatrix(), devPathPtr);
-        if (!drawWithGPUMaskFilter(fContext, *devPathPtr, stroke, paint.getMaskFilter(),
-                                   *draw.fClip, draw.fBounder, &grPaint)) {
-            SkPaint::Style style = stroke.isHairlineStyle() ? SkPaint::kStroke_Style :
-                                                              SkPaint::kFill_Style;
-            drawWithMaskFilter(fContext, *devPathPtr, paint.getMaskFilter(),
-                               *draw.fClip, draw.fBounder, &grPaint, style);
+
+        SkRect maskRect;
+        if (paint.getMaskFilter()->canFilterMaskGPU(devPathPtr->getBounds(),
+                                                    draw.fClip->getBounds(),
+                                                    fContext->getMatrix(),
+                                                    &maskRect)) {
+            SkIRect finalIRect;
+            maskRect.roundOut(&finalIRect);
+            if (draw.fClip->quickReject(finalIRect)) {
+                // clipped out
+                return;
+            }
+            if (NULL != draw.fBounder && !draw.fBounder->doIRect(finalIRect)) {
+                // nothing to draw
+                return;
+            }
+
+            GrAutoScratchTexture mask;
+
+            if (create_mask_GPU(fContext, maskRect, *devPathPtr, stroke, 
+                                grPaint.isAntiAlias(), &mask)) {
+                GrTexture* filtered;
+
+                if (paint.getMaskFilter()->filterMaskGPU(mask.texture(), maskRect, &filtered, true)) {
+                    SkAutoTUnref<GrTexture> atu(filtered);
+
+                    if (draw_mask(fContext, maskRect, &grPaint, filtered)) {
+                        // This path is completely drawn
+                        return;
+                    }
+                }
+            }
         }
+
+        // draw the mask on the CPU - this is a fallthrough path in case the
+        // GPU path fails
+        SkPaint::Style style = stroke.isHairlineStyle() ? SkPaint::kStroke_Style :
+                                                          SkPaint::kFill_Style;
+        draw_with_mask_filter(fContext, *devPathPtr, paint.getMaskFilter(),
+                              *draw.fClip, draw.fBounder, &grPaint, style);
         return;
     }
 
@@ -1437,15 +1375,6 @@
     fContext->drawRectToRect(*grPaint, dstRect, paintRect, &m);
 }
 
-static SkBitmap wrap_texture(GrTexture* texture) {
-    SkBitmap result;
-    bool dummy;
-    SkBitmap::Config config = grConfig2skConfig(texture->config(), &dummy);
-    result.setConfig(config, texture->width(), texture->height());
-    result.setPixelRef(SkNEW_ARGS(SkGrPixelRef, (texture)))->unref();
-    return result;
-}
-
 static bool filter_texture(SkDevice* device, GrContext* context,
                            GrTexture* texture, SkImageFilter* filter,
                            int w, int h, SkBitmap* result) {
@@ -1608,8 +1537,6 @@
         return false;
     }
 
-    GrPaint paint;
-
     GrTexture* texture;
     // We assume here that the filter will not attempt to tile the src. Otherwise, this cache lookup
     // must be pushed upstack.
diff --git a/tests/BlurTest.cpp b/tests/BlurTest.cpp
index 57dc940..6fb36b6 100644
--- a/tests/BlurTest.cpp
+++ b/tests/BlurTest.cpp
@@ -105,18 +105,6 @@
             SkMaskFilter* filter;
             filter = SkBlurMaskFilter::Create(radius, blurStyle, flags);
 
-            SkMaskFilter::BlurInfo info;
-            sk_bzero(&info, sizeof(info));
-            SkMaskFilter::BlurType type = filter->asABlur(&info);
-
-            REPORTER_ASSERT(reporter, type ==
-                static_cast<SkMaskFilter::BlurType>(style + 1));
-            REPORTER_ASSERT(reporter, info.fRadius == radius);
-            REPORTER_ASSERT(reporter, info.fIgnoreTransform ==
-                SkToBool(flags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag));
-            REPORTER_ASSERT(reporter, info.fHighQuality ==
-                SkToBool(flags & SkBlurMaskFilter::kHighQuality_BlurFlag));
-
             paint.setMaskFilter(filter);
             filter->unref();
 
diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp
index 12a1070..2730a23 100644
--- a/tools/PictureRenderer.cpp
+++ b/tools/PictureRenderer.cpp
@@ -64,10 +64,9 @@
 
     virtual bool filter(SkPaint* paint, Type t) {
         paint->setFlags(paint->getFlags() & ~fFlags[t] & SkPaint::kAllFlags);
-        if (PictureRenderer::kBlur_DrawFilterFlag & fFlags[t]) {
+        if (PictureRenderer::kMaskFilter_DrawFilterFlag & fFlags[t]) {
             SkMaskFilter* maskFilter = paint->getMaskFilter();
-            SkMaskFilter::BlurInfo blurInfo;
-            if (maskFilter && maskFilter->asABlur(&blurInfo)) {
+            if (NULL != maskFilter) {
                 paint->setMaskFilter(NULL);
             }
         }
diff --git a/tools/PictureRenderer.h b/tools/PictureRenderer.h
index cb06679..738b812 100644
--- a/tools/PictureRenderer.h
+++ b/tools/PictureRenderer.h
@@ -59,13 +59,13 @@
     // this uses SkPaint::Flags as a base and adds additional flags
     enum DrawFilterFlags {
         kNone_DrawFilterFlag = 0,
-        kBlur_DrawFilterFlag = 0x8000, // toggles between blur and no blur
+        kMaskFilter_DrawFilterFlag = 0x8000, // toggles on/off mask filters (e.g., blurs)
         kHinting_DrawFilterFlag = 0x10000, // toggles between no hinting and normal hinting
         kSlightHinting_DrawFilterFlag = 0x20000, // toggles between slight and normal hinting
         kAAClip_DrawFilterFlag = 0x40000, // toggles between soft and hard clip
     };
 
-    SK_COMPILE_ASSERT(!(kBlur_DrawFilterFlag & SkPaint::kAllFlags), blur_flag_must_be_greater);
+    SK_COMPILE_ASSERT(!(kMaskFilter_DrawFilterFlag & SkPaint::kAllFlags), maskfilter_flag_must_be_greater);
     SK_COMPILE_ASSERT(!(kHinting_DrawFilterFlag & SkPaint::kAllFlags),
             hinting_flag_must_be_greater);
     SK_COMPILE_ASSERT(!(kSlightHinting_DrawFilterFlag & SkPaint::kAllFlags),