Skip to content

Commit

Permalink
Add a tessellation mode that triangulates the inner polygon separately
Browse files Browse the repository at this point in the history
Wedges fanning out from the center work fine for relatively simple
paths, but for paths made up of thousands of verbs, a fan is an
inefficient triangulation to give the rasterizer. This CL adds a
tessellation mode that draws the inner polygon and standalone cubics
separately, and triangulates the inner polygon by recursive
subdivision.

This reduces the stencil time from 7.4ms -> 3.0ms on desk_ynevsvg.skp.

Change-Id: Ie56e760d98e6c69e9a97752fe851726f36a7f574
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/265522
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
  • Loading branch information
csmartdalton86 authored and Skia Commit-Bot committed Jan 22, 2020
1 parent dbc9f64 commit f9aea7f
Show file tree
Hide file tree
Showing 12 changed files with 556 additions and 221 deletions.
4 changes: 2 additions & 2 deletions gn/gpu.gni
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,10 @@ skia_gpu_sources = [
"$_src/gpu/tessellate/GrGpuTessellationPathRenderer.h",
"$_src/gpu/tessellate/GrPathParser.cpp",
"$_src/gpu/tessellate/GrPathParser.h",
"$_src/gpu/tessellate/GrStencilPathShader.cpp",
"$_src/gpu/tessellate/GrStencilPathShader.h",
"$_src/gpu/tessellate/GrTessellatePathOp.cpp",
"$_src/gpu/tessellate/GrTessellatePathOp.h",
"$_src/gpu/tessellate/GrTessellateWedgeShader.cpp",
"$_src/gpu/tessellate/GrTessellateWedgeShader.h",

# text
"$_src/gpu/text/GrAtlasManager.cpp",
Expand Down
21 changes: 19 additions & 2 deletions samplecode/SampleTessellatedWedge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,20 @@
class TessellatedWedgeView : public Sample {
public:
TessellatedWedgeView() {
#if 0
fPath.moveTo(1, 0);
int numSides = 32 * 3;
for (int i = 1; i < numSides; ++i) {
float theta = 2*3.1415926535897932384626433832785 * i / numSides;
fPath.lineTo(std::cos(theta), std::sin(theta));
}
fPath.transform(SkMatrix::MakeScale(200, 200));
fPath.transform(SkMatrix::MakeTrans(300, 300));
#else
fPath.moveTo(100, 200);
fPath.cubicTo(100, 100, 400, 100, 400, 200);
fPath.lineTo(250, 500);
#endif
}

private:
Expand Down Expand Up @@ -87,8 +98,14 @@ void TessellatedWedgeView::onDrawContent(SkCanvas* canvas) {
SkPaint pointsPaint;
pointsPaint.setColor(SK_ColorBLUE);
pointsPaint.setStrokeWidth(8);
canvas->drawPoints(SkCanvas::kPoints_PointMode, fPath.countPoints(),
SkPathPriv::PointData(fPath), pointsPaint);
SkPath devPath = fPath;
devPath.transform(canvas->getTotalMatrix());
{
SkAutoCanvasRestore acr(canvas, true);
canvas->setMatrix(SkMatrix::I());
canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(),
SkPathPriv::PointData(devPath), pointsPaint);
}

fLastViewMatrix = canvas->getTotalMatrix();
}
Expand Down
4 changes: 3 additions & 1 deletion src/gpu/GrProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ class GrProcessor {
kGrSRGBEffect_ClassID,
kGrSampleMaskProcessor_ClassID,
kGrSweepGradientLayout_ClassID,
kGrTessellateWedgeShader_ClassID,
kGrTextureEffect_ClassID,
kGrTextureGradientColorizer_ClassID,
kGrTiledGradientEffect_ClassID,
Expand All @@ -162,6 +161,9 @@ class GrProcessor {
kSwizzleFragmentProcessor_ClassID,
kTessellationTestTriShader_ClassID,
kTessellationTestRectShader_ClassID,
kTessellate_GrStencilCubicShader_ClassID,
kTessellate_GrStencilTriangleShader_ClassID,
kTessellate_GrStencilWedgeShader_ClassID,
kTestFP_ClassID,
kTestRectOp_ClassID,
kFlatNormalsFP_ClassID,
Expand Down
12 changes: 6 additions & 6 deletions src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ bool GrGpuTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
SkPath path;
args.fShape->asPath(&path);

GrOpMemoryPool* pool = args.fContext->priv().opMemoryPool();
args.fRenderTargetContext->addDrawOp(*args.fClip, pool->allocate<GrTessellatePathOp>(
*args.fViewMatrix, path, std::move(args.fPaint), args.fAAType));
auto op = args.fContext->priv().opMemoryPool()->allocate<GrTessellatePathOp>(
*args.fViewMatrix, path, std::move(args.fPaint), args.fAAType);
args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));

return true;
}
Expand All @@ -54,7 +54,7 @@ void GrGpuTessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {

GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;

GrOpMemoryPool* pool = args.fContext->priv().opMemoryPool();
args.fRenderTargetContext->addDrawOp(*args.fClip, pool->allocate<GrTessellatePathOp>(
*args.fViewMatrix, path, GrPaint(), aaType, GrTessellatePathOp::Flags::kStencilOnly));
auto op = args.fContext->priv().opMemoryPool()->allocate<GrTessellatePathOp>(
*args.fViewMatrix, path, GrPaint(), aaType, GrTessellatePathOp::Flags::kStencilOnly);
args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
}
115 changes: 109 additions & 6 deletions src/gpu/tessellate/GrPathParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@

