blob: 302cfe2f2e17329f8b641ba4f950e01dc2116089 [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 Dalton9f2dab02018-04-18 14:07:03 -060030 fCurrContourTallies = {fVerbs.count(), 0, 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 Daltonb0601a42018-04-10 00:23:45 -060061static inline bool are_collinear(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
62 float tolerance = 1/16.f) { // 1/16 of a pixel.
63 Sk2f l = p2 - p0; // Line from p0 -> p2.
Chris Dalton900cd052017-09-07 10:36:51 -060064
Chris Daltonb0601a42018-04-10 00:23:45 -060065 // lwidth = Manhattan width of l.
66 Sk2f labs = l.abs();
67 float lwidth = labs[0] + labs[1];
Chris Dalton900cd052017-09-07 10:36:51 -060068
Chris Daltonb0601a42018-04-10 00:23:45 -060069 // d = |p1 - p0| dot | l.y|
70 // |-l.x| = distance from p1 to l.
71 Sk2f dd = (p1 - p0) * SkNx_shuffle<1,0>(l);
72 float d = dd[0] - dd[1];
Chris Dalton900cd052017-09-07 10:36:51 -060073
Chris Daltonb0601a42018-04-10 00:23:45 -060074 // We are collinear if a box with radius "tolerance", centered on p1, touches the line l.
75 // To decide this, we check if the distance from p1 to the line is less than the distance from
76 // p1 to the far corner of this imaginary box, along that same normal vector.
77 // The far corner of the box can be found at "p1 + sign(n) * tolerance", where n is normal to l:
78 //
79 // abs(dot(p1 - p0, n)) <= dot(sign(n) * tolerance, n)
80 //
81 // Which reduces to:
82 //
83 // abs(d) <= (n.x * sign(n.x) + n.y * sign(n.y)) * tolerance
84 // abs(d) <= (abs(n.x) + abs(n.y)) * tolerance
85 //
86 // Use "<=" in case l == 0.
87 return std::abs(d) <= lwidth * tolerance;
88}
89
90static inline bool are_collinear(const SkPoint P[4], float tolerance = 1/16.f) { // 1/16 of a pixel.
91 Sk4f Px, Py; // |Px Py| |p0 - p3|
92 Sk4f::Load2(P, &Px, &Py); // |. . | = |p1 - p3|
93 Px -= Px[3]; // |. . | |p2 - p3|
94 Py -= Py[3]; // |. . | | 0 |
95
96 // Find [lx, ly] = the line from p3 to the furthest-away point from p3.
97 Sk4f Pwidth = Px.abs() + Py.abs(); // Pwidth = Manhattan width of each point.
98 int lidx = Pwidth[0] > Pwidth[1] ? 0 : 1;
99 lidx = Pwidth[lidx] > Pwidth[2] ? lidx : 2;
100 float lx = Px[lidx], ly = Py[lidx];
101 float lwidth = Pwidth[lidx]; // lwidth = Manhattan width of [lx, ly].
102
103 // |Px Py|
104 // d = |. . | * | ly| = distances from each point to l (two of the distances will be zero).
105 // |. . | |-lx|
106 // |. . |
107 Sk4f d = Px*ly - Py*lx;
108
109 // We are collinear if boxes with radius "tolerance", centered on all 4 points all touch line l.
110 // (See the rationale for this formula in the above, 3-point version of this function.)
111 // Use "<=" in case l == 0.
112 return (d.abs() <= lwidth * tolerance).allTrue();
Chris Dalton900cd052017-09-07 10:36:51 -0600113}
114
Chris Dalton419a94d2017-08-28 10:24:22 -0600115// Returns whether the (convex) curve segment is monotonic with respect to [endPt - startPt].
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600116static inline bool is_convex_curve_monotonic(const Sk2f& startPt, const Sk2f& tan0,
117 const Sk2f& endPt, const Sk2f& tan1) {
Chris Dalton419a94d2017-08-28 10:24:22 -0600118 Sk2f v = endPt - startPt;
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600119 float dot0 = dot(tan0, v);
120 float dot1 = dot(tan1, v);
Chris Dalton419a94d2017-08-28 10:24:22 -0600121
122 // A small, negative tolerance handles floating-point error in the case when one tangent
123 // approaches 0 length, meaning the (convex) curve segment is effectively a flat line.
124 float tolerance = -std::max(std::abs(dot0), std::abs(dot1)) * SK_ScalarNearlyZero;
125 return dot0 >= tolerance && dot1 >= tolerance;
126}
127
Chris Dalton9f2dab02018-04-18 14:07:03 -0600128template<int N> static inline SkNx<N,float> lerp(const SkNx<N,float>& a, const SkNx<N,float>& b,
129 const SkNx<N,float>& t) {
Chris Dalton419a94d2017-08-28 10:24:22 -0600130 return SkNx_fma(t, b - a, a);
131}
132
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600133void GrCCGeometry::quadraticTo(const SkPoint P[3]) {
Chris Daltonc1e59632017-09-05 00:30:07 -0600134 SkASSERT(fBuildingContour);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600135 SkASSERT(P[0] == fPoints.back());
136 Sk2f p0 = Sk2f::Load(P);
137 Sk2f p1 = Sk2f::Load(P+1);
138 Sk2f p2 = Sk2f::Load(P+2);
Chris Daltonc1e59632017-09-05 00:30:07 -0600139
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600140 // Don't crunch on the curve if it is nearly flat (or just very small). Flat curves can break
141 // The monotonic chopping math.
142 if (are_collinear(p0, p1, p2)) {
143 this->appendLine(p2);
144 return;
145 }
Chris Dalton419a94d2017-08-28 10:24:22 -0600146
Chris Dalton29011a22017-09-28 12:08:33 -0600147 this->appendMonotonicQuadratics(p0, p1, p2);
148}
149
Chris Dalton383a2ef2018-01-08 17:21:41 -0500150inline void GrCCGeometry::appendMonotonicQuadratics(const Sk2f& p0, const Sk2f& p1,
151 const Sk2f& p2) {
Chris Dalton419a94d2017-08-28 10:24:22 -0600152 Sk2f tan0 = p1 - p0;
153 Sk2f tan1 = p2 - p1;
Chris Dalton29011a22017-09-28 12:08:33 -0600154
Chris Dalton419a94d2017-08-28 10:24:22 -0600155 // This should almost always be this case for well-behaved curves in the real world.
Chris Dalton43646532017-12-07 12:47:02 -0700156 if (is_convex_curve_monotonic(p0, tan0, p2, tan1)) {
157 this->appendSingleMonotonicQuadratic(p0, p1, p2);
Chris Daltonc1e59632017-09-05 00:30:07 -0600158 return;
Chris Dalton419a94d2017-08-28 10:24:22 -0600159 }
160
161 // 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 -0600162 // tangent angle is halfway between tan0 and tan1.
Chris Dalton419a94d2017-08-28 10:24:22 -0600163 Sk2f n = normalize(tan0) - normalize(tan1);
164
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600165 // The midtangent can be found where (dQ(t) dot n) = 0:
Chris Dalton419a94d2017-08-28 10:24:22 -0600166 //
167 // 0 = (dQ(t) dot n) = | 2*t 1 | * | p0 - 2*p1 + p2 | * | n |
168 // | -2*p0 + 2*p1 | | . |
169 //
170 // = | 2*t 1 | * | tan1 - tan0 | * | n |
171 // | 2*tan0 | | . |
172 //
173 // = 2*t * ((tan1 - tan0) dot n) + (2*tan0 dot n)
174 //
175 // t = (tan0 dot n) / ((tan0 - tan1) dot n)
176 Sk2f dQ1n = (tan0 - tan1) * n;
177 Sk2f dQ0n = tan0 * n;
178 Sk2f t = (dQ0n + SkNx_shuffle<1,0>(dQ0n)) / (dQ1n + SkNx_shuffle<1,0>(dQ1n));
179 t = Sk2f::Min(Sk2f::Max(t, 0), 1); // Clamp for FP error.
180
181 Sk2f p01 = SkNx_fma(t, tan0, p0);
182 Sk2f p12 = SkNx_fma(t, tan1, p1);
183 Sk2f p012 = lerp(p01, p12, t);
184
Chris Dalton43646532017-12-07 12:47:02 -0700185 this->appendSingleMonotonicQuadratic(p0, p01, p012);
186 this->appendSingleMonotonicQuadratic(p012, p12, p2);
187}
188
Chris Dalton383a2ef2018-01-08 17:21:41 -0500189inline void GrCCGeometry::appendSingleMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1,
190 const Sk2f& p2) {
Chris Dalton43646532017-12-07 12:47:02 -0700191 SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
192
193 // Don't send curves to the GPU if we know they are nearly flat (or just very small).
194 if (are_collinear(p0, p1, p2)) {
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600195 this->appendLine(p2);
Chris Dalton43646532017-12-07 12:47:02 -0700196 return;
197 }
198
199 p1.store(&fPoints.push_back());
Chris Daltonc1e59632017-09-05 00:30:07 -0600200 p2.store(&fPoints.push_back());
Chris Dalton43646532017-12-07 12:47:02 -0700201 fVerbs.push_back(Verb::kMonotonicQuadraticTo);
202 ++fCurrContourTallies.fQuadratics;
Chris Daltonc1e59632017-09-05 00:30:07 -0600203}
204
Chris Dalton7f578bf2017-09-05 16:46:48 -0600205using ExcludedTerm = GrPathUtils::ExcludedTerm;
Chris Daltonc1e59632017-09-05 00:30:07 -0600206
Chris Dalton7f578bf2017-09-05 16:46:48 -0600207// Calculates the padding to apply around inflection points, in homogeneous parametric coordinates.
208//
209// More specifically, if the inflection point lies at C(t/s), then C((t +/- returnValue) / s) will
210// be the two points on the curve at which a square box with radius "padRadius" will have a corner
211// that touches the inflection point's tangent line.
212//
213// A serpentine cubic has two inflection points, so this method takes Sk2f and computes the padding
214// for both in SIMD.
215static inline Sk2f calc_inflect_homogeneous_padding(float padRadius, const Sk2f& t, const Sk2f& s,
216 const SkMatrix& CIT, ExcludedTerm skipTerm) {
217 SkASSERT(padRadius >= 0);
Chris Daltonc1e59632017-09-05 00:30:07 -0600218
Chris Dalton7f578bf2017-09-05 16:46:48 -0600219 Sk2f Clx = s*s*s;
220 Sk2f Cly = (ExcludedTerm::kLinearTerm == skipTerm) ? s*s*t*-3 : s*t*t*3;
221
222 Sk2f Lx = CIT[0] * Clx + CIT[3] * Cly;
223 Sk2f Ly = CIT[1] * Clx + CIT[4] * Cly;
224
225 float ret[2];
226 Sk2f bloat = padRadius * (Lx.abs() + Ly.abs());
227 (bloat * s >= 0).thenElse(bloat, -bloat).store(ret);
228
229 ret[0] = cbrtf(ret[0]);
230 ret[1] = cbrtf(ret[1]);
231 return Sk2f::Load(ret);
232}
233
234static inline void swap_if_greater(float& a, float& b) {
235 if (a > b) {
236 std::swap(a, b);
237 }
238}
239
240// Calculates all parameter values for a loop at which points a square box with radius "padRadius"
241// will have a corner that touches a tangent line from the intersection.
242//
243// T2 must contain the lesser parameter value of the loop intersection in its first component, and
244// the greater in its second.
245//
246// roots[0] will be filled with 1 or 3 sorted parameter values, representing the padding points
247// around the first tangent. roots[1] will be filled with the padding points for the second tangent.
248static inline void calc_loop_intersect_padding_pts(float padRadius, const Sk2f& T2,
249 const SkMatrix& CIT, ExcludedTerm skipTerm,
250 SkSTArray<3, float, true> roots[2]) {
251 SkASSERT(padRadius >= 0);
252 SkASSERT(T2[0] <= T2[1]);
253 SkASSERT(roots[0].empty());
254 SkASSERT(roots[1].empty());
255
256 Sk2f T1 = SkNx_shuffle<1,0>(T2);
257 Sk2f Cl = (ExcludedTerm::kLinearTerm == skipTerm) ? T2*-2 - T1 : T2*T2 + T2*T1*2;
258 Sk2f Lx = Cl * CIT[3] + CIT[0];
259 Sk2f Ly = Cl * CIT[4] + CIT[1];
260
261 Sk2f bloat = Sk2f(+.5f * padRadius, -.5f * padRadius) * (Lx.abs() + Ly.abs());
262 Sk2f q = (1.f/3) * (T2 - T1);
263
264 Sk2f qqq = q*q*q;
265 Sk2f discr = qqq*bloat*2 + bloat*bloat;
266
267 float numRoots[2], D[2];
268 (discr < 0).thenElse(3, 1).store(numRoots);
269 (T2 - q).store(D);
270
271 // Values for calculating one root.
272 float R[2], QQ[2];
273 if ((discr >= 0).anyTrue()) {
274 Sk2f r = qqq + bloat;
275 Sk2f s = r.abs() + discr.sqrt();
276 (r > 0).thenElse(-s, s).store(R);
277 (q*q).store(QQ);
Chris Daltonc1e59632017-09-05 00:30:07 -0600278 }
279
Chris Dalton7f578bf2017-09-05 16:46:48 -0600280 // Values for calculating three roots.
281 float P[2], cosTheta3[2];
282 if ((discr < 0).anyTrue()) {
283 (q.abs() * -2).store(P);
284 ((q >= 0).thenElse(1, -1) + bloat / qqq.abs()).store(cosTheta3);
Chris Daltonc1e59632017-09-05 00:30:07 -0600285 }
286
Chris Dalton7f578bf2017-09-05 16:46:48 -0600287 for (int i = 0; i < 2; ++i) {
288 if (1 == numRoots[i]) {
289 float A = cbrtf(R[i]);
290 float B = A != 0 ? QQ[i]/A : 0;
291 roots[i].push_back(A + B + D[i]);
Chris Daltonc1e59632017-09-05 00:30:07 -0600292 continue;
293 }
294
Chris Dalton7f578bf2017-09-05 16:46:48 -0600295 static constexpr float k2PiOver3 = 2 * SK_ScalarPI / 3;
296 float theta = std::acos(cosTheta3[i]) * (1.f/3);
297 roots[i].push_back(P[i] * std::cos(theta) + D[i]);
298 roots[i].push_back(P[i] * std::cos(theta + k2PiOver3) + D[i]);
299 roots[i].push_back(P[i] * std::cos(theta - k2PiOver3) + D[i]);
Chris Daltonc1e59632017-09-05 00:30:07 -0600300
Chris Dalton7f578bf2017-09-05 16:46:48 -0600301 // Sort the three roots.
302 swap_if_greater(roots[i][0], roots[i][1]);
303 swap_if_greater(roots[i][1], roots[i][2]);
304 swap_if_greater(roots[i][0], roots[i][1]);
305 }
306}
307
Chris Dalton29011a22017-09-28 12:08:33 -0600308static inline Sk2f first_unless_nearly_zero(const Sk2f& a, const Sk2f& b) {
309 Sk2f aa = a*a;
310 aa += SkNx_shuffle<1,0>(aa);
311 SkASSERT(aa[0] == aa[1]);
312
313 Sk2f bb = b*b;
314 bb += SkNx_shuffle<1,0>(bb);
315 SkASSERT(bb[0] == bb[1]);
316
317 return (aa > bb * SK_ScalarNearlyZero).thenElse(a, b);
318}
319
320static inline bool is_cubic_nearly_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600321 const Sk2f& p3, Sk2f& tan0, Sk2f& tan1, Sk2f& c) {
Chris Dalton29011a22017-09-28 12:08:33 -0600322 tan0 = first_unless_nearly_zero(p1 - p0, p2 - p0);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600323 tan1 = first_unless_nearly_zero(p3 - p2, p3 - p1);
Chris Dalton29011a22017-09-28 12:08:33 -0600324
325 Sk2f c1 = SkNx_fma(Sk2f(1.5f), tan0, p0);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600326 Sk2f c2 = SkNx_fma(Sk2f(-1.5f), tan1, p3);
Chris Dalton29011a22017-09-28 12:08:33 -0600327 c = (c1 + c2) * .5f; // Hopefully optimized out if not used?
328
329 return ((c1 - c2).abs() <= 1).allTrue();
330}
331
Chris Dalton9f2dab02018-04-18 14:07:03 -0600332// Given a convex curve segment with the following order-2 tangent function:
333//
334// |C2x C2y|
335// tan = some_scale * |dx/dt dy/dt| = |t^2 t 1| * |C1x C1y|
336// |C0x C0y|
337//
338// This function finds the T value whose tangent angle is halfway between the tangents at T=0 and
339// T=1 (tan0 and tan1).
340static inline float find_midtangent(const Sk2f& tan0, const Sk2f& tan1,
341 float scale2, const Sk2f& C2,
342 float scale1, const Sk2f& C1,
343 float scale0, const Sk2f& C0) {
344 // Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the
345 // midtangent. 'n' will therefore bisect tan0 and -tan1, giving us the normal to the midtangent.
346 //
347 // n dot midtangent = 0
348 //
349 Sk2f n = normalize(tan0) - normalize(tan1);
350
351 // Find the T value at the midtangent. This is a simple quadratic equation:
352 //
353 // midtangent dot n = 0
354 //
355 // (|t^2 t 1| * C) dot n = 0
356 //
357 // |t^2 t 1| dot C*n = 0
358 //
359 // First find coeffs = C*n.
360 Sk4f C[2];
361 Sk2f::Store4(C, C2, C1, C0, 0);
362 Sk4f coeffs = C[0]*n[0] + C[1]*n[1];
363 if (1 != scale2 || 1 != scale1 || 1 != scale0) {
364 coeffs *= Sk4f(scale2, scale1, scale0, 0);
365 }
366
367 // Now solve the quadratic.
368 float a = coeffs[0], b = coeffs[1], c = coeffs[2];
369 float discr = b*b - 4*a*c;
370 if (discr < 0) {
371 return 0; // This will only happen if the curve is a line.
372 }
373
374 // The roots are q/a and c/q. Pick the one closer to T=.5.
375 float q = -.5f * (b + copysignf(std::sqrt(discr), b));
376 float r = .5f*q*a;
377 return std::abs(q*q - r) < std::abs(a*c - r) ? q/a : c/q;
378}
379
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600380void GrCCGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) {
Chris Dalton7f578bf2017-09-05 16:46:48 -0600381 SkASSERT(fBuildingContour);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600382 SkASSERT(P[0] == fPoints.back());
Chris Daltonb0601a42018-04-10 00:23:45 -0600383
384 // Don't crunch on the curve or inflate geometry if it is nearly flat (or just very small).
385 // Flat curves can break the math below.
386 if (are_collinear(P)) {
387 this->lineTo(P[3]);
388 return;
389 }
390
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600391 Sk2f p0 = Sk2f::Load(P);
392 Sk2f p1 = Sk2f::Load(P+1);
393 Sk2f p2 = Sk2f::Load(P+2);
394 Sk2f p3 = Sk2f::Load(P+3);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600395
Chris Dalton29011a22017-09-28 12:08:33 -0600396 // Also detect near-quadratics ahead of time.
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600397 Sk2f tan0, tan1, c;
398 if (is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, c)) {
Chris Dalton29011a22017-09-28 12:08:33 -0600399 this->appendMonotonicQuadratics(p0, c, p3);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600400 return;
401 }
402
Chris Dalton29011a22017-09-28 12:08:33 -0600403 double tt[2], ss[2];
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600404 fCurrCubicType = SkClassifyCubic(P, tt, ss);
Chris Dalton29011a22017-09-28 12:08:33 -0600405 SkASSERT(!SkCubicIsDegenerate(fCurrCubicType)); // Should have been caught above.
406
Chris Dalton7f578bf2017-09-05 16:46:48 -0600407 SkMatrix CIT;
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600408 ExcludedTerm skipTerm = GrPathUtils::calcCubicInverseTransposePowerBasisMatrix(P, &CIT);
Chris Dalton29011a22017-09-28 12:08:33 -0600409 SkASSERT(ExcludedTerm::kNonInvertible != skipTerm); // Should have been caught above.
Chris Dalton7f578bf2017-09-05 16:46:48 -0600410 SkASSERT(0 == CIT[6]);
411 SkASSERT(0 == CIT[7]);
412 SkASSERT(1 == CIT[8]);
413
414 // Each cubic has five different sections (not always inside t=[0..1]):
415 //
416 // 1. The section before the first inflection or loop intersection point, with padding.
417 // 2. The section that passes through the first inflection/intersection (aka the K,L
418 // intersection point or T=tt[0]/ss[0]).
419 // 3. The section between the two inflections/intersections, with padding.
420 // 4. The section that passes through the second inflection/intersection (aka the K,M
421 // intersection point or T=tt[1]/ss[1]).
422 // 5. The section after the second inflection/intersection, with padding.
423 //
424 // Sections 1,3,5 can be rendered directly using the CCPR cubic shader.
425 //
426 // Sections 2 & 4 must be approximated. For loop intersections we render them with
427 // quadratic(s), and when passing through an inflection point we use a plain old flat line.
428 //
429 // We find T0..T3 below to be the dividing points between these five sections.
430 float T0, T1, T2, T3;
431 if (SkCubicType::kLoop != fCurrCubicType) {
432 Sk2f t = Sk2f(static_cast<float>(tt[0]), static_cast<float>(tt[1]));
433 Sk2f s = Sk2f(static_cast<float>(ss[0]), static_cast<float>(ss[1]));
434 Sk2f pad = calc_inflect_homogeneous_padding(inflectPad, t, s, CIT, skipTerm);
435
436 float T[2];
437 ((t - pad) / s).store(T);
438 T0 = T[0];
439 T2 = T[1];
440
441 ((t + pad) / s).store(T);
442 T1 = T[0];
443 T3 = T[1];
444 } else {
445 const float T[2] = {static_cast<float>(tt[0]/ss[0]), static_cast<float>(tt[1]/ss[1])};
446 SkSTArray<3, float, true> roots[2];
447 calc_loop_intersect_padding_pts(loopIntersectPad, Sk2f::Load(T), CIT, skipTerm, roots);
448 T0 = roots[0].front();
449 if (1 == roots[0].count() || 1 == roots[1].count()) {
450 // The loop is tighter than our desired padding. Collapse the middle section to a point
451 // somewhere in the middle-ish of the loop and Sections 2 & 4 will approximate the the
452 // whole thing with quadratics.
453 T1 = T2 = (T[0] + T[1]) * .5f;
454 } else {
455 T1 = roots[0][1];
456 T2 = roots[1][1];
457 }
458 T3 = roots[1].back();
459 }
460
461 // Guarantee that T0..T3 are monotonic.
462 if (T0 > T3) {
463 // This is not a mathematically valid scenario. The only reason it would happen is if
464 // padding is very small and we have encountered FP rounding error.
465 T0 = T1 = T2 = T3 = (T0 + T3) / 2;
466 } else if (T1 > T2) {
467 // This just means padding before the middle section overlaps the padding after it. We
468 // collapse the middle section to a single point that splits the difference between the
469 // overlap in padding.
470 T1 = T2 = (T1 + T2) / 2;
471 }
472 // Clamp T1 & T2 inside T0..T3. The only reason this would be necessary is if we have
473 // encountered FP rounding error.
474 T1 = std::max(T0, std::min(T1, T3));
475 T2 = std::max(T0, std::min(T2, T3));
476
477 // Next we chop the cubic up at all T0..T3 inside 0..1 and store the resulting segments.
478 if (T1 >= 1) {
479 // Only sections 1 & 2 can be in 0..1.
Chris Dalton383a2ef2018-01-08 17:21:41 -0500480 this->chopCubic<&GrCCGeometry::appendMonotonicCubics,
481 &GrCCGeometry::appendCubicApproximation>(p0, p1, p2, p3, T0);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600482 return;
483 }
484
485 if (T2 <= 0) {
486 // Only sections 4 & 5 can be in 0..1.
Chris Dalton383a2ef2018-01-08 17:21:41 -0500487 this->chopCubic<&GrCCGeometry::appendCubicApproximation,
488 &GrCCGeometry::appendMonotonicCubics>(p0, p1, p2, p3, T3);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600489 return;
490 }
491
492 Sk2f midp0, midp1; // These hold the first two bezier points of the middle section, if needed.
493
494 if (T1 > 0) {
495 Sk2f T1T1 = Sk2f(T1);
496 Sk2f ab1 = lerp(p0, p1, T1T1);
497 Sk2f bc1 = lerp(p1, p2, T1T1);
498 Sk2f cd1 = lerp(p2, p3, T1T1);
499 Sk2f abc1 = lerp(ab1, bc1, T1T1);
500 Sk2f bcd1 = lerp(bc1, cd1, T1T1);
501 Sk2f abcd1 = lerp(abc1, bcd1, T1T1);
502
503 // Sections 1 & 2.
Chris Dalton383a2ef2018-01-08 17:21:41 -0500504 this->chopCubic<&GrCCGeometry::appendMonotonicCubics,
505 &GrCCGeometry::appendCubicApproximation>(p0, ab1, abc1, abcd1, T0/T1);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600506
507 if (T2 >= 1) {
508 // The rest of the curve is Section 3 (middle section).
509 this->appendMonotonicCubics(abcd1, bcd1, cd1, p3);
510 return;
Chris Daltonc1e59632017-09-05 00:30:07 -0600511 }
512
Chris Dalton7f578bf2017-09-05 16:46:48 -0600513 // Now calculate the first two bezier points of the middle section. The final two will come
514 // from when we chop the other side, as that is numerically more stable.
515 midp0 = abcd1;
516 midp1 = lerp(abcd1, bcd1, Sk2f((T2 - T1) / (1 - T1)));
517 } else if (T2 >= 1) {
518 // The entire cubic is Section 3 (middle section).
519 this->appendMonotonicCubics(p0, p1, p2, p3);
520 return;
Chris Daltonc1e59632017-09-05 00:30:07 -0600521 }
522
Chris Dalton7f578bf2017-09-05 16:46:48 -0600523 SkASSERT(T2 > 0 && T2 < 1);
524
525 Sk2f T2T2 = Sk2f(T2);
526 Sk2f ab2 = lerp(p0, p1, T2T2);
527 Sk2f bc2 = lerp(p1, p2, T2T2);
528 Sk2f cd2 = lerp(p2, p3, T2T2);
529 Sk2f abc2 = lerp(ab2, bc2, T2T2);
530 Sk2f bcd2 = lerp(bc2, cd2, T2T2);
531 Sk2f abcd2 = lerp(abc2, bcd2, T2T2);
532
533 if (T1 <= 0) {
534 // The curve begins at Section 3 (middle section).
535 this->appendMonotonicCubics(p0, ab2, abc2, abcd2);
536 } else if (T2 > T1) {
537 // Section 3 (middle section).
Chris Dalton9f2dab02018-04-18 14:07:03 -0600538 Sk2f midp2 = lerp(abc2, abcd2, Sk2f(T1/T2));
Chris Dalton7f578bf2017-09-05 16:46:48 -0600539 this->appendMonotonicCubics(midp0, midp1, midp2, abcd2);
540 }
541
542 // Sections 4 & 5.
Chris Dalton383a2ef2018-01-08 17:21:41 -0500543 this->chopCubic<&GrCCGeometry::appendCubicApproximation,
544 &GrCCGeometry::appendMonotonicCubics>(abcd2, bcd2, cd2, p3, (T3-T2) / (1-T2));
Chris Daltonc1e59632017-09-05 00:30:07 -0600545}
546
Chris Dalton383a2ef2018-01-08 17:21:41 -0500547template<GrCCGeometry::AppendCubicFn AppendLeftRight>
548inline void GrCCGeometry::chopCubicAtMidTangent(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
549 const Sk2f& p3, const Sk2f& tan0,
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600550 const Sk2f& tan1, int maxFutureSubdivisions) {
Chris Dalton9f2dab02018-04-18 14:07:03 -0600551 float midT = find_midtangent(tan0, tan1, 3, p3 + (p1 - p2)*3 - p0,
552 6, p0 - p1*2 + p2,
553 3, p1 - p0);
554 // Use positive logic since NaN fails comparisons. (However midT should not be NaN since we cull
555 // near-flat cubics in cubicTo().)
556 if (!(midT > 0 && midT < 1)) {
557 // The cubic is flat. Otherwise there would be a real midtangent inside T=0..1.
558 this->appendLine(p3);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600559 return;
560 }
561
Chris Dalton9f2dab02018-04-18 14:07:03 -0600562 this->chopCubic<AppendLeftRight, AppendLeftRight>(p0, p1, p2, p3, midT, maxFutureSubdivisions);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600563}
564
Chris Dalton383a2ef2018-01-08 17:21:41 -0500565template<GrCCGeometry::AppendCubicFn AppendLeft, GrCCGeometry::AppendCubicFn AppendRight>
566inline void GrCCGeometry::chopCubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
567 const Sk2f& p3, float T, int maxFutureSubdivisions) {
Chris Dalton7f578bf2017-09-05 16:46:48 -0600568 if (T >= 1) {
569 (this->*AppendLeft)(p0, p1, p2, p3, maxFutureSubdivisions);
570 return;
571 }
572
573 if (T <= 0) {
574 (this->*AppendRight)(p0, p1, p2, p3, maxFutureSubdivisions);
575 return;
576 }
577
578 Sk2f TT = T;
579 Sk2f ab = lerp(p0, p1, TT);
580 Sk2f bc = lerp(p1, p2, TT);
581 Sk2f cd = lerp(p2, p3, TT);
582 Sk2f abc = lerp(ab, bc, TT);
583 Sk2f bcd = lerp(bc, cd, TT);
584 Sk2f abcd = lerp(abc, bcd, TT);
585 (this->*AppendLeft)(p0, ab, abc, abcd, maxFutureSubdivisions);
586 (this->*AppendRight)(abcd, bcd, cd, p3, maxFutureSubdivisions);
587}
588
Chris Dalton383a2ef2018-01-08 17:21:41 -0500589void GrCCGeometry::appendMonotonicCubics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
590 const Sk2f& p3, int maxSubdivisions) {
Chris Dalton29011a22017-09-28 12:08:33 -0600591 SkASSERT(maxSubdivisions >= 0);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600592 if ((p0 == p3).allTrue()) {
593 return;
594 }
595
596 if (maxSubdivisions) {
597 Sk2f tan0 = first_unless_nearly_zero(p1 - p0, p2 - p0);
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600598 Sk2f tan1 = first_unless_nearly_zero(p3 - p2, p3 - p1);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600599
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600600 if (!is_convex_curve_monotonic(p0, tan0, p3, tan1)) {
Chris Dalton383a2ef2018-01-08 17:21:41 -0500601 this->chopCubicAtMidTangent<&GrCCGeometry::appendMonotonicCubics>(p0, p1, p2, p3,
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600602 tan0, tan1,
Chris Dalton383a2ef2018-01-08 17:21:41 -0500603 maxSubdivisions - 1);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600604 return;
605 }
606 }
607
608 SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
Chris Dalton43646532017-12-07 12:47:02 -0700609
610 // Don't send curves to the GPU if we know they are nearly flat (or just very small).
611 // Since the cubic segment is known to be convex at this point, our flatness check is simple.
612 if (are_collinear(p0, (p1 + p2) * .5f, p3)) {
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600613 this->appendLine(p3);
Chris Dalton43646532017-12-07 12:47:02 -0700614 return;
615 }
616
Chris Dalton7f578bf2017-09-05 16:46:48 -0600617 p1.store(&fPoints.push_back());
618 p2.store(&fPoints.push_back());
619 p3.store(&fPoints.push_back());
Chris Daltonbe4ffab2017-12-08 10:59:58 -0700620 fVerbs.push_back(Verb::kMonotonicCubicTo);
621 ++fCurrContourTallies.fCubics;
Chris Daltonc1e59632017-09-05 00:30:07 -0600622}
623
Chris Dalton383a2ef2018-01-08 17:21:41 -0500624void GrCCGeometry::appendCubicApproximation(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
625 const Sk2f& p3, int maxSubdivisions) {
Chris Dalton29011a22017-09-28 12:08:33 -0600626 SkASSERT(maxSubdivisions >= 0);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600627 if ((p0 == p3).allTrue()) {
628 return;
629 }
630
631 if (SkCubicType::kLoop != fCurrCubicType && SkCubicType::kQuadratic != fCurrCubicType) {
632 // This section passes through an inflection point, so we can get away with a flat line.
633 // This can cause some curves to feel slightly more flat when inspected rigorously back and
634 // forth against another renderer, but for now this seems acceptable given the simplicity.
635 SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600636 this->appendLine(p3);
Chris Dalton7f578bf2017-09-05 16:46:48 -0600637 return;
638 }
639
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600640 Sk2f tan0, tan1, c;
641 if (!is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, c) && maxSubdivisions) {
Chris Dalton383a2ef2018-01-08 17:21:41 -0500642 this->chopCubicAtMidTangent<&GrCCGeometry::appendCubicApproximation>(p0, p1, p2, p3,
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600643 tan0, tan1,
Chris Dalton383a2ef2018-01-08 17:21:41 -0500644 maxSubdivisions - 1);
Chris Dalton29011a22017-09-28 12:08:33 -0600645 return;
Chris Dalton7f578bf2017-09-05 16:46:48 -0600646 }
647
Chris Dalton43646532017-12-07 12:47:02 -0700648 if (maxSubdivisions) {
649 this->appendMonotonicQuadratics(p0, c, p3);
650 } else {
651 this->appendSingleMonotonicQuadratic(p0, c, p3);
652 }
Chris Dalton7f578bf2017-09-05 16:46:48 -0600653}
654
Chris Dalton9f2dab02018-04-18 14:07:03 -0600655void GrCCGeometry::conicTo(const SkPoint P[3], float w) {
656 SkASSERT(fBuildingContour);
657 SkASSERT(P[0] == fPoints.back());
658 Sk2f p0 = Sk2f::Load(P);
659 Sk2f p1 = Sk2f::Load(P+1);
660 Sk2f p2 = Sk2f::Load(P+2);
661
662 // Don't crunch on the curve if it is nearly flat (or just very small). Collinear control points
663 // can break the midtangent-finding math below.
664 if (are_collinear(p0, p1, p2)) {
665 this->appendLine(p2);
666 return;
667 }
668
669 Sk2f tan0 = p1 - p0;
670 Sk2f tan1 = p2 - p1;
671 // The derivative of a conic has a cumbersome order-4 denominator. However, this isn't necessary
672 // if we are only interested in a vector in the same *direction* as a given tangent line. Since
673 // the denominator scales dx and dy uniformly, we can throw it out completely after evaluating
674 // the derivative with the standard quotient rule. This leaves us with a simpler quadratic
675 // function that we use to find the midtangent.
676 float midT = find_midtangent(tan0, tan1, 1, (w - 1) * (p2 - p0),
677 1, (p2 - p0) - 2*w*(p1 - p0),
678 1, w*(p1 - p0));
679 // Use positive logic since NaN fails comparisons. (However midT should not be NaN since we cull
680 // near-linear conics above. And while w=0 is flat, it's not a line and has valid midtangents.)
681 if (!(midT > 0 && midT < 1)) {
682 // The conic is flat. Otherwise there would be a real midtangent inside T=0..1.
683 this->appendLine(p2);
684 return;
685 }
686
687 // Evaluate the conic at midT.
688 Sk4f p3d0 = Sk4f(p0[0], p0[1], 1, 0);
689 Sk4f p3d1 = Sk4f(p1[0], p1[1], 1, 0) * w;
690 Sk4f p3d2 = Sk4f(p2[0], p2[1], 1, 0);
691 Sk4f midT4 = midT;
692
693 Sk4f p3d01 = lerp(p3d0, p3d1, midT4);
694 Sk4f p3d12 = lerp(p3d1, p3d2, midT4);
695 Sk4f p3d012 = lerp(p3d01, p3d12, midT4);
696
697 Sk2f midpoint = Sk2f(p3d012[0], p3d012[1]) / p3d012[2];
698
699 if (are_collinear(p0, midpoint, p2, 1) || // Check if the curve is within one pixel of flat.
700 ((midpoint - p1).abs() < 1).allTrue()) { // Check if the curve is almost a triangle.
701 // Draw the conic as a triangle instead. Our AA approximation won't do well if the curve
702 // gets wrapped too tightly, and if we get too close to p1 we will pick up artifacts from
703 // the implicit function's reflection.
704 this->appendLine(midpoint);
705 this->appendLine(p2);
706 return;
707 }
708
709 if (!is_convex_curve_monotonic(p0, tan0, p2, tan1)) {
710 // Chop the conic at midtangent to produce two monotonic segments.
711 Sk2f ww = Sk2f(p3d01[2], p3d12[2]) * Sk2f(p3d012[2]).rsqrt();
712 this->appendMonotonicConic(p0, Sk2f(p3d01[0], p3d01[1]) / p3d01[2], midpoint, ww[0]);
713 this->appendMonotonicConic(midpoint, Sk2f(p3d12[0], p3d12[1]) / p3d12[2], p2, ww[1]);
714 return;
715 }
716
717 this->appendMonotonicConic(p0, p1, p2, w);
718}
719
720void GrCCGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, float w) {
721 SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
722
723 // Don't send curves to the GPU if we know they are nearly flat (or just very small).
724 if (are_collinear(p0, p1, p2)) {
725 this->appendLine(p2);
726 return;
727 }
728
729 p1.store(&fPoints.push_back());
730 p2.store(&fPoints.push_back());
731 fConicWeights.push_back(w);
732 fVerbs.push_back(Verb::kMonotonicConicTo);
733 ++fCurrContourTallies.fConics;
734}
735
Chris Dalton383a2ef2018-01-08 17:21:41 -0500736GrCCGeometry::PrimitiveTallies GrCCGeometry::endContour() {
Chris Daltonc1e59632017-09-05 00:30:07 -0600737 SkASSERT(fBuildingContour);
738 SkASSERT(fVerbs.count() >= fCurrContourTallies.fTriangles);
739
740 // The fTriangles field currently contains this contour's starting verb index. We can now
741 // use it to calculate the size of the contour's fan.
742 int fanSize = fVerbs.count() - fCurrContourTallies.fTriangles;
Chris Dalton7ca3b7b2018-04-10 00:21:19 -0600743 if (fPoints.back() == fCurrAnchorPoint) {
Chris Daltonc1e59632017-09-05 00:30:07 -0600744 --fanSize;
745 fVerbs.push_back(Verb::kEndClosedContour);
746 } else {
747 fVerbs.push_back(Verb::kEndOpenContour);
748 }
749
750 fCurrContourTallies.fTriangles = SkTMax(fanSize - 2, 0);
751
Chris Dalton383a2ef2018-01-08 17:21:41 -0500752 SkDEBUGCODE(fBuildingContour = false);
Chris Daltonc1e59632017-09-05 00:30:07 -0600753 return fCurrContourTallies;
Chris Dalton419a94d2017-08-28 10:24:22 -0600754}