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:
parent
2514bd790a
commit
5f56cb1d3b
@ -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 {
|
||||
|
@ -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",
|
||||
|
89
src/gpu/tessellate/GrInnerPolygonContourParser.h
Normal file
89
src/gpu/tessellate/GrInnerPolygonContourParser.h
Normal 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
|
@ -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
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user