Skip to content

Commit 9d6f955

Browse files
csmartdalton86Skia Commit-Bot
authored and
Skia Commit-Bot
committed
Chop tessellated strokes in the vertex shader
The stroke tessellator can't handle inflections and can't handle rotations greater than 180 degrees. Before this CL we had to use the CPU to chop curves at their inflections and midtangents in order to meet these constraints. This CL adds a vertex shader to the tessellation pipeline that handles all the chopping logic on the GPU. Bug: skia:10419 Change-Id: I2d3279076bafa415395e014772a305e616fcff71 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/315797 Commit-Queue: Chris Dalton <[email protected]> Reviewed-by: Brian Salomon <[email protected]>
1 parent 0f64e0d commit 9d6f955

6 files changed

+575
-368
lines changed

src/gpu/tessellate/GrStrokePatchBuilder.cpp

+95-110
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,43 @@
1717
#include "src/gpu/tessellate/GrVectorXform.h"
1818
#include "src/gpu/tessellate/GrWangsFormula.h"
1919

20-
constexpr static float kStandardCubicType = GrStrokeTessellateShader::kStandardCubicType;
21-
constexpr static float kDoubleSidedRoundJoinType = -GrStrokeTessellateShader::kRoundJoinType;
20+
using Patch = GrStrokeTessellateShader::Patch;
21+
22+
constexpr static float kDoubleSidedRoundJoinType = -Patch::kRoundJoinType;
2223

2324
static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
2425
SkASSERT(1 != T); // The below does not guarantee lerp(a, b, 1) === b.
2526
return (b - a) * T + a;
2627
}
2728

