GPU implementation of drawArc.

This adds analytic shaders for filled arcs and butt-cap stroked arcs where the center point is not used.

BUG=skia:5227
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2259323003

Review-Url: https://codereview.chromium.org/2259323003
diff --git a/include/gpu/GrDrawContext.h b/include/gpu/GrDrawContext.h
index 77a3443..993bb56 100644
--- a/include/gpu/GrDrawContext.h
+++ b/include/gpu/GrDrawContext.h
@@ -230,9 +230,31 @@
                   const SkMatrix& viewMatrix,
                   const SkRect& oval,
                   const GrStyle& style);
+   /**
+    * Draws a partial arc of an oval.
+    *
+    * @param paint         describes how to color pixels.
+    * @param viewMatrix    transformation matrix.
+    * @param oval          the bounding rect of the oval.
+    * @param startAngle    starting angle in degrees.
+    * @param sweepAngle    angle to sweep in degrees. Must be in (-360, 360)
+    * @param useCenter     true means that the implied path begins at the oval center, connects as a
+    *                      line to the point indicated by the start contains the arc indicated by
+    *                      the sweep angle. If false the line beginning at the center point is
+    *                      omitted.
+    * @param style         style to apply to the oval.
+    */
+    void drawArc(const GrClip&,
+                 const GrPaint& paint,
+                 const SkMatrix& viewMatrix,
+                 const SkRect& oval,
+                 SkScalar startAngle,
+                 SkScalar sweepAngle,
+                 bool useCenter,
+                 const GrStyle& style);
 
     /**
-     *  Draw the image as a set of rects, specified by |iter|.
+     * Draw the image as a set of rects, specified by |iter|.
      */
     void drawImageLattice(const GrClip&,
                           const GrPaint& paint,
diff --git a/src/gpu/GrDrawContext.cpp b/src/gpu/GrDrawContext.cpp
index 7044b49..3b346b6 100644
--- a/src/gpu/GrDrawContext.cpp
+++ b/src/gpu/GrDrawContext.cpp
@@ -997,6 +997,44 @@
     this->internalDrawPath(clip, paint, viewMatrix, path, style);
 }
 
