Tessellating GPU path renderer.
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
2015-02-26 14:58:17 +00:00
|
|
|
/*
|
|
|
|
* 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"
|
2015-08-05 20:57:49 +00:00
|
|
|
#include "SkPath.h"
|
Tessellating GPU path renderer.
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
2015-02-26 14:58:17 +00:00
|
|
|
|
|
|
|
#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();
|
|
|
|
}
|
|
|
|
|
Tessellator: stop copying vertices into Polys and Monotones.
The vertices which are produced by stage 5 of the
tesselator are copied into the Polys and MonotonePolys it
produces. This is necessary because each vertex may have an
arbitrary valence, since it may participate in an arbitrary
number of Polys, so we can't use the vertex's prev/next
pointers to represent all the Monotones of which this
vertex may be a member.
However, each Edge can only be a member of two Polys (one
on each side of the edge). So by adding two prev/next
pointer pairs to each Edge, we can represent each Monotone
as a list of edges instead. Then we no longer need to copy
the vertices.
One wrinkle is that the ear-clipping stage (6) of the
tessellator does require prev/next pointers, in order to
remove vertices as their ears are clipped. So we convert
the edge list into a vertex list during Monotone::emit(),
using the prev/next pointers temporarily for that monotone.
This change improves performance by 7-20% on a non-caching
version of the tessellator, and reduces memory use.
Other notes:
1) Polys are initially constructed empty (no edges), but
with the top vertex, which is needed for splitting
Polys. Edges are added to Polys only after their bottom
vertex is seen.
2) MonotonePolys are always constructed with one edge, so
we always know their handedness (left/right).
MonotonePoly::addEdge() no longer detects when a monotone
is "done" (edge of opposite handedness); this is handled
by Poly::addEdge(), so MonotonePoly::addEdge() has no
return value.
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2029243002
Review-Url: https://codereview.chromium.org/2029243002
2016-06-02 18:36:48 +00:00
|
|
|
// Two "island" triangles inside a containing rect.
|
|
|
|
// This exercises the partnering code in the tessellator.
|
|
|
|
void test_partners(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(300, 200);
|
|
|
|
path.moveTo(20, 80);
|
|
|
|
path.lineTo(80, 80);
|
|
|
|
path.lineTo(80, 20);
|
|
|
|
path.lineTo(20, 20);
|
|
|
|
path.moveTo(30, 30);
|
|
|
|
path.lineTo(45, 50);
|
|
|
|
path.lineTo(30, 70);
|
|
|
|
path.moveTo(70, 30);
|
|
|
|
path.lineTo(70, 70);
|
|
|
|
path.lineTo(55, 50);
|
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
canvas->restore();
|
|
|
|
}
|
|
|
|
|
Tessellating GPU path renderer.
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
2015-02-26 14:58:17 +00:00
|
|
|
// 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:
|
2015-03-26 01:17:31 +00:00
|
|
|
SkString onShortName() override {
|
Tessellating GPU path renderer.
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
2015-02-26 14:58:17 +00:00
|
|
|
return SkString("concavepaths");
|
|
|
|
}
|
|
|
|
|
2015-03-26 01:17:31 +00:00
|
|
|
SkISize onISize() override {
|
Tessellating GPU path renderer.
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
2015-02-26 14:58:17 +00:00
|
|
|
return SkISize::Make(WIDTH, HEIGHT);
|
|
|
|
}
|
|
|
|
|
2015-03-26 01:17:31 +00:00
|
|
|
void onDraw(SkCanvas* canvas) override {
|
Tessellating GPU path renderer.
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
2015-02-26 14:58:17 +00:00
|
|
|
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);
|
Tessellator: stop copying vertices into Polys and Monotones.
The vertices which are produced by stage 5 of the
tesselator are copied into the Polys and MonotonePolys it
produces. This is necessary because each vertex may have an
arbitrary valence, since it may participate in an arbitrary
number of Polys, so we can't use the vertex's prev/next
pointers to represent all the Monotones of which this
vertex may be a member.
However, each Edge can only be a member of two Polys (one
on each side of the edge). So by adding two prev/next
pointer pairs to each Edge, we can represent each Monotone
as a list of edges instead. Then we no longer need to copy
the vertices.
One wrinkle is that the ear-clipping stage (6) of the
tessellator does require prev/next pointers, in order to
remove vertices as their ears are clipped. So we convert
the edge list into a vertex list during Monotone::emit(),
using the prev/next pointers temporarily for that monotone.
This change improves performance by 7-20% on a non-caching
version of the tessellator, and reduces memory use.
Other notes:
1) Polys are initially constructed empty (no edges), but
with the top vertex, which is needed for splitting
Polys. Edges are added to Polys only after their bottom
vertex is seen.
2) MonotonePolys are always constructed with one edge, so
we always know their handedness (left/right).
MonotonePoly::addEdge() no longer detects when a monotone
is "done" (edge of opposite handedness); this is handled
by Poly::addEdge(), so MonotonePoly::addEdge() has no
return value.
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2029243002
Review-Url: https://codereview.chromium.org/2029243002
2016-06-02 18:36:48 +00:00
|
|
|
test_partners(canvas, paint);
|
Tessellating GPU path renderer.
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
2015-02-26 14:58:17 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2015-12-10 21:31:59 +00:00
|
|
|
DEF_GM( return new ConcavePathsGM; )
|