Revert "Optimize GrTessellatePathOp's code to emit inner triangles"

This reverts commit 0f9ebd100e.

Reason for revert: breaking a couple of bots (e.g. https://chromium-swarm.appspot.com/task?id=4bd6ccc785fa3110)

Original change's description:
> Optimize GrTessellatePathOp's code to emit inner triangles
> 
> Previously we used a naive algorithm to generate "middle-out" topologies
> for inner polygons, including copying all endpoints to a new array.
> 
> This CL adds a "GrMiddleOutPolygonTriangulator" class that
> accomplishes the same thing in 1/5th the time using a small O(log N)
> stack.
> 
> Change-Id: I3a7059e5d133a730b7084a17d8fbaaa3aaa81336
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/285531
> Commit-Queue: Chris Dalton <csmartdalton@google.com>
> Reviewed-by: Michael Ludwig <michaelludwig@google.com>

TBR=csmartdalton@google.com,michaelludwig@google.com

Change-Id: I8ebca7078f2b9c12246447759efa9ce0cbb7e46b
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/285719
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
This commit is contained in:
Ethan Nicholas 2020-04-27 23:37:55 +00:00 committed by Skia Commit-Bot
parent 2514bd790a
commit 5f56cb1d3b
6 changed files with 113 additions and 203 deletions

View File

@ -10,7 +10,6 @@
#include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/tessellate/GrTessellatePathOp.h"
#include "tools/ToolUtils.h"
// This is the number of cubics in desk_chalkboard.skp. (There are no quadratics in the chalkboard.)
constexpr static int kNumCubicsInChalkboard = 47182;
@ -48,7 +47,7 @@ public:
const char* onGetName() override { return fName.c_str(); }
bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
class MiddleOutInnerTrianglesBench;
class InnerTrianglesBench;
class OuterCubicsBench;
class CubicWedgesBench;
@ -72,23 +71,20 @@ private:
SkString fName;
};
class GrTessellatePathOp::TestingOnly_Benchmark::MiddleOutInnerTrianglesBench
class GrTessellatePathOp::TestingOnly_Benchmark::InnerTrianglesBench
: public GrTessellatePathOp::TestingOnly_Benchmark {
public:
MiddleOutInnerTrianglesBench()
: TestingOnly_Benchmark("prepareMiddleOutInnerTriangles",
ToolUtils::make_star(SkRect::MakeWH(100, 100),
kNumCubicsInChalkboard),
SkMatrix::I()) {
InnerTrianglesBench()
: TestingOnly_Benchmark("prepareInnerTriangles", make_cubic_path(), SkMatrix::I()) {
}
void runBench(GrOpFlushState* flushState, GrTessellatePathOp* op) override {
int numBeziers;
op->prepareMiddleOutInnerTriangles(flushState, &numBeziers);
op->prepareInnerTriangles(flushState, &numBeziers);
SkASSERT(numBeziers == kNumCubicsInChalkboard);
}
};
DEF_BENCH( return new GrTessellatePathOp::TestingOnly_Benchmark::MiddleOutInnerTrianglesBench(););
DEF_BENCH( return new GrTessellatePathOp::TestingOnly_Benchmark::InnerTrianglesBench(););
class GrTessellatePathOp::TestingOnly_Benchmark::OuterCubicsBench
: public GrTessellatePathOp::TestingOnly_Benchmark {

View File

@ -426,13 +426,11 @@ skia_gpu_sources = [
"$_src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h",
"$_src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp",
"$_src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h",
# tessellate
"$_src/gpu/tessellate/GrDrawAtlasPathOp.cpp",
"$_src/gpu/tessellate/GrDrawAtlasPathOp.h",
"$_src/gpu/tessellate/GrFillPathShader.cpp",
"$_src/gpu/tessellate/GrFillPathShader.h",
"$_src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h",
"$_src/gpu/tessellate/GrInnerPolygonContourParser.h",
"$_src/gpu/tessellate/GrMidpointContourParser.h",
"$_src/gpu/tessellate/GrPathShader.h",
"$_src/gpu/tessellate/GrStencilPathShader.cpp",

View File

@ -0,0 +1,89 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrInnerPolygonContourParser_DEFINED
#define GrInnerPolygonContourParser_DEFINED
#include "include/private/SkTArray.h"
#include "src/core/SkPathPriv.h"
// SkTPathContourParser specialization that triangulates the path's inner polygon via recursive
// subdivision. 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.)
class GrInnerPolygonContourParser : public SkTPathContourParser<GrInnerPolygonContourParser> {
public:
GrInnerPolygonContourParser(const SkPath& path, int vertexReserveCount)
: SkTPathContourParser(path)
, fPolyPoints(vertexReserveCount) {
}
// Triangulates the polygon defined by the points in the range [first..last] inclusive.
// Called by InnerPolygonContourParser::emitInnerPolygon() (and recursively).
static int EmitSubpolygon(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 += EmitSubpolygon(points, first, mid, vertexData + vertexCount);
vertexCount += EmitSubpolygon(points, mid, last, vertexData + vertexCount);
return vertexCount;
}
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 += EmitSubpolygon(fPolyPoints.begin(), 0, i1, vertexData + vertexCount);
vertexCount += EmitSubpolygon(fPolyPoints.begin(), i1, i2, vertexData + vertexCount);
int i3 = fPolyPoints.size();
fPolyPoints.push_back(fPolyPoints.front());
vertexCount += EmitSubpolygon(fPolyPoints.begin(), i2, i3, vertexData + vertexCount);
fPolyPoints.pop_back();
return vertexCount;
}
int numCountedCurves() const { return fNumCountedCurves; }
private:
void resetGeometry(const SkPoint& startPoint) {
fPolyPoints.pop_back_n(fPolyPoints.count());
fPolyPoints.push_back(startPoint);
}
void geometryTo(SkPathVerb verb, const SkPoint& endpoint) {
fPolyPoints.push_back(endpoint);
if (SkPathVerb::kLine != verb) {
++fNumCountedCurves;
}
}
SkSTArray<128, SkPoint> fPolyPoints;
int fNumCountedCurves = 0;
friend class SkTPathContourParser<GrInnerPolygonContourParser>;
};
#endif

View File

@ -1,151 +0,0 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrMiddleOutPolygonTriangulator_DEFINED
#define GrMiddleOutPolygonTriangulator_DEFINED
#include "include/core/SkPoint.h"
#include "include/private/SkTemplates.h"
#include "src/core/SkMathPriv.h"
// This class emits a polygon triangulation with a "middle-out" topology. Conceptually, middle-out
// emits one large triangle with vertices on both endpoints and a middle point, then recurses on
// both sides of the new triangle. i.e.:
//
// void emit_middle_out_triangulation(int startIdx, int endIdx) {
// if (startIdx + 1 == endIdx) {
// return;
// }
// int middleIdx = startIdx + SkNextPow2(endIdx - startIdx) / 2;
//
// // Recurse on the left half.
// emit_middle_out_triangulation(startIdx, middleIdx);
//
// // Emit a large triangle with vertices on both endpoints and a middle point.
// emit_triangle(vertices[startIdx], vertices[middleIdx], vertices[endIdx - 1]);
//
// // Recurse on the right half.
// emit_middle_out_triangulation(middleIdx, endIdx);
// }
//
// Middle-out produces drastically less work for the rasterizer as compared a linear triangle strip
// or fan.
//
// This class is designed to not know or store all the vertices in the polygon at once. The caller
// pushes each vertex in linear order (perhaps while parsing a path), then rather than relying on
// recursion, we manipulate an O(log N) stack to determine the correct middle-out triangulation.
class GrMiddleOutPolygonTriangulator {
public:
GrMiddleOutPolygonTriangulator(SkPoint* vertexData, int maxVertices)
: fTriangleData(reinterpret_cast<std::array<SkPoint, 3>*>(vertexData)) {
// Determine the deepest our stack can ever go.
int maxStackDepth = SkNextLog2(maxVertices) + 1;
if (maxStackDepth > kStackPreallocCount) {
fVertexStack.reset(maxStackDepth);
}
SkDEBUGCODE(fStackAllocCount = maxStackDepth;)
// The stack will always contain a starting point. This is an implicit moveTo(0, 0)
// initially, but will be overridden if moveTo() gets called before adding geometry.
fVertexStack[0] = {0, {0, 0}};
fTop = fVertexStack;
}
void pushVertex(const SkPoint& pt) {
if (pt == fVertexStack[0].fPoint) {
this->close();
return;
}
// This new vertex we are about to add is one vertex away from the top of the stack.
// i.e., it is guaranteed to be the next vertex in the polygon after the one stored in fTop.
int vertexIdxDelta = 1;
// Our topology wants triangles that have the same vertexIdxDelta on both sides:
// e.g., a run of 9 points should be triangulated as:
//
// [0, 1, 2], [2, 3, 4], [4, 5, 6], [6, 7, 8] // vertexIdxDelta == 1
// [0, 2, 4], [4, 6, 8] // vertexIdxDelta == 2
// [0, 4, 8] // vertexIdxDelta == 4
//
// Emit as many new triangles as we can with equal-delta sides and pop their vertices off
// the stack before pushing this new vertex.
//
// (This is a stack-based implementation of the recursive example method from the class
// comment.)
while (vertexIdxDelta == fTop->fVertexIdxDelta) {
*fTriangleData++ = {fTop[-1].fPoint, fTop->fPoint, pt};
vertexIdxDelta *= 2;
this->popTop();
}
this->pushTop();
*fTop = {vertexIdxDelta, pt};
}
int close() {
if (fTop == &fVertexStack[0]) { // The stack only contains one point (the starting point).
return fTotalClosedVertexCount;
}
// We will count vertices by walking the stack backwards.
int finalVertexCount = 1;
// Add an implicit line back to the starting point, then triangulate the rest of the
// polygon. Since we simply have to finish now, we aren't picky anymore about making the
// vertexIdxDeltas match.
const SkPoint& p0 = fVertexStack[0].fPoint;
SkASSERT(fTop->fPoint != p0); // We should have detected and handled this case earlier.
while (fTop > &fVertexStack[1]) {
*fTriangleData++ = {fTop[-1].fPoint, fTop->fPoint, p0};
finalVertexCount += fTop->fVertexIdxDelta;
this->popTop();
}
finalVertexCount += fTop->fVertexIdxDelta;
this->popTop();
SkASSERT(fTop == &fVertexStack[0]);
SkASSERT(fVertexStack[0].fVertexIdxDelta == 0);
int numTriangles = finalVertexCount - 2;
SkASSERT(numTriangles >= 0);
fTotalClosedVertexCount += numTriangles * 3;
return fTotalClosedVertexCount;
}
void closeAndMove(const SkPoint& startPt) {
this->close();
SkASSERT(fTop == &fVertexStack[0]); // The stack should only contain a starting point now.
fTop->fPoint = startPt; // Modify the starting point.
SkASSERT(fTop->fVertexIdxDelta == 0); // Ensure we are in the initial stack state.
}
private:
struct StackVertex {
// How many polygon vertices away is this vertex from the previous vertex on the stack?
// i.e., the ith stack element's vertex index in the original polygon is:
//
// fVertexStack[i].fVertexIdxDelta + fVertexStack[i - 1].fVertexIdxDelta + ... +
// fVertexStack[1].fVertexIdxDelta.
//
// NOTE: fVertexStack[0].fVertexIdxDelta always == 0.
int fVertexIdxDelta;
SkPoint fPoint;
};
void pushTop() {
++fTop;
// We should never push deeper than fStackAllocCount.
SkASSERT(fTop < &fVertexStack[fStackAllocCount]);
}
void popTop() {
SkASSERT(fTop > &fVertexStack[0]); // We should never pop the starting point.
--fTop;
}
constexpr static int kStackPreallocCount = 32;
SkAutoSTMalloc<kStackPreallocCount, StackVertex> fVertexStack;
SkDEBUGCODE(int fStackAllocCount;)
StackVertex* fTop;
std::array<SkPoint, 3>* fTriangleData;
int fTotalClosedVertexCount = 0;
};
#endif

View File

@ -12,7 +12,7 @@
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrTriangulator.h"
#include "src/gpu/tessellate/GrFillPathShader.h"
#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
#include "src/gpu/tessellate/GrInnerPolygonContourParser.h"
#include "src/gpu/tessellate/GrMidpointContourParser.h"
#include "src/gpu/tessellate/GrStencilPathShader.h"
@ -48,7 +48,7 @@ void GrTessellatePathOp::onPrepare(GrOpFlushState* state) {
int numCountedCurves;
// This will fail if the inner triangles do not form a simple polygon (e.g., self
// intersection, double winding).
if (this->prepareNonOverlappingInnerTriangles(state, &numCountedCurves)) {
if (this->prepareSimpleInnerPolygonTriangulation(state, &numCountedCurves)) {
// Prepare cubics on an instance boundary so we can use the buffer to fill local convex
// hulls as well.
this->prepareOuterCubics(state, numCountedCurves,
@ -65,7 +65,7 @@ void GrTessellatePathOp::onPrepare(GrOpFlushState* state) {
float rasterEdgeWork = (bounds.height() + bounds.width()) * scales[1] * fPath.countVerbs();
if (rasterEdgeWork > 1000 * 1000) {
int numCountedCurves;
this->prepareMiddleOutInnerTriangles(state, &numCountedCurves);
this->prepareInnerTriangles(state, &numCountedCurves);
// We will fill the path with a bounding box instead local cubic convex hulls, so there is
// no need to prepare the cubics on an instance boundary.
this->prepareOuterCubics(state, numCountedCurves, CubicDataAlignment::kVertexBoundary);
@ -76,8 +76,8 @@ void GrTessellatePathOp::onPrepare(GrOpFlushState* state) {
this->prepareCubicWedges(state);
}
bool GrTessellatePathOp::prepareNonOverlappingInnerTriangles(GrOpFlushState* flushState,
int* numCountedCurves) {
bool GrTessellatePathOp::prepareSimpleInnerPolygonTriangulation(GrOpFlushState* flushState,
int* numCountedCurves) {
SkASSERT(!fTriangleBuffer);
SkASSERT(!fDoStencilTriangleBuffer);
SkASSERT(!fDoFillTriangleBuffer);
@ -106,13 +106,12 @@ bool GrTessellatePathOp::prepareNonOverlappingInnerTriangles(GrOpFlushState* flu
return true;
}
void GrTessellatePathOp::prepareMiddleOutInnerTriangles(GrOpFlushState* flushState,
int* numCountedCurves) {
void GrTessellatePathOp::prepareInnerTriangles(GrOpFlushState* flushState, int* numCountedCurves) {
SkASSERT(!fTriangleBuffer);
SkASSERT(!fDoStencilTriangleBuffer);
SkASSERT(!fDoFillTriangleBuffer);
// No initial moveTo, plus an implicit close at the end; n-2 triangles fill an n-gon.
// No initial moveTo, plus an implicit close at the end; n-2 trianles fill an n-gon.
// Each triangle has 3 vertices.
int maxVertices = (fPath.countVerbs() - 1) * 3;
@ -121,32 +120,13 @@ void GrTessellatePathOp::prepareMiddleOutInnerTriangles(GrOpFlushState* flushSta
if (!vertexData) {
return;
}
fTriangleVertexCount = 0;
GrMiddleOutPolygonTriangulator middleOut(vertexData, maxVertices);
int localCurveCount = 0;
const SkPoint* pts = SkPathPriv::PointData(fPath);
for (SkPath::Verb verb : SkPathPriv::Verbs(fPath)) {
switch ((uint8_t)verb) {
case SkPath::kMove_Verb:
middleOut.closeAndMove(*pts++);
break;
case SkPath::kClose_Verb:
middleOut.close();
break;
static_assert(SkPath::kLine_Verb == 1); case 1:
static_assert(SkPath::kQuad_Verb == 2); case 2:
static_assert(SkPath::kConic_Verb == 3); case 3:
static_assert(SkPath::kCubic_Verb == 4); case 4:
constexpr static int kPtsAdvance[] = {0, 1, 2, 2, 3};
constexpr static int kCurveCountAdvance[] = {0, 0, 1, 1, 1};
pts += kPtsAdvance[verb];
middleOut.pushVertex(pts[-1]);
localCurveCount += kCurveCountAdvance[verb];
break;
}
GrInnerPolygonContourParser parser(fPath, maxVertices);
while (parser.parseNextContour()) {
fTriangleVertexCount += parser.emitInnerPolygon(vertexData + fTriangleVertexCount);
}
fTriangleVertexCount = middleOut.close();
*numCountedCurves = localCurveCount;
*numCountedCurves = parser.numCountedCurves();
vertexAlloc.unlock(fTriangleVertexCount);

View File

@ -62,19 +62,17 @@ private:
// triangles directly and bypass stencilling them.
//
// Returns false if the inner triangles do not form a simple polygon (e.g., self intersection,
// double winding). Non-simple polygons would need to split edges in order to avoid overlap,
// and this is not an option as it would introduce T-junctions with the outer cubics.
bool prepareNonOverlappingInnerTriangles(GrOpFlushState*, int* numCountedCurves);
// double winding).
bool prepareSimpleInnerPolygonTriangulation(GrOpFlushState*, int* numCountedCurves);
// Produces a "Red Book" style triangulation of the SkPath's inner polygon(s). 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.) Stencilled together with the outer cubics, these
// define the complete path.
//
// This method emits the inner triangles with a "middle-out" topology. Middle-out can reduce
// the load on the rasterizer by a great deal as compared to a linear triangle strip or fan.
// See GrMiddleOutPolygonTriangulator.
void prepareMiddleOutInnerTriangles(GrOpFlushState*, int* numCountedCurves);
// 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.
void prepareInnerTriangles(GrOpFlushState*, int* numCountedCurves);
enum class CubicDataAlignment : bool {
kVertexBoundary,