Coverage Report

Created: 2021-08-22 09:07

/src/skia/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2020 Google LLC.
3
 *
4
 * Use of this source code is governed by a BSD-style license that can be
5
 * found in the LICENSE file.
6
 */
7
8
#include "src/gpu/tessellate/GrStrokeHardwareTessellator.h"
9
10
#include "src/core/SkMathPriv.h"
11
#include "src/core/SkPathPriv.h"
12
#include "src/gpu/GrMeshDrawTarget.h"
13
#include "src/gpu/GrRecordingContextPriv.h"
14
#include "src/gpu/GrVx.h"
15
#include "src/gpu/geometry/GrPathUtils.h"
16
#include "src/gpu/geometry/GrWangsFormula.h"
17
#include "src/gpu/tessellate/GrCullTest.h"
18
19
namespace {
20
21
0
static float num_combined_segments(float numParametricSegments, float numRadialSegments) {
22
    // The first and last edges are shared by both the parametric and radial sets of edges, so
23
    // the total number of edges is:
24
    //
25
    //   numCombinedEdges = numParametricEdges + numRadialEdges - 2
26
    //
27
    // It's also important to differentiate between the number of edges and segments in a strip:
28
    //
29
    //   numCombinedSegments = numCombinedEdges - 1
30
    //
31
    // So the total number of segments in the combined strip is:
32
    //
33
    //   numCombinedSegments = numParametricEdges + numRadialEdges - 2 - 1
34
    //                       = numParametricSegments + 1 + numRadialSegments + 1 - 2 - 1
35
    //                       = numParametricSegments + numRadialSegments - 1
36
    //
37
0
    return numParametricSegments + numRadialSegments - 1;
38
0
}
39
40
0
static grvx::float2 pow4(grvx::float2 x) {
41
0
    auto xx = x*x;
42
0
    return xx*xx;
43
0
}
44
45
class PatchWriter {
46
public:
47
    using ShaderFlags = GrStrokeTessellator::ShaderFlags;
48
49
    enum class JoinType {
50
        kMiter = SkPaint::kMiter_Join,
51
        kRound = SkPaint::kRound_Join,
52
        kBevel = SkPaint::kBevel_Join,
53
        kBowtie = SkPaint::kLast_Join + 1  // Double sided round join.
54
    };
55
56
    PatchWriter(ShaderFlags shaderFlags, GrMeshDrawTarget* target,
57
                const SkRect& strokeCullBounds, const SkMatrix& viewMatrix, float matrixMaxScale,
58
                GrVertexChunkArray* patchChunks, size_t patchStride, int minPatchesPerChunk)
59
            : fShaderFlags(shaderFlags)
60
            , fCullTest(strokeCullBounds, viewMatrix)
61
            , fChunkBuilder(target, patchChunks, patchStride, minPatchesPerChunk)
62
            // Subtract 2 because the tessellation shader chops every cubic at two locations, and
63
            // each chop has the potential to introduce an extra segment.
64
            , fMaxTessellationSegments(target->caps().shaderCaps()->maxTessellationSegments() - 2)
65
0
            , fParametricPrecision(GrStrokeTolerances::CalcParametricPrecision(matrixMaxScale)) {
66
0
    }
67
68
    // This is the precision value, adjusted for the view matrix, to use with Wang's formulas when
69
    // determining how many parametric segments a curve will require.
70
0
    float parametricPrecision() const {
71
0
        return fParametricPrecision;
72
0
    }
73
    // Will a line and worst-case previous join both fit in a single patch together?
74
0
    bool lineFitsInPatch_withJoin() {
75
0
        return fMaxCombinedSegments_withJoin >= 1;
76
0
    }
77
    // Will a stroke with the given number of parametric segments and a worst-case rotation of 180
78
    // degrees fit in a single patch?
79
0
    bool stroke180FitsInPatch(float numParametricSegments_pow4) {
80
0
        return numParametricSegments_pow4 <= fMaxParametricSegments_pow4[0];
81
0
    }
82
    // Will a worst-case 180-degree stroke with the given number of parametric segments, and a
83
    // worst-case join fit in a single patch together?
84
0
    bool stroke180FitsInPatch_withJoin(float numParametricSegments_pow4) {
85
0
        return numParametricSegments_pow4 <= fMaxParametricSegments_pow4_withJoin[0];
86
0
    }
87
    // Will a stroke with the given number of parametric segments and a worst-case rotation of 360
88
    // degrees fit in a single patch?
89
0
    bool stroke360FitsInPatch(float numParametricSegments_pow4) {
90
0
        return numParametricSegments_pow4 <= fMaxParametricSegments_pow4[1];
91
0
    }
92
    // Will a worst-case 360-degree stroke with the given number of parametric segments, and a
93
    // worst-case join fit in a single patch together?
94
0
    bool stroke360FitsInPatch_withJoin(float numParametricSegments_pow4) {
95
0
        return numParametricSegments_pow4 <= fMaxParametricSegments_pow4_withJoin[1];
96
0
    }
97
98
0
    void updateTolerances(float numRadialSegmentsPerRadian, SkPaint::Join joinType) {
99
0
        using grvx::float2;
100
101
0
        fNumRadialSegmentsPerRadian = numRadialSegmentsPerRadian;
102
103
        // Calculate the worst-case numbers of parametric segments our hardware can support for the
104
        // current stroke radius, in the event that there are also enough radial segments to rotate
105
        // 180 and 360 degrees respectively. These are used for "quick accepts" that allow us to
106
        // send almost all curves directly to the hardware without having to chop.
107
0
        float2 numRadialSegments_180_360 = skvx::max(skvx::ceil(
108
0
                float2{SK_ScalarPI, 2*SK_ScalarPI} * fNumRadialSegmentsPerRadian), 1);
109
        // numEdges = numSegments + 1. See num_combined_segments().
110
0
        float maxTotalEdges = fMaxTessellationSegments + 1;
111
        // numParametricSegments = numTotalEdges - numRadialSegments. See num_combined_segments().
112
0
        float2 maxParametricSegments = skvx::max(maxTotalEdges - numRadialSegments_180_360, 0);
113
0
        float2 maxParametricSegments_pow4 = pow4(maxParametricSegments);
114
0
        maxParametricSegments_pow4.store(fMaxParametricSegments_pow4);
115
116
        // Find the worst-case numbers of parametric segments if we are to integrate a join into the
117
        // same patch as the curve.
118
0
        float numRadialSegments180 = numRadialSegments_180_360[0];
119
0
        float worstCaseNumSegmentsInJoin;
120
0
        switch (joinType) {
121
0
            case SkPaint::kBevel_Join: worstCaseNumSegmentsInJoin = 1; break;
122
0
            case SkPaint::kMiter_Join: worstCaseNumSegmentsInJoin = 2; break;
123
0
            case SkPaint::kRound_Join: worstCaseNumSegmentsInJoin = numRadialSegments180; break;
124
0
        }
125
126
        // Now calculate the worst-case numbers of parametric segments if we also want to combine a
127
        // join with the patch. Subtract an extra 1 off the end because when we integrate a join,
128
        // the tessellator has to add a redundant edge between the join and curve.
129
0
        float2 maxParametricSegments_pow4_withJoin = pow4(skvx::max(
130
0
                maxParametricSegments - worstCaseNumSegmentsInJoin - 1, 0));
131
0
        maxParametricSegments_pow4_withJoin.store(fMaxParametricSegments_pow4_withJoin);
132
133
0
        fMaxCombinedSegments_withJoin = fMaxTessellationSegments - worstCaseNumSegmentsInJoin - 1;
134
0
        fSoloRoundJoinAlwaysFitsInPatch = (numRadialSegments180 <= fMaxTessellationSegments);
135
0
        fStrokeJoinType = JoinType(joinType);
136
0
    }
137
138
0
    void updateDynamicStroke(const SkStrokeRec& stroke) {
139
0
        SkASSERT(fShaderFlags & ShaderFlags::kDynamicStroke);
140
0
        fDynamicStroke.set(stroke);
141
0
    }
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::updateDynamicStroke(SkStrokeRec const&)
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::updateDynamicStroke(SkStrokeRec const&)
142
143
0
    void updateDynamicColor(const SkPMColor4f& color) {
144
0
        SkASSERT(fShaderFlags & ShaderFlags::kDynamicColor);
145
0
        bool wideColor = fShaderFlags & ShaderFlags::kWideColor;
146
0
        SkASSERT(wideColor || color.fitsInBytes());
147
0
        fDynamicColor.set(color, wideColor);
148
0
    }
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::updateDynamicColor(SkRGBA4f<(SkAlphaType)2> const&)
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::updateDynamicColor(SkRGBA4f<(SkAlphaType)2> const&)
149
150
0
    void moveTo(SkPoint pt) {
151
0
        fCurrContourStartPoint = pt;
152
0
        fHasLastControlPoint = false;
153
0
    }
154
155
    // Writes out the given line, possibly chopping its previous join until the segments fit in
156
    // tessellation patches.
157
0
    void writeLineTo(SkPoint p0, SkPoint p1) {
158
0
        this->writeLineTo(fStrokeJoinType, p0, p1);
159
0
    }
160
0
    void writeLineTo(JoinType prevJoinType, SkPoint p0, SkPoint p1) {
161
        // Zero-length paths need special treatment because they are spec'd to behave differently.
162
0
        if (p0 == p1) {
163
0
            return;
164
0
        }
165
0
        SkPoint asPatch[4] = {p0, p0, p1, p1};
166
0
        this->internalPatchTo(prevJoinType, this->lineFitsInPatch_withJoin(), asPatch, p1);
167
0
    }
168
169
    // Recursively chops the given conic and its previous join until the segments fit in
170
    // tessellation patches.
171
0
    void writeConicPatchesTo(const SkPoint p[3], float w) {
172
0
        this->internalConicPatchesTo(fStrokeJoinType, p, w);
173
0
    }
174
175
    // Chops the given cubic at points of inflection and 180-degree rotation, and then recursively
176
    // chops the previous join and cubic sections as necessary until the segments fit in
177
    // tessellation patches.
178
0
    void writeCubicConvex180PatchesTo(const SkPoint p[4]) {
179
0
        SkPoint chops[10];
180
0
        float chopT[2];
181
0
        bool areCusps;
182
0
        int numChops = GrPathUtils::findCubicConvex180Chops(p, chopT, &areCusps);
183
0
        if (numChops == 0) {
184
            // The curve is already convex and rotates no more than 180 degrees.
185
0
            this->internalCubicConvex180PatchesTo(fStrokeJoinType, p);
186
0
        } else if (numChops == 1) {
187
0
            SkChopCubicAt(p, chops, chopT[0]);
188
0
            if (areCusps) {
189
                // When chopping on a perfect cusp, these 3 points will be equal.
190
0
                chops[2] = chops[4] = chops[3];
191
0
            }
192
0
            this->internalCubicConvex180PatchesTo(fStrokeJoinType, chops);
193
0
            this->internalCubicConvex180PatchesTo(JoinType::kBowtie, chops + 3);
194
0
        } else {
195
0
            SkASSERT(numChops == 2);
196
0
            SkChopCubicAt(p, chops, chopT[0], chopT[1]);
197
            // Two cusps are only possible on a flat line with two 180-degree turnarounds.
198
0
            if (areCusps) {
199
0
                this->writeLineTo(chops[0], chops[3]);
200
0
                this->writeLineTo(JoinType::kBowtie, chops[3], chops[6]);
201
0
                this->writeLineTo(JoinType::kBowtie, chops[6], chops[9]);
202
0
                return;
203
0
            }
204
0
            this->internalCubicConvex180PatchesTo(fStrokeJoinType, chops);
205
0
            this->internalCubicConvex180PatchesTo(JoinType::kBowtie, chops + 3);
206
0
            this->internalCubicConvex180PatchesTo(JoinType::kBowtie, chops + 6);
207
0
        }
208
0
    }
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::writeCubicConvex180PatchesTo(SkPoint const*)
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::writeCubicConvex180PatchesTo(SkPoint const*)
209
210
    // Writes out the given stroke patch exactly as provided, without chopping or checking the
211
    // number of segments. Possibly chops its previous join until the segments fit in tessellation
212
    // patches.
213
    SK_ALWAYS_INLINE void writePatchTo(bool prevJoinFitsInPatch, const SkPoint p[4],
214
0
                                       SkPoint endControlPoint) {
215
0
        SkASSERT(fStrokeJoinType != JoinType::kBowtie);
216
217
0
        if (!fHasLastControlPoint) {
218
            // The first stroke doesn't have a previous join (yet). If the current contour ends up
219
            // closing itself, we will add that join as its own patch. TODO: Consider deferring the
220
            // first stroke until we know whether the contour will close. This will allow us to use
221
            // the closing join as the first patch's previous join.
222
0
            fHasLastControlPoint = true;
223
0
            fCurrContourFirstControlPoint = (p[1] != p[0]) ? p[1] : p[2];
224
0
            fLastControlPoint = p[0];  // Disables the join section of this patch.
225
0
        } else if (!prevJoinFitsInPatch) {
226
            // There aren't enough guaranteed segments to fold the previous join into this patch.
227
            // Emit the join in its own separate patch.
228
0
            this->internalJoinTo(fStrokeJoinType, p[0], (p[1] != p[0]) ? p[1] : p[2]);
229
0
            fLastControlPoint = p[0];  // Disables the join section of this patch.
230
0
        }
231
232
0
        if (GrVertexWriter patchWriter = fChunkBuilder.appendVertex()) {
233
0
            patchWriter.write(fLastControlPoint);
234
0
            patchWriter.writeArray(p, 4);
235
0
            this->writeDynamicAttribs(&patchWriter);
236
0
        }
237
238
0
        fLastControlPoint = endControlPoint;
239
0
    }
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::writePatchTo(bool, SkPoint const*, SkPoint)
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::writePatchTo(bool, SkPoint const*, SkPoint)
240
241
    void writeClose(SkPoint contourEndpoint, const SkMatrix& viewMatrix,
242
0
                    const SkStrokeRec& stroke) {
243
0
        if (!fHasLastControlPoint) {
244
            // Draw caps instead of closing if the subpath is zero length:
245
            //
246
            //   "Any zero length subpath ...  shall be stroked if the 'stroke-linecap' property has
247
            //   a value of round or square producing respectively a circle or a square."
248
            //
249
            //   (https://www.w3.org/TR/SVG11/painting.html#StrokeProperties)
250
            //
251
0
            this->writeCaps(contourEndpoint, viewMatrix, stroke);
252
0
            return;
253
0
        }
254
255
        // Draw a line back to the beginning. (This will be discarded if
256
        // contourEndpoint == fCurrContourStartPoint.)
257
0
        this->writeLineTo(contourEndpoint, fCurrContourStartPoint);
258
0
        this->internalJoinTo(fStrokeJoinType, fCurrContourStartPoint, fCurrContourFirstControlPoint);
259
260
0
        fHasLastControlPoint = false;
261
0
    }
262
263
0
    void writeCaps(SkPoint contourEndpoint, const SkMatrix& viewMatrix, const SkStrokeRec& stroke) {
264
0
        if (!fHasLastControlPoint) {
265
            // We don't have any control points to orient the caps. In this case, square and round
266
            // caps are specified to be drawn as an axis-aligned square or circle respectively.
267
            // Assign default control points that achieve this.
268
0
            SkVector outset;
269
0
            if (!stroke.isHairlineStyle()) {
270
0
                outset = {1, 0};
271
0
            } else {
272
                // If the stroke is hairline, orient the square on the post-transform x-axis
273
                // instead. We don't need to worry about the vector length since it will be
274
                // normalized later. Since the matrix cannot have perspective, the below is
275
                // equivalent to:
276
                //
277
                //    outset = inverse(|a b|) * |1| * arbitrary_scale
278
                //                     |c d|    |0|
279
                //
280
                //    == 1/det * | d -b| * |1| * arbitrary_scale
281
                //               |-c  a|   |0|
282
                //
283
                //    == 1/det * | d| * arbitrary_scale
284
                //               |-c|
285
                //
286
                //    == | d|
287
                //       |-c|
288
                //
289
0
                SkASSERT(!viewMatrix.hasPerspective());
290
0
                float c=viewMatrix.getSkewY(), d=viewMatrix.getScaleY();
291
0
                outset = {d, -c};
292
0
            }
293
0
            fCurrContourFirstControlPoint = fCurrContourStartPoint - outset;
294
0
            fLastControlPoint = fCurrContourStartPoint + outset;
295
0
            fHasLastControlPoint = true;
296
0
            contourEndpoint = fCurrContourStartPoint;
297
0
        }
298
299
0
        switch (stroke.getCap()) {
300
0
            case SkPaint::kButt_Cap:
301
0
                break;
302
0
            case SkPaint::kRound_Cap: {
303
                // A round cap is the same thing as a 180-degree round join.
304
                // If our join type isn't round we can alternatively use a bowtie.
305
0
                JoinType roundCapJoinType = (stroke.getJoin() == SkPaint::kRound_Join)
306
0
                        ? JoinType::kRound : JoinType::kBowtie;
307
0
                this->internalJoinTo(roundCapJoinType, contourEndpoint, fLastControlPoint);
308
0
                this->internalMoveTo(fCurrContourStartPoint, fCurrContourFirstControlPoint);
309
0
                this->internalJoinTo(roundCapJoinType, fCurrContourStartPoint,
310
0
                                     fCurrContourFirstControlPoint);
311
0
                break;
312
0
            }
313
0
            case SkPaint::kSquare_Cap: {
314
                // A square cap is the same as appending lineTos.
315
0
                auto strokeJoinType = JoinType(stroke.getJoin());
316
0
                SkVector lastTangent = contourEndpoint - fLastControlPoint;
317
0
                if (!stroke.isHairlineStyle()) {
318
                    // Extend the cap by 1/2 stroke width.
319
0
                    lastTangent *= (.5f * stroke.getWidth()) / lastTangent.length();
320
0
                } else {
321
                    // Extend the cap by what will be 1/2 pixel after transformation.
322
0
                    lastTangent *=
323
0
                            .5f / viewMatrix.mapVector(lastTangent.fX, lastTangent.fY).length();
324
0
                }
325
0
                this->writeLineTo(strokeJoinType, contourEndpoint, contourEndpoint + lastTangent);
326
0
                this->internalMoveTo(fCurrContourStartPoint, fCurrContourFirstControlPoint);
327
0
                SkVector firstTangent = fCurrContourFirstControlPoint - fCurrContourStartPoint;
328
0
                if (!stroke.isHairlineStyle()) {
329
                    // Set the the cap back by 1/2 stroke width.
330
0
                    firstTangent *= (-.5f * stroke.getWidth()) / firstTangent.length();
331
0
                } else {
332
                    // Set the cap back by what will be 1/2 pixel after transformation.
333
0
                    firstTangent *=
334
0
                            -.5f / viewMatrix.mapVector(firstTangent.fX, firstTangent.fY).length();
335
0
                }
336
0
                this->writeLineTo(strokeJoinType, fCurrContourStartPoint,
337
0
                                  fCurrContourStartPoint + firstTangent);
338
0
                break;
339
0
            }
340
0
        }
341
342
0
        fHasLastControlPoint = false;
343
0
    }
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::writeCaps(SkPoint, SkMatrix const&, SkStrokeRec const&)
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::writeCaps(SkPoint, SkMatrix const&, SkStrokeRec const&)
344
345
private:
346
0
    void internalMoveTo(SkPoint pt, SkPoint lastControlPoint) {
347
0
        fCurrContourStartPoint = pt;
348
0
        fCurrContourFirstControlPoint = fLastControlPoint = lastControlPoint;
349
0
        fHasLastControlPoint = true;
350
0
    }
351
352
    // Recursively chops the given conic and its previous join until the segments fit in
353
    // tessellation patches.
354
    void internalConicPatchesTo(JoinType prevJoinType, const SkPoint p[3], float w,
355
0
                                int maxDepth = -1) {
356
0
        if (!fCullTest.areVisible3(p)) {
357
            // The stroke is out of view. Discard it.
358
0
            this->discardStroke(p, 3);
359
0
            return;
360
0
        }
361
        // Zero-length paths need special treatment because they are spec'd to behave differently.
362
        // If the control point is colocated on an endpoint then this might end up being the case.
363
        // Fall back on a lineTo and let it make the final check.
364
0
        if (p[1] == p[0] || p[1] == p[2] || w == 0) {
365
0
            this->writeLineTo(prevJoinType, p[0], p[2]);
366
0
            return;
367
0
        }
368
369
        // Convert to a patch.
370
0
        SkPoint asPatch[4];
371
0
        if (w == 1) {
372
0
            GrPathUtils::convertQuadToCubic(p, asPatch);
373
0
        } else {
374
0
            GrTessellationShader::WriteConicPatch(p, w, asPatch);
375
0
        }
376
377
0
        float numParametricSegments_pow4;
378
0
        if (w == 1) {
379
0
            numParametricSegments_pow4 = GrWangsFormula::quadratic_pow4(fParametricPrecision, p);
380
0
        } else {
381
0
            float n = GrWangsFormula::conic_pow2(fParametricPrecision, p, w);
382
0
            numParametricSegments_pow4 = n*n;
383
0
        }
384
0
        if (this->stroke180FitsInPatch(numParametricSegments_pow4) || maxDepth == 0) {
385
0
            this->internalPatchTo(prevJoinType,
386
0
                                  this->stroke180FitsInPatch_withJoin(numParametricSegments_pow4),
387
0
                                  asPatch, p[2]);
388
0
            return;
389
0
        }
390
391
        // We still might have enough tessellation segments to render the curve. Check again with
392
        // the actual rotation.
393
0
        float numRadialSegments = SkMeasureQuadRotation(p) * fNumRadialSegmentsPerRadian;
394
0
        numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
395
0
        float numParametricSegments = GrWangsFormula::root4(numParametricSegments_pow4);
396
0
        numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
397
0
        float numCombinedSegments = num_combined_segments(numParametricSegments, numRadialSegments);
398
0
        if (numCombinedSegments > fMaxTessellationSegments) {
399
            // The hardware doesn't support enough segments for this curve. Chop and recurse.
400
0
            if (maxDepth < 0) {
401
                // Decide on an extremely conservative upper bound for when to quit chopping. This
402
                // is solely to protect us from infinite recursion in instances where FP error
403
                // prevents us from chopping at the correct midtangent.
404
0
                maxDepth = sk_float_nextlog2(numParametricSegments) +
405
0
                           sk_float_nextlog2(numRadialSegments) + 1;
406
0
                maxDepth = std::max(maxDepth, 1);
407
0
            }
408
0
            if (w == 1) {
409
0
                SkPoint chops[5];
410
0
                if (numParametricSegments >= numRadialSegments) {
411
0
                    SkChopQuadAtHalf(p, chops);
412
0
                } else {
413
0
                    SkChopQuadAtMidTangent(p, chops);
414
0
                }
415
0
                this->internalConicPatchesTo(prevJoinType, chops, 1, maxDepth - 1);
416
0
                this->internalConicPatchesTo(JoinType::kBowtie, chops + 2, 1, maxDepth - 1);
417
0
            } else {
418
0
                SkConic conic(p, w);
419
0
                float chopT = (numParametricSegments >= numRadialSegments) ? .5f
420
0
                                                                           : conic.findMidTangent();
421
0
                SkConic chops[2];
422
0
                if (conic.chopAt(chopT, chops)) {
423
0
                    this->internalConicPatchesTo(prevJoinType, chops[0].fPts, chops[0].fW,
424
0
                                                  maxDepth - 1);
425
0
                    this->internalConicPatchesTo(JoinType::kBowtie, chops[1].fPts, chops[1].fW,
426
0
                                                  maxDepth - 1);
427
0
                }
428
0
            }
429
0
            return;
430
0
        }
431
432
0
        this->internalPatchTo(prevJoinType, (numCombinedSegments <= fMaxCombinedSegments_withJoin),
433
0
                              asPatch, p[2]);
434
0
    }
435
436
    // Recursively chops the given cubic and its previous join until the segments fit in
437
    // tessellation patches. The cubic must be convex and must not rotate more than 180 degrees.
438
    void internalCubicConvex180PatchesTo(JoinType prevJoinType, const SkPoint p[4],
439
0
                                         int maxDepth = -1) {
440
0
        if (!fCullTest.areVisible4(p)) {
441
            // The stroke is out of view. Discard it.
442
0
            this->discardStroke(p, 4);
443
0
            return;
444
0
        }
445
        // The stroke tessellation shader assigns special meaning to p0==p1==p2 and p1==p2==p3. If
446
        // this is the case then we need to rewrite the cubic.
447
0
        if (p[1] == p[2] && (p[1] == p[0] || p[1] == p[3])) {
448
0
            this->writeLineTo(prevJoinType, p[0], p[3]);
449
0
            return;
450
0
        }
451
452
0
        float numParametricSegments_pow4 = GrWangsFormula::cubic_pow4(fParametricPrecision, p);
453
0
        if (this->stroke180FitsInPatch(numParametricSegments_pow4) || maxDepth == 0) {
454
0
            this->internalPatchTo(prevJoinType,
455
0
                                  this->stroke180FitsInPatch_withJoin(numParametricSegments_pow4),
456
0
                                  p, p[3]);
457
0
            return;
458
0
        }
459
460
        // We still might have enough tessellation segments to render the curve. Check again with
461
        // its actual rotation.
462
0
        float numRadialSegments = SkMeasureNonInflectCubicRotation(p) * fNumRadialSegmentsPerRadian;
463
0
        numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
464
0
        float numParametricSegments = GrWangsFormula::root4(numParametricSegments_pow4);
465
0
        numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
466
0
        float numCombinedSegments = num_combined_segments(numParametricSegments, numRadialSegments);
467
0
        if (numCombinedSegments > fMaxTessellationSegments) {
468
            // The hardware doesn't support enough segments for this curve. Chop and recurse.
469
0
            SkPoint chops[7];
470
0
            if (maxDepth < 0) {
471
                // Decide on an extremely conservative upper bound for when to quit chopping. This
472
                // is solely to protect us from infinite recursion in instances where FP error
473
                // prevents us from chopping at the correct midtangent.
474
0
                maxDepth = sk_float_nextlog2(numParametricSegments) +
475
0
                           sk_float_nextlog2(numRadialSegments) + 1;
476
0
                maxDepth = std::max(maxDepth, 1);
477
0
            }
478
0
            if (numParametricSegments >= numRadialSegments) {
479
0
                SkChopCubicAtHalf(p, chops);
480
0
            } else {
481
0
                SkChopCubicAtMidTangent(p, chops);
482
0
            }
483
0
            this->internalCubicConvex180PatchesTo(prevJoinType, chops, maxDepth - 1);
484
0
            this->internalCubicConvex180PatchesTo(JoinType::kBowtie, chops + 3, maxDepth - 1);
485
0
            return;
486
0
        }
487
488
0
        this->internalPatchTo(prevJoinType, (numCombinedSegments <= fMaxCombinedSegments_withJoin),
489
0
                              p, p[3]);
490
0
    }
491
492
    // Writes out the given stroke patch exactly as provided, without chopping or checking the
493
    // number of segments. Possibly chops its previous join until the segments fit in tessellation
494
    // patches. It is valid for prevJoinType to be kBowtie.
495
    void internalPatchTo(JoinType prevJoinType, bool prevJoinFitsInPatch, const SkPoint p[4],
496
0
                         SkPoint endPt) {
497
0
        if (prevJoinType == JoinType::kBowtie) {
498
0
            SkASSERT(fHasLastControlPoint);
499
            // Bowtie joins are only used on internal chops, and internal chops almost always have
500
            // continuous tangent angles (i.e., the ending tangent of the first chop and the
501
            // beginning tangent of the second both point in the same direction). The tangents will
502
            // only ever not point in the same direction if we chopped at a cusp point, so that's
503
            // the only time we actually need a bowtie.
504
0
            SkPoint nextControlPoint = (p[1] == p[0]) ? p[2] : p[1];
505
0
            SkVector a = p[0] - fLastControlPoint;
506
0
            SkVector b = nextControlPoint - p[0];
507
0
            float ab_cosTheta = a.dot(b);
508
0
            float ab_pow2 = a.dot(a) * b.dot(b);
509
            // To check if tangents 'a' and 'b' do not point in the same direction, any of the
510
            // following formulas work:
511
            //
512
            //          0 != theta
513
            //          1 != cosTheta
514
            //          1 != cosTheta * abs(cosTheta)  [Still false when cosTheta == -1]
515
            //
516
            // Introducing a slop term for fuzzy equality gives:
517
            //
518
            //          1 !~= cosTheta * abs(cosTheta)                [tolerance = epsilon]
519
            //     (ab)^2 !~= (ab)^2 * cosTheta * abs(cosTheta)       [tolerance = (ab)^2 * epsilon]
520
            //     (ab)^2 !~= (ab * cosTheta) * (ab * abs(cosTheta))  [tolerance = (ab)^2 * epsilon]
521
            //     (ab)^2 !~= (ab * cosTheta) * abs(ab * cosTheta)    [tolerance = (ab)^2 * epsilon]
522
            //
523
            // Since we also scale the tolerance, the formula is unaffected by the magnitude of the
524
            // tangent vectors. (And we can fold "ab" in to the abs() because it's always positive.)
525
0
            if (!SkScalarNearlyEqual(ab_pow2, ab_cosTheta * fabsf(ab_cosTheta),
526
0
                                     ab_pow2 * SK_ScalarNearlyZero)) {
527
0
                this->internalJoinTo(JoinType::kBowtie, p[0], nextControlPoint);
528
0
                fLastControlPoint = p[0];  // Disables the join section of this patch.
529
0
                prevJoinFitsInPatch = true;
530
0
            }
531
0
        }
532
533
0
        this->writePatchTo(prevJoinFitsInPatch, p, (p[2] != endPt) ? p[2] : p[1]);
534
0
    }
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::internalPatchTo((anonymous namespace)::PatchWriter::JoinType, bool, SkPoint const*, SkPoint)
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::internalPatchTo((anonymous namespace)::PatchWriter::JoinType, bool, SkPoint const*, SkPoint)
535
536
    // Recursively chops the given join until the segments fit in tessellation patches.
537
    void internalJoinTo(JoinType joinType, SkPoint junctionPoint, SkPoint nextControlPoint,
538
0
                        int maxDepth = -1) {
539
0
        if (!fHasLastControlPoint) {
540
            // The first stroke doesn't have a previous join.
541
0
            return;
542
0
        }
543
544
0
        if (!fSoloRoundJoinAlwaysFitsInPatch && maxDepth != 0 &&
545
0
            (joinType == JoinType::kRound || joinType == JoinType::kBowtie)) {
546
0
            SkVector tan0 = junctionPoint - fLastControlPoint;
547
0
            SkVector tan1 = nextControlPoint - junctionPoint;
548
0
            float rotation = SkMeasureAngleBetweenVectors(tan0, tan1);
549
0
            float numRadialSegments = rotation * fNumRadialSegmentsPerRadian;
550
0
            if (numRadialSegments > fMaxTessellationSegments) {
551
                // This is a round join that requires more segments than the tessellator supports.
552
                // Split it and recurse.
553
0
                if (maxDepth < 0) {
554
                    // Decide on an upper bound for when to quit chopping. This is solely to protect
555
                    // us from infinite recursion due to FP precision issues.
556
0
                    maxDepth = sk_float_nextlog2(numRadialSegments / fMaxTessellationSegments);
557
0
                    maxDepth = std::max(maxDepth, 1);
558
0
                }
559
                // Find the bisector so we can split the join in half.
560
0
                SkPoint bisector = SkFindBisector(tan0, tan1);
561
                // c0 will be the "next" control point for the first join half, and c1 will be the
562
                // "previous" control point for the second join half.
563
0
                SkPoint c0, c1;
564
                // FIXME(skia:11347): This hack ensures "c0 - junctionPoint" gives the exact same
565
                // ieee fp32 vector as "-(c1 - junctionPoint)". Tessellated stroking is becoming
566
                // less experimental, so t's time to think of a cleaner method to avoid T-junctions
567
                // when we chop joins.
568
0
                int maxAttempts = 10;
569
0
                do {
570
0
                    bisector = (junctionPoint + bisector) - (junctionPoint - bisector);
571
0
                    c0 = junctionPoint + bisector;
572
0
                    c1 = junctionPoint - bisector;
573
0
                } while (c0 - junctionPoint != -(c1 - junctionPoint) && --maxAttempts);
574
                // First join half.
575
0
                this->internalJoinTo(joinType, junctionPoint, c0, maxDepth - 1);
576
0
                fLastControlPoint = c1;
577
                // Second join half.
578
0
                this->internalJoinTo(joinType, junctionPoint, nextControlPoint, maxDepth - 1);
579
0
                return;
580
0
            }
581
0
        }
582
583
        // We should never write out joins before the first curve.
584
0
        SkASSERT(fHasLastControlPoint);
585
586
0
        if (GrVertexWriter patchWriter = fChunkBuilder.appendVertex()) {
587
0
            patchWriter.write(fLastControlPoint, junctionPoint);
588
0
            if (joinType == JoinType::kBowtie) {
589
                // {prevControlPoint, [p0, p0, p0, p3]} is a reserved patch pattern that means this
590
                // patch is a bowtie. The bowtie is anchored on p0 and its tangent angles go from
591
                // (p0 - prevControlPoint) to (p3 - p0).
592
0
                patchWriter.write(junctionPoint, junctionPoint);
593
0
            } else {
594
                // {prevControlPoint, [p0, p3, p3, p3]} is a reserved patch pattern that means this
595
                // patch is a join only (no curve sections in the patch). The join is anchored on p0
596
                // and its tangent angles go from (p0 - prevControlPoint) to (p3 - p0).
597
0
                patchWriter.write(nextControlPoint, nextControlPoint);
598
0
            }
599
0
            patchWriter.write(nextControlPoint);
600
0
            this->writeDynamicAttribs(&patchWriter);
601
0
        }
602
603
0
        fLastControlPoint = nextControlPoint;
604
0
    }
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::internalJoinTo((anonymous namespace)::PatchWriter::JoinType, SkPoint, SkPoint, int)
Unexecuted instantiation: GrStrokeHardwareTessellator.cpp:(anonymous namespace)::PatchWriter::internalJoinTo((anonymous namespace)::PatchWriter::JoinType, SkPoint, SkPoint, int)
605
606
0
    SK_ALWAYS_INLINE void writeDynamicAttribs(GrVertexWriter* patchWriter) {
607
0
        if (fShaderFlags & ShaderFlags::kDynamicStroke) {
608
0
            patchWriter->write(fDynamicStroke);
609
0
        }
610
0
        if (fShaderFlags & ShaderFlags::kDynamicColor) {
611
0
            patchWriter->write(fDynamicColor);
612
0
        }
613
0
    }
614
615
0
    void discardStroke(const SkPoint p[], int numPoints) {
616
0
        if (!fHasLastControlPoint) {
617
            // This disables the first join, if any. (The first join gets added as a standalone
618
            // patch during close(), but setting fCurrContourFirstControlPoint to p[0] causes us to
619
            // skip that join if we attempt to add it later.)
620
0
            fCurrContourFirstControlPoint = p[0];
621
0
            fHasLastControlPoint = true;
622
0
        }
623
        // Set fLastControlPoint to the next stroke's p0 (which will be equal to the final point of
624
        // this stroke). This has the effect of disabling the next stroke's join.
625
0
        fLastControlPoint = p[numPoints - 1];
626
0
    }
627
628
    const ShaderFlags fShaderFlags;
629
    const GrCullTest fCullTest;
630
    GrVertexChunkBuilder fChunkBuilder;
631
632
    // The maximum number of tessellation segments the hardware can emit for a single patch.
633
    const int fMaxTessellationSegments;
634
635
    // This is the precision value, adjusted for the view matrix, to use with Wang's formulas when
636
    // determining how many parametric segments a curve will require.
637
    const float fParametricPrecision;
638
639
    // Number of radial segments required for each radian of rotation in order to look smooth with
640
    // the current stroke radius.
641
    float fNumRadialSegmentsPerRadian;
642
643
    // These arrays contain worst-case numbers of parametric segments, raised to the 4th power, that
644
    // our hardware can support for the current stroke radius. They assume curve rotations of 180
645
    // and 360 degrees respectively. These are used for "quick accepts" that allow us to send almost
646
    // all curves directly to the hardware without having to chop. We raise to the 4th power because
647
    // the "pow4" variants of Wang's formula are the quickest to evaluate.
648
    float fMaxParametricSegments_pow4[2];  // Values for strokes that rotate 180 and 360 degrees.
649
    float fMaxParametricSegments_pow4_withJoin[2];  // For strokes that rotate 180 and 360 degrees.
650
651
    // Maximum number of segments we can allocate for a stroke if we are stuffing it in a patch
652
    // together with a worst-case join.
653
    float fMaxCombinedSegments_withJoin;
654
655
    // Additional info on the current stroke radius/join type.
656
    bool fSoloRoundJoinAlwaysFitsInPatch;
657
    JoinType fStrokeJoinType;
658
659
    // Variables related to the specific contour that we are currently iterating during
660
    // prepareBuffers().
661
    bool fHasLastControlPoint = false;
662
    SkPoint fCurrContourStartPoint;
663
    SkPoint fCurrContourFirstControlPoint;
664
    SkPoint fLastControlPoint;
665
666
    // Values for the current dynamic state (if any) that will get written out with each patch.
667
    GrStrokeTessellationShader::DynamicStroke fDynamicStroke;
668
    GrVertexColor fDynamicColor;
669
};
670
671
0
SK_ALWAYS_INLINE static bool cubic_has_cusp(const SkPoint p[4]) {
672
0
    using grvx::float2;
673
674
0
    float2 p0 = skvx::bit_pun<float2>(p[0]);
675
0
    float2 p1 = skvx::bit_pun<float2>(p[1]);
676
0
    float2 p2 = skvx::bit_pun<float2>(p[2]);
677
0
    float2 p3 = skvx::bit_pun<float2>(p[3]);
678
679
    // See GrPathUtils::findCubicConvex180Chops() for the math.
680
0
    float2 C = p1 - p0;
681
0
    float2 D = p2 - p1;
682
0
    float2 E = p3 - p0;
683
0
    float2 B = D - C;
684
0
    float2 A = grvx::fast_madd<2>(-3, D, E);
685
686
0
    float a = grvx::cross(A, B);
687
0
    float b = grvx::cross(A, C);
688
0
    float c = grvx::cross(B, C);
689
0
    float discr = b*b - 4*a*c;
690
691
    // If -cuspThreshold <= discr <= cuspThreshold, it means the two roots are within a distance of
692
    // 2^-11 from one another in parametric space. This is close enough for our purposes to take the
693
    // slow codepath that knows how to handle cusps.
694
0
    constexpr static float kEpsilon = 1.f / (1 << 11);
695
0
    float cuspThreshold = (2*kEpsilon) * a;
696
0
    cuspThreshold *= cuspThreshold;
697
698
0
    return fabsf(discr) <= cuspThreshold &&
699
           // The most common type of cusp we encounter is when p0==p1 or p2==p3. Unless the curve
700
           // is a flat line (a==b==c==0), these don't actually need special treatment because the
701
           // cusp occurs at t=0 or t=1.
702
0
           (!(skvx::all(p0 == p1) || skvx::all(p2 == p3)) || (a == 0 && b == 0 && c == 0));
703
0
}
704
705
}  // namespace
706
707
GrStrokeHardwareTessellator::GrStrokeHardwareTessellator(const GrShaderCaps& shaderCaps,
708
                                                         ShaderFlags shaderFlags,
709
                                                         const SkMatrix& viewMatrix,
710
                                                         PathStrokeList* pathStrokeList,
711
                                                         std::array<float,2> matrixMinMaxScales,
712
                                                         const SkRect& strokeCullBounds)
