blob: c289c40453a648c200f3252206c80ceafb0191ca [file] [log] [blame]
Chris Dalton419a94d2017-08-28 10:24:22 -06001/*
2 * Copyright 2017 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
Chris Dalton383a2ef2018-01-08 17:21:41 -05008#include "GrCCGeometry.h"
Chris Dalton419a94d2017-08-28 10:24:22 -06009
10#include "GrTypes.h"
Chris Dalton7f578bf2017-09-05 16:46:48 -060011#include "GrPathUtils.h"
Chris Dalton419a94d2017-08-28 10:24:22 -060012#include <algorithm>
13#include <cmath>
14#include <cstdlib>
15
16// We convert between SkPoint and Sk2f freely throughout this file.
17GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT);
18GR_STATIC_ASSERT(2 * sizeof(float) == sizeof(SkPoint));
19GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX));
20
Chris Dalton383a2ef2018-01-08 17:21:41 -050021void GrCCGeometry::beginPath() {
Chris Daltonc1e59632017-09-05 00:30:07 -060022 SkASSERT(!fBuildingContour);
23 fVerbs.push_back(Verb::kBeginPath);
24}
25
Chris Dalton7ca3b7b2018-04-10 00:21:19 -060026void GrCCGeometry::beginContour(const SkPoint& pt) {
Chris Daltonc1e59632017-09-05 00:30:07 -060027 SkASSERT(!fBuildingContour);
Chris Daltonc1e59632017-09-05 00:30:07 -060028 // Store the current verb count in the fTriangles field for now. When we close the contour we
29 // will use this value to calculate the actual number of triangles in its fan.
Chris Dalton84403d72018-02-13 21:46:17 -050030 fCurrContourTallies = {fVerbs.count(), 0, 0, 0};
Chris Daltonc1e59632017-09-05 00:30:07 -060031
Chris Dalton7ca3b7b2018-04-10 00:21:19 -060032 fPoints.push_back(pt);
Chris Daltonc1e59632017-09-05 00:30:07 -060033 fVerbs.push_back(Verb::kBeginContour);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -060034 fCurrAnchorPoint = pt;
Chris Daltonc1e59632017-09-05 00:30:07 -060035
Chris Dalton383a2ef2018-01-08 17:21:41 -050036 SkDEBUGCODE(fBuildingContour = true);
Chris Daltonc1e59632017-09-05 00:30:07 -060037}
38
Chris Dalton7ca3b7b2018-04-10 00:21:19 -060039void GrCCGeometry::lineTo(const SkPoint& pt) {
Chris Daltonc1e59632017-09-05 00:30:07 -060040 SkASSERT(fBuildingContour);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -060041 fPoints.push_back(pt);
42 fVerbs.push_back(Verb::kLineTo);
43}
44
45void GrCCGeometry::appendLine(const Sk2f& endpt) {
46 endpt.store(&fPoints.push_back());
Chris Daltonc1e59632017-09-05 00:30:07 -060047 fVerbs.push_back(Verb::kLineTo);
48}
49
Chris Dalton419a94d2017-08-28 10:24:22 -060050static inline Sk2f normalize(const Sk2f& n) {
51 Sk2f nn = n*n;
52 return n * (nn + SkNx_shuffle<1,0>(nn)).rsqrt();
53}
54
55static inline float dot(const Sk2f& a, const Sk2f& b) {
56 float product[2];
57 (a * b).store(product);
58 return product[0] + product[1];
59}
60
Chris Dalton900cd052017-09-07 10:36:51 -060061static inline bool are_collinear(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) {
Chris Dalton43646532017-12-07 12:47:02 -070062 static constexpr float kFlatnessTolerance = 4; // 1/4 of a pixel.
Chris Dalton900cd052017-09-07 10:36:51 -060063
64 // Area (times 2) of the triangle.
65 Sk2f a = (p0 - p1) * SkNx_shuffle<1,0>(p1 - p2);
66 a = (a - SkNx_shuffle<1,0>(a)).abs();
67
68 // Bounding box of the triangle.
69 Sk2f bbox0 = Sk2f::Min(Sk2f::Min(p0, p1), p2);
70 Sk2f bbox1 = Sk2f::Max(Sk2f::Max(p0, p1), p2);
71
72 // The triangle is linear if its area is within a fraction of the largest bounding box
73 // dimension, or else if its area is within a fraction of a pixel.
74 return (a * (kFlatnessTolerance/2) < Sk2f::Max(bbox1 - bbox0, 1)).anyTrue();
75}
76
Chris Dalton419a94d2017-08-28 10:24:22 -060077// Returns whether the (convex) curve segment is monotonic with respect to [endPt - startPt].
Chris Dalton7ca3b7b2018-04-10 00:21:19 -060078static inline bool is_convex_curve_monotonic(const Sk2f& startPt, const Sk2f& tan0,
79 const Sk2f& endPt, const Sk2f& tan1) {
Chris Dalton419a94d2017-08-28 10:24:22 -060080 Sk2f v = endPt - startPt;
Chris Dalton7ca3b7b2018-04-10 00:21:19 -060081 float dot0 = dot(tan0, v);
82 float dot1 = dot(tan1, v);
Chris Dalton419a94d2017-08-28 10:24:22 -060083
84 // A small, negative tolerance handles floating-point error in the case when one tangent
85 // approaches 0 length, meaning the (convex) curve segment is effectively a flat line.
86 float tolerance = -std::max(std::abs(dot0), std::abs(dot1)) * SK_ScalarNearlyZero;
87 return dot0 >= tolerance && dot1 >= tolerance;
88}
89
90static inline Sk2f lerp(const Sk2f& a, const Sk2f& b, const Sk2f& t) {
91 return SkNx_fma(t, b - a, a);
92}
93
Chris Dalton7ca3b7b2018-04-10 00:21:19 -060094void GrCCGeometry::quadraticTo(const SkPoint P[3]) {
Chris Daltonc1e59632017-09-05 00:30:07 -060095 SkASSERT(fBuildingContour);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -060096 SkASSERT(P[0] == fPoints.back());
97 Sk2f p0 = Sk2f::Load(P);
98 Sk2f p1 = Sk2f::Load(P+1);
99 Sk2f p2 = Sk2f::Load(P+2);
Chris Daltonc1e59632017-09-05 00:30:07 -0600100
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600101 // Don't crunch on the curve if it is nearly flat (or just very small). Flat curves can break
102 // The monotonic chopping math.
103 if (are_collinear(p0, p1, p2)) {
104 this->appendLine(p2);
105 return;
106 }
Chris Dalton419a94d2017-08-28 10:24:22 -0600107
Chris Dalton29011a22017-09-28 12:08:33 -0600108 this->appendMonotonicQuadratics(p0, p1, p2);
109}
110
Chris Dalton383a2ef2018-01-08 17:21:41 -0500111inline void GrCCGeometry::appendMonotonicQuadratics(const Sk2f& p0, const Sk2f& p1,
112 const Sk2f& p2) {
Chris Dalton419a94d2017-08-28 10:24:22 -0600113 Sk2f tan0 = p1 - p0;
114 Sk2f tan1 = p2 - p1;
Chris Dalton29011a22017-09-28 12:08:33 -0600115
Chris Dalton419a94d2017-08-28 10:24:22 -0600116 // This should almost always be this case for well-behaved curves in the real world.
Chris Dalton43646532017-12-07 12:47:02 -0700117 if (is_convex_curve_monotonic(p0, tan0, p2, tan1)) {
118 this->appendSingleMonotonicQuadratic(p0, p1, p2);
Chris Daltonc1e59632017-09-05 00:30:07 -0600119 return;
Chris Dalton419a94d2017-08-28 10:24:22 -0600120 }
121
122 // Chop the curve into two segments with equal curvature. To do this we find the T value whose
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600123 // tangent angle is halfway between tan0 and tan1.
Chris Dalton419a94d2017-08-28 10:24:22 -0600124 Sk2f n = normalize(tan0) - normalize(tan1);
125
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600126 // The midtangent can be found where (dQ(t) dot n) = 0:
Chris Dalton419a94d2017-08-28 10:24:22 -0600127 //
128 // 0 = (dQ(t) dot n) = | 2*t 1 | * | p0 - 2*p1 + p2 | * | n |
129 // | -2*p0 + 2*p1 | | . |
130 //
131 // = | 2*t 1 | * | tan1 - tan0 | * | n |
132 // | 2*tan0 | | . |
133 //
134 // = 2*t * ((tan1 - tan0) dot n) + (2*tan0 dot n)
135 //
136 // t = (tan0 dot n) / ((tan0 - tan1) dot n)
137 Sk2f dQ1n = (tan0 - tan1) * n;
138 Sk2f dQ0n = tan0 * n;
139 Sk2f t = (dQ0n + SkNx_shuffle<1,0>(dQ0n)) / (dQ1n + SkNx_shuffle<1,0>(dQ1n));
140 t = Sk2f::Min(Sk2f::Max(t, 0), 1); // Clamp for FP error.
141
142 Sk2f p01 = SkNx_fma(t, tan0, p0);
143 Sk2f p12 = SkNx_fma(t, tan1, p1);
144 Sk2f p012 = lerp(p01, p12, t);
145
Chris Dalton43646532017-12-07 12:47:02 -0700146 this->appendSingleMonotonicQuadratic(p0, p01, p012);
147 this->appendSingleMonotonicQuadratic(p012, p12, p2);
148}
149
Chris Dalton383a2ef2018-01-08 17:21:41 -0500150inline void GrCCGeometry::appendSingleMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1,
151 const Sk2f& p2) {
Chris Dalton43646532017-12-07 12:47:02 -0700152 SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
153
154 // Don't send curves to the GPU if we know they are nearly flat (or just very small).
155 if (are_collinear(p0, p1, p2)) {
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600156 this->appendLine(p2);
Chris Dalton43646532017-12-07 12:47:02 -0700157 return;
158 }
159
160 p1.store(&fPoints.push_back());
Chris Daltonc1e59632017-09-05 00:30:07 -0600161 p2.store(&fPoints.push_back());
Chris Dalton43646532017-12-07 12:47:02 -0700162 fVerbs.push_back(Verb::kMonotonicQuadraticTo);
163 ++fCurrContourTallies.fQuadratics;
Chris Daltonc1e59632017-09-05 00:30:07 -0600164}
165
Chris Dalton7f578bf2017-09-05 16:46:48 -0600166using ExcludedTerm = GrPathUtils::ExcludedTerm;
Chris Daltonc1e59632017-09-05 00:30:07 -0600167
Chris Dalton7f578bf2017-09-05 16:46:48 -0600168// Calculates the padding to apply around inflection points, in homogeneous parametric coordinates.
169//
170// More specifically, if the inflection point lies at C(t/s), then C((t +/- returnValue) / s) will
171// be the two points on the curve at which a square box with radius "padRadius" will have a corner
172// that touches the inflection point's tangent line.
173//
174// A serpentine cubic has two inflection points, so this method takes Sk2f and computes the padding
175// for both in SIMD.
176static inline Sk2f calc_inflect_homogeneous_padding(float padRadius, const Sk2f& t, const Sk2f& s,
177 const SkMatrix& CIT, ExcludedTerm skipTerm) {
178 SkASSERT(padRadius >= 0);
Chris Daltonc1e59632017-09-05 00:30:07 -0600179
Chris Dalton7f578bf2017-09-05 16:46:48 -0600180 Sk2f Clx = s*s*s;
181 Sk2f Cly = (ExcludedTerm::kLinearTerm == skipTerm) ? s*s*t*-3 : s*t*t*3;
182
183 Sk2f Lx = CIT[0] * Clx + CIT[3] * Cly;
184 Sk2f Ly = CIT[1] * Clx + CIT[4] * Cly;
185
186 float ret[2];
187 Sk2f bloat = padRadius * (Lx.abs() + Ly.abs());
188 (bloat * s >= 0).thenElse(bloat, -bloat).store(ret);
189
190 ret[0] = cbrtf(ret[0]);
191 ret[1] = cbrtf(ret[1]);
192 return Sk2f::Load(ret);
193}
194
195static inline void swap_if_greater(float& a, float& b) {
196 if (a > b) {
197 std::swap(a, b);
198 }
199}
200
201// Calculates all parameter values for a loop at which points a square box with radius "padRadius"
202// will have a corner that touches a tangent line from the intersection.
203//
204// T2 must contain the lesser parameter value of the loop intersection in its first component, and
205// the greater in its second.
206//
207// roots[0] will be filled with 1 or 3 sorted parameter values, representing the padding points
208// around the first tangent. roots[1] will be filled with the padding points for the second tangent.
209static inline void calc_loop_intersect_padding_pts(float padRadius, const Sk2f& T2,
210 const SkMatrix& CIT, ExcludedTerm skipTerm,
211 SkSTArray<3, float, true> roots[2]) {
212 SkASSERT(padRadius >= 0);
213 SkASSERT(T2[0] <= T2[1]);
214 SkASSERT(roots[0].empty());
215 SkASSERT(roots[1].empty());
216
217 Sk2f T1 = SkNx_shuffle<1,0>(T2);
218 Sk2f Cl = (ExcludedTerm::kLinearTerm == skipTerm) ? T2*-2 - T1 : T2*T2 + T2*T1*2;
219 Sk2f Lx = Cl * CIT[3] + CIT[0];
220 Sk2f Ly = Cl * CIT[4] + CIT[1];
221
222 Sk2f bloat = Sk2f(+.5f * padRadius, -.5f * padRadius) * (Lx.abs() + Ly.abs());
223 Sk2f q = (1.f/3) * (T2 - T1);
224
225 Sk2f qqq = q*q*q;
226 Sk2f discr = qqq*bloat*2 + bloat*bloat;
227
228 float numRoots[2], D[2];
229 (discr < 0).thenElse(3, 1).store(numRoots);
230 (T2 - q).store(D);
231
232 // Values for calculating one root.
233 float R[2], QQ[2];
234 if ((discr >= 0).anyTrue()) {
235 Sk2f r = qqq + bloat;
236 Sk2f s = r.abs() + discr.sqrt();
237 (r > 0).thenElse(-s, s).store(R);
238 (q*q).store(QQ);
Chris Daltonc1e59632017-09-05 00:30:07 -0600239 }
240
Chris Dalton7f578bf2017-09-05 16:46:48 -0600241 // Values for calculating three roots.
242 float P[2], cosTheta3[2];
243 if ((discr < 0).anyTrue()) {
244 (q.abs() * -2).store(P);
245 ((q >= 0).thenElse(1, -1) + bloat / qqq.abs()).store(cosTheta3);
Chris Daltonc1e59632017-09-05 00:30:07 -0600246 }
247
Chris Dalton7f578bf2017-09-05 16:46:48 -0600248 for (int i = 0; i < 2; ++i) {
249 if (1 == numRoots[i]) {
250 float A = cbrtf(R[i]);
251 float B = A != 0 ? QQ[i]/A : 0;
252 roots[i].push_back(A + B + D[i]);
Chris Daltonc1e59632017-09-05 00:30:07 -0600253 continue;
254 }
255
Chris Dalton7f578bf2017-09-05 16:46:48 -0600256 static constexpr float k2PiOver3 = 2 * SK_ScalarPI / 3;
257 float theta = std::acos(cosTheta3[i]) * (1.f/3);
258 roots[i].push_back(P[i] * std::cos(theta) + D[i]);
259 roots[i].push_back(P[i] * std::cos(theta + k2PiOver3) + D[i]);
260 roots[i].push_back(P[i] * std::cos(theta - k2PiOver3) + D[i]);
Chris Daltonc1e59632017-09-05 00:30:07 -0600261
Chris Dalton7f578bf2017-09-05 16:46:48 -0600262 // Sort the three roots.
263 swap_if_greater(roots[i][0], roots[i][1]);
264 swap_if_greater(roots[i][1], roots[i][2]);
265 swap_if_greater(roots[i][0], roots[i][1]);
266 }
267}
268
Chris Dalton29011a22017-09-28 12:08:33 -0600269static inline Sk2f first_unless_nearly_zero(const Sk2f& a, const Sk2f& b) {
270 Sk2f aa = a*a;
271 aa += SkNx_shuffle<1,0>(aa);
272 SkASSERT(aa[0] == aa[1]);
273
274 Sk2f bb = b*b;
275 bb += SkNx_shuffle<1,0>(bb);
276 SkASSERT(bb[0] == bb[1]);
277
278 return (aa > bb * SK_ScalarNearlyZero).thenElse(a, b);
279}
280
281static inline bool is_cubic_nearly_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600282 const Sk2f& p3, Sk2f& tan0, Sk2f& tan1, Sk2f& c) {
Chris Dalton29011a22017-09-28 12:08:33 -0600283 tan0 = first_unless_nearly_zero(p1 - p0, p2 - p0);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600284 tan1 = first_unless_nearly_zero(p3 - p2, p3 - p1);
Chris Dalton29011a22017-09-28 12:08:33 -0600285
286 Sk2f c1 = SkNx_fma(Sk2f(1.5f), tan0, p0);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600287 Sk2f c2 = SkNx_fma(Sk2f(-1.5f), tan1, p3);
Chris Dalton29011a22017-09-28 12:08:33 -0600288 c = (c1 + c2) * .5f; // Hopefully optimized out if not used?
289
290 return ((c1 - c2).abs() <= 1).allTrue();
291}
292
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600293void GrCCGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) {
Chris Dalton7f578bf2017-09-05 16:46:48 -0600294 SkASSERT(fBuildingContour);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600295 SkASSERT(P[0] == fPoints.back());
296 Sk2f p0 = Sk2f::Load(P);
297 Sk2f p1 = Sk2f::Load(P+1);
298 Sk2f p2 = Sk2f::Load(P+2);
299 Sk2f p3 = Sk2f::Load(P+3);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600300
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600301 // Don't crunch on the curve or inflate geometry if it is nearly flat (or just very small).
302 // Flat curves can break the math below.
Chris Dalton900cd052017-09-07 10:36:51 -0600303 if (are_collinear(p0, p1, p2) &&
304 are_collinear(p1, p2, p3) &&
305 are_collinear(p0, (p1 + p2) * .5f, p3)) {
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600306 this->appendLine(p3);
Chris Dalton900cd052017-09-07 10:36:51 -0600307 return;
308 }
309
Chris Dalton29011a22017-09-28 12:08:33 -0600310 // Also detect near-quadratics ahead of time.
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600311 Sk2f tan0, tan1, c;
312 if (is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, c)) {
Chris Dalton29011a22017-09-28 12:08:33 -0600313 this->appendMonotonicQuadratics(p0, c, p3);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600314 return;
315 }
316
Chris Dalton29011a22017-09-28 12:08:33 -0600317 double tt[2], ss[2];
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600318 fCurrCubicType = SkClassifyCubic(P, tt, ss);
Chris Dalton29011a22017-09-28 12:08:33 -0600319 SkASSERT(!SkCubicIsDegenerate(fCurrCubicType)); // Should have been caught above.
320
Chris Dalton7f578bf2017-09-05 16:46:48 -0600321 SkMatrix CIT;
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600322 ExcludedTerm skipTerm = GrPathUtils::calcCubicInverseTransposePowerBasisMatrix(P, &CIT);
Chris Dalton29011a22017-09-28 12:08:33 -0600323 SkASSERT(ExcludedTerm::kNonInvertible != skipTerm); // Should have been caught above.
Chris Dalton7f578bf2017-09-05 16:46:48 -0600324 SkASSERT(0 == CIT[6]);
325 SkASSERT(0 == CIT[7]);
326 SkASSERT(1 == CIT[8]);
327
328 // Each cubic has five different sections (not always inside t=[0..1]):
329 //
330 // 1. The section before the first inflection or loop intersection point, with padding.
331 // 2. The section that passes through the first inflection/intersection (aka the K,L
332 // intersection point or T=tt[0]/ss[0]).
333 // 3. The section between the two inflections/intersections, with padding.
334 // 4. The section that passes through the second inflection/intersection (aka the K,M
335 // intersection point or T=tt[1]/ss[1]).
336 // 5. The section after the second inflection/intersection, with padding.
337 //
338 // Sections 1,3,5 can be rendered directly using the CCPR cubic shader.
339 //
340 // Sections 2 & 4 must be approximated. For loop intersections we render them with
341 // quadratic(s), and when passing through an inflection point we use a plain old flat line.
342 //
343 // We find T0..T3 below to be the dividing points between these five sections.
344 float T0, T1, T2, T3;
345 if (SkCubicType::kLoop != fCurrCubicType) {
346 Sk2f t = Sk2f(static_cast<float>(tt[0]), static_cast<float>(tt[1]));
347 Sk2f s = Sk2f(static_cast<float>(ss[0]), static_cast<float>(ss[1]));
348 Sk2f pad = calc_inflect_homogeneous_padding(inflectPad, t, s, CIT, skipTerm);
349
350 float T[2];
351 ((t - pad) / s).store(T);
352 T0 = T[0];
353 T2 = T[1];
354
355 ((t + pad) / s).store(T);
356 T1 = T[0];
357 T3 = T[1];
358 } else {
359 const float T[2] = {static_cast<float>(tt[0]/ss[0]), static_cast<float>(tt[1]/ss[1])};
360 SkSTArray<3, float, true> roots[2];
361 calc_loop_intersect_padding_pts(loopIntersectPad, Sk2f::Load(T), CIT, skipTerm, roots);
362 T0 = roots[0].front();
363 if (1 == roots[0].count() || 1 == roots[1].count()) {
364 // The loop is tighter than our desired padding. Collapse the middle section to a point
365 // somewhere in the middle-ish of the loop and Sections 2 & 4 will approximate the the
366 // whole thing with quadratics.
367 T1 = T2 = (T[0] + T[1]) * .5f;
368 } else {
369 T1 = roots[0][1];
370 T2 = roots[1][1];
371 }
372 T3 = roots[1].back();
373 }
374
375 // Guarantee that T0..T3 are monotonic.
376 if (T0 > T3) {
377 // This is not a mathematically valid scenario. The only reason it would happen is if
378 // padding is very small and we have encountered FP rounding error.
379 T0 = T1 = T2 = T3 = (T0 + T3) / 2;
380 } else if (T1 > T2) {
381 // This just means padding before the middle section overlaps the padding after it. We
382 // collapse the middle section to a single point that splits the difference between the
383 // overlap in padding.
384 T1 = T2 = (T1 + T2) / 2;
385 }
386 // Clamp T1 & T2 inside T0..T3. The only reason this would be necessary is if we have
387 // encountered FP rounding error.
388 T1 = std::max(T0, std::min(T1, T3));
389 T2 = std::max(T0, std::min(T2, T3));
390
391 // Next we chop the cubic up at all T0..T3 inside 0..1 and store the resulting segments.
392 if (T1 >= 1) {
393 // Only sections 1 & 2 can be in 0..1.
Chris Dalton383a2ef2018-01-08 17:21:41 -0500394 this->chopCubic<&GrCCGeometry::appendMonotonicCubics,
395 &GrCCGeometry::appendCubicApproximation>(p0, p1, p2, p3, T0);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600396 return;
397 }
398
399 if (T2 <= 0) {
400 // Only sections 4 & 5 can be in 0..1.
Chris Dalton383a2ef2018-01-08 17:21:41 -0500401 this->chopCubic<&GrCCGeometry::appendCubicApproximation,
402 &GrCCGeometry::appendMonotonicCubics>(p0, p1, p2, p3, T3);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600403 return;
404 }
405
406 Sk2f midp0, midp1; // These hold the first two bezier points of the middle section, if needed.
407
408 if (T1 > 0) {
409 Sk2f T1T1 = Sk2f(T1);
410 Sk2f ab1 = lerp(p0, p1, T1T1);
411 Sk2f bc1 = lerp(p1, p2, T1T1);
412 Sk2f cd1 = lerp(p2, p3, T1T1);
413 Sk2f abc1 = lerp(ab1, bc1, T1T1);
414 Sk2f bcd1 = lerp(bc1, cd1, T1T1);
415 Sk2f abcd1 = lerp(abc1, bcd1, T1T1);
416
417 // Sections 1 & 2.
Chris Dalton383a2ef2018-01-08 17:21:41 -0500418 this->chopCubic<&GrCCGeometry::appendMonotonicCubics,
419 &GrCCGeometry::appendCubicApproximation>(p0, ab1, abc1, abcd1, T0/T1);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600420
421 if (T2 >= 1) {
422 // The rest of the curve is Section 3 (middle section).
423 this->appendMonotonicCubics(abcd1, bcd1, cd1, p3);
424 return;
Chris Daltonc1e59632017-09-05 00:30:07 -0600425 }
426
Chris Dalton7f578bf2017-09-05 16:46:48 -0600427 // Now calculate the first two bezier points of the middle section. The final two will come
428 // from when we chop the other side, as that is numerically more stable.
429 midp0 = abcd1;
430 midp1 = lerp(abcd1, bcd1, Sk2f((T2 - T1) / (1 - T1)));
431 } else if (T2 >= 1) {
432 // The entire cubic is Section 3 (middle section).
433 this->appendMonotonicCubics(p0, p1, p2, p3);
434 return;
Chris Daltonc1e59632017-09-05 00:30:07 -0600435 }
436
Chris Dalton7f578bf2017-09-05 16:46:48 -0600437 SkASSERT(T2 > 0 && T2 < 1);
438
439 Sk2f T2T2 = Sk2f(T2);
440 Sk2f ab2 = lerp(p0, p1, T2T2);
441 Sk2f bc2 = lerp(p1, p2, T2T2);
442 Sk2f cd2 = lerp(p2, p3, T2T2);
443 Sk2f abc2 = lerp(ab2, bc2, T2T2);
444 Sk2f bcd2 = lerp(bc2, cd2, T2T2);
445 Sk2f abcd2 = lerp(abc2, bcd2, T2T2);
446
447 if (T1 <= 0) {
448 // The curve begins at Section 3 (middle section).
449 this->appendMonotonicCubics(p0, ab2, abc2, abcd2);
450 } else if (T2 > T1) {
451 // Section 3 (middle section).
452 Sk2f midp2 = lerp(abc2, abcd2, T1/T2);
453 this->appendMonotonicCubics(midp0, midp1, midp2, abcd2);
454 }
455
456 // Sections 4 & 5.
Chris Dalton383a2ef2018-01-08 17:21:41 -0500457 this->chopCubic<&GrCCGeometry::appendCubicApproximation,
458 &GrCCGeometry::appendMonotonicCubics>(abcd2, bcd2, cd2, p3, (T3-T2) / (1-T2));
Chris Daltonc1e59632017-09-05 00:30:07 -0600459}
460
Chris Dalton383a2ef2018-01-08 17:21:41 -0500461template<GrCCGeometry::AppendCubicFn AppendLeftRight>
462inline void GrCCGeometry::chopCubicAtMidTangent(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
463 const Sk2f& p3, const Sk2f& tan0,
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600464 const Sk2f& tan1, int maxFutureSubdivisions) {
465 // Find the T value whose tangent is perpendicular to the vector that bisects tan0 and -tan1.
466 Sk2f n = normalize(tan0) - normalize(tan1);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600467
468 float a = 3 * dot(p3 + (p1 - p2)*3 - p0, n);
469 float b = 6 * dot(p0 - p1*2 + p2, n);
470 float c = 3 * dot(p1 - p0, n);
471
472 float discr = b*b - 4*a*c;
473 if (discr < 0) {
474 // If this is the case then the cubic must be nearly flat.
475 (this->*AppendLeftRight)(p0, p1, p2, p3, maxFutureSubdivisions);
476 return;
477 }
478
479 float q = -.5f * (b + copysignf(std::sqrt(discr), b));
480 float m = .5f*q*a;
481 float T = std::abs(q*q - m) < std::abs(a*c - m) ? q/a : c/q;
482
483 this->chopCubic<AppendLeftRight, AppendLeftRight>(p0, p1, p2, p3, T, maxFutureSubdivisions);
484}
485
Chris Dalton383a2ef2018-01-08 17:21:41 -0500486template<GrCCGeometry::AppendCubicFn AppendLeft, GrCCGeometry::AppendCubicFn AppendRight>
487inline void GrCCGeometry::chopCubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
488 const Sk2f& p3, float T, int maxFutureSubdivisions) {
Chris Dalton7f578bf2017-09-05 16:46:48 -0600489 if (T >= 1) {
490 (this->*AppendLeft)(p0, p1, p2, p3, maxFutureSubdivisions);
491 return;
492 }
493
494 if (T <= 0) {
495 (this->*AppendRight)(p0, p1, p2, p3, maxFutureSubdivisions);
496 return;
497 }
498
499 Sk2f TT = T;
500 Sk2f ab = lerp(p0, p1, TT);
501 Sk2f bc = lerp(p1, p2, TT);
502 Sk2f cd = lerp(p2, p3, TT);
503 Sk2f abc = lerp(ab, bc, TT);
504 Sk2f bcd = lerp(bc, cd, TT);
505 Sk2f abcd = lerp(abc, bcd, TT);
506 (this->*AppendLeft)(p0, ab, abc, abcd, maxFutureSubdivisions);
507 (this->*AppendRight)(abcd, bcd, cd, p3, maxFutureSubdivisions);
508}
509
Chris Dalton383a2ef2018-01-08 17:21:41 -0500510void GrCCGeometry::appendMonotonicCubics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
511 const Sk2f& p3, int maxSubdivisions) {
Chris Dalton29011a22017-09-28 12:08:33 -0600512 SkASSERT(maxSubdivisions >= 0);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600513 if ((p0 == p3).allTrue()) {
514 return;
515 }
516
517 if (maxSubdivisions) {
518 Sk2f tan0 = first_unless_nearly_zero(p1 - p0, p2 - p0);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600519 Sk2f tan1 = first_unless_nearly_zero(p3 - p2, p3 - p1);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600520
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600521 if (!is_convex_curve_monotonic(p0, tan0, p3, tan1)) {
Chris Dalton383a2ef2018-01-08 17:21:41 -0500522 this->chopCubicAtMidTangent<&GrCCGeometry::appendMonotonicCubics>(p0, p1, p2, p3,
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600523 tan0, tan1,
Chris Dalton383a2ef2018-01-08 17:21:41 -0500524 maxSubdivisions - 1);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600525 return;
526 }
527 }
528
529 SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
Chris Dalton43646532017-12-07 12:47:02 -0700530
531 // Don't send curves to the GPU if we know they are nearly flat (or just very small).
532 // Since the cubic segment is known to be convex at this point, our flatness check is simple.
533 if (are_collinear(p0, (p1 + p2) * .5f, p3)) {
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600534 this->appendLine(p3);
Chris Dalton43646532017-12-07 12:47:02 -0700535 return;
536 }
537
Chris Dalton7f578bf2017-09-05 16:46:48 -0600538 p1.store(&fPoints.push_back());
539 p2.store(&fPoints.push_back());
540 p3.store(&fPoints.push_back());
Chris Daltonbe4ffab2017-12-08 10:59:58 -0700541 fVerbs.push_back(Verb::kMonotonicCubicTo);
542 ++fCurrContourTallies.fCubics;
Chris Daltonc1e59632017-09-05 00:30:07 -0600543}
544
Chris Dalton383a2ef2018-01-08 17:21:41 -0500545void GrCCGeometry::appendCubicApproximation(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
546 const Sk2f& p3, int maxSubdivisions) {
Chris Dalton29011a22017-09-28 12:08:33 -0600547 SkASSERT(maxSubdivisions >= 0);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600548 if ((p0 == p3).allTrue()) {
549 return;
550 }
551
552 if (SkCubicType::kLoop != fCurrCubicType && SkCubicType::kQuadratic != fCurrCubicType) {
553 // This section passes through an inflection point, so we can get away with a flat line.
554 // This can cause some curves to feel slightly more flat when inspected rigorously back and
555 // forth against another renderer, but for now this seems acceptable given the simplicity.
556 SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600557 this->appendLine(p3);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600558 return;
559 }
560
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600561 Sk2f tan0, tan1, c;
562 if (!is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, c) && maxSubdivisions) {
Chris Dalton383a2ef2018-01-08 17:21:41 -0500563 this->chopCubicAtMidTangent<&GrCCGeometry::appendCubicApproximation>(p0, p1, p2, p3,
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600564 tan0, tan1,
Chris Dalton383a2ef2018-01-08 17:21:41 -0500565 maxSubdivisions - 1);
Chris Dalton29011a22017-09-28 12:08:33 -0600566 return;
Chris Dalton7f578bf2017-09-05 16:46:48 -0600567 }
568
Chris Dalton43646532017-12-07 12:47:02 -0700569 if (maxSubdivisions) {
570 this->appendMonotonicQuadratics(p0, c, p3);
571 } else {
572 this->appendSingleMonotonicQuadratic(p0, c, p3);
573 }
Chris Dalton7f578bf2017-09-05 16:46:48 -0600574}
575
Chris Dalton383a2ef2018-01-08 17:21:41 -0500576GrCCGeometry::PrimitiveTallies GrCCGeometry::endContour() {
Chris Daltonc1e59632017-09-05 00:30:07 -0600577 SkASSERT(fBuildingContour);
578 SkASSERT(fVerbs.count() >= fCurrContourTallies.fTriangles);
579
580 // The fTriangles field currently contains this contour's starting verb index. We can now
581 // use it to calculate the size of the contour's fan.
582 int fanSize = fVerbs.count() - fCurrContourTallies.fTriangles;
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600583 if (fPoints.back() == fCurrAnchorPoint) {
Chris Daltonc1e59632017-09-05 00:30:07 -0600584 --fanSize;
585 fVerbs.push_back(Verb::kEndClosedContour);
586 } else {
587 fVerbs.push_back(Verb::kEndOpenContour);
588 }
589
590 fCurrContourTallies.fTriangles = SkTMax(fanSize - 2, 0);
591
Chris Dalton383a2ef2018-01-08 17:21:41 -0500592 SkDEBUGCODE(fBuildingContour = false);
Chris Daltonc1e59632017-09-05 00:30:07 -0600593 return fCurrContourTallies;
Chris Dalton419a94d2017-08-28 10:24:22 -0600594}