#include "src/gpu/tessellate/GrPathParser.h"

#include "include/private/SkTArray.h"
#include "src/core/SkPathPriv.h"

namespace GrPathParser {

static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
SkASSERT(1 != T); // The below does not guarantee lerp(a, b, 1) === b.
return (b - a) * T + a;
Expand All @@ -25,7 +24,7 @@ static SkPoint write_line_as_cubic(SkPoint* data, const SkPoint& p0, const SkPoi
}

static SkPoint write_quadratic_as_cubic(SkPoint* data, const SkPoint& p0, const SkPoint& p1,
const SkPoint& p2) {
const SkPoint& p2) {
data[0] = p0;
data[1] = lerp(p0, p1, 2/3.f);
data[2] = lerp(p1, p2, 1/3.f);
Expand Down Expand Up @@ -77,7 +76,7 @@ class MidpointContourParser : public SkTPathContourParser<MidpointContourParser>
int fMidpointWeight;
};

int EmitCenterWedges(const SkPath& path, SkPoint* patchData) {
int GrPathParser::EmitCenterWedgePatches(const SkPath& path, SkPoint* patchData) {
int vertexCount = 0;
MidpointContourParser parser(path);
while (parser.parseNextContour()) {
Expand Down Expand Up @@ -122,8 +121,112 @@ int EmitCenterWedges(const SkPath& path, SkPoint* patchData) {
}
}

SkASSERT(vertexCount <= MaxPossibleWedgeVertices(path));
SkASSERT(vertexCount <= MaxWedgeVertices(path));
return vertexCount;
}

// Triangulates the polygon defined by the points in the range [first..last] inclusive.
// Called by InnerPolygonContourParser::emitInnerPolygon() (and recursively).
static int emit_subpolygon(const SkPoint* points, int first, int last, SkPoint* vertexData) {
if (last - first < 2) {
return 0;
}

// For sub-polygons we subdivide the points in two and connect the endpoints.
int mid = (first + last) / 2;
vertexData[0] = points[first];
vertexData[1] = points[mid];
vertexData[2] = points[last];

// Emit the sub-polygon at each outer-edge of our new triangle.
int vertexCount = 3;
vertexCount += emit_subpolygon(points, first, mid, vertexData + vertexCount);
vertexCount += emit_subpolygon(points, mid, last, vertexData + vertexCount);
return vertexCount;
}

class InnerPolygonContourParser : public SkTPathContourParser<InnerPolygonContourParser> {
public:
InnerPolygonContourParser(const SkPath& path) : SkTPathContourParser(path) {
fPolyPoints.reserve(GrPathParser::MaxInnerPolygonVertices(path));
}

int emitInnerPolygon(SkPoint* vertexData) {
if (fPolyPoints.size() < 3) {
return 0;
}

// For the first triangle in the polygon, subdivide our points into thirds.
int i1 = fPolyPoints.size() / 3;
int i2 = (2 * fPolyPoints.size()) / 3;
vertexData[0] = fPolyPoints[0];
vertexData[1] = fPolyPoints[i1];
vertexData[2] = fPolyPoints[i2];

// Emit the sub-polygons at all three edges of our first triangle.
int vertexCount = 3;
vertexCount += emit_subpolygon(fPolyPoints.begin(), 0, i1, vertexData + vertexCount);
vertexCount += emit_subpolygon(fPolyPoints.begin(), i1, i2, vertexData + vertexCount);
int i3 = fPolyPoints.size();
fPolyPoints.push_back(fPolyPoints.front());
vertexCount += emit_subpolygon(fPolyPoints.begin(), i2, i3, vertexData + vertexCount);
fPolyPoints.pop_back();

return vertexCount;
}

int numCurves() const { return fNumCurves; }

private:
friend class SkTPathContourParser<InnerPolygonContourParser>;

void resetGeometry(const SkPoint& startPoint) {
fPolyPoints.pop_back_n(fPolyPoints.count());
fPolyPoints.push_back(startPoint);
fNumCurves = 0;
}

void geometryTo(SkPathVerb verb, const SkPoint& endpoint) {
fPolyPoints.push_back(endpoint);
if (SkPathVerb::kLine != verb) {
++fNumCurves;
}
}

SkSTArray<128, SkPoint> fPolyPoints;
int fNumCurves;
};

int GrPathParser::EmitInnerPolygonTriangles(const SkPath& path, SkPoint* vertexData,
int* numCurves) {
*numCurves = 0;
int vertexCount = 0;
InnerPolygonContourParser parser(path);
while (parser.parseNextContour()) {
vertexCount += parser.emitInnerPolygon(vertexData + vertexCount);
*numCurves += parser.numCurves();
}

SkASSERT(vertexCount <= MaxInnerPolygonVertices(path));
return vertexCount;
}

} // namespace
int GrPathParser::EmitCubicInstances(const SkPath& path, SkPoint* vertexData) {
int instanceCount = 0;
SkPath::Iter iter(path, false);
SkPath::Verb verb;
SkPoint pts[4];
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
if (SkPath::kQuad_Verb == verb) {
write_quadratic_as_cubic(vertexData + (instanceCount * 4), pts[0], pts[1], pts[2]);
++instanceCount;
continue;
}
if (SkPath::kCubic_Verb == verb) {
write_cubic(vertexData + (instanceCount * 4), pts[0], pts[1], pts[2], pts[3]);
++instanceCount;
continue;
}
}
return instanceCount;
}
31 changes: 26 additions & 5 deletions src/gpu/tessellate/GrPathParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

