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