xref: /aosp_15_r20/external/skia/src/sksl/sksl_graphite_vert.sksl (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1// Graphite-specific vertex shader code
2
3const float $PI = 3.141592653589793238;
4
5///////////////////////////////////////////////////////////////////////////////////////////////////
6// Support functions for tessellating path renderers
7
8const float $kCubicCurveType = 0;            // skgpu::tess::kCubicCurveType
9const float $kConicCurveType = 1;            // skgpu::tess::kConicCurveType
10const float $kTriangularConicCurveType = 2;  // skgpu::tess::kTriangularConicCurveType
11
12// This function can be used on GPUs with infinity support to infer the curve type from the specific
13// path control-point encoding used by tessellating path renderers. Calling this function on a
14// platform that lacks infinity support may result in a shader compilation error.
15$pure float curve_type_using_inf_support(float4 p23) {
16    return isinf(p23.z) ? $kTriangularConicCurveType :
17           isinf(p23.w) ? $kConicCurveType :
18                          $kCubicCurveType;
19}
20
21$pure bool $is_conic_curve(float curveType) {
22    return curveType != $kCubicCurveType;
23}
24
25$pure bool $is_triangular_conic_curve(float curveType) {
26    return curveType == $kTriangularConicCurveType;
27}
28
29// Wang's formula gives the minimum number of evenly spaced (in the parametric sense) line segments
30// that a bezier curve must be chopped into in order to guarantee all lines stay within a distance
31// of "1/precision" pixels from the true curve. Its definition for a bezier curve of degree "n" is
32// as follows:
33//
34//     maxLength = max([length(p[i+2] - 2p[i+1] + p[i]) for (0 <= i <= n-2)])
35//     numParametricSegments = sqrt(maxLength * precision * n*(n - 1)/8)
36//
37// (Goldman, Ron. (2003). 5.6.3 Wang's Formula. "Pyramid Algorithms: A Dynamic Programming Approach
38// to Curves and Surfaces for Geometric Modeling". Morgan Kaufmann Publishers.)
39
40const float $kDegree = 3;
41const float $kPrecision = 4; // Must match skgpu::tess::kPrecision
42const float $kLengthTerm     = ($kDegree * ($kDegree - 1) / 8.0) * $kPrecision;
43const float $kLengthTermPow2 = (($kDegree * $kDegree) * (($kDegree - 1) * ($kDegree - 1)) / 64.0) *
44                               ($kPrecision * $kPrecision);
45
46// Returns the length squared of the largest forward difference from Wang's cubic formula.
47$pure float $wangs_formula_max_fdiff_p2(float2 p0, float2 p1, float2 p2, float2 p3,
48                                        float2x2 matrix) {
49    float2 d0 = matrix * (fma(float2(-2), p1, p2) + p0);
50    float2 d1 = matrix * (fma(float2(-2), p2, p3) + p1);
51    return max(dot(d0,d0), dot(d1,d1));
52}
53
54$pure float $wangs_formula_cubic(float2 p0, float2 p1, float2 p2, float2 p3,
55                                 float2x2 matrix) {
56    float m = $wangs_formula_max_fdiff_p2(p0, p1, p2, p3, matrix);
57    return max(ceil(sqrt($kLengthTerm * sqrt(m))), 1.0);
58}
59
60$pure float $wangs_formula_cubic_log2(float2 p0, float2 p1, float2 p2, float2 p3,
61                                      float2x2 matrix) {
62    float m = $wangs_formula_max_fdiff_p2(p0, p1, p2, p3, matrix);
63    return ceil(log2(max($kLengthTermPow2 * m, 1.0)) * .25);
64}
65
66$pure float $wangs_formula_conic_p2(float2 p0, float2 p1, float2 p2, float w) {
67    // Translate the bounding box center to the origin.
68    float2 C = (min(min(p0, p1), p2) + max(max(p0, p1), p2)) * 0.5;
69    p0 -= C;
70    p1 -= C;
71    p2 -= C;
72
73    // Compute max length.
74    float m = sqrt(max(max(dot(p0,p0), dot(p1,p1)), dot(p2,p2)));
75
76    // Compute forward differences.
77    float2 dp = fma(float2(-2.0 * w), p1, p0) + p2;
78    float dw = abs(fma(-2.0, w, 2.0));
79
80    // Compute numerator and denominator for parametric step size of linearization. Here, the
81    // epsilon referenced from the cited paper is 1/precision.
82    float rp_minus_1 = max(0.0, fma(m, $kPrecision, -1.0));
83    float numer = length(dp) * $kPrecision + rp_minus_1 * dw;
84    float denom = 4 * min(w, 1.0);
85
86    return numer/denom;
87}
88
89$pure float $wangs_formula_conic(float2 p0, float2 p1, float2 p2, float w) {
90    float n2 = $wangs_formula_conic_p2(p0, p1, p2, w);
91    return max(ceil(sqrt(n2)), 1.0);
92}
93
94$pure float $wangs_formula_conic_log2(float2 p0, float2 p1, float2 p2, float w) {
95    float n2 = $wangs_formula_conic_p2(p0, p1, p2, w);
96    return ceil(log2(max(n2, 1.0)) * .5);
97}
98
99// Returns the normalized difference between a and b, i.e. normalize(a - b), with care taken for
100// if 'a' and/or 'b' have large coordinates.
101$pure float2 $robust_normalize_diff(float2 a, float2 b) {
102    float2 diff = a - b;
103    if (diff == float2(0.0)) {
104        return float2(0.0);
105    } else {
106        float invMag = 1.0 / max(abs(diff.x), abs(diff.y));
107        return normalize(invMag * diff);
108    }
109}
110
111// Returns the cosine of the angle between a and b, assuming a and b are unit vectors already.
112// Guaranteed to be between [-1, 1].
113$pure float $cosine_between_unit_vectors(float2 a, float2 b) {
114    // Since a and b are assumed to be normalized, the cosine is equal to the dot product, although
115    // we clamp that to ensure it falls within the expected range of [-1, 1].
116    return clamp(dot(a, b), -1.0, 1.0);
117}
118
119// Extends the middle radius to either the miter point, or the bevel edge if we surpassed the
120// miter limit and need to revert to a bevel join.
121$pure float $miter_extent(float cosTheta, float miterLimit) {
122    float x = fma(cosTheta, .5, .5);
123    return (x * miterLimit * miterLimit >= 1.0) ? inversesqrt(x) : sqrt(x);
124}
125
126// Returns the number of radial segments required for each radian of rotation, in order for the
127// curve to appear "smooth" as defined by the approximate device-space stroke radius.
128$pure float $num_radial_segments_per_radian(float approxDevStrokeRadius) {
129    return .5 / acos(max(1.0 - (1.0 / $kPrecision) / approxDevStrokeRadius, -1.0));
130}
131
132// Unlike mix(), this does not return b when t==1. But it otherwise seems to get better
133// precision than "a*(1 - t) + b*t" for things like chopping cubics on exact cusp points.
134// We override this result anyway when t==1 so it shouldn't be a problem.
135$pure float $unchecked_mix(float a, float b, float T) {
136    return fma(b - a, T, a);
137}
138$pure float2 $unchecked_mix(float2 a, float2 b, float T) {
139    return fma(b - a, float2(T), a);
140}
141$pure float4 $unchecked_mix(float4 a, float4 b, float4 T) {
142    return fma(b - a, T, a);
143}
144
145// Compute a vertex position for the curve described by p01 and p23 packed control points,
146// tessellated to the given resolve level, and assuming it will be drawn as a filled curve.
147$pure float2 tessellate_filled_curve(float2x2 vectorXform,
148                                     float resolveLevel, float idxInResolveLevel,
149                                     float4 p01, float4 p23,
150                                     float curveType) {
151    float2 localcoord;
152    if ($is_triangular_conic_curve(curveType)) {
153        // This patch is an exact triangle.
154        localcoord = (resolveLevel != 0)      ? p01.zw
155                   : (idxInResolveLevel != 0) ? p23.xy
156                                              : p01.xy;
157    } else {
158        float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
159        float w = -1;  // w < 0 tells us to treat the instance as an integral cubic.
160        float maxResolveLevel;
161        if ($is_conic_curve(curveType)) {
162            // Conics are 3 points, with the weight in p3.
163            w = p3.x;
164            maxResolveLevel = $wangs_formula_conic_log2(vectorXform*p0,
165                                                        vectorXform*p1,
166                                                        vectorXform*p2, w);
167            p1 *= w;  // Unproject p1.
168            p3 = p2;  // Duplicate the endpoint for shared code that also runs on cubics.
169        } else {
170            // The patch is an integral cubic.
171            maxResolveLevel = $wangs_formula_cubic_log2(p0, p1, p2, p3, vectorXform);
172        }
173        if (resolveLevel > maxResolveLevel) {
174            // This vertex is at a higher resolve level than we need. Demote to a lower
175            // resolveLevel, which will produce a degenerate triangle.
176            idxInResolveLevel = floor(ldexp(idxInResolveLevel,
177                                            int(maxResolveLevel - resolveLevel)));
178            resolveLevel = maxResolveLevel;
179        }
180        // Promote our location to a discrete position in the maximum fixed resolve level.
181        // This is extra paranoia to ensure we get the exact same fp32 coordinates for
182        // colocated points from different resolve levels (e.g., the vertices T=3/4 and
183        // T=6/8 should be exactly colocated).
184        float fixedVertexID = floor(.5 + ldexp(idxInResolveLevel, int(5 - resolveLevel)));
185        if (0 < fixedVertexID && fixedVertexID < 32) {
186            float T = fixedVertexID * (1 / 32.0);
187
188            // Evaluate at T. Use De Casteljau's for its accuracy and stability.
189            float2 ab = mix(p0, p1, T);
190            float2 bc = mix(p1, p2, T);
191            float2 cd = mix(p2, p3, T);
192            float2 abc = mix(ab, bc, T);
193            float2 bcd = mix(bc, cd, T);
194            float2 abcd = mix(abc, bcd, T);
195
196            // Evaluate the conic weight at T.
197            float u = mix(1.0, w, T);
198            float v = w + 1 - u;  // == mix(w, 1, T)
199            float uv = mix(u, v, T);
200
201            localcoord = (w < 0) ? /*cubic*/ abcd : /*conic*/ abc/uv;
202        } else {
203            localcoord = (fixedVertexID == 0) ? p0.xy : p3.xy;
204        }
205    }
206    return localcoord;
207}
208
209// Device coords are in xy, local coords are in zw, since for now perspective isn't supported.
210$pure float4 tessellate_stroked_curve(float edgeID, float maxEdges,
211                                      float2x2 affineMatrix,
212                                      float2 translate,
213                                      float maxScale /* derived from affineMatrix */,
214                                      float4 p01, float4 p23,
215                                      float2 lastControlPoint,
216                                      float2 strokeParams,
217                                      float curveType) {
218    float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
219    float w = -1;  // w<0 means the curve is an integral cubic.
220    if ($is_conic_curve(curveType)) {
221        // Conics are 3 points, with the weight in p3.
222        w = p3.x;
223        p3 = p2;  // Setting p3 equal to p2 works for the remaining rotational logic.
224    }
225
226    // Call Wang's formula to determine parametric segments before transform points for hairlines
227    // so that it is consistent with how the CPU tested the control points for chopping.
228    float numParametricSegments;
229    if (w < 0) {
230        if (p0 == p1 && p2 == p3) {
231            numParametricSegments = 1; // a line
232        } else {
233            numParametricSegments = $wangs_formula_cubic(p0, p1, p2, p3, affineMatrix);
234        }
235    } else {
236        numParametricSegments = $wangs_formula_conic(affineMatrix * p0,
237                                                     affineMatrix * p1,
238                                                     affineMatrix * p2, w);
239    }
240
241    // Matches skgpu::tess::StrokeParams
242    float strokeRadius = strokeParams.x;
243    float joinType = strokeParams.y; // <0 = round join, ==0 = bevel join, >0 encodes miter limit
244    bool isHairline = strokeParams.x == 0.0;
245    float numRadialSegmentsPerRadian;
246    if (isHairline) {
247        numRadialSegmentsPerRadian = $num_radial_segments_per_radian(1.0);
248        strokeRadius = 0.5;
249    } else {
250        numRadialSegmentsPerRadian = $num_radial_segments_per_radian(maxScale * strokeParams.x);
251    }
252
253    if (isHairline) {
254        // Hairline case. Transform the points before tessellation. We can still hold off on the
255        // translate until the end; we just need to perform the scale and skew right now.
256        p0 = affineMatrix * p0;
257        p1 = affineMatrix * p1;
258        p2 = affineMatrix * p2;
259        p3 = affineMatrix * p3;
260        lastControlPoint = affineMatrix * lastControlPoint;
261    }
262
263    // Find the starting and ending tangents.
264    float2 tan0 = $robust_normalize_diff((p0 == p1) ? ((p1 == p2) ? p3 : p2) : p1, p0);
265    float2 tan1 = $robust_normalize_diff(p3, (p3 == p2) ? ((p2 == p1) ? p0 : p1) : p2);
266    if (tan0 == float2(0)) {
267        // The stroke is a point. This special case tells us to draw a stroke-width circle as a
268        // 180 degree point stroke instead.
269        tan0 = float2(1,0);
270        tan1 = float2(-1,0);
271    }
272
273    // Determine how many edges to give to the join. We emit the first and final edges
274    // of the join twice: once full width and once restricted to half width. This guarantees
275    // perfect seaming by matching the vertices from the join as well as from the strokes on
276    // either side.
277    float numEdgesInJoin;
278    if (joinType >= 0 /*Is the join not a round type?*/) {
279        // Bevel(0) and miter(+) joins get 1 and 2 segments respectively.
280        // +2 because we emit the beginning and ending edges twice (see above comments).
281        numEdgesInJoin = sign(joinType) + (1 + 2);
282    } else {
283        float2 prevTan = $robust_normalize_diff(p0, lastControlPoint);
284        float joinRads = acos($cosine_between_unit_vectors(prevTan, tan0));
285        float numRadialSegmentsInJoin = max(ceil(joinRads * numRadialSegmentsPerRadian), 1);
286        // +2 because we emit the beginning and ending edges twice (see above comment).
287        numEdgesInJoin = numRadialSegmentsInJoin + 2;
288        // The stroke section needs at least two edges. Don't assign more to the join than
289        // "maxEdges - 2". (This is only relevant when the ideal max edge count calculated
290        // on the CPU had to be limited to maxEdges in the draw call).
291        numEdgesInJoin = min(numEdgesInJoin, maxEdges - 2);
292    }
293
294    // Find which direction the curve turns.
295    // NOTE: Since the curve is not allowed to inflect, we can just check F'(.5) x F''(.5).
296    // NOTE: F'(.5) x F''(.5) has the same sign as (P2 - P0) x (P3 - P1)
297    float turn = cross_length_2d(p2 - p0, p3 - p1);
298    float combinedEdgeID = abs(edgeID) - numEdgesInJoin;
299    if (combinedEdgeID < 0) {
300        tan1 = tan0;
301        // Don't let tan0 become zero. The code as-is isn't built to handle that case. tan0=0
302        // means the join is disabled, and to disable it with the existing code we can leave
303        // tan0 equal to tan1.
304        if (lastControlPoint != p0) {
305            tan0 = $robust_normalize_diff(p0, lastControlPoint);
306        }
307        turn = cross_length_2d(tan0, tan1);
308    }
309
310    // Calculate the curve's starting angle and rotation.
311    float cosTheta = $cosine_between_unit_vectors(tan0, tan1);
312    float rotation = acos(cosTheta);
313    if (turn < 0) {
314        // Adjust sign of rotation to match the direction the curve turns.
315        rotation = -rotation;
316    }
317
318    float numRadialSegments;
319    float strokeOutset = sign(edgeID);
320    if (combinedEdgeID < 0) {
321        // We belong to the preceding join. The first and final edges get duplicated, so we only
322        // have "numEdgesInJoin - 2" segments.
323        numRadialSegments = numEdgesInJoin - 2;
324        numParametricSegments = 1;  // Joins don't have parametric segments.
325        p3 = p2 = p1 = p0;  // Colocate all points on the junction point.
326        // Shift combinedEdgeID to the range [-1, numRadialSegments]. This duplicates the first
327        // edge and lands one edge at the very end of the join. (The duplicated final edge will
328        // actually come from the section of our strip that belongs to the stroke.)
329        combinedEdgeID += numRadialSegments + 1;
330        if (combinedEdgeID < 0) {
331            combinedEdgeID = 0;
332        } else {
333            // We normally restrict the join on one side of the junction, but if the tangents are
334            // nearly equivalent this could theoretically result in bad seaming and/or cracks on the
335            // side we don't put it on. If the tangents are nearly equivalent then we leave the join
336            // double-sided.
337            const float sinEpsilon = 1e-2;  // ~= sin(180deg / 3000)
338            bool tangentsNearlyParallel =
339                    (abs(turn) * inversesqrt(dot(tan0, tan0) * dot(tan1, tan1))) < sinEpsilon;
340            if (!tangentsNearlyParallel || dot(tan0, tan1) < 0) {
341                // There are two edges colocated at the beginning. Leave the first one double sided
342                // for seaming with the previous stroke. (The double sided edge at the end will
343                // actually come from the section of our strip that belongs to the stroke.)
344                strokeOutset = (turn < 0) ? min(strokeOutset, 0) : max(strokeOutset, 0);
345            }
346        }
347    } else {
348        // We belong to the stroke. Unless numRadialSegmentsPerRadian is incredibly high,
349        // clamping to maxCombinedSegments will be a no-op because the draw call was invoked with
350        // sufficient vertices to cover the worst case scenario of 180 degree rotation.
351        float maxCombinedSegments = maxEdges - numEdgesInJoin - 1;
352        numRadialSegments = max(ceil(abs(rotation) * numRadialSegmentsPerRadian), 1);
353        numRadialSegments = min(numRadialSegments, maxCombinedSegments);
354        numParametricSegments = min(numParametricSegments,
355                                    maxCombinedSegments - numRadialSegments + 1);
356    }
357
358    // Additional parameters for final tessellation evaluation.
359    float radsPerSegment = rotation / numRadialSegments;
360    float numCombinedSegments = numParametricSegments + numRadialSegments - 1;
361    bool isFinalEdge = (combinedEdgeID >= numCombinedSegments);
362    if (combinedEdgeID > numCombinedSegments) {
363        strokeOutset = 0;  // The strip has more edges than we need. Drop this one.
364    }
365    // Edge #2 extends to the miter point.
366    if (abs(edgeID) == 2 && joinType > 0/*Is the join a miter type?*/) {
367        strokeOutset *= $miter_extent(cosTheta, joinType/*miterLimit*/);
368    }
369
370    float2 tangent, strokeCoord;
371    if (combinedEdgeID != 0 && !isFinalEdge) {
372        // Compute the location and tangent direction of the stroke edge with the integral id
373        // "combinedEdgeID", where combinedEdgeID is the sorted-order index of parametric and radial
374        // edges. Start by finding the tangent function's power basis coefficients. These define a
375        // tangent direction (scaled by some uniform value) as:
376        //                                                 |T^2|
377        //     Tangent_Direction(T) = dx,dy = |A  2B  C| * |T  |
378        //                                    |.   .  .|   |1  |
379        float2 A, B, C = p1 - p0;
380        float2 D = p3 - p0;
381        if (w >= 0.0) {
382            // P0..P2 represent a conic and P3==P2. The derivative of a conic has a cumbersome
383            // order-4 denominator. However, this isn't necessary if we are only interested in a
384            // vector in the same *direction* as a given tangent line. Since the denominator scales
385            // dx and dy uniformly, we can throw it out completely after evaluating the derivative
386            // with the standard quotient rule. This leaves us with a simpler quadratic function
387            // that we use to find a tangent.
388            C *= w;
389            B = .5*D - C;
390            A = (w - 1.0) * D;
391            p1 *= w;
392        } else {
393            float2 E = p2 - p1;
394            B = E - C;
395            A = fma(float2(-3), E, D);
396        }
397        // FIXME(crbug.com/800804,skbug.com/11268): Consider normalizing the exponents in A,B,C at
398        // this point in order to prevent fp32 overflow.
399
400        // Now find the coefficients that give a tangent direction from a parametric edge ID:
401        //
402        //                                                                 |parametricEdgeID^2|
403        //     Tangent_Direction(parametricEdgeID) = dx,dy = |A  B_  C_| * |parametricEdgeID  |
404        //                                                   |.   .   .|   |1                 |
405        //
406        float2 B_ = B * (numParametricSegments * 2.0);
407        float2 C_ = C * (numParametricSegments * numParametricSegments);
408
409        // Run a binary search to determine the highest parametric edge that is located on or before
410        // the combinedEdgeID. A combined ID is determined by the sum of complete parametric and
411        // radial segments behind it. i.e., find the highest parametric edge where:
412        //
413        //    parametricEdgeID + floor(numRadialSegmentsAtParametricT) <= combinedEdgeID
414        //
415        float lastParametricEdgeID = 0.0;
416        float maxParametricEdgeID = min(numParametricSegments - 1.0, combinedEdgeID);
417        float negAbsRadsPerSegment = -abs(radsPerSegment);
418        float maxRotation0 = (1.0 + combinedEdgeID) * abs(radsPerSegment);
419        for (float exp = 32.0; exp >= 1.0; exp *= 0.5) {
420            // Test the parametric edge at lastParametricEdgeID + (32, 16, 8, 4, 2, 1).
421            float testParametricID = lastParametricEdgeID + exp;
422            if (testParametricID <= maxParametricEdgeID) {
423                float2 testTan = fma(float2(testParametricID), A, B_);
424                testTan = fma(float2(testParametricID), testTan, C_);
425                float cosRotation = dot(normalize(testTan), tan0);
426                float maxRotation = fma(testParametricID, negAbsRadsPerSegment, maxRotation0);
427                maxRotation = min(maxRotation, $PI);
428                // Is rotation <= maxRotation? (i.e., is the number of complete radial segments
429                // behind testT, + testParametricID <= combinedEdgeID?)
430                if (cosRotation >= cos(maxRotation)) {
431                    // testParametricID is on or before the combinedEdgeID. Keep it!
432                    lastParametricEdgeID = testParametricID;
433                }
434            }
435        }
436
437        // Find the T value of the parametric edge at lastParametricEdgeID.
438        float parametricT = lastParametricEdgeID / numParametricSegments;
439
440        // Now that we've identified the highest parametric edge on or before the
441        // combinedEdgeID, the highest radial edge is easy:
442        float lastRadialEdgeID = combinedEdgeID - lastParametricEdgeID;
443
444        // Find the angle of tan0, i.e. the angle between tan0 and the positive x axis.
445        float angle0 = acos(clamp(tan0.x, -1.0, 1.0));
446        angle0 = tan0.y >= 0.0 ? angle0 : -angle0;
447
448        // Find the tangent vector on the edge at lastRadialEdgeID. By construction it is already
449        // normalized.
450        float radialAngle = fma(lastRadialEdgeID, radsPerSegment, angle0);
451        tangent = float2(cos(radialAngle), sin(radialAngle));
452        float2 norm = float2(-tangent.y, tangent.x);
453
454        // Find the T value where the tangent is orthogonal to norm. This is a quadratic:
455        //
456        //     dot(norm, Tangent_Direction(T)) == 0
457        //
458        //                         |T^2|
459        //     norm * |A  2B  C| * |T  | == 0
460        //            |.   .  .|   |1  |
461        //
462        float a=dot(norm,A), b_over_2=dot(norm,B), c=dot(norm,C);
463        float discr_over_4 = max(b_over_2*b_over_2 - a*c, 0.0);
464        float q = sqrt(discr_over_4);
465        if (b_over_2 > 0.0) {
466            q = -q;
467        }
468        q -= b_over_2;
469
470        // Roots are q/a and c/q. Since each curve section does not inflect or rotate more than 180
471        // degrees, there can only be one tangent orthogonal to "norm" inside 0..1. Pick the root
472        // nearest .5.
473        float _5qa = -.5*q*a;
474        float2 root = (abs(fma(q,q,_5qa)) < abs(fma(a,c,_5qa))) ? float2(q,a) : float2(c,q);
475
476        // The root finder above can become unstable when lastRadialEdgeID == 0 (e.g., if there are
477        // roots at exatly 0 and 1 both). radialT should always equal 0 in this case.
478        float radialT = (lastRadialEdgeID != 0.0 && root.t != 0.0)
479                            ? saturate(root.s / root.t)
480                            : 0.0;
481
482        // Now that we've identified the T values of the last parametric and radial edges, our final
483        // T value for combinedEdgeID is whichever is larger.
484        float T = max(parametricT, radialT);
485
486        // Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability.
487        float2 ab = $unchecked_mix(p0, p1, T);
488        float2 bc = $unchecked_mix(p1, p2, T);
489        float2 cd = $unchecked_mix(p2, p3, T);
490        float2 abc = $unchecked_mix(ab, bc, T);
491        float2 bcd = $unchecked_mix(bc, cd, T);
492        float2 abcd = $unchecked_mix(abc, bcd, T);
493
494        // Evaluate the conic weight at T.
495        float u = $unchecked_mix(1.0, w, T);
496        float v = w + 1 - u;  // == mix(w, 1, T)
497        float uv = $unchecked_mix(u, v, T);
498
499        // If we went with T=parametricT, then update the tangent. Otherwise leave it at the radial
500        // tangent found previously. (In the event that parametricT == radialT, we keep the radial
501        // tangent.)
502        if (T != radialT) {
503            // We must re-normalize here because the tangent is determined by the curve coefficients
504            tangent = w >= 0.0 ? $robust_normalize_diff(bc*u, ab*v)
505                               : $robust_normalize_diff(bcd, abc);
506        }
507
508        strokeCoord = (w >= 0.0) ? abc/uv : abcd;
509    } else {
510        // Edges at the beginning and end of the strip use exact endpoints and tangents. This
511        // ensures crack-free seaming between instances.
512        tangent = (combinedEdgeID == 0) ? tan0 : tan1;
513        strokeCoord = (combinedEdgeID == 0) ? p0 : p3;
514    }
515
516    // At this point 'tangent' is normalized, so the orthogonal vector is also normalized.
517    float2 ortho = float2(tangent.y, -tangent.x);
518    strokeCoord += ortho * (strokeRadius * strokeOutset);
519
520    if (isHairline) {
521        // Hairline case. The scale and skew already happened before tessellation.
522        // TODO: There's probably a more efficient way to tessellate the hairline that lets us
523        // avoid inverting the affine matrix to get back to local coords, but it's just a 2x2 so
524        // this works for now.
525        return float4(strokeCoord + translate, inverse(affineMatrix) * strokeCoord);
526    } else {
527        // Normal case. Do the transform after tessellation.
528        return float4(affineMatrix * strokeCoord + translate, strokeCoord);
529    }
530}
531
532float4 analytic_rrect_vertex_fn(// Vertex Attributes
533                                float2 position,
534                                float2 normal,
535                                float normalScale,
536                                float centerWeight,
537                                // Instance Attributes
538                                float4 xRadiiOrFlags,
539                                float4 radiiOrQuadXs,
540                                float4 ltrbOrQuadYs,
541                                float4 center,
542                                float depth,
543                                float3x3 localToDevice,
544                                // Varyings
545                                out float4 jacobian,
546                                out float4 edgeDistances,
547                                out float4 xRadii,
548                                out float4 yRadii,
549                                out float2 strokeParams,
550                                out float2 perPixelControl,
551                                // Render Step
552                                out float2 stepLocalCoords) {
553    const uint kCornerVertexCount = 9; // KEEP IN SYNC WITH C++'s
554                                       // AnalyticRRectRenderStep::kCornerVertexCount
555    const float kMiterScale = 1.0;
556    const float kBevelScale = 0.0;
557    const float kRoundScale = 0.41421356237; // sqrt(2)-1
558
559    const float kEpsilon = 0.00024; // SK_ScalarNearlyZero
560
561    // Default to miter'ed vertex positioning. Corners with sufficiently large corner radii, or
562    // bevel'ed strokes will adjust vertex placement on a per corner basis. This will not affect
563    // the final coverage calculations in the fragment shader.
564    float joinScale = kMiterScale;
565
566    // Unpack instance-level state that determines the vertex placement and style of shape.
567    bool bidirectionalCoverage = center.z <= 0.0;
568    bool deviceSpaceDistances = false;
569    float4 xs, ys; // ordered TL, TR, BR, BL
570    float4 edgeAA = float4(1.0); // ordered L,T,R,B. 1 = AA, 0 = no AA
571    bool strokedLine = false;
572    if (xRadiiOrFlags.x < -1.0) {
573        // Stroked [round] rect or line
574        // If y > 0, unpack the line end points, otherwise unpack the rect edges
575        strokedLine = xRadiiOrFlags.y > 0.0;
576        xs = strokedLine ? ltrbOrQuadYs.LLRR : ltrbOrQuadYs.LRRL;
577        ys = ltrbOrQuadYs.TTBB;
578
579        if (xRadiiOrFlags.y < 0.0) {
580            // A hairline [r]rect so the X radii are encoded as negative values in this field,
581            // and Y radii are stored directly in the subsequent float4.
582            xRadii = -xRadiiOrFlags - 2.0;
583            yRadii = radiiOrQuadXs;
584
585            // All hairlines use miter joins (join style > 0)
586            strokeParams = float2(0.0, 1.0);
587        } else {
588            xRadii = radiiOrQuadXs;
589            yRadii = xRadii; // regular strokes are circular
590            strokeParams = xRadiiOrFlags.zw;
591
592            // `sign(strokeParams.y)` evaluates to kMiterScale (1.0) when the
593            // input is positive, and kBevelScale (0.0) when it is zero.
594            // kRoundScale uses the stroke radius to round rectangular corners.
595            joinScale = (strokeParams.y < 0.0) ? kRoundScale
596                                               : sign(strokeParams.y);
597        }
598    } else if (any(greaterThan(xRadiiOrFlags, float4(0.0)))) {
599        // Filled round rect
600        xs = ltrbOrQuadYs.LRRL;
601        ys = ltrbOrQuadYs.TTBB;
602
603        xRadii = xRadiiOrFlags;
604        yRadii = radiiOrQuadXs;
605
606        strokeParams = float2(0.0, -1.0); // A negative join style is "round"
607    } else {
608        // Per-edge quadrilateral, so we have to calculate the corner's basis from the
609        // quad's edges.
610        xs = radiiOrQuadXs;
611        ys = ltrbOrQuadYs;
612        edgeAA = -xRadiiOrFlags; // AA flags needed to be < 0 on upload, so flip the sign.
613
614        xRadii = float4(0.0);
615        yRadii = float4(0.0);
616
617        strokeParams = float2(0.0, 1.0); // Will be ignored, but set to a "miter"
618        deviceSpaceDistances = true;
619    }
620
621    // Adjust state on a per-corner basis
622    uint cornerID = uint(sk_VertexID) / kCornerVertexCount;
623    float2 cornerRadii = float2(xRadii[cornerID], yRadii[cornerID]);
624    if (cornerID % 2 != 0) {
625        // Corner radii are uploaded in the local coordinate frame, but vertex placement happens
626        // in a consistent winding before transforming to final local coords, so swap the
627        // radii for odd corners.
628        cornerRadii = cornerRadii.yx;
629    }
630
631    float2 cornerAspectRatio = float2(1.0);
632    if (all(greaterThan(cornerRadii, float2(0.0)))) {
633        // Position vertices for an elliptical corner; overriding any previous join style since
634        // that only applies when radii are 0.
635        joinScale = kRoundScale;
636        cornerAspectRatio = cornerRadii.yx;
637    }
638
639    // Calculate the local edge vectors, ordered L, T, R, B starting from the bottom left point.
640    // For quadrilaterals these are not necessarily axis-aligned, but in all cases they orient
641    // the +X/+Y normalized vertex template for each corner.
642    float4 dx = xs - xs.wxyz;
643    float4 dy = ys - ys.wxyz;
644    float4 edgeSquaredLen = dx*dx + dy*dy;
645
646    float4 edgeMask = sign(edgeSquaredLen); // 0 for zero-length edge, 1 for non-zero edge.
647    float4 edgeBias = float4(0.0); // adjustment to edge distance for butt cap correction
648    float2 strokeRadius = float2(strokeParams.x);
649    if (any(equal(edgeMask, float4(0.0)))) {
650        // Must clean up (dx,dy) depending on the empty edge configuration
651        if (all(equal(edgeMask, float4(0.0)))) {
652            // A point so use the canonical basis
653            dx = float4( 0.0, 1.0, 0.0, -1.0);
654            dy = float4(-1.0, 0.0, 1.0,  0.0);
655            edgeSquaredLen = float4(1.0);
656        } else {
657            // Triangles (3 non-zero edges) copy the adjacent edge. Otherwise it's a line so
658            // replace empty edges with the left-hand normal vector of the adjacent edge.
659            bool triangle = (edgeMask[0] + edgeMask[1] + edgeMask[2] + edgeMask[3]) > 2.5;
660            float4 edgeX = triangle ? dx.yzwx :  dy.yzwx;
661            float4 edgeY = triangle ? dy.yzwx : -dx.yzwx;
662
663            dx = mix(edgeX, dx, edgeMask);
664            dy = mix(edgeY, dy, edgeMask);
665            edgeSquaredLen = mix(edgeSquaredLen.yzwx, edgeSquaredLen, edgeMask);
666            edgeAA = mix(edgeAA.yzwx, edgeAA, edgeMask);
667
668            if (!triangle && joinScale == kBevelScale) {
669                // Don't outset by stroke radius for butt caps on the zero-length edge, but
670                // adjust edgeBias and strokeParams to calculate an AA miter'ed shape with the
671                // non-uniform stroke outset.
672                strokeRadius *= float2(edgeMask[cornerID], edgeMask.yzwx[cornerID]);
673                edgeBias = (edgeMask - 1.0) * strokeParams.x;
674                strokeParams.y = 1.0;
675                joinScale = kMiterScale;
676            }
677        }
678    }
679
680    float4 inverseEdgeLen = inversesqrt(edgeSquaredLen);
681    dx *= inverseEdgeLen;
682    dy *= inverseEdgeLen;
683
684    // Calculate local coordinate for the vertex (relative to xAxis and yAxis at first).
685    float2 xAxis = -float2(dx.yzwx[cornerID], dy.yzwx[cornerID]);
686    float2 yAxis =  float2(dx.xyzw[cornerID], dy.xyzw[cornerID]);
687    float2 localPos;
688    bool snapToCenter = false;
689    if (normalScale < 0.0) {
690        // Vertex is inset from the base shape, so we scale by (cornerRadii - strokeRadius)
691        // and have to check for the possibility of an inner miter. It is always inset by an
692        // additional conservative AA amount.
693        if (center.w < 0.0 || centerWeight * center.z != 0.0) {
694            snapToCenter = true;
695        } else {
696            float localAARadius = center.w;
697            float2 insetRadii =
698                    cornerRadii + (bidirectionalCoverage ? -strokeRadius : strokeRadius);
699            if (joinScale == kMiterScale ||
700                any(lessThanEqual(insetRadii, float2(localAARadius)))) {
701                // Miter the inset position
702                localPos = (insetRadii - localAARadius);
703            } else {
704                localPos = insetRadii*position - localAARadius*normal;
705            }
706        }
707    } else {
708        // Vertex is outset from the base shape (and possibly with an additional AA outset later
709        // in device space).
710        localPos = (cornerRadii + strokeRadius) * (position + joinScale*position.yx);
711    }
712
713    if (snapToCenter) {
714        // Center is already relative to true local coords, not the corner basis.
715        localPos = center.xy;
716    } else {
717        // Transform from corner basis to true local coords.
718        localPos -= cornerRadii;
719        localPos = float2(xs[cornerID], ys[cornerID]) + xAxis*localPos.x + yAxis*localPos.y;
720    }
721
722    // Calculate edge distances and device space coordinate for the vertex
723    edgeDistances = dy*(xs - localPos.x) - dx*(ys - localPos.y) + edgeBias;
724
725    // NOTE: This 3x3 inverse is different than just taking the 1st two columns of the 4x4
726    // inverse of the original SkM44 local-to-device matrix. We could calculate the 3x3 inverse
727    // and upload it, but it does not seem to be a bottleneck and saves on bandwidth to
728    // calculate it here instead.
729    float3x3 deviceToLocal = inverse(localToDevice);
730    float3 devPos = localToDevice * localPos.xy1;
731    jacobian = float4(deviceToLocal[0].xy - deviceToLocal[0].z*localPos,
732                      deviceToLocal[1].xy - deviceToLocal[1].z*localPos);
733
734    if (deviceSpaceDistances) {
735        // Apply the Jacobian in the vertex shader so any quadrilateral normals do not have to
736        // be passed to the fragment shader. However, it's important to use the Jacobian at a
737        // vertex on the edge, not the current vertex's Jacobian.
738        float4 gx = -dy*(deviceToLocal[0].x - deviceToLocal[0].z*xs) +
739                     dx*(deviceToLocal[0].y - deviceToLocal[0].z*ys);
740        float4 gy = -dy*(deviceToLocal[1].x - deviceToLocal[1].z*xs) +
741                     dx*(deviceToLocal[1].y - deviceToLocal[1].z*ys);
742        // NOTE: The gradient is missing a W term so edgeDistances must still be multiplied by
743        // 1/w in the fragment shader. The same goes for the encoded coverage scale.
744        edgeDistances *= inversesqrt(gx*gx + gy*gy);
745
746        // Bias non-AA edge distances by device W so its coverage contribution is >= 1.0
747        edgeDistances += (1 - edgeAA)*abs(devPos.z);
748
749        // Mixed edge AA shapes do not use subpixel scale+bias for coverage, since they tile
750        // to a large shape of unknown--but likely not subpixel--size. Triangles and quads do
751        // not use subpixel coverage since the scale+bias is not constant over the shape, but
752        // we can't evaluate per-fragment since we aren't passing down their arbitrary normals.
753        bool subpixelCoverage = edgeAA == float4(1.0) &&
754                                dot(abs(dx*dx.yzwx + dy*dy.yzwx), float4(1.0)) < kEpsilon;
755        if (subpixelCoverage) {
756            // Reconstructs the actual device-space width and height for all rectangle vertices.
757            float2 dim = edgeDistances.xy + edgeDistances.zw;
758            perPixelControl.y = 1.0 + min(min(dim.x, dim.y), abs(devPos.z));
759        } else {
760            perPixelControl.y = 1.0 + abs(devPos.z); // standard 1px width pre W division.
761        }
762    }
763
764    // Only outset for a vertex that is in front of the w=0 plane to avoid dealing with outset
765    // triangles rasterizing differently from the main triangles as w crosses 0.
766    if (normalScale > 0.0 && devPos.z > 0.0) {
767        // Note that when there's no perspective, the jacobian is equivalent to the normal
768        // matrix (inverse transpose), but produces correct results when there's perspective
769        // because it accounts for the position's influence on a line's projected direction.
770        float2x2 J = float2x2(jacobian);
771
772        float2 edgeAANormal = float2(edgeAA[cornerID], edgeAA.yzwx[cornerID]) * normal;
773        float2 nx = cornerAspectRatio.x * edgeAANormal.x * perp(-yAxis) * J;
774        float2 ny = cornerAspectRatio.y * edgeAANormal.y * perp( xAxis) * J;
775
776        bool isMidVertex = all(notEqual(edgeAANormal, float2(0)));
777        if (joinScale == kMiterScale && isMidVertex) {
778            // Produce a bisecting vector in device space.
779            nx = normalize(nx);
780            ny = normalize(ny);
781            if (dot(nx, ny) < -0.8) {
782                // Normals are in nearly opposite directions, so adjust to avoid float error.
783                float s = sign(cross_length_2d(nx, ny));
784                nx =  s*perp(nx);
785                ny = -s*perp(ny);
786            }
787        }
788        // Adding the normal components together directly results in what we'd have
789        // calculated if we'd just transformed 'normal' in one go, assuming they weren't
790        // normalized in the if-block above. If they were normalized, the sum equals the
791        // bisector between the original nx and ny.
792        //
793        // We multiply by W so that after perspective division the new point is offset by the
794        // now-unit normal.
795        // NOTE: (nx + ny) can become the zero vector if the device outset is for an edge
796        // marked as non-AA. In this case normalize() could produce the zero vector or NaN.
797        // Until a counter-example is found, GPUs seem to discard triangles with NaN vertices,
798        // which has the same effect as outsetting by the zero vector with this mesh, so we
799        // don't bother guarding the normalize() (yet).
800        devPos.xy += devPos.z * normalize(nx + ny);
801
802        // By construction these points are 1px away from the outer edge in device space.
803        if (deviceSpaceDistances) {
804            // Apply directly to edgeDistances to save work per pixel later on.
805            edgeDistances -= devPos.z;
806        } else {
807            // Otherwise store separately so edgeDistances can be used to reconstruct corner pos
808            perPixelControl.y = -devPos.z;
809        }
810    } else if (!deviceSpaceDistances) {
811        // Triangles are within the original shape so there's no additional outsetting to
812        // take into account for coverage calculations.
813        perPixelControl.y = 0.0;
814    }
815
816    perPixelControl.x = (centerWeight != 0.0)
817            // A positive value signals that a pixel is trivially full coverage.
818            ? 1.0
819            // A negative value signals bidirectional coverage, and a zero value signals a solid
820            // interior with per-pixel coverage.
821            : bidirectionalCoverage ? -1.0 : 0.0;
822
823    // The fragment shader operates in a canonical basis (x-axis = (1,0), y-axis = (0,1)). For
824    // stroked lines, incorporate their local orientation into the Jacobian to preserve this.
825    if (strokedLine) {
826        // The updated Jacobian is J' = B^-1 * J, where B is float2x2(xAxis, yAxis) for the
827        // top-left corner (so that B^-1 is constant over the whole shape). Since it's a line
828        // the basis was constructed to be orthonormal, det(B) = 1 and B^-1 is trivial.
829        // NOTE: float2x2 is column-major.
830        jacobian = float4(float2x2(dy[0], -dy[1], -dx[0], dx[1]) * float2x2(jacobian));
831    }
832
833    // Write out final results
834    stepLocalCoords = localPos;
835    return float4(devPos.xy, devPos.z*depth, devPos.z);
836}
837
838float4 per_edge_aa_quad_vertex_fn(// Vertex Attributes
839                                  float2 normal,
840                                  // Instance Attributes
841                                  float4 edgeAA,
842                                  float4 xs, // ordered TL, TR, BR, BL
843                                  float4 ys,
844                                  float depth,
845                                  float3x3 localToDevice,
846                                  // Varyings
847                                  out float4 edgeDistances,
848                                  // Render Step
849                                  out float2 stepLocalCoords) {
850    const uint kCornerVertexCount = 4; // KEEP IN SYNC WITH C++'s
851                                       // PerEdgeAAQuadRenderStep::kCornerVertexCount
852
853    const float kEpsilon = 0.00024; // SK_ScalarNearlyZero
854
855    // Calculate the local edge vectors, ordered L, T, R, B starting from the bottom left point.
856    // For quadrilaterals these are not necessarily axis-aligned, but in all cases they orient
857    // the +X/+Y normalized vertex template for each corner.
858    float4 dx = xs - xs.wxyz;
859    float4 dy = ys - ys.wxyz;
860    float4 edgeSquaredLen = dx*dx + dy*dy;
861
862    float4 edgeMask = sign(edgeSquaredLen); // 0 for zero-length edge, 1 for non-zero edge.
863    if (any(equal(edgeMask, float4(0.0)))) {
864        // Must clean up (dx,dy) depending on the empty edge configuration
865        if (all(equal(edgeMask, float4(0.0)))) {
866            // A point so use the canonical basis
867            dx = float4( 0.0, 1.0, 0.0, -1.0);
868            dy = float4(-1.0, 0.0, 1.0,  0.0);
869            edgeSquaredLen = float4(1.0);
870        } else {
871            // Triangles (3 non-zero edges) copy the adjacent edge. Otherwise it's a line so
872            // replace empty edges with the left-hand normal vector of the adjacent edge.
873            bool triangle = (edgeMask[0] + edgeMask[1] + edgeMask[2] + edgeMask[3]) > 2.5;
874            float4 edgeX = triangle ? dx.yzwx :  dy.yzwx;
875            float4 edgeY = triangle ? dy.yzwx : -dx.yzwx;
876
877            dx = mix(edgeX, dx, edgeMask);
878            dy = mix(edgeY, dy, edgeMask);
879            edgeSquaredLen = mix(edgeSquaredLen.yzwx, edgeSquaredLen, edgeMask);
880            edgeAA = mix(edgeAA.yzwx, edgeAA, edgeMask);
881        }
882    }
883
884    float4 inverseEdgeLen = inversesqrt(edgeSquaredLen);
885    dx *= inverseEdgeLen;
886    dy *= inverseEdgeLen;
887
888    // Calculate local coordinate for the vertex (relative to xAxis and yAxis at first).
889    uint cornerID = uint(sk_VertexID) / kCornerVertexCount;
890    float2 xAxis = -float2(dx.yzwx[cornerID], dy.yzwx[cornerID]);
891    float2 yAxis =  float2(dx.xyzw[cornerID], dy.xyzw[cornerID]);
892
893    // Vertex is outset from the base shape (and possibly with an additional AA outset later
894    // in device space).
895    float2 localPos = float2(xs[cornerID], ys[cornerID]);
896
897    // Calculate edge distances and device space coordinate for the vertex
898    edgeDistances = dy*(xs - localPos.x) - dx*(ys - localPos.y);
899
900    // NOTE: This 3x3 inverse is different than just taking the 1st two columns of the 4x4
901    // inverse of the original SkM44 local-to-device matrix. We could calculate the 3x3 inverse
902    // and upload it, but it does not seem to be a bottleneck and saves on bandwidth to
903    // calculate it here instead.
904    float3x3 deviceToLocal = inverse(localToDevice);
905    float3 devPos = localToDevice * localPos.xy1;
906
907    // Apply the Jacobian in the vertex shader so any quadrilateral normals do not have to
908    // be passed to the fragment shader. However, it's important to use the Jacobian at a
909    // vertex on the edge, not the current vertex's Jacobian.
910    float4 gx = -dy*(deviceToLocal[0].x - deviceToLocal[0].z*xs) +
911                 dx*(deviceToLocal[0].y - deviceToLocal[0].z*ys);
912    float4 gy = -dy*(deviceToLocal[1].x - deviceToLocal[1].z*xs) +
913                 dx*(deviceToLocal[1].y - deviceToLocal[1].z*ys);
914    // NOTE: The gradient is missing a W term so edgeDistances must still be multiplied by
915    // 1/w in the fragment shader. The same goes for the encoded coverage scale.
916    edgeDistances *= inversesqrt(gx*gx + gy*gy);
917
918    // Bias non-AA edge distances by device W so its coverage contribution is >= 1.0
919    // Add additional 1/2 bias here so we don't have to do so in the fragment shader.
920    edgeDistances += (1.5 - edgeAA)*abs(devPos.z);
921
922    // Only outset for a vertex that is in front of the w=0 plane to avoid dealing with outset
923    // triangles rasterizing differently from the main triangles as w crosses 0.
924    if (any(notEqual(normal, float2(0.0))) && devPos.z > 0.0) {
925        // Note that when there's no perspective, the jacobian is equivalent to the normal
926        // matrix (inverse transpose), but produces correct results when there's perspective
927        // because it accounts for the position's influence on a line's projected direction.
928        float2x2 J = float2x2(deviceToLocal[0].xy - deviceToLocal[0].z*localPos,
929                              deviceToLocal[1].xy - deviceToLocal[1].z*localPos);
930
931        float2 edgeAANormal = float2(edgeAA[cornerID], edgeAA.yzwx[cornerID]) * normal;
932        float2 nx = edgeAANormal.x * perp(-yAxis) * J;
933        float2 ny = edgeAANormal.y * perp( xAxis) * J;
934
935        bool isMidVertex = all(notEqual(edgeAANormal, float2(0)));
936        if (isMidVertex) {
937            // Produce a bisecting vector in device space.
938            nx = normalize(nx);
939            ny = normalize(ny);
940            if (dot(nx, ny) < -0.8) {
941                // Normals are in nearly opposite directions, so adjust to avoid float error.
942                float s = sign(cross_length_2d(nx, ny));
943                nx =  s*perp(nx);
944                ny = -s*perp(ny);
945            }
946        }
947        // Adding the normal components together directly results in what we'd have
948        // calculated if we'd just transformed 'normal' in one go, assuming they weren't
949        // normalized in the if-block above. If they were normalized, the sum equals the
950        // bisector between the original nx and ny.
951        //
952        // We multiply by W so that after perspective division the new point is offset by the
953        // now-unit normal.
954        // NOTE: (nx + ny) can become the zero vector if the device outset is for an edge
955        // marked as non-AA. In this case normalize() could produce the zero vector or NaN.
956        // Until a counter-example is found, GPUs seem to discard triangles with NaN vertices,
957        // which has the same effect as outsetting by the zero vector with this mesh, so we
958        // don't bother guarding the normalize() (yet).
959        devPos.xy += devPos.z * normalize(nx + ny);
960
961        // By construction these points are 1px away from the outer edge in device space.
962        // Apply directly to edgeDistances to save work per pixel later on.
963        edgeDistances -= devPos.z;
964    }
965
966    // Write out final results
967    stepLocalCoords = localPos;
968    return float4(devPos.xy, devPos.z*depth, devPos.z);
969}
970
971float4 circular_arc_vertex_fn(float3 position,
972                              // Instance Attributes
973                              float4 centerScales,
974                              float3 radiiAndFlags,
975                              float3 geoClipPlane,
976                              float3 fragClipPlane0,
977                              float3 fragClipPlane1,
978                              float4 inRoundCapPos,
979                              float depth,
980                              float3x3 localToDevice,
981                              // Varyings
982                              out float4 circleEdge,
983                              out float3 clipPlane,
984                              out float3 isectPlane,
985                              out float3 unionPlane,
986                              out float  roundCapRadius,
987                              out float4 roundCapPos,
988                              // Render Step
989                              out float2 stepLocalCoords) {
990    // TODO: clip offset against clip planes
991    float2 localCenter = centerScales.xy;
992    float2 localPos = localCenter;
993    // do geometric clip in normalized space
994    float dist = min(dot(position.xy, geoClipPlane.xy) + geoClipPlane.z, 0);
995    position.xy -= geoClipPlane.xy * dist;
996    // Get the new length to use below for scaling the offset
997    // (origLength is the initial length of position.xy).
998    float offsetScale = length(position.xy);
999
1000    // scale and translate to local space
1001    if (position.z > 0) {
1002        localPos += position.xy * centerScales.z;
1003    } else {
1004        localPos += position.xy * centerScales.w;
1005    }
1006
1007    float3 devPos = localToDevice * localPos.xy1;
1008    float3 devCenter = localToDevice * localCenter.xy1;
1009    float2 offset = devPos.xy - devCenter.xy;
1010    // offset for AA and correct length of offset
1011    if (offset != float2(0)) {
1012        offset = normalize(offset);
1013        devPos.xy += position.z*offset;
1014        if (position.z > 0) {
1015            // Scale using distance from center of unit octagon to the vertex
1016            // Because of geometry clipping we need to scale by 1.0823922*newLength/origLength
1017            // But the original length is 1.0823922 so the offsetScale is just newLength
1018            offset *= offsetScale;
1019        } else {
1020            // Because of geometry clipping we need to scale by innerRadius*newLength/origLength
1021            // But the original length is 1 so this is just innerRadius*newLength
1022            offset *= offsetScale*radiiAndFlags.y;
1023        }
1024    }
1025
1026    circleEdge = float4(offset, radiiAndFlags.xy);
1027    if (radiiAndFlags.z > 0) {
1028        clipPlane = fragClipPlane0;
1029        isectPlane = fragClipPlane1;
1030        unionPlane = float3(0, 0, 0);
1031    } else {
1032        clipPlane = fragClipPlane0;
1033        isectPlane = float3(0, 0, 1);
1034        unionPlane = fragClipPlane1;
1035    }
1036    if (abs(radiiAndFlags.z) > 1) {
1037        // This is the cap radius in normalized space where the outer radius is 1 and
1038        // radii.y is the normalized inner radius.
1039        roundCapRadius = (1.0 - radiiAndFlags.y) / 2.0;
1040    } else {
1041        roundCapRadius = 0;
1042    }
1043    roundCapPos = inRoundCapPos;
1044    stepLocalCoords = localPos;
1045
1046    // We assume no perspective
1047    return float4(devPos.xy, depth, 1);
1048}
1049
1050float4 text_vertex_fn(float2 baseCoords,
1051                      // Uniforms
1052                      float4x4 subRunDeviceMatrix,
1053                      float4x4 deviceToLocal,
1054                      float2 atlasSizeInv,
1055                      // Instance Attributes
1056                      float2 size,
1057                      float2 uvPos,
1058                      float2 xyPos,
1059                      float strikeToSourceScale,
1060                      float depth,
1061                      // Varyings
1062                      out float2 textureCoords,
1063                      out float2 unormTexCoords,  // used as varying in SDFText
1064                      // Render Step
1065                      out float2 stepLocalCoords) {
1066    baseCoords.xy *= float2(size);
1067
1068    // Sub runs have a decomposed transform and are sometimes already transformed into device
1069    // space, in which `subRunCoords` represents the bounds projected to device space without
1070    // the local-to-device translation and `subRunDeviceMatrix` contains the translation.
1071    float2 subRunCoords = strikeToSourceScale * baseCoords + xyPos;
1072    float4 position = subRunDeviceMatrix * subRunCoords.xy01;
1073
1074    // Calculate the local coords used for shading.
1075    // TODO(b/246963258): This is incorrect if the transform has perspective, which would
1076    // require a division + a valid z coordinate (which is currently set to 0).
1077    stepLocalCoords = (deviceToLocal * position).xy;
1078
1079    unormTexCoords = baseCoords + uvPos;
1080    textureCoords = unormTexCoords * atlasSizeInv;
1081
1082    return float4(position.xy, depth*position.w, position.w);
1083}
1084
1085float4 coverage_mask_vertex_fn(float2 quadCoords,
1086                               // Uniforms
1087                               float3x3 maskToDeviceRemainder,
1088                               // Instance Attributes
1089                               float4 drawBounds,
1090                               float4 maskBoundsIn,
1091                               float2 deviceOrigin,
1092                               float depth,
1093                               float3x3 deviceToLocal,
1094                               // Varyings
1095                               out float4 maskBounds,
1096                               out float2 textureCoords,
1097                               out half invert,
1098                               // Render Step
1099                               out float2 stepLocalCoords) {
1100    // An atlas shape is an axis-aligned rectangle tessellated as a triangle strip.
1101    //
1102    // The bounds coordinates are in an intermediate space, pixel-aligned with the mask texture
1103    // that's sampled in the fragment shader. The coords must be transformed by both
1104    // maskToDeviceRemainder and translated by deviceOrigin to get device coords.
1105    textureCoords = mix(drawBounds.xy, drawBounds.zw, quadCoords);
1106    float3 drawCoords = maskToDeviceRemainder*((textureCoords + deviceOrigin).xy1);
1107
1108    // Local coordinates used for shading are derived from the final device coords and the inverse
1109    // of the original local-to-device matrix.
1110    float3 localCoords = deviceToLocal * drawCoords;
1111    // TODO: Support float3 local coordinates if the matrix has perspective so that W is
1112    // interpolated correctly to the fragment shader.
1113    stepLocalCoords = localCoords.xy / localCoords.z;
1114
1115    // For an inverse fill, `textureCoords` will get clamped to `maskBounds` and the edge pixels
1116    // will always land on a 0-coverage border pixel assuming the atlas was prepared with 1px
1117    // padding around each mask entry. This includes inverse fills where the mask was fully clipped
1118    // out, since then maskBounds.RBLT == (0,0,-1,-1) and we sample the top-left-most pixel of the
1119    // atlas, which is guaranteed to be transparent.
1120    if (all(lessThanEqual(maskBoundsIn.LT, maskBoundsIn.RB))) {
1121        // Regular fill
1122        maskBounds = maskBoundsIn;
1123        invert = 0;
1124    } else {
1125        // Re-arrange the mask bounds to sorted order for texture clamping in the fragment shader
1126        maskBounds = maskBoundsIn.RBLT;
1127        invert = 1;
1128    }
1129
1130    return float4(drawCoords.xy, depth*drawCoords.z, drawCoords.z);
1131}
1132
1133float4 cover_bounds_vertex_fn(float2 corner,
1134                              float4 bounds,
1135                              float depth,
1136                              float3x3 matrix,
1137                              out float2 stepLocalCoords) {
1138    if (all(lessThanEqual(bounds.LT, bounds.RB))) {
1139        // A regular fill
1140        corner = mix(bounds.LT, bounds.RB, corner);
1141        float3 devCorner = matrix * corner.xy1;
1142        stepLocalCoords = corner;
1143        return float4(devCorner.xy, depth*devCorner.z, devCorner.z);
1144    } else {
1145        // An inverse fill
1146        corner = mix(bounds.RB, bounds.LT, corner);
1147        // TODO(b/351923375): Get the 3x3 inverse  of the local-to-device transform from the CPU
1148        // if it can be computed fast enough on the CPU from the cached 4x4 inverse.
1149        float3 localCoords = inverse(matrix) * corner.xy1;
1150        // Dividing the inverse mapped local coords by its homogenous coordinate reconstructs the
1151        // original local coords.
1152        float invW = 1.0 / localCoords.z;
1153        stepLocalCoords = localCoords.xy * invW;
1154
1155        // 1/W also happens to be equal to (matrix*stepLocalCoords.xy1).z, which is the device-space
1156        // homogenous coordinate we want perspective interpolation to respect. We multiply the
1157        // output position by 1/W and set the output position's homogenous coord to that same 1/W
1158        // which ensures the projected vertices are still the device-space corners, but
1159        // stepLocalCoords will be correctly perspective interpolated by HW.
1160        return float4(corner*invW, depth*invW, invW);
1161    }
1162}
1163