namespace GrPathParser {

// Returns the maximum possible number of vertices that can be written by EmitCenterWedges() for the
// given path.
inline int MaxPossibleWedgeVertices(const SkPath& path) {
// Returns the maximum number of vertices that can be written by EmitCenterWedges() for the given
// path.
inline int MaxWedgeVertices(const SkPath& path) {
// No initial moveTo, one wedge per verb, plus an implicit close at the end.
// Each wedge has 5 vertices.
return (path.countVerbs() + 1) * 5;
Expand All @@ -29,8 +29,29 @@ inline int MaxPossibleWedgeVertices(const SkPath& path) {
//
// Returns the number of vertices written to the array.
//
// NOTE: The incoming patchData must have allocated at least MaxWedgeVertices() vertices.
int EmitCenterWedges(const SkPath&, SkPoint* patchData);
// The incoming patchData array must have at least MaxWedgeVertices() elements.
int EmitCenterWedgePatches(const SkPath&, SkPoint* patchData);

// Returns the maximum number of vertices required to triangulate the given path's inner polygon(s).
inline int MaxInnerPolygonVertices(const SkPath& path) {
// No initial moveTo, plus an implicit close at the end; n-2 trianles fill an n-gon.
// Each triangle has 3 vertices.
return (path.countVerbs() - 1) * 3;
}

// Triangulates the path's inner polygon(s) and writes the result to "vertexData". The inner
// polygons connect the endpoints of each verb. (i.e., they are the path that would result from
// collapsing all curves to single lines.)
//
// This method works by recursively subdividing the path rather than emitting a linear triangle fan
// or strip. This can reduce the load on the rasterizer by a great deal on complex paths.
//
// Returns the number of vertices written to the array.
//
// The incoming vertexData array must have at least MaxInnerPolygonVertices() elements.
int EmitInnerPolygonTriangles(const SkPath&, SkPoint* vertexData, int* numCurves);

int EmitCubicInstances(const SkPath&, SkPoint* vertexData);

} // namespace

Expand Down
Loading

0 comments on commit f9aea7f

Please sign in to comment.