1 /* 2 * Copyright 2021 Google LLC. 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 8 #ifndef tessellate_StrokeTessellator_DEFINED 9 #define tessellate_StrokeTessellator_DEFINED 10 11 #include "include/core/SkPath.h" 12 #include "include/core/SkStrokeRec.h" 13 #include "include/private/SkColorData.h" 14 #include "src/gpu/tessellate/Tessellation.h" 15 16 class GrMeshDrawTarget; 17 class GrOpFlushState; 18 19 namespace skgpu { 20 21 // Prepares GPU data for, and then draws a stroke's tessellated geometry. 22 class StrokeTessellator { 23 public: 24 struct PathStrokeList { PathStrokeListPathStrokeList25 PathStrokeList(const SkPath& path, const SkStrokeRec& stroke, const SkPMColor4f& color) 26 : fPath(path), fStroke(stroke), fColor(color) {} 27 SkPath fPath; 28 SkStrokeRec fStroke; 29 SkPMColor4f fColor; 30 PathStrokeList* fNext = nullptr; 31 }; 32 StrokeTessellator(PatchAttribs attribs)33 StrokeTessellator(PatchAttribs attribs) : fAttribs(attribs) {} 34 35 // Called before draw(). Prepares GPU buffers containing the geometry to tessellate. 36 // 37 // Returns the fixed number of edges the tessellator will draw per patch, if using fixed-count 38 // rendering, otherwise 0. 39 virtual int prepare(GrMeshDrawTarget*, 40 const SkMatrix& shaderMatrix, 41 std::array<float,2> matrixMinMaxScales, 42 PathStrokeList*, 43 int totalCombinedVerbCnt) = 0; 44 45 #if SK_GPU_V1 46 // Issues draw calls for the tessellated stroke. The caller is responsible for creating and 47 // binding a pipeline that uses this class's shader() before calling draw(). 48 virtual void draw(GrOpFlushState*) const = 0; 49 #endif 50 ~StrokeTessellator()51 virtual ~StrokeTessellator() {} 52 53 protected: 54 PatchAttribs fAttribs; 55 }; 56 57 // These tolerances decide the number of parametric and radial segments the tessellator will 58 // linearize strokes into. These decisions are made in (pre-viewMatrix) local path space. 59 struct StrokeTolerances { 60 // Decides the number of parametric segments the tessellator adds for each curve. (Uniform 61 // steps in parametric space.) The tessellator will add enough parametric segments so that, 62 // once transformed into device space, they never deviate by more than 63 // 1/kTessellationPrecision pixels from the true curve. CalcParametricPrecisionStrokeTolerances64 constexpr static float CalcParametricPrecision(float matrixMaxScale) { 65 return matrixMaxScale * kTessellationPrecision; 66 } 67 // Decides the number of radial segments the tessellator adds for each curve. (Uniform steps 68 // in tangent angle.) The tessellator will add this number of radial segments for each 69 // radian of rotation in local path space. CalcNumRadialSegmentsPerRadianStrokeTolerances70 static float CalcNumRadialSegmentsPerRadian(float parametricPrecision, 71 float strokeWidth) { 72 return .5f / acosf(std::max(1 - 2 / (parametricPrecision * strokeWidth), -1.f)); 73 } ApproxNumRadialSegmentsPerRadianStrokeTolerances74 template<int N> static vec<N> ApproxNumRadialSegmentsPerRadian(float parametricPrecision, 75 vec<N> strokeWidths) { 76 vec<N> cosTheta = skvx::max(1 - 2 / (parametricPrecision * strokeWidths), -1); 77 // Subtract SKVX_APPROX_ACOS_MAX_ERROR so we never account for too few segments. 78 return .5f / (skvx::approx_acos(cosTheta) - SKVX_APPROX_ACOS_MAX_ERROR); 79 } 80 // Returns the equivalent stroke width in (pre-viewMatrix) local path space that the 81 // tessellator will use when rendering this stroke. This only differs from the actual stroke 82 // width for hairlines. GetLocalStrokeWidthStrokeTolerances83 static float GetLocalStrokeWidth(const float matrixMinMaxScales[2], float strokeWidth) { 84 SkASSERT(strokeWidth >= 0); 85 float localStrokeWidth = strokeWidth; 86 if (localStrokeWidth == 0) { // Is the stroke a hairline? 87 float matrixMinScale = matrixMinMaxScales[0]; 88 float matrixMaxScale = matrixMinMaxScales[1]; 89 // If the stroke is hairline then the tessellator will operate in post-transform 90 // space instead. But for the sake of CPU methods that need to conservatively 91 // approximate the number of segments to emit, we use 92 // localStrokeWidth ~= 1/matrixMinScale. 93 float approxScale = matrixMinScale; 94 // If the matrix has strong skew, don't let the scale shoot off to infinity. (This 95 // does not affect the tessellator; only the CPU methods that approximate the number 96 // of segments to emit.) 97 approxScale = std::max(matrixMinScale, matrixMaxScale * .25f); 98 localStrokeWidth = 1/approxScale; 99 if (localStrokeWidth == 0) { 100 // We just can't accidentally return zero from this method because zero means 101 // "hairline". Otherwise return whatever we calculated above. 102 localStrokeWidth = SK_ScalarNearlyZero; 103 } 104 } 105 return localStrokeWidth; 106 } MakeStrokeTolerances107 static StrokeTolerances Make(const float matrixMinMaxScales[2], float strokeWidth) { 108 return MakeNonHairline(matrixMinMaxScales[1], 109 GetLocalStrokeWidth(matrixMinMaxScales, strokeWidth)); 110 } MakeNonHairlineStrokeTolerances111 static StrokeTolerances MakeNonHairline(float matrixMaxScale, float strokeWidth) { 112 SkASSERT(strokeWidth > 0); 113 float parametricPrecision = CalcParametricPrecision(matrixMaxScale); 114 return {parametricPrecision, 115 CalcNumRadialSegmentsPerRadian(parametricPrecision, strokeWidth)}; 116 } 117 float fParametricPrecision; 118 float fNumRadialSegmentsPerRadian; 119 }; 120 121 // Calculates and buffers up future values for "numRadialSegmentsPerRadian" using SIMD. 122 class alignas(sizeof(float) * 4) StrokeToleranceBuffer { 123 public: 124 using PathStrokeList = StrokeTessellator::PathStrokeList; 125 StrokeToleranceBuffer(float parametricPrecision)126 StrokeToleranceBuffer(float parametricPrecision) : fParametricPrecision(parametricPrecision) {} 127 fetchRadialSegmentsPerRadian(PathStrokeList * head)128 float fetchRadialSegmentsPerRadian(PathStrokeList* head) { 129 // StrokeTessellateOp::onCombineIfPossible does not allow hairlines to become dynamic. If 130 // this changes, we will need to call StrokeTolerances::GetLocalStrokeWidth() for each 131 // stroke. 132 SkASSERT(!head->fStroke.isHairlineStyle()); 133 if (fBufferIdx == 4) { 134 // We ran out of values. Peek ahead and buffer up 4 more. 135 PathStrokeList* peekAhead = head; 136 int i = 0; 137 do { 138 fStrokeWidths[i++] = peekAhead->fStroke.getWidth(); 139 } while ((peekAhead = peekAhead->fNext) && i < 4); 140 auto tol = StrokeTolerances::ApproxNumRadialSegmentsPerRadian(fParametricPrecision, 141 fStrokeWidths); 142 tol.store(fNumRadialSegmentsPerRadian); 143 fBufferIdx = 0; 144 } 145 SkASSERT(0 <= fBufferIdx && fBufferIdx < 4); 146 SkASSERT(fStrokeWidths[fBufferIdx] == head->fStroke.getWidth()); 147 return fNumRadialSegmentsPerRadian[fBufferIdx++]; 148 } 149 150 private: 151 float4 fStrokeWidths{}; // Must be first for alignment purposes. 152 float fNumRadialSegmentsPerRadian[4]; 153 const float fParametricPrecision; 154 int fBufferIdx = 4; // Initialize the buffer as "empty"; 155 }; 156 157 } // namespace skgpu 158 159 #endif // tessellate_StrokeTessellator_DEFINED 160