Revert "Handle too many (or too large) paths in GrDefaultPathRenderer"

This reverts commit 6383b29848.

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>
This commit is contained in:
Brian Osman 2017-06-07 14:43:17 +00:00 committed by Skia Commit-Bot
parent 7785dd235d
commit fd7819c5d8
3 changed files with 223 additions and 288 deletions

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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();
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;
}
~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);
}
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);
}
}
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();
// Append copies of the points we saved so the two meshes will weld properly
if (!this->isHairline()) {
*(fCurVert++) = subpathStartPt;
}
*(fCurVert++) = lastPt;
}
}
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++) {
const PathData& args = fPaths[i];
isIndexed = isIndexed || PathGeoBuilder::PathHasMultipleSubpaths(args.fPath);
}
// determine primitiveType
GrPrimitiveType primitiveType;
if (this->isHairline()) {
primitiveType = isIndexed ? kLines_GrPrimitiveType : kLineStrip_GrPrimitiveType;
} else {
primitiveType = isIndexed ? kTriangles_GrPrimitiveType : kTriangleFan_GrPrimitiveType;
}
PathGeoBuilder pathGeoBuilder(primitiveType, target, gp.get(), this->pipeline());
// fill buffers
for (int i = 0; i < instanceCount; i++) {
const PathData& args = fPaths[i];
pathGeoBuilder.beginInstance();
pathGeoBuilder.addPath(args.fPath, args.fTolerance);
pathGeoBuilder.endInstance();
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()) {
if (isIndexed) {
maxIndices = 2 * maxVertices;
primitiveType = kLines_GrPrimitiveType;
} else {
primitiveType = kLineStrip_GrPrimitiveType;
}
} else {
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;
}
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];
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; }