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