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