• 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 #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