Tessellating GPU path renderer.
authorsenorblanco <senorblanco@chromium.org>
Thu, 26 Feb 2015 14:58:17 +0000 (06:58 -0800)
committerCommit bot <commit-bot@chromium.org>
Thu, 26 Feb 2015 14:58:17 +0000 (06:58 -0800)
This path renderer converts paths to linear contours, resolves intersections via Bentley-Ottman, implements a trapezoidal decomposition a la Fournier and Montuno to produce triangles, and renders those with a single draw call. It does not currently do antialiasing, so it must be used in conjunction with multisampling.

A fair amount of the code is to handle floating point edge cases in intersections. Rather than perform exact computations (which would require arbitrary precision arithmetic), we reconnect the mesh to reflect the intersection points. For example, intersections can occur above the current vertex, and force edges to be merged into the current vertex, requiring a restart of the intersections. Splitting edges for intersections can also force them to merge with formerly-distinct edges in the same polygon, or to violate the ordering of the active edge list, or the active edge state of split edges.

BUG=skia:

Review URL: https://codereview.chromium.org/855513004

gm/concavepaths.cpp [new file with mode: 0644]
gyp/gmslides.gypi
gyp/gpu.gypi
gyp/tests.gypi
src/gpu/GrAddPathRenderers_default.cpp
src/gpu/GrTessellatingPathRenderer.cpp [new file with mode: 0644]
src/gpu/GrTessellatingPathRenderer.h [new file with mode: 0644]
tests/TessellatingPathRendererTests.cpp [new file with mode: 0644]

