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