Convert MiddleOutPolygonTriangulator to an iterator

Rather than having this class write directly to a GPU buffer, which
also included an ugly hack to support extra instance data that was not
points, it is now just an iterator style object. This allows the
caller to iterate over all the triangles and do whatever it wants with
them. This is both faster and more versatile.

Bug: skia:12524
Change-Id: I43cdd721bc50607cc34ebf415390b4d493f4d697
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/466917
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Chris Dalton 2021-11-03 14:50:06 -06:00 committed by SkCQ
parent 292bbb13d8
commit 233f4d8a21
7 changed files with 208 additions and 181 deletions

View File

@ -14,6 +14,7 @@
#include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
#include "src/gpu/tessellate/PathCurveTessellator.h"
#include "src/gpu/tessellate/PathWedgeTessellator.h"
#include "src/gpu/tessellate/PathXform.h"
#include "src/gpu/tessellate/StrokeFixedCountTessellator.h"
#include "src/gpu/tessellate/StrokeHardwareTessellator.h"
#include "src/gpu/tessellate/WangsFormula.h"
@ -235,13 +236,12 @@ DEF_PATH_TESS_BENCH(middle_out_triangulation,
int baseVertex;
VertexWriter vertexWriter = static_cast<SkPoint*>(fTarget->makeVertexSpace(
sizeof(SkPoint), kNumCubicsInChalkboard, &buffer, &baseVertex));
int numTrianglesWritten;
WritePathMiddleOutInnerFan(std::move(vertexWriter),
0,
0,
gAlmostIdentity,
fPath,
&numTrianglesWritten);
PathXform m(gAlmostIdentity);
for (PathMiddleOutFanIter it(fPath); !it.done();) {
for (auto [p0, p1, p2] : it.nextStack()) {
vertexWriter << m.map2Points(p0, p1) << m.mapPoint(p2);
}
}
}
using PathStrokeList = StrokeTessellator::PathStrokeList;

View File

