Add shadowrrect geometry processor

GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=4233

Change-Id: I637099709cfe30f7d3c1883e23840a47a7a25c10
Reviewed-on: https://skia-review.googlesource.com/4233
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index fca4939..a705ed6 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -264,6 +264,8 @@
   "$_src/gpu/batches/GrRectBatchFactory.cpp",
   "$_src/gpu/batches/GrRegionBatch.cpp",
   "$_src/gpu/batches/GrRegionBatch.h",
+  "$_src/gpu/batches/GrShadowRRectBatch.cpp",
+  "$_src/gpu/batches/GrShadowRRectBatch.h",
   "$_src/gpu/batches/GrStencilAndCoverPathRenderer.cpp",
   "$_src/gpu/batches/GrStencilAndCoverPathRenderer.h",
   "$_src/gpu/batches/GrStencilPathBatch.h",
@@ -305,6 +307,8 @@
   "$_src/gpu/effects/GrPorterDuffXferProcessor.cpp",
   "$_src/gpu/effects/GrRRectEffect.cpp",
   "$_src/gpu/effects/GrRRectEffect.h",
+  "$_src/gpu/effects/GrShadowGeoProc.cpp",
+  "$_src/gpu/effects/GrShadowGeoProc.h",
   "$_src/gpu/effects/GrSimpleTextureEffect.cpp",
   "$_src/gpu/effects/GrSimpleTextureEffect.h",
   "$_src/gpu/effects/GrSingleTextureEffect.cpp",
