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