28-
void GrStrokePatchBuilder::allocVertexChunk(int minVertexAllocCount) {
29-
VertexChunk* chunk = &fVertexChunkArray->push_back();
30-
fCurrChunkVertexData = (SkPoint*)fTarget->makeVertexSpaceAtLeast(
31-
sizeof(SkPoint), minVertexAllocCount, minVertexAllocCount, &chunk->fVertexBuffer,
32-
&chunk->fBaseVertex, &fCurrChunkVertexCapacity);
33-
fCurrChunkMinVertexAllocCount = minVertexAllocCount;
29+
void GrStrokePatchBuilder::allocPatchChunkAtLeast(int minPatchAllocCount) {
30+
PatchChunk* chunk = &fPatchChunkArray->push_back();
31+
fCurrChunkPatchData = (Patch*)fTarget->makeVertexSpaceAtLeast(sizeof(Patch), minPatchAllocCount,
32+
minPatchAllocCount,
33+
&chunk->fPatchBuffer,
34+
&chunk->fBasePatch,
35+
&fCurrChunkPatchCapacity);
36+
fCurrChunkMinPatchAllocCount = minPatchAllocCount;
3437
}
3538

36-
SkPoint* GrStrokePatchBuilder::reservePatch() {
37-
constexpr static int kNumVerticesPerPatch = GrStrokeTessellateShader::kNumVerticesPerPatch;
38-
if (fVertexChunkArray->back().fVertexCount + kNumVerticesPerPatch > fCurrChunkVertexCapacity) {
39+
Patch* GrStrokePatchBuilder::reservePatch() {
40+
if (fPatchChunkArray->back().fPatchCount >= fCurrChunkPatchCapacity) {
3941
// The current chunk is full. Time to allocate a new one. (And no need to put back vertices;
4042
// the buffer is full.)
41-
this->allocVertexChunk(fCurrChunkMinVertexAllocCount * 2);
43+
this->allocPatchChunkAtLeast(fCurrChunkMinPatchAllocCount * 2);
4244
}
43-
if (!fCurrChunkVertexData) {
45+
if (!fCurrChunkPatchData) {
4446
SkDebugf("WARNING: Failed to allocate vertex buffer for tessellated stroke.");
4547
return nullptr;
4648
}
47-
SkASSERT(fVertexChunkArray->back().fVertexCount + kNumVerticesPerPatch <=
48-
fCurrChunkVertexCapacity);
49-
SkPoint* patch = fCurrChunkVertexData + fVertexChunkArray->back().fVertexCount;
50-
fVertexChunkArray->back().fVertexCount += kNumVerticesPerPatch;
49+
SkASSERT(fPatchChunkArray->back().fPatchCount <= fCurrChunkPatchCapacity);
50+
Patch* patch = fCurrChunkPatchData + fPatchChunkArray->back().fPatchCount;
51+
++fPatchChunkArray->back().fPatchCount;
5152
return patch;
5253
}
5354

54-
void GrStrokePatchBuilder::writeCubicSegment(float prevJoinType, const SkPoint pts[4]) {
55+
void GrStrokePatchBuilder::writeCubicSegment(float prevJoinType, const SkPoint pts[4],
56+
float cubicType) {
5557
SkPoint c1 = (pts[1] == pts[0]) ? pts[2] : pts[1];
5658
SkPoint c2 = (pts[2] == pts[3]) ? pts[1] : pts[2];
5759

@@ -62,9 +64,11 @@ void GrStrokePatchBuilder::writeCubicSegment(float prevJoinType, const SkPoint p
6264
fHasPreviousSegment = true;
6365
}
6466

65-
if (SkPoint* patch = this->reservePatch()) {
66-
memcpy(patch, pts, sizeof(SkPoint) * 4);
67-
patch[4].set(kStandardCubicType, fCurrStrokeRadius);
67+
SkASSERT(cubicType == Patch::kStandardCubicType || cubicType == Patch::kFlatLineType);
68+
if (Patch* patch = this->reservePatch()) {
69+
memcpy(patch->fPts.data(), pts, sizeof(patch->fPts));
70+
patch->fPatchType = cubicType;
71+
patch->fStrokeRadius = fCurrStrokeRadius;
6872
}
6973

7074
fLastControlPoint = c2;
@@ -73,12 +77,12 @@ void GrStrokePatchBuilder::writeCubicSegment(float prevJoinType, const SkPoint p
7377

7478
void GrStrokePatchBuilder::writeJoin(float joinType, const SkPoint& prevControlPoint,
7579
const SkPoint& anchorPoint, const SkPoint& nextControlPoint) {
76-
if (SkPoint* joinPatch = this->reservePatch()) {
77-
joinPatch[0] = prevControlPoint;
78-
joinPatch[1] = anchorPoint;
79-
joinPatch[2] = anchorPoint;
80-
joinPatch[3] = nextControlPoint;
81-
joinPatch[4].set(joinType, fCurrStrokeRadius);
80+
SkASSERT(joinType == Patch::kRoundJoinType || joinType == Patch::kMiterJoinType ||
81+
joinType == Patch::kRoundJoinType || joinType == kDoubleSidedRoundJoinType);
82+
if (Patch* joinPatch = this->reservePatch()) {
83+
joinPatch->fPts = {{prevControlPoint, anchorPoint, anchorPoint, nextControlPoint}};
84+
joinPatch->fPatchType = joinType;
85+
joinPatch->fStrokeRadius = fCurrStrokeRadius;
8286
}
8387
}
8488

@@ -89,12 +93,10 @@ void GrStrokePatchBuilder::writeSquareCap(const SkPoint& endPoint, const SkPoint
8993
// Add a join to guarantee we get water tight seaming. Make the join type negative so it's
9094
// double sided.
9195
this->writeJoin(-fCurrStrokeJoinType, controlPoint, endPoint, capPoint);
92-
if (SkPoint* capPatch = this->reservePatch()) {
93-
capPatch[0] = endPoint;
94-
capPatch[1] = endPoint;
95-
capPatch[2] = capPoint;
96-
capPatch[3] = capPoint;
97-
capPatch[4].set(kStandardCubicType, fCurrStrokeRadius);
96+
if (Patch* capPatch = this->reservePatch()) {
97+
capPatch->fPts = {{endPoint, endPoint, capPoint, capPoint}};
98+
capPatch->fPatchType = Patch::kFlatLineType;
99+
capPatch->fStrokeRadius = fCurrStrokeRadius;
98100
}
99101
}
100102

@@ -113,10 +115,10 @@ void GrStrokePatchBuilder::writeCaps(SkPaint::Cap capType) {
113115
break;
114116
case SkPaint::kRound_Cap:
115117
// A round cap is the same thing as a 180-degree round join.
116-
this->writeJoin(GrStrokeTessellateShader::kRoundJoinType, fCurrContourFirstControlPoint,
118+
this->writeJoin(Patch::kRoundJoinType, fCurrContourFirstControlPoint,
117119
fCurrContourStartPoint, fCurrContourFirstControlPoint);
118-
this->writeJoin(GrStrokeTessellateShader::kRoundJoinType, fLastControlPoint,
119-
fCurrentPoint, fLastControlPoint);
120+
this->writeJoin(Patch::kRoundJoinType, fLastControlPoint, fCurrentPoint,
121+
fLastControlPoint);
120122
break;
121123
case SkPaint::kSquare_Cap:
122124
this->writeSquareCap(fCurrContourStartPoint, fCurrContourFirstControlPoint);
@@ -128,11 +130,11 @@ void GrStrokePatchBuilder::writeCaps(SkPaint::Cap capType) {
128130
static float join_type_from_join(SkPaint::Join join) {
129131
switch (join) {
130132
case SkPaint::kBevel_Join:
131-
return GrStrokeTessellateShader::kBevelJoinType;
133+
return GrStrokeTessellateShader::Patch::kBevelJoinType;
132134
case SkPaint::kMiter_Join:
133-
return GrStrokeTessellateShader::kMiterJoinType;
135+
return GrStrokeTessellateShader::Patch::kMiterJoinType;
134136
case SkPaint::kRound_Join:
135-
return GrStrokeTessellateShader::kRoundJoinType;
137+
return GrStrokeTessellateShader::Patch::kRoundJoinType;
136138
}
137139
SkUNREACHABLE;
138140
}
@@ -210,7 +212,7 @@ void GrStrokePatchBuilder::lineTo(float prevJoinType, const SkPoint& p0, const S
210212
}
211213

212214
SkPoint cubic[4] = {p0, p0, p1, p1};
213-
this->writeCubicSegment(prevJoinType, cubic);
215+
this->writeCubicSegment(prevJoinType, cubic, Patch::kFlatLineType);
214216
}
215217

216218
void GrStrokePatchBuilder::quadraticTo(float prevJoinType, const SkPoint p[3], int maxDepth) {
@@ -257,44 +259,11 @@ void GrStrokePatchBuilder::quadraticTo(float prevJoinType, const SkPoint p[3], i
257259
}
258260

259261
SkPoint cubic[4] = {p[0], lerp(p[0], p[1], 2/3.f), lerp(p[1], p[2], 1/3.f), p[2]};
260-
this->writeCubicSegment(prevJoinType, cubic);
261-
}
262-
263-
void GrStrokePatchBuilder::cubicTo(float prevJoinType, const SkPoint inputPts[4]) {
264-
const SkPoint* p = inputPts;
265-
int numCubics = 1;
266-
SkPoint chopped[10];
267-
double tt[2], ss[2];
268-
if (SkClassifyCubic(p, tt, ss) == SkCubicType::kSerpentine) {
269-
// TEMPORARY: Don't allow cubics to have inflection points.
270-
// TODO: This will soon be moved into the GPU tessellation pipeline and handled more
271-
// elegantly.
272-
float t[2] = {(float)(tt[0]/ss[0]), (float)(tt[1]/ss[1])};
273-
const float* begin = (t[0] > 0 && t[0] < 1) ? t : t+1;
274-
const float* end = (t[1] > 0 && t[1] > t[0] && t[1] < 1) ? t+2 : t+1;
275-
numCubics = (end - begin) + 1;
276-
if (numCubics > 1) {
277-
SkChopCubicAt(p, chopped, begin, end - begin);
278-
p = chopped;
279-
}
280-
} else if (SkMeasureNonInflectCubicRotation(p) > SK_ScalarPI*15/16) {
281-
// TEMPORARY: Don't allow cubics to turn more than 180 degrees. We chop them when they get
282-
// close, just to be sure.
283-
// TODO: This will soon be moved into the GPU tessellation pipeline and handled more
284-
// elegantly.
285-
SkChopCubicAtMidTangent(p, chopped);
286-
p = chopped;
287-
numCubics = 2;
288-
}
289-
for (int i = 0; i < numCubics; ++i) {
290-
this->nonInflectCubicTo(prevJoinType, p + i*3);
291-
// Use kDoubleSidedRoundJoinType in case we happened to chop at an exact cusp or turnaround
292-
// point of a flat cubic, in which case we would lose 180 degrees of rotation.
293-
prevJoinType = kDoubleSidedRoundJoinType;
294-
}
262+
this->writeCubicSegment(prevJoinType, cubic, Patch::kStandardCubicType);
295263
}
296264

297-
void GrStrokePatchBuilder::nonInflectCubicTo(float prevJoinType, const SkPoint p[4], int maxDepth) {
265+
void GrStrokePatchBuilder::cubicTo(float prevJoinType, const SkPoint p[4], int maxDepth,
266+
bool mightInflect) {
298267
// The stroker relies on p1 and p2 to find tangents at the endpoints. (We have to treat the
299268
// endpoint tangents carefully in order to get water tight seams with the join segments.) If p0
300269
// and p1 are both colocated on an endpoint then we need to draw this cubic as a line instead.
@@ -303,46 +272,62 @@ void GrStrokePatchBuilder::nonInflectCubicTo(float prevJoinType, const SkPoint p
303272
return;
304273
}
305274

306-
// Ensure our hardware supports enough tessellation segments to render the curve. The first
307-
// branch assumes a worst-case rotation of 360 degrees and checks if even then we have enough.
308-
// In practice it is rare to take even the first branch.
309-
//
310-
// NOTE: We could technically assume a worst-case rotation of 180 because cubicTo() chops at
311-
// midtangents and inflections. However, this is only temporary so we leave it at 360 where it
312-
// will arrive at in the future.
275+
// Early-out if by conservative estimate we can ensure our hardware supports enough tessellation
276+
// segments to render the curve. In practice we almost always take this branch.
313277
float numParametricSegments = GrWangsFormula::cubic(fLinearizationIntolerance, p);
314-
if (numParametricSegments > fMaxParametricSegments360 && maxDepth != 0) {
315-
// We still might have enough tessellation segments to render the curve. Check again with
316-
// the actual rotation.
317-
float numRadialSegments = SkMeasureNonInflectCubicRotation(p) * fNumRadialSegmentsPerRad;
318-
numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
319-
numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
320-
if (numParametricSegments + numRadialSegments - 1 > fMaxTessellationSegments) {
321-
// The hardware doesn't support enough segments for this curve. Chop and recurse.
322-
if (maxDepth < 0) {
323-
// Decide on an extremely conservative upper bound for when to quit chopping. This
324-
// is solely to protect us from infinite recursion in instances where FP error
325-
// prevents us from chopping at the correct midtangent.
326-
maxDepth = sk_float_nextlog2(numParametricSegments) +
327-
sk_float_nextlog2(numRadialSegments) + 1;
328-
SkASSERT(maxDepth >= 1);
329-
}
330-
SkPoint chopped[7];
331-
if (numParametricSegments >= numRadialSegments) {
332-
SkChopCubicAtHalf(p, chopped);
333-
} else {
334-
SkChopCubicAtMidTangent(p, chopped);
278+
if (numParametricSegments <= fMaxParametricSegments360 || maxDepth == 0) {
279+
this->writeCubicSegment(prevJoinType, p, Patch::kStandardCubicType);
280+
return;
281+
}
282+
283+
// Ensure the curve does not inflect before we attempt to measure its rotation.
284+
SkPoint chopped[10];
285+
if (mightInflect) {
286+
float inflectT[2];
287+
if (int n = SkFindCubicInflections(p, inflectT)) {
288+
SkChopCubicAt(p, chopped, inflectT, n);
289+
for (int i = 0; i <= n; ++i) {
290+
this->cubicTo(prevJoinType, chopped + i*3, maxDepth, false);
291+
// Switch to kDoubleSidedRoundJoinType in case we happened to chop at an exact cusp
292+
// or turnaround point of a flat cubic, in which case we would lose 180 degrees of
293+
// rotation.
294+
prevJoinType = kDoubleSidedRoundJoinType;
335295
}
336-
this->nonInflectCubicTo(prevJoinType, chopped, maxDepth - 1);
337-
// Use kDoubleSidedRoundJoinType in case we happened to chop at an exact cusp or
338-
// turnaround point of a flat cubic, in which case we would lose 180 degrees of
339-
// rotation.
340-
this->nonInflectCubicTo(kDoubleSidedRoundJoinType, chopped + 3, maxDepth - 1);
341296
return;
342297
}
343298
}
344299

345-
this->writeCubicSegment(prevJoinType, p);
300+
// We still might have enough tessellation segments to render the curve. Check again with
301+
// its actual rotation.
302+
float numRadialSegments = SkMeasureNonInflectCubicRotation(p) * fNumRadialSegmentsPerRad;
303+
numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
304+
numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
305+
if (numParametricSegments + numRadialSegments - 1 <= fMaxTessellationSegments) {
306+
this->writeCubicSegment(prevJoinType, p, Patch::kStandardCubicType);
307+
return;
308+
}
309+
310+
// The hardware doesn't support enough segments to tessellate this curve. Chop and recurse.
311+
if (numParametricSegments >= numRadialSegments) {
312+
SkChopCubicAtHalf(p, chopped);
313+
} else {
314+
SkChopCubicAtMidTangent(p, chopped);
315+
}
316+
317+
if (maxDepth < 0) {
318+
// Decide on an extremely conservative upper bound for when to quit chopping. This
319+
// is solely to protect us from infinite recursion in instances where FP error
320+
// prevents us from chopping at the correct midtangent.
321+
maxDepth = sk_float_nextlog2(numParametricSegments) +
322+
sk_float_nextlog2(numRadialSegments) + 1;
323+
SkASSERT(maxDepth >= 1);
324+
}
325+
326+
this->cubicTo(prevJoinType, chopped, maxDepth - 1, false);
327+
// Use kDoubleSidedRoundJoinType in case we happened to chop at an exact cusp or
328+
// turnaround point of a flat cubic, in which case we would lose 180 degrees of
329+
// rotation.
330+
this->cubicTo(kDoubleSidedRoundJoinType, chopped + 3, maxDepth - 1, false);
346331
}
347332

348333
void GrStrokePatchBuilder::close(SkPaint::Cap capType) {

0 commit comments

Comments
 (0)