+void GrDrawContext::drawArc(const GrClip& clip,
+                            const GrPaint& paint,
+                            const SkMatrix& viewMatrix,
+                            const SkRect& oval,
+                            SkScalar startAngle,
+                            SkScalar sweepAngle,
+                            bool useCenter,
+                            const GrStyle& style) {
+    bool useHWAA;
+    if (should_apply_coverage_aa(paint, fRenderTarget.get(), &useHWAA)) {
+        GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
+        SkAutoTUnref<GrDrawBatch> batch(GrOvalRenderer::CreateArcBatch(paint.getColor(),
+                                                                       viewMatrix,
+                                                                       oval,
+                                                                       startAngle,
+                                                                       sweepAngle,
+                                                                       useCenter,
+                                                                       style,
+                                                                       shaderCaps));
+        if (batch) {
+            GrPipelineBuilder pipelineBuilder(paint, useHWAA);
+            this->getDrawTarget()->drawBatch(pipelineBuilder, this, clip, batch);
+            return;
+        }
+    }
+    SkPath path;
+    path.setIsVolatile(true);
+    if (useCenter) {
+        path.moveTo(oval.centerX(), oval.centerY());
+    }
+    path.arcTo(oval, startAngle, sweepAngle, !useCenter);
+    if (useCenter) {
+        path.close();
+    }
+    this->internalDrawPath(clip, paint, viewMatrix, path, style);
+    return;
+}
+
 void GrDrawContext::drawImageLattice(const GrClip& clip,
                                      const GrPaint& paint,
                                      const SkMatrix& viewMatrix,
diff --git a/src/gpu/GrOvalRenderer.cpp b/src/gpu/GrOvalRenderer.cpp
index 079c261..c17388dc 100644
--- a/src/gpu/GrOvalRenderer.cpp
+++ b/src/gpu/GrOvalRenderer.cpp
@@ -13,6 +13,7 @@
 #include "GrInvariantOutput.h"
 #include "GrProcessor.h"
 #include "GrResourceProvider.h"
+#include "GrStyle.h"
 #include "SkRRect.h"
 #include "SkStrokeRec.h"
 #include "batches/GrVertexBatch.h"
@@ -28,14 +29,6 @@
 
 namespace {
 
-struct CircleVertex {
-    SkPoint  fPos;
-    GrColor  fColor;
-    SkPoint  fOffset;
-    SkScalar fOuterRadius;
-    SkScalar fInnerRadius;
-};
-
 struct EllipseVertex {
     SkPoint  fPos;
     GrColor  fColor;
@@ -75,20 +68,42 @@
  *             v is a normalized vector pointing to the outer edge
  *             outerDistance is the distance to the outer edge, < 0 if we are outside of the shape
  *             if stroking, innerDistance is the distance to the inner edge, < 0 if outside
+ * Additional clip planes are supported for rendering circular arcs. The additional planes are
+ * either intersected or unioned together. Up to three planes are supported (an initial plane,
+ * a plane intersected with the initial plane, and a plane unioned with the first two). Only two
+ * are useful for any given arc, but having all three in one instance allows batching different
+ * types of arcs.
  */
 
 class CircleGeometryProcessor : public GrGeometryProcessor {
 public:
-    CircleGeometryProcessor(bool stroke, const SkMatrix& localMatrix) : fLocalMatrix(localMatrix){
+    CircleGeometryProcessor(bool stroke, bool clipPlane, bool isectPlane, bool unionPlane,
+                            const SkMatrix& localMatrix)
+            : fLocalMatrix(localMatrix) {
         this->initClassID<CircleGeometryProcessor>();
         fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
                                              kHigh_GrSLPrecision);
         fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
         fInCircleEdge = &this->addVertexAttrib("inCircleEdge", kVec4f_GrVertexAttribType);
+        if (clipPlane) {
+            fInClipPlane = &this->addVertexAttrib("inClipPlane", kVec3f_GrVertexAttribType);
+        } else {
+            fInClipPlane = nullptr;
+        }
+        if (isectPlane) {
+            fInIsectPlane = &this->addVertexAttrib("inIsectPlane", kVec3f_GrVertexAttribType);
+        } else {
+            fInIsectPlane = nullptr;
+        }
+        if (unionPlane) {
+            fInUnionPlane = &this->addVertexAttrib("inUnionPlane", kVec3f_GrVertexAttribType);
+        } else {
+            fInUnionPlane = nullptr;
+        }
         fStroke = stroke;
     }
 
-    bool implementsDistanceVector() const override { return true; };
+    bool implementsDistanceVector() const override { return !fInClipPlane; };
 
     virtual ~CircleGeometryProcessor() {}
 
@@ -112,15 +127,27 @@
             GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
             GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
             GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
+            GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
             // emit attributes
             varyingHandler->emitAttributes(cgp);
+            fragBuilder->codeAppend("vec4 circleEdge;");
+            varyingHandler->addPassThroughAttribute(cgp.fInCircleEdge, "circleEdge");
+            if (cgp.fInClipPlane) {
+                fragBuilder->codeAppend("vec3 clipPlane;");
+                varyingHandler->addPassThroughAttribute(cgp.fInClipPlane, "clipPlane");
+            }
+            if (cgp.fInIsectPlane) {
+                SkASSERT(cgp.fInClipPlane);
+                fragBuilder->codeAppend("vec3 isectPlane;");
+                varyingHandler->addPassThroughAttribute(cgp.fInIsectPlane, "isectPlane");
+            }
+            if (cgp.fInUnionPlane) {
+                SkASSERT(cgp.fInClipPlane);
+                fragBuilder->codeAppend("vec3 unionPlane;");
+                varyingHandler->addPassThroughAttribute(cgp.fInUnionPlane, "unionPlane");
+            }
 
-            GrGLSLVertToFrag v(kVec4f_GrSLType);
-            varyingHandler->addVarying("CircleEdge", &v);
-            vertBuilder->codeAppendf("%s = %s;", v.vsOut(), cgp.fInCircleEdge->fName);
-
-            GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
             // setup pass through color
             varyingHandler->addPassThroughAttribute(cgp.fInColor, args.fOutputColor);
 
@@ -137,12 +164,11 @@
                                  args.fTransformsIn,
                                  args.fTransformsOut);
 
-            fragBuilder->codeAppendf("float d = length(%s.xy);", v.fsIn());
-            fragBuilder->codeAppendf("float distanceToOuterEdge = %s.z * (1.0 - d);", v.fsIn());
-            fragBuilder->codeAppendf("float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);");
+            fragBuilder->codeAppend("float d = length(circleEdge.xy);");
+            fragBuilder->codeAppend("float distanceToOuterEdge = circleEdge.z * (1.0 - d);");
+            fragBuilder->codeAppend("float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);");
             if (cgp.fStroke) {
-                fragBuilder->codeAppendf("float distanceToInnerEdge = %s.z * (d - %s.w);",
-                                         v.fsIn(), v.fsIn());
+                fragBuilder->codeAppend("float distanceToInnerEdge = circleEdge.z * (d - circleEdge.w);");
                 fragBuilder->codeAppend("float innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);");
                 fragBuilder->codeAppend("edgeAlpha *= innerAlpha;");
             }
@@ -154,12 +180,20 @@
                                          "%s);", // no normalize
                                          args.fDistanceVectorName, innerEdgeDistance);
                 fragBuilder->codeAppend ("} else {");
-                fragBuilder->codeAppendf("    %s = vec4(normalize(%s.xy), distanceToOuterEdge, "
-                                         "%s);",
-                                         args.fDistanceVectorName, v.fsIn(), innerEdgeDistance);
+                fragBuilder->codeAppendf("    %s = vec4(normalize(circleEdge.xy), distanceToOuterEdge, %s);",
+                                         args.fDistanceVectorName, innerEdgeDistance);
                 fragBuilder->codeAppend ("}");
             }
-
+            if (cgp.fInClipPlane) {
+                fragBuilder->codeAppend("float clip = clamp(circleEdge.z * dot(circleEdge.xy, clipPlane.xy) + clipPlane.z, 0.0, 1.0);");
+                if (cgp.fInIsectPlane) {
+                    fragBuilder->codeAppend("clip *= clamp(circleEdge.z * dot(circleEdge.xy, isectPlane.xy) + isectPlane.z, 0.0, 1.0);");
+                }
+                if (cgp.fInUnionPlane) {
+                    fragBuilder->codeAppend("clip += (1-clip)*clamp(circleEdge.z * dot(circleEdge.xy, unionPlane.xy) + unionPlane.z, 0.0, 1.0);");
+                }
+                fragBuilder->codeAppend("edgeAlpha *= clip;");
+            }
             fragBuilder->codeAppendf("%s = vec4(edgeAlpha);", args.fOutputCoverage);
         }
 
