| 1 | /* | 
| 2 |  * Copyright 2018 Google Inc. | 
| 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/GrStrokePatchBuilder.h" | 
| 9 |  | 
| 10 | #include "include/core/SkStrokeRec.h" | 
| 11 | #include "include/private/SkNx.h" | 
| 12 | #include "src/core/SkGeometry.h" | 
| 13 | #include "src/core/SkMathPriv.h" | 
| 14 | #include "src/core/SkPathPriv.h" | 
| 15 | #include "src/gpu/tessellate/GrStrokeTessellateShader.h" | 
| 16 | #include "src/gpu/tessellate/GrVectorXform.h" | 
| 17 | #include "src/gpu/tessellate/GrWangsFormula.h" | 
| 18 |  | 
| 19 | // This is the maximum distance in pixels that we can stray from the edge of a stroke when | 
| 20 | // converting it to flat line segments. | 
| 21 | static constexpr float kLinearizationIntolerance = 8;  // 1/8 pixel. | 
| 22 |  | 
| 23 | constexpr static float kInternalRoundJoinType = GrStrokeTessellateShader::kInternalRoundJoinType; | 
| 24 |  | 
| 25 | static Sk2f lerp(const Sk2f& a, const Sk2f& b, float T) { | 
| 26 |     SkASSERT(1 != T);  // The below does not guarantee lerp(a, b, 1) === b. | 
| 27 |     return (b - a) * T + a; | 
| 28 | } | 
| 29 |  | 
| 30 | static inline void transpose(const Sk2f& a, const Sk2f& b, Sk2f* X, Sk2f* Y) { | 
| 31 |     float transpose[4]; | 
| 32 |     a.store(transpose); | 
| 33 |     b.store(transpose+2); | 
| 34 |     Sk2f::Load2(transpose, X, Y); | 
| 35 | } | 
| 36 |  | 
| 37 | static inline float calc_curvature_costheta(const Sk2f& leftTan, const Sk2f& rightTan) { | 
| 38 |     Sk2f X, Y; | 
| 39 |     transpose(leftTan, rightTan, &X, &Y); | 
| 40 |     Sk2f invlength = (X*X + Y*Y).rsqrt(); | 
| 41 |     Sk2f dotprod = leftTan * rightTan; | 
| 42 |     return (dotprod[0] + dotprod[1]) * invlength[0] * invlength[1]; | 
| 43 | } | 
| 44 |  | 
| 45 | void GrStrokePatchBuilder::allocVertexChunk(int minVertexAllocCount) { | 
| 46 |     VertexChunk* chunk = &fVertexChunkArray->push_back(); | 
| 47 |     fCurrChunkVertexData = (SkPoint*)fTarget->makeVertexSpaceAtLeast( | 
| 48 |             sizeof(SkPoint), minVertexAllocCount, minVertexAllocCount, &chunk->fVertexBuffer, | 
| 49 |             &chunk->fBaseVertex, &fCurrChunkVertexCapacity); | 
| 50 |     fCurrChunkMinVertexAllocCount = minVertexAllocCount; | 
| 51 | } | 
| 52 |  | 
| 53 | SkPoint* GrStrokePatchBuilder::reservePatch() { | 
| 54 |     constexpr static int kNumVerticesPerPatch = GrStrokeTessellateShader::kNumVerticesPerPatch; | 
| 55 |     if (fVertexChunkArray->back().fVertexCount + kNumVerticesPerPatch > fCurrChunkVertexCapacity) { | 
| 56 |         // The current chunk is full. Time to allocate a new one. (And no need to put back vertices; | 
| 57 |         // the buffer is full.) | 
| 58 |         this->allocVertexChunk(fCurrChunkMinVertexAllocCount * 2); | 
| 59 |     } | 
| 60 |     if (!fCurrChunkVertexData) { | 
| 61 |         SkDebugf("WARNING: Failed to allocate vertex buffer for tessellated stroke." ); | 
| 62 |         return nullptr; | 
| 63 |     } | 
| 64 |     SkASSERT(fVertexChunkArray->back().fVertexCount + kNumVerticesPerPatch <= | 
| 65 |              fCurrChunkVertexCapacity); | 
| 66 |     SkPoint* patch = fCurrChunkVertexData + fVertexChunkArray->back().fVertexCount; | 
| 67 |     fVertexChunkArray->back().fVertexCount += kNumVerticesPerPatch; | 
| 68 |     return patch; | 
| 69 | } | 
| 70 |  | 
| 71 | void GrStrokePatchBuilder::writeCubicSegment(float leftJoinType, const SkPoint pts[4], | 
| 72 |                                              float overrideNumSegments) { | 
| 73 |     SkPoint c1 = (pts[1] == pts[0]) ? pts[2] : pts[1]; | 
| 74 |     SkPoint c2 = (pts[2] == pts[3]) ? pts[1] : pts[2]; | 
| 75 |  | 
| 76 |     if (fHasPreviousSegment) { | 
| 77 |         this->writeJoin(leftJoinType, pts[0], fLastControlPoint, c1); | 
| 78 |     } else { | 
| 79 |         fCurrContourFirstControlPoint = c1; | 
| 80 |         fHasPreviousSegment = true; | 
| 81 |     } | 
| 82 |  | 
| 83 |     if (SkPoint* patch = this->reservePatch()) { | 
| 84 |         memcpy(patch, pts, sizeof(SkPoint) * 4); | 
| 85 |         patch[4].set(overrideNumSegments, fCurrStrokeRadius); | 
| 86 |     } | 
| 87 |  | 
| 88 |     fLastControlPoint = c2; | 
| 89 |     fCurrentPoint = pts[3]; | 
| 90 | } | 
| 91 |  | 
| 92 | void GrStrokePatchBuilder::writeJoin(float joinType, const SkPoint& anchorPoint, | 
| 93 |                                      const SkPoint& prevControlPoint, | 
| 94 |                                      const SkPoint& nextControlPoint) { | 
| 95 |     if (SkPoint* joinPatch = this->reservePatch()) { | 
| 96 |         joinPatch[0] = anchorPoint; | 
| 97 |         joinPatch[1] = prevControlPoint; | 
| 98 |         joinPatch[2] = nextControlPoint; | 
| 99 |         joinPatch[3] = anchorPoint; | 
| 100 |         joinPatch[4].set(joinType, fCurrStrokeRadius); | 
| 101 |     } | 
| 102 | } | 
| 103 |  | 
| 104 | void GrStrokePatchBuilder::writeSquareCap(const SkPoint& endPoint, const SkPoint& controlPoint) { | 
| 105 |     SkVector v = (endPoint - controlPoint); | 
| 106 |     v.normalize(); | 
| 107 |     SkPoint capPoint = endPoint + v*fCurrStrokeRadius; | 
| 108 |     // Construct a line that incorporates controlPoint so we get a water tight edge with the rest of | 
| 109 |     // the stroke. The cubic will technically step outside the cap, but we will force it to only | 
| 110 |     // have one segment, giving edges only at the endpoints. | 
| 111 |     if (SkPoint* capPatch = this->reservePatch()) { | 
| 112 |         capPatch[0] = endPoint; | 
| 113 |         capPatch[1] = controlPoint; | 
| 114 |         // Straddle the midpoint of the cap because the tessellated geometry emits a center point at | 
| 115 |         // T=.5, and we need to ensure that point stays inside the cap. | 
| 116 |         capPatch[2] = endPoint + capPoint - controlPoint; | 
| 117 |         capPatch[3] = capPoint; | 
| 118 |         capPatch[4].set(1, fCurrStrokeRadius); | 
| 119 |     } | 
| 120 | } | 
| 121 |  | 
| 122 | void GrStrokePatchBuilder::writeCaps() { | 
| 123 |     if (!fHasPreviousSegment) { | 
| 124 |         // We don't have any control points to orient the caps. In this case, square and round caps | 
| 125 |         // are specified to be drawn as an axis-aligned square or circle respectively. Assign | 
| 126 |         // default control points that achieve this. | 
| 127 |         fCurrContourFirstControlPoint = fCurrContourStartPoint - SkPoint{1,0}; | 
| 128 |         fLastControlPoint = fCurrContourStartPoint + SkPoint{1,0}; | 
| 129 |         fCurrentPoint = fCurrContourStartPoint; | 
| 130 |     } | 
| 131 |  | 
| 132 |     switch (fCurrStrokeCapType) { | 
| 133 |         case SkPaint::kButt_Cap: | 
| 134 |             break; | 
| 135 |         case SkPaint::kRound_Cap: | 
| 136 |             // A round cap is the same thing as a 180-degree round join. | 
| 137 |             this->writeJoin(GrStrokeTessellateShader::kRoundJoinType, fCurrContourStartPoint, | 
| 138 |                             fCurrContourFirstControlPoint, fCurrContourFirstControlPoint); | 
| 139 |             this->writeJoin(GrStrokeTessellateShader::kRoundJoinType, fCurrentPoint, | 
| 140 |                             fLastControlPoint, fLastControlPoint); | 
| 141 |             break; | 
| 142 |         case SkPaint::kSquare_Cap: | 
| 143 |             this->writeSquareCap(fCurrContourStartPoint, fCurrContourFirstControlPoint); | 
| 144 |             this->writeSquareCap(fCurrentPoint, fLastControlPoint); | 
| 145 |             break; | 
| 146 |     } | 
| 147 | } | 
| 148 |  | 
| 149 | void GrStrokePatchBuilder::addPath(const SkPath& path, const SkStrokeRec& stroke) { | 
| 150 |     this->beginPath(stroke); | 
| 151 |     SkPathVerb previousVerb = SkPathVerb::kClose; | 
| 152 |     for (auto [verb, rawPts, w] : SkPathPriv::Iterate(path)) { | 
| 153 |         SkPoint pts[4]; | 
| 154 |         int numPtsInVerb = SkPathPriv::PtsInIter((unsigned)verb); | 
| 155 |         for (int i = 0; i < numPtsInVerb; ++i) { | 
| 156 |             // TEMPORORY: Scale all the points up front. SkFind*MaxCurvature and GrWangsFormula::* | 
| 157 |             // both expect arrays of points. As we refine this class and its math, this scale will | 
| 158 |             // hopefully be integrated more efficiently. | 
| 159 |             pts[i] = rawPts[i] * fMatrixScale; | 
| 160 |         } | 
| 161 |         switch (verb) { | 
| 162 |             case SkPathVerb::kMove: | 
| 163 |                 // "A subpath ... consisting of a single moveto shall not be stroked." | 
| 164 |                 // https://www.w3.org/TR/SVG11/painting.html#StrokeProperties | 
| 165 |                 if (previousVerb != SkPathVerb::kMove && previousVerb != SkPathVerb::kClose) { | 
| 166 |                     this->writeCaps(); | 
| 167 |                 } | 
| 168 |                 this->moveTo(pts[0]); | 
| 169 |                 break; | 
| 170 |             case SkPathVerb::kClose: | 
| 171 |                 this->close(); | 
| 172 |                 break; | 
| 173 |             case SkPathVerb::kLine: | 
| 174 |                 SkASSERT(previousVerb != SkPathVerb::kClose); | 
| 175 |                 this->lineTo(pts[0], pts[1]); | 
| 176 |                 break; | 
| 177 |             case SkPathVerb::kQuad: | 
| 178 |                 SkASSERT(previousVerb != SkPathVerb::kClose); | 
| 179 |                 this->quadraticTo(pts); | 
| 180 |                 break; | 
| 181 |             case SkPathVerb::kCubic: | 
| 182 |                 SkASSERT(previousVerb != SkPathVerb::kClose); | 
| 183 |                 this->cubicTo(pts); | 
| 184 |                 break; | 
| 185 |             case SkPathVerb::kConic: | 
| 186 |                 SkASSERT(previousVerb != SkPathVerb::kClose); | 
| 187 |                 SkUNREACHABLE; | 
| 188 |         } | 
| 189 |         previousVerb = verb; | 
| 190 |     } | 
| 191 |     if (previousVerb != SkPathVerb::kMove && previousVerb != SkPathVerb::kClose) { | 
| 192 |         this->writeCaps(); | 
| 193 |     } | 
| 194 | } | 
| 195 |  | 
| 196 | static float join_type_from_join(SkPaint::Join join) { | 
| 197 |     switch (join) { | 
| 198 |         case SkPaint::kBevel_Join: | 
| 199 |             return GrStrokeTessellateShader::kBevelJoinType; | 
| 200 |         case SkPaint::kMiter_Join: | 
| 201 |             return GrStrokeTessellateShader::kMiterJoinType; | 
| 202 |         case SkPaint::kRound_Join: | 
| 203 |             return GrStrokeTessellateShader::kRoundJoinType; | 
| 204 |     } | 
| 205 |     SkUNREACHABLE; | 
| 206 | } | 
| 207 |  | 
| 208 | void GrStrokePatchBuilder::beginPath(const SkStrokeRec& stroke) { | 
| 209 |     // We don't support hairline strokes. For now, the client can transform the path into device | 
| 210 |     // space and then use a stroke width of 1. | 
| 211 |     SkASSERT(stroke.getWidth() > 0); | 
| 212 |  | 
| 213 |     fCurrStrokeRadius = stroke.getWidth()/2 * fMatrixScale; | 
| 214 |     fCurrStrokeJoinType = join_type_from_join(stroke.getJoin()); | 
| 215 |     fCurrStrokeCapType = stroke.getCap(); | 
| 216 |  | 
| 217 |     // Find the angle of curvature where the arc height above a simple line from point A to point B | 
| 218 |     // is equal to 1/kLinearizationIntolerance. (The arc height is always the same no matter how | 
| 219 |     // long the line is. What we are interested in is the difference in height between the part of | 
| 220 |     // the stroke whose normal is orthogonal to the line, vs the heights at the endpoints.) | 
| 221 |     float r = std::max(1 - 1/(fCurrStrokeRadius * kLinearizationIntolerance), 0.f); | 
| 222 |     fMaxCurvatureCosTheta = 2*r*r - 1; | 
| 223 |  | 
| 224 |     fHasPreviousSegment = false; | 
| 225 | } | 
| 226 |  | 
| 227 | void GrStrokePatchBuilder::moveTo(const SkPoint& pt) { | 
| 228 |     fHasPreviousSegment = false; | 
| 229 |     fCurrContourStartPoint = pt; | 
| 230 | } | 
| 231 |  | 
| 232 | void GrStrokePatchBuilder::lineTo(const SkPoint& p0, const SkPoint& p1) { | 
| 233 |     this->lineTo(fCurrStrokeJoinType, p0, p1); | 
| 234 | } | 
| 235 |  | 
| 236 | void GrStrokePatchBuilder::lineTo(float leftJoinType, const SkPoint& pt0, const SkPoint& pt1) { | 
| 237 |     Sk2f p0 = Sk2f::Load(&pt0); | 
| 238 |     Sk2f p1 = Sk2f::Load(&pt1); | 
| 239 |     if ((p0 == p1).allTrue()) { | 
| 240 |         return; | 
| 241 |     } | 
| 242 |     this->writeCubicSegment(leftJoinType, p0, lerp(p0, p1, 1/3.f), lerp(p0, p1, 2/3.f), p1, 1); | 
| 243 | } | 
| 244 |  | 
| 245 | void GrStrokePatchBuilder::quadraticTo(const SkPoint P[3]) { | 
| 246 |     this->quadraticTo(fCurrStrokeJoinType, P, SkFindQuadMaxCurvature(P)); | 
| 247 | } | 
| 248 |  | 
| 249 | void GrStrokePatchBuilder::quadraticTo(float leftJoinType, const SkPoint P[3], | 
| 250 |                                        float maxCurvatureT) { | 
| 251 |     if (P[1] == P[0] || P[1] == P[2]) { | 
| 252 |         this->lineTo(leftJoinType, P[0], P[2]); | 
| 253 |         return; | 
| 254 |     } | 
| 255 |  | 
| 256 |     // Decide a lower bound on the length (in parametric sense) of linear segments the curve will be | 
| 257 |     // chopped into. | 
| 258 |     int numSegments = 1 << GrWangsFormula::quadratic_log2(kLinearizationIntolerance, P); | 
| 259 |     float segmentLength = SkScalarInvert(numSegments); | 
| 260 |  | 
| 261 |     Sk2f p0 = Sk2f::Load(P); | 
| 262 |     Sk2f p1 = Sk2f::Load(P+1); | 
| 263 |     Sk2f p2 = Sk2f::Load(P+2); | 
| 264 |  | 
| 265 |     Sk2f tan0 = p1 - p0; | 
| 266 |     Sk2f tan1 = p2 - p1; | 
| 267 |  | 
| 268 |     // At + B gives a vector tangent to the quadratic. | 
| 269 |     Sk2f A = p0 - p1*2 + p2; | 
| 270 |     Sk2f B = p1 - p0; | 
| 271 |  | 
| 272 |     // Find a line segment that crosses max curvature. | 
| 273 |     float leftT = maxCurvatureT - segmentLength/2; | 
| 274 |     float rightT = maxCurvatureT + segmentLength/2; | 
| 275 |     Sk2f leftTan, rightTan; | 
| 276 |     if (leftT <= 0) { | 
| 277 |         leftT = 0; | 
| 278 |         leftTan = tan0; | 
| 279 |         rightT = segmentLength; | 
| 280 |         rightTan = A*rightT + B; | 
| 281 |     } else if (rightT >= 1) { | 
| 282 |         leftT = 1 - segmentLength; | 
| 283 |         leftTan = A*leftT + B; | 
| 284 |         rightT = 1; | 
| 285 |         rightTan = tan1; | 
| 286 |     } else { | 
| 287 |         leftTan = A*leftT + B; | 
| 288 |         rightTan = A*rightT + B; | 
| 289 |     } | 
| 290 |  | 
| 291 |     // Check if curvature is too strong for a triangle strip on the line segment that crosses max | 
| 292 |     // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins. | 
| 293 |     // | 
| 294 |     // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We | 
| 295 |     // would benefit significantly from a quick reject that detects curves that don't need special | 
| 296 |     // treatment for strong curvature. | 
| 297 |     if (numSegments > 1 && calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta) { | 
| 298 |         SkPoint ptsBuffer[5]; | 
| 299 |         const SkPoint* currQuadratic = P; | 
| 300 |  | 
| 301 |         if (leftT > 0) { | 
| 302 |             SkChopQuadAt(currQuadratic, ptsBuffer, leftT); | 
| 303 |             this->quadraticTo(leftJoinType, ptsBuffer, /*maxCurvatureT=*/1); | 
| 304 |             if (rightT < 1) { | 
| 305 |                 rightT = (rightT - leftT) / (1 - leftT); | 
| 306 |             } | 
| 307 |             currQuadratic = ptsBuffer + 2; | 
| 308 |         } else { | 
| 309 |             this->rotateTo(leftJoinType, currQuadratic[0], currQuadratic[1]); | 
| 310 |         } | 
| 311 |  | 
| 312 |         if (rightT < 1) { | 
| 313 |             SkChopQuadAt(currQuadratic, ptsBuffer, rightT); | 
| 314 |             this->lineTo(kInternalRoundJoinType, ptsBuffer[0], ptsBuffer[2]); | 
| 315 |             this->quadraticTo(kInternalRoundJoinType, ptsBuffer + 2, /*maxCurvatureT=*/0); | 
| 316 |         } else { | 
| 317 |             this->lineTo(kInternalRoundJoinType, currQuadratic[0], currQuadratic[2]); | 
| 318 |             this->rotateTo(kInternalRoundJoinType, currQuadratic[2], | 
| 319 |                            currQuadratic[2]*2 - currQuadratic[1]); | 
| 320 |         } | 
| 321 |         return; | 
| 322 |     } | 
| 323 |     if (numSegments > fMaxTessellationSegments) { | 
| 324 |         SkPoint ptsBuffer[5]; | 
| 325 |         SkChopQuadAt(P, ptsBuffer, 0.5f); | 
| 326 |         this->quadraticTo(leftJoinType, ptsBuffer, 0); | 
| 327 |         this->quadraticTo(kInternalRoundJoinType, ptsBuffer + 3, 0); | 
| 328 |         return; | 
| 329 |     } | 
| 330 |  | 
| 331 |     this->writeCubicSegment(leftJoinType, p0, lerp(p0, p1, 2/3.f), lerp(p1, p2, 1/3.f), p2); | 
| 332 | } | 
| 333 |  | 
| 334 | void GrStrokePatchBuilder::cubicTo(const SkPoint P[4]) { | 
| 335 |     float roots[3]; | 
| 336 |     int numRoots = SkFindCubicMaxCurvature(P, roots); | 
| 337 |     this->cubicTo(fCurrStrokeJoinType, P, | 
| 338 |                   numRoots > 0 ? roots[numRoots/2] : 0, | 
| 339 |                   numRoots > 1 ? roots[0] : kLeftMaxCurvatureNone, | 
| 340 |                   numRoots > 2 ? roots[2] : kRightMaxCurvatureNone); | 
| 341 | } | 
| 342 |  | 
| 343 | void GrStrokePatchBuilder::cubicTo(float leftJoinType, const SkPoint P[4], float maxCurvatureT, | 
| 344 |                                    float leftMaxCurvatureT, float rightMaxCurvatureT) { | 
| 345 |     if (P[1] == P[2] && (P[1] == P[0] || P[1] == P[3])) { | 
| 346 |         this->lineTo(leftJoinType, P[0], P[3]); | 
| 347 |         return; | 
| 348 |     } | 
| 349 |  | 
| 350 |     // Decide a lower bound on the length (in parametric sense) of linear segments the curve will be | 
| 351 |     // chopped into. | 
| 352 |     int numSegments = 1 << GrWangsFormula::cubic_log2(kLinearizationIntolerance, P); | 
| 353 |     float segmentLength = SkScalarInvert(numSegments); | 
| 354 |  | 
| 355 |     Sk2f p0 = Sk2f::Load(P); | 
| 356 |     Sk2f p1 = Sk2f::Load(P+1); | 
| 357 |     Sk2f p2 = Sk2f::Load(P+2); | 
| 358 |     Sk2f p3 = Sk2f::Load(P+3); | 
| 359 |  | 
| 360 |     Sk2f tan0 = p1 - p0; | 
| 361 |     Sk2f tan1 = p3 - p2; | 
| 362 |  | 
| 363 |     // At^2 + Bt + C gives a vector tangent to the cubic. (More specifically, it's the derivative | 
| 364 |     // minus an irrelevant scale by 3, since all we care about is the direction.) | 
| 365 |     Sk2f A = p3 + (p1 - p2)*3 - p0; | 
| 366 |     Sk2f B = (p0 - p1*2 + p2)*2; | 
| 367 |     Sk2f C = p1 - p0; | 
| 368 |  | 
| 369 |     // Find a line segment that crosses max curvature. | 
| 370 |     float leftT = maxCurvatureT - segmentLength/2; | 
| 371 |     float rightT = maxCurvatureT + segmentLength/2; | 
| 372 |     Sk2f leftTan, rightTan; | 
| 373 |     if (leftT <= 0) { | 
| 374 |         leftT = 0; | 
| 375 |         leftTan = tan0; | 
| 376 |         rightT = segmentLength; | 
| 377 |         rightTan = A*rightT*rightT + B*rightT + C; | 
| 378 |     } else if (rightT >= 1) { | 
| 379 |         leftT = 1 - segmentLength; | 
| 380 |         leftTan = A*leftT*leftT + B*leftT + C; | 
| 381 |         rightT = 1; | 
| 382 |         rightTan = tan1; | 
| 383 |     } else { | 
| 384 |         leftTan = A*leftT*leftT + B*leftT + C; | 
| 385 |         rightTan = A*rightT*rightT + B*rightT + C; | 
| 386 |     } | 
| 387 |  | 
| 388 |     // Check if curvature is too strong for a triangle strip on the line segment that crosses max | 
| 389 |     // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins. | 
| 390 |     // | 
| 391 |     // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We | 
| 392 |     // would benefit significantly from a quick reject that detects curves that don't need special | 
| 393 |     // treatment for strong curvature. | 
| 394 |     if (numSegments > 1 && calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta) { | 
| 395 |         SkPoint ptsBuffer[7]; | 
| 396 |         p0.store(ptsBuffer); | 
| 397 |         p1.store(ptsBuffer + 1); | 
| 398 |         p2.store(ptsBuffer + 2); | 
| 399 |         p3.store(ptsBuffer + 3); | 
| 400 |         const SkPoint* currCubic = ptsBuffer; | 
| 401 |  | 
| 402 |         if (leftT > 0) { | 
| 403 |             SkChopCubicAt(currCubic, ptsBuffer, leftT); | 
| 404 |             this->cubicTo(leftJoinType, ptsBuffer, /*maxCurvatureT=*/1, | 
| 405 |                           (kLeftMaxCurvatureNone != leftMaxCurvatureT) | 
| 406 |                                   ? leftMaxCurvatureT/leftT : kLeftMaxCurvatureNone, | 
| 407 |                           kRightMaxCurvatureNone); | 
| 408 |             if (rightT < 1) { | 
| 409 |                 rightT = (rightT - leftT) / (1 - leftT); | 
| 410 |             } | 
| 411 |             if (rightMaxCurvatureT < 1 && kRightMaxCurvatureNone != rightMaxCurvatureT) { | 
| 412 |                 rightMaxCurvatureT = (rightMaxCurvatureT - leftT) / (1 - leftT); | 
| 413 |             } | 
| 414 |             currCubic = ptsBuffer + 3; | 
| 415 |         } else { | 
| 416 |             SkPoint c1 = (ptsBuffer[1] == ptsBuffer[0]) ? ptsBuffer[2] : ptsBuffer[1]; | 
| 417 |             this->rotateTo(leftJoinType, ptsBuffer[0], c1); | 
| 418 |         } | 
| 419 |  | 
| 420 |         if (rightT < 1) { | 
| 421 |             SkChopCubicAt(currCubic, ptsBuffer, rightT); | 
| 422 |             this->lineTo(kInternalRoundJoinType, ptsBuffer[0], ptsBuffer[3]); | 
| 423 |             currCubic = ptsBuffer + 3; | 
| 424 |             this->cubicTo(kInternalRoundJoinType, currCubic, /*maxCurvatureT=*/0, | 
| 425 |                           kLeftMaxCurvatureNone, kRightMaxCurvatureNone); | 
| 426 |         } else { | 
| 427 |             this->lineTo(kInternalRoundJoinType, currCubic[0], currCubic[3]); | 
| 428 |             SkPoint c2 = (currCubic[2] == currCubic[3]) ? currCubic[1] : currCubic[2]; | 
| 429 |             this->rotateTo(kInternalRoundJoinType, currCubic[3], currCubic[3]*2 - c2); | 
| 430 |         } | 
| 431 |         return; | 
| 432 |     } | 
| 433 |  | 
| 434 |     // Recurse and check the other two points of max curvature, if any. | 
| 435 |     if (kRightMaxCurvatureNone != rightMaxCurvatureT) { | 
| 436 |         this->cubicTo(leftJoinType, P, rightMaxCurvatureT, leftMaxCurvatureT, | 
| 437 |                       kRightMaxCurvatureNone); | 
| 438 |         return; | 
| 439 |     } | 
| 440 |     if (kLeftMaxCurvatureNone != leftMaxCurvatureT) { | 
| 441 |         SkASSERT(kRightMaxCurvatureNone == rightMaxCurvatureT); | 
| 442 |         this->cubicTo(leftJoinType, P, leftMaxCurvatureT, kLeftMaxCurvatureNone, | 
| 443 |                       kRightMaxCurvatureNone); | 
| 444 |         return; | 
| 445 |     } | 
| 446 |     if (numSegments > fMaxTessellationSegments) { | 
| 447 |         SkPoint ptsBuffer[7]; | 
| 448 |         SkChopCubicAt(P, ptsBuffer, 0.5f); | 
| 449 |         this->cubicTo(leftJoinType, ptsBuffer, 0, kLeftMaxCurvatureNone, kRightMaxCurvatureNone); | 
| 450 |         this->cubicTo(kInternalRoundJoinType, ptsBuffer + 3, 0, kLeftMaxCurvatureNone, | 
| 451 |                       kRightMaxCurvatureNone); | 
| 452 |         return; | 
| 453 |     } | 
| 454 |  | 
| 455 |     this->writeCubicSegment(leftJoinType, p0, p1, p2, p3); | 
| 456 | } | 
| 457 |  | 
| 458 | void GrStrokePatchBuilder::rotateTo(float leftJoinType, const SkPoint& anchorPoint, | 
| 459 |                                     const SkPoint& controlPoint) { | 
| 460 |     // Effectively rotate the current normal by drawing a zero length, 1-segment cubic. | 
| 461 |     // writeCubicSegment automatically adds the necessary join and the zero length cubic serves as | 
| 462 |     // a glue that guarantees a water tight rasterized edge between the new join and the segment | 
| 463 |     // that comes after the rotate. | 
| 464 |     SkPoint pts[4] = {anchorPoint, controlPoint, anchorPoint*2 - controlPoint, anchorPoint}; | 
| 465 |     this->writeCubicSegment(leftJoinType, pts, 1); | 
| 466 | } | 
| 467 |  | 
| 468 | void GrStrokePatchBuilder::close() { | 
| 469 |     if (!fHasPreviousSegment) { | 
| 470 |         // Draw caps instead of closing if the subpath is zero length: | 
| 471 |         // | 
| 472 |         //   "Any zero length subpath ...  shall be stroked if the 'stroke-linecap' property has a | 
| 473 |         //   value of round or square producing respectively a circle or a square." | 
| 474 |         // | 
| 475 |         //   (https://www.w3.org/TR/SVG11/painting.html#StrokeProperties) | 
| 476 |         // | 
| 477 |         this->writeCaps(); | 
| 478 |         return; | 
| 479 |     } | 
| 480 |  | 
| 481 |     // Draw a line back to the beginning. (This will be discarded if | 
| 482 |     // fCurrentPoint == fCurrContourStartPoint.) | 
| 483 |     this->lineTo(fCurrStrokeJoinType, fCurrentPoint, fCurrContourStartPoint); | 
| 484 |     this->writeJoin(fCurrStrokeJoinType, fCurrContourStartPoint, fLastControlPoint, | 
| 485 |                     fCurrContourFirstControlPoint); | 
| 486 | } | 
| 487 |  |