@ -16,8 +16,10 @@
#include "src/gpu/glsl/GrGLSLVarying.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
#include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
#include "src/gpu/tessellate/PathCurveTessellator.h"
#include "src/gpu/tessellate/PathWedgeTessellator.h"
#include "src/gpu/tessellate/PathXform.h"
#include "src/gpu/tessellate/Tessellation.h"
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
@ -245,14 +247,13 @@ void PathStencilCoverOp::onPrepare(GrOpFlushState* flushState) {
VertexWriter triangleVertexWriter = vertexAlloc.lock<SkPoint>(maxTrianglesInFans * 3);
int fanTriangleCount = 0;
for (auto [pathMatrix, path] : *fPathDrawList) {
int numTrianglesWritten;
triangleVertexWriter = WritePathMiddleOutInnerFan(std::move(triangleVertexWriter),
0,
0,
pathMatrix,
path,
&numTrianglesWritten);
fanTriangleCount += numTrianglesWritten;
PathXform m(pathMatrix);
for (PathMiddleOutFanIter it(path); !it.done();) {
for (auto [p0, p1, p2] : it.nextStack()) {
triangleVertexWriter << m.map2Points(p0, p1) << m.mapPoint(p2);
++fanTriangleCount;
}
}
}
SkASSERT(fanTriangleCount <= maxTrianglesInFans);
fFanVertexCount = fanTriangleCount * 3;

View File

@ -13,14 +13,13 @@
#include "include/private/SkTemplates.h"
#include "src/core/SkMathPriv.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/BufferWriter.h"
#include "src/gpu/tessellate/PathXform.h"
#include <tuple>
namespace skgpu {
// 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.:
// This class generates a middle-out triangulation of a polygon. 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) {
@ -45,92 +44,10 @@ namespace skgpu {
// 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 MiddleOutPolygonTriangulator {
public:
// Writes out 3 SkPoints per triangle to "vertexWriter". Additionally writes out "pad32Count"
// repetitions of "pad32Value" after each triangle. Set pad32Count to 0 if the triangles are
// to be tightly packed.
MiddleOutPolygonTriangulator(VertexWriter&& vertexWriter,
int pad32Count,
uint32_t pad32Value,
int maxPushVertexCalls)
: fVertexWriter(std::move(vertexWriter))
, fPad32Count(pad32Count)
, fPad32Value(pad32Value) {
// Determine the deepest our stack can ever go.
int maxStackDepth = SkNextLog2(maxPushVertexCalls) + 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) {
this->popTopTriangle(pt);
vertexIdxDelta *= 2;
}
this->pushVertex(vertexIdxDelta, pt);
}
int close() {
if (fTop == fVertexStack) { // The stack only contains one point (the starting point).
return fTotalClosedTriangleCount;
}
// 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 - 1 > fVertexStack) {
finalVertexCount += fTop->fVertexIdxDelta;
this->popTopTriangle(p0);
}
SkASSERT(fTop == fVertexStack + 1);
finalVertexCount += fTop->fVertexIdxDelta;
SkASSERT(fVertexStack[0].fVertexIdxDelta == 0);
fTop = fVertexStack;
int numTriangles = finalVertexCount - 2;
SkASSERT(numTriangles >= 0);
fTotalClosedTriangleCount += numTriangles;
return fTotalClosedTriangleCount;
}
void closeAndMove(const SkPoint& startPt) {
this->close();
SkASSERT(fTop == fVertexStack); // 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.
}
VertexWriter detachVertexWriter() { return std::move(fVertexWriter); }
private:
// Internal representation of how we store vertices on our stack.
struct StackVertex {
SkPoint fPoint;
// 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:
//
@ -139,35 +56,185 @@ private:
//
// NOTE: fVertexStack[0].fVertexIdxDelta always == 0.
int fVertexIdxDelta;
SkPoint fPoint;
};
void pushVertex(int vertexIdxDelta, const SkPoint& point) {
++fTop;
// We should never push deeper than fStackAllocCount.
SkASSERT(fTop < fVertexStack + fStackAllocCount);
fTop->fVertexIdxDelta = vertexIdxDelta;
fTop->fPoint = point;
}
void popTopTriangle(const SkPoint& lastPt) {
SkASSERT(fTop > fVertexStack); // We should never pop the starting point.
--fTop;
fVertexWriter << fTop[0].fPoint << fTop[1].fPoint << lastPt;
if (fPad32Count) {
// Output a 4-point conic with w=Inf.
fVertexWriter.fill(fPad32Value, fPad32Count);
public:
// RAII. This class is designed to first allow the caller to iterate the triangles that will be
// popped off our stack, and then (during the destructor) it actually pops the finished vertices
// and pushes a new one. Example usage:
//
// for (auto [p0, p1, p2] : middleOut.pushVertex(pt)) {
// vertexWriter << p0 << p1 << p2;
// }
//
// The above code iterates over the triangles being popped, and then once iteration is finished,
// the PoppedTriangleStack is destroyed, triggering the pending stack update.
class PoppedTriangleStack {
public:
PoppedTriangleStack(MiddleOutPolygonTriangulator* middleOut,
SkPoint lastPoint,
StackVertex* end,
StackVertex* newTopVertex,
StackVertex newTopValue)
: fMiddleOut(middleOut)
, fLastPoint(lastPoint)
, fEnd(end)
, fNewTopVertex(newTopVertex)
, fNewTopValue(newTopValue) {
}
PoppedTriangleStack(PoppedTriangleStack&& that) {
memcpy(this, &that, sizeof(*this));
that.fMiddleOut = nullptr; // Don't do a stack update during our destructor.
}
~PoppedTriangleStack() {
if (fMiddleOut) {
fMiddleOut->fTop = fNewTopVertex;
*fNewTopVertex = fNewTopValue;
}
}
struct Iter {
bool operator!=(const Iter& iter) { return fVertex != iter.fVertex; }
void operator++() { --fVertex; }
std::tuple<SkPoint, SkPoint, SkPoint> operator*() {
return {fVertex[-1].fPoint, fVertex[0].fPoint, fLastPoint};
}
StackVertex* fVertex;
SkPoint fLastPoint;
};
Iter begin() const { return {fMiddleOut ? fMiddleOut->fTop : fEnd, fLastPoint}; }
Iter end() const { return {fEnd, fLastPoint}; }
private:
MiddleOutPolygonTriangulator* fMiddleOut;
SkPoint fLastPoint;
StackVertex* fEnd;
StackVertex* fNewTopVertex;
StackVertex fNewTopValue;
};
// maxPushVertexCalls is an upper bound on the number of times the caller will call
// pushVertex(). The caller must not call it more times than this. (Beware of int overflow.)
MiddleOutPolygonTriangulator(int maxPushVertexCalls) {
SkASSERT(maxPushVertexCalls >= 0);
// Determine the deepest our stack can ever go.
int maxStackDepth = SkNextLog2(maxPushVertexCalls) + 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;
}
// Returns an RAII object that first allows the caller to iterate the triangles we will pop,
// pops those triangles, and finally pushes 'pt' onto the vertex stack.
SK_WARN_UNUSED_RESULT PoppedTriangleStack pushVertex(SkPoint pt) {
// 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
//
// Find as many new triangles as we can pop off the stack that have equal-delta sides. (This
// is a stack-based implementation of the recursive example method from the class comment.)
StackVertex* endVertex = fTop;
int vertexIdxDelta = 1;
while (endVertex->fVertexIdxDelta == vertexIdxDelta) {
--endVertex;
vertexIdxDelta *= 2;
}
// Once the above triangles are popped, push 'pt' to the top of the stack.
StackVertex* newTopVertex = endVertex + 1;
StackVertex newTopValue = {pt, vertexIdxDelta};
SkASSERT(newTopVertex < fVertexStack + fStackAllocCount); // Is fStackAllocCount enough?
return PoppedTriangleStack(this, pt, endVertex, newTopVertex, newTopValue);
}
// Returns an RAII object that first allows the caller to iterate the remaining triangles, then
// resets the vertex stack with newStartPoint.
SK_WARN_UNUSED_RESULT PoppedTriangleStack closeAndMove(SkPoint newStartPoint) {
// Add an implicit line back to the starting point.
SkPoint startPt = fVertexStack[0].fPoint;
// Triangulate the rest of the polygon. Since we simply have to finish now, we can't be
// picky anymore about getting a pure middle-out topology.
StackVertex* endVertex = std::min(fTop, fVertexStack + 1);
// Once every remaining triangle is popped, reset the vertex stack with newStartPoint.
StackVertex* newTopVertex = fVertexStack;
StackVertex newTopValue = {newStartPoint, 0};
return PoppedTriangleStack(this, startPt, endVertex, newTopVertex, newTopValue);
}
// Returns an RAII object that first allows the caller to iterate the remaining triangles, then
// resets the vertex stack with the same starting point as it had before.
SK_WARN_UNUSED_RESULT PoppedTriangleStack close() {
return this->closeAndMove(fVertexStack[0].fPoint);
}
private:
constexpr static int kStackPreallocCount = 32;
VertexWriter fVertexWriter;
const int fPad32Count;
const uint32_t fPad32Value;
SkAutoSTMalloc<kStackPreallocCount, StackVertex> fVertexStack;
SkDEBUGCODE(int fStackAllocCount;)
StackVertex* fTop;
int fTotalClosedTriangleCount = 0;
};
// This is a helper class that transforms and pushes a path's inner fan vertices onto a
// MiddleOutPolygonTriangulator. Example usage:
//
// for (PathMiddleOutFanIter it(pathMatrix, path); !it.done();) {
// for (auto [p0, p1, p2] : it.nextStack()) {
// vertexWriter << p0 << p1 << p2;
// }
// }
//
class PathMiddleOutFanIter {
public:
PathMiddleOutFanIter(const SkPath& path) : fMiddleOut(path.countVerbs()) {
SkPathPriv::Iterate it(path);
fPathIter = it.begin();
fPathEnd = it.end();
}
bool done() const { return fDone; }
MiddleOutPolygonTriangulator::PoppedTriangleStack nextStack() {
SkASSERT(!fDone);
if (fPathIter == fPathEnd) {
fDone = true;
return fMiddleOut.close();
}
switch (auto [verb, pts, w] = *fPathIter++; verb) {
SkPoint pt;
case SkPathVerb::kMove:
return fMiddleOut.closeAndMove(pts[0]);
case SkPathVerb::kLine:
case SkPathVerb::kQuad:
case SkPathVerb::kConic:
case SkPathVerb::kCubic:
pt = pts[SkPathPriv::PtsInIter((unsigned)verb) - 1];
return fMiddleOut.pushVertex(pt);
case SkPathVerb::kClose:
return fMiddleOut.close();
}
SkUNREACHABLE;
}
private:
MiddleOutPolygonTriangulator fMiddleOut;
SkPathPriv::RangeIter fPathIter;
SkPathPriv::RangeIter fPathEnd;
bool fDone = false;
};
} // namespace skgpu

View File

@ -228,22 +228,19 @@ void PathCurveTessellator::prepare(GrMeshDrawTarget* target,
}
int numRemainingTriangles = maxTriangles;
if (fDrawInnerFan) {
// Pad the triangles with 2 infinities. This produces conic patches with w=Infinity. In
// the case where infinity is not supported, we also write out a 3rd float that
// explicitly tells the shader to interpret these patches as triangles.
int pad32Count = shaderCaps.infinitySupport() ? 2 : 3;
uint32_t pad32Value = shaderCaps.infinitySupport()
? VertexWriter::kIEEE_32_infinity
: sk_bit_cast<uint32_t>(GrTessellationShader::kTriangularConicCurveType);
for (auto [pathMatrix, path] : pathDrawList) {
int numTrianglesWritten;
vertexWriter = WritePathMiddleOutInnerFan(std::move(vertexWriter),
pad32Count,
pad32Value,
pathMatrix,
path,
&numTrianglesWritten);
numRemainingTriangles -= numTrianglesWritten;
PathXform m(pathMatrix);
for (PathMiddleOutFanIter it(path); !it.done();) {
for (auto [p0, p1, p2] : it.nextStack()) {
vertexWriter << m.map2Points(p0, p1) << m.mapPoint(p2);
// Mark this instance as a triangle by setting it to a conic with w=Inf.
vertexWriter.fill(VertexWriter::kIEEE_32_infinity, 2);
vertexWriter << VertexWriter::If(
!shaderCaps.infinitySupport(),
GrTessellationShader::kTriangularConicCurveType);
--numRemainingTriangles;
}
}
}
}
if (breadcrumbTriangleList) {

View File

@ -54,6 +54,10 @@ public:
return skvx::bit_pun<SkPoint>(this->mapPoint(skvx::bit_pun<float2>(p)));
}
SK_ALWAYS_INLINE float4 map2Points(SkPoint p0, SkPoint p1) const {
return this->map2Points(float4(skvx::bit_pun<float2>(p0), skvx::bit_pun<float2>(p1)));
}
SK_ALWAYS_INLINE void map3Points(VertexWriter* writer, const SkPoint pts[3]) const {
*writer << this->map2Points(pts);
*writer << this->map1Point(pts + 2);

View File

@ -122,36 +122,4 @@ SkPath PreChopPathCurves(const SkPath& path, const SkMatrix& matrix, const SkRec
return chopper.path();
}
VertexWriter WritePathMiddleOutInnerFan(VertexWriter&& vertexWriter,
int pad32Count,
uint32_t pad32Value,
const SkMatrix& matrix,
const SkPath& path,
int* numTrianglesWritten) {
MiddleOutPolygonTriangulator middleOut(std::move(vertexWriter),
pad32Count,
pad32Value,
path.countVerbs());
PathXform pathXform(matrix);
for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
switch (verb) {
SkPoint pt;
case SkPathVerb::kMove:
middleOut.closeAndMove(pathXform.mapPoint(pts[0]));
break;
case SkPathVerb::kLine:
case SkPathVerb::kQuad:
case SkPathVerb::kConic:
case SkPathVerb::kCubic:
pt = pts[SkPathPriv::PtsInIter((unsigned)verb) - 1];
middleOut.pushVertex(pathXform.mapPoint(pt));
break;
case SkPathVerb::kClose:
break;
}
}
*numTrianglesWritten = middleOut.close();
return middleOut.detachVertexWriter();
}
} // namespace skgpu

View File

@ -75,16 +75,6 @@ constexpr static float kMaxTessellationSegmentsPerCurve SK_MAYBE_UNUSED = 1024;
// the viewport are flattened into lines.
SkPath PreChopPathCurves(const SkPath&, const SkMatrix&, const SkRect& viewport);
// Writes out the path's inner fan using a middle-out topology. Writes 3 points per triangle.
// Additionally writes out "pad32Count" repetitions of "pad32Value" after each triangle. Set
// pad32Count to 0 if the triangles are to be tightly packed.
VertexWriter WritePathMiddleOutInnerFan(VertexWriter&&,
int pad32Count,
uint32_t pad32Value,
const SkMatrix&,
const SkPath&,
int* numTrianglesWritten);
} // namespace skgpu
#endif // tessellate_Tessellation_DEFINED