@@ -167,14 +201,16 @@
                            const GrGLSLCaps&,
                            GrProcessorKeyBuilder* b) {
             const CircleGeometryProcessor& cgp = gp.cast<CircleGeometryProcessor>();
-            uint16_t key = cgp.fStroke ? 0x1 : 0x0;
-            key |= cgp.fLocalMatrix.hasPerspective() ? 0x2 : 0x0;
+            uint16_t key;
+            key  = cgp.fStroke                       ? 0x01 : 0x0;
+            key |= cgp.fLocalMatrix.hasPerspective() ? 0x02 : 0x0;
+            key |= cgp.fInClipPlane                  ? 0x04 : 0x0;
+            key |= cgp.fInIsectPlane                 ? 0x08 : 0x0;
+            key |= cgp.fInUnionPlane                 ? 0x10 : 0x0;
             b->add32(key);
         }
 
-        void setData(const GrGLSLProgramDataManager& pdman,
-                     const GrPrimitiveProcessor& gp) override {
-        }
+        void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&) override {}
 
         void setTransformData(const GrPrimitiveProcessor& primProc,
                               const GrGLSLProgramDataManager& pdman,
@@ -192,6 +228,9 @@
     const Attribute* fInPosition;
     const Attribute* fInColor;
     const Attribute* fInCircleEdge;
+    const Attribute* fInClipPlane;
+    const Attribute* fInIsectPlane;
+    const Attribute* fInUnionPlane;
     bool             fStroke;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
@@ -203,7 +242,9 @@
 
 sk_sp<GrGeometryProcessor> CircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
     return sk_sp<GrGeometryProcessor>(
-        new CircleGeometryProcessor(d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom)));
+        new CircleGeometryProcessor(d->fRandom->nextBool(), d->fRandom->nextBool(),
+                                    d->fRandom->nextBool(), d->fRandom->nextBool(),
+                                    GrTest::TestMatrix(d->fRandom)));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -529,19 +570,47 @@
 public:
     DEFINE_BATCH_CLASS_ID
 
