Screenspace AA tessellated GPU path rendering.

This is an approach to antialiased concave path rendering
on the GPU without using MSAA. It uses GrTessellator to
extract boundary contours from the given path, then
inflates by half a pixel in screen space each direction,
then renders the result with zero alpha on the outer
contour and one alpha on in the inner contour. This
requires two passes through the tessellation code: one
to extract the boundaries, then one to tessellate the
result.

This gives approximately a 3X improvement on the IE
chalkboard demo in non-MSAA mode, a 30-40% improvement
on MotionMark's "Fill Paths", and a ~3X improvement on
MotionMark's "canvas arcTo segments".

It works best for large, simple paths, so there's currently
a limit of 10 verbs in the onCanDrawPath() check. This
dovetails nicely with the distance field path renderer's
support for small, detailed (and cached) paths.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1152733009

Review-Url: https://codereview.chromium.org/1152733009
This commit is contained in:
senorblanco 2016-08-31 08:41:57 -07:00 committed by Commit bot
parent 4bcd62e331
commit 9992bdef8a
5 changed files with 606 additions and 163 deletions

View File

@ -24,7 +24,7 @@ namespace GrDefaultGeoProcFactory {
struct PositionCoverageAttr {
SkPoint fPosition;
GrColor fCoverage;
float fCoverage;
};
struct PositionColorAttr {
@ -35,7 +35,7 @@ namespace GrDefaultGeoProcFactory {
struct PositionColorCoverageAttr {
SkPoint fPosition;
SkColor fColor;
GrColor fCoverage;
float fCoverage;
};
struct PositionLocalCoordAttr {
@ -46,7 +46,7 @@ namespace GrDefaultGeoProcFactory {
struct PositionLocalCoordCoverageAttr {
SkPoint fPosition;
SkPoint fLocalCoord;
GrColor fCoverage;
float fCoverage;
};
struct PositionColorLocalCoordAttr {
@ -59,7 +59,7 @@ namespace GrDefaultGeoProcFactory {
SkPoint fPosition;
GrColor fColor;
SkPoint fLocalCoord;
GrColor fCoverage;
float fCoverage;
};
struct Color {

View File

@ -36,7 +36,6 @@ GrPathRendererChain::GrPathRendererChain(GrContext* context) {
if (caps.sampleShadingSupport()) {
this->addPathRenderer(new GrMSAAPathRenderer)->unref();
}
this->addPathRenderer(new GrTessellatingPathRenderer)->unref();
this->addPathRenderer(new GrAAHairLinePathRenderer)->unref();
this->addPathRenderer(new GrAAConvexPathRenderer)->unref();
this->addPathRenderer(new GrAALinearizingConvexPathRenderer)->unref();
@ -44,6 +43,7 @@ GrPathRendererChain::GrPathRendererChain(GrContext* context) {
this->addPathRenderer(new GrPLSPathRenderer)->unref();
}
this->addPathRenderer(new GrAADistanceFieldPathRenderer)->unref();
this->addPathRenderer(new GrTessellatingPathRenderer)->unref();
this->addPathRenderer(new GrDefaultPathRenderer(caps.twoSidedStencilSupport(),
caps.stencilWrapOpsSupport()))->unref();
}

View File

@ -7,6 +7,7 @@
#include "GrTessellator.h"
#include "GrDefaultGeoProcFactory.h"
#include "GrPathUtils.h"
#include "SkChunkAlloc.h"
@ -16,7 +17,7 @@
#include <stdio.h>
/*
* There are six stages to the algorithm:
* There are six stages to the basic algorithm:
*
* 1) Linearize the path contours into piecewise linear segments (path_to_contours()).
* 2) Build a mesh of edges connecting the vertices (build_edges()).
@ -25,6 +26,16 @@
* 5) Tessellate the simplified mesh into monotone polygons (tessellate()).
* 6) Triangulate the monotone polygons directly into a vertex buffer (polys_to_triangles()).
*
* For screenspace antialiasing, the algorithm is modified as follows:
*
* Run steps 1-5 above to produce polygons.
* 5b) Apply fill rules to extract boundary contours from the polygons (extract_boundaries()).
* 5c) Simplify boundaries to remove "pointy" vertices which cause inversions (simplify_boundary()).
* 5d) Displace edges by half a pixel inward and outward along their normals. Intersect to find
* new vertices, and set zero alpha on the exterior and one alpha on the interior. Build a new
* antialiased mesh from those vertices (boundary_to_aa_mesh()).
* Run steps 3-6 above on the new mesh, and produce antialiased 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).
*
@ -130,11 +141,12 @@ void list_remove(T* t, T** head, T** tail) {
*/
struct Vertex {
Vertex(const SkPoint& point)
Vertex(const SkPoint& point, uint8_t alpha)
: fPoint(point), fPrev(nullptr), fNext(nullptr)
, fFirstEdgeAbove(nullptr), fLastEdgeAbove(nullptr)
, fFirstEdgeBelow(nullptr), fLastEdgeBelow(nullptr)
, fProcessed(false)
, fAlpha(alpha)
#if LOGGING_ENABLED
, fID (-1.0f)
#endif
@ -147,6 +159,7 @@ struct Vertex {
Edge* fFirstEdgeBelow; // Linked list of edges below this vertex.
Edge* fLastEdgeBelow; // "
bool fProcessed; // Has this vertex been seen in simplify()?
uint8_t fAlpha;
#if LOGGING_ENABLED
float fID; // Identifier used for logging.
#endif
@ -154,6 +167,11 @@ struct Vertex {
/***************************************************************************************/
struct AAParams {
bool fTweakAlpha;
GrColor fColor;
};
typedef bool (*CompareFunc)(const SkPoint& a, const SkPoint& b);
struct Comparator {
@ -177,33 +195,43 @@ bool sweep_gt_vert(const SkPoint& a, const SkPoint& b) {
return a.fY == b.fY ? a.fX > b.fX : a.fY > b.fY;
}
inline SkPoint* emit_vertex(Vertex* v, SkPoint* data) {
*data++ = v->fPoint;
return data;
inline void* emit_vertex(Vertex* v, const AAParams* aaParams, void* data) {
if (!aaParams) {
SkPoint* d = static_cast<SkPoint*>(data);
*d++ = v->fPoint;
return d;
}
if (aaParams->fTweakAlpha) {
auto d = static_cast<GrDefaultGeoProcFactory::PositionColorAttr*>(data);
d->fPosition = v->fPoint;
d->fColor = SkAlphaMulQ(aaParams->fColor, v->fAlpha);
d++;
return d;
}
auto d = static_cast<GrDefaultGeoProcFactory::PositionColorCoverageAttr*>(data);
d->fPosition = v->fPoint;
d->fColor = aaParams->fColor;
d->fCoverage = GrNormalizeByteToFloat(v->fAlpha);
d++;
return d;
}
SkPoint* emit_triangle(Vertex* v0, Vertex* v1, Vertex* v2, SkPoint* 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);
void* emit_triangle(Vertex* v0, Vertex* v1, Vertex* v2, const AAParams* aaParams, void* data) {
#if TESSELLATOR_WIREFRAME
data = emit_vertex(v0, aaParams, data);
data = emit_vertex(v1, aaParams, data);
data = emit_vertex(v1, aaParams, data);
data = emit_vertex(v2, aaParams, data);
data = emit_vertex(v2, aaParams, data);
data = emit_vertex(v0, aaParams, data);
#else
data = emit_vertex(v0, data);
data = emit_vertex(v1, data);
data = emit_vertex(v2, data);
data = emit_vertex(v0, aaParams, data);
data = emit_vertex(v1, aaParams, data);
data = emit_vertex(v2, aaParams, data);
#endif
return data;
}
struct EdgeList {
EdgeList() : fHead(nullptr), fTail(nullptr) {}
Edge* fHead;
Edge* fTail;
};
struct VertexList {
VertexList() : fHead(nullptr), fTail(nullptr) {}
Vertex* fHead;
@ -217,8 +245,21 @@ struct VertexList {
void prepend(Vertex* v) {
insert(v, nullptr, fHead);
}
void close() {
if (fHead && fTail) {
fTail->fNext = fHead;
fHead->fPrev = fTail;
}
}
};
// Round to nearest quarter-pixel. This is used for screenspace tessellation.
inline void round(SkPoint* p) {
p->fX = SkScalarRoundToScalar(p->fX * SkFloatToScalar(4.0f)) * SkFloatToScalar(0.25f);
p->fY = SkScalarRoundToScalar(p->fY * SkFloatToScalar(4.0f)) * SkFloatToScalar(0.25f);
}
/**
* 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().
@ -320,8 +361,33 @@ struct Edge {
p->fY = SkDoubleToScalar(fTop->fPoint.fY + s * fDY);
return true;
}
bool isActive(EdgeList* activeEdges) const {
return activeEdges && (fLeft || fRight || activeEdges->fHead == this);
};
struct EdgeList {
EdgeList() : fHead(nullptr), fTail(nullptr), fNext(nullptr), fCount(0) {}
Edge* fHead;
Edge* fTail;
EdgeList* fNext;
int fCount;
void insert(Edge* edge, Edge* prev, Edge* next) {
list_insert<Edge, &Edge::fLeft, &Edge::fRight>(edge, prev, next, &fHead, &fTail);
fCount++;
}
void append(Edge* e) {
insert(e, fTail, nullptr);
}
void remove(Edge* edge) {
list_remove<Edge, &Edge::fLeft, &Edge::fRight>(edge, &fHead, &fTail);
fCount--;
}
void close() {
if (fHead && fTail) {
fTail->fRight = fHead;
fHead->fLeft = fTail;
}
}
bool contains(Edge* edge) const {
return edge->fLeft || edge->fRight || fHead == edge;
}
};
@ -372,7 +438,7 @@ struct Poly {
}
}
SkPoint* emit(SkPoint* data) {
void* emit(const AAParams* aaParams, void* data) {
Edge* e = fFirstEdge;
e->fTop->fPrev = e->fTop->fNext = nullptr;
VertexList vertices;
@ -399,7 +465,7 @@ struct Poly {
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);
data = emit_triangle(prev, curr, next, aaParams, data);
v->fPrev->fNext = v->fNext;
v->fNext->fPrev = v->fPrev;
if (v->fPrev == first) {
@ -455,13 +521,13 @@ struct Poly {
}
return poly;
}
SkPoint* emit(SkPoint *data) {
void* emit(const AAParams* aaParams, void *data) {
if (fCount < 3) {
return data;
}
LOG("emit() %d, size %d\n", fID, fCount);
for (MonotonePoly* m = fHead; m != nullptr; m = m->fNext) {
data = m->emit(data);
data = m->emit(aaParams, data);
}
return data;
}
@ -491,9 +557,16 @@ Poly* new_poly(Poly** head, Vertex* v, int winding, SkChunkAlloc& alloc) {
return poly;
}
EdgeList* new_contour(EdgeList** head, SkChunkAlloc& alloc) {
EdgeList* contour = ALLOC_NEW(EdgeList, (), alloc);
contour->fNext = *head;
*head = contour;
return contour;
}
Vertex* append_point_to_contour(const SkPoint& p, Vertex* prev, Vertex** head,
SkChunkAlloc& alloc) {
Vertex* v = ALLOC_NEW(Vertex, (p), alloc);
Vertex* v = ALLOC_NEW(Vertex, (p, 255), alloc);
#if LOGGING_ENABLED
static float gID = 0.0f;
v->fID = gID++;
@ -578,7 +651,7 @@ void path_to_contours(const SkPath& path, SkScalar tolerance, const SkRect& clip
if (path.isInverseFillType()) {
SkPoint quad[4];
clipBounds.toQuad(quad);
for (int i = 3; i >= 0; i--) {
for (int i = 0; i < 4; i++) {
prev = append_point_to_contour(quad[i], prev, &head, alloc);
}
head->fPrev = prev;
@ -649,14 +722,18 @@ void path_to_contours(const SkPath& path, SkScalar tolerance, const SkRect& clip
}
}
inline bool apply_fill_type(SkPath::FillType fillType, int winding) {
inline bool apply_fill_type(SkPath::FillType fillType, Poly* poly) {
if (!poly) {
return false;
}
int winding = poly->fWinding;
switch (fillType) {
case SkPath::kWinding_FillType:
return winding != 0;
case SkPath::kEvenOdd_FillType:
return (winding & 1) != 0;
case SkPath::kInverseWinding_FillType:
return winding == 1;
return winding == -1;
case SkPath::kInverseEvenOdd_FillType:
return (winding & 1) == 1;
default:
@ -665,8 +742,9 @@ inline bool apply_fill_type(SkPath::FillType fillType, int winding) {
}
}
Edge* new_edge(Vertex* prev, Vertex* next, SkChunkAlloc& alloc, Comparator& c) {
int winding = c.sweep_lt(prev->fPoint, next->fPoint) ? 1 : -1;
Edge* new_edge(Vertex* prev, Vertex* next, SkChunkAlloc& alloc, Comparator& c,
int winding_scale = 1) {
int winding = c.sweep_lt(prev->fPoint, next->fPoint) ? winding_scale : -winding_scale;
Vertex* top = winding < 0 ? next : prev;
Vertex* bottom = winding < 0 ? prev : next;
return ALLOC_NEW(Edge, (top, bottom, winding), alloc);
@ -674,15 +752,15 @@ Edge* new_edge(Vertex* prev, Vertex* next, SkChunkAlloc& alloc, Comparator& c) {
void remove_edge(Edge* edge, EdgeList* edges) {
LOG("removing edge %g -> %g\n", edge->fTop->fID, edge->fBottom->fID);
SkASSERT(edge->isActive(edges));
list_remove<Edge, &Edge::fLeft, &Edge::fRight>(edge, &edges->fHead, &edges->fTail);
SkASSERT(edges->contains(edge));
edges->remove(edge);
}
void insert_edge(Edge* edge, Edge* prev, EdgeList* edges) {
LOG("inserting edge %g -> %g\n", edge->fTop->fID, edge->fBottom->fID);
SkASSERT(!edge->isActive(edges));
SkASSERT(!edges->contains(edge));
Edge* next = prev ? prev->fRight : edges->fHead;
list_insert<Edge, &Edge::fLeft, &Edge::fRight>(edge, prev, next, &edges->fHead, &edges->fTail);
edges->insert(edge, prev, next);
}
void find_enclosing_edges(Vertex* v, EdgeList* edges, Edge** left, Edge** right) {
@ -722,7 +800,7 @@ void find_enclosing_edges(Edge* edge, EdgeList* edges, Comparator& c, Edge** lef
}
void fix_active_state(Edge* edge, EdgeList* activeEdges, Comparator& c) {
if (edge->isActive(activeEdges)) {
if (activeEdges && activeEdges->contains(edge)) {
if (edge->fBottom->fProcessed || !edge->fTop->fProcessed) {
remove_edge(edge, activeEdges);
}
@ -791,7 +869,7 @@ void erase_edge_if_zero_winding(Edge* edge, EdgeList* edges) {
LOG("erasing edge (%g -> %g)\n", edge->fTop->fID, edge->fBottom->fID);
remove_edge_above(edge);
remove_edge_below(edge);
if (edge->isActive(edges)) {
if (edges && edges->contains(edge)) {
remove_edge(edge, edges);
}
}
@ -928,9 +1006,24 @@ void split_edge(Edge* edge, Vertex* v, EdgeList* activeEdges, Comparator& c, SkC
}
}
Edge* connect(Vertex* prev, Vertex* next, SkChunkAlloc& alloc, Comparator c,
int winding_scale = 1) {
Edge* edge = new_edge(prev, next, alloc, c, winding_scale);
if (edge->fWinding > 0) {
insert_edge_below(edge, prev, c);
insert_edge_above(edge, next, c);
} else {
insert_edge_below(edge, next, c);
insert_edge_above(edge, prev, c);
}
merge_collinear_edges(edge, nullptr, c);
return edge;
}
void merge_vertices(Vertex* src, Vertex* dst, Vertex** head, Comparator& c, SkChunkAlloc& alloc) {
LOG("found coincident verts at %g, %g; merging %g into %g\n", src->fPoint.fX, src->fPoint.fY,
src->fID, dst->fID);
dst->fAlpha = SkTMax(src->fAlpha, dst->fAlpha);
for (Edge* edge = src->fFirstEdgeAbove; edge;) {
Edge* next = edge->fNextEdgeAbove;
set_bottom(edge, dst, nullptr, c);
@ -944,6 +1037,11 @@ void merge_vertices(Vertex* src, Vertex* dst, Vertex** head, Comparator& c, SkCh
list_remove<Vertex, &Vertex::fPrev, &Vertex::fNext>(src, head, nullptr);
}
uint8_t max_edge_alpha(Edge* a, Edge* b) {
return SkTMax(SkTMax(a->fTop->fAlpha, a->fBottom->fAlpha),
SkTMax(b->fTop->fAlpha, b->fBottom->fAlpha));
}
Vertex* check_for_intersection(Edge* edge, Edge* other, EdgeList* activeEdges, Comparator& c,
SkChunkAlloc& alloc) {
SkPoint p;
@ -979,7 +1077,8 @@ Vertex* check_for_intersection(Edge* edge, Edge* other, EdgeList* activeEdges, C
} else if (coincident(nextV->fPoint, p)) {
v = nextV;
} else {
v = ALLOC_NEW(Vertex, (p), alloc);
uint8_t alpha = max_edge_alpha(edge, other);
v = ALLOC_NEW(Vertex, (p, alpha), 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);
@ -999,10 +1098,13 @@ Vertex* check_for_intersection(Edge* edge, Edge* other, EdgeList* activeEdges, C
return nullptr;
}
void sanitize_contours(Vertex** contours, int contourCnt) {
void sanitize_contours(Vertex** contours, int contourCnt, bool approximate) {
for (int i = 0; i < contourCnt; ++i) {
SkASSERT(contours[i]);
for (Vertex* v = contours[i];;) {
if (approximate) {
round(&v->fPoint);
}
if (coincident(v->fPrev->fPoint, v->fPoint)) {
LOG("vertex %g,%g coincident; removing\n", v->fPoint.fX, v->fPoint.fY);
if (v->fPrev == v) {
@ -1042,15 +1144,7 @@ Vertex* build_edges(Vertex** contours, int contourCnt, Comparator& c, SkChunkAll
for (int i = 0; i < contourCnt; ++i) {
for (Vertex* v = contours[i]; v != nullptr;) {
Vertex* vNext = v->fNext;
Edge* edge = new_edge(v->fPrev, v, alloc, c);
if (edge->fWinding > 0) {
insert_edge_below(edge, v->fPrev, c);
insert_edge_above(edge, v, c);
} else {
insert_edge_below(edge, v, c);
insert_edge_above(edge, v->fPrev, c);
}
merge_collinear_edges(edge, nullptr, c);
connect(v->fPrev, v, alloc, c);
if (prev) {
prev->fNext = v;
v->fPrev = prev;
@ -1145,7 +1239,7 @@ void simplify(Vertex* vertices, Comparator& c, SkChunkAlloc& alloc) {
continue;
}
#if LOGGING_ENABLED
LOG("\nvertex %g: (%g,%g)\n", v->fID, v->fPoint.fX, v->fPoint.fY);
LOG("\nvertex %g: (%g,%g), alpha %d\n", v->fID, v->fPoint.fX, v->fPoint.fY, v->fAlpha);
#endif
Edge* leftEnclosingEdge = nullptr;
Edge* rightEnclosingEdge = nullptr;
@ -1175,6 +1269,12 @@ void simplify(Vertex* vertices, Comparator& c, SkChunkAlloc& alloc) {
}
} while (restartChecks);
if (v->fAlpha == 0) {
if ((leftEnclosingEdge && leftEnclosingEdge->fWinding < 0) &&
(rightEnclosingEdge && rightEnclosingEdge->fWinding > 0)) {
v->fAlpha = max_edge_alpha(leftEnclosingEdge, rightEnclosingEdge);
}
}
for (Edge* e = v->fFirstEdgeAbove; e; e = e->fNextEdgeAbove) {
remove_edge(e, &activeEdges);
}
@ -1198,7 +1298,7 @@ Poly* tessellate(Vertex* vertices, SkChunkAlloc& alloc) {
continue;
}
#if LOGGING_ENABLED
LOG("\nvertex %g: (%g,%g)\n", v->fID, v->fPoint.fX, v->fPoint.fY);
LOG("\nvertex %g: (%g,%g), alpha %d\n", v->fID, v->fPoint.fX, v->fPoint.fY, v->fAlpha);
#endif
Edge* leftEnclosingEdge = nullptr;
Edge* rightEnclosingEdge = nullptr;
@ -1298,18 +1398,220 @@ Poly* tessellate(Vertex* vertices, SkChunkAlloc& alloc) {
return polys;
}
bool is_boundary_edge(Edge* edge, SkPath::FillType fillType) {
return apply_fill_type(fillType, edge->fLeftPoly) !=
apply_fill_type(fillType, edge->fRightPoly);
}
bool is_boundary_start(Edge* edge, SkPath::FillType fillType) {
return !apply_fill_type(fillType, edge->fLeftPoly) &&
apply_fill_type(fillType, edge->fRightPoly);
}
Vertex* remove_non_boundary_edges(Vertex* vertices, SkPath::FillType fillType,
SkChunkAlloc& alloc) {
for (Vertex* v = vertices; v != nullptr; v = v->fNext) {
for (Edge* e = v->fFirstEdgeBelow; e != nullptr;) {
Edge* next = e->fNextEdgeBelow;
if (!is_boundary_edge(e, fillType)) {
remove_edge_above(e);
remove_edge_below(e);
}
e = next;
}
}
return vertices;
}
// This is different from Edge::intersect, in that it intersects lines, not line segments.
bool intersect(const Edge& a, const Edge& b, SkPoint* point) {
double denom = a.fDX * b.fDY - a.fDY * b.fDX;
if (denom == 0.0) {
return false;
}
double scale = 1.0f / denom;
point->fX = SkDoubleToScalar((b.fDX * a.fC - a.fDX * b.fC) * scale);
point->fY = SkDoubleToScalar((b.fDY * a.fC - a.fDY * b.fC) * scale);
round(point);
return true;
}
void get_edge_normal(const Edge* e, SkVector* normal) {
normal->setNormalize(SkDoubleToScalar(e->fDX) * e->fWinding,
SkDoubleToScalar(e->fDY) * e->fWinding);
}
// Stage 5c: detect and remove "pointy" vertices whose edge normals point in opposite directions
// and whose adjacent vertices are less than a quarter pixel from an edge. These are guaranteed to
// invert on stroking.
void simplify_boundary(EdgeList* boundary, Comparator& c, SkChunkAlloc& alloc) {
Edge* prevEdge = boundary->fTail;
SkVector prevNormal;
get_edge_normal(prevEdge, &prevNormal);
for (Edge* e = boundary->fHead; e != nullptr;) {
Vertex* prev = prevEdge->fWinding == 1 ? prevEdge->fTop : prevEdge->fBottom;
Vertex* next = e->fWinding == 1 ? e->fBottom : e->fTop;
double dist = e->dist(prev->fPoint);
SkVector normal;
get_edge_normal(e, &normal);
float denom = 0.25f * static_cast<float>(e->fDX * e->fDX + e->fDY * e->fDY);
if (prevNormal.dot(normal) < 0.0 && (dist * dist) <= denom) {
Edge* join = new_edge(prev, next, alloc, c);
insert_edge(join, e, boundary);
remove_edge(prevEdge, boundary);
remove_edge(e, boundary);
if (join->fLeft && join->fRight) {
prevEdge = join->fLeft;
e = join;
} else {
prevEdge = boundary->fTail;
e = boundary->fHead; // join->fLeft ? join->fLeft : join;
}
get_edge_normal(prevEdge, &prevNormal);
} else {
prevEdge = e;
prevNormal = normal;
e = e->fRight;
}
}
}
// Stage 5d: Displace edges by half a pixel inward and outward along their normals. Intersect to
// find new vertices, and set zero alpha on the exterior and one alpha on the interior. Build a
// new antialiased mesh from those vertices.
void boundary_to_aa_mesh(EdgeList* boundary, VertexList* mesh, Comparator& c, SkChunkAlloc& alloc) {
EdgeList outerContour;
Edge* prevEdge = boundary->fTail;
float radius = 0.5f;
double offset = radius * sqrt(prevEdge->fDX * prevEdge->fDX + prevEdge->fDY * prevEdge->fDY)
* prevEdge->fWinding;
Edge prevInner(prevEdge->fTop, prevEdge->fBottom, prevEdge->fWinding);
prevInner.fC -= offset;
Edge prevOuter(prevEdge->fTop, prevEdge->fBottom, prevEdge->fWinding);
prevOuter.fC += offset;
VertexList innerVertices;
VertexList outerVertices;
SkScalar innerCount = SK_Scalar1, outerCount = SK_Scalar1;
for (Edge* e = boundary->fHead; e != nullptr; e = e->fRight) {
double offset = radius * sqrt(e->fDX * e->fDX + e->fDY * e->fDY) * e->fWinding;
Edge inner(e->fTop, e->fBottom, e->fWinding);
inner.fC -= offset;
Edge outer(e->fTop, e->fBottom, e->fWinding);
outer.fC += offset;
SkPoint innerPoint, outerPoint;
if (intersect(prevInner, inner, &innerPoint) &&
intersect(prevOuter, outer, &outerPoint)) {
Vertex* innerVertex = ALLOC_NEW(Vertex, (innerPoint, 255), alloc);
Vertex* outerVertex = ALLOC_NEW(Vertex, (outerPoint, 0), alloc);
if (innerVertices.fTail && outerVertices.fTail) {
Edge innerEdge(innerVertices.fTail, innerVertex, 1);
Edge outerEdge(outerVertices.fTail, outerVertex, 1);
SkVector innerNormal;
get_edge_normal(&innerEdge, &innerNormal);
SkVector outerNormal;
get_edge_normal(&outerEdge, &outerNormal);
SkVector normal;
get_edge_normal(prevEdge, &normal);
if (normal.dot(innerNormal) < 0) {
innerPoint += innerVertices.fTail->fPoint * innerCount;
innerCount++;
innerPoint *= SkScalarInvert(innerCount);
innerVertices.fTail->fPoint = innerVertex->fPoint = innerPoint;
} else {
innerCount = SK_Scalar1;
}
if (normal.dot(outerNormal) < 0) {
outerPoint += outerVertices.fTail->fPoint * outerCount;
outerCount++;
outerPoint *= SkScalarInvert(outerCount);
outerVertices.fTail->fPoint = outerVertex->fPoint = outerPoint;
} else {
outerCount = SK_Scalar1;
}
}
innerVertices.append(innerVertex);
outerVertices.append(outerVertex);
prevEdge = e;
}
prevInner = inner;
prevOuter = outer;
}
innerVertices.close();
outerVertices.close();
Vertex* innerVertex = innerVertices.fHead;
Vertex* outerVertex = outerVertices.fHead;
// Alternate clockwise and counterclockwise polys, so the tesselator
// doesn't cancel out the interior edges.
if (!innerVertex || !outerVertex) {
return;
}
do {
connect(outerVertex->fNext, outerVertex, alloc, c);
connect(innerVertex->fNext, innerVertex, alloc, c, 2);
connect(innerVertex, outerVertex->fNext, alloc, c, 2);
connect(outerVertex, innerVertex, alloc, c, 2);
Vertex* innerNext = innerVertex->fNext;
Vertex* outerNext = outerVertex->fNext;
mesh->append(innerVertex);
mesh->append(outerVertex);
innerVertex = innerNext;
outerVertex = outerNext;
} while (innerVertex != innerVertices.fHead && outerVertex != outerVertices.fHead);
}
void extract_boundary(EdgeList* boundary, Edge* e, SkPath::FillType fillType, SkChunkAlloc& alloc) {
bool down = is_boundary_start(e, fillType);
while (e) {
e->fWinding = down ? 1 : -1;
Edge* next;
boundary->append(e);
if (down) {
// Find outgoing edge, in clockwise order.
if ((next = e->fNextEdgeAbove)) {
down = false;
} else if ((next = e->fBottom->fLastEdgeBelow)) {
down = true;
} else if ((next = e->fPrevEdgeAbove)) {
down = false;
}
} else {
// Find outgoing edge, in counter-clockwise order.
if ((next = e->fPrevEdgeBelow)) {
down = true;
} else if ((next = e->fTop->fFirstEdgeAbove)) {
down = false;
} else if ((next = e->fNextEdgeBelow)) {
down = true;
}
}
remove_edge_above(e);
remove_edge_below(e);
e = next;
}
}
// Stage 5b: Extract boundary edges.
EdgeList* extract_boundaries(Vertex* vertices, SkPath::FillType fillType, SkChunkAlloc& alloc) {
LOG("extracting boundaries\n");
vertices = remove_non_boundary_edges(vertices, fillType, alloc);
EdgeList* boundaries = nullptr;
for (Vertex* v = vertices; v != nullptr; v = v->fNext) {
while (v->fFirstEdgeBelow) {
EdgeList* boundary = new_contour(&boundaries, alloc);
extract_boundary(boundary, v->fFirstEdgeBelow, fillType, alloc);
}
}
return boundaries;
}
// This is a driver function which calls stages 2-5 in turn.
Poly* contours_to_polys(Vertex** contours, int contourCnt, const SkRect& pathBounds,
SkChunkAlloc& alloc) {
Comparator c;
if (pathBounds.width() > pathBounds.height()) {
c.sweep_lt = sweep_lt_horiz;
c.sweep_gt = sweep_gt_horiz;
} else {
c.sweep_lt = sweep_lt_vert;
c.sweep_gt = sweep_gt_vert;
}
Vertex* contours_to_mesh(Vertex** contours, int contourCnt, bool antialias,
Comparator& c, SkChunkAlloc& alloc) {
#if LOGGING_ENABLED
for (int i = 0; i < contourCnt; ++i) {
Vertex* v = contours[i];
@ -1320,27 +1622,69 @@ Poly* contours_to_polys(Vertex** contours, int contourCnt, const SkRect& pathBou
}
}
#endif
sanitize_contours(contours, contourCnt);
Vertex* vertices = build_edges(contours, contourCnt, c, alloc);
if (!vertices) {
sanitize_contours(contours, contourCnt, antialias);
return build_edges(contours, contourCnt, c, alloc);
}
Poly* mesh_to_polys(Vertex** vertices, SkPath::FillType fillType, Comparator& c,
SkChunkAlloc& alloc) {
if (!vertices || !*vertices) {
return nullptr;
}
// Sort vertices in Y (secondarily in X).
merge_sort(&vertices, c);
merge_coincident_vertices(&vertices, c, alloc);
merge_sort(vertices, c);
merge_coincident_vertices(vertices, c, alloc);
#if LOGGING_ENABLED
for (Vertex* v = vertices; v != nullptr; v = v->fNext) {
for (Vertex* v = *vertices; v != nullptr; v = v->fNext) {
static float gID = 0.0f;
v->fID = gID++;
}
#endif
simplify(vertices, c, alloc);
return tessellate(vertices, alloc);
simplify(*vertices, c, alloc);
return tessellate(*vertices, alloc);
}
Poly* contours_to_polys(Vertex** contours, int contourCnt, SkPath::FillType fillType,
const SkRect& pathBounds, bool antialias,
SkChunkAlloc& alloc) {
Comparator c;
if (pathBounds.width() > pathBounds.height()) {
c.sweep_lt = sweep_lt_horiz;
c.sweep_gt = sweep_gt_horiz;
} else {
c.sweep_lt = sweep_lt_vert;
c.sweep_gt = sweep_gt_vert;
}
Vertex* mesh = contours_to_mesh(contours, contourCnt, antialias, c, alloc);
Poly* polys = mesh_to_polys(&mesh, fillType, c, alloc);
if (antialias) {
EdgeList* boundaries = extract_boundaries(mesh, fillType, alloc);
VertexList aaMesh;
for (EdgeList* boundary = boundaries; boundary != nullptr; boundary = boundary->fNext) {
simplify_boundary(boundary, c, alloc);
if (boundary->fCount > 2) {
boundary_to_aa_mesh(boundary, &aaMesh, c, alloc);
}
}
return mesh_to_polys(&aaMesh.fHead, SkPath::kWinding_FillType, c, alloc);
}
return polys;
}
// Stage 6: Triangulate the monotone polygons into a vertex buffer.
void* polys_to_triangles(Poly* polys, SkPath::FillType fillType, const AAParams* aaParams,
void* data) {
for (Poly* poly = polys; poly; poly = poly->fNext) {
if (apply_fill_type(fillType, poly)) {
data = poly->emit(aaParams, data);
}
}
return data;
}
Poly* path_to_polys(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
int contourCnt, SkChunkAlloc& alloc, bool* isLinear) {
int contourCnt, SkChunkAlloc& alloc, bool antialias, bool* isLinear) {
SkPath::FillType fillType = path.getFillType();
if (SkPath::IsInverseFillType(fillType)) {
contourCnt++;
@ -1348,7 +1692,8 @@ Poly* path_to_polys(const SkPath& path, SkScalar tolerance, const SkRect& clipBo
SkAutoTDeleteArray<Vertex*> contours(new Vertex* [contourCnt]);
path_to_contours(path, tolerance, clipBounds, contours.get(), alloc, isLinear);
return contours_to_polys(contours.get(), contourCnt, path.getBounds(), alloc);
return contours_to_polys(contours.get(), contourCnt, path.getFillType(), path.getBounds(),
antialias, alloc);
}
void get_contour_count_and_size_estimate(const SkPath& path, SkScalar tolerance, int* contourCnt,
@ -1373,7 +1718,7 @@ void get_contour_count_and_size_estimate(const SkPath& path, SkScalar tolerance,
int count_points(Poly* polys, SkPath::FillType fillType) {
int count = 0;
for (Poly* poly = polys; poly; poly = poly->fNext) {
if (apply_fill_type(fillType, poly->fWinding) && poly->fCount >= 3) {
if (apply_fill_type(fillType, poly) && poly->fCount >= 3) {
count += (poly->fCount - 2) * (TESSELLATOR_WIREFRAME ? 6 : 3);
}
}
@ -1387,7 +1732,8 @@ namespace GrTessellator {
// Stage 6: Triangulate the monotone polygons into a vertex buffer.
int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
VertexAllocator* vertexAllocator, bool* isLinear) {
VertexAllocator* vertexAllocator, bool antialias, const GrColor& color,
bool canTweakAlphaForCoverage, bool* isLinear) {
int contourCnt;
int sizeEstimate;
get_contour_count_and_size_estimate(path, tolerance, &contourCnt, &sizeEstimate);
@ -1396,26 +1742,28 @@ int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBo
return 0;
}
SkChunkAlloc alloc(sizeEstimate);
Poly* polys = path_to_polys(path, tolerance, clipBounds, contourCnt, alloc, isLinear);
Poly* polys = path_to_polys(path, tolerance, clipBounds, contourCnt, alloc, antialias,
isLinear);
SkPath::FillType fillType = path.getFillType();
int count = count_points(polys, fillType);
if (0 == count) {
return 0;
}
SkPoint* verts = vertexAllocator->lock(count);
void* verts = vertexAllocator->lock(count);
if (!verts) {
SkDebugf("Could not allocate vertices\n");
return 0;
}
SkPoint* end = verts;
for (Poly* poly = polys; poly; poly = poly->fNext) {
if (apply_fill_type(fillType, poly->fWinding)) {
end = poly->emit(end);
}
}
int actualCount = static_cast<int>(end - verts);
LOG("actual count: %d\n", actualCount);
LOG("emitting %d verts\n", count);
AAParams aaParams;
aaParams.fTweakAlpha = canTweakAlphaForCoverage;
aaParams.fColor = color;
void* end = polys_to_triangles(polys, fillType, antialias ? &aaParams : nullptr, verts);
int actualCount = static_cast<int>((static_cast<uint8_t*>(end) - static_cast<uint8_t*>(verts))
/ vertexAllocator->stride());
SkASSERT(actualCount <= count);
vertexAllocator->unlock(actualCount);
return actualCount;
@ -1431,7 +1779,7 @@ int PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBou
}
SkChunkAlloc alloc(sizeEstimate);
bool isLinear;
Poly* polys = path_to_polys(path, tolerance, clipBounds, contourCnt, alloc, &isLinear);
Poly* polys = path_to_polys(path, tolerance, clipBounds, contourCnt, alloc, false, &isLinear);
SkPath::FillType fillType = path.getFillType();
int count = count_points(polys, fillType);
if (0 == count) {
@ -1444,9 +1792,9 @@ int PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBou
SkPoint* points = new SkPoint[count];
SkPoint* pointsEnd = points;
for (Poly* poly = polys; poly; poly = poly->fNext) {
if (apply_fill_type(fillType, poly->fWinding)) {
if (apply_fill_type(fillType, poly)) {
SkPoint* start = pointsEnd;
pointsEnd = poly->emit(pointsEnd);
pointsEnd = static_cast<SkPoint*>(poly->emit(nullptr, pointsEnd));
while (start != pointsEnd) {
vertsEnd->fPos = *start;
vertsEnd->fWinding = poly->fWinding;

View File

@ -8,6 +8,7 @@
#ifndef GrTessellator_DEFINED
#define GrTessellator_DEFINED
#include "GrColor.h"
#include "SkPoint.h"
class SkPath;
@ -23,9 +24,13 @@ namespace GrTessellator {
class VertexAllocator {
public:
VertexAllocator(size_t stride) : fStride(stride) {}
virtual ~VertexAllocator() {}
virtual SkPoint* lock(int vertexCount) = 0;
virtual void* lock(int vertexCount) = 0;
virtual void unlock(int actualCount) = 0;
size_t stride() const { return fStride; }
private:
size_t fStride;
};
struct WindingVertex {
@ -41,7 +46,8 @@ int PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBou
WindingVertex** verts);
int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
VertexAllocator*, bool *isLinear);
VertexAllocator*, bool antialias, const GrColor& color,
bool canTweakAlphaForCoverage, bool *isLinear);
}
#endif

View File

@ -12,6 +12,7 @@
#include "GrBatchTest.h"
#include "GrClip.h"
#include "GrDefaultGeoProcFactory.h"
#include "GrDrawTarget.h"
#include "GrMesh.h"
#include "GrPathUtils.h"
#include "GrPipelineBuilder.h"
@ -24,10 +25,12 @@
#include <stdio.h>
#define SK_DISABLE_SCREENSPACE_TESS_AA_PATH_RENDERER
/*
* This path renderer tessellates the path into triangles using GrTessellator, 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.
* This path renderer tessellates the path into triangles using GrTessellator, uploads the
* triangles to a vertex buffer, and renders them with a single draw call. It can do screenspace
* antialiasing with a one-pixel coverage ramp.
*/
namespace {
@ -64,22 +67,23 @@ bool cache_match(GrBuffer* vertexBuffer, SkScalar tol, int* actualCount) {
class StaticVertexAllocator : public GrTessellator::VertexAllocator {
public:
StaticVertexAllocator(GrResourceProvider* resourceProvider, bool canMapVB)
: fResourceProvider(resourceProvider)
StaticVertexAllocator(size_t stride, GrResourceProvider* resourceProvider, bool canMapVB)
: VertexAllocator(stride)
, fResourceProvider(resourceProvider)
, fCanMapVB(canMapVB)
, fVertices(nullptr) {
}
SkPoint* lock(int vertexCount) override {
size_t size = vertexCount * sizeof(SkPoint);
void* lock(int vertexCount) override {
size_t size = vertexCount * stride();
fVertexBuffer.reset(fResourceProvider->createBuffer(
size, kVertex_GrBufferType, kStatic_GrAccessPattern, 0));
if (!fVertexBuffer.get()) {
return nullptr;
}
if (fCanMapVB) {
fVertices = static_cast<SkPoint*>(fVertexBuffer->map());
fVertices = fVertexBuffer->map();
} else {
fVertices = new SkPoint[vertexCount];
fVertices = sk_malloc_throw(vertexCount * stride());
}
return fVertices;
}
@ -87,8 +91,8 @@ public:
if (fCanMapVB) {
fVertexBuffer->unmap();
} else {
fVertexBuffer->updateData(fVertices, actualCount * sizeof(SkPoint));
delete[] fVertices;
fVertexBuffer->updateData(fVertices, actualCount * stride());
sk_free(fVertices);
}
fVertices = nullptr;
}
@ -97,7 +101,34 @@ private:
SkAutoTUnref<GrBuffer> fVertexBuffer;
GrResourceProvider* fResourceProvider;
bool fCanMapVB;
SkPoint* fVertices;
void* fVertices;
};
class DynamicVertexAllocator : public GrTessellator::VertexAllocator {
public:
DynamicVertexAllocator(size_t stride, GrVertexBatch::Target* target)
: VertexAllocator(stride)
, fTarget(target)
, fVertexBuffer(nullptr)
, fVertices(nullptr) {
}
void* lock(int vertexCount) override {
fVertexCount = vertexCount;
fVertices = fTarget->makeVertexSpace(stride(), vertexCount, &fVertexBuffer, &fFirstVertex);
return fVertices;
}
void unlock(int actualCount) override {
fTarget->putBackVertices(fVertexCount - actualCount, stride());
fVertices = nullptr;
}
const GrBuffer* vertexBuffer() const { return fVertexBuffer; }
int firstVertex() const { return fFirstVertex; }
private:
GrVertexBatch::Target* fTarget;
const GrBuffer* fVertexBuffer;
int fVertexCount;
int fFirstVertex;
void* fVertices;
};
} // namespace
@ -106,13 +137,30 @@ GrTessellatingPathRenderer::GrTessellatingPathRenderer() {
}
bool GrTessellatingPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
// This path renderer can draw fill styles but does not do antialiasing. It can do convex and
// concave paths, but we'll leave the convex ones to simpler algorithms. We pass on paths that
// have styles, though they may come back around after applying the styling information to the
// geometry to create a filled path. We also skip paths that don't have a key since the real
// advantage of this path renderer comes from caching the tessellated geometry.
return !args.fShape->style().applies() && args.fShape->style().isSimpleFill() &&
!args.fAntiAlias && args.fShape->hasUnstyledKey() && !args.fShape->knownToBeConvex();
// This path renderer can draw fill styles, and can do screenspace antialiasing via a
// one-pixel coverage ramp. It can do convex and concave paths, but we'll leave the convex
// ones to simpler algorithms. We pass on paths that have styles, though they may come back
// around after applying the styling information to the geometry to create a filled path. In
// the non-AA case, We skip paths thta don't have a key since the real advantage of this path
// renderer comes from caching the tessellated geometry. In the AA case, we do not cache, so we
// accept paths without keys.
if (!args.fShape->style().isSimpleFill() || args.fShape->knownToBeConvex()) {
return false;
}
if (args.fAntiAlias) {
#ifdef SK_DISABLE_SCREENSPACE_TESS_AA_PATH_RENDERER
return false;
#else
SkPath path;
args.fShape->asPath(&path);
if (path.countVerbs() > 10) {
return false;
}
#endif
} else if (!args.fShape->hasUnstyledKey()) {
return false;
}
return true;
}
class TessellatingPathBatch : public GrVertexBatch {
@ -122,8 +170,9 @@ public:
static GrDrawBatch* Create(const GrColor& color,
const GrShape& shape,
const SkMatrix& viewMatrix,
SkRect clipBounds) {
return new TessellatingPathBatch(color, shape, viewMatrix, clipBounds);
SkIRect devClipBounds,
bool antiAlias) {
return new TessellatingPathBatch(color, shape, viewMatrix, devClipBounds, antiAlias);
}
const char* name() const override { return "TessellatingPathBatch"; }
@ -145,41 +194,53 @@ private:
fPipelineInfo = overrides;
}
void draw(Target* target, const GrGeometryProcessor* gp) const {
GrResourceProvider* rp = target->resourceProvider();
SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance;
SkScalar tol = GrPathUtils::scaleToleranceToSrc(screenSpaceTol, fViewMatrix,
fShape.bounds());
SkPath getPath() const {
SkASSERT(!fShape.style().applies());
SkPath path;
fShape.asPath(&path);
bool inverseFill = path.isInverseFillType();
return path;
}
void draw(Target* target, const GrGeometryProcessor* gp) const {
SkASSERT(!fAntiAlias);
GrResourceProvider* rp = target->resourceProvider();
bool inverseFill = fShape.inverseFilled();
// construct a cache key from the path's genID and the view matrix
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey key;
static constexpr int kClipBoundsCnt = sizeof(fClipBounds) / sizeof(uint32_t);
static constexpr int kClipBoundsCnt = sizeof(fDevClipBounds) / sizeof(uint32_t);
int shapeKeyDataCnt = fShape.unstyledKeySize();
SkASSERT(shapeKeyDataCnt >= 0);
GrUniqueKey::Builder builder(&key, kDomain, shapeKeyDataCnt + kClipBoundsCnt);
fShape.writeUnstyledKey(&builder[0]);
// For inverse fills, the tessellation is dependent on clip bounds.
if (inverseFill) {
memcpy(&builder[shapeKeyDataCnt], &fClipBounds, sizeof(fClipBounds));
memcpy(&builder[shapeKeyDataCnt], &fDevClipBounds, sizeof(fDevClipBounds));
} else {
memset(&builder[shapeKeyDataCnt], 0, sizeof(fClipBounds));
memset(&builder[shapeKeyDataCnt], 0, sizeof(fDevClipBounds));
}
builder.finish();
SkAutoTUnref<GrBuffer> cachedVertexBuffer(rp->findAndRefTByUniqueKey<GrBuffer>(key));
int actualCount;
SkScalar tol = GrPathUtils::kDefaultTolerance;
tol = GrPathUtils::scaleToleranceToSrc(tol, fViewMatrix, fShape.bounds());
if (cache_match(cachedVertexBuffer.get(), tol, &actualCount)) {
this->drawVertices(target, gp, cachedVertexBuffer.get(), 0, actualCount);
return;
}
SkRect clipBounds = SkRect::Make(fDevClipBounds);
SkMatrix vmi;
if (!fViewMatrix.invert(&vmi)) {
return;
}
vmi.mapRect(&clipBounds);
bool isLinear;
bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags();
StaticVertexAllocator allocator(rp, canMapVB);
int count = GrTessellator::PathToTriangles(path, tol, fClipBounds, &allocator, &isLinear);
StaticVertexAllocator allocator(gp->getVertexStride(), rp, canMapVB);
int count = GrTessellator::PathToTriangles(getPath(), tol, clipBounds, &allocator,
false, GrColor(), false, &isLinear);
if (count == 0) {
return;
}
@ -191,6 +252,27 @@ private:
rp->assignUniqueKeyToResource(key, allocator.vertexBuffer());
}
void drawAA(Target* target, const GrGeometryProcessor* gp) const {
SkASSERT(fAntiAlias);
SkPath path = getPath();
if (path.isEmpty()) {
return;
}
SkRect clipBounds = SkRect::Make(fDevClipBounds);
path.transform(fViewMatrix);
SkScalar tol = GrPathUtils::kDefaultTolerance;
bool isLinear;
DynamicVertexAllocator allocator(gp->getVertexStride(), target);
bool canTweakAlphaForCoverage = fPipelineInfo.canTweakAlphaForCoverage();
int count = GrTessellator::PathToTriangles(path, tol, clipBounds, &allocator,
true, fColor, canTweakAlphaForCoverage,
&isLinear);
if (count == 0) {
return;
}
drawVertices(target, gp, allocator.vertexBuffer(), allocator.firstVertex(), count);
}
void onPrepareDraws(Target* target) const override {
sk_sp<GrGeometryProcessor> gp;
{
@ -201,21 +283,35 @@ private:
LocalCoords::kUsePosition_Type :
LocalCoords::kUnused_Type);
Coverage::Type coverageType;
if (fPipelineInfo.readsCoverage()) {
if (fAntiAlias) {
color = Color(Color::kAttribute_Type);
if (fPipelineInfo.canTweakAlphaForCoverage()) {
coverageType = Coverage::kSolid_Type;
} else {
coverageType = Coverage::kAttribute_Type;
}
} else if (fPipelineInfo.readsCoverage()) {
coverageType = Coverage::kSolid_Type;
} else {
coverageType = Coverage::kNone_Type;
}
Coverage coverage(coverageType);
if (fAntiAlias) {
gp = GrDefaultGeoProcFactory::MakeForDeviceSpace(color, coverage, localCoords,
fViewMatrix);
} else {
gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, fViewMatrix);
}
}
if (fAntiAlias) {
this->drawAA(target, gp.get());
} else {
this->draw(target, gp.get());
}
}
void drawVertices(Target* target, const GrGeometryProcessor* gp, const GrBuffer* vb,
int firstVertex, int count) const {
SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
GrPrimitiveType primitiveType = TESSELLATOR_WIREFRAME ? kLines_GrPrimitiveType
: kTriangles_GrPrimitiveType;
GrMesh mesh;
@ -228,24 +324,29 @@ private:
TessellatingPathBatch(const GrColor& color,
const GrShape& shape,
const SkMatrix& viewMatrix,
const SkRect& clipBounds)
const SkIRect& devClipBounds,
bool antiAlias)
: INHERITED(ClassID())
, fColor(color)
, fShape(shape)
, fViewMatrix(viewMatrix) {
const SkRect& pathBounds = shape.bounds();
fClipBounds = clipBounds;
, fViewMatrix(viewMatrix)
, fDevClipBounds(devClipBounds)
, fAntiAlias(antiAlias) {
SkRect devBounds;
viewMatrix.mapRect(&devBounds, shape.bounds());
if (shape.inverseFilled()) {
// Because the clip bounds are used to add a contour for inverse fills, they must also
// include the path bounds.
fClipBounds.join(pathBounds);
const SkRect& srcBounds = shape.inverseFilled() ? fClipBounds : pathBounds;
this->setTransformedBounds(srcBounds, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
devBounds.join(SkRect::Make(fDevClipBounds));
}
this->setBounds(devBounds, HasAABloat::kNo, IsZeroArea::kNo);
}
GrColor fColor;
GrShape fShape;
SkMatrix fViewMatrix;
SkRect fClipBounds; // in source space
SkIRect fDevClipBounds;
bool fAntiAlias;
GrXPOverridesForBatch fPipelineInfo;
typedef GrVertexBatch INHERITED;
@ -254,22 +355,14 @@ private:
bool GrTessellatingPathRenderer::onDrawPath(const DrawPathArgs& args) {
GR_AUDIT_TRAIL_AUTO_FRAME(args.fDrawContext->auditTrail(),
"GrTessellatingPathRenderer::onDrawPath");
SkASSERT(!args.fAntiAlias);
SkIRect clipBoundsI;
args.fClip->getConservativeBounds(args.fDrawContext->width(), args.fDrawContext->height(),
&clipBoundsI);
SkRect clipBounds = SkRect::Make(clipBoundsI);
SkMatrix vmi;
if (!args.fViewMatrix->invert(&vmi)) {
return false;
}
vmi.mapRect(&clipBounds);
SkPath path;
args.fShape->asPath(&path);
SkAutoTUnref<GrDrawBatch> batch(TessellatingPathBatch::Create(args.fPaint->getColor(),
*args.fShape,
*args.fViewMatrix, clipBounds));
*args.fViewMatrix,
clipBoundsI,
args.fAntiAlias));
GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fDrawContext->mustUseHWAA(*args.fPaint));
pipelineBuilder.setUserStencil(args.fUserStencilSettings);
@ -287,19 +380,15 @@ DRAW_BATCH_TEST_DEFINE(TesselatingPathBatch) {
GrColor color = GrRandomColor(random);
SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
SkPath path = GrTest::TestPath(random);
SkRect clipBounds = GrTest::TestRect(random);
SkMatrix vmi;
bool result = viewMatrix.invert(&vmi);
if (!result) {
SkFAIL("Cannot invert matrix\n");
}
vmi.mapRect(&clipBounds);
SkIRect devClipBounds = SkIRect::MakeXYWH(
random->nextU(), random->nextU(), random->nextU(), random->nextU());
bool antiAlias = random->nextBool();
GrStyle style;
do {
GrTest::TestStyle(random, &style);
} while (style.strokeRec().isHairlineStyle());
GrShape shape(path, style);
return TessellatingPathBatch::Create(color, shape, viewMatrix, clipBounds);
return TessellatingPathBatch::Create(color, shape, viewMatrix, devClipBounds, antiAlias);
}
#endif