• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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