-    CircleBatch(GrColor color, const SkMatrix& viewMatrix, const SkRect& circle,
-                const SkStrokeRec& stroke)
-            : INHERITED(ClassID())
-            , fViewMatrixIfUsingLocalCoords(viewMatrix) {
-        SkPoint center = SkPoint::Make(circle.centerX(), circle.centerY());
+    /** Optional extra params to render a partial arc rather than a full circle. */
+    struct ArcParams {
+        SkScalar fStartAngleRadians;
+        SkScalar fSweepAngleRadians;
+        bool fUseCenter;
+    };
+    static GrDrawBatch* Create(GrColor color, const SkMatrix& viewMatrix, SkPoint center,
+                               SkScalar radius, const GrStyle& style,
+                               const ArcParams* arcParams = nullptr) {
+        SkASSERT(circle_stays_circle(viewMatrix));
+        const SkStrokeRec& stroke = style.strokeRec();
+        if (style.hasPathEffect()) {
+            return nullptr;
+        }
+        SkStrokeRec::Style recStyle = stroke.getStyle();
+        if (arcParams) {
+            // Arc support depends on the style.
+            switch (recStyle) {
+                case SkStrokeRec::kStrokeAndFill_Style:
+                    // This produces a strange result that this batch doesn't implement.
+                    return nullptr;
+                case SkStrokeRec::kFill_Style:
+                    // This supports all fills.
+                    break;
+                case SkStrokeRec::kStroke_Style: // fall through
+                case SkStrokeRec::kHairline_Style:
+                    // Strokes that don't use the center point are supported with butt cap.
+                    if (arcParams->fUseCenter || stroke.getCap() != SkPaint::kButt_Cap) {
+                        return nullptr;
+                    }
+                    break;
+            }
+        }
+
         viewMatrix.mapPoints(&center, 1);
-        SkScalar radius = viewMatrix.mapRadius(SkScalarHalf(circle.width()));
+        radius = viewMatrix.mapRadius(radius);
         SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth());
 
-        SkStrokeRec::Style style = stroke.getStyle();
-        bool isStrokeOnly = SkStrokeRec::kStroke_Style == style ||
-                            SkStrokeRec::kHairline_Style == style;
-        bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
+        bool isStrokeOnly = SkStrokeRec::kStroke_Style == recStyle ||
+                            SkStrokeRec::kHairline_Style == recStyle;
+        bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle;
 
         SkScalar innerRadius = 0.0f;
         SkScalar outerRadius = radius;
@@ -565,21 +634,111 @@
         // rendered and the outset ensures the box will cover all partially covered by the circle.
         outerRadius += SK_ScalarHalf;
         innerRadius -= SK_ScalarHalf;
+        CircleBatch* batch = new CircleBatch();
+        batch->fViewMatrixIfUsingLocalCoords = viewMatrix;
 
-        fGeoData.emplace_back(Geometry {
-            color,
-            innerRadius,
-            outerRadius,
-            SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
-                             center.fX + outerRadius, center.fY + outerRadius)
-        });
+        // This makes every point fully inside the intersection plane.
+        static constexpr SkScalar kUnusedIsectPlane[] = {0.f, 0.f, 1.f};
+        // This makes every point fully outside the union plane.
+        static constexpr SkScalar kUnusedUnionPlane[] = {0.f, 0.f, 0.f};
+        SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
+                                            center.fX + outerRadius, center.fY + outerRadius);
+
+        if (arcParams) {
+            // The shader operates in a space where the circle is translated to be centered at the
+            // origin. Here we compute points on the unit circle at the starting and ending angles.
+            SkPoint startPoint, stopPoint;
+            startPoint.fY = SkScalarSinCos(arcParams->fStartAngleRadians, &startPoint.fX);
+            SkScalar endAngle = arcParams->fStartAngleRadians + arcParams->fSweepAngleRadians;
+            stopPoint.fY = SkScalarSinCos(endAngle, &stopPoint.fX);
+            // Like a fill without useCenter, butt-cap stroke can be implemented by clipping against
+            // radial lines. However, in both cases we have to be careful about the half-circle.
+            // case. In that case the two radial lines are equal and so that edge gets clipped
+            // twice. Since the shared edge goes through the center we fall back on the useCenter
+            // case.
+            bool useCenter = (arcParams->fUseCenter || isStrokeOnly) &&
+                             !SkScalarNearlyEqual(SkScalarAbs(arcParams->fSweepAngleRadians),
+                                                  SK_ScalarPI);
+            if (useCenter) {
+                SkVector norm0 = {startPoint.fY, -startPoint.fX};
+                SkVector norm1 = {stopPoint.fY, -stopPoint.fX};
+                if (arcParams->fSweepAngleRadians > 0) {
+                    norm0.negate();
+                } else {
+                    norm1.negate();
+                }
+                batch->fClipPlane = true;
+                if (SkScalarAbs(arcParams->fSweepAngleRadians) > SK_ScalarPI) {
+                    batch->fGeoData.emplace_back(Geometry {
+                            color,
+                            innerRadius,
+                            outerRadius,
+                            {norm0.fX, norm0.fY, 0.5f},
+                            {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
+                            {norm1.fX, norm1.fY, 0.5f},
+                            devBounds
+                    });
+                    batch->fClipPlaneIsect = false;
+                    batch->fClipPlaneUnion = true;
+                } else {
+                    batch->fGeoData.emplace_back(Geometry {
+                            color,
+                            innerRadius,
+                            outerRadius,
+                            {norm0.fX, norm0.fY, 0.5f},
+                            {norm1.fX, norm1.fY, 0.5f},
+                            {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
+                            devBounds
+                    });
+                    batch->fClipPlaneIsect = true;
+                    batch->fClipPlaneUnion = false;
+                }
+            } else {
+                // We clip to a secant of the original circle.
+                startPoint.scale(radius);
+                stopPoint.scale(radius);
+                SkVector norm = {startPoint.fY - stopPoint.fY, stopPoint.fX - startPoint.fX};
+                norm.normalize();
+                if (arcParams->fSweepAngleRadians > 0) {
+                    norm.negate();
+                }
+                SkScalar d = -norm.dot(startPoint) + 0.5f;
+
+                batch->fGeoData.emplace_back(Geometry {
+                        color,
+                        innerRadius,
+                        outerRadius,
+                        {norm.fX, norm.fY, d},
+                        {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
+                        {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
+                        devBounds
+                });
+                batch->fClipPlane = true;
+                batch->fClipPlaneIsect = false;
+                batch->fClipPlaneUnion = false;
+            }
+        } else {
+            batch->fGeoData.emplace_back(Geometry {
+                color,
+                innerRadius,
+                outerRadius,
+                {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
+                {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]},
+                {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]},
+                devBounds
+            });
+            batch->fClipPlane = false;
+            batch->fClipPlaneIsect = false;
+            batch->fClipPlaneUnion = false;
+        }
         // Use the original radius and stroke radius for the bounds so that it does not include the
         // AA bloat.
         radius += halfWidth;
-        this->setBounds({center.fX - radius, center.fY - radius,
-                         center.fX + radius, center.fY + radius},
-                        HasAABloat::kYes, IsZeroArea::kNo);
-        fStroked = isStrokeOnly && innerRadius > 0;
+        batch->setBounds({center.fX - radius, center.fY - radius,
+                          center.fX + radius, center.fY + radius},
+                          HasAABloat::kYes, IsZeroArea::kNo);
+        batch->fStroked = isStrokeOnly && innerRadius > 0;
+        return batch;
     }
 
     const char* name() const override { return "CircleBatch"; }
@@ -608,6 +767,7 @@
     }
 
 private:
+    CircleBatch() : INHERITED(ClassID()) {}
     void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
         // Handle any overrides that affect our GP.
         overrides.getOverrideColorIfSet(&fGeoData[0].fColor);
@@ -623,15 +783,29 @@
         }
 
         // Setup geometry processor