713
        : GrStrokeTessellator(shaderCaps, GrStrokeTessellationShader::Mode::kHardwareTessellation,
714
                              shaderFlags, SkNextLog2(shaderCaps.maxTessellationSegments()),
715
0
                              viewMatrix, pathStrokeList, matrixMinMaxScales, strokeCullBounds) {
716
0
}
717
718
0
void GrStrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCombinedVerbCnt) {
719
0
    using JoinType = PatchWriter::JoinType;
720
721
    // Over-allocate enough patches for 1 in 4 strokes to chop and for 8 extra caps.
722
0
    int strokePreallocCount = totalCombinedVerbCnt * 5/4;
723
0
    int capPreallocCount = 8;
724
0
    int minPatchesPerChunk = strokePreallocCount + capPreallocCount;
725
0
    PatchWriter patchWriter(fShader.flags(), target, fStrokeCullBounds, fShader.viewMatrix(),
726
0
                            fMatrixMinMaxScales[1], &fPatchChunks, fShader.vertexStride(),
727
0
                            minPatchesPerChunk);
728
729
0
    if (!fShader.hasDynamicStroke()) {
730
        // Strokes are static. Calculate tolerances once.
731
0
        const SkStrokeRec& stroke = fPathStrokeList->fStroke;
732
0
        float localStrokeWidth = GrStrokeTolerances::GetLocalStrokeWidth(fMatrixMinMaxScales.data(),
733
0
                                                                         stroke.getWidth());
734
0
        float numRadialSegmentsPerRadian = GrStrokeTolerances::CalcNumRadialSegmentsPerRadian(
735
0
                patchWriter.parametricPrecision(), localStrokeWidth);
736
0
        patchWriter.updateTolerances(numRadialSegmentsPerRadian, stroke.getJoin());
737
0
    }
738
739
    // Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we
740
    // have dynamic strokes.
741
0
    GrStrokeToleranceBuffer toleranceBuffer(patchWriter.parametricPrecision());
742
743
0
    for (PathStrokeList* pathStroke = fPathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
744
0
        const SkStrokeRec& stroke = pathStroke->fStroke;
745
0
        if (fShader.hasDynamicStroke()) {
746
            // Strokes are dynamic. Update tolerances with every new stroke.
747
0
            patchWriter.updateTolerances(toleranceBuffer.fetchRadialSegmentsPerRadian(pathStroke),
748
0
                                         stroke.getJoin());
749
0
            patchWriter.updateDynamicStroke(stroke);
750
0
        }
751
0
        if (fShader.hasDynamicColor()) {
752
0
            patchWriter.updateDynamicColor(pathStroke->fColor);
753
0
        }
754
755
0
        const SkPath& path = pathStroke->fPath;
756
0
        bool contourIsEmpty = true;
757
0
        for (auto [verb, p, w] : SkPathPriv::Iterate(path)) {
758
0
            bool prevJoinFitsInPatch;
759
0
            SkPoint scratchPts[4];
760
0
            const SkPoint* patchPts;
761
0
            SkPoint endControlPoint;
762
0
            switch (verb) {
763
0
                case SkPathVerb::kMove:
764
                    // "A subpath ... consisting of a single moveto shall not be stroked."
765
                    // https://www.w3.org/TR/SVG11/painting.html#StrokeProperties
766
0
                    if (!contourIsEmpty) {
767
0
                        patchWriter.writeCaps(p[-1], fShader.viewMatrix(), stroke);
768
0
                    }
769
0
                    patchWriter.moveTo(p[0]);
770
0
                    contourIsEmpty = true;
771
0
                    continue;
772
0
                case SkPathVerb::kClose:
773
0
                    patchWriter.writeClose(p[0], fShader.viewMatrix(), stroke);
774
0
                    contourIsEmpty = true;
775
0
                    continue;
776
0
                case SkPathVerb::kLine:
777
                    // Set this to false first, before the upcoming continue might disrupt our flow.
778
0
                    contourIsEmpty = false;
779
0
                    if (p[0] == p[1]) {
780
0
                        continue;
781
0
                    }
782
0
                    prevJoinFitsInPatch = patchWriter.lineFitsInPatch_withJoin();
783
0
                    scratchPts[0] = scratchPts[1] = p[0];
784
0
                    scratchPts[2] = scratchPts[3] = p[1];
785
0
                    patchPts = scratchPts;
786
0
                    endControlPoint = p[0];
787
0
                    break;
788
0
                case SkPathVerb::kQuad: {
789
0
                    contourIsEmpty = false;
790
0
                    if (p[1] == p[0] || p[1] == p[2]) {
791
                        // Zero-length paths need special treatment because they are spec'd to
792
                        // behave differently. If the control point is colocated on an endpoint then
793
                        // this might end up being the case. Fall back on a lineTo and let it make
794
                        // the final check.
795
0
                        patchWriter.writeLineTo(p[0], p[2]);
796
0
                        continue;
797
0
                    }
798
0
                    if (GrPathUtils::conicHasCusp(p)) {
799
                        // Cusps are rare, but the tessellation shader can't handle them. Chop the
800
                        // curve into segments that the shader can handle.
801
0
                        SkPoint cusp = SkEvalQuadAt(p, SkFindQuadMidTangent(p));
802
0
                        patchWriter.writeLineTo(p[0], cusp);
803
0
                        patchWriter.writeLineTo(JoinType::kBowtie, cusp, p[2]);
804
0
                        continue;
805
0
                    }
806
0
                    float numParametricSegments_pow4 =
807
0
                            GrWangsFormula::quadratic_pow4(patchWriter.parametricPrecision(), p);
808
0
                    if (!patchWriter.stroke180FitsInPatch(numParametricSegments_pow4)) {
809
                        // The curve requires more tessellation segments than the hardware can
810
                        // support. This is rare. Recursively chop until each sub-curve fits.
811
0
                        patchWriter.writeConicPatchesTo(p, 1);
812
0
                        continue;
813
0
                    }
814
                    // The curve fits in a single tessellation patch. This is the most common case.
815
                    // Write it out directly.
816
0
                    prevJoinFitsInPatch = patchWriter.stroke180FitsInPatch_withJoin(
817
0
                            numParametricSegments_pow4);
818
0
                    GrPathUtils::convertQuadToCubic(p, scratchPts);
819
0
                    patchPts = scratchPts;
820
0
                    endControlPoint = patchPts[2];
821
0
                    break;
822
0
                }
823
0
                case SkPathVerb::kConic: {
824
0
                    contourIsEmpty = false;
825
0
                    if (p[1] == p[0] || p[1] == p[2]) {
826
                        // Zero-length paths need special treatment because they are spec'd to
827
                        // behave differently. If the control point is colocated on an endpoint then
828
                        // this might end up being the case. Fall back on a lineTo and let it make
829
                        // the final check.
830
0
                        patchWriter.writeLineTo(p[0], p[2]);
831
0
                        continue;
832
0
                    }
833
0
                    if (GrPathUtils::conicHasCusp(p)) {
834
                        // Cusps are rare, but the tessellation shader can't handle them. Chop the
835
                        // curve into segments that the shader can handle.
836
0
                        SkConic conic(p, *w);
837
0
                        SkPoint cusp = conic.evalAt(conic.findMidTangent());
838
0
                        patchWriter.writeLineTo(p[0], cusp);
839
0
                        patchWriter.writeLineTo(JoinType::kBowtie, cusp, p[2]);
840
0
                        continue;
841
0
                    }
842
                    // For now, the tessellation shader still uses Wang's quadratic formula when it
843
                    // draws conics.
844
                    // TODO: Update here when the shader starts using the real conic formula.
845
0
                    float n = GrWangsFormula::conic_pow2(patchWriter.parametricPrecision(), p, *w);
846
0
                    float numParametricSegments_pow4 = n*n;
847
0
                    if (!patchWriter.stroke180FitsInPatch(numParametricSegments_pow4)) {
848
                        // The curve requires more tessellation segments than the hardware can
849
                        // support. This is rare. Recursively chop until each sub-curve fits.
850
0
                        patchWriter.writeConicPatchesTo(p, *w);
851
0
                        continue;
852
0
                    }
853
                    // The curve fits in a single tessellation patch. This is the most common
854
                    // case. Write it out directly.
855
0
                    prevJoinFitsInPatch = patchWriter.stroke180FitsInPatch_withJoin(
856
0
                            numParametricSegments_pow4);
857
0
                    GrTessellationShader::WriteConicPatch(p, *w, scratchPts);
858
0
                    patchPts = scratchPts;
859
0
                    endControlPoint = p[1];
860
0
                    break;
861
0
                }
862
0
                case SkPathVerb::kCubic: {
863
0
                    contourIsEmpty = false;
864
0
                    if (p[1] == p[2] && (p[1] == p[0] || p[1] == p[3])) {
865
                        // The stroke tessellation shader assigns special meaning to p0==p1==p2 and
866
                        // p1==p2==p3. If this is the case then we need to rewrite the cubic.
867
0
                        patchWriter.writeLineTo(p[0], p[3]);
868
0
                        continue;
869
0
                    }
870
0
                    float numParametricSegments_pow4 =
871
0
                            GrWangsFormula::cubic_pow4(patchWriter.parametricPrecision(), p);
872
0
                    if (!patchWriter.stroke360FitsInPatch(numParametricSegments_pow4) ||
873
0
                        cubic_has_cusp(p)) {
874
                        // Either the curve requires more tessellation segments than the hardware
875
                        // can support, or it has cusp(s). Either case is rare. Chop it into
876
                        // sections that rotate 180 degrees or less (which will naturally be the
877
                        // cusp points if there are any), and then recursively chop each section
878
                        // until it fits.
879
0
                        patchWriter.writeCubicConvex180PatchesTo(p);
880
0
                        continue;
881
0
                    }
882
                    // The curve fits in a single tessellation patch. This is the most common case.
883
                    // Write it out directly.
884
0
                    prevJoinFitsInPatch = patchWriter.stroke360FitsInPatch_withJoin(
885
0
                            numParametricSegments_pow4);
886
0
                    patchPts = p;
887
0
                    endControlPoint = (p[2] != p[3]) ? p[2] : p[1];
888
0
                    break;
889
0
                }
890
0
            }
891
0
            patchWriter.writePatchTo(prevJoinFitsInPatch, patchPts, endControlPoint);
892
0
        }
893
0
        if (!contourIsEmpty) {
894
0
            const SkPoint* p = SkPathPriv::PointData(path);
895
0
            patchWriter.writeCaps(p[path.countPoints() - 1], fShader.viewMatrix(), stroke);
896
0
        }
897
0
    }
898
0
}
899
900
#if SK_GPU_V1
901
#include "src/gpu/GrOpFlushState.h"
902
903
0
void GrStrokeHardwareTessellator::draw(GrOpFlushState* flushState) const {
904
0
    for (const auto& vertexChunk : fPatchChunks) {
905
0
        flushState->bindBuffers(nullptr, nullptr, vertexChunk.fBuffer);
906
0
        flushState->draw(vertexChunk.fCount, vertexChunk.fBase);
907
0
    }
908
0
}
909
910
#endif