aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar Brian Osman <brianosman@google.com>2017-06-07 14:43:17 +0000
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-06-07 14:43:29 +0000
commitfd7819c5d8779e7394277abb49e0a0b2b03d1045 (patch)
tree6fc8e37394d7922c1baf8c09312edce584a6fe5f /src
parent7785dd235d50e51f80bc4bb82639b0ffb29540b5 (diff)
Revert "Handle too many (or too large) paths in GrDefaultPathRenderer"
This reverts commit 6383b298489504d7f8f822d7da575b04b14a9737. Reason for revert: Test failures Original change's description: > Handle too many (or too large) paths in GrDefaultPathRenderer > > PathGeoBuilder constructs the geometry with the same basic > technique as before, but allows interrupting the process > to emit multiple draws. > > Original test case was 2000 non-AA stroked circles, which > created ~66000 vertices. That now renders, as do various > tests with a single large path (as well as filled paths). > > TODO: I think that this could be extracted and re-used for > MSAA path renderer without too much work? I need to read > that code more carefully to make sure it lines up. > > Bug: skia:6695 > Change-Id: I18983ba3d4f475ae0651946958b4911008aa623f > Reviewed-on: https://skia-review.googlesource.com/18360 > Reviewed-by: Brian Salomon <bsalomon@google.com> > Commit-Queue: Brian Osman <brianosman@google.com> TBR=bsalomon@google.com,robertphillips@google.com,brianosman@google.com No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: skia:6695 Change-Id: I78ce9879a2e45e19f53027ca506cc2e8fae664b3 Reviewed-on: https://skia-review.googlesource.com/18981 Reviewed-by: Brian Osman <brianosman@google.com> Commit-Queue: Brian Osman <brianosman@google.com>
Diffstat (limited to 'src')
-rw-r--r--src/gpu/GrPathUtils.cpp13
-rw-r--r--src/gpu/GrPathUtils.h3
-rw-r--r--src/gpu/ops/GrDefaultPathRenderer.cpp475
3 files changed, 213 insertions, 278 deletions
diff --git a/src/gpu/GrPathUtils.cpp b/src/gpu/GrPathUtils.cpp
index 918493982a..91a48f7b6b 100644
--- a/src/gpu/GrPathUtils.cpp
+++ b/src/gpu/GrPathUtils.cpp
@@ -11,6 +11,7 @@
#include "SkGeometry.h"
#include "SkMathPriv.h"
+static const int MAX_POINTS_PER_CURVE = 1 << 10;
static const SkScalar gMinCurveTol = 0.0001f;
SkScalar GrPathUtils::scaleToleranceToSrc(SkScalar devTol,
@@ -44,7 +45,7 @@ uint32_t GrPathUtils::quadraticPointCount(const SkPoint points[], SkScalar tol)
SkScalar d = points[1].distanceToLineSegmentBetween(points[0], points[2]);
if (!SkScalarIsFinite(d)) {
- return kMaxPointsPerCurve;
+ return MAX_POINTS_PER_CURVE;
} else if (d <= tol) {
return 1;
} else {
@@ -54,7 +55,7 @@ uint32_t GrPathUtils::quadraticPointCount(const SkPoint points[], SkScalar tol)
// 2^(log4(x)) = sqrt(x);
SkScalar divSqrt = SkScalarSqrt(d / tol);
if (((SkScalar)SK_MaxS32) <= divSqrt) {
- return kMaxPointsPerCurve;
+ return MAX_POINTS_PER_CURVE;
} else {
int temp = SkScalarCeilToInt(divSqrt);
int pow2 = GrNextPow2(temp);
@@ -64,7 +65,7 @@ uint32_t GrPathUtils::quadraticPointCount(const SkPoint points[], SkScalar tol)
if (pow2 < 1) {
pow2 = 1;
}
- return SkTMin(pow2, kMaxPointsPerCurve);
+ return SkTMin(pow2, MAX_POINTS_PER_CURVE);
}
}
}
@@ -104,13 +105,13 @@ uint32_t GrPathUtils::cubicPointCount(const SkPoint points[],
points[2].distanceToLineSegmentBetweenSqd(points[0], points[3]));
d = SkScalarSqrt(d);
if (!SkScalarIsFinite(d)) {
- return kMaxPointsPerCurve;
+ return MAX_POINTS_PER_CURVE;
} else if (d <= tol) {
return 1;
} else {
SkScalar divSqrt = SkScalarSqrt(d / tol);
if (((SkScalar)SK_MaxS32) <= divSqrt) {
- return kMaxPointsPerCurve;
+ return MAX_POINTS_PER_CURVE;
} else {
int temp = SkScalarCeilToInt(SkScalarSqrt(d / tol));
int pow2 = GrNextPow2(temp);
@@ -120,7 +121,7 @@ uint32_t GrPathUtils::cubicPointCount(const SkPoint points[],
if (pow2 < 1) {
pow2 = 1;
}
- return SkTMin(pow2, kMaxPointsPerCurve);
+ return SkTMin(pow2, MAX_POINTS_PER_CURVE);
}
}
}
diff --git a/src/gpu/GrPathUtils.h b/src/gpu/GrPathUtils.h
index 421fca129b..fdfd375427 100644
--- a/src/gpu/GrPathUtils.h
+++ b/src/gpu/GrPathUtils.h
@@ -165,8 +165,5 @@ namespace GrPathUtils {
// This value was chosen to approximate the supersampling accuracy of the raster path (16
// samples, or one quarter pixel).
static const SkScalar kDefaultTolerance = SkDoubleToScalar(0.25);
-
- // We guarantee that no quad or cubic will ever produce more than this many points
- static const int kMaxPointsPerCurve = 1 << 10;
};
#endif
diff --git a/src/gpu/ops/GrDefaultPathRenderer.cpp b/src/gpu/ops/GrDefaultPathRenderer.cpp
index 22e24e6544..c282036971 100644
--- a/src/gpu/ops/GrDefaultPathRenderer.cpp
+++ b/src/gpu/ops/GrDefaultPathRenderer.cpp
@@ -59,269 +59,37 @@ GrDefaultPathRenderer::onGetStencilSupport(const GrShape& shape) const {
}
}
-// Needs to be large enough to handle quads/cubics, which have a worst-case of 1k points
-static const int kVerticesPerChunk = 16384;
-
-class PathGeoBuilder {
-public:
- PathGeoBuilder(GrPrimitiveType primitiveType, GrMeshDrawOp::Target* target,
- GrGeometryProcessor* geometryProcessor, const GrPipeline* pipeline)
- : fMesh(primitiveType)
- , fTarget(target)
- , fVertexStride(sizeof(SkPoint))
- , fGeometryProcessor(geometryProcessor)
- , fPipeline(pipeline)
- , fIndexBuffer(nullptr)
- , fFirstIndex(0)
- , fIndices(nullptr) {
- this->allocNewBuffers();
- }
-
- ~PathGeoBuilder() {
- this->emitMesh();
- this->putBackReserve();
- }
-
- // Called before we start each path
- void beginInstance() {
- fSubpathIndexStart = fVertexOffset;
- fCurIdx = fIndices + fIndexOffset;
- fCurVert = fVertices + fVertexOffset;
- }
-
- // Called after we end each path
- void endInstance() {
- fVertexOffset = fCurVert - fVertices;
- fIndexOffset = fCurIdx - fIndices;
- SkASSERT(fVertexOffset <= kVerticesPerChunk);
- SkASSERT(fIndexOffset <= this->maxIndices());
- }
-
- /**
- * Path verbs
- */
- void moveTo(const SkPoint& p) {
- needSpace(1);
-
- fSubpathIndexStart = this->currentIndex();
- *(fCurVert++) = p;
- }
-
- void addLine(const SkPoint& p) {
- needSpace(1, this->indexScale());
-
- if (this->isIndexed()) {
- uint16_t prevIdx = this->currentIndex() - 1;
- appendCountourEdgeIndices(prevIdx);
- }
- *(fCurVert++) = p;
- }
-
- void addQuad(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) {
- this->needSpace(GrPathUtils::kMaxPointsPerCurve,
- GrPathUtils::kMaxPointsPerCurve * this->indexScale());
-
- // First pt of quad is the pt we ended on in previous step
- uint16_t firstQPtIdx = this->currentIndex() - 1;
- uint16_t numPts = (uint16_t)GrPathUtils::generateQuadraticPoints(
- pts[0], pts[1], pts[2], srcSpaceTolSqd, &fCurVert,
- GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
- if (this->isIndexed()) {
- for (uint16_t i = 0; i < numPts; ++i) {
- appendCountourEdgeIndices(firstQPtIdx + i);
- }
- }
- }
-
- void addConic(SkScalar weight, const SkPoint pts[], SkScalar srcSpaceTolSqd,
- SkScalar srcSpaceTol) {
- SkAutoConicToQuads converter;
- const SkPoint* quadPts = converter.computeQuads(pts, weight, srcSpaceTol);
- for (int i = 0; i < converter.countQuads(); ++i) {
- this->addQuad(quadPts + i * 2, srcSpaceTolSqd, srcSpaceTol);
- }
- }
-
- void addCubic(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) {
- this->needSpace(GrPathUtils::kMaxPointsPerCurve,
- GrPathUtils::kMaxPointsPerCurve * this->indexScale());
-
- // First pt of cubic is the pt we ended on in previous step
- uint16_t firstCPtIdx = this->currentIndex() - 1;
- uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints(
- pts[0], pts[1], pts[2], pts[3], srcSpaceTolSqd, &fCurVert,
- GrPathUtils::cubicPointCount(pts, srcSpaceTol));
- if (this->isIndexed()) {
- for (uint16_t i = 0; i < numPts; ++i) {
- appendCountourEdgeIndices(firstCPtIdx + i);
- }
- }
- }
-
- void addPath(const SkPath& path, SkScalar srcSpaceTol) {
- SkScalar srcSpaceTolSqd = srcSpaceTol * srcSpaceTol;
-
- SkPath::Iter iter(path, false);
- SkPoint pts[4];
-
- bool done = false;
- while (!done) {
- SkPath::Verb verb = iter.next(pts);
- switch (verb) {
- case SkPath::kMove_Verb:
- this->moveTo(pts[0]);
- break;
- case SkPath::kLine_Verb:
- this->addLine(pts[1]);
- break;
- case SkPath::kConic_Verb:
- this->addConic(iter.conicWeight(), pts, srcSpaceTolSqd, srcSpaceTol);
- break;
- case SkPath::kQuad_Verb:
- this->addQuad(pts, srcSpaceTolSqd, srcSpaceTol);
- break;
- case SkPath::kCubic_Verb:
- this->addCubic(pts, srcSpaceTolSqd, srcSpaceTol);
- break;
- case SkPath::kClose_Verb:
- break;
- case SkPath::kDone_Verb:
- done = true;
- }
- }
- }
-
- static bool PathHasMultipleSubpaths(const SkPath& path) {
- bool first = true;
-
- SkPath::Iter iter(path, false);
- SkPath::Verb verb;
-
- SkPoint pts[4];
- while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
- if (SkPath::kMove_Verb == verb && !first) {
- return true;
- }
- first = false;
- }
- return false;
- }
-
-private:
- /**
- * Derived properties
- * TODO: Cache some of these for better performance, rather than re-computing?
- */
- bool isIndexed() const {
- return kLines_GrPrimitiveType == fMesh.primitiveType() ||
- kTriangles_GrPrimitiveType == fMesh.primitiveType();
- }
- bool isHairline() const {
- return kLines_GrPrimitiveType == fMesh.primitiveType() ||
- kLineStrip_GrPrimitiveType == fMesh.primitiveType();
- }
- int indexScale() const {
- switch (fMesh.primitiveType()) {
- case kLines_GrPrimitiveType:
- return 2;
- case kTriangles_GrPrimitiveType:
- return 3;
- default:
- return 0;
- }
- }
- int maxIndices() const { return kVerticesPerChunk * this->indexScale(); }
-
- uint16_t currentIndex() const { return fCurVert - fVertices; }
-
- void putBackReserve() {
- fTarget->putBackIndices((size_t)(this->maxIndices() - fIndexOffset));
- fTarget->putBackVertices((size_t)(kVerticesPerChunk - fVertexOffset), fVertexStride);
- }
-
- // Allocate vertex and (possibly) index buffers
- void allocNewBuffers() {
- fVertices = static_cast<SkPoint*>(fTarget->makeVertexSpace(fVertexStride, kVerticesPerChunk,
- &fVertexBuffer, &fFirstVertex));
- if (this->isIndexed()) {
- fIndices = fTarget->makeIndexSpace(this->maxIndices(), &fIndexBuffer, &fFirstIndex);
- }
- fVertexOffset = 0;
- fIndexOffset = 0;
- }
-
- void appendCountourEdgeIndices(uint16_t edgeV0Idx) {
- // When drawing lines we're appending line segments along the countour. When applying the
- // other fill rules we're drawing triangle fans around the start of the current (sub)path.
- if (!this->isHairline()) {
- *(fCurIdx++) = fSubpathIndexStart;
- }
- *(fCurIdx++) = edgeV0Idx;
- *(fCurIdx++) = edgeV0Idx + 1;
- }
-
- // Emits a single draw with all accumulated vertex/index data
- void emitMesh() {
- if (!this->isIndexed()) {
- fMesh.setNonIndexedNonInstanced(fVertexOffset);
- } else {
- fMesh.setIndexed(fIndexBuffer, fIndexOffset, fFirstIndex, 0, fVertexOffset - 1);
- }
- fMesh.setVertexData(fVertexBuffer, fFirstVertex);
- fTarget->draw(fGeometryProcessor, fPipeline, fMesh);
- }
-
- void needSpace(int vertsNeeded, int indicesNeeded = 0) {
- if (fCurVert + vertsNeeded > fVertices + kVerticesPerChunk ||
- fCurIdx + indicesNeeded > fIndices + this->maxIndices()) {
- // We are about to run out of space (possibly)
-
- // To maintain continuity, we need to remember one or two points from the current mesh.
- // Lines only need the last point, fills need the first point from the current contour.
- // We always grab both here, and append the ones we need at the end of this process.
- SkPoint lastPt = *(fCurVert - 1);
- SkPoint subpathStartPt = fVertices[fSubpathIndexStart];
-
- // Pretend that we've reached the end of an entire path, so our offsets are correct
- this->endInstance();
-
- // Draw the mesh we've accumulated
- this->emitMesh();
-
- // Put back any unused space, get new buffers
- this->putBackReserve();
- this->allocNewBuffers();
-
- // Start a "new" path, which is really just a continuation of the in-progress one
- this->beginInstance();
+static inline void append_countour_edge_indices(bool hairLine,
+ uint16_t fanCenterIdx,
+ uint16_t edgeV0Idx,
+ uint16_t** indices) {
+ // when drawing lines we're appending line segments along
+ // the contour. When applying the other fill rules we're
+ // drawing triangle fans around fanCenterIdx.
+ if (!hairLine) {
+ *((*indices)++) = fanCenterIdx;
+ }
+ *((*indices)++) = edgeV0Idx;
+ *((*indices)++) = edgeV0Idx + 1;
+}
- // Append copies of the points we saved so the two meshes will weld properly
- if (!this->isHairline()) {
- *(fCurVert++) = subpathStartPt;
- }
- *(fCurVert++) = lastPt;
+static inline void add_quad(SkPoint** vert, const SkPoint* base, const SkPoint pts[],
+ SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol, bool indexed,
+ bool isHairline, uint16_t subpathIdxStart, int offset, uint16_t** idx) {
+ // first pt of quad is the pt we ended on in previous step
+ uint16_t firstQPtIdx = (uint16_t)(*vert - base) - 1 + offset;
+ uint16_t numPts = (uint16_t)
+ GrPathUtils::generateQuadraticPoints(
+ pts[0], pts[1], pts[2],
+ srcSpaceTolSqd, vert,
+ GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
+ if (indexed) {
+ for (uint16_t i = 0; i < numPts; ++i) {
+ append_countour_edge_indices(isHairline, subpathIdxStart,
+ firstQPtIdx + i, idx);
}
}
-
- GrMesh fMesh;
- GrMeshDrawOp::Target* fTarget;
- size_t fVertexStride;
- GrGeometryProcessor* fGeometryProcessor;
- const GrPipeline* fPipeline;
-
- const GrBuffer* fVertexBuffer;
- int fFirstVertex;
- SkPoint* fVertices;
- SkPoint* fCurVert;
- int fVertexOffset;
-
- const GrBuffer* fIndexBuffer;
- int fFirstIndex;
- uint16_t* fIndices;
- uint16_t* fCurIdx;
- int fIndexOffset;
- uint16_t fSubpathIndexStart;
-};
+}
class DefaultPathOp final : public GrLegacyMeshDrawOp {
public:
@@ -385,35 +153,112 @@ private:
gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, this->viewMatrix());
}
- SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
+ size_t vertexStride = gp->getVertexStride();
+ SkASSERT(vertexStride == sizeof(SkPoint));
int instanceCount = fPaths.count();
+ // compute number of vertices
+ int maxVertices = 0;
+
// We will use index buffers if we have multiple paths or one path with multiple contours
bool isIndexed = instanceCount > 1;
- for (int i = 0; !isIndexed && i < instanceCount; i++) {
+ for (int i = 0; i < instanceCount; i++) {
const PathData& args = fPaths[i];
- isIndexed = isIndexed || PathGeoBuilder::PathHasMultipleSubpaths(args.fPath);
+
+ int contourCount;
+ maxVertices += GrPathUtils::worstCasePointCount(args.fPath, &contourCount,
+ args.fTolerance);
+
+ isIndexed = isIndexed || contourCount > 1;
+ }
+
+ if (maxVertices == 0 || maxVertices > ((int)SK_MaxU16 + 1)) {
+ //SkDebugf("Cannot render path (%d)\n", maxVertices);
+ return;
}
// determine primitiveType
+ int maxIndices = 0;
GrPrimitiveType primitiveType;
if (this->isHairline()) {
- primitiveType = isIndexed ? kLines_GrPrimitiveType : kLineStrip_GrPrimitiveType;
+ if (isIndexed) {
+ maxIndices = 2 * maxVertices;
+ primitiveType = kLines_GrPrimitiveType;
+ } else {
+ primitiveType = kLineStrip_GrPrimitiveType;
+ }
} else {
- primitiveType = isIndexed ? kTriangles_GrPrimitiveType : kTriangleFan_GrPrimitiveType;
+ if (isIndexed) {
+ maxIndices = 3 * maxVertices;
+ primitiveType = kTriangles_GrPrimitiveType;
+ } else {
+ primitiveType = kTriangleFan_GrPrimitiveType;
+ }
+ }
+
+ // allocate vertex / index buffers
+ const GrBuffer* vertexBuffer;
+ int firstVertex;
+
+ void* verts = target->makeVertexSpace(vertexStride, maxVertices,
+ &vertexBuffer, &firstVertex);
+
+ if (!verts) {
+ SkDebugf("Could not allocate vertices\n");
+ return;
}
- PathGeoBuilder pathGeoBuilder(primitiveType, target, gp.get(), this->pipeline());
+ const GrBuffer* indexBuffer = nullptr;
+ int firstIndex = 0;
+
+ void* indices = nullptr;
+ if (isIndexed) {
+ indices = target->makeIndexSpace(maxIndices, &indexBuffer, &firstIndex);
+
+ if (!indices) {
+ SkDebugf("Could not allocate indices\n");
+ return;
+ }
+ }
// fill buffers
+ int vertexOffset = 0;
+ int indexOffset = 0;
for (int i = 0; i < instanceCount; i++) {
const PathData& args = fPaths[i];
- pathGeoBuilder.beginInstance();
- pathGeoBuilder.addPath(args.fPath, args.fTolerance);
- pathGeoBuilder.endInstance();
+ int vertexCnt = 0;
+ int indexCnt = 0;
+ if (!this->createGeom(verts,
+ vertexOffset,
+ indices,
+ indexOffset,
+ &vertexCnt,
+ &indexCnt,
+ args.fPath,
+ args.fTolerance,
+ isIndexed)) {
+ return;
+ }
+
+ vertexOffset += vertexCnt;
+ indexOffset += indexCnt;
+ SkASSERT(vertexOffset <= maxVertices && indexOffset <= maxIndices);
}
+
+ GrMesh mesh(primitiveType);
+ if (!isIndexed) {
+ mesh.setNonIndexedNonInstanced(vertexOffset);
+ } else {
+ mesh.setIndexed(indexBuffer, indexOffset, firstIndex, 0, vertexOffset - 1);
+ }
+ mesh.setVertexData(vertexBuffer, firstVertex);
+ target->draw(gp.get(), this->pipeline(), mesh);
+
+ // put back reserves
+ target->putBackIndices((size_t)(maxIndices - indexOffset));
+ target->putBackVertices((size_t)(maxVertices - vertexOffset), (size_t)vertexStride);
}
bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -444,6 +289,98 @@ private:
return true;
}
+ bool createGeom(void* vertices,
+ size_t vertexOffset,
+ void* indices,
+ size_t indexOffset,
+ int* vertexCnt,
+ int* indexCnt,
+ const SkPath& path,
+ SkScalar srcSpaceTol,
+ bool isIndexed) const {
+ SkScalar srcSpaceTolSqd = srcSpaceTol * srcSpaceTol;
+
+ uint16_t indexOffsetU16 = (uint16_t)indexOffset;
+ uint16_t vertexOffsetU16 = (uint16_t)vertexOffset;
+
+ uint16_t* idxBase = reinterpret_cast<uint16_t*>(indices) + indexOffsetU16;
+ uint16_t* idx = idxBase;
+ uint16_t subpathIdxStart = vertexOffsetU16;
+
+ SkPoint* base = reinterpret_cast<SkPoint*>(vertices) + vertexOffset;
+ SkPoint* vert = base;
+
+ SkPoint pts[4];
+
+ bool first = true;
+ int subpath = 0;
+
+ SkPath::Iter iter(path, false);
+
+ bool done = false;
+ while (!done) {
+ SkPath::Verb verb = iter.next(pts);
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (!first) {
+ uint16_t currIdx = (uint16_t) (vert - base) + vertexOffsetU16;
+ subpathIdxStart = currIdx;
+ ++subpath;
+ }
+ *vert = pts[0];
+ vert++;
+ break;
+ case SkPath::kLine_Verb:
+ if (isIndexed) {
+ uint16_t prevIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16;
+ append_countour_edge_indices(this->isHairline(), subpathIdxStart,
+ prevIdx, &idx);
+ }
+ *(vert++) = pts[1];
+ break;
+ case SkPath::kConic_Verb: {
+ SkScalar weight = iter.conicWeight();
+ SkAutoConicToQuads converter;
+ const SkPoint* quadPts = converter.computeQuads(pts, weight, srcSpaceTol);
+ for (int i = 0; i < converter.countQuads(); ++i) {
+ add_quad(&vert, base, quadPts + i*2, srcSpaceTolSqd, srcSpaceTol,
+ isIndexed, this->isHairline(), subpathIdxStart,
+ (int)vertexOffset, &idx);
+ }
+ break;
+ }
+ case SkPath::kQuad_Verb:
+ add_quad(&vert, base, pts, srcSpaceTolSqd, srcSpaceTol, isIndexed,
+ this->isHairline(), subpathIdxStart, (int)vertexOffset, &idx);
+ break;
+ case SkPath::kCubic_Verb: {
+ // first pt of cubic is the pt we ended on in previous step
+ uint16_t firstCPtIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16;
+ uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints(
+ pts[0], pts[1], pts[2], pts[3],
+ srcSpaceTolSqd, &vert,
+ GrPathUtils::cubicPointCount(pts, srcSpaceTol));
+ if (isIndexed) {
+ for (uint16_t i = 0; i < numPts; ++i) {
+ append_countour_edge_indices(this->isHairline(), subpathIdxStart,
+ firstCPtIdx + i, &idx);
+ }
+ }
+ break;
+ }
+ case SkPath::kClose_Verb:
+ break;
+ case SkPath::kDone_Verb:
+ done = true;
+ }
+ first = false;
+ }
+
+ *vertexCnt = static_cast<int>(vert - base);
+ *indexCnt = static_cast<int>(idx - idxBase);
+ return true;
+ }
+
GrColor color() const { return fColor; }
uint8_t coverage() const { return fCoverage; }
bool usesLocalCoords() const { return fUsesLocalCoords; }