-        SkAutoTUnref<GrGeometryProcessor> gp(new CircleGeometryProcessor(fStroked, localMatrix));
+        SkAutoTUnref<GrGeometryProcessor> gp(new CircleGeometryProcessor(fStroked, fClipPlane,
+                                                                         fClipPlaneIsect,
+                                                                         fClipPlaneUnion,
+                                                                         localMatrix));
+
+        struct CircleVertex {
+            SkPoint  fPos;
+            GrColor  fColor;
+            SkPoint  fOffset;
+            SkScalar fOuterRadius;
+            SkScalar fInnerRadius;
+            // These planes may or may not be present in the vertex buffer.
+            SkScalar fHalfPlanes[3][3];
+        };
 
         int instanceCount = fGeoData.count();
         size_t vertexStride = gp->getVertexStride();
-        SkASSERT(vertexStride == sizeof(CircleVertex));
+        SkASSERT(vertexStride == sizeof(CircleVertex) - (fClipPlane ? 0 : 3 * sizeof(SkScalar))
+                                                      - (fClipPlaneIsect? 0 : 3 * sizeof(SkScalar))
+                                                      - (fClipPlaneUnion? 0 : 3 * sizeof(SkScalar)));
         QuadHelper helper;
-        CircleVertex* verts = reinterpret_cast<CircleVertex*>(helper.init(target, vertexStride,
-                                                                          instanceCount));
-        if (!verts) {
+        char* vertices = reinterpret_cast<char*>(helper.init(target, vertexStride, instanceCount));
+        if (!vertices) {
             return;
         }
 
@@ -643,34 +817,57 @@
             SkScalar outerRadius = geom.fOuterRadius;
 
             const SkRect& bounds = geom.fDevBounds;
+            CircleVertex* v0 = reinterpret_cast<CircleVertex*>(vertices + (4 * i + 0)*vertexStride);
+            CircleVertex* v1 = reinterpret_cast<CircleVertex*>(vertices + (4 * i + 1)*vertexStride);
+            CircleVertex* v2 = reinterpret_cast<CircleVertex*>(vertices + (4 * i + 2)*vertexStride);
+            CircleVertex* v3 = reinterpret_cast<CircleVertex*>(vertices + (4 * i + 3)*vertexStride);
 
             // The inner radius in the vertex data must be specified in normalized space.
             innerRadius = innerRadius / outerRadius;
-            verts[0].fPos = SkPoint::Make(bounds.fLeft,  bounds.fTop);
-            verts[0].fColor = color;
-            verts[0].fOffset = SkPoint::Make(-1, -1);
-            verts[0].fOuterRadius = outerRadius;
-            verts[0].fInnerRadius = innerRadius;
+            v0->fPos = SkPoint::Make(bounds.fLeft,  bounds.fTop);
+            v0->fColor = color;
+            v0->fOffset = SkPoint::Make(-1, -1);
+            v0->fOuterRadius = outerRadius;
+            v0->fInnerRadius = innerRadius;
 
-            verts[1].fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
-            verts[1].fColor = color;
-            verts[1].fOffset = SkPoint::Make(-1, 1);
-            verts[1].fOuterRadius = outerRadius;
-            verts[1].fInnerRadius = innerRadius;
+            v1->fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
+            v1->fColor = color;
+            v1->fOffset = SkPoint::Make(-1, 1);
+            v1->fOuterRadius = outerRadius;
+            v1->fInnerRadius = innerRadius;
 
-            verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
-            verts[2].fColor = color;
-            verts[2].fOffset = SkPoint::Make(1, 1);
-            verts[2].fOuterRadius = outerRadius;
-            verts[2].fInnerRadius = innerRadius;
+            v2->fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
+            v2->fColor = color;
+            v2->fOffset = SkPoint::Make(1, 1);
+            v2->fOuterRadius = outerRadius;
+            v2->fInnerRadius = innerRadius;
 
-            verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
-            verts[3].fColor = color;
-            verts[3].fOffset = SkPoint::Make(1, -1);
-            verts[3].fOuterRadius = outerRadius;
-            verts[3].fInnerRadius = innerRadius;
+            v3->fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
+            v3->fColor = color;
+            v3->fOffset = SkPoint::Make(1, -1);
+            v3->fOuterRadius = outerRadius;
+            v3->fInnerRadius = innerRadius;
 
-            verts += kVerticesPerQuad;
+            if (fClipPlane) {
+                memcpy(v0->fHalfPlanes[0], geom.fClipPlane, 3 * sizeof(SkScalar));
+                memcpy(v1->fHalfPlanes[0], geom.fClipPlane, 3 * sizeof(SkScalar));
+                memcpy(v2->fHalfPlanes[0], geom.fClipPlane, 3 * sizeof(SkScalar));
+                memcpy(v3->fHalfPlanes[0], geom.fClipPlane, 3 * sizeof(SkScalar));
+            }
+            int unionIdx = 1;
+            if (fClipPlaneIsect) {
+                memcpy(v0->fHalfPlanes[1], geom.fIsectPlane, 3 * sizeof(SkScalar));
+                memcpy(v1->fHalfPlanes[1], geom.fIsectPlane, 3 * sizeof(SkScalar));
+                memcpy(v2->fHalfPlanes[1], geom.fIsectPlane, 3 * sizeof(SkScalar));
+                memcpy(v3->fHalfPlanes[1], geom.fIsectPlane, 3 * sizeof(SkScalar));
+                unionIdx = 2;
+            }
+            if (fClipPlaneUnion) {
+                memcpy(v0->fHalfPlanes[unionIdx], geom.fUnionPlane, 3 * sizeof(SkScalar));
+                memcpy(v1->fHalfPlanes[unionIdx], geom.fUnionPlane, 3 * sizeof(SkScalar));
+                memcpy(v2->fHalfPlanes[unionIdx], geom.fUnionPlane, 3 * sizeof(SkScalar));
+                memcpy(v3->fHalfPlanes[unionIdx], geom.fUnionPlane, 3 * sizeof(SkScalar));
+            }
         }
         helper.recordDraw(target, gp);
     }
