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