diff --git a/gm/concavepaths.cpp b/gm/concavepaths.cpp
new file mode 100644 (file)
index 0000000..bc1b533
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkCanvas.h"
+
+#define WIDTH 400
+#define HEIGHT 600
+
+namespace {
+// Concave test
+void test_concave(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->translate(0, 0);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(30), SkIntToScalar(30));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    canvas->drawPath(path, paint);
+}
+
+// Reverse concave test
+void test_reverse_concave(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(100, 0);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(30), SkIntToScalar(30));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Bowtie (intersection)
+void test_bowtie(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(200, 0);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// "fake" bowtie (concave, but no intersection)
+void test_fake_bowtie(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(300, 0);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(40));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(60));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Fish test (intersection/concave)
+void test_fish(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(0, 100);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(70), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(0), SkIntToScalar(50));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Collinear edges
+void test_collinear_edges(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(100, 100);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(80));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Square polygon with a square hole.
+void test_hole(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(200, 100);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.moveTo(SkIntToScalar(30), SkIntToScalar(30));
+    path.lineTo(SkIntToScalar(30), SkIntToScalar(70));
+    path.lineTo(SkIntToScalar(70), SkIntToScalar(70));
+    path.lineTo(SkIntToScalar(70), SkIntToScalar(30));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Star test (self-intersecting)
+void test_star(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(300, 100);
+    path.moveTo(30, 20);
+    path.lineTo(50, 80);
+    path.lineTo(70, 20);
+    path.lineTo(20, 57);
+    path.lineTo(80, 57);
+    path.close();
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Stairstep with repeated vert (intersection)
+void test_stairstep(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(0, 200);
+    path.moveTo(SkIntToScalar(50), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+void test_stairstep2(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(100, 200);
+    path.moveTo(20, 60);
+    path.lineTo(35, 80);
+    path.lineTo(50, 60);
+    path.lineTo(65, 80);
+    path.lineTo(80, 60);
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Overlapping segments
+void test_overlapping(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(200, 200);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(30));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Monotone test 1 (point in the middle)
+void test_monotone_1(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(0, 300);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.quadTo(SkIntToScalar(20), SkIntToScalar(50),
+                SkIntToScalar(80), SkIntToScalar(50));
+    path.quadTo(SkIntToScalar(20), SkIntToScalar(50),
+                SkIntToScalar(20), SkIntToScalar(80));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Monotone test 2 (point at the top)
+void test_monotone_2(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(100, 300);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(30));
+    path.quadTo(SkIntToScalar(20), SkIntToScalar(20),
+                SkIntToScalar(20), SkIntToScalar(80));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Monotone test 3 (point at the bottom)
+void test_monotone_3(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(200, 300);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(70));
+    path.quadTo(SkIntToScalar(20), SkIntToScalar(80),
+                SkIntToScalar(20), SkIntToScalar(20));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Monotone test 4 (merging of two monotones)
+void test_monotone_4(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(300, 300);
+    path.moveTo(80, 25);
+    path.lineTo(50, 39);
+    path.lineTo(20, 25);
+    path.lineTo(40, 45);
+    path.lineTo(70, 50);
+    path.lineTo(80, 80);
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Monotone test 5 (aborted merging of two monotones)
+void test_monotone_5(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(0, 400);
+    path.moveTo(50, 20);
+    path.lineTo(80, 80);
+    path.lineTo(50, 50);
+    path.lineTo(20, 80);
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+// Degenerate intersection test
+void test_degenerate(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(100, 400);
+    path.moveTo(50, 20);
+    path.lineTo(70, 30);
+    path.lineTo(20, 50);
+    path.moveTo(50, 20);
+    path.lineTo(80, 80);
+    path.lineTo(50, 80);
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+// Two triangles with a coincident edge.
+void test_coincident_edge(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(200, 400);
+
+    path.moveTo(80, 20);
+    path.lineTo(80, 80);
+    path.lineTo(20, 80);
+
+    path.moveTo(20, 20);
+    path.lineTo(80, 80);
+    path.lineTo(20, 80);
+
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+// Bowtie with a coincident triangle (one triangle vertex coincident with the
+// bowtie's intersection).
+void test_bowtie_coincident_triangle(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(300, 400);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.moveTo(SkIntToScalar(50), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(80));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+// Coincident edges (big ones first, coincident vert on top).
+void test_coincident_edges_1(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(0, 500);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(50));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+// Coincident edges (small ones first, coincident vert on top).
+void test_coincident_edges_2(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(100, 500);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(50));
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+// Coincident edges (small ones first, coincident vert on bottom).
+void test_coincident_edges_3(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(200, 500);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(50));
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+// Coincident edges (big ones first, coincident vert on bottom).
+void test_coincident_edges_4(SkCanvas* canvas, const SkPaint& paint) {
+    SkPath path;
+    canvas->save();
+    canvas->translate(300, 500);
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
+    path.moveTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(50), SkIntToScalar(50));
+    canvas->drawPath(path, paint);
+    canvas->restore();
+}
+
+};
+
+class ConcavePathsGM : public skiagm::GM {
+public:
+    ConcavePathsGM() {}
+
+protected:
+    SkString onShortName() SK_OVERRIDE {
+        return SkString("concavepaths");
+    }
+
+    SkISize onISize() SK_OVERRIDE {
+        return SkISize::Make(WIDTH, HEIGHT);
+    }
+
+    void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+        SkPaint paint;
+
+        paint.setAntiAlias(true);
+        paint.setStyle(SkPaint::kFill_Style);
+
+        test_concave(canvas, paint);
+        test_reverse_concave(canvas, paint);
+        test_bowtie(canvas, paint);
+        test_fake_bowtie(canvas, paint);
+        test_fish(canvas, paint);
+        test_collinear_edges(canvas, paint);
+        test_hole(canvas, paint);
+        test_star(canvas, paint);
+        test_stairstep(canvas, paint);
+        test_stairstep2(canvas, paint);
+        test_overlapping(canvas, paint);
+        test_monotone_1(canvas, paint);
+        test_monotone_2(canvas, paint);
+        test_monotone_3(canvas, paint);
+        test_monotone_4(canvas, paint);
+        test_monotone_5(canvas, paint);
+        test_degenerate(canvas, paint);
+        test_coincident_edge(canvas, paint);
+        test_bowtie_coincident_triangle(canvas, paint);
+        test_coincident_edges_1(canvas, paint);
+        test_coincident_edges_2(canvas, paint);
+        test_coincident_edges_3(canvas, paint);
+        test_coincident_edges_4(canvas, paint);
+    }
+
+private:
+    typedef skiagm::GM INHERITED;
+};
+
+static skiagm::GM* F0(void*) { return new ConcavePathsGM; }
+static skiagm::GMRegistry R0(F0);
index 43189ba..739f8fe 100644 (file)
@@ -54,6 +54,7 @@
         '../gm/colortype.cpp',
         '../gm/colortypexfermode.cpp',
         '../gm/colorwheel.cpp',
+        '../gm/concavepaths.cpp',
         '../gm/complexclip.cpp',
         '../gm/complexclip2.cpp',
         '../gm/complexclip3.cpp',
index 79c60c1..270971d 100644 (file)
       '<(skia_src_path)/gpu/GrTraceMarker.cpp',
       '<(skia_src_path)/gpu/GrTraceMarker.h',
       '<(skia_src_path)/gpu/GrTracing.h',
+      '<(skia_src_path)/gpu/GrTessellatingPathRenderer.cpp',
+      '<(skia_src_path)/gpu/GrTessellatingPathRenderer.h',
       '<(skia_src_path)/gpu/GrSWMaskHelper.cpp',
       '<(skia_src_path)/gpu/GrSWMaskHelper.h',
       '<(skia_src_path)/gpu/GrSoftwarePathRenderer.cpp',
index 3267360..3acb825 100644 (file)
     '../tests/StrokerTest.cpp',
     '../tests/SurfaceTest.cpp',
     '../tests/SVGDeviceTest.cpp',
+    '../tests/TessellatingPathRendererTests.cpp',
     '../tests/TArrayTest.cpp',
     '../tests/TDPQueueTest.cpp',
     '../tests/Time.cpp',
index 2c5058f..f5d6934 100644 (file)
@@ -11,6 +11,7 @@
 #include "GrAAHairLinePathRenderer.h"
 #include "GrAAConvexPathRenderer.h"
 #include "GrAADistanceFieldPathRenderer.h"
+#include "GrTessellatingPathRenderer.h"
 #if GR_STROKE_PATH_RENDERING
 #include "../../experimental/StrokePathRenderer/GrStrokePathRenderer.h"
 #endif
 #include "../../experimental/AndroidPathRenderer/GrAndroidPathRenderer.h"
 #endif
 
+#ifndef GR_TESSELLATING_PATH_RENDERING
+#define GR_TESSELLATING_PATH_RENDERING 0
+#endif
+
 void GrPathRenderer::AddPathRenderers(GrContext* ctx, GrPathRendererChain* chain) {
 #if GR_STROKE_PATH_RENDERING
     chain->addPathRenderer(SkNEW(GrStrokePathRenderer))->unref();
@@ -25,6 +30,9 @@ void GrPathRenderer::AddPathRenderers(GrContext* ctx, GrPathRendererChain* chain
 #if GR_ANDROID_PATH_RENDERING
     chain->addPathRenderer(SkNEW(GrAndroidPathRenderer))->unref();
 #endif
+#if GR_TESSELLATING_PATH_RENDERING
+    chain->addPathRenderer(new GrTessellatingPathRenderer)->unref();
+#endif
     if (GrPathRenderer* pr = GrStencilAndCoverPathRenderer::Create(ctx)) {
         chain->addPathRenderer(pr)->unref();
     }
diff --git a/src/gpu/GrTessellatingPathRenderer.cpp b/src/gpu/GrTessellatingPathRenderer.cpp
new file mode 100644 (file)
index 0000000..3d7d89b
--- /dev/null
@@ -0,0 +1,1508 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrTessellatingPathRenderer.h"
+
+#include "GrDefaultGeoProcFactory.h"
+#include "GrPathUtils.h"
+#include "SkChunkAlloc.h"
+#include "SkGeometry.h"
+
+#include <stdio.h>
+
+/*
+ * This path renderer tessellates the path into triangles, uploads the triangles to a
+ * vertex buffer, and renders them with a single draw call. It does not currently do
+ * antialiasing, so it must be used in conjunction with multisampling.
+ *
+ * There are six stages to the algorithm:
+ *
+ * 1) Linearize the path contours into piecewise linear segments (path_to_contours()).
+ * 2) Build a mesh of edges connecting the vertices (build_edges()).
+ * 3) Sort the vertices in Y (and secondarily in X) (merge_sort()).
+ * 4) Simplify the mesh by inserting new vertices at intersecting edges (simplify()).
+ * 5) Tessellate the simplified mesh into monotone polygons (tessellate()).
+ * 6) Triangulate the monotone polygons directly into a vertex buffer (polys_to_triangles()).
+ *
+ * The vertex sorting in step (3) is a merge sort, since it plays well with the linked list
+ * of vertices (and the necessity of inserting new vertices on intersection).
+ *
+ * Stages (4) and (5) use an active edge list, which a list of all edges for which the
+ * sweep line has crossed the top vertex, but not the bottom vertex.  It's sorted
+ * left-to-right based on the point where both edges are active (when both top vertices
+ * have been seen, so the "lower" top vertex of the two). If the top vertices are equal
+ * (shared), it's sorted based on the last point where both edges are active, so the
+ * "upper" bottom vertex.
+ *
+ * The most complex step is the simplification (4). It's based on the Bentley-Ottman
+ * line-sweep algorithm, but due to floating point inaccuracy, the intersection points are
+ * not exact and may violate the mesh topology or active edge list ordering. We
+ * accommodate this by adjusting the topology of the mesh and AEL to match the intersection
+ * points. This occurs in three ways:
+ *
+ * A) Intersections may cause a shortened edge to no longer be ordered with respect to its
+ *    neighbouring edges at the top or bottom vertex. This is handled by merging the
+ *    edges (merge_collinear_edges()).
+ * B) Intersections may cause an edge to violate the left-to-right ordering of the
+ *    active edge list. This is handled by splitting the neighbour edge on the
+ *    intersected vertex (cleanup_active_edges()).
+ * C) Shortening an edge may cause an active edge to become inactive or an inactive edge
+ *    to become active. This is handled by removing or inserting the edge in the active
+ *    edge list (fix_active_state()).
+ *
+ * The tessellation steps (5) and (6) are based on "Triangulating Simple Polygons and
+ * Equivalent Problems" (Fournier and Montuno); also a line-sweep algorithm. Note that it
+ * currently uses a linked list for the active edge list, rather than a 2-3 tree as the
+ * paper describes. The 2-3 tree gives O(lg N) lookups, but insertion and removal also
+ * become O(lg N). In all the test cases, it was found that the cost of frequent O(lg N)
+ * insertions and removals was greater than the cost of infrequent O(N) lookups with the
+ * linked list implementation. With the latter, all removals are O(1), and most insertions
+ * are O(1), since we know the adjacent edge in the active edge list based on the topology.
+ * Only type 2 vertices (see paper) require the O(N) lookups, and these are much less
+ * frequent. There may be other data structures worth investigating, however.
+ *
+ * Note that there is a compile-time flag (SWEEP_IN_X) which changes the orientation of the
+ * line sweep algorithms. When SWEEP_IN_X is unset, we sort vertices based on increasing
+ * Y coordinate, and secondarily by increasing X coordinate. When SWEEP_IN_X is set, we sort by
+ * increasing X coordinate, but secondarily by *decreasing* Y coordinate. This is so that the
+ * "left" and "right" orientation in the code remains correct (edges to the left are increasing
+ * in Y; edges to the right are decreasing in Y). That is, the setting rotates 90 degrees
+ * counterclockwise, rather that transposing.
+ *
+ * The choice is arbitrary, but most test cases are wider than they are tall, so the
+ * default is to sweep in X. In the future, we may want to make this a runtime parameter
+ * and base it on the aspect ratio of the clip bounds.
+ */
+#define LOGGING_ENABLED 0
+#define WIREFRAME 0
+#define SWEEP_IN_X 1
+
+#if LOGGING_ENABLED
+#define LOG printf
+#else
+#define LOG(...)
+#endif
+
+#define ALLOC_NEW(Type, args, alloc) \
+    SkNEW_PLACEMENT_ARGS(alloc.allocThrow(sizeof(Type)), Type, args)
+
+namespace {
+
+struct Vertex;
+struct Edge;
+struct Poly;
+
+template <class T, T* T::*Prev, T* T::*Next>
+void insert(T* t, T* prev, T* next, T** head, T** tail) {
+    t->*Prev = prev;
+    t->*Next = next;
+    if (prev) {
+        prev->*Next = t;
+    } else if (head) {
+        *head = t;
+    }
+    if (next) {
+        next->*Prev = t;
+    } else if (tail) {
+        *tail = t;
+    }
+}
+
+template <class T, T* T::*Prev, T* T::*Next>
+void remove(T* t, T** head, T** tail) {
+    if (t->*Prev) {
+        t->*Prev->*Next = t->*Next;
+    } else if (head) {
+        *head = t->*Next;
+    }
+    if (t->*Next) {
+        t->*Next->*Prev = t->*Prev;
+    } else if (tail) {
+        *tail = t->*Prev;
+    }
+    t->*Prev = t->*Next = NULL;
+}
+
+/**
+ * Vertices are used in three ways: first, the path contours are converted into a
+ * circularly-linked list of Vertices for each contour. After edge construction, the same Vertices
+ * are re-ordered by the merge sort according to the sweep_lt comparator (usually, increasing
+ * in Y) using the same fPrev/fNext pointers that were used for the contours, to avoid
+ * reallocation. Finally, MonotonePolys are built containing a circularly-linked list of
+ * Vertices. (Currently, those Vertices are newly-allocated for the MonotonePolys, since
+ * an individual Vertex from the path mesh may belong to multiple
+ * MonotonePolys, so the original Vertices cannot be re-used.
+ */
+
+struct Vertex {
+  Vertex(const SkPoint& point)
+    : fPoint(point), fPrev(NULL), fNext(NULL)
+    , fFirstEdgeAbove(NULL), fLastEdgeAbove(NULL)
+    , fFirstEdgeBelow(NULL), fLastEdgeBelow(NULL)
+    , fProcessed(false)
+#if LOGGING_ENABLED
+    , fID (-1.0f)
+#endif
+    {}
+    SkPoint fPoint;           // Vertex position
+    Vertex* fPrev;            // Linked list of contours, then Y-sorted vertices.
+    Vertex* fNext;            // "
+    Edge*   fFirstEdgeAbove;  // Linked list of edges above this vertex.
+    Edge*   fLastEdgeAbove;   // "
+    Edge*   fFirstEdgeBelow;  // Linked list of edges below this vertex.
+    Edge*   fLastEdgeBelow;   // "
+    bool    fProcessed;       // Has this vertex been seen in simplify()?
+#if LOGGING_ENABLED
+    float   fID;              // Identifier used for logging.
+#endif
+};
+
+/***************************************************************************************/
+
+bool sweep_lt(const SkPoint& a, const SkPoint& b) {
+#if SWEEP_IN_X
+    return a.fX == b.fX ? a.fY > b.fY : a.fX < b.fX;
+#else
+    return a.fY == b.fY ? a.fX < b.fX : a.fY < b.fY;
+#endif
+}
+
+bool sweep_gt(const SkPoint& a, const SkPoint& b) {
+#if SWEEP_IN_X
+    return a.fX == b.fX ? a.fY < b.fY : a.fX > b.fX;
+#else
+    return a.fY == b.fY ? a.fX > b.fX : a.fY > b.fY;
+#endif
+}
+
+inline void* emit_vertex(Vertex* v, void* data) {
+    SkPoint* d = static_cast<SkPoint*>(data);
+    *d++ = v->fPoint;
+    return d;
+}
+
+void* emit_triangle(Vertex* v0, Vertex* v1, Vertex* v2, void* data) {
+#if WIREFRAME
+    data = emit_vertex(v0, data);
+    data = emit_vertex(v1, data);
+    data = emit_vertex(v1, data);
+    data = emit_vertex(v2, data);
+    data = emit_vertex(v2, data);
+    data = emit_vertex(v0, data);
+#else
+    data = emit_vertex(v0, data);
+    data = emit_vertex(v1, data);
+    data = emit_vertex(v2, data);
+#endif
+    return data;
+}
+
+/**
+ * An Edge joins a top Vertex to a bottom Vertex. Edge ordering for the list of "edges above" and
+ * "edge below" a vertex as well as for the active edge list is handled by isLeftOf()/isRightOf().
+ * Note that an Edge will give occasionally dist() != 0 for its own endpoints (because floating
+ * point). For speed, that case is only tested by the callers which require it (e.g.,
+ * cleanup_active_edges()). Edges also handle checking for intersection with other edges.
+ * Currently, this converts the edges to the parametric form, in order to avoid doing a division
+ * until an intersection has been confirmed. This is slightly slower in the "found" case, but
+ * a lot faster in the "not found" case.
+ *
+ * The coefficients of the line equation stored in double precision to avoid catastrphic
+ * cancellation in the isLeftOf() and isRightOf() checks. Using doubles ensures that the result is
+ * correct in float, since it's a polynomial of degree 2. The intersect() function, being
+ * degree 5, is still subject to catastrophic cancellation. We deal with that by assuming its
+ * output may be incorrect, and adjusting the mesh topology to match (see comment at the top of
+ * this file).
+ */
+
+struct Edge {
+    Edge(Vertex* top, Vertex* bottom, int winding)
+        : fWinding(winding)
+        , fTop(top)
+        , fBottom(bottom)
+        , fLeft(NULL)
+        , fRight(NULL)
+        , fPrevEdgeAbove(NULL)
+        , fNextEdgeAbove(NULL)
+        , fPrevEdgeBelow(NULL)
+        , fNextEdgeBelow(NULL)
+        , fLeftPoly(NULL)
+        , fRightPoly(NULL) {
+            recompute();
+        }
+    int      fWinding;          // 1 == edge goes downward; -1 = edge goes upward.
+    Vertex*  fTop;              // The top vertex in vertex-sort-order (sweep_lt).
+    Vertex*  fBottom;           // The bottom vertex in vertex-sort-order.
+    Edge*    fLeft;             // The linked list of edges in the active edge list.
+    Edge*    fRight;            // "
+    Edge*    fPrevEdgeAbove;    // The linked list of edges in the bottom Vertex's "edges above".
+    Edge*    fNextEdgeAbove;    // "
+    Edge*    fPrevEdgeBelow;    // The linked list of edges in the top Vertex's "edges below".
+    Edge*    fNextEdgeBelow;    // "
+    Poly*    fLeftPoly;         // The Poly to the left of this edge, if any.
+    Poly*    fRightPoly;        // The Poly to the right of this edge, if any.
+    double   fDX;               // The line equation for this edge, in implicit form.
+    double   fDY;               // fDY * x + fDX * y + fC = 0, for point (x, y) on the line.
+    double   fC;
+    double dist(const SkPoint& p) const {
+        return fDY * p.fX - fDX * p.fY + fC;
+    }
+    bool isRightOf(Vertex* v) const {
+        return dist(v->fPoint) < 0.0;
+    }
+    bool isLeftOf(Vertex* v) const {
+        return dist(v->fPoint) > 0.0;
+    }
+    void recompute() {
+        fDX = static_cast<double>(fBottom->fPoint.fX) - fTop->fPoint.fX;
+        fDY = static_cast<double>(fBottom->fPoint.fY) - fTop->fPoint.fY;
+        fC = static_cast<double>(fTop->fPoint.fY) * fBottom->fPoint.fX -
+             static_cast<double>(fTop->fPoint.fX) * fBottom->fPoint.fY;
+    }
+    bool intersect(const Edge& other, SkPoint* p) {
+        LOG("intersecting %g -> %g with %g -> %g\n",
+               fTop->fID, fBottom->fID,
+               other.fTop->fID, other.fBottom->fID);
+        if (fTop == other.fTop || fBottom == other.fBottom) {
+            return false;
+        }
+        double denom = fDX * other.fDY - fDY * other.fDX;
+        if (denom == 0.0) {
+            return false;
+        }
+        double dx = static_cast<double>(fTop->fPoint.fX) - other.fTop->fPoint.fX;
+        double dy = static_cast<double>(fTop->fPoint.fY) - other.fTop->fPoint.fY;
+        double sNumer = dy * other.fDX - dx * other.fDY;
+        double tNumer = dy * fDX - dx * fDY;
+        // If (sNumer / denom) or (tNumer / denom) is not in [0..1], exit early.
+        // This saves us doing the divide below unless absolutely necessary.
+        if (denom > 0.0 ? (sNumer < 0.0 || sNumer > denom || tNumer < 0.0 || tNumer > denom)
+                        : (sNumer > 0.0 || sNumer < denom || tNumer > 0.0 || tNumer < denom)) {
+            return false;
+        }
+        double s = sNumer / denom;
+        SkASSERT(s >= 0.0 && s <= 1.0);
+        p->fX = SkDoubleToScalar(fTop->fPoint.fX + s * fDX);
+        p->fY = SkDoubleToScalar(fTop->fPoint.fY + s * fDY);
+        return true;
+    }
+    bool isActive(Edge** activeEdges) const {
+        return activeEdges && (fLeft || fRight || *activeEdges == this);
+    }
+};
+
+/***************************************************************************************/
+
+struct Poly {
+    Poly(int winding)
+        : fWinding(winding)
+        , fHead(NULL)
+        , fTail(NULL)
+        , fActive(NULL)
+        , fNext(NULL)
+        , fPartner(NULL)
+        , fCount(0)
+    {
+#if LOGGING_ENABLED
+        static int gID = 0;
+        fID = gID++;
+        LOG("*** created Poly %d\n", fID);
+#endif
+    }
+    typedef enum { kNeither_Side, kLeft_Side, kRight_Side } Side;
+    struct MonotonePoly {
+        MonotonePoly()
+            : fSide(kNeither_Side)
+            , fHead(NULL)
+            , fTail(NULL)
+            , fPrev(NULL)
+            , fNext(NULL) {}
+        Side          fSide;
+        Vertex*       fHead;
+        Vertex*       fTail;
+        MonotonePoly* fPrev;
+        MonotonePoly* fNext;
+        bool addVertex(Vertex* v, Side side, SkChunkAlloc& alloc) {
+            Vertex* newV = ALLOC_NEW(Vertex, (v->fPoint), alloc);
+            bool done = false;
+            if (fSide == kNeither_Side) {
+                fSide = side;
+            } else {
+                done = side != fSide;
+            }
+            if (fHead == NULL) {
+                fHead = fTail = newV;
+            } else if (fSide == kRight_Side) {
+                newV->fPrev = fTail;
+                fTail->fNext = newV;
+                fTail = newV;
+            } else {
+                newV->fNext = fHead;
+                fHead->fPrev = newV;
+                fHead = newV;
+            }
+            return done;
+        }
+
+        void* emit(void* data) {
+            Vertex* first = fHead;
+            Vertex* v = first->fNext;
+            while (v != fTail) {
+                SkASSERT(v && v->fPrev && v->fNext);
+#ifdef SK_DEBUG
+                validate();
+#endif
+                Vertex* prev = v->fPrev;
+                Vertex* curr = v;
+                Vertex* next = v->fNext;
+                double ax = static_cast<double>(curr->fPoint.fX) - prev->fPoint.fX;
+                double ay = static_cast<double>(curr->fPoint.fY) - prev->fPoint.fY;
+                double bx = static_cast<double>(next->fPoint.fX) - curr->fPoint.fX;
+                double by = static_cast<double>(next->fPoint.fY) - curr->fPoint.fY;
+                if (ax * by - ay * bx >= 0.0) {
+                    data = emit_triangle(prev, curr, next, data);
+                    v->fPrev->fNext = v->fNext;
+                    v->fNext->fPrev = v->fPrev;
+                    if (v->fPrev == first) {
+                        v = v->fNext;
+                    } else {
+                        v = v->fPrev;
+                    }
+                } else {
+                    v = v->fNext;
+                    SkASSERT(v != fTail);
+                }
+            }
+            return data;
+        }
+
+#ifdef SK_DEBUG
+        void validate() {
+            int winding = sweep_lt(fHead->fPoint, fTail->fPoint) ? 1 : -1;
+            Vertex* top = winding < 0 ? fTail : fHead;
+            Vertex* bottom = winding < 0 ? fHead : fTail;
+            Edge e(top, bottom, winding);
+            for (Vertex* v = fHead->fNext; v != fTail; v = v->fNext) {
+                if (fSide == kRight_Side) {
+                    SkASSERT(!e.isRightOf(v));
+                } else if (fSide == Poly::kLeft_Side) {
+                    SkASSERT(!e.isLeftOf(v));
+                }
+            }
+        }
+#endif
+    };
+    Poly* addVertex(Vertex* v, Side side, SkChunkAlloc& alloc) {
+        LOG("addVertex() to %d at %g (%g, %g), %s side\n", fID, v->fID, v->fPoint.fX, v->fPoint.fY,
+               side == kLeft_Side ? "left" : side == kRight_Side ? "right" : "neither");
+        Poly* partner = fPartner;
+        Poly* poly = this;
+        if (partner) {
+            fPartner = partner->fPartner = NULL;
+        }
+        if (!fActive) {
+            fActive = ALLOC_NEW(MonotonePoly, (), alloc);
+        }
+        if (fActive->addVertex(v, side, alloc)) {
+#ifdef SK_DEBUG
+            fActive->validate();
+#endif
+            if (fTail) {
+                fActive->fPrev = fTail;
+                fTail->fNext = fActive;
+                fTail = fActive;
+            } else {
+                fHead = fTail = fActive;
+            }
+            if (partner) {
+                partner->addVertex(v, side, alloc);
+                poly = partner;
+            } else {
+                Vertex* prev = fActive->fSide == Poly::kLeft_Side ?
+                               fActive->fHead->fNext : fActive->fTail->fPrev;
+                fActive = ALLOC_NEW(MonotonePoly, , alloc);
+                fActive->addVertex(prev, Poly::kNeither_Side, alloc);
+                fActive->addVertex(v, side, alloc);
+            }
+        }
+        fCount++;
+        return poly;
+    }
+    void end(Vertex* v, SkChunkAlloc& alloc) {
+        LOG("end() %d at %g, %g\n", fID, v->fPoint.fX, v->fPoint.fY);
+        if (fPartner) {
+            fPartner = fPartner->fPartner = NULL;
+        }
+        addVertex(v, fActive->fSide == kLeft_Side ? kRight_Side : kLeft_Side, alloc);
+    }
+    void* emit(void *data) {
+        if (fCount < 3) {
+            return data;
+        }
+        LOG("emit() %d, size %d\n", fID, fCount);
+        for (MonotonePoly* m = fHead; m != NULL; m = m->fNext) {
+            data = m->emit(data);
+        }
+        return data;
+    }
+    int fWinding;
+    MonotonePoly* fHead;
+    MonotonePoly* fTail;
+    MonotonePoly* fActive;
+    Poly* fNext;
+    Poly* fPartner;
+    int fCount;
+#if LOGGING_ENABLED
+    int fID;
+#endif
+};
+
+/***************************************************************************************/
+
+bool coincident(const SkPoint& a, const SkPoint& b) {
+    return a == b;
+}
+
+Poly* new_poly(Poly** head, Vertex* v, int winding, SkChunkAlloc& alloc) {
+    Poly* poly = ALLOC_NEW(Poly, (winding), alloc);
+    poly->addVertex(v, Poly::kNeither_Side, alloc);
+    poly->fNext = *head;
+    *head = poly;
+    return poly;
+}
+
+#ifdef SK_DEBUG
+void validate_edges(Edge* head) {
+    for (Edge* e = head; e != NULL; e = e->fRight) {
+        SkASSERT(e->fTop != e->fBottom);
+        if (e->fLeft) {
+            SkASSERT(e->fLeft->fRight == e);
+            if (sweep_gt(e->fTop->fPoint, e->fLeft->fTop->fPoint)) {
+                SkASSERT(e->fLeft->isLeftOf(e->fTop));
+            }
+            if (sweep_lt(e->fBottom->fPoint, e->fLeft->fBottom->fPoint)) {
+                SkASSERT(e->fLeft->isLeftOf(e->fBottom));
+            }
+        } else {
+            SkASSERT(e == head);
+        }
+        if (e->fRight) {
+            SkASSERT(e->fRight->fLeft == e);
+            if (sweep_gt(e->fTop->fPoint, e->fRight->fTop->fPoint)) {
+                SkASSERT(e->fRight->isRightOf(e->fTop));
+            }
+            if (sweep_lt(e->fBottom->fPoint, e->fRight->fBottom->fPoint)) {
+                SkASSERT(e->fRight->isRightOf(e->fBottom));
+            }
+        }
+    }
+}
+
+void validate_connectivity(Vertex* v) {
+    for (Edge* e = v->fFirstEdgeAbove; e != NULL; e = e->fNextEdgeAbove) {
+        SkASSERT(e->fBottom == v);
+        if (e->fPrevEdgeAbove) {
+            SkASSERT(e->fPrevEdgeAbove->fNextEdgeAbove == e);
+            SkASSERT(e->fPrevEdgeAbove->isLeftOf(e->fTop));
+        } else {
+            SkASSERT(e == v->fFirstEdgeAbove);
+        }
+        if (e->fNextEdgeAbove) {
+            SkASSERT(e->fNextEdgeAbove->fPrevEdgeAbove == e);
+            SkASSERT(e->fNextEdgeAbove->isRightOf(e->fTop));
+        } else {
+            SkASSERT(e == v->fLastEdgeAbove);
+        }
+    }
+    for (Edge* e = v->fFirstEdgeBelow; e != NULL; e = e->fNextEdgeBelow) {
+        SkASSERT(e->fTop == v);
+        if (e->fPrevEdgeBelow) {
+            SkASSERT(e->fPrevEdgeBelow->fNextEdgeBelow == e);
+            SkASSERT(e->fPrevEdgeBelow->isLeftOf(e->fBottom));
+        } else {
+            SkASSERT(e == v->fFirstEdgeBelow);
+        }
+        if (e->fNextEdgeBelow) {
+            SkASSERT(e->fNextEdgeBelow->fPrevEdgeBelow == e);
+            SkASSERT(e->fNextEdgeBelow->isRightOf(e->fBottom));
+        } else {
+            SkASSERT(e == v->fLastEdgeBelow);
+        }
+    }
+}
+#endif
+
+Vertex* append_point_to_contour(const SkPoint& p, Vertex* prev, Vertex** head,
+                                SkChunkAlloc& alloc) {
+    Vertex* v = ALLOC_NEW(Vertex, (p), alloc);
+#if LOGGING_ENABLED
+    static float gID = 0.0f;
+    v->fID = gID++;
+#endif
+    if (prev) {
+        prev->fNext = v;
+        v->fPrev = prev;
+    } else {
+        *head = v;
+    }
+    return v;
+}
+
+Vertex* generate_quadratic_points(const SkPoint& p0,
+                                  const SkPoint& p1,
+                                  const SkPoint& p2,
+                                  SkScalar tolSqd,
+                                  Vertex* prev,
+                                  Vertex** head,
+                                  int pointsLeft,
+                                  SkChunkAlloc& alloc) {
+    SkScalar d = p1.distanceToLineSegmentBetweenSqd(p0, p2);
+    if (pointsLeft < 2 || d < tolSqd || !SkScalarIsFinite(d)) {
+        return append_point_to_contour(p2, prev, head, alloc);
+    }
+
+    const SkPoint q[] = {
+        { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) },
+        { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) },
+    };
+    const SkPoint r = { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) };
+
+    pointsLeft >>= 1;
+    prev = generate_quadratic_points(p0, q[0], r, tolSqd, prev, head, pointsLeft, alloc);
+    prev = generate_quadratic_points(r, q[1], p2, tolSqd, prev, head, pointsLeft, alloc);
+    return prev;
+}
+
+Vertex* generate_cubic_points(const SkPoint& p0,
+                              const SkPoint& p1,
+                              const SkPoint& p2,
+                              const SkPoint& p3,
+                              SkScalar tolSqd,
+                              Vertex* prev,
+                              Vertex** head,
+                              int pointsLeft,
+                              SkChunkAlloc& alloc) {
+    SkScalar d1 = p1.distanceToLineSegmentBetweenSqd(p0, p3);
+    SkScalar d2 = p2.distanceToLineSegmentBetweenSqd(p0, p3);
+    if (pointsLeft < 2 || (d1 < tolSqd && d2 < tolSqd) ||
+        !SkScalarIsFinite(d1) || !SkScalarIsFinite(d2)) {
+        return append_point_to_contour(p3, prev, head, alloc);
+    }
+    const SkPoint q[] = {
+        { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) },
+        { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) },
+        { SkScalarAve(p2.fX, p3.fX), SkScalarAve(p2.fY, p3.fY) }
+    };
+    const SkPoint r[] = {
+        { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) },
+        { SkScalarAve(q[1].fX, q[2].fX), SkScalarAve(q[1].fY, q[2].fY) }
+    };
+    const SkPoint s = { SkScalarAve(r[0].fX, r[1].fX), SkScalarAve(r[0].fY, r[1].fY) };
+    pointsLeft >>= 1;
+    prev = generate_cubic_points(p0, q[0], r[0], s, tolSqd, prev, head, pointsLeft, alloc);
+    prev = generate_cubic_points(s, r[1], q[2], p3, tolSqd, prev, head, pointsLeft, alloc);
+    return prev;
+}
+
+// Stage 1: convert the input path to a set of linear contours (linked list of Vertices).
+
+void path_to_contours(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
+                      Vertex** contours, SkChunkAlloc& alloc) {
+
+    SkScalar toleranceSqd = tolerance * tolerance;
+
+    SkPoint pts[4];
+    bool done = false;
+    SkPath::Iter iter(path, false);
+    Vertex* prev = NULL;
+    Vertex* head = NULL;
+    if (path.isInverseFillType()) {
+        SkPoint quad[4];
+        clipBounds.toQuad(quad);
+        for (int i = 3; i >= 0; i--) {
+            prev = append_point_to_contour(quad[i], prev, &head, alloc);
+        }
+        head->fPrev = prev;
+        prev->fNext = head;
+        *contours++ = head;
+        head = prev = NULL;
+    }
+    SkAutoConicToQuads converter;
+    while (!done) {
+        SkPath::Verb verb = iter.next(pts);
+        switch (verb) {
+            case SkPath::kConic_Verb: {
+                SkScalar weight = iter.conicWeight();
+                const SkPoint* quadPts = converter.computeQuads(pts, weight, toleranceSqd);
+                for (int i = 0; i < converter.countQuads(); ++i) {
+                    int pointsLeft = GrPathUtils::quadraticPointCount(quadPts, toleranceSqd);
+                    prev = generate_quadratic_points(quadPts[0], quadPts[1], quadPts[2],
+                                                     toleranceSqd, prev, &head, pointsLeft, alloc);
+                    quadPts += 2;
+                }
+                break;
+            }
+            case SkPath::kMove_Verb:
+                if (head) {
+                    head->fPrev = prev;
+                    prev->fNext = head;
+                    *contours++ = head;
+                }
+                head = prev = NULL;
+                prev = append_point_to_contour(pts[0], prev, &head, alloc);
+                break;
+            case SkPath::kLine_Verb: {
+                prev = append_point_to_contour(pts[1], prev, &head, alloc);
+                break;
+            }
+            case SkPath::kQuad_Verb: {
+                int pointsLeft = GrPathUtils::quadraticPointCount(pts, toleranceSqd);
+                prev = generate_quadratic_points(pts[0], pts[1], pts[2], toleranceSqd, prev,
+                                                 &head, pointsLeft, alloc);
+                break;
+            }
+            case SkPath::kCubic_Verb: {
+                int pointsLeft = GrPathUtils::cubicPointCount(pts, toleranceSqd);
+                prev = generate_cubic_points(pts[0], pts[1], pts[2], pts[3],
+                                toleranceSqd, prev, &head, pointsLeft, alloc);
+                break;
+            }
+            case SkPath::kClose_Verb:
+                if (head) {
+                    head->fPrev = prev;
+                    prev->fNext = head;
+                    *contours++ = head;
+                }
+                head = prev = NULL;
+                break;
+            case SkPath::kDone_Verb:
+                if (head) {
+                    head->fPrev = prev;
+                    prev->fNext = head;
+                    *contours++ = head;
+                }
+                done = true;
+                break;
+        }
+    }
+}
+
+inline bool apply_fill_type(SkPath::FillType fillType, int winding) {
+    switch (fillType) {
+        case SkPath::kWinding_FillType:
+            return winding != 0;
+        case SkPath::kEvenOdd_FillType:
+            return (winding & 1) != 0;
+        case SkPath::kInverseWinding_FillType:
+            return winding == 1;
+        case SkPath::kInverseEvenOdd_FillType:
+            return (winding & 1) == 1;
+        default:
+            SkASSERT(false);
+            return false;
+    }
+}
+
+Edge* new_edge(Vertex* prev, Vertex* next, SkChunkAlloc& alloc) {
+    int winding = sweep_lt(prev->fPoint, next->fPoint) ? 1 : -1;
+    Vertex* top = winding < 0 ? next : prev;
+    Vertex* bottom = winding < 0 ? prev : next;
+    return ALLOC_NEW(Edge, (top, bottom, winding), alloc);
+}
+
+void remove_edge(Edge* edge, Edge** head) {
+    LOG("removing edge %g -> %g\n", edge->fTop->fID, edge->fBottom->fID);
+    SkASSERT(edge->isActive(head));
+    remove<Edge, &Edge::fLeft, &Edge::fRight>(edge, head, NULL);
+}
+
+void insert_edge(Edge* edge, Edge* prev, Edge** head) {
+    LOG("inserting edge %g -> %g\n", edge->fTop->fID, edge->fBottom->fID);
+    SkASSERT(!edge->isActive(head));
+    Edge* next = prev ? prev->fRight : *head;
+    insert<Edge, &Edge::fLeft, &Edge::fRight>(edge, prev, next, head, NULL);
+}
+
+void find_enclosing_edges(Vertex* v, Edge* head, Edge** left, Edge** right) {
+    if (v->fFirstEdgeAbove) {
+        *left = v->fFirstEdgeAbove->fLeft;
+        *right = v->fLastEdgeAbove->fRight;
+        return;
+    }
+    Edge* prev = NULL;
+    Edge* next;
+    for (next = head; next != NULL; next = next->fRight) {
+        if (next->isRightOf(v)) {
+            break;
+        }
+        prev = next;
+    }
+    *left = prev;
+    *right = next;
+    return;
+}
+
+void find_enclosing_edges(Edge* edge, Edge* head, Edge** left, Edge** right) {
+    Edge* prev = NULL;
+    Edge* next;
+    for (next = head; next != NULL; next = next->fRight) {
+        if ((sweep_gt(edge->fTop->fPoint, next->fTop->fPoint) && next->isRightOf(edge->fTop)) ||
+            (sweep_gt(next->fTop->fPoint, edge->fTop->fPoint) && edge->isLeftOf(next->fTop)) ||
+            (sweep_lt(edge->fBottom->fPoint, next->fBottom->fPoint) &&
+             next->isRightOf(edge->fBottom)) ||
+            (sweep_lt(next->fBottom->fPoint, edge->fBottom->fPoint) &&
+             edge->isLeftOf(next->fBottom))) {
+            break;
+        }
+        prev = next;
+    }
+    *left = prev;
+    *right = next;
+    return;
+}
+
+void fix_active_state(Edge* edge, Edge** activeEdges) {
+    if (edge->isActive(activeEdges)) {
+        if (edge->fBottom->fProcessed || !edge->fTop->fProcessed) {
+            remove_edge(edge, activeEdges);
+        }
+    } else if (edge->fTop->fProcessed && !edge->fBottom->fProcessed) {
+        Edge* left;
+        Edge* right;
+        find_enclosing_edges(edge, *activeEdges, &left, &right);
+        insert_edge(edge, left, activeEdges);
+    }
+}
+
+void insert_edge_above(Edge* edge, Vertex* v) {
+    if (edge->fTop->fPoint == edge->fBottom->fPoint ||
+        sweep_gt(edge->fTop->fPoint, edge->fBottom->fPoint)) {
+        SkASSERT(false);
+        return;
+    }
+    LOG("insert edge (%g -> %g) above vertex %g\n", edge->fTop->fID, edge->fBottom->fID, v->fID);
+    Edge* prev = NULL;
+    Edge* next;
+    for (next = v->fFirstEdgeAbove; next; next = next->fNextEdgeAbove) {
+        if (next->isRightOf(edge->fTop)) {
+            break;
+        }
+        prev = next;
+    }
+    insert<Edge, &Edge::fPrevEdgeAbove, &Edge::fNextEdgeAbove>(
+        edge, prev, next, &v->fFirstEdgeAbove, &v->fLastEdgeAbove);
+}
+
+void insert_edge_below(Edge* edge, Vertex* v) {
+    if (edge->fTop->fPoint == edge->fBottom->fPoint ||
+        sweep_gt(edge->fTop->fPoint, edge->fBottom->fPoint)) {
+        SkASSERT(false);
+        return;
+    }
+    LOG("insert edge (%g -> %g) below vertex %g\n", edge->fTop->fID, edge->fBottom->fID, v->fID);
+    Edge* prev = NULL;
+    Edge* next;
+    for (next = v->fFirstEdgeBelow; next; next = next->fNextEdgeBelow) {
+        if (next->isRightOf(edge->fBottom)) {
+            break;
+        }
+        prev = next;
+    }
+    insert<Edge, &Edge::fPrevEdgeBelow, &Edge::fNextEdgeBelow>(
+        edge, prev, next, &v->fFirstEdgeBelow, &v->fLastEdgeBelow);
+}
+
+void remove_edge_above(Edge* edge) {
+    LOG("removing edge (%g -> %g) above vertex %g\n", edge->fTop->fID, edge->fBottom->fID,
+        edge->fBottom->fID);
+    remove<Edge, &Edge::fPrevEdgeAbove, &Edge::fNextEdgeAbove>(
+        edge, &edge->fBottom->fFirstEdgeAbove, &edge->fBottom->fLastEdgeAbove);
+}
+
+void remove_edge_below(Edge* edge) {
+    LOG("removing edge (%g -> %g) below vertex %g\n", edge->fTop->fID, edge->fBottom->fID,
+        edge->fTop->fID);
+    remove<Edge, &Edge::fPrevEdgeBelow, &Edge::fNextEdgeBelow>(
+        edge, &edge->fTop->fFirstEdgeBelow, &edge->fTop->fLastEdgeBelow);
+}
+
+void erase_edge_if_zero_winding(Edge* edge, Edge** head) {
+    if (edge->fWinding != 0) {
+        return;
+    }
+    LOG("erasing edge (%g -> %g)\n", edge->fTop->fID, edge->fBottom->fID);
+    remove_edge_above(edge);
+    remove_edge_below(edge);
+    if (edge->isActive(head)) {
+        remove_edge(edge, head);
+    }
+}
+
+void merge_collinear_edges(Edge* edge, Edge** activeEdges);
+
+void set_top(Edge* edge, Vertex* v, Edge** activeEdges) {
+    remove_edge_below(edge);
+    edge->fTop = v;
+    edge->recompute();
+    insert_edge_below(edge, v);
+    fix_active_state(edge, activeEdges);
+    merge_collinear_edges(edge, activeEdges);
+}
+
+void set_bottom(Edge* edge, Vertex* v, Edge** activeEdges) {
+    remove_edge_above(edge);
+    edge->fBottom = v;
+    edge->recompute();
+    insert_edge_above(edge, v);
+    fix_active_state(edge, activeEdges);
+    merge_collinear_edges(edge, activeEdges);
+}
+
+void merge_edges_above(Edge* edge, Edge* other, Edge** activeEdges) {
+    if (coincident(edge->fTop->fPoint, other->fTop->fPoint)) {
+        LOG("merging coincident above edges (%g, %g) -> (%g, %g)\n",
+            edge->fTop->fPoint.fX, edge->fTop->fPoint.fY,
+            edge->fBottom->fPoint.fX, edge->fBottom->fPoint.fY);
+        other->fWinding += edge->fWinding;
+        erase_edge_if_zero_winding(other, activeEdges);
+        edge->fWinding = 0;
+        erase_edge_if_zero_winding(edge, activeEdges);
+    } else if (sweep_lt(edge->fTop->fPoint, other->fTop->fPoint)) {
+        other->fWinding += edge->fWinding;
+        erase_edge_if_zero_winding(other, activeEdges);
+        set_bottom(edge, other->fTop, activeEdges);
+    } else {
+        edge->fWinding += other->fWinding;
+        erase_edge_if_zero_winding(edge, activeEdges);
+        set_bottom(other, edge->fTop, activeEdges);
+    }
+}
+
+void merge_edges_below(Edge* edge, Edge* other, Edge** activeEdges) {
+    if (coincident(edge->fBottom->fPoint, other->fBottom->fPoint)) {
+        LOG("merging coincident below edges (%g, %g) -> (%g, %g)\n",
+            edge->fTop->fPoint.fX, edge->fTop->fPoint.fY,
+            edge->fBottom->fPoint.fX, edge->fBottom->fPoint.fY);
+        other->fWinding += edge->fWinding;
+        erase_edge_if_zero_winding(other, activeEdges);
+        edge->fWinding = 0;
+        erase_edge_if_zero_winding(edge, activeEdges);
+    } else if (sweep_lt(edge->fBottom->fPoint, other->fBottom->fPoint)) {
+        edge->fWinding += other->fWinding;
+        erase_edge_if_zero_winding(edge, activeEdges);
+        set_top(other, edge->fBottom, activeEdges);
+    } else {
+        other->fWinding += edge->fWinding;
+        erase_edge_if_zero_winding(other, activeEdges);
+        set_top(edge, other->fBottom, activeEdges);
+    }
+}
+
+void merge_collinear_edges(Edge* edge, Edge** activeEdges) {
+    if (edge->fPrevEdgeAbove && (edge->fTop == edge->fPrevEdgeAbove->fTop ||
+                                 !edge->fPrevEdgeAbove->isLeftOf(edge->fTop))) {
+        merge_edges_above(edge, edge->fPrevEdgeAbove, activeEdges);
+    } else if (edge->fNextEdgeAbove && (edge->fTop == edge->fNextEdgeAbove->fTop ||
+                                        !edge->isLeftOf(edge->fNextEdgeAbove->fTop))) {
+        merge_edges_above(edge, edge->fNextEdgeAbove, activeEdges);
+    }
+    if (edge->fPrevEdgeBelow && (edge->fBottom == edge->fPrevEdgeBelow->fBottom ||
+                                 !edge->fPrevEdgeBelow->isLeftOf(edge->fBottom))) {
+        merge_edges_below(edge, edge->fPrevEdgeBelow, activeEdges);
+    } else if (edge->fNextEdgeBelow && (edge->fBottom == edge->fNextEdgeBelow->fBottom ||
+                                        !edge->isLeftOf(edge->fNextEdgeBelow->fBottom))) {
+        merge_edges_below(edge, edge->fNextEdgeBelow, activeEdges);
+    }
+}
+
+void split_edge(Edge* edge, Vertex* v, Edge** activeEdges, SkChunkAlloc& alloc);
+
+void cleanup_active_edges(Edge* edge, Edge** activeEdges, SkChunkAlloc& alloc) {
+    Vertex* top = edge->fTop;
+    Vertex* bottom = edge->fBottom;
+    if (edge->fLeft) {
+        Vertex* leftTop = edge->fLeft->fTop;
+        Vertex* leftBottom = edge->fLeft->fBottom;
+        if (sweep_gt(top->fPoint, leftTop->fPoint) && !edge->fLeft->isLeftOf(top)) {
+            split_edge(edge->fLeft, edge->fTop, activeEdges, alloc);
+        } else if (sweep_gt(leftTop->fPoint, top->fPoint) && !edge->isRightOf(leftTop)) {
+            split_edge(edge, leftTop, activeEdges, alloc);
+        } else if (sweep_lt(bottom->fPoint, leftBottom->fPoint) && !edge->fLeft->isLeftOf(bottom)) {
+            split_edge(edge->fLeft, bottom, activeEdges, alloc);
+        } else if (sweep_lt(leftBottom->fPoint, bottom->fPoint) && !edge->isRightOf(leftBottom)) {
+            split_edge(edge, leftBottom, activeEdges, alloc);
+        }
+    }
+    if (edge->fRight) {
+        Vertex* rightTop = edge->fRight->fTop;
+        Vertex* rightBottom = edge->fRight->fBottom;
+        if (sweep_gt(top->fPoint, rightTop->fPoint) && !edge->fRight->isRightOf(top)) {
+            split_edge(edge->fRight, top, activeEdges, alloc);
+        } else if (sweep_gt(rightTop->fPoint, top->fPoint) && !edge->isLeftOf(rightTop)) {
+            split_edge(edge, rightTop, activeEdges, alloc);
+        } else if (sweep_lt(bottom->fPoint, rightBottom->fPoint) &&
+                   !edge->fRight->isRightOf(bottom)) {
+            split_edge(edge->fRight, bottom, activeEdges, alloc);
+        } else if (sweep_lt(rightBottom->fPoint, bottom->fPoint) &&
+                   !edge->isLeftOf(rightBottom)) {
+            split_edge(edge, rightBottom, activeEdges, alloc);
+        }
+    }
+}
+
+void split_edge(Edge* edge, Vertex* v, Edge** activeEdges, SkChunkAlloc& alloc) {
+    LOG("splitting edge (%g -> %g) at vertex %g (%g, %g)\n",
+        edge->fTop->fID, edge->fBottom->fID,
+        v->fID, v->fPoint.fX, v->fPoint.fY);
+    Edge* newEdge = ALLOC_NEW(Edge, (v, edge->fBottom, edge->fWinding), alloc);
+    insert_edge_below(newEdge, v);
+    insert_edge_above(newEdge, edge->fBottom);
+    set_bottom(edge, v, activeEdges);
+    cleanup_active_edges(edge, activeEdges, alloc);
+    fix_active_state(newEdge, activeEdges);
+    merge_collinear_edges(newEdge, activeEdges);
+}
+
+void merge_vertices(Vertex* src, Vertex* dst, Vertex** head, SkChunkAlloc& alloc) {
+    LOG("found coincident verts at %g, %g; merging %g into %g\n", src->fPoint.fX, src->fPoint.fY,
+        src->fID, dst->fID);
+    for (Edge* edge = src->fFirstEdgeAbove; edge;) {
+        Edge* next = edge->fNextEdgeAbove;
+        set_bottom(edge, dst, NULL);
+        edge = next;
+    }
+    for (Edge* edge = src->fFirstEdgeBelow; edge;) {
+        Edge* next = edge->fNextEdgeBelow;
+        set_top(edge, dst, NULL);
+        edge = next;
+    }
+    remove<Vertex, &Vertex::fPrev, &Vertex::fNext>(src, head, NULL);
+}
+
+Vertex* check_for_intersection(Edge* edge, Edge* other, Edge** activeEdges, SkChunkAlloc& alloc) {
+    SkPoint p;
+    if (!edge || !other) {
+        return NULL;
+    }
+    if (edge->intersect(*other, &p)) {
+        Vertex* v;
+        LOG("found intersection, pt is %g, %g\n", p.fX, p.fY);
+        if (p == edge->fTop->fPoint || sweep_lt(p, edge->fTop->fPoint)) {
+            split_edge(other, edge->fTop, activeEdges, alloc);
+            v = edge->fTop;
+        } else if (p == edge->fBottom->fPoint || sweep_gt(p, edge->fBottom->fPoint)) {
+            split_edge(other, edge->fBottom, activeEdges, alloc);
+            v = edge->fBottom;
+        } else if (p == other->fTop->fPoint || sweep_lt(p, other->fTop->fPoint)) {
+            split_edge(edge, other->fTop, activeEdges, alloc);
+            v = other->fTop;
+        } else if (p == other->fBottom->fPoint || sweep_gt(p, other->fBottom->fPoint)) {
+            split_edge(edge, other->fBottom, activeEdges, alloc);
+            v = other->fBottom;
+        } else {
+            Vertex* nextV = edge->fTop;
+            while (sweep_lt(p, nextV->fPoint)) {
+                nextV = nextV->fPrev;
+            }
+            while (sweep_lt(nextV->fPoint, p)) {
+                nextV = nextV->fNext;
+            }
+            Vertex* prevV = nextV->fPrev;
+            if (coincident(prevV->fPoint, p)) {
+                v = prevV;
+            } else if (coincident(nextV->fPoint, p)) {
+                v = nextV;
+            } else {
+                v = ALLOC_NEW(Vertex, (p), alloc);
+                LOG("inserting between %g (%g, %g) and %g (%g, %g)\n",
+                    prevV->fID, prevV->fPoint.fX, prevV->fPoint.fY,
+                    nextV->fID, nextV->fPoint.fX, nextV->fPoint.fY);
+#if LOGGING_ENABLED
+                v->fID = (nextV->fID + prevV->fID) * 0.5f;
+#endif
+                v->fPrev = prevV;
+                v->fNext = nextV;
+                prevV->fNext = v;
+                nextV->fPrev = v;
+            }
+            split_edge(edge, v, activeEdges, alloc);
+            split_edge(other, v, activeEdges, alloc);
+        }
+#ifdef SK_DEBUG
+        validate_connectivity(v);
+#endif
+        return v;
+    }
+    return NULL;
+}
+
+void sanitize_contours(Vertex** contours, int contourCnt) {
+    for (int i = 0; i < contourCnt; ++i) {
+        SkASSERT(contours[i]);
+        for (Vertex* v = contours[i];;) {
+            if (coincident(v->fPrev->fPoint, v->fPoint)) {
+                LOG("vertex %g,%g coincident; removing\n", v->fPoint.fX, v->fPoint.fY);
+                if (v->fPrev == v) {
+                    contours[i] = NULL;
+                    break;
+                }
+                v->fPrev->fNext = v->fNext;
+                v->fNext->fPrev = v->fPrev;
+                if (contours[i] == v) {
+                    contours[i] = v->fNext;
+                }
+                v = v->fPrev;
+            } else {
+                v = v->fNext;
+                if (v == contours[i]) break;
+            }
+        }
+    }
+}
+
+void merge_coincident_vertices(Vertex** vertices, SkChunkAlloc& alloc) {
+    for (Vertex* v = (*vertices)->fNext; v != NULL; v = v->fNext) {
+        if (sweep_lt(v->fPoint, v->fPrev->fPoint)) {
+            v->fPoint = v->fPrev->fPoint;
+        }
+        if (coincident(v->fPrev->fPoint, v->fPoint)) {
+            merge_vertices(v->fPrev, v, vertices, alloc);
+        }
+    }
+}
+
+// Stage 2: convert the contours to a mesh of edges connecting the vertices.
+
+Vertex* build_edges(Vertex** contours, int contourCnt, SkChunkAlloc& alloc) {
+    Vertex* vertices = NULL;
+    Vertex* prev = NULL;
+    for (int i = 0; i < contourCnt; ++i) {
+        for (Vertex* v = contours[i]; v != NULL;) {
+            Vertex* vNext = v->fNext;
+            Edge* edge = new_edge(v->fPrev, v, alloc);
+            if (edge->fWinding > 0) {
+                insert_edge_below(edge, v->fPrev);
+                insert_edge_above(edge, v);
+            } else {
+                insert_edge_below(edge, v);
+                insert_edge_above(edge, v->fPrev);
+            }
+            merge_collinear_edges(edge, NULL);
+            if (prev) {
+                prev->fNext = v;
+                v->fPrev = prev;
+            } else {
+                vertices = v;
+            }
+            prev = v;
+            v = vNext;
+            if (v == contours[i]) break;
+        }
+    }
+    if (prev) {
+        prev->fNext = vertices->fPrev = NULL;
+    }
+    return vertices;
+}
+
+// Stage 3: sort the vertices by increasing Y (or X if SWEEP_IN_X is on).
+
+Vertex* sorted_merge(Vertex* a, Vertex* b);
+
+void front_back_split(Vertex* v, Vertex** pFront, Vertex** pBack) {
+    Vertex* fast;
+    Vertex* slow;
+    if (!v || !v->fNext) {
+        *pFront = v;
+        *pBack = NULL;
+    } else {
+        slow = v;
+        fast = v->fNext;
+
+        while (fast != NULL) {
+            fast = fast->fNext;
+            if (fast != NULL) {
+                slow = slow->fNext;
+                fast = fast->fNext;
+            }
+        }
+
+        *pFront = v;
+        *pBack = slow->fNext;
+        slow->fNext->fPrev = NULL;
+        slow->fNext = NULL;
+    }
+}
+
+void merge_sort(Vertex** head) {
+    if (!*head || !(*head)->fNext) {
+        return;
+    }
+
+    Vertex* a;
+    Vertex* b;
+    front_back_split(*head, &a, &b);
+
+    merge_sort(&a);
+    merge_sort(&b);
+
+    *head = sorted_merge(a, b);
+}
+
+Vertex* sorted_merge(Vertex* a, Vertex* b) {
+    if (!a) {
+        return b;
+    } else if (!b) {
+        return a;
+    }
+
+    Vertex* result = NULL;
+
+    if (sweep_lt(a->fPoint, b->fPoint)) {
+        result = a;
+        result->fNext = sorted_merge(a->fNext, b);
+    } else {
+        result = b;
+        result->fNext = sorted_merge(a, b->fNext);
+    }
+    result->fNext->fPrev = result;
+    return result;
+}
+
+// Stage 4: Simplify the mesh by inserting new vertices at intersecting edges.
+
+void simplify(Vertex* vertices, SkChunkAlloc& alloc) {
+    LOG("simplifying complex polygons\n");
+    Edge* activeEdges = NULL;
+    for (Vertex* v = vertices; v != NULL; v = v->fNext) {
+        if (!v->fFirstEdgeAbove && !v->fFirstEdgeBelow) {
+            continue;
+        }
+#if LOGGING_ENABLED
+        LOG("\nvertex %g: (%g,%g)\n", v->fID, v->fPoint.fX, v->fPoint.fY);
+#endif
+#ifdef SK_DEBUG
+        validate_connectivity(v);
+#endif
+        Edge* leftEnclosingEdge = NULL;
+        Edge* rightEnclosingEdge = NULL;
+        bool restartChecks;
+        do {
+            restartChecks = false;
+            find_enclosing_edges(v, activeEdges, &leftEnclosingEdge, &rightEnclosingEdge);
+            if (v->fFirstEdgeBelow) {
+                for (Edge* edge = v->fFirstEdgeBelow; edge != NULL; edge = edge->fNextEdgeBelow) {
+                    if (check_for_intersection(edge, leftEnclosingEdge, &activeEdges, alloc)) {
+                        restartChecks = true;
+                        break;
+                    }
+                    if (check_for_intersection(edge, rightEnclosingEdge, &activeEdges, alloc)) {
+                        restartChecks = true;
+                        break;
+                    }
+                }
+            } else {
+                if (Vertex* pv = check_for_intersection(leftEnclosingEdge, rightEnclosingEdge,
+                                                        &activeEdges, alloc)) {
+                    if (sweep_lt(pv->fPoint, v->fPoint)) {
+                        v = pv;
+                    }
+                    restartChecks = true;
+                }
+
+            }
+        } while (restartChecks);
+        SkASSERT(!leftEnclosingEdge || leftEnclosingEdge->isLeftOf(v));
+        SkASSERT(!rightEnclosingEdge || rightEnclosingEdge->isRightOf(v));
+#ifdef SK_DEBUG
+        validate_edges(activeEdges);
+#endif
+        for (Edge* e = v->fFirstEdgeAbove; e; e = e->fNextEdgeAbove) {
+            remove_edge(e, &activeEdges);
+        }
+        Edge* leftEdge = leftEnclosingEdge;
+        for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) {
+            insert_edge(e, leftEdge, &activeEdges);
+            leftEdge = e;
+        }
+        v->fProcessed = true;
+    }
+}
+
+// Stage 5: Tessellate the simplified mesh into monotone polygons.
+
+Poly* tessellate(Vertex* vertices, SkChunkAlloc& alloc) {
+    LOG("tessellating simple polygons\n");
+    Edge* activeEdges = NULL;
+    Poly* polys = NULL;
+    for (Vertex* v = vertices; v != NULL; v = v->fNext) {
+        if (!v->fFirstEdgeAbove && !v->fFirstEdgeBelow) {
+            continue;
+        }
+#if LOGGING_ENABLED
+        LOG("\nvertex %g: (%g,%g)\n", v->fID, v->fPoint.fX, v->fPoint.fY);
+#endif
+#ifdef SK_DEBUG
+        validate_connectivity(v);
+#endif
+        Edge* leftEnclosingEdge = NULL;
+        Edge* rightEnclosingEdge = NULL;
+        find_enclosing_edges(v, activeEdges, &leftEnclosingEdge, &rightEnclosingEdge);
+        SkASSERT(!leftEnclosingEdge || leftEnclosingEdge->isLeftOf(v));
+        SkASSERT(!rightEnclosingEdge || rightEnclosingEdge->isRightOf(v));
+#ifdef SK_DEBUG
+        validate_edges(activeEdges);
+#endif
+        Poly* leftPoly = NULL;
+        Poly* rightPoly = NULL;
+        if (v->fFirstEdgeAbove) {
+            leftPoly = v->fFirstEdgeAbove->fLeftPoly;
+            rightPoly = v->fLastEdgeAbove->fRightPoly;
+        } else {
+            leftPoly = leftEnclosingEdge ? leftEnclosingEdge->fRightPoly : NULL;
+            rightPoly = rightEnclosingEdge ? rightEnclosingEdge->fLeftPoly : NULL;
+        }
+#if LOGGING_ENABLED
+        LOG("edges above:\n");
+        for (Edge* e = v->fFirstEdgeAbove; e; e = e->fNextEdgeAbove) {
+            LOG("%g -> %g, lpoly %d, rpoly %d\n", e->fTop->fID, e->fBottom->fID,
+                e->fLeftPoly ? e->fLeftPoly->fID : -1, e->fRightPoly ? e->fRightPoly->fID : -1);
+        }
+        LOG("edges below:\n");
+        for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) {
+            LOG("%g -> %g, lpoly %d, rpoly %d\n", e->fTop->fID, e->fBottom->fID,
+                e->fLeftPoly ? e->fLeftPoly->fID : -1, e->fRightPoly ? e->fRightPoly->fID : -1);
+        }
+#endif
+        if (v->fFirstEdgeAbove) {
+            if (leftPoly) {
+                leftPoly = leftPoly->addVertex(v, Poly::kRight_Side, alloc);
+            }
+            if (rightPoly) {
+                rightPoly = rightPoly->addVertex(v, Poly::kLeft_Side, alloc);
+            }
+            for (Edge* e = v->fFirstEdgeAbove; e != v->fLastEdgeAbove; e = e->fNextEdgeAbove) {
+                Edge* leftEdge = e;
+                Edge* rightEdge = e->fNextEdgeAbove;
+                SkASSERT(rightEdge->isRightOf(leftEdge->fTop));
+                remove_edge(leftEdge, &activeEdges);
+                if (leftEdge->fRightPoly) {
+                    leftEdge->fRightPoly->end(v, alloc);
+                }
+                if (rightEdge->fLeftPoly && rightEdge->fLeftPoly != leftEdge->fRightPoly) {
+                    rightEdge->fLeftPoly->end(v, alloc);
+                }
+            }
+            remove_edge(v->fLastEdgeAbove, &activeEdges);
+            if (!v->fFirstEdgeBelow) {
+                if (leftPoly && rightPoly && leftPoly != rightPoly) {
+                    SkASSERT(leftPoly->fPartner == NULL && rightPoly->fPartner == NULL);
+                    rightPoly->fPartner = leftPoly;
+                    leftPoly->fPartner = rightPoly;
+                }
+            }
+        }
+        if (v->fFirstEdgeBelow) {
+            if (!v->fFirstEdgeAbove) {
+                if (leftPoly && leftPoly == rightPoly) {
+                    // Split the poly.
+                    if (leftPoly->fActive->fSide == Poly::kLeft_Side) {
+                        leftPoly = new_poly(&polys, leftEnclosingEdge->fTop, leftPoly->fWinding,
+                                            alloc);
+                        leftPoly->addVertex(v, Poly::kRight_Side, alloc);
+                        rightPoly->addVertex(v, Poly::kLeft_Side, alloc);
+                        leftEnclosingEdge->fRightPoly = leftPoly;
+                    } else {
+                        rightPoly = new_poly(&polys, rightEnclosingEdge->fTop, rightPoly->fWinding,
+                                             alloc);
+                        rightPoly->addVertex(v, Poly::kLeft_Side, alloc);
+                        leftPoly->addVertex(v, Poly::kRight_Side, alloc);
+                        rightEnclosingEdge->fLeftPoly = rightPoly;
+                    }
+                } else {
+                    if (leftPoly) {
+                        leftPoly = leftPoly->addVertex(v, Poly::kRight_Side, alloc);
+                    }
+                    if (rightPoly) {
+                        rightPoly = rightPoly->addVertex(v, Poly::kLeft_Side, alloc);
+                    }
+                }
+            }
+            Edge* leftEdge = v->fFirstEdgeBelow;
+            leftEdge->fLeftPoly = leftPoly;
+            insert_edge(leftEdge, leftEnclosingEdge, &activeEdges);
+            for (Edge* rightEdge = leftEdge->fNextEdgeBelow; rightEdge;
+                 rightEdge = rightEdge->fNextEdgeBelow) {
+                insert_edge(rightEdge, leftEdge, &activeEdges);
+                int winding = leftEdge->fLeftPoly ? leftEdge->fLeftPoly->fWinding : 0;
+                winding += leftEdge->fWinding;
+                if (winding != 0) {
+                    Poly* poly = new_poly(&polys, v, winding, alloc);
+                    leftEdge->fRightPoly = rightEdge->fLeftPoly = poly;
+                }
+                leftEdge = rightEdge;
+            }
+            v->fLastEdgeBelow->fRightPoly = rightPoly;
+        }
+#ifdef SK_DEBUG
+        validate_edges(activeEdges);
+#endif
+#if LOGGING_ENABLED
+        LOG("\nactive edges:\n");
+        for (Edge* e = activeEdges; e != NULL; e = e->fRight) {
+            LOG("%g -> %g, lpoly %d, rpoly %d\n", e->fTop->fID, e->fBottom->fID,
+                e->fLeftPoly ? e->fLeftPoly->fID : -1, e->fRightPoly ? e->fRightPoly->fID : -1);
+        }
+#endif
+    }
+    return polys;
+}
+
+// This is a driver function which calls stages 2-5 in turn.
+
+Poly* contours_to_polys(Vertex** contours, int contourCnt, SkChunkAlloc& alloc) {
+#if LOGGING_ENABLED
+    for (int i = 0; i < contourCnt; ++i) {
+        Vertex* v = contours[i];
+        SkASSERT(v);
+        LOG("path.moveTo(%20.20g, %20.20g);\n", v->fPoint.fX, v->fPoint.fY);
+        for (v = v->fNext; v != contours[i]; v = v->fNext) {
+            LOG("path.lineTo(%20.20g, %20.20g);\n", v->fPoint.fX, v->fPoint.fY);
+        }
+    }
+#endif
+    sanitize_contours(contours, contourCnt);
+    Vertex* vertices = build_edges(contours, contourCnt, alloc);
+    if (!vertices) {
+        return NULL;
+    }
+
+    // Sort vertices in Y (secondarily in X).
+    merge_sort(&vertices);
+    merge_coincident_vertices(&vertices, alloc);
+#if LOGGING_ENABLED
+    for (Vertex* v = vertices; v != NULL; v = v->fNext) {
+        static float gID = 0.0f;
+        v->fID = gID++;
+    }
+#endif
+    simplify(vertices, alloc);
+    return tessellate(vertices, alloc);
+}
+
+// Stage 6: Triangulate the monotone polygons into a vertex buffer.
+
+void* polys_to_triangles(Poly* polys, SkPath::FillType fillType, void* data) {
+    void* d = data;
+    for (Poly* poly = polys; poly; poly = poly->fNext) {
+        if (apply_fill_type(fillType, poly->fWinding)) {
+            d = poly->emit(d);
+        }
+    }
+    return d;
+}
+
+};
+
+GrTessellatingPathRenderer::GrTessellatingPathRenderer() {
+}
+
+GrPathRenderer::StencilSupport GrTessellatingPathRenderer::onGetStencilSupport(
+                                                            const GrDrawTarget*,
+                                                            const GrPipelineBuilder*,
+                                                            const SkPath&,
+                                                            const SkStrokeRec&) const {
+    return GrPathRenderer::kNoSupport_StencilSupport;
+}
+
+bool GrTessellatingPathRenderer::canDrawPath(const GrDrawTarget* target,
+                                             const GrPipelineBuilder* pipelineBuilder,
+                                             const SkMatrix& viewMatrix,
+                                             const SkPath& path,
+                                             const SkStrokeRec& stroke,
+                                             bool antiAlias) const {
+    // This path renderer can draw all fill styles, but does not do antialiasing. It can do convex
+    // and concave paths, but we'll leave the convex ones to simpler algorithms.
+    return stroke.isFillStyle() && !antiAlias && !path.isConvex();
+}
+
+bool GrTessellatingPathRenderer::onDrawPath(GrDrawTarget* target,
+                                            GrPipelineBuilder* pipelineBuilder,
+                                            GrColor color,
+                                            const SkMatrix& viewM,
+                                            const SkPath& path,
+                                            const SkStrokeRec& stroke,
+                                            bool antiAlias) {
+    SkASSERT(!antiAlias);
+    const GrRenderTarget* rt = pipelineBuilder->getRenderTarget();
+    if (NULL == rt) {
+        return false;
+    }
+
+    SkScalar tol = GrPathUtils::scaleToleranceToSrc(SK_Scalar1, viewM, path.getBounds());
+
+    int contourCnt;
+    int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt, tol);
+    if (maxPts <= 0) {
+        return false;
+    }
+    if (maxPts > ((int)SK_MaxU16 + 1)) {
+        SkDebugf("Path not rendered, too many verts (%d)\n", maxPts);
+        return false;
+    }
+    SkPath::FillType fillType = path.getFillType();
+    if (SkPath::IsInverseFillType(fillType)) {
+        contourCnt++;
+    }
+
+    LOG("got %d pts, %d contours\n", maxPts, contourCnt);
+
+    SkAutoTDeleteArray<Vertex*> contours(SkNEW_ARRAY(Vertex *, contourCnt));
+
+    // For the initial size of the chunk allocator, estimate based on the point count:
+    // one vertex per point for the initial passes, plus two for the vertices in the
+    // resulting Polys, since the same point may end up in two Polys.  Assume minimal
+    // connectivity of one Edge per Vertex (will grow for intersections).
+    SkChunkAlloc alloc(maxPts * (3 * sizeof(Vertex) + sizeof(Edge)));
+    SkIRect clipBoundsI;
+    pipelineBuilder->clip().getConservativeBounds(rt, &clipBoundsI);
+    SkRect clipBounds = SkRect::Make(clipBoundsI);
+    SkMatrix vmi;
+    if (!viewM.invert(&vmi)) {
+        return false;
+    }
+    vmi.mapRect(&clipBounds);
+    path_to_contours(path, tol, clipBounds, contours.get(), alloc);
+    Poly* polys;
+    uint32_t flags = GrDefaultGeoProcFactory::kPosition_GPType;
+    polys = contours_to_polys(contours.get(), contourCnt, alloc);
+    SkAutoTUnref<const GrGeometryProcessor> gp(
+        GrDefaultGeoProcFactory::Create(flags, color, viewM, SkMatrix::I()));
+    int count = 0;
+    for (Poly* poly = polys; poly; poly = poly->fNext) {
+        if (apply_fill_type(fillType, poly->fWinding) && poly->fCount >= 3) {
+            count += (poly->fCount - 2) * (WIREFRAME ? 6 : 3);
+        }
+    }
+
+    int stride = gp->getVertexStride();
+    GrDrawTarget::AutoReleaseGeometry arg;
+    if (!arg.set(target, count, stride, 0)) {
+        return false;
+    }
+    LOG("emitting %d verts\n", count);
+    void* end = polys_to_triangles(polys, fillType, arg.vertices());
+    int actualCount = (static_cast<char*>(end) - static_cast<char*>(arg.vertices())) / stride;
+    LOG("actual count: %d\n", actualCount);
+    SkASSERT(actualCount <= count);
+
+    GrPrimitiveType primitiveType = WIREFRAME ? kLines_GrPrimitiveType
+                                              : kTriangles_GrPrimitiveType;
+    target->drawNonIndexed(pipelineBuilder, gp, primitiveType, 0, actualCount);
+
+    return true;
+}
diff --git a/src/gpu/GrTessellatingPathRenderer.h b/src/gpu/GrTessellatingPathRenderer.h
new file mode 100644 (file)
index 0000000..3262c9a
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTessellatingPathRenderer_DEFINED
+#define GrTessellatingPathRenderer_DEFINED
+
+#include "GrPathRenderer.h"
+
+/**
+ *  Subclass that renders the path by converting to screen-space trapezoids plus
+ *   extra 1-pixel geometry for AA.
+ */
+class SK_API GrTessellatingPathRenderer : public GrPathRenderer {
+public:
+    GrTessellatingPathRenderer();
+
+    bool canDrawPath(const GrDrawTarget*,
+                     const GrPipelineBuilder*,
+                     const SkMatrix&,
+                     const SkPath&,
+                     const SkStrokeRec&,
+                     bool antiAlias) const SK_OVERRIDE;
+protected:
+
+    StencilSupport onGetStencilSupport(const GrDrawTarget*,
+                                       const GrPipelineBuilder*,
+                                       const SkPath&,
+                                       const SkStrokeRec&) const SK_OVERRIDE;
+
+    bool onDrawPath(GrDrawTarget*,
+                    GrPipelineBuilder*,
+                    GrColor,
+                    const SkMatrix& viewMatrix,
+                    const SkPath&,
+                    const SkStrokeRec&,
+                    bool antiAlias) SK_OVERRIDE;
+
+    typedef GrPathRenderer INHERITED;
+};
+
+#endif
diff --git a/tests/TessellatingPathRendererTests.cpp b/tests/TessellatingPathRendererTests.cpp
new file mode 100644 (file)
index 0000000..1625bf2
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#if SK_SUPPORT_GPU
+#include "GrContextFactory.h"
+#include "GrTessellatingPathRenderer.h"
+#include "GrTest.h"
+#include "Test.h"
+
+/*
+ * These tests pass by not crashing, hanging or asserting in Debug.
+ */
+
+// Tests active edges made inactive by splitting.
+// Also tests active edge list forced into an invalid ordering by
+// splitting (mopped up in cleanup_active_edges()).
+static SkPath create_path_0() {
+    SkPath path;
+    path.moveTo(229.127044677734375f,  67.34100341796875f);
+    path.lineTo(187.8097381591796875f, -6.7729740142822265625f);
+    path.lineTo(171.411407470703125f,  50.94266510009765625f);
+    path.lineTo(245.5253753662109375f,  9.6253643035888671875f);
+    path.moveTo(208.4683990478515625f, 30.284009933471679688f);
+    path.lineTo(171.411407470703125f,  50.94266510009765625f);
+    path.lineTo(187.8097381591796875f, -6.7729740142822265625f);
+    return path;
+}
+
+// Intersections which fall exactly on the current vertex, and require
+// a restart of the intersection checking.
+static SkPath create_path_1() {
+    SkPath path;
+    path.moveTo(314.483551025390625f, 486.246002197265625f);
+    path.lineTo(385.41949462890625f,  532.8087158203125f);
+    path.lineTo(373.232879638671875f, 474.05938720703125f);
+    path.lineTo(326.670166015625f,    544.995361328125f);
+    path.moveTo(349.951507568359375f, 509.52734375f);
+    path.lineTo(373.232879638671875f, 474.05938720703125f);
+    path.lineTo(385.41949462890625f,  532.8087158203125f);
+    return path;
+}
+
+// Tests active edges which are removed by splitting.
+static SkPath create_path_2() {
+    SkPath path;
+    path.moveTo(343.107391357421875f, 613.62176513671875f);
+    path.lineTo(426.632415771484375f, 628.5740966796875f);
+    path.lineTo(392.3460693359375f,   579.33544921875f);
+    path.lineTo(377.39373779296875f,  662.86041259765625f);
+    path.moveTo(384.869873046875f,    621.097900390625f);
+    path.lineTo(392.3460693359375f,   579.33544921875f);
+    path.lineTo(426.632415771484375f, 628.5740966796875f);
+    return path;
+}
+
+// Collinear edges merged in set_top().
+// Also, an intersection between left and right enclosing edges which
+// falls above the current vertex.
+static SkPath create_path_3() {
+    SkPath path;
+    path.moveTo(545.95751953125f,    791.69854736328125f);
+    path.lineTo(612.05816650390625f, 738.494140625f);
+    path.lineTo(552.4056396484375f,  732.0460205078125f);
+    path.lineTo(605.61004638671875f, 798.14666748046875f);
+    path.moveTo(579.00787353515625f, 765.0963134765625f);
+    path.lineTo(552.4056396484375f,  732.0460205078125f);
+    path.lineTo(612.05816650390625f, 738.494140625f);
+    return path;
+}
+
+// Tests active edges which are made inactive by set_top().
+static SkPath create_path_4() {
+    SkPath path;
+    path.moveTo(819.2725830078125f,  751.77447509765625f);
+    path.lineTo(820.70904541015625f, 666.933837890625f);
+    path.lineTo(777.57049560546875f, 708.63592529296875f);
+    path.lineTo(862.4111328125f,     710.0723876953125f);
+    path.moveTo(819.99078369140625f, 709.3541259765625f);
+    path.lineTo(777.57049560546875f, 708.63592529296875f);
+    path.lineTo(820.70904541015625f, 666.933837890625f);
+    return path;
+}
+
+static SkPath create_path_5() {
+    SkPath path;
+    path.moveTo(823.33209228515625f, 749.052734375f);
+    path.lineTo(823.494873046875f,   664.20013427734375f);
+    path.lineTo(780.9871826171875f,  706.5450439453125f);
+    path.lineTo(865.8397216796875f,  706.70782470703125f);
+    path.moveTo(823.4134521484375f,  706.6263427734375f);
+    path.lineTo(780.9871826171875f,  706.5450439453125f);
+    path.lineTo(823.494873046875f,   664.20013427734375f);
+    return path;
+}
+
+static SkPath create_path_6() {
+    SkPath path;
+    path.moveTo(954.862548828125f,   562.8349609375f);
+    path.lineTo(899.32818603515625f, 498.679443359375f);
+    path.lineTo(895.017578125f,      558.52435302734375f);
+    path.lineTo(959.17315673828125f, 502.990081787109375f);
+    path.moveTo(927.0953369140625f,  530.7572021484375f);
+    path.lineTo(895.017578125f,      558.52435302734375f);
+    path.lineTo(899.32818603515625f, 498.679443359375f);
+    return path;
+}
+
+static SkPath create_path_7() {
+    SkPath path;
+    path.moveTo(958.5330810546875f,  547.35516357421875f);
+    path.lineTo(899.93109130859375f, 485.989013671875f);
+    path.lineTo(898.54901123046875f, 545.97308349609375f);
+    path.lineTo(959.9151611328125f,  487.37109375f);
+    path.moveTo(929.2320556640625f,  516.67205810546875f);
+    path.lineTo(898.54901123046875f, 545.97308349609375f);
+    path.lineTo(899.93109130859375f, 485.989013671875f);
+    return path;
+}
+
+static SkPath create_path_8() {
+    SkPath path;
+    path.moveTo(389.8609619140625f,   369.326873779296875f);
+    path.lineTo(470.6290283203125f,   395.33697509765625f);
+    path.lineTo(443.250030517578125f, 341.9478759765625f);
+    path.lineTo(417.239959716796875f, 422.7159423828125f);
+    path.moveTo(430.244964599609375f, 382.3319091796875f);
+    path.lineTo(443.250030517578125f, 341.9478759765625f);
+    path.lineTo(470.6290283203125f,   395.33697509765625f);
+    return path;
+}
+
+static SkPath create_path_9() {
+    SkPath path;
+    path.moveTo(20, 20);
+    path.lineTo(50, 80);
+    path.lineTo(20, 80);
+    path.moveTo(80, 50);
+    path.lineTo(50, 50);
+    path.lineTo(20, 50);
+    return path;
+}
+
+static SkPath create_path_10() {
+    SkPath path;
+    path.moveTo(257.19439697265625f, 320.876617431640625f);
+    path.lineTo(190.113037109375f,   320.58978271484375f);
+    path.lineTo(203.64404296875f,    293.8145751953125f);
+    path.moveTo(203.357177734375f,   360.896026611328125f);
+    path.lineTo(216.88824462890625f, 334.120819091796875f);
+    path.lineTo(230.41925048828125f, 307.345611572265625f);
+    return path;
+}
+
+// A degenerate segments case, where both upper and lower segments of
+// a split edge must remain active.
+static SkPath create_path_11() {
+    SkPath path;
+    path.moveTo(231.9331207275390625f, 306.2012939453125f);
+    path.lineTo(191.4859161376953125f, 306.04547119140625f);
+    path.lineTo(231.0659332275390625f, 300.2642822265625f);
+    path.moveTo(189.946807861328125f,  302.072265625f);
+    path.lineTo(179.79705810546875f,   294.859771728515625f);
+    path.lineTo(191.0016021728515625f, 296.165679931640625f);
+    path.moveTo(150.8942108154296875f, 304.900146484375f);
+    path.lineTo(179.708892822265625f,  297.849029541015625f);
+    path.lineTo(190.4742279052734375f, 299.11895751953125f);
+    return path;
+}
+
+// Handle the case where edge.dist(edge.fTop) != 0.0.
+static SkPath create_path_12() {
+    SkPath path;
+    path.moveTo(                  0.0f,  400.0f);
+    path.lineTo(                138.0f,  202.0f);
+    path.lineTo(                  0.0f,  202.0f);
+    path.moveTo( 12.62693023681640625f,  250.57464599609375f);
+    path.lineTo(  8.13896942138671875f,  254.556884765625f);
+    path.lineTo(-18.15641021728515625f,  220.40203857421875f);
+    path.lineTo(-15.986493110656738281f, 219.6513519287109375f);
+    path.moveTo( 36.931194305419921875f, 282.485504150390625f);
+    path.lineTo( 15.617521286010742188f, 261.2901611328125f);
+    path.lineTo( 10.3829498291015625f,   252.565765380859375f);
+    path.lineTo(-16.165292739868164062f, 222.646026611328125f);
+    return path;
+}
+
+// A degenerate segments case which exercises inactive edges being
+// made active by splitting.
+static SkPath create_path_13() {
+    SkPath path;
+    path.moveTo(690.62127685546875f, 509.25555419921875f);
+    path.lineTo(99.336181640625f,    511.71405029296875f);
+    path.lineTo(708.362548828125f,   512.4349365234375f);
+    path.lineTo(729.9940185546875f,  516.3114013671875f);
+    path.lineTo(738.708984375f,      518.76995849609375f);
+    path.lineTo(678.3463134765625f,  510.0819091796875f);
+    path.lineTo(681.21795654296875f, 504.81378173828125f);
+    path.moveTo(758.52764892578125f, 521.55963134765625f);
+    path.lineTo(719.1549072265625f,  514.50372314453125f);
+    path.lineTo(689.59063720703125f, 512.0628662109375f);
+    path.lineTo(679.78216552734375f, 507.447845458984375f);
+    return path;
+}
+
+// Tests vertices which become "orphaned" (ie., no connected edges)
+// after simplification.
+static SkPath create_path_14() {
+    SkPath path;
+    path.moveTo(217.326019287109375f, 166.4752960205078125f);
+    path.lineTo(226.279266357421875f, 170.929473876953125f);
+    path.lineTo(234.3973388671875f,   177.0623626708984375f);
+    path.lineTo(262.0921630859375f,   188.746124267578125f);
+    path.moveTo(196.23638916015625f,  174.0722198486328125f);
+    path.lineTo(416.15277099609375f,  180.138214111328125f);
+    path.lineTo(192.651947021484375f, 304.0228271484375f);
+    return path;
+}
+
+static void test_path(GrDrawTarget* dt, GrRenderTarget* rt, const SkPath& path) {
+    GrTessellatingPathRenderer tess;
+    GrPipelineBuilder pipelineBuilder;
+    pipelineBuilder.setRenderTarget(rt);
+    SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+    tess.drawPath(dt, &pipelineBuilder, SK_ColorWHITE, SkMatrix::I(), path, stroke, false);
+}
+
+DEF_GPUTEST(TessellatingPathRendererTests, reporter, factory) {
+    GrContext* context = factory->get(static_cast<GrContextFactory::GLContextType>(0));
+    GrSurfaceDesc desc;
+    desc.fFlags = kRenderTarget_GrSurfaceFlag;
+    desc.fWidth = 800;
+    desc.fHeight = 800;
+    desc.fConfig = kSkia8888_GrPixelConfig;
+    desc.fOrigin = kTopLeft_GrSurfaceOrigin;
+    SkAutoTUnref<GrTexture> texture(
+        context->refScratchTexture(desc, GrContext::kExact_ScratchTexMatch)
+    );
+    GrTestTarget tt;
+    context->getTestTarget(&tt);
+    GrRenderTarget* rt = texture->asRenderTarget();
+    GrDrawTarget* dt = tt.target();
+
+    test_path(dt, rt, create_path_0());
+    test_path(dt, rt, create_path_1());
+    test_path(dt, rt, create_path_2());
+    test_path(dt, rt, create_path_3());
+    test_path(dt, rt, create_path_4());
+    test_path(dt, rt, create_path_5());
+    test_path(dt, rt, create_path_6());
+    test_path(dt, rt, create_path_7());
+    test_path(dt, rt, create_path_8());
+    test_path(dt, rt, create_path_9());
+    test_path(dt, rt, create_path_10());
+    test_path(dt, rt, create_path_11());
+    test_path(dt, rt, create_path_12());
+    test_path(dt, rt, create_path_13());
+    test_path(dt, rt, create_path_14());
+}
+#endif