@@ -686,6 +883,12 @@
             return false;
         }
 
+        // Because we've set up the batches that don't use the planes with noop values
+        // we can just accumulate used planes by later batches.
+        fClipPlane |= that->fClipPlane;
+        fClipPlaneIsect |= that->fClipPlaneIsect;
+        fClipPlaneUnion |= that->fClipPlaneUnion;
+
         if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
             return false;
         }
@@ -696,13 +899,19 @@
     }
 
     struct Geometry {
-        GrColor fColor;
+        GrColor  fColor;
         SkScalar fInnerRadius;
         SkScalar fOuterRadius;
-        SkRect fDevBounds;
+        SkScalar fClipPlane[3];
+        SkScalar fIsectPlane[3];
+        SkScalar fUnionPlane[3];
+        SkRect   fDevBounds;
     };
 
     bool                         fStroked;
+    bool                         fClipPlane;
+    bool                         fClipPlaneIsect;
+    bool                         fClipPlaneUnion;
     SkMatrix                     fViewMatrixIfUsingLocalCoords;
     SkSTArray<1, Geometry, true> fGeoData;
 
@@ -1243,7 +1452,17 @@
         }
 
         // Setup geometry processor
-        SkAutoTUnref<GrGeometryProcessor> gp(new CircleGeometryProcessor(fStroked, localMatrix));
+        SkAutoTUnref<GrGeometryProcessor> gp(new CircleGeometryProcessor(fStroked, false, false,
+                                                                         false, localMatrix));
+
+        struct CircleVertex {
+            SkPoint  fPos;
+            GrColor  fColor;
+            SkPoint  fOffset;
+            SkScalar fOuterRadius;
+            SkScalar fInnerRadius;
+            // No half plane, we don't use it here.
+        };
 
         int instanceCount = fGeoData.count();
         size_t vertexStride = gp->getVertexStride();
