xref: /aosp_15_r20/external/skia/src/gpu/tessellate/LinearTolerances.h (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2022 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #ifndef skgpu_tessellate_LinearTolerances_DEFINED
9*c8dee2aaSAndroid Build Coastguard Worker #define skgpu_tessellate_LinearTolerances_DEFINED
10*c8dee2aaSAndroid Build Coastguard Worker 
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/tessellate/Tessellation.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/tessellate/WangsFormula.h"
15*c8dee2aaSAndroid Build Coastguard Worker 
16*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
17*c8dee2aaSAndroid Build Coastguard Worker 
18*c8dee2aaSAndroid Build Coastguard Worker namespace skgpu::tess {
19*c8dee2aaSAndroid Build Coastguard Worker 
20*c8dee2aaSAndroid Build Coastguard Worker /**
21*c8dee2aaSAndroid Build Coastguard Worker  * LinearTolerances stores state to approximate the final device-space transform applied
22*c8dee2aaSAndroid Build Coastguard Worker  * to curves, and uses that to calculate segmentation levels for both the parametric curves and
23*c8dee2aaSAndroid Build Coastguard Worker  * radial components (when stroking, where you have to represent the offset of a curve).
24*c8dee2aaSAndroid Build Coastguard Worker  * These tolerances determine the worst-case number of parametric and radial segments required to
25*c8dee2aaSAndroid Build Coastguard Worker  * accurately linearize curves.
26*c8dee2aaSAndroid Build Coastguard Worker  * - segments = a linear subsection on the curve, either defined as parametric (linear in t) or
27*c8dee2aaSAndroid Build Coastguard Worker  *   radial (linear in curve's internal rotation).
28*c8dee2aaSAndroid Build Coastguard Worker  * - edges = orthogonal geometry to segments, used in stroking to offset from the central curve by
29*c8dee2aaSAndroid Build Coastguard Worker  *   half the stroke width, or to construct the join geometry.
30*c8dee2aaSAndroid Build Coastguard Worker  *
31*c8dee2aaSAndroid Build Coastguard Worker  * The tolerance values and decisions are estimated in the local path space, although PatchWriter
32*c8dee2aaSAndroid Build Coastguard Worker  * uses a 2x2 vector transform that approximates the scale/skew (as-best-as-possible) of the full
33*c8dee2aaSAndroid Build Coastguard Worker  * local-to-device transform applied in the vertex shader.
34*c8dee2aaSAndroid Build Coastguard Worker  *
35*c8dee2aaSAndroid Build Coastguard Worker  * The properties tracked in LinearTolerances can be used to compute the final segmentation factor
36*c8dee2aaSAndroid Build Coastguard Worker  * for filled paths (the resolve level) or stroked paths (the number of edges).
37*c8dee2aaSAndroid Build Coastguard Worker  */
38*c8dee2aaSAndroid Build Coastguard Worker class LinearTolerances {
39*c8dee2aaSAndroid Build Coastguard Worker public:
numParametricSegments_p4()40*c8dee2aaSAndroid Build Coastguard Worker     float numParametricSegments_p4() const { return fNumParametricSegments_p4; }
numRadialSegmentsPerRadian()41*c8dee2aaSAndroid Build Coastguard Worker     float numRadialSegmentsPerRadian() const { return fNumRadialSegmentsPerRadian; }
numEdgesInJoins()42*c8dee2aaSAndroid Build Coastguard Worker     int   numEdgesInJoins() const { return fEdgesInJoins; }
43*c8dee2aaSAndroid Build Coastguard Worker 
44*c8dee2aaSAndroid Build Coastguard Worker     // Fast log2 of minimum required # of segments per tracked Wang's formula calculations.
requiredResolveLevel()45*c8dee2aaSAndroid Build Coastguard Worker     int requiredResolveLevel() const {
46*c8dee2aaSAndroid Build Coastguard Worker         // log16(n^4) == log2(n)
47*c8dee2aaSAndroid Build Coastguard Worker         return wangs_formula::nextlog16(fNumParametricSegments_p4);
48*c8dee2aaSAndroid Build Coastguard Worker     }
49*c8dee2aaSAndroid Build Coastguard Worker 
requiredStrokeEdges()50*c8dee2aaSAndroid Build Coastguard Worker     int requiredStrokeEdges() const {
51*c8dee2aaSAndroid Build Coastguard Worker         // The maximum rotation we can have in a stroke is 180 degrees (SK_ScalarPI radians).
52*c8dee2aaSAndroid Build Coastguard Worker         int maxRadialSegmentsInStroke =
53*c8dee2aaSAndroid Build Coastguard Worker                 std::max(SkScalarCeilToInt(fNumRadialSegmentsPerRadian * SK_ScalarPI), 1);
54*c8dee2aaSAndroid Build Coastguard Worker 
55*c8dee2aaSAndroid Build Coastguard Worker         int maxParametricSegmentsInStroke =
56*c8dee2aaSAndroid Build Coastguard Worker                 SkScalarCeilToInt(wangs_formula::root4(fNumParametricSegments_p4));
57*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(maxParametricSegmentsInStroke >= 1);
58*c8dee2aaSAndroid Build Coastguard Worker 
59*c8dee2aaSAndroid Build Coastguard Worker         // Now calculate the maximum number of edges we will need in the stroke portion of the
60*c8dee2aaSAndroid Build Coastguard Worker         // instance. The first and last edges in a stroke are shared by both the parametric and
61*c8dee2aaSAndroid Build Coastguard Worker         // radial sets of edges, so the total number of edges is:
62*c8dee2aaSAndroid Build Coastguard Worker         //
63*c8dee2aaSAndroid Build Coastguard Worker         //   numCombinedEdges = numParametricEdges + numRadialEdges - 2
64*c8dee2aaSAndroid Build Coastguard Worker         //
65*c8dee2aaSAndroid Build Coastguard Worker         // It's important to differentiate between the number of edges and segments in a strip:
66*c8dee2aaSAndroid Build Coastguard Worker         //
67*c8dee2aaSAndroid Build Coastguard Worker         //   numSegments = numEdges - 1
68*c8dee2aaSAndroid Build Coastguard Worker         //
69*c8dee2aaSAndroid Build Coastguard Worker         // So the total number of combined edges in the stroke is:
70*c8dee2aaSAndroid Build Coastguard Worker         //
71*c8dee2aaSAndroid Build Coastguard Worker         //   numEdgesInStroke = numParametricSegments + 1 + numRadialSegments + 1 - 2
72*c8dee2aaSAndroid Build Coastguard Worker         //                    = numParametricSegments + numRadialSegments
73*c8dee2aaSAndroid Build Coastguard Worker         //
74*c8dee2aaSAndroid Build Coastguard Worker         int maxEdgesInStroke = maxRadialSegmentsInStroke + maxParametricSegmentsInStroke;
75*c8dee2aaSAndroid Build Coastguard Worker 
76*c8dee2aaSAndroid Build Coastguard Worker         // Each triangle strip has two sections: It starts with a join then transitions to a
77*c8dee2aaSAndroid Build Coastguard Worker         // stroke. The number of edges in an instance is the sum of edges from the join and
78*c8dee2aaSAndroid Build Coastguard Worker         // stroke sections both.
79*c8dee2aaSAndroid Build Coastguard Worker         // NOTE: The final join edge and the first stroke edge are co-located, however we still
80*c8dee2aaSAndroid Build Coastguard Worker         // need to emit both because the join's edge is half-width and the stroke is full-width.
81*c8dee2aaSAndroid Build Coastguard Worker         return fEdgesInJoins + maxEdgesInStroke;
82*c8dee2aaSAndroid Build Coastguard Worker     }
83*c8dee2aaSAndroid Build Coastguard Worker 
setParametricSegments(float n4)84*c8dee2aaSAndroid Build Coastguard Worker     void setParametricSegments(float n4) {
85*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(n4 >= 0.f);
86*c8dee2aaSAndroid Build Coastguard Worker         fNumParametricSegments_p4 = n4;
87*c8dee2aaSAndroid Build Coastguard Worker     }
88*c8dee2aaSAndroid Build Coastguard Worker 
setStroke(const StrokeParams & strokeParams,float maxScale)89*c8dee2aaSAndroid Build Coastguard Worker     void setStroke(const StrokeParams& strokeParams, float maxScale) {
90*c8dee2aaSAndroid Build Coastguard Worker         float approxDeviceStrokeRadius;
91*c8dee2aaSAndroid Build Coastguard Worker         if (strokeParams.fRadius == 0.f) {
92*c8dee2aaSAndroid Build Coastguard Worker             // Hairlines are always 1 px wide
93*c8dee2aaSAndroid Build Coastguard Worker             approxDeviceStrokeRadius = 0.5f;
94*c8dee2aaSAndroid Build Coastguard Worker         } else {
95*c8dee2aaSAndroid Build Coastguard Worker             // Approximate max scale * local stroke width / 2
96*c8dee2aaSAndroid Build Coastguard Worker             approxDeviceStrokeRadius = strokeParams.fRadius * maxScale;
97*c8dee2aaSAndroid Build Coastguard Worker         }
98*c8dee2aaSAndroid Build Coastguard Worker 
99*c8dee2aaSAndroid Build Coastguard Worker         fNumRadialSegmentsPerRadian = CalcNumRadialSegmentsPerRadian(approxDeviceStrokeRadius);
100*c8dee2aaSAndroid Build Coastguard Worker 
101*c8dee2aaSAndroid Build Coastguard Worker         fEdgesInJoins = NumFixedEdgesInJoin(strokeParams);
102*c8dee2aaSAndroid Build Coastguard Worker         if (strokeParams.fJoinType < 0.f && fNumRadialSegmentsPerRadian > 0.f) {
103*c8dee2aaSAndroid Build Coastguard Worker             // For round joins we need to count the radial edges on our own. Account for a
104*c8dee2aaSAndroid Build Coastguard Worker             // worst-case join of 180 degrees (SK_ScalarPI radians).
105*c8dee2aaSAndroid Build Coastguard Worker             fEdgesInJoins += SkScalarCeilToInt(fNumRadialSegmentsPerRadian * SK_ScalarPI) - 1;
106*c8dee2aaSAndroid Build Coastguard Worker         }
107*c8dee2aaSAndroid Build Coastguard Worker     }
108*c8dee2aaSAndroid Build Coastguard Worker 
accumulate(const LinearTolerances & tolerances)109*c8dee2aaSAndroid Build Coastguard Worker     void accumulate(const LinearTolerances& tolerances) {
110*c8dee2aaSAndroid Build Coastguard Worker         if (tolerances.fNumParametricSegments_p4 > fNumParametricSegments_p4) {
111*c8dee2aaSAndroid Build Coastguard Worker             fNumParametricSegments_p4 = tolerances.fNumParametricSegments_p4;
112*c8dee2aaSAndroid Build Coastguard Worker         }
113*c8dee2aaSAndroid Build Coastguard Worker         if (tolerances.fNumRadialSegmentsPerRadian > fNumRadialSegmentsPerRadian) {
114*c8dee2aaSAndroid Build Coastguard Worker             fNumRadialSegmentsPerRadian = tolerances.fNumRadialSegmentsPerRadian;
115*c8dee2aaSAndroid Build Coastguard Worker         }
116*c8dee2aaSAndroid Build Coastguard Worker         if (tolerances.fEdgesInJoins > fEdgesInJoins) {
117*c8dee2aaSAndroid Build Coastguard Worker             fEdgesInJoins = tolerances.fEdgesInJoins;
118*c8dee2aaSAndroid Build Coastguard Worker         }
119*c8dee2aaSAndroid Build Coastguard Worker     }
120*c8dee2aaSAndroid Build Coastguard Worker 
121*c8dee2aaSAndroid Build Coastguard Worker private:
122*c8dee2aaSAndroid Build Coastguard Worker     // Used for both fills and strokes, always at least one parametric segment
123*c8dee2aaSAndroid Build Coastguard Worker     float fNumParametricSegments_p4 = 1.f;
124*c8dee2aaSAndroid Build Coastguard Worker     // Used for strokes, adding additional segments along the curve to account for its rotation
125*c8dee2aaSAndroid Build Coastguard Worker     // TODO: Currently we assume the worst case 180 degree rotation for any curve, but tracking
126*c8dee2aaSAndroid Build Coastguard Worker     // max(radialSegments * patch curvature) would be tighter. This would require computing
127*c8dee2aaSAndroid Build Coastguard Worker     // rotation per patch, which could be approximated by tracking min of the tangent dot
128*c8dee2aaSAndroid Build Coastguard Worker     // products, but then we'd be left with the slightly less accurate
129*c8dee2aaSAndroid Build Coastguard Worker     // "max(radialSegments) * acos(min(tan dot product))". It is also unknown if requesting
130*c8dee2aaSAndroid Build Coastguard Worker     // tighter bounds pays off with less GPU work for more CPU work
131*c8dee2aaSAndroid Build Coastguard Worker     float fNumRadialSegmentsPerRadian = 0.f;
132*c8dee2aaSAndroid Build Coastguard Worker     // Used for strokes, tracking the number of additional vertices required to handle joins
133*c8dee2aaSAndroid Build Coastguard Worker     // based on the join type and stroke width.
134*c8dee2aaSAndroid Build Coastguard Worker     // TODO: For round joins, we could also track the rotation angle of the join, instead of
135*c8dee2aaSAndroid Build Coastguard Worker     // assuming 180 degrees. PatchWriter has all necessary control points to do so, but runs
136*c8dee2aaSAndroid Build Coastguard Worker     // into similar trade offs between CPU vs GPU work, and accuracy vs. reducing calls to acos.
137*c8dee2aaSAndroid Build Coastguard Worker     int   fEdgesInJoins = 0;
138*c8dee2aaSAndroid Build Coastguard Worker };
139*c8dee2aaSAndroid Build Coastguard Worker 
140*c8dee2aaSAndroid Build Coastguard Worker }  // namespace skgpu::tess
141*c8dee2aaSAndroid Build Coastguard Worker 
142*c8dee2aaSAndroid Build Coastguard Worker #endif // skgpu_tessellate_LinearTolerances_DEFINED
143