• 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 #include "src/gpu/tessellate/StrokeFixedCountTessellator.h"
9 
10 #include "src/core/SkGeometry.h"
11 #include "src/gpu/tessellate/PatchWriter.h"
12 #include "src/gpu/tessellate/StrokeIterator.h"
13 #include "src/gpu/tessellate/WangsFormula.h"
14 
15 #if SK_GPU_V1
16 #include "src/gpu/GrMeshDrawTarget.h"
17 #include "src/gpu/GrOpFlushState.h"
18 #include "src/gpu/GrResourceProvider.h"
19 #endif
20 
21 namespace skgpu {
22 
23 namespace {
24 
25 // Writes out strokes to the given instance chunk array, chopping if necessary so that all instances
26 // require 32 parametric segments or less. (We don't consider radial segments here. The tessellator
27 // will just add enough additional segments to handle a worst-case 180 degree stroke.)
28 class InstanceWriter {
29     using VectorXform = wangs_formula::VectorXform;
30 public:
InstanceWriter(PatchWriter & patchWriter,float matrixMaxScale)31     InstanceWriter(PatchWriter& patchWriter, float matrixMaxScale)
32             : fPatchWriter(patchWriter)
33             , fParametricPrecision(StrokeTolerances::CalcParametricPrecision(matrixMaxScale)) {
34         SkASSERT(fPatchWriter.attribs() & PatchAttribs::kJoinControlPoint);
35     }
36 
parametricPrecision() const37     float parametricPrecision() const { return fParametricPrecision; }
38 
lineTo(SkPoint start,SkPoint end)39     SK_ALWAYS_INLINE void lineTo(SkPoint start, SkPoint end) {
40         fPatchWriter.writeLine(start, end);
41     }
42 
quadraticTo(const SkPoint p[3])43     SK_ALWAYS_INLINE void quadraticTo(const SkPoint p[3]) {
44         fPatchWriter.writeQuadratic(p, VectorXform(), fParametricPrecision);
45     }
46 
conicTo(const SkPoint p[3],float w)47     SK_ALWAYS_INLINE void conicTo(const SkPoint p[3], float w) {
48         fPatchWriter.writeConic(p, w, VectorXform(), fParametricPrecision);
49     }
50 
cubicConvex180To(const SkPoint p[4])51     SK_ALWAYS_INLINE void cubicConvex180To(const SkPoint p[4]) {
52         fPatchWriter.writeCubic(p, VectorXform(), fParametricPrecision);
53     }
54 
55     // Called when we encounter the verb "kMoveWithinContour". Moves invalidate the previous control
56     // point. The stroke iterator tells us the new value to use for the previous control point.
setLastControlPoint(SkPoint newLastControlPoint)57     void setLastControlPoint(SkPoint newLastControlPoint) {
58         fPatchWriter.updateJoinControlPointAttrib(newLastControlPoint);
59     }
60 
61     // Draws a circle whose diameter is equal to the stroke width. We emit circles at cusp points
62     // round caps, and empty strokes that are specified to be drawn as circles.
writeCircle(SkPoint location)63     void writeCircle(SkPoint location) {
64         fPatchWriter.writeCircle(location);
65     }
66 
finishContour()67     void finishContour() {
68         fPatchWriter.writeDeferredStrokePatch();
69     }
70 
71 private:
72     PatchWriter& fPatchWriter;
73     const float fParametricPrecision;
74 };
75 
76 // Returns the worst-case number of edges we will need in order to draw a join of the given type.
worst_case_edges_in_join(SkPaint::Join joinType,float numRadialSegmentsPerRadian)77 int worst_case_edges_in_join(SkPaint::Join joinType, float numRadialSegmentsPerRadian) {
78     int numEdges = StrokeFixedCountTessellator::NumFixedEdgesInJoin(joinType);
79     if (joinType == SkPaint::kRound_Join) {
80         // For round joins we need to count the radial edges on our own. Account for a worst-case
81         // join of 180 degrees (SK_ScalarPI radians).
82         numEdges += std::max(SkScalarCeilToInt(numRadialSegmentsPerRadian * SK_ScalarPI) - 1, 0);
83     }
84     return numEdges;
85 }
86 
87 }  // namespace
88 
89 
patchPreallocCount(int totalCombinedStrokeVerbCnt) const90 int StrokeFixedCountTessellator::patchPreallocCount(int totalCombinedStrokeVerbCnt) const {
91     // Over-allocate enough patches for each stroke to chop once, and for 8 extra caps. Since we
92     // have to chop at inflections, points of 180 degree rotation, and anywhere a stroke requires
93     // too many parametric segments, many strokes will end up getting choppped.
94     int strokePreallocCount = totalCombinedStrokeVerbCnt * 2;
95     int capPreallocCount = 8;
96     return strokePreallocCount + capPreallocCount;
97 }
98 
writePatches(PatchWriter & patchWriter,const SkMatrix & shaderMatrix,std::array<float,2> matrixMinMaxScales,PathStrokeList * pathStrokeList)99 int StrokeFixedCountTessellator::writePatches(PatchWriter& patchWriter,
100                                               const SkMatrix& shaderMatrix,
101                                               std::array<float,2> matrixMinMaxScales,
102                                               PathStrokeList* pathStrokeList) {
103     int maxEdgesInJoin = 0;
104     float maxRadialSegmentsPerRadian = 0;
105 
106     InstanceWriter instanceWriter(patchWriter, matrixMinMaxScales[1]);
107 
108     if (!(fAttribs & PatchAttribs::kStrokeParams)) {
109         // Strokes are static. Calculate tolerances once.
110         const SkStrokeRec& stroke = pathStrokeList->fStroke;
111         float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(),
112                                                                        stroke.getWidth());
113         float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian(
114                 instanceWriter.parametricPrecision(), localStrokeWidth);
115         maxEdgesInJoin = worst_case_edges_in_join(stroke.getJoin(), numRadialSegmentsPerRadian);
116         maxRadialSegmentsPerRadian = numRadialSegmentsPerRadian;
117     }
118 
119     // Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we
120     // have dynamic stroke.
121     StrokeToleranceBuffer toleranceBuffer(instanceWriter.parametricPrecision());
122 
123     for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
124         const SkStrokeRec& stroke = pathStroke->fStroke;
125         if (fAttribs & PatchAttribs::kStrokeParams) {
126             // Strokes are dynamic. Calculate tolerances every time.
127             float numRadialSegmentsPerRadian =
128                     toleranceBuffer.fetchRadialSegmentsPerRadian(pathStroke);
129             maxEdgesInJoin = std::max(
130                     worst_case_edges_in_join(stroke.getJoin(), numRadialSegmentsPerRadian),
131                     maxEdgesInJoin);
132             maxRadialSegmentsPerRadian = std::max(numRadialSegmentsPerRadian,
133                                                   maxRadialSegmentsPerRadian);
134             patchWriter.updateStrokeParamsAttrib(stroke);
135         }
136         if (fAttribs & PatchAttribs::kColor) {
137             patchWriter.updateColorAttrib(pathStroke->fColor);
138         }
139         StrokeIterator strokeIter(pathStroke->fPath, &pathStroke->fStroke, &shaderMatrix);
140         while (strokeIter.next()) {
141             using Verb = StrokeIterator::Verb;
142             const SkPoint* p = strokeIter.pts();
143             int numChops;
144             switch (strokeIter.verb()) {
145                 case Verb::kContourFinished:
146                     instanceWriter.finishContour();
147                     break;
148                 case Verb::kCircle:
149                     // Round cap or else an empty stroke that is specified to be drawn as a circle.
150                     instanceWriter.writeCircle(p[0]);
151                     [[fallthrough]];
152                 case Verb::kMoveWithinContour:
153                     instanceWriter.setLastControlPoint(p[0]);
154                     break;
155                 case Verb::kLine:
156                     instanceWriter.lineTo(p[0], p[1]);
157                     break;
158                 case Verb::kQuad:
159                     if (ConicHasCusp(p)) {
160                         // The cusp is always at the midtandent.
161                         SkPoint cusp = SkEvalQuadAt(p, SkFindQuadMidTangent(p));
162                         instanceWriter.writeCircle(cusp);
163                         // A quad can only have a cusp if it's flat with a 180-degree turnaround.
164                         instanceWriter.lineTo(p[0], cusp);
165                         instanceWriter.lineTo(cusp, p[2]);
166                     } else {
167                         instanceWriter.quadraticTo(p);
168                     }
169                     break;
170                 case Verb::kConic:
171                     if (ConicHasCusp(p)) {
172                         // The cusp is always at the midtandent.
173                         SkConic conic(p, strokeIter.w());
174                         SkPoint cusp = conic.evalAt(conic.findMidTangent());
175                         instanceWriter.writeCircle(cusp);
176                         // A conic can only have a cusp if it's flat with a 180-degree turnaround.
177                         instanceWriter.lineTo(p[0], cusp);
178                         instanceWriter.lineTo(cusp, p[2]);
179                     } else {
180                         instanceWriter.conicTo(p, strokeIter.w());
181                     }
182                     break;
183                 case Verb::kCubic:
184                     SkPoint chops[10];
185                     float T[2];
186                     bool areCusps;
187                     numChops = FindCubicConvex180Chops(p, T, &areCusps);
188                     if (numChops == 0) {
189                         instanceWriter.cubicConvex180To(p);
190                     } else if (numChops == 1) {
191                         SkChopCubicAt(p, chops, T[0]);
192                         if (areCusps) {
193                             instanceWriter.writeCircle(chops[3]);
194                             // In a perfect world, these 3 points would be be equal after chopping
195                             // on a cusp.
196                             chops[2] = chops[4] = chops[3];
197                         }
198                         instanceWriter.cubicConvex180To(chops);
199                         instanceWriter.cubicConvex180To(chops + 3);
200                     } else {
201                         SkASSERT(numChops == 2);
202                         SkChopCubicAt(p, chops, T[0], T[1]);
203                         if (areCusps) {
204                             instanceWriter.writeCircle(chops[3]);
205                             instanceWriter.writeCircle(chops[6]);
206                             // Two cusps are only possible if it's a flat line with two 180-degree
207                             // turnarounds.
208                             instanceWriter.lineTo(chops[0], chops[3]);
209                             instanceWriter.lineTo(chops[3], chops[6]);
210                             instanceWriter.lineTo(chops[6], chops[9]);
211                         } else {
212                             instanceWriter.cubicConvex180To(chops);
213                             instanceWriter.cubicConvex180To(chops + 3);
214                             instanceWriter.cubicConvex180To(chops + 6);
215                         }
216                     }
217                     break;
218             }
219         }
220     }
221 
222     // The maximum rotation we can have in a stroke is 180 degrees (SK_ScalarPI radians).
223     int maxRadialSegmentsInStroke =
224             std::max(SkScalarCeilToInt(maxRadialSegmentsPerRadian * SK_ScalarPI), 1);
225 
226     int maxParametricSegmentsInStroke = patchWriter.requiredFixedSegments();
227     SkASSERT(maxParametricSegmentsInStroke >= 1);
228 
229     // Now calculate the maximum number of edges we will need in the stroke portion of the instance.
230     // The first and last edges in a stroke are shared by both the parametric and radial sets of
231     // edges, so the total number of edges is:
232     //
233     //   numCombinedEdges = numParametricEdges + numRadialEdges - 2
234     //
235     // It's also important to differentiate between the number of edges and segments in a strip:
236     //
237     //   numSegments = numEdges - 1
238     //
239     // So the total number of combined edges in the stroke is:
240     //
241     //   numEdgesInStroke = numParametricSegments + 1 + numRadialSegments + 1 - 2
242     //                    = numParametricSegments + numRadialSegments
243     //
244     int maxEdgesInStroke = maxRadialSegmentsInStroke + maxParametricSegmentsInStroke;
245 
246     // Each triangle strip has two sections: It starts with a join then transitions to a stroke. The
247     // number of edges in an instance is the sum of edges from the join and stroke sections both.
248     // NOTE: The final join edge and the first stroke edge are co-located, however we still need to
249     // emit both because the join's edge is half-width and the stroke's is full-width.
250     return maxEdgesInJoin + maxEdgesInStroke;
251 }
252 
InitializeVertexIDFallbackBuffer(VertexWriter vertexWriter,size_t bufferSize)253 void StrokeFixedCountTessellator::InitializeVertexIDFallbackBuffer(VertexWriter vertexWriter,
254                                                                    size_t bufferSize) {
255     SkASSERT(bufferSize % (sizeof(float) * 2) == 0);
256     int edgeCount = bufferSize / (sizeof(float) * 2);
257     for (int i = 0; i < edgeCount; ++i) {
258         vertexWriter << (float)i << (float)-i;
259     }
260 }
261 
262 #if SK_GPU_V1
263 
264 SKGPU_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
265 
prepare(GrMeshDrawTarget * target,const SkMatrix & shaderMatrix,std::array<float,2> matrixMinMaxScales,PathStrokeList * pathStrokeList,int totalCombinedStrokeVerbCnt)266 int StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target,
267                                          const SkMatrix& shaderMatrix,
268                                          std::array<float,2> matrixMinMaxScales,
269                                          PathStrokeList* pathStrokeList,
270                                          int totalCombinedStrokeVerbCnt) {
271     // NOTE: For now InstanceWriter manually chops curves that exceed kMaxParametricSegments_pow4,
272     // so passing in kMaxParametricSegments to PatchWriter avoids its auto-chopping while still
273     // correctly accumulating the min required segment count.
274     PatchWriter patchWriter(target, this, kMaxParametricSegments,
275                             this->patchPreallocCount(totalCombinedStrokeVerbCnt));
276 
277     fFixedEdgeCount = this->writePatches(patchWriter,
278                                          shaderMatrix,
279                                          matrixMinMaxScales,
280                                          pathStrokeList);
281 
282     // Don't draw more vertices than can be indexed by a signed short. We just have to draw the line
283     // somewhere and this seems reasonable enough. (There are two vertices per edge, so 2^14 edges
284     // make 2^15 vertices.)
285     fFixedEdgeCount = std::min(fFixedEdgeCount, (1 << 14) - 1);
286 
287     if (!target->caps().shaderCaps()->vertexIDSupport()) {
288         // Our shader won't be able to use sk_VertexID. Bind a fallback vertex buffer with the IDs
289         // in it instead.
290         constexpr static int kMaxVerticesInFallbackBuffer = 2048;
291         fFixedEdgeCount = std::min(fFixedEdgeCount, kMaxVerticesInFallbackBuffer/2);
292 
293         SKGPU_DEFINE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
294 
295         fVertexBufferIfNoIDSupport = target->resourceProvider()->findOrMakeStaticBuffer(
296                 GrGpuBufferType::kVertex,
297                 kMaxVerticesInFallbackBuffer * sizeof(float),
298                 gVertexIDFallbackBufferKey,
299                 InitializeVertexIDFallbackBuffer);
300     }
301 
302     return fFixedEdgeCount;
303 }
304 
draw(GrOpFlushState * flushState) const305 void StrokeFixedCountTessellator::draw(GrOpFlushState* flushState) const {
306     if (fVertexChunkArray.empty() || fFixedEdgeCount <= 0) {
307         return;
308     }
309     if (!flushState->caps().shaderCaps()->vertexIDSupport() &&
310         !fVertexBufferIfNoIDSupport) {
311         return;
312     }
313     for (const auto& instanceChunk : fVertexChunkArray) {
314         flushState->bindBuffers(nullptr, instanceChunk.fBuffer, fVertexBufferIfNoIDSupport);
315         flushState->drawInstanced(instanceChunk.fCount,
316                                   instanceChunk.fBase,
317                                   fFixedEdgeCount * 2,
318                                   0);
319     }
320 }
321 
322 #endif
323 
324 }  // namespace skgpu
325