diff --git a/include/gpu/GrRenderTargetContext.h b/include/gpu/GrRenderTargetContext.h
index d1896db..6ed321c 100644
--- a/include/gpu/GrRenderTargetContext.h
+++ b/include/gpu/GrRenderTargetContext.h
@@ -144,6 +144,23 @@
                    const GrStyle& style);
 
     /**
+     *  Draw a roundrect using a paint and a shadow shader. This is separate from drawRRect
+     *  because it uses different underlying geometry and GeometryProcessor
+     *
+     *  @param paint        describes how to color pixels.
+     *  @param viewMatrix   transformation matrix
+     *  @param rrect        the roundrect to draw
+     *  @param blurRadius   amount of shadow blur to apply (in device space)
+     *  @param style        style to apply to the rrect. Currently path effects are not allowed.
+     */
+    void drawShadowRRect(const GrClip&,
+                         const GrPaint&,
+                         const SkMatrix& viewMatrix,
+                         const SkRRect& rrect,
+                         SkScalar blurRadius,
+                         const GrStyle& style);
+
+    /**
      *  Shortcut for drawing an SkPath consisting of nested rrects using a paint.
      *  Does not support stroking. The result is undefined if outer does not contain
      *  inner.
diff --git a/samplecode/SampleAndroidShadows.cpp b/samplecode/SampleAndroidShadows.cpp
index 0b9358a..87fa051 100644
--- a/samplecode/SampleAndroidShadows.cpp
+++ b/samplecode/SampleAndroidShadows.cpp
@@ -434,7 +434,7 @@
         canvas->translate(200, 90);
         lightPos.fX += 200;
         lightPos.fY += 90;
-        this->drawShadowedPath(canvas, fRectPath, 2, paint, kAmbientAlpha, 
+        this->drawShadowedPath(canvas, fRectPath, 2, paint, kAmbientAlpha,
                                lightPos, kLightWidth, kSpotAlpha);
 
         paint.setColor(SK_ColorRED);
diff --git a/src/effects/SkShadowMaskFilter.cpp b/src/effects/SkShadowMaskFilter.cpp
index 3894d74..958ab17 100755
--- a/src/effects/SkShadowMaskFilter.cpp
+++ b/src/effects/SkShadowMaskFilter.cpp
@@ -154,125 +154,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-class GrShadowEdgeEffect : public GrFragmentProcessor {
-public:
-    enum Type {
-        kGaussian_Type,
-        kSmoothStep_Type,
-        kGeometric_Type
-    };
-
-    static sk_sp<GrFragmentProcessor> Make(Type type);
-
-    ~GrShadowEdgeEffect() override {}
-    const char* name() const override { return "GrShadowEdge"; }
-
-private:
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-
-    GrShadowEdgeEffect(Type type);
-
-    void onGetGLSLProcessorKey(const GrGLSLCaps& caps,
-                               GrProcessorKeyBuilder* b) const override;
-
-    bool onIsEqual(const GrFragmentProcessor& other) const override;
-
-    void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
-
-    Type fType;
-
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
-
-    typedef GrFragmentProcessor INHERITED;
-};
-
-sk_sp<GrFragmentProcessor> GrShadowEdgeEffect::Make(Type type) {
-    return sk_sp<GrFragmentProcessor>(new GrShadowEdgeEffect(type));
-}
-
-void GrShadowEdgeEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const {
-    inout->mulByUnknownSingleComponent();
-}
-
-GrShadowEdgeEffect::GrShadowEdgeEffect(Type type)
-    : fType(type) {
-    this->initClassID<GrShadowEdgeEffect>();
-    // TODO: remove this when we switch to a non-distance based approach
-    // enable output of distance information for shape
-    fUsesDistanceVectorField = true;
-}
-
-bool GrShadowEdgeEffect::onIsEqual(const GrFragmentProcessor& other) const {
-    const GrShadowEdgeEffect& see = other.cast<GrShadowEdgeEffect>();
-    return fType == see.fType;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrShadowEdgeEffect);
-
-sk_sp<GrFragmentProcessor> GrShadowEdgeEffect::TestCreate(GrProcessorTestData* d) {
-    int t = d->fRandom->nextRangeU(0, 2);
-    GrShadowEdgeEffect::Type type = kGaussian_Type;
-    if (1 == t) {
-        type = kSmoothStep_Type;
-    } else if (2 == t) {
-        type = kGeometric_Type;
-    }
-    return GrShadowEdgeEffect::Make(type);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-class GrGLShadowEdgeEffect : public GrGLSLFragmentProcessor {
-public:
-    void emitCode(EmitArgs&) override;
-
-protected:
-    void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
-
-private:
-    typedef GrGLSLFragmentProcessor INHERITED;
-};
-
-void GrGLShadowEdgeEffect::emitCode(EmitArgs& args) {
-
-    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-
-    // TODO: handle smoothstep and geometric cases
-    if (!args.fGpImplementsDistanceVector) {
-        fragBuilder->codeAppendf("// GP does not implement fsDistanceVector - "
-                                 " returning semi-transparent black in GrGLShadowEdgeEffect\n");
-        fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
-        fragBuilder->codeAppendf("%s = vec4(0.0, 0.0, 0.0, color.r);", args.fOutputColor);
-    } else {
-        fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
-        fragBuilder->codeAppend("float radius = color.r*256.0*64.0 + color.g*64.0;");
-        fragBuilder->codeAppend("float pad = color.b*64.0;");
-
-        fragBuilder->codeAppendf("float factor = 1.0 - clamp((%s.z - pad)/radius, 0.0, 1.0);",
-                                 fragBuilder->distanceVectorName());
-        fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;");
-        fragBuilder->codeAppendf("%s = factor*vec4(0.0, 0.0, 0.0, color.a);",
-                                 args.fOutputColor);
-    }
-}
-
-void GrGLShadowEdgeEffect::onSetData(const GrGLSLProgramDataManager& pdman,
-                                    const GrProcessor& proc) {
-}
-
-void GrShadowEdgeEffect::onGetGLSLProcessorKey(const GrGLSLCaps& caps,
-                                              GrProcessorKeyBuilder* b) const {
-    GrGLShadowEdgeEffect::GenKey(*this, caps, b);
-}
-
-GrGLSLFragmentProcessor* GrShadowEdgeEffect::onCreateGLSLInstance() const {
-    return new GrGLShadowEdgeEffect;
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
 bool SkShadowMaskFilterImpl::canFilterMaskGPU(const SkRRect& devRRect,
                                               const SkIRect& clipBounds,
                                               const SkMatrix& ctm,
@@ -309,11 +190,8 @@
     return false;
 }
 
-#define MAX_BLUR_RADIUS 16383.75f
-#define MAX_PAD         64
-
 bool SkShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
-                                                      GrRenderTargetContext* drawContext,
+                                                      GrRenderTargetContext* renderTargetContext,
                                                       GrPaint* grp,
                                                       const GrClip& clip,
                                                       const SkMatrix& viewMatrix,
@@ -355,10 +233,6 @@
         static const float kGeomFactor = 64.0f;
 
         SkScalar srcSpaceAmbientRadius = fOccluderHeight * kHeightFactor * kGeomFactor;
-        // the device-space radius sent to the blur shader must fit in 14.2 fixed point
-        if (srcSpaceAmbientRadius*scaleFactor > MAX_BLUR_RADIUS) {
-            srcSpaceAmbientRadius = MAX_BLUR_RADIUS / scaleFactor;
-        }
         const float umbraAlpha = 1.0f / (1.0f + SkTMax(fOccluderHeight * kHeightFactor, 0.0f));
         const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
 
@@ -375,44 +249,25 @@
              rrect.outset(ambientPathOutset, ambientPathOutset, &ambientRRect);
         }
 
-        // we outset the stroke a little to cover up AA on the interior edge
-        float pad = 0.5f;
-        // handle scale of radius and pad due to CTM
-        pad *= scaleFactor;
         const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
-        SkASSERT(devSpaceAmbientRadius <= MAX_BLUR_RADIUS);
-        SkASSERT(pad < MAX_PAD);
-        // convert devSpaceAmbientRadius to 14.2 fixed point and place in the R & G components
-        // convert pad to 6.2 fixed point and place in the B component
-        // TODO: replace this with separate vertex attributes passed by a new GeoProc.
-        // For now we can't easily pass attributes to the fragment shader, so we're overriding
-        // the paint color.
-        uint16_t iDevSpaceAmbientRadius = (uint16_t)(4.0f * devSpaceAmbientRadius);
 
         GrPaint newPaint(*grp);
         newPaint.setAntiAlias(true);
+        GrColor4f color = newPaint.getColor4f();
+        color.fRGBA[3] *= fAmbientAlpha;
+        newPaint.setColor4f(color);
         SkStrokeRec ambientStrokeRec(SkStrokeRec::kHairline_InitStyle);
-        ambientStrokeRec.setStrokeStyle(srcSpaceAmbientRadius + 2.0f * pad, false);
-        newPaint.setColor4f(GrColor4f((iDevSpaceAmbientRadius >> 8)/255.f,
-                                      (iDevSpaceAmbientRadius & 0xff)/255.f,
-                                      4.0f * pad/255.f,
-                                      fAmbientAlpha));
+        ambientStrokeRec.setStrokeStyle(srcSpaceAmbientRadius, false);
 
-        sk_sp<GrFragmentProcessor> fp(GrShadowEdgeEffect::Make(GrShadowEdgeEffect::kGaussian_Type));
-        // TODO: switch to coverage FP
-        newPaint.addColorFragmentProcessor(std::move(fp));
-        drawContext->drawRRect(clip, newPaint, viewMatrix, ambientRRect,
-                               GrStyle(ambientStrokeRec, nullptr));
+        renderTargetContext->drawShadowRRect(clip, newPaint, viewMatrix, ambientRRect,
+                                             devSpaceAmbientRadius,
+                                             GrStyle(ambientStrokeRec, nullptr));
     }
 
     if (fSpotAlpha > 0.0f) {
         float zRatio = SkTPin(fOccluderHeight / (fLightPos.fZ - fOccluderHeight), 0.0f, 0.95f);
 
         SkScalar srcSpaceSpotRadius = 2.0f * fLightRadius * zRatio;
-        // the device-space radius sent to the blur shader must fit in 14.2 fixed point
-        if (srcSpaceSpotRadius > MAX_BLUR_RADIUS) {
-            srcSpaceSpotRadius = MAX_BLUR_RADIUS;
-        }
 
         SkRRect spotRRect;
         if (isRect) {
@@ -442,13 +297,11 @@
         // We want to extend the stroked area in so that it meets up with the caster
         // geometry. The stroked geometry will, by definition already be inset half the
         // stroke width but we also have to account for the scaling.
-        // We also add 1/2 to cover up AA on the interior edge.
         SkScalar scaleOffset = (scale - 1.0f) * SkTMax(SkTMax(SkTAbs(rrect.rect().fLeft),
                                                               SkTAbs(rrect.rect().fRight)),
                                                        SkTMax(SkTAbs(rrect.rect().fTop),
                                                               SkTAbs(rrect.rect().fBottom)));
-        SkScalar insetAmount = spotOffset.length() - (0.5f * srcSpaceSpotRadius) +
-                               scaleOffset + 0.5f;
+        SkScalar insetAmount = spotOffset.length() - (0.5f * srcSpaceSpotRadius) + scaleOffset;
 
         // Compute area
         SkScalar strokeWidth = srcSpaceSpotRadius + insetAmount;
@@ -459,6 +312,10 @@
 
         GrPaint newPaint(*grp);
         newPaint.setAntiAlias(true);
+        GrColor4f color = newPaint.getColor4f();
+        color.fRGBA[3] *= fSpotAlpha;
+        newPaint.setColor4f(color);
+        
         SkStrokeRec spotStrokeRec(SkStrokeRec::kFill_InitStyle);
         // If the area of the stroked geometry is larger than the fill geometry,
         // or if the caster is transparent, just fill it.
@@ -478,29 +335,12 @@
 
         // handle scale of radius and pad due to CTM
         const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
-        SkASSERT(devSpaceSpotRadius <= MAX_BLUR_RADIUS);
 
-        const SkScalar devSpaceSpotPad = 0;
-        SkASSERT(devSpaceSpotPad < MAX_PAD);
-
-        // convert devSpaceSpotRadius to 14.2 fixed point and place in the R & G
-        // components convert devSpaceSpotPad to 6.2 fixed point and place in the B component
-        // TODO: replace this with separate vertex attributes passed by a new GeoProc.
-        // For now we can't easily pass attributes to the fragment shader, so we're overriding
-        // the paint color.
-        uint16_t iDevSpaceSpotRadius = (uint16_t)(4.0f * devSpaceSpotRadius);
-        newPaint.setColor4f(GrColor4f((iDevSpaceSpotRadius >> 8) / 255.f,
-                                      (iDevSpaceSpotRadius & 0xff) / 255.f,
-                                      devSpaceSpotPad,
-                                      fSpotAlpha));
         spotShadowRRect.offset(spotOffset.fX, spotOffset.fY);
 
-        sk_sp<GrFragmentProcessor> fp(GrShadowEdgeEffect::Make(GrShadowEdgeEffect::kGaussian_Type));
-        // TODO: switch to coverage FP
-        newPaint.addColorFragmentProcessor(std::move(fp));
-
-        drawContext->drawRRect(clip, newPaint, viewMatrix, spotShadowRRect,
-                               GrStyle(spotStrokeRec, nullptr));
+        renderTargetContext->drawShadowRRect(clip, newPaint, viewMatrix, spotShadowRRect,
+                                             devSpaceSpotRadius,
+                                             GrStyle(spotStrokeRec, nullptr));
     }
 
     return true;
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index e311c3d..6238fa8 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -27,6 +27,7 @@
 #include "batches/GrRectBatchFactory.h"
 #include "batches/GrNinePatch.h" // TODO Factory
 #include "batches/GrRegionBatch.h"
+#include "batches/GrShadowRRectBatch.h"
 
 #include "effects/GrRRectEffect.h"
 
@@ -913,6 +914,66 @@
     this->internalDrawPath(*clip, paint, viewMatrix, path, style);
 }
 
+///////////////////////////////////////////////////////////////////////////////
+
+void GrRenderTargetContext::drawShadowRRect(const GrClip& clip,
+                                            const GrPaint& paint,
+                                            const SkMatrix& viewMatrix,
+                                            const SkRRect& rrect,
+                                            SkScalar blurRadius,
+                                            const GrStyle& style) {
+    ASSERT_SINGLE_OWNER
+    RETURN_IF_ABANDONED
+    SkDEBUGCODE(this->validate();)
+    GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrRenderTargetContext::drawShadowRRect");
+    if (rrect.isEmpty()) {
+        return;
+    }
+
+    SkASSERT(!style.pathEffect()); // this should've been devolved to a path in SkGpuDevice
+
+    AutoCheckFlush acf(fDrawingManager);
+    const SkStrokeRec stroke = style.strokeRec();
+    bool useHWAA;
+
+    // TODO: add instancing support
+    //if (GrCaps::InstancedSupport::kNone != fContext->caps()->instancedSupport() &&
+    //    stroke.isFillStyle()) {
+    //    InstancedRendering* ir = this->getOpList()->instancedRendering();
+    //    SkAutoTUnref<GrDrawBatch> batch(ir->recordRRect(rrect, viewMatrix, paint.getColor(),
+    //                                                    paint.isAntiAlias(), fInstancedPipelineInfo,
+    //                                                    &useHWAA));
+    //    if (batch) {
+    //        GrPipelineBuilder pipelineBuilder(paint, useHWAA);
+    //        this->getOpList()->drawBatch(pipelineBuilder, this, *clip, batch);
+    //        return;
+    //    }
+    //}
+
+    if (should_apply_coverage_aa(paint, fRenderTargetProxy.get(), &useHWAA)) {
+        GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
+        sk_sp<GrDrawBatch> batch(CreateShadowRRectBatch(
+                                        paint.getColor(),
+                                        viewMatrix,
+                                        rrect,
+                                        blurRadius,
+                                        stroke,
+                                        shaderCaps));
+        if (batch) {
+            GrPipelineBuilder pipelineBuilder(paint, useHWAA);
+            this->getOpList()->drawBatch(pipelineBuilder, this, clip, batch.get());
+            return;
+        }
+    }
+
+    SkPath path;
+    path.setIsVolatile(true);
+    path.addRRect(rrect);
+    this->internalDrawPath(clip, paint, viewMatrix, path, style);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
 bool GrRenderTargetContext::drawFilledDRRect(const GrClip& clip,
                                              const GrPaint& paintIn,
                                              const SkMatrix& viewMatrix,
diff --git a/src/gpu/batches/GrShadowRRectBatch.cpp b/src/gpu/batches/GrShadowRRectBatch.cpp
new file mode 100755
index 0000000..08f7e86
--- /dev/null
+++ b/src/gpu/batches/GrShadowRRectBatch.cpp
@@ -0,0 +1,964 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrShadowRRectBatch.h"
+
+#include "GrBatchFlushState.h"
+#include "GrBatchTest.h"
+#include "GrResourceProvider.h"
+#include "GrStyle.h"
+
+#include "effects/GrShadowGeoProc.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+// We have two possible cases for geometry for a circle:
+
+// In the case of a normal fill, we draw geometry for the circle as an octagon.
+static const uint16_t gFillCircleIndices[] = {
+    // enter the octagon
+    0, 1, 8, 1, 2, 8,
+    2, 3, 8, 3, 4, 8,
+    4, 5, 8, 5, 6, 8,
+    6, 7, 8, 7, 0, 8,
+};
+
+// For stroked circles, we use two nested octagons.
+static const uint16_t gStrokeCircleIndices[] = {
+    // enter the octagon
+    0, 1, 9, 0, 9, 8,
+    1, 2, 10, 1, 10, 9,
+    2, 3, 11, 2, 11, 10,
+    3, 4, 12, 3, 12, 11,
+    4, 5, 13, 4, 13, 12,
+    5, 6, 14, 5, 14, 13,
+    6, 7, 15, 6, 15, 14,
+    7, 0, 8, 7, 8, 15,
+};
+
+static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
+static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
+static const int kVertsPerStrokeCircle = 16;
+static const int kVertsPerFillCircle = 9;
+
+static int circle_type_to_vert_count(bool stroked) {
+    return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
+}
+
+static int circle_type_to_index_count(bool stroked) {
+    return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
+}
+
+static const uint16_t* circle_type_to_indices(bool stroked) {
+    return stroked ? gStrokeCircleIndices : gFillCircleIndices;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class ShadowCircleBatch : public GrVertexBatch {
+public:
+    DEFINE_BATCH_CLASS_ID
+
+    static GrDrawBatch* Create(GrColor color, const SkMatrix& viewMatrix, SkPoint center,
+                               SkScalar radius, SkScalar blurRadius, const GrStyle& style) {
+        SkASSERT(viewMatrix.isSimilarity());
+        const SkStrokeRec& stroke = style.strokeRec();
+        if (style.hasPathEffect()) {
+            return nullptr;
+        }
+        SkStrokeRec::Style recStyle = stroke.getStyle();
+
+        viewMatrix.mapPoints(&center, 1);
+        radius = viewMatrix.mapRadius(radius);
+        SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth());
+
+        bool isStrokeOnly = SkStrokeRec::kStroke_Style == recStyle ||
+                            SkStrokeRec::kHairline_Style == recStyle;
+        bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle;
+
+        SkScalar innerRadius = -SK_ScalarHalf;
+        SkScalar outerRadius = radius;
+        SkScalar halfWidth = 0;
+        if (hasStroke) {
+            if (SkScalarNearlyZero(strokeWidth)) {
+                halfWidth = SK_ScalarHalf;
+            } else {
+                halfWidth = SkScalarHalf(strokeWidth);
+            }
+
+            outerRadius += halfWidth;
+            if (isStrokeOnly) {
+                innerRadius = radius - halfWidth;
+            }
+        }
+
+        // TODO: still needed?
+        // The radii are outset for two reasons. First, it allows the shader to simply perform
+        // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
+        // Second, the outer radius is used to compute the verts of the bounding box that is
+        // rendered and the outset ensures the box will cover all partially covered by the circle.
+        outerRadius += SK_ScalarHalf;
+        innerRadius -= SK_ScalarHalf;
+        bool stroked = isStrokeOnly && innerRadius > 0.0f;
+        ShadowCircleBatch* batch = new ShadowCircleBatch();
+        batch->fViewMatrixIfUsingLocalCoords = viewMatrix;
+
+        SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
+                                            center.fX + outerRadius, center.fY + outerRadius);
+
+        batch->fGeoData.emplace_back(Geometry{
+            color,
+            outerRadius,
+            innerRadius,
+            blurRadius,
+            devBounds,
+            stroked
+        });
+
+        // Use the original radius and stroke radius for the bounds so that it does not include the
+        // AA bloat.
+        radius += halfWidth;
+        batch->setBounds({ center.fX - radius, center.fY - radius,
+                         center.fX + radius, center.fY + radius },
+                         HasAABloat::kNo, IsZeroArea::kNo);
+        batch->fVertCount = circle_type_to_vert_count(stroked);
+        batch->fIndexCount = circle_type_to_index_count(stroked);
+        return batch;
+    }
+
+    const char* name() const override { return "ShadowCircleBatch"; }
+
+    SkString dumpInfo() const override {
+        SkString string;
+        for (int i = 0; i < fGeoData.count(); ++i) {
+            string.appendf("Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
+                           "OuterRad: %.2f, InnerRad: %.2f, BlurRad: %.2f\n",
+                           fGeoData[i].fColor,
+                           fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.fTop,
+                           fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds.fBottom,
+                           fGeoData[i].fOuterRadius, fGeoData[i].fInnerRadius,
+                           fGeoData[i].fBlurRadius);
+        }
+        string.append(INHERITED::dumpInfo());
+        return string;
+    }
+
+    void computePipelineOptimizations(GrInitInvariantOutput* color,
+                                      GrInitInvariantOutput* coverage,
+                                      GrBatchToXPOverrides* overrides) const override {
+        // When this is called on a batch, there is only one geometry bundle
+        color->setKnownFourComponents(fGeoData[0].fColor);
+        coverage->setUnknownSingleComponent();
+    }
+
+private:
+    ShadowCircleBatch() : INHERITED(ClassID()) {}
+    void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
+        // Handle any overrides that affect our GP.
+        overrides.getOverrideColorIfSet(&fGeoData[0].fColor);
+        if (!overrides.readsLocalCoords()) {
+            fViewMatrixIfUsingLocalCoords.reset();
+        }
+    }
+
+    void onPrepareDraws(Target* target) const override {
+        SkMatrix localMatrix;
+        if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
+            return;
+        }
+
+        // Setup geometry processor
+        sk_sp<GrGeometryProcessor> gp(GrRRectShadowGeoProc::Make(localMatrix));
+
+        struct CircleVertex {
+            SkPoint  fPos;
+            GrColor  fColor;
+            SkPoint  fOffset;
+            SkScalar fOuterRadius;
+            SkScalar fBlurRadius;
+        };
+
+        int instanceCount = fGeoData.count();
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == sizeof(CircleVertex));
+
+        const GrBuffer* vertexBuffer;
+        int firstVertex;
+        char* vertices = (char*)target->makeVertexSpace(vertexStride, fVertCount,
+                                                        &vertexBuffer, &firstVertex);
+        if (!vertices) {
+            SkDebugf("Could not allocate vertices\n");
+            return;
+        }
+
+        const GrBuffer* indexBuffer = nullptr;
+        int firstIndex = 0;
+        uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
+        if (!indices) {
+            SkDebugf("Could not allocate indices\n");
+            return;
+        }
+
+        int currStartVertex = 0;
+        for (int i = 0; i < instanceCount; i++) {
+            const Geometry& geom = fGeoData[i];
+
+            GrColor color = geom.fColor;
+            SkScalar outerRadius = geom.fOuterRadius;
+            SkScalar innerRadius = geom.fInnerRadius;
+            SkScalar blurRadius = geom.fBlurRadius;
+
+            const SkRect& bounds = geom.fDevBounds;
+            CircleVertex* ov0 = reinterpret_cast<CircleVertex*>(vertices + 0 * vertexStride);
+            CircleVertex* ov1 = reinterpret_cast<CircleVertex*>(vertices + 1 * vertexStride);
+            CircleVertex* ov2 = reinterpret_cast<CircleVertex*>(vertices + 2 * vertexStride);
+            CircleVertex* ov3 = reinterpret_cast<CircleVertex*>(vertices + 3 * vertexStride);
+            CircleVertex* ov4 = reinterpret_cast<CircleVertex*>(vertices + 4 * vertexStride);
+            CircleVertex* ov5 = reinterpret_cast<CircleVertex*>(vertices + 5 * vertexStride);
+            CircleVertex* ov6 = reinterpret_cast<CircleVertex*>(vertices + 6 * vertexStride);
+            CircleVertex* ov7 = reinterpret_cast<CircleVertex*>(vertices + 7 * vertexStride);
+
+            // The inner radius in the vertex data must be specified in normalized space.
+            innerRadius = innerRadius / outerRadius;
+            
+            SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
+            SkScalar halfWidth = 0.5f*bounds.width();
+            SkScalar octOffset = 0.41421356237f;  // sqrt(2) - 1
+
+            ov0->fPos = center + SkPoint::Make(-octOffset*halfWidth, -halfWidth);
+            ov0->fColor = color;
+            ov0->fOffset = SkPoint::Make(-octOffset, -1);
+            ov0->fOuterRadius = outerRadius;
+            ov0->fBlurRadius = blurRadius;
+
+            ov1->fPos = center + SkPoint::Make(octOffset*halfWidth, -halfWidth);
+            ov1->fColor = color;
+            ov1->fOffset = SkPoint::Make(octOffset, -1);
+            ov1->fOuterRadius = outerRadius;
+            ov1->fBlurRadius = blurRadius;
+
+            ov2->fPos = center + SkPoint::Make(halfWidth, -octOffset*halfWidth);
+            ov2->fColor = color;
+            ov2->fOffset = SkPoint::Make(1, -octOffset);
+            ov2->fOuterRadius = outerRadius;
+            ov2->fBlurRadius = blurRadius;
+
+            ov3->fPos = center + SkPoint::Make(halfWidth, octOffset*halfWidth);
+            ov3->fColor = color;
+            ov3->fOffset = SkPoint::Make(1, octOffset);
+            ov3->fOuterRadius = outerRadius;
+            ov3->fBlurRadius = blurRadius;
+
+            ov4->fPos = center + SkPoint::Make(octOffset*halfWidth, halfWidth);
+            ov4->fColor = color;
+            ov4->fOffset = SkPoint::Make(octOffset, 1);
+            ov4->fOuterRadius = outerRadius;
+            ov4->fBlurRadius = blurRadius;
+
+            ov5->fPos = center + SkPoint::Make(-octOffset*halfWidth, halfWidth);
+            ov5->fColor = color;
+            ov5->fOffset = SkPoint::Make(-octOffset, 1);
+            ov5->fOuterRadius = outerRadius;
+            ov5->fBlurRadius = blurRadius;
+
+            ov6->fPos = center + SkPoint::Make(-halfWidth, octOffset*halfWidth);
+            ov6->fColor = color;
+            ov6->fOffset = SkPoint::Make(-1, octOffset);
+            ov6->fOuterRadius = outerRadius;
+            ov6->fBlurRadius = blurRadius;
+
+            ov7->fPos = center + SkPoint::Make(-halfWidth, -octOffset*halfWidth);
+            ov7->fColor = color;
+            ov7->fOffset = SkPoint::Make(-1, -octOffset);
+            ov7->fOuterRadius = outerRadius;
+            ov7->fBlurRadius = blurRadius;
+
+            if (geom.fStroked) {
+                // compute the inner ring
+                CircleVertex* iv0 = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride);
+                CircleVertex* iv1 = reinterpret_cast<CircleVertex*>(vertices + 9 * vertexStride);
+                CircleVertex* iv2 = reinterpret_cast<CircleVertex*>(vertices + 10 * vertexStride);
+                CircleVertex* iv3 = reinterpret_cast<CircleVertex*>(vertices + 11 * vertexStride);
+                CircleVertex* iv4 = reinterpret_cast<CircleVertex*>(vertices + 12 * vertexStride);
+                CircleVertex* iv5 = reinterpret_cast<CircleVertex*>(vertices + 13 * vertexStride);
+                CircleVertex* iv6 = reinterpret_cast<CircleVertex*>(vertices + 14 * vertexStride);
+                CircleVertex* iv7 = reinterpret_cast<CircleVertex*>(vertices + 15 * vertexStride);
+
+                // cosine and sine of pi/8
+                SkScalar c = 0.923579533f;
+                SkScalar s = 0.382683432f;
+                SkScalar r = geom.fInnerRadius;
+
+                iv0->fPos = center + SkPoint::Make(-s*r, -c*r);
+                iv0->fColor = color;
+                iv0->fOffset = SkPoint::Make(-s*innerRadius, -c*innerRadius);
+                iv0->fOuterRadius = outerRadius;
+                iv0->fBlurRadius = blurRadius;
+
+                iv1->fPos = center + SkPoint::Make(s*r, -c*r);
+                iv1->fColor = color;
+                iv1->fOffset = SkPoint::Make(s*innerRadius, -c*innerRadius);
+                iv1->fOuterRadius = outerRadius;
+                iv1->fBlurRadius = blurRadius;
+
+                iv2->fPos = center + SkPoint::Make(c*r, -s*r);
+                iv2->fColor = color;
+                iv2->fOffset = SkPoint::Make(c*innerRadius, -s*innerRadius);
+                iv2->fOuterRadius = outerRadius;
+                iv2->fBlurRadius = blurRadius;
+
+                iv3->fPos = center + SkPoint::Make(c*r, s*r);
+                iv3->fColor = color;
+                iv3->fOffset = SkPoint::Make(c*innerRadius, s*innerRadius);
+                iv3->fOuterRadius = outerRadius;
+                iv3->fBlurRadius = blurRadius;
+
+                iv4->fPos = center + SkPoint::Make(s*r, c*r);
+                iv4->fColor = color;
+                iv4->fOffset = SkPoint::Make(s*innerRadius, c*innerRadius);
+                iv4->fOuterRadius = outerRadius;
+                iv4->fBlurRadius = blurRadius;
+
+                iv5->fPos = center + SkPoint::Make(-s*r, c*r);
+                iv5->fColor = color;
+                iv5->fOffset = SkPoint::Make(-s*innerRadius, c*innerRadius);
+                iv5->fOuterRadius = outerRadius;
+                iv5->fBlurRadius = blurRadius;
+
+                iv6->fPos = center + SkPoint::Make(-c*r, s*r);
+                iv6->fColor = color;
+                iv6->fOffset = SkPoint::Make(-c*innerRadius, s*innerRadius);
+                iv6->fOuterRadius = outerRadius;
+                iv6->fBlurRadius = blurRadius;
+
+                iv7->fPos = center + SkPoint::Make(-c*r, -s*r);
+                iv7->fColor = color;
+                iv7->fOffset = SkPoint::Make(-c*innerRadius, -s*innerRadius);
+                iv7->fOuterRadius = outerRadius;
+                iv7->fBlurRadius = blurRadius;
+            } else {
+                // filled
+                CircleVertex* iv = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride);
+                iv->fPos = center;
+                iv->fColor = color;
+                iv->fOffset = SkPoint::Make(0, 0);
+                iv->fOuterRadius = outerRadius;
+                iv->fBlurRadius = blurRadius;
+            }
+
+            const uint16_t* primIndices = circle_type_to_indices(geom.fStroked);
+            const int primIndexCount = circle_type_to_index_count(geom.fStroked);
+            for (int i = 0; i < primIndexCount; ++i) {
+                *indices++ = primIndices[i] + currStartVertex;
+            }
+
+            currStartVertex += circle_type_to_vert_count(geom.fStroked);
+            vertices += circle_type_to_vert_count(geom.fStroked)*vertexStride;
+        }
+
+        GrMesh mesh;
+        mesh.initIndexed(kTriangles_GrPrimitiveType, vertexBuffer, indexBuffer, firstVertex,
+                         firstIndex, fVertCount, fIndexCount);
+        target->draw(gp.get(), mesh);
+    }
+
+    bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
+        ShadowCircleBatch* that = t->cast<ShadowCircleBatch>();
+        if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
+                                    that->bounds(), caps)) {
+            return false;
+        }
+
+        if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
+            return false;
+        }
+
+        fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
+        this->joinBounds(*that);
+        fVertCount += that->fVertCount;
+        fIndexCount += that->fIndexCount;
+        return true;
+    }
+
+    struct Geometry {
+        GrColor  fColor;
+        SkScalar fOuterRadius;
+        SkScalar fInnerRadius;
+        SkScalar fBlurRadius;
+        SkRect   fDevBounds;
+        bool     fStroked;
+    };
+
+    SkSTArray<1, Geometry, true> fGeoData;
+    SkMatrix                     fViewMatrixIfUsingLocalCoords;
+    int                          fVertCount;
+    int                          fIndexCount;
+
+    typedef GrVertexBatch INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// We have two possible cases for geometry for a shadow roundrect.
+//
+// In the case of a normal stroke, we draw the roundrect as a 9-patch without the center quad.
+//    ____________
+//   |_|________|_|
+//   | |        | |
+//   | |        | |
+//   | |        | |
+//   |_|________|_|
+//   |_|________|_|
+//
+// In the case where the stroke width is greater than twice the corner radius (overstroke),
+// we add additional geometry to mark out the rectangle in the center. The shared vertices
+// are duplicated so we can set a different outer radius for the fill calculation.
+//    ____________
+//   |_|________|_|
+//   | |\ ____ /| |
+//   | | |    | | |
+//   | | |____| | |
+//   |_|/______\|_|
+//   |_|________|_|
+//
+// For filled rrects we reuse the overstroke geometry but make the inner rect degenerate
+// (either a point or a horizontal or vertical line).
+
+static const uint16_t gOverstrokeRRectIndices[] = {
+    // corners
+    0, 1, 5, 0, 5, 4,
+    2, 3, 7, 2, 7, 6,
+    8, 9, 13, 8, 13, 12,
+    10, 11, 15, 10, 15, 14,
+
+    // edges
+    1, 2, 6, 1, 6, 5,
+    4, 5, 9, 4, 9, 8,
+    6, 7, 11, 6, 11, 10,
+    9, 10, 14, 9, 14, 13,
+
+    // overstroke quads
+    // we place this at the end so that we can skip these indices when rendering as stroked
+    16, 17, 19, 16, 19, 18,
+    19, 17, 23, 19, 23, 21,
+    21, 23, 22, 21, 22, 20,
+    22, 16, 18, 22, 18, 20,
+};
+// standard stroke indices start at the same place, but will skip the overstroke "ring"
+static const uint16_t* gStrokeRRectIndices = gOverstrokeRRectIndices;
+
+// overstroke count
+static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices);
+// simple stroke count skips overstroke indices
+static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6;
+static const int kVertsPerStrokeRRect = 16;
+static const int kVertsPerOverstrokeRRect = 24;
+
+enum RRectType {
+    kFill_RRectType,
+    kStroke_RRectType,
+    kOverstroke_RRectType,
+};
+
+static int rrect_type_to_vert_count(RRectType type) {
+    static const int kTypeToVertCount[] = {
+        kVertsPerOverstrokeRRect,
+        kVertsPerStrokeRRect,
+        kVertsPerOverstrokeRRect,
+    };
+
+    return kTypeToVertCount[type];
+}
+
+static int rrect_type_to_index_count(RRectType type) {
+    static const int kTypeToIndexCount[] = {
+        kIndicesPerOverstrokeRRect,
+        kIndicesPerStrokeRRect,
+        kIndicesPerOverstrokeRRect,
+    };
+
+    return kTypeToIndexCount[type];
+}
+
+static const uint16_t* rrect_type_to_indices(RRectType type) {
+    static const uint16_t* kTypeToIndices[] = {
+        gOverstrokeRRectIndices,
+        gStrokeRRectIndices,
+        gOverstrokeRRectIndices,
+    };
+
+    return kTypeToIndices[type];
+}
+
+// For distance computations in the interior of filled rrects we:
+//
+//   add a interior degenerate (point or line) rect
+//   each vertex of that rect gets -outerRad as its radius
+//      this makes the computation of the distance to the outer edge be negative
+//      negative values are caught and then handled differently in the GP's onEmitCode
+//   each vertex is also given the normalized x & y distance from the interior rect's edge
+//      the GP takes the min of those depths +1 to get the normalized distance to the outer edge
+
+class ShadowCircularRRectBatch : public GrVertexBatch {
+public:
+    DEFINE_BATCH_CLASS_ID
+
+    // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates
+    // whether the rrect is only stroked or stroked and filled.
+    ShadowCircularRRectBatch(GrColor color, const SkMatrix& viewMatrix,
+                             const SkRect& devRect, float devRadius, float blurRadius,
+                             float devStrokeWidth, bool strokeOnly)
+        : INHERITED(ClassID())
+        , fViewMatrixIfUsingLocalCoords(viewMatrix) {
+        SkRect bounds = devRect;
+        SkASSERT(!(devStrokeWidth <= 0 && strokeOnly));
+        SkScalar innerRadius = 0.0f;
+        SkScalar outerRadius = devRadius;
+        SkScalar halfWidth = 0;
+        RRectType type = kFill_RRectType;
+        if (devStrokeWidth > 0) {
+            if (SkScalarNearlyZero(devStrokeWidth)) {
+                halfWidth = SK_ScalarHalf;
+            } else {
+                halfWidth = SkScalarHalf(devStrokeWidth);
+            }
+
+            if (strokeOnly) {
+                // Outset stroke by 1/4 pixel
+                devStrokeWidth += 0.25f;
+                // If stroke is greater than width or height, this is still a fill
+                // Otherwise we compute stroke params
+                if (devStrokeWidth <= devRect.width() &&
+                    devStrokeWidth <= devRect.height()) {
+                    innerRadius = devRadius - halfWidth;
+                    type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType;
+                }
+            }
+            outerRadius += halfWidth;
+            bounds.outset(halfWidth, halfWidth);
+        }
+
+        // TODO: still needed?
+        // The radii are outset for two reasons. First, it allows the shader to simply perform
+        // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
+        // Second, the outer radius is used to compute the verts of the bounding box that is
+        // rendered and the outset ensures the box will cover all partially covered by the rrect
+        // corners.
+        outerRadius += SK_ScalarHalf;
+        innerRadius -= SK_ScalarHalf;
+
+        this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
+
+        // Expand the rect for aa to generate correct vertices.
+        bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+
+        fGeoData.emplace_back(Geometry{color, outerRadius, innerRadius, blurRadius, bounds, type});
+        fVertCount = rrect_type_to_vert_count(type);
+        fIndexCount = rrect_type_to_index_count(type);
+    }
+
+    const char* name() const override { return "ShadowCircularRRectBatch"; }
+
+    SkString dumpInfo() const override {
+        SkString string;
+        for (int i = 0; i < fGeoData.count(); ++i) {
+            string.appendf("Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
+                           "OuterRad: %.2f, InnerRad: %.2f, BlurRad: %.2f\n",
+                           fGeoData[i].fColor,
+                           fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.fTop,
+                           fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds.fBottom,
+                           fGeoData[i].fOuterRadius, fGeoData[i].fInnerRadius,
+                           fGeoData[i].fBlurRadius);
+        }
+        string.append(INHERITED::dumpInfo());
+        return string;
+    }
+
+    void computePipelineOptimizations(GrInitInvariantOutput* color,
+                                      GrInitInvariantOutput* coverage,
+                                      GrBatchToXPOverrides* overrides) const override {
+        // When this is called on a batch, there is only one geometry bundle
+        color->setKnownFourComponents(fGeoData[0].fColor);
+        coverage->setUnknownSingleComponent();
+    }
+
+private:
+    void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
+        // Handle any overrides that affect our GP.
+        overrides.getOverrideColorIfSet(&fGeoData[0].fColor);
+        if (!overrides.readsLocalCoords()) {
+            fViewMatrixIfUsingLocalCoords.reset();
+        }
+    }
+
+    struct CircleVertex {
+        SkPoint  fPos;
+        GrColor  fColor;
+        SkPoint  fOffset;
+        SkScalar fOuterRadius;
+        SkScalar fBlurRadius;
+    };
+
+    static void FillInOverstrokeVerts(CircleVertex** verts, const SkRect& bounds,
+                                      SkScalar smInset, SkScalar bigInset, SkScalar xOffset,
+                                      SkScalar outerRadius, GrColor color, SkScalar blurRadius) {
+        SkASSERT(smInset < bigInset);
+
+        // TL
+        (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fTop + smInset);
+        (*verts)->fColor = color;
+        (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+        (*verts)->fOuterRadius = outerRadius;
+        (*verts)->fBlurRadius = blurRadius;
+        (*verts)++;
+
+        // TR
+        (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fTop + smInset);
+        (*verts)->fColor = color;
+        (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+        (*verts)->fOuterRadius = outerRadius;
+        (*verts)->fBlurRadius = blurRadius;
+        (*verts)++;
+
+        (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fTop + bigInset);
+        (*verts)->fColor = color;
+        (*verts)->fOffset = SkPoint::Make(0, 0);
+        (*verts)->fOuterRadius = outerRadius;
+        (*verts)->fBlurRadius = blurRadius;
+        (*verts)++;
+
+        (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fTop + bigInset);
+        (*verts)->fColor = color;
+        (*verts)->fOffset = SkPoint::Make(0, 0);
+        (*verts)->fOuterRadius = outerRadius;
+        (*verts)->fBlurRadius = blurRadius;
+        (*verts)++;
+
+        (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fBottom - bigInset);
+        (*verts)->fColor = color;
+        (*verts)->fOffset = SkPoint::Make(0, 0);
+        (*verts)->fOuterRadius = outerRadius;
+        (*verts)->fBlurRadius = blurRadius;
+        (*verts)++;
+
+        (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fBottom - bigInset);
+        (*verts)->fColor = color;
+        (*verts)->fOffset = SkPoint::Make(0, 0);
+        (*verts)->fOuterRadius = outerRadius;
+        (*verts)->fBlurRadius = blurRadius;
+        (*verts)++;
+
+        // BL
+        (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fBottom - smInset);
+        (*verts)->fColor = color;
+        (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+        (*verts)->fOuterRadius = outerRadius;
+        (*verts)->fBlurRadius = blurRadius;
+        (*verts)++;
+
+        // BR
+        (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fBottom - smInset);
+        (*verts)->fColor = color;
+        (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+        (*verts)->fOuterRadius = outerRadius;
+        (*verts)->fBlurRadius = blurRadius;
+        (*verts)++;
+    }
+
+    void onPrepareDraws(Target* target) const override {
+        // Invert the view matrix as a local matrix (if any other processors require coords).
+        SkMatrix localMatrix;
+        if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
+            return;
+        }
+
+        // Setup geometry processor
+        sk_sp<GrGeometryProcessor> gp(GrRRectShadowGeoProc::Make(localMatrix));
+
+        int instanceCount = fGeoData.count();
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(sizeof(CircleVertex) == vertexStride);
+
+        const GrBuffer* vertexBuffer;
+        int firstVertex;
+
+        CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(vertexStride, fVertCount,
+                                                                     &vertexBuffer, &firstVertex);
+        if (!verts) {
+            SkDebugf("Could not allocate vertices\n");
+            return;
+        }
+
+        const GrBuffer* indexBuffer = nullptr;
+        int firstIndex = 0;
+        uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
+        if (!indices) {
+            SkDebugf("Could not allocate indices\n");
+            return;
+        }
+
+        int currStartVertex = 0;
+        for (int i = 0; i < instanceCount; i++) {
+            const Geometry& args = fGeoData[i];
+
+            GrColor color = args.fColor;
+            SkScalar outerRadius = args.fOuterRadius;
+
+            const SkRect& bounds = args.fDevBounds;
+
+            SkScalar yCoords[4] = {
+                bounds.fTop,
+                bounds.fTop + outerRadius,
+                bounds.fBottom - outerRadius,
+                bounds.fBottom
+            };
+
+            SkScalar yOuterRadii[4] = { -1, 0, 0, 1 };
+            // The inner radius in the vertex data must be specified in normalized space.
+            // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius.
+            SkScalar blurRadius = args.fBlurRadius;
+            for (int i = 0; i < 4; ++i) {
+                verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
+                verts->fColor = color;
+                verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]);
+                verts->fOuterRadius = outerRadius;
+                verts->fBlurRadius = blurRadius;
+                verts++;
+
+                verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]);
+                verts->fColor = color;
+                verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+                verts->fOuterRadius = outerRadius;
+                verts->fBlurRadius = blurRadius;
+                verts++;
+
+                verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]);
+                verts->fColor = color;
+                verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+                verts->fOuterRadius = outerRadius;
+                verts->fBlurRadius = blurRadius;
+                verts++;
+
+                verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
+                verts->fColor = color;
+                verts->fOffset = SkPoint::Make(1, yOuterRadii[i]);
+                verts->fOuterRadius = outerRadius;
+                verts->fBlurRadius = blurRadius;
+                verts++;
+            }
+            // Add the additional vertices for overstroked rrects.
+            // Effectively this is an additional stroked rrect, with its
+            // outer radius = outerRadius - innerRadius, and inner radius = 0.
+            // This will give us correct AA in the center and the correct
+            // distance to the outer edge.
+            //
+            // Also, the outer offset is a constant vector pointing to the right, which
+            // guarantees that the distance value along the outer rectangle is constant.
+            if (kOverstroke_RRectType == args.fType) {
+                SkASSERT(args.fInnerRadius <= 0.0f);
+
+                SkScalar overstrokeOuterRadius = outerRadius - args.fInnerRadius;
+                // this is the normalized distance from the outer rectangle of this
+                // geometry to the outer edge
+                SkScalar maxOffset = -args.fInnerRadius / overstrokeOuterRadius;
+
+                FillInOverstrokeVerts(&verts, bounds, outerRadius, overstrokeOuterRadius,
+                                      maxOffset, overstrokeOuterRadius, color, blurRadius);
+            }
+
+            if (kFill_RRectType == args.fType) {
+                SkScalar halfMinDim = 0.5f * SkTMin(bounds.width(), bounds.height());
+
+                SkScalar xOffset = 1.0f - outerRadius / halfMinDim;
+
+                FillInOverstrokeVerts(&verts, bounds, outerRadius, halfMinDim,
+                                      xOffset, halfMinDim, color, blurRadius);
+            }
+
+            const uint16_t* primIndices = rrect_type_to_indices(args.fType);
+            const int primIndexCount = rrect_type_to_index_count(args.fType);
+            for (int i = 0; i < primIndexCount; ++i) {
+                *indices++ = primIndices[i] + currStartVertex;
+            }
+
+            currStartVertex += rrect_type_to_vert_count(args.fType);
+        }
+
+        GrMesh mesh;
+        mesh.initIndexed(kTriangles_GrPrimitiveType, vertexBuffer, indexBuffer, firstVertex,
+                         firstIndex, fVertCount, fIndexCount);
+        target->draw(gp.get(), mesh);
+    }
+
+    bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
+        ShadowCircularRRectBatch* that = t->cast<ShadowCircularRRectBatch>();
+        if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
+                                    that->bounds(), caps)) {
+            return false;
+        }
+
+        if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
+            return false;
+        }
+
+        fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
+        this->joinBounds(*that);
+        fVertCount += that->fVertCount;
+        fIndexCount += that->fIndexCount;
+        return true;
+    }
+
+    struct Geometry {
+        GrColor  fColor;
+        SkScalar fOuterRadius;
+        SkScalar fInnerRadius;
+        SkScalar fBlurRadius;
+        SkRect fDevBounds;
+        RRectType fType;
+    };
+
+    SkSTArray<1, Geometry, true> fGeoData;
+    SkMatrix                     fViewMatrixIfUsingLocalCoords;
+    int                          fVertCount;
+    int                          fIndexCount;
+
+    typedef GrVertexBatch INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static GrDrawBatch* create_shadow_circle_batch(GrColor color,
+                                               const SkMatrix& viewMatrix,
+                                               const SkRect& oval,
+                                               SkScalar blurRadius,
+                                               const SkStrokeRec& stroke,
+                                               const GrShaderCaps* shaderCaps) {
+    // we can only draw circles
+    SkScalar width = oval.width();
+    SkASSERT(SkScalarNearlyEqual(width, oval.height()) && viewMatrix.isSimilarity());
+    SkPoint center = { oval.centerX(), oval.centerY() };
+    return ShadowCircleBatch::Create(color, viewMatrix, center, width / 2.f,
+                                     blurRadius, GrStyle(stroke, nullptr));
+}
+
+static GrDrawBatch* create_shadow_rrect_batch(GrColor color,
+                                              const SkMatrix& viewMatrix,
+                                              const SkRRect& rrect,
+                                              SkScalar blurRadius,
+                                              const SkStrokeRec& stroke) {
+    SkASSERT(viewMatrix.rectStaysRect());
+    SkASSERT(rrect.isSimple());
+    SkASSERT(!rrect.isOval());
+
+    // Shadow rrect batchs only handle simple circular rrects
+    // do any matrix crunching before we reset the draw state for device coords
+    const SkRect& rrectBounds = rrect.getBounds();
+    SkRect bounds;
+    viewMatrix.mapRect(&bounds, rrectBounds);
+
+    SkVector radii = rrect.getSimpleRadii();
+    SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX +
+                                   viewMatrix[SkMatrix::kMSkewY] * radii.fY);
+    SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX +
+                                   viewMatrix[SkMatrix::kMScaleY] * radii.fY);
+    SkASSERT(SkScalarNearlyEqual(xRadius, yRadius));
+
+    SkStrokeRec::Style style = stroke.getStyle();
+
+    // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws.
+    SkVector scaledStroke = { -1, -1 };
+    SkScalar strokeWidth = stroke.getWidth();
+
+    bool isStrokeOnly = SkStrokeRec::kStroke_Style == style ||
+                        SkStrokeRec::kHairline_Style == style;
+    bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
+
+    if (hasStroke) {
+        if (SkStrokeRec::kHairline_Style == style) {
+            scaledStroke.set(1, 1);
+        } else {
+            scaledStroke.fX = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMScaleX] +
+                                                       viewMatrix[SkMatrix::kMSkewY]));
+            scaledStroke.fY = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMSkewX] +
+                                                       viewMatrix[SkMatrix::kMScaleY]));
+        }
+
+        // we don't handle anisotropic strokes
+        if (!SkScalarNearlyEqual(scaledStroke.fX, scaledStroke.fY)) {
+            return nullptr;
+        }
+    }
+
+    // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
+    // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
+    // patch will have fractional coverage. This only matters when the interior is actually filled.
+    // We could consider falling back to rect rendering here, since a tiny radius is
+    // indistinguishable from a square corner.
+    if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) {
+        return nullptr;
+    }
+
+    return new ShadowCircularRRectBatch(color, viewMatrix, bounds, xRadius,
+                                        blurRadius, scaledStroke.fX, isStrokeOnly);
+}
+
+GrDrawBatch* CreateShadowRRectBatch(GrColor color,
+                                    const SkMatrix& viewMatrix,
+                                    const SkRRect& rrect,
+                                    const SkScalar blurRadius,
+                                    const SkStrokeRec& stroke,
+                                    const GrShaderCaps* shaderCaps) {
+    if (rrect.isOval()) {
+        return create_shadow_circle_batch(color, viewMatrix, rrect.getBounds(),
+                                          blurRadius, stroke, shaderCaps);
+    }
+
+    if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) {
+        return nullptr;
+    }
+
+    return create_shadow_rrect_batch(color, viewMatrix, rrect, blurRadius, stroke);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef GR_TEST_UTILS
+
+DRAW_BATCH_TEST_DEFINE(ShadowCircleBatch) {
+    do {
+        SkScalar rotate = random->nextSScalar1() * 360.f;
+        SkScalar translateX = random->nextSScalar1() * 1000.f;
+        SkScalar translateY = random->nextSScalar1() * 1000.f;
+        SkScalar scale = random->nextSScalar1() * 100.f;
+        SkMatrix viewMatrix;
+        viewMatrix.setRotate(rotate);
+        viewMatrix.postTranslate(translateX, translateY);
+        viewMatrix.postScale(scale, scale);
+        GrColor color = GrRandomColor(random);
+        SkRect circle = GrTest::TestSquare(random);
+        SkPoint center = { circle.centerX(), circle.centerY() };
+        SkScalar radius = circle.width() / 2.f;
+        SkStrokeRec stroke = GrTest::TestStrokeRec(random);
+        SkScalar blurRadius = random->nextSScalar1() * 72.f;
+        GrDrawBatch* batch = ShadowCircleBatch::Create(color, viewMatrix, center, radius,
+                                                       blurRadius, GrStyle(stroke, nullptr));
+        if (batch) {
+            return batch;
+        }
+    } while (true);
+}
+
+DRAW_BATCH_TEST_DEFINE(ShadowRRectBatch) {
+    SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
+    GrColor color = GrRandomColor(random);
+    const SkRRect& rrect = GrTest::TestRRectSimple(random);
+    SkScalar blurRadius = random->nextSScalar1() * 72.f;
+    return create_shadow_rrect_batch(color, viewMatrix, rrect,
+                                     blurRadius, GrTest::TestStrokeRec(random));
+}
+
+#endif
diff --git a/src/gpu/batches/GrShadowRRectBatch.h b/src/gpu/batches/GrShadowRRectBatch.h
new file mode 100755
index 0000000..617af38
--- /dev/null
+++ b/src/gpu/batches/GrShadowRRectBatch.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrShadowRRectBatch_DEFINED
+#define GrShadowRRectBatch_DEFINED
+
+#include "GrColor.h"
+
+class GrDrawBatch;
+class GrShaderCaps;
+class SkMatrix;
+class SkRRect;
+class SkStrokeRec;
+
+GrDrawBatch* CreateShadowRRectBatch(GrColor,
+                                    const SkMatrix& viewMatrix,
+                                    const SkRRect& rrect,
+                                    const SkScalar blurRadius,
+                                    const SkStrokeRec& stroke,
+                                    const GrShaderCaps* shaderCaps);
+
+#endif
diff --git a/src/gpu/effects/GrShadowGeoProc.cpp b/src/gpu/effects/GrShadowGeoProc.cpp
new file mode 100755
index 0000000..8544a05
--- /dev/null
+++ b/src/gpu/effects/GrShadowGeoProc.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrShadowGeoProc.h"
+
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLGeometryProcessor.h"
+#include "glsl/GrGLSLUniformHandler.h"
+#include "glsl/GrGLSLVarying.h"
+#include "glsl/GrGLSLVertexShaderBuilder.h"
+
+class GrGLSLRRectShadowGeoProc : public GrGLSLGeometryProcessor {
+public:
+    GrGLSLRRectShadowGeoProc() {}
+
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
+        const GrRRectShadowGeoProc& rsgp = args.fGP.cast<GrRRectShadowGeoProc>();
+        GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
+        GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
+        GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
+        GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
+
+        // emit attributes
+        varyingHandler->emitAttributes(rsgp);
+        fragBuilder->codeAppend("vec4 shadowParams;");
+        varyingHandler->addPassThroughAttribute(rsgp.inShadowParams(), "shadowParams");
+
+        // setup pass through color
+        varyingHandler->addPassThroughAttribute(rsgp.inColor(), args.fOutputColor);
+
+        // Setup position
+        this->setupPosition(vertBuilder, gpArgs, rsgp.inPosition()->fName);
+
+        // emit transforms
+        this->emitTransforms(vertBuilder,
+                             varyingHandler,
+                             uniformHandler,
+                             gpArgs->fPositionVar,
+                             rsgp.inPosition()->fName,
+                             rsgp.localMatrix(),
+                             args.fFPCoordTransformHandler);
+
+        fragBuilder->codeAppend("float d = length(shadowParams.xy);");
+        fragBuilder->codeAppend("float distance = shadowParams.z * (1.0 - d);");
+
+        fragBuilder->codeAppend("float radius = shadowParams.w;");
+        
+        fragBuilder->codeAppend("float factor = 1.0 - clamp(distance/radius, 0.0, 1.0);");
+        fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;");
+        fragBuilder->codeAppendf("%s = vec4(factor);",
+                                 args.fOutputCoverage);
+    }
+
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
+                 FPCoordTransformIter&& transformIter) override {
+        this->setTransformDataHelper(proc.cast<GrRRectShadowGeoProc>().localMatrix(),
+                                     pdman, &transformIter);
+    }
+
+    static inline void GenKey(const GrGeometryProcessor& gp,
+                              const GrGLSLCaps&,
+                              GrProcessorKeyBuilder* b) {
+        const GrRRectShadowGeoProc& rsgp = gp.cast<GrRRectShadowGeoProc>();
+        uint16_t key;
+        key = rsgp.localMatrix().hasPerspective() ? 0x1 : 0x0;
+        b->add32(key);
+    }
+
+private:
+    typedef GrGLSLGeometryProcessor INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrRRectShadowGeoProc::GrRRectShadowGeoProc(const SkMatrix& localMatrix)
+    : fLocalMatrix(localMatrix) {
+
+    this->initClassID<GrRRectShadowGeoProc>();
+    fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
+                                         kHigh_GrSLPrecision);
+    fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
+    fInShadowParams = &this->addVertexAttrib("inShadowParams", kVec4f_GrVertexAttribType);
+}
+
+void GrRRectShadowGeoProc::getGLSLProcessorKey(const GrGLSLCaps& caps,
+                                               GrProcessorKeyBuilder* b) const {
+    GrGLSLRRectShadowGeoProc::GenKey(*this, caps, b);
+}
+
+GrGLSLPrimitiveProcessor* GrRRectShadowGeoProc::createGLSLInstance(const GrGLSLCaps&) const {
+    return new GrGLSLRRectShadowGeoProc();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrRRectShadowGeoProc);
+
+sk_sp<GrGeometryProcessor> GrRRectShadowGeoProc::TestCreate(GrProcessorTestData* d) {
+    return GrRRectShadowGeoProc::Make(GrTest::TestMatrix(d->fRandom));
+}
diff --git a/src/gpu/effects/GrShadowGeoProc.h b/src/gpu/effects/GrShadowGeoProc.h
new file mode 100755
index 0000000..29e2bde
--- /dev/null
+++ b/src/gpu/effects/GrShadowGeoProc.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrShadowGeoProc_DEFINED
+#define GrShadowGeoProc_DEFINED
+
+#include "GrProcessor.h"
+#include "GrGeometryProcessor.h"
+
+class GrGLRRectShadowGeoProc;
+
+/**
+ * The output color of this effect is a coverage mask for a rrect shadow,
+ * assuming circular corner geometry.
+ */
+class GrRRectShadowGeoProc : public GrGeometryProcessor {
+public:
+    static sk_sp<GrGeometryProcessor> Make(const SkMatrix& localMatrix) {
+        return sk_sp<GrGeometryProcessor>(new GrRRectShadowGeoProc(localMatrix));
+    }
+
+    ~GrRRectShadowGeoProc() override {}
+
+    const char* name() const override { return "RRectShadow"; }
+
+    const Attribute* inPosition() const { return fInPosition; }
+    const Attribute* inColor() const { return fInColor; }
+    const Attribute* inShadowParams() const { return fInShadowParams; }
+    GrColor color() const { return fColor; }
+    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
+    const SkMatrix& localMatrix() const { return fLocalMatrix; }
+
+    void getGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override;
+
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override;
+
+private:
+    GrRRectShadowGeoProc(const SkMatrix& localMatrix);
+
+    GrColor          fColor;
+    SkMatrix         fLocalMatrix;
+    const Attribute* fInPosition;
+    const Attribute* fInColor;
+    const Attribute* fInShadowParams;
+
+    GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
+
+    typedef GrGeometryProcessor INHERITED;
+};
+
+
+#endif