@@ -1631,7 +1850,7 @@
                                               const SkMatrix& viewMatrix,
                                               const SkRRect& rrect,
                                               const SkStrokeRec& stroke,
-                                              GrShaderCaps* shaderCaps) {
+                                              const GrShaderCaps* shaderCaps) {
     if (rrect.isOval()) {
         return CreateOvalBatch(color, viewMatrix, rrect.getBounds(), stroke, shaderCaps);
     }
@@ -1649,10 +1868,13 @@
                                              const SkMatrix& viewMatrix,
                                              const SkRect& oval,
                                              const SkStrokeRec& stroke,
-                                             GrShaderCaps* shaderCaps) {
+                                             const GrShaderCaps* shaderCaps) {
     // we can draw circles
-    if (SkScalarNearlyEqual(oval.width(), oval.height()) && circle_stays_circle(viewMatrix)) {
-        return new CircleBatch(color, viewMatrix, oval, stroke);
+    SkScalar width = oval.width();
+    if (SkScalarNearlyEqual(width, oval.height()) && circle_stays_circle(viewMatrix)) {
+        SkPoint center = {oval.centerX(), oval.centerY()};
+        return CircleBatch::Create(color, viewMatrix, center, width / 2.f,
+                                   GrStyle(stroke, nullptr));
     }
 
     // if we have shader derivative support, render as device-independent
@@ -1670,13 +1892,51 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+GrDrawBatch* GrOvalRenderer::CreateArcBatch(GrColor color,
+                                            const SkMatrix& viewMatrix,
+                                            const SkRect& oval,
+                                            SkScalar startAngle, SkScalar sweepAngle,
+                                            bool useCenter,
+                                            const GrStyle& style,
+                                            const GrShaderCaps* shaderCaps) {
+    SkScalar width = oval.width();
+    if (!SkScalarNearlyEqual(width, oval.height()) || !circle_stays_circle(viewMatrix)) {
+        return nullptr;
+    }
+    SkPoint center = {oval.centerX(), oval.centerY()};
+    CircleBatch::ArcParams arcParams = {
+        SkDegreesToRadians(startAngle),
+        SkDegreesToRadians(sweepAngle),
+        useCenter
+    };
+    return CircleBatch::Create(color, viewMatrix, center, width/2.f, style, &arcParams);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
 #ifdef GR_TEST_UTILS
 
 DRAW_BATCH_TEST_DEFINE(CircleBatch) {
-    SkMatrix viewMatrix = GrTest::TestMatrix(random);
-    GrColor color = GrRandomColor(random);
-    SkRect circle = GrTest::TestSquare(random);
-    return new CircleBatch(color, viewMatrix, circle, GrTest::TestStrokeRec(random));
+    do {
+        SkMatrix viewMatrix = GrTest::TestMatrix(random);
+        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);
+        CircleBatch::ArcParams arcParamsTmp;
+        const CircleBatch::ArcParams* arcParams = nullptr;
+        if (random->nextBool()) {
+            arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2;
+            arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2 - .01f;
+            arcParams = &arcParamsTmp;
+        }
+        GrDrawBatch* batch = CircleBatch::Create(color, viewMatrix, center, radius,
+                                                 GrStyle(stroke, nullptr), arcParams);
+        if (batch) {
+            return batch;
+        }
+    } while (true);
 }
 
 DRAW_BATCH_TEST_DEFINE(EllipseBatch) {
diff --git a/src/gpu/GrOvalRenderer.h b/src/gpu/GrOvalRenderer.h
index 66182b2..c4ea496 100644
--- a/src/gpu/GrOvalRenderer.h
+++ b/src/gpu/GrOvalRenderer.h
@@ -12,6 +12,7 @@
 
 class GrDrawBatch;
 class GrShaderCaps;
+class GrStyle;
 class SkMatrix;
 struct SkRect;
 class SkRRect;
@@ -26,15 +27,21 @@
                                         const SkMatrix& viewMatrix,
                                         const SkRect& oval,
                                         const SkStrokeRec& stroke,
-                                        GrShaderCaps* shaderCaps);
+                                        const GrShaderCaps* shaderCaps);
     static GrDrawBatch* CreateRRectBatch(GrColor,
                                          const SkMatrix& viewMatrix,
                                          const SkRRect& rrect,
                                          const SkStrokeRec& stroke,
-                                         GrShaderCaps* shaderCaps);
+                                         const GrShaderCaps* shaderCaps);
 
