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
|
|
|
|
|
|
|
namespace {
|
|
|
|
// Concave test
|
|
|
|
void test_concave(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->translate(0, 0);
|
2018-08-15 14:23:39 +00:00
|
|
|
path.moveTo(SkIntToScalar(20), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(80), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(30), SkIntToScalar(30))
|
|
|
|
.lineTo(SkIntToScalar(20), SkIntToScalar(80));
|
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
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reverse concave test
|
|
|
|
void test_reverse_concave(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(100, 0);
|
2018-08-15 14:23:39 +00:00
|
|
|
path.moveTo(SkIntToScalar(20), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(20), SkIntToScalar(80))
|
|
|
|
.lineTo(SkIntToScalar(30), SkIntToScalar(30))
|
|
|
|
.lineTo(SkIntToScalar(80), SkIntToScalar(20));
|
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
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
canvas->restore();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bowtie (intersection)
|
|
|
|
void test_bowtie(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(200, 0);
|
2018-08-15 14:23:39 +00:00
|
|
|
path.moveTo(SkIntToScalar(20), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(80), SkIntToScalar(80))
|
|
|
|
.lineTo(SkIntToScalar(80), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(20), SkIntToScalar(80));
|
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
|
|
|
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);
|
2018-08-15 14:23:39 +00:00
|
|
|
path.moveTo(SkIntToScalar(20), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(50), SkIntToScalar(40))
|
|
|
|
.lineTo(SkIntToScalar(80), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(80), SkIntToScalar(80))
|
|
|
|
.lineTo(SkIntToScalar(50), SkIntToScalar(60))
|
|
|
|
.lineTo(SkIntToScalar(20), SkIntToScalar(80));
|
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
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
canvas->restore();
|
|
|
|
}
|
|
|
|
|
2017-02-13 18:55:42 +00:00
|
|
|
// Bowtie with a smaller right hand lobe. The outer vertex of the left hand
|
|
|
|
// lobe intrudes into the interior of the right hand lobe.
|
|
|
|
void test_intruding_vertex(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(400, 0);
|
|
|
|
path.setIsVolatile(true);
|
2018-08-15 14:23:39 +00:00
|
|
|
path.moveTo(20, 20)
|
|
|
|
.lineTo(50, 50)
|
|
|
|
.lineTo(68, 20)
|
|
|
|
.lineTo(68, 80)
|
|
|
|
.lineTo(50, 50)
|
|
|
|
.lineTo(20, 80);
|
2017-02-13 18:55:42 +00:00
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
canvas->restore();
|
|
|
|
}
|
|
|
|
|
|
|
|
// A shape with an edge that becomes inverted on AA stroking and that also contains
|
|
|
|
// a repeated start/end vertex.
|
|
|
|
void test_inversion_repeat_vertex(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(400, 100);
|
|
|
|
path.setIsVolatile(true);
|
2018-08-15 14:23:39 +00:00
|
|
|
path.moveTo(80, 50)
|
|
|
|
.lineTo(40, 80)
|
|
|
|
.lineTo(60, 20)
|
|
|
|
.lineTo(20, 20)
|
|
|
|
.lineTo(39.99f, 80)
|
|
|
|
.lineTo(80, 50);
|
2017-02-13 18:55:42 +00:00
|
|
|
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
|
|
|
// Fish test (intersection/concave)
|
|
|
|
void test_fish(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(0, 100);
|
2018-08-15 14:23:39 +00:00
|
|
|
path.moveTo(SkIntToScalar(20), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(80), SkIntToScalar(80))
|
|
|
|
.lineTo(SkIntToScalar(70), SkIntToScalar(50))
|
|
|
|
.lineTo(SkIntToScalar(80), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(20), SkIntToScalar(80))
|
|
|
|
.lineTo(SkIntToScalar(0), SkIntToScalar(50));
|
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
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
canvas->restore();
|
|
|
|
}
|
|
|
|
|
2017-01-03 21:20:01 +00:00
|
|
|
// Overlapping "Fast-forward" icon: tests coincidence of inner and outer
|
|
|
|
// vertices generated by intersection.
|
|
|
|
void test_fast_forward(SkCanvas* canvas, const SkPaint& 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
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(100, 100);
|
2018-08-15 14:23:39 +00:00
|
|
|
path.moveTo(SkIntToScalar(20), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(60), SkIntToScalar(50))
|
|
|
|
.lineTo(SkIntToScalar(20), SkIntToScalar(80))
|
|
|
|
.moveTo(SkIntToScalar(40), SkIntToScalar(20))
|
|
|
|
.lineTo(SkIntToScalar(40), SkIntToScalar(80))
|
|
|
|
.lineTo(SkIntToScalar(80), SkIntToScalar(50));
|
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
|
|
|
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);
|
2018-08-15 14:23:39 +00:00
|
|
|
path.addPoly({{20,20}, {80,20}, {80,80}, {20,80}}, false)
|
|
|
|
.addPoly({{30,30}, {30,70}, {70,70}, {70,30}}, false);
|
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
|
|
|
canvas->drawPath(path, paint);
|
|
|
|
canvas->restore();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Star test (self-intersecting)
|
|
|
|
void test_star(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(300, 100);
|
2018-08-15 14:23:39 +00:00
|
|
|
canvas->drawPath(SkPath().addPoly({{30,20}, {50,80}, {70,20}, {20,57}, {80,57}}, false),
|
|
|
|
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
|
|
|
canvas->restore();
|
|
|
|
}
|
|
|
|
|
GrTessellator: implement out-of-range splitting and AEL rewinding.
Due to floating point inaccuracy, when intersecting edges, the
intersection point may fall above one of the edges' top vertices
or below one of the bottom vertices. In these cases, we were simply
splitting one edge on the relevant endpoint of the other edge. This
is incorrect if the intersection is far from the endpoint (e.g.,
the test case in the linked bug, where one of the intersected edges
is near-horizontal but the intersection falls below both of its
endpoints, in the middle of the edge.)
The correct solution is to split both edges as normal, and take care
to produce edges with the correct ordering where the intersection is
above or below an edge. However, since the new vertex may be above
the current vertex, simply restarting intersection checks at the
current vertex won't work. We need to process the intersection
vertex before the current one.
This introduces another problem: unlike all other splitting modes
(which always shorten edges), splitting an edge above the top or
below the bottom can lengthen it, causing it to violate the AEL
with an adjacent edge which then shortens it back to the original
point (in cleanup_active_edges()). Since the splitting and merging
code can't agree, we loop forever.
Instead of simply fusing neighboring edges in cleanup_active_edges(),
the proper fix to this problem is to detect the AEL violation and
rewind all processing to the vertex above it. For performance, we
only rewind when we detect that a split edge is no longer ordered
within the mesh (merge_enclosing_edges()) or within the the AEL
(rewind_if_necessary()). We also store the enclosing edges of each
vertex, which allows us to rewind quickly, since we know exactly which
edges need to be added/removed from the AEL.
cleanup_active_edges(), fix_active_state() and Vertex::fProcessed have
been removed. In their place are rewind_active_edges() and
rewind_if_necessary(), which uses the same logic as
cleanup_active_edges() but uses it to know when to rewind.
Bug: skia:5026
Change-Id: I3638a429f5428498d6df6bb7b98c67374dc291aa
Reviewed-on: https://skia-review.googlesource.com/18900
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Stephen White <senorblanco@chromium.org>
2017-06-06 18:51:19 +00:00
|
|
|
// Exercise a case where the intersection is below a bottom edge.
|
|
|
|
void test_twist(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
path.moveTo( 0.5, 6);
|
|
|
|
path.lineTo(5.8070392608642578125, 6.4612660408020019531);
|
|
|
|
path.lineTo(-2.9186885356903076172, 2.811046600341796875);
|
|
|
|
path.lineTo(0.49999994039535522461, -1.4124038219451904297);
|
|
|
|
canvas->translate(420, 220);
|
|
|
|
canvas->scale(10, 10);
|
|
|
|
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
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
2018-06-01 13:49:39 +00:00
|
|
|
// A split edge causes one half to be merged to zero winding (destroyed).
|
|
|
|
// Test that the other half of the split doesn't also get zero winding.
|
|
|
|
void test_winding_merged_to_zero(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(400, 350);
|
|
|
|
path.moveTo(20, 80);
|
|
|
|
path.moveTo(70, -0.000001f);
|
|
|
|
path.lineTo(70, 0.0);
|
|
|
|
path.lineTo(60, -30.0);
|
|
|
|
path.lineTo(40, 20.0);
|
|
|
|
path.moveTo(50, 50.0);
|
|
|
|
path.lineTo(50, -50.0);
|
|
|
|
path.lineTo(10, 50.0);
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2018-07-17 20:14:31 +00:00
|
|
|
// Collinear outer boundary edges. In the edge-AA codepath, this creates an overlap region
|
|
|
|
// which contains a boundary edge. It can't be removed, but it must have the correct winding.
|
|
|
|
void test_collinear_outer_boundary_edge(SkCanvas* canvas, const SkPaint& paint) {
|
|
|
|
SkPath path;
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(400, 400);
|
|
|
|
path.moveTo(20, 20);
|
|
|
|
path.lineTo(20, 50);
|
|
|
|
path.lineTo(50, 50);
|
|
|
|
path.moveTo(80, 50);
|
|
|
|
path.lineTo(50, 50);
|
|
|
|
path.lineTo(80, 20);
|
|
|
|
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
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2017-02-13 18:55:42 +00:00
|
|
|
DEF_SIMPLE_GM(concavepaths, canvas, 500, 600) {
|
2017-01-12 22:15:50 +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);
|
2017-02-13 18:55:42 +00:00
|
|
|
test_intruding_vertex(canvas, paint);
|
2017-01-12 22:15:50 +00:00
|
|
|
test_fish(canvas, paint);
|
|
|
|
test_fast_forward(canvas, paint);
|
|
|
|
test_hole(canvas, paint);
|
|
|
|
test_star(canvas, paint);
|
GrTessellator: implement out-of-range splitting and AEL rewinding.
Due to floating point inaccuracy, when intersecting edges, the
intersection point may fall above one of the edges' top vertices
or below one of the bottom vertices. In these cases, we were simply
splitting one edge on the relevant endpoint of the other edge. This
is incorrect if the intersection is far from the endpoint (e.g.,
the test case in the linked bug, where one of the intersected edges
is near-horizontal but the intersection falls below both of its
endpoints, in the middle of the edge.)
The correct solution is to split both edges as normal, and take care
to produce edges with the correct ordering where the intersection is
above or below an edge. However, since the new vertex may be above
the current vertex, simply restarting intersection checks at the
current vertex won't work. We need to process the intersection
vertex before the current one.
This introduces another problem: unlike all other splitting modes
(which always shorten edges), splitting an edge above the top or
below the bottom can lengthen it, causing it to violate the AEL
with an adjacent edge which then shortens it back to the original
point (in cleanup_active_edges()). Since the splitting and merging
code can't agree, we loop forever.
Instead of simply fusing neighboring edges in cleanup_active_edges(),
the proper fix to this problem is to detect the AEL violation and
rewind all processing to the vertex above it. For performance, we
only rewind when we detect that a split edge is no longer ordered
within the mesh (merge_enclosing_edges()) or within the the AEL
(rewind_if_necessary()). We also store the enclosing edges of each
vertex, which allows us to rewind quickly, since we know exactly which
edges need to be added/removed from the AEL.
cleanup_active_edges(), fix_active_state() and Vertex::fProcessed have
been removed. In their place are rewind_active_edges() and
rewind_if_necessary(), which uses the same logic as
cleanup_active_edges() but uses it to know when to rewind.
Bug: skia:5026
Change-Id: I3638a429f5428498d6df6bb7b98c67374dc291aa
Reviewed-on: https://skia-review.googlesource.com/18900
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Stephen White <senorblanco@chromium.org>
2017-06-06 18:51:19 +00:00
|
|
|
test_twist(canvas, paint);
|
2017-02-13 18:55:42 +00:00
|
|
|
test_inversion_repeat_vertex(canvas, paint);
|
2017-01-12 22:15:50 +00:00
|
|
|
test_stairstep(canvas, paint);
|
|
|
|
test_stairstep2(canvas, paint);
|
|
|
|
test_overlapping(canvas, paint);
|
|
|
|
test_partners(canvas, paint);
|
2018-06-01 13:49:39 +00:00
|
|
|
test_winding_merged_to_zero(canvas, paint);
|
2017-01-12 22:15:50 +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);
|
2018-07-17 20:14:31 +00:00
|
|
|
test_collinear_outer_boundary_edge(canvas, paint);
|
2017-01-12 22:15:50 +00:00
|
|
|
test_coincident_edges_1(canvas, paint);
|
|
|
|
test_coincident_edges_2(canvas, paint);
|
|
|
|
test_coincident_edges_3(canvas, paint);
|
|
|
|
test_coincident_edges_4(canvas, paint);
|
|
|
|
}
|