diff options
-rw-r--r-- | src/gpu/GrPathUtils.cpp | 13 | ||||
-rw-r--r-- | src/gpu/GrPathUtils.h | 3 | ||||
-rw-r--r-- | src/gpu/ops/GrDefaultPathRenderer.cpp | 477 |
3 files changed, 213 insertions, 280 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 a30c8f4d2d..c282036971 100644 --- a/src/gpu/ops/GrDefaultPathRenderer.cpp +++ b/src/gpu/ops/GrDefaultPathRenderer.cpp @@ -59,271 +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 (fVertexOffset > 0) { - 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: @@ -387,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 { @@ -446,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; } |