-private:
-    GrOvalRenderer();
+    static GrDrawBatch* CreateArcBatch(GrColor,
+                                       const SkMatrix& viewMatrix,
+                                       const SkRect& oval,
+                                       SkScalar startAngle,
+                                       SkScalar sweepAngle,
+                                       bool useCenter,
+                                       const GrStyle&,
+                                       const GrShaderCaps* shaderCaps);
 };
 
 #endif // GrOvalRenderer_DEFINED
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 258355e..a6c8a1f 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -554,6 +554,25 @@
     fDrawContext->drawOval(fClip, grPaint, *draw.fMatrix, oval, GrStyle(paint));
 }
 
+void SkGpuDevice::drawArc(const SkDraw& draw, const SkRect& oval, SkScalar startAngle,
+                          SkScalar sweepAngle, bool useCenter, const SkPaint& paint) {
+    ASSERT_SINGLE_OWNER
+    GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawArc", fContext);
+    CHECK_SHOULD_DRAW(draw);
+
+    if (paint.getMaskFilter()) {
+        this->INHERITED::drawArc(draw, oval, startAngle, sweepAngle, useCenter, paint);
+        return;
+    }
+    GrPaint grPaint;
+    if (!SkPaintToGrPaint(this->context(), fDrawContext.get(), paint, *draw.fMatrix, &grPaint)) {
+        return;
+    }
+
+    fDrawContext->drawArc(fClip, grPaint, *draw.fMatrix, oval, startAngle, sweepAngle, useCenter,
+                          GrStyle(paint));
+}
+
 #include "SkMaskFilter.h"
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/SkGpuDevice.h b/src/gpu/SkGpuDevice.h
index 4de0a50..bbf15b5 100644
--- a/src/gpu/SkGpuDevice.h
+++ b/src/gpu/SkGpuDevice.h
@@ -77,6 +77,8 @@
                             const SkRRect& inner, const SkPaint& paint) override;
     virtual void drawOval(const SkDraw&, const SkRect& oval,
                           const SkPaint& paint) override;
+    virtual void drawArc(const SkDraw&, const SkRect& oval, SkScalar startAngle,
+                         SkScalar sweepAngle, bool useCenter, const SkPaint& paint) override;
     virtual void drawPath(const SkDraw&, const SkPath& path,
                           const SkPaint& paint, const SkMatrix* prePathMatrix,
                           bool pathIsMutable) override;