This CL introduces a new path renderer.

Here are the characteristics :
- It uses the original path, before stroking
- It supports traight lines only (no curves)
- It supports butt or square caps only
- It supports miter or bevel joins only
- No AA support
Support for these will be added step by step later on.

A first pass at the benchmarks on my linux machine gave me these approximate speed improvements (running all bench with the option '--forceAA 0') :
path_stroke_small_long_line 4X
path_stroke_small_sawtooth 4X
path_stroke_big_rect 4X
path_stroke_small_rect 6X
path_stroke_big_triangle 4X
path_stroke_small_triangle 10X
lines_1_BW 1.5X
dashline_2_square 1.5X
dashline_1_square 1.5X

Also note that I can't submit this code until GrDrawTarget::isOpaque() is implemented, unless I just disable my renderer completely for now.

BUG=chromium:135111
TEST=The following gms are affected and may require rebaselining : lineclosepath, linepath, strokes_poly
Review URL: https://codereview.appspot.com/7026049

git-svn-id: http://skia.googlecode.com/svn/trunk@7047 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/experimental/StrokePathRenderer/GrStrokePathRenderer.cpp b/experimental/StrokePathRenderer/GrStrokePathRenderer.cpp
new file mode 100644
index 0000000..e23d9ef
--- /dev/null
+++ b/experimental/StrokePathRenderer/GrStrokePathRenderer.cpp
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrStrokePathRenderer.h"
+
+#include "GrDrawTarget.h"
+#include "SkPath.h"
+#include "SkStrokeRec.h"
+
+namespace {
+
+bool is_clockwise(const SkVector& before, const SkVector& after) {
+    return before.cross(after) > 0;
+}
+
+enum IntersectionType {
+    kNone_IntersectionType,
+    kIn_IntersectionType,
+    kOut_IntersectionType
+};
+
+IntersectionType intersection(const SkPoint& p1, const SkPoint& p2,
+                              const SkPoint& p3, const SkPoint& p4,
+                                    SkPoint& res) {
+    // Store the values for fast access and easy
+    // equations-to-code conversion
+    SkScalar x1 = p1.x(), x2 = p2.x(), x3 = p3.x(), x4 = p4.x();
+    SkScalar y1 = p1.y(), y2 = p2.y(), y3 = p3.y(), y4 = p4.y();
+ 
+    SkScalar d = SkScalarMul(x1 - x2, y3 - y4) - SkScalarMul(y1 - y2, x3 - x4);
+    // If d is zero, there is no intersection
+    if (SkScalarNearlyZero(d)) {
+        return kNone_IntersectionType;
+    }
+ 
+    // Get the x and y
+    SkScalar pre  = SkScalarMul(x1, y2) - SkScalarMul(y1, x2),
+             post = SkScalarMul(x3, y4) - SkScalarMul(y3, x4);
+    // Compute the point of intersection
+    res.set(SkScalarDiv(SkScalarMul(pre, x3 - x4) - SkScalarMul(x1 - x2, post), d),
+            SkScalarDiv(SkScalarMul(pre, y3 - y4) - SkScalarMul(y1 - y2, post), d));
+
+    // Check if the x and y coordinates are within both lines
+    return (res.x() < GrMin(x1, x2) || res.x() > GrMax(x1, x2) ||
+            res.x() < GrMin(x3, x4) || res.x() > GrMax(x3, x4) ||
+            res.y() < GrMin(y1, y2) || res.y() > GrMax(y1, y2) ||
+            res.y() < GrMin(y3, y4) || res.y() > GrMax(y3, y4)) ?
+            kOut_IntersectionType : kIn_IntersectionType;
+}
+
+} // namespace
+
+GrStrokePathRenderer::GrStrokePathRenderer() {
+}
+
+bool GrStrokePathRenderer::canDrawPath(const SkPath& path,
+                                       const SkStrokeRec& stroke,
+                                       const GrDrawTarget* target,
+                                       bool antiAlias) const {
+    // FIXME : put the proper condition once GrDrawTarget::isOpaque is implemented
+    const bool isOpaque = true; // target->isOpaque();
+
+    // FIXME : remove this requirement once we have AA circles and implement the
+    //         circle joins/caps appropriately in the ::onDrawPath() function.
+    const bool requiresAACircle = (stroke.getCap()  == SkPaint::kRound_Cap) ||
+                                  (stroke.getJoin() == SkPaint::kRound_Join);
+
+    // Indices being stored in uint16, we don't want to overflow the indices capacity
+    static const int maxVBSize = 1 << 16;
+    const int maxNbVerts = (path.countPoints() + 1) * 5;
+    
+    // Check that the path contains no curved lines, only straight lines
+    static const uint32_t unsupportedMask = SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask;
+
+    // Must not be filled nor hairline nor semi-transparent
+    // Note : May require a check to path.isConvex() if AA is supported
+    return ((stroke.getStyle() == SkStrokeRec::kStroke_Style) && (maxNbVerts < maxVBSize) &&
+            !path.isInverseFillType() && isOpaque && !requiresAACircle && !antiAlias &&
+            ((path.getSegmentMasks() & unsupportedMask) == 0));
+}
+
+bool GrStrokePathRenderer::onDrawPath(const SkPath& origPath,
+                                      const SkStrokeRec& stroke,
+                                      GrDrawTarget* target,
+                                      bool antiAlias) {
+    if (origPath.isEmpty()) {
+        return true;
+    }
+
+    SkScalar width = stroke.getWidth();
+    if (width <= 0) {
+        return false;
+    }
+
+    // Get the join type
+    SkPaint::Join join = stroke.getJoin();
+    SkScalar miterLimit = stroke.getMiter();
+    SkScalar sqMiterLimit = SkScalarMul(miterLimit, miterLimit);
+    if ((join == SkPaint::kMiter_Join) && (miterLimit <= SK_Scalar1)) {
+        // If the miter limit is small, treat it as a bevel join
+        join = SkPaint::kBevel_Join;
+    }
+    const bool isMiter       = (join == SkPaint::kMiter_Join);
+    const bool isBevel       = (join == SkPaint::kBevel_Join);
+    SkScalar invMiterLimit   = isMiter ? SK_Scalar1 / miterLimit : 0;
+    SkScalar invMiterLimitSq = SkScalarMul(invMiterLimit, invMiterLimit);
+
+    // Allocate vertices
+    const int nbQuads     = origPath.countPoints() + 1; // Could be "-1" if path is not closed
+    GrVertexLayout layout = 0; // Just 3D points
+    const int extraVerts  = isMiter || isBevel ? 1 : 0;
+    const int maxVertexCount = nbQuads * (4 + extraVerts);
+    const int maxIndexCount  = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle
+    GrDrawTarget::AutoReleaseGeometry arg(target, layout, maxVertexCount, maxIndexCount);
+    if (!arg.succeeded()) {
+        return false;
+    }
+    SkPoint* verts = reinterpret_cast<SkPoint*>(arg.vertices());
+    uint16_t* idxs = reinterpret_cast<uint16_t*>(arg.indices());
+    int vCount = 0, iCount = 0;
+
+    // Transform the path into a list of triangles
+    SkPath::Iter iter(origPath, false);
+    SkPoint pts[4];
+    const SkScalar radius = SkScalarMul(width, 0.5);
+    SkPoint *firstPt = verts, *lastPt = NULL;
+    SkVector firstDir, dir;
+    firstDir.set(0, 0);
+    dir.set(0, 0);
+    bool isOpen = true;
+    for(SkPath::Verb v = iter.next(pts); v != SkPath::kDone_Verb; v = iter.next(pts)) {
+        switch(v) {
+            case SkPath::kMove_Verb:
+                // This will already be handled as pts[0] of the 1st line
+                break;
+            case SkPath::kClose_Verb:
+                isOpen = (lastPt == NULL);
+                break;
+            case SkPath::kLine_Verb:
+            {
+                SkVector v0 = dir;
+                dir = pts[1] - pts[0];
+                if (dir.setLength(radius)) {
+                    SkVector dirT;
+                    dirT.set(dir.fY, -dir.fX); // Get perpendicular direction
+                    SkPoint l1a = pts[0]+dirT, l1b = pts[1]+dirT,
+                            l2a = pts[0]-dirT, l2b = pts[1]-dirT;
+                    SkPoint miterPt[2];
+                    bool useMiterPoint = false;
+                    int idx0(-1), idx1(-1);
+                    if (NULL == lastPt) {
+                        firstDir = dir;
+                    } else {
+                        SkVector v1 = dir;
+                        if (v0.normalize() && v1.normalize()) {
+                            SkScalar dotProd = v0.dot(v1);
+                            // No need for bevel or miter join if the angle
+                            // is either 0 or 180 degrees
+                            if (!SkScalarNearlyZero(dotProd + SK_Scalar1) &&
+                                !SkScalarNearlyZero(dotProd - SK_Scalar1)) {
+                                bool ccw = !is_clockwise(v0, v1);
+                                int offset = ccw ? 1 : 0;
+                                idx0 = vCount-2+offset;
+                                idx1 = vCount+offset;
+                                const SkPoint* pt0 = &(lastPt[offset]);
+                                const SkPoint* pt1 = ccw ? &l2a : &l1a;
+                                switch(join) {
+                                    case SkPaint::kMiter_Join:
+                                    {
+                                        // *Note : Logic is from MiterJoiner
+
+                                        // FIXME : Special case if we have a right angle ?
+                                        // if (SkScalarNearlyZero(dotProd)) {...}
+
+                                        SkScalar sinHalfAngleSq =
+                                                SkScalarHalf(SK_Scalar1 + dotProd);
+                                        if (sinHalfAngleSq >= invMiterLimitSq) {
+                                            // Find the miter point (or points if it is further
+                                            // than the miter limit)
+                                            const SkPoint pt2 = *pt0+v0, pt3 = *pt1+v1;
+                                            if (intersection(*pt0, pt2, *pt1, pt3, miterPt[0]) !=
+                                                kNone_IntersectionType) {
+                                                SkPoint miterPt0 = miterPt[0] - *pt0;
+                                                SkPoint miterPt1 = miterPt[0] - *pt1;
+                                                SkScalar sqDist0 = miterPt0.dot(miterPt0);
+                                                SkScalar sqDist1 = miterPt1.dot(miterPt1);
+                                                const SkScalar rSq =
+                                                        SkScalarDiv(SkScalarMul(radius, radius),
+                                                                    sinHalfAngleSq);
+                                                const SkScalar sqRLimit =
+                                                        SkScalarMul(sqMiterLimit, rSq);
+                                                if (sqDist0 > sqRLimit || sqDist1 > sqRLimit) {
+                                                    if (sqDist1 > sqRLimit) {
+                                                        v1.setLength(SkScalarSqrt(sqRLimit));
+                                                        miterPt[1] = *pt1+v1;
+                                                    } else {
+                                                        miterPt[1] = miterPt[0];
+                                                    }
+                                                    if (sqDist0 > sqRLimit) {
+                                                        v0.setLength(SkScalarSqrt(sqRLimit));
+                                                        miterPt[0] = *pt0+v0;
+                                                    }
+                                                } else {
+                                                    miterPt[1] = miterPt[0];
+                                                }
+                                                useMiterPoint = true;
+                                            }
+                                        }
+                                        if (useMiterPoint && (miterPt[1] == miterPt[0])) {
+                                            break;
+                                        }
+                                    }
+                                    default:
+                                    case SkPaint::kBevel_Join:
+                                    {
+                                        // Note : This currently causes some overdraw where both
+                                        //        lines initially intersect. We'd need to add
+                                        //        another line intersection check here if the
+                                        //        overdraw becomes an issue instead of using the
+                                        //        current point directly.
+
+                                        // Add center point
+                                        *verts++ = pts[0]; // Use current point directly
+                                        // This idx is passed the current point so increment it
+                                        ++idx1;
+                                        // Add center triangle
+                                        *idxs++ = idx0;
+                                        *idxs++ = vCount;
+                                        *idxs++ = idx1;
+                                        vCount++;
+                                        iCount += 3;
+                                    }
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    *verts++ = l1a;
+                    *verts++ = l2a;
+                    lastPt   = verts;
+                    *verts++ = l1b;
+                    *verts++ = l2b;
+
+                    if (useMiterPoint && (idx0 >= 0) && (idx1 >= 0)) {
+                        firstPt[idx0] = miterPt[0];
+                        firstPt[idx1] = miterPt[1];
+                    }
+
+                    // 1st triangle
+                    *idxs++  = vCount+0;
+                    *idxs++  = vCount+2;
+                    *idxs++  = vCount+1;
+                    // 2nd triangle
+                    *idxs++  = vCount+1;
+                    *idxs++  = vCount+2;
+                    *idxs++  = vCount+3;
+
+                    vCount += 4;
+                    iCount += 6;
+                }
+            }
+                break;
+            case SkPath::kQuad_Verb:
+            case SkPath::kCubic_Verb:
+                GrAssert(!"Curves not supported!");
+            default:
+                // Unhandled cases
+                GrAssert(false);
+        }
+    }
+
+    if (isOpen) {
+        // Add caps
+        switch (stroke.getCap()) {
+            case SkPaint::kSquare_Cap:
+                firstPt[0] -= firstDir;
+                firstPt[1] -= firstDir;
+                lastPt [0] += dir;
+                lastPt [1] += dir;
+                break;
+            case SkPaint::kRound_Cap:
+                GrAssert(!"Round caps not supported!");
+            default: // No cap
+                break;
+        }
+    }
+
+    GrAssert(vCount <= maxVertexCount);
+    GrAssert(iCount <= maxIndexCount);
+
+    if (vCount > 0) {
+        target->drawIndexed(kTriangles_GrPrimitiveType,
+                            0,        // start vertex
+                            0,        // start index
+                            vCount,
+                            iCount);
+    }
+
+    return true;
+}
+
diff --git a/experimental/StrokePathRenderer/GrStrokePathRenderer.h b/experimental/StrokePathRenderer/GrStrokePathRenderer.h
new file mode 100644
index 0000000..9cf34d8
--- /dev/null
+++ b/experimental/StrokePathRenderer/GrStrokePathRenderer.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrPathRenderer.h"
+
+// This path renderer is made to create geometry (i.e. primitives) from the original path (before
+// the path is stroked) and render using the GPU directly rather than using any software rendering
+// step. It can be rendered in a single pass for simple cases and use multiple passes for features
+// like AA or opacity support.
+
+class GrStrokePathRenderer : public GrPathRenderer {
+
+public:
+    GrStrokePathRenderer();
+
+    virtual bool canDrawPath(const SkPath& path,
+                             const SkStrokeRec& stroke,
+                             const GrDrawTarget* target,
+                             bool antiAlias) const SK_OVERRIDE;
+
+protected:
+    virtual bool onDrawPath(const SkPath& path,
+                            const SkStrokeRec& stroke,
+                            GrDrawTarget* target,
+                            bool antiAlias) SK_OVERRIDE;
+};
diff --git a/gyp/common_variables.gypi b/gyp/common_variables.gypi
index 042340b..fc2b3a9 100644
--- a/gyp/common_variables.gypi
+++ b/gyp/common_variables.gypi
@@ -81,6 +81,7 @@
       'skia_scalar%': 'float',
       'skia_mesa%': 0,
       'skia_nv_path_rendering%': 0,
+      'skia_stroke_path_rendering%': 0,
       'skia_texture_cache_mb_limit%': 0,
       'skia_angle%': 0,
       'skia_directwrite%': 0,
@@ -101,6 +102,7 @@
     'skia_scalar%': '<(skia_scalar)',
     'skia_mesa%': '<(skia_mesa)',
     'skia_nv_path_rendering%': '<(skia_nv_path_rendering)',
+    'skia_stroke_path_rendering%': '<(skia_stroke_path_rendering)',
     'skia_texture_cache_mb_limit%': '<(skia_texture_cache_mb_limit)',
     'skia_angle%': '<(skia_angle)',
     'skia_arch_width%': '<(skia_arch_width)',
diff --git a/gyp/gpu.gyp b/gyp/gpu.gyp
index d5223e4..474bdc1 100644
--- a/gyp/gpu.gyp
+++ b/gyp/gpu.gyp
@@ -205,6 +205,15 @@
             'GR_GL_USE_NV_PATH_RENDERING=1',
           ],
         }],
+        [ 'skia_stroke_path_rendering', {
+          'sources': [
+            '../experimental/StrokePathRenderer/GrStrokePathRenderer.h',
+            '../experimental/StrokePathRenderer/GrStrokePathRenderer.cpp',
+          ],
+          'defines': [
+            'GR_STROKE_PATH_RENDERING=1',
+          ],
+        }],
         [ 'skia_os == "linux"', {
           'sources!': [
             '../src/gpu/gl/GrGLDefaultInterface_none.cpp',
diff --git a/include/gpu/GrConfig.h b/include/gpu/GrConfig.h
index 766212d..928cc56 100644
--- a/include/gpu/GrConfig.h
+++ b/include/gpu/GrConfig.h
@@ -384,6 +384,14 @@
     #define GR_USE_NEW_GL_SHADER_SOURCE_SIGNATURE 0
 #endif
 
+/**
+ * GR_STROKE_PATH_RENDERING controls whether or not the GrStrokePathRenderer can be selected
+ * as a path renderer. GrStrokePathRenderer is currently an experimental path renderer.
+ */
+#if !defined(GR_STROKE_PATH_RENDERING)
+    #define GR_STROKE_PATH_RENDERING                 0
+#endif
+
 ///////////////////////////////////////////////////////////////////////////////
 // tail section:
 //
diff --git a/src/gpu/GrAddPathRenderers_default.cpp b/src/gpu/GrAddPathRenderers_default.cpp
index 69be15a..618bd31 100644
--- a/src/gpu/GrAddPathRenderers_default.cpp
+++ b/src/gpu/GrAddPathRenderers_default.cpp
@@ -10,8 +10,14 @@
 #include "GrStencilAndCoverPathRenderer.h"
 #include "GrAAHairLinePathRenderer.h"
 #include "GrAAConvexPathRenderer.h"
+#if GR_STROKE_PATH_RENDERING
+#include "../../experimental/StrokePathRenderer/GrStrokePathRenderer.h"
+#endif
 
 void GrPathRenderer::AddPathRenderers(GrContext* ctx, GrPathRendererChain* chain) {
+#if GR_STROKE_PATH_RENDERING
+    chain->addPathRenderer(SkNEW(GrStrokePathRenderer))->unref();
+#endif
     if (GrPathRenderer* pr = GrStencilAndCoverPathRenderer::Create(ctx)) {
         chain->addPathRenderer(pr)->unref();
     }
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 12fbeb6..5961aaf 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -1063,24 +1063,15 @@
        return;
     }
 
-    const SkPath* pathPtr = &path;
-    SkPath tmpPath;
-    SkStrokeRec strokeRec(stroke);
-    if (!strokeRec.isHairlineStyle()) {
-        if (strokeRec.applyToPath(&tmpPath, *pathPtr)) {
-            pathPtr = &tmpPath;
-            strokeRec.setFillStyle();
-        }
-    }
-
     SkRect ovalRect;
-    if (!pathPtr->isInverseFillType() && pathPtr->isOval(&ovalRect)) {
-        SkScalar width = strokeRec.isHairlineStyle() ? 0 : -SK_Scalar1;
+    if ((stroke.isHairlineStyle() || stroke.isFillStyle()) && !path.isInverseFillType() &&
+        path.isOval(&ovalRect)) {
+        SkScalar width = stroke.isHairlineStyle() ? 0 : -SK_Scalar1;
         this->drawOval(paint, ovalRect, width);
         return;
     }
 
-    this->internalDrawPath(paint, *pathPtr, strokeRec);
+    this->internalDrawPath(paint, path, stroke);
 }
 
 void GrContext::internalDrawPath(const GrPaint& paint, const SkPath& path, const SkStrokeRec& stroke) {
@@ -1109,7 +1100,23 @@
     GrPathRendererChain::DrawType type = prAA ? GrPathRendererChain::kColorAntiAlias_DrawType :
                                                 GrPathRendererChain::kColor_DrawType;
 
-    GrPathRenderer* pr = this->getPathRenderer(path, stroke, target, true, type);
+    const SkPath* pathPtr = &path;
+    SkPath tmpPath;
+    SkStrokeRec strokeRec(stroke);
+
+    // Try a 1st time without stroking the path and without allowing the SW renderer
+    GrPathRenderer* pr = this->getPathRenderer(*pathPtr, strokeRec, target, false, type);
+
+    if ((NULL == pr) && !strokeRec.isHairlineStyle()) {
+        // It didn't work the 1st time, so try again with the stroked path
+        if (strokeRec.applyToPath(&tmpPath, *pathPtr)) {
+            pathPtr = &tmpPath;
+            strokeRec.setFillStyle();
+        }
+        // This time, allow SW renderer
+        pr = this->getPathRenderer(*pathPtr, strokeRec, target, true, type);
+    }
+
     if (NULL == pr) {
 #if GR_DEBUG
         GrPrintf("Unable to find path renderer compatible with path.\n");
@@ -1117,7 +1124,7 @@
         return;
     }
 
-    pr->drawPath(path, stroke, target, prAA);
+    pr->drawPath(*pathPtr, strokeRec, target, prAA);
 }
 
 ////////////////////////////////////////////////////////////////////////////////