• 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/GrMeshDrawTarget.h"
12 #include "src/gpu/GrResourceProvider.h"
13 #include "src/gpu/geometry/GrPathUtils.h"
14 #include "src/gpu/tessellate/StrokeIterator.h"
15 #include "src/gpu/tessellate/WangsFormula.h"
16 #include "src/gpu/tessellate/shaders/GrTessellationShader.h"
17 
18 #if SK_GPU_V1
19 #include "src/gpu/GrOpFlushState.h"
20 #endif
21 
22 namespace skgpu {
23 
24 namespace {
25 
26 constexpr static float kMaxParametricSegments_pow4 =
27         StrokeFixedCountTessellator::kMaxParametricSegments_pow4;
28 
29 // Writes out strokes to the given instance chunk array, chopping if necessary so that all instances
30 // require 32 parametric segments or less. (We don't consider radial segments here. The tessellator
31 // will just add enough additional segments to handle a worst-case 180 degree stroke.)
32 class InstanceWriter {
33 public:
InstanceWriter(PatchAttribs attribs,GrMeshDrawTarget * target,float matrixMaxScale,const SkMatrix & viewMatrix,GrVertexChunkArray * patchChunks,size_t instanceStride,int minInstancesPerChunk)34     InstanceWriter(PatchAttribs attribs,
35                    GrMeshDrawTarget* target,
36                    float matrixMaxScale,
37                    const SkMatrix& viewMatrix,
38                    GrVertexChunkArray* patchChunks,
39                    size_t instanceStride,
40                    int minInstancesPerChunk)
41             : fAttribs(attribs)
42             , fChunkBuilder(target, patchChunks, instanceStride, minInstancesPerChunk)
43             , fParametricPrecision(StrokeTolerances::CalcParametricPrecision(matrixMaxScale)) {
44     }
45 
parametricPrecision() const46     float parametricPrecision() const { return fParametricPrecision; }
47 
48     // maxParametricSegments^4, or the number of parametric segments, raised to the 4th power,
49     // that are required by the single instance we've written that requires the most segments.
maxParametricSegments_pow4() const50     float maxParametricSegments_pow4() const { return fMaxParametricSegments_pow4; }
51 
52     // Updates the dynamic stroke state that we will write out with each instance.
updateDynamicStroke(const SkStrokeRec & stroke)53     void updateDynamicStroke(const SkStrokeRec& stroke) {
54         SkASSERT(!fHasDeferredFirstStroke);
55         SkASSERT(fAttribs & PatchAttribs::kStrokeParams);
56         fDynamicStroke.set(stroke);
57     }
58 
59     // Updates the dynamic color state that we will write out with each instance.
updateDynamicColor(const SkPMColor4f & color)60     void updateDynamicColor(const SkPMColor4f& color) {
61         SkASSERT(!fHasDeferredFirstStroke);
62         SkASSERT(fAttribs & PatchAttribs::kColor);
63         bool wideColor = fAttribs & PatchAttribs::kWideColorIfEnabled;
64         SkASSERT(wideColor || color.fitsInBytes());
65         fDynamicColor.set(color, wideColor);
66     }
67 
lineTo(SkPoint start,SkPoint end)68     SK_ALWAYS_INLINE void lineTo(SkPoint start, SkPoint end) {
69         SkPoint cubic[] = {start, start, end, end};
70         SkPoint endControlPoint = start;
71         this->writeStroke(cubic, endControlPoint, GrTessellationShader::kCubicCurveType);
72     }
73 
quadraticTo(const SkPoint p[3])74     SK_ALWAYS_INLINE void quadraticTo(const SkPoint p[3]) {
75         float numParametricSegments_pow4 = wangs_formula::quadratic_pow4(fParametricPrecision, p);
76         if (numParametricSegments_pow4 > kMaxParametricSegments_pow4) {
77             this->chopQuadraticTo(p);
78             return;
79         }
80         SkPoint cubic[4];
81         GrPathUtils::convertQuadToCubic(p, cubic);
82         SkPoint endControlPoint = cubic[2];
83         this->writeStroke(cubic, endControlPoint, GrTessellationShader::kCubicCurveType);
84         fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
85                                                fMaxParametricSegments_pow4);
86     }
87 
conicTo(const SkPoint p[3],float w)88     SK_ALWAYS_INLINE void conicTo(const SkPoint p[3], float w) {
89         float n = wangs_formula::conic_pow2(fParametricPrecision, p, w);
90         float numParametricSegments_pow4 = n*n;
91         if (numParametricSegments_pow4 > kMaxParametricSegments_pow4) {
92             this->chopConicTo({p, w});
93             return;
94         }
95         SkPoint conic[4];
96         GrTessellationShader::WriteConicPatch(p, w, conic);
97         SkPoint endControlPoint = conic[1];
98         this->writeStroke(conic, endControlPoint, GrTessellationShader::kConicCurveType);
99         fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
100                                                fMaxParametricSegments_pow4);
101     }
102 
cubicConvex180To(const SkPoint p[4])103     SK_ALWAYS_INLINE void cubicConvex180To(const SkPoint p[4]) {
104         float numParametricSegments_pow4 = wangs_formula::cubic_pow4(fParametricPrecision, p);
105         if (numParametricSegments_pow4 > kMaxParametricSegments_pow4) {
106             this->chopCubicConvex180To(p);
107             return;
108         }
109         SkPoint endControlPoint = (p[3] != p[2]) ? p[2] : (p[2] != p[1]) ? p[1] : p[0];
110         this->writeStroke(p, endControlPoint, GrTessellationShader::kCubicCurveType);
111         fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
112                                                fMaxParametricSegments_pow4);
113     }
114 
115     // Called when we encounter the verb "kMoveWithinContour". Moves invalidate the previous control
116     // point. The stroke iterator tells us the new value to use for the previous control point.
setLastControlPoint(SkPoint newLastControlPoint)117     void setLastControlPoint(SkPoint newLastControlPoint) {
118         fLastControlPoint = newLastControlPoint;
119         fHasLastControlPoint = true;
120     }
121 
122     // Draws a circle whose diameter is equal to the stroke width. We emit circles at cusp points
123     // round caps, and empty strokes that are specified to be drawn as circles.
writeCircle(SkPoint location)124     void writeCircle(SkPoint location) {
125         if (VertexWriter writer = fChunkBuilder.appendVertex()) {
126             // The shader interprets an empty stroke + empty join as a special case that denotes a
127             // circle, or 180-degree point stroke.
128             writer.fill(location, 5);
129             this->writeDynamicAttribs(&writer, GrTessellationShader::kCubicCurveType);
130         }
131     }
132 
finishContour()133     void finishContour() {
134         if (fHasDeferredFirstStroke) {
135             // We deferred the first stroke because we didn't know the previous control point to use
136             // for its join. We write it out now.
137             SkASSERT(fHasLastControlPoint);
138             this->writeStroke(fDeferredFirstStroke, SkPoint(),
139                               fDeferredCurveTypeIfUnsupportedInfinity);
140             fHasDeferredFirstStroke = false;
141         }
142         fHasLastControlPoint = false;
143     }
144 
145 private:
chopQuadraticTo(const SkPoint p[3])146     void chopQuadraticTo(const SkPoint p[3]) {
147         SkPoint chops[5];
148         SkChopQuadAtHalf(p, chops);
149         this->quadraticTo(chops);
150         this->quadraticTo(chops + 2);
151     }
152 
chopConicTo(const SkConic & conic)153     void chopConicTo(const SkConic& conic) {
154         SkConic chops[2];
155         if (!conic.chopAt(.5f, chops)) {
156             return;
157         }
158         this->conicTo(chops[0].fPts, chops[0].fW);
159         this->conicTo(chops[1].fPts, chops[1].fW);
160     }
161 
chopCubicConvex180To(const SkPoint p[4])162     void chopCubicConvex180To(const SkPoint p[4]) {
163         SkPoint chops[7];
164         SkChopCubicAtHalf(p, chops);
165         this->cubicConvex180To(chops);
166         this->cubicConvex180To(chops + 3);
167     }
168 
writeStroke(const SkPoint p[4],SkPoint endControlPoint,float curveTypeIfUnsupportedInfinity)169     SK_ALWAYS_INLINE void writeStroke(const SkPoint p[4], SkPoint endControlPoint,
170                                       float curveTypeIfUnsupportedInfinity) {
171         if (!fHasLastControlPoint) {
172             // We don't know the previous control point yet to use for the join. Defer writing out
173             // this stroke until the end.
174             memcpy(fDeferredFirstStroke, p, sizeof(fDeferredFirstStroke));
175             fDeferredCurveTypeIfUnsupportedInfinity = curveTypeIfUnsupportedInfinity;
176             fHasDeferredFirstStroke = true;
177             fHasLastControlPoint = true;
178         } else if (VertexWriter writer = fChunkBuilder.appendVertex()) {
179             writer.writeArray(p, 4);
180             writer << fLastControlPoint;
181             this->writeDynamicAttribs(&writer, curveTypeIfUnsupportedInfinity);
182         }
183         fLastControlPoint = endControlPoint;
184     }
185 
writeDynamicAttribs(VertexWriter * writer,float curveTypeIfUnsupportedInfinity)186     SK_ALWAYS_INLINE void writeDynamicAttribs(VertexWriter* writer,
187                                               float curveTypeIfUnsupportedInfinity) {
188         if (fAttribs & PatchAttribs::kStrokeParams) {
189             *writer << fDynamicStroke;
190         }
191         if (fAttribs & PatchAttribs::kColor) {
192             *writer << fDynamicColor;
193         }
194         if (fAttribs & PatchAttribs::kExplicitCurveType) {
195             *writer << curveTypeIfUnsupportedInfinity;
196         }
197     }
198 
discardStroke(const SkPoint p[],int numPts)199     void discardStroke(const SkPoint p[], int numPts) {
200         // Set fLastControlPoint to the next stroke's p0 (which will be equal to the final point of
201         // this stroke). This has the effect of disabling the next stroke's join.
202         fLastControlPoint = p[numPts - 1];
203         fHasLastControlPoint = true;
204     }
205 
206     const PatchAttribs fAttribs;
207     GrVertexChunkBuilder fChunkBuilder;
208     const float fParametricPrecision;
209     float fMaxParametricSegments_pow4 = 1;
210 
211     // We can't write out the first stroke until we know the previous control point for its join.
212     SkPoint fDeferredFirstStroke[4];
213     float fDeferredCurveTypeIfUnsupportedInfinity;
214     SkPoint fLastControlPoint;  // Used to configure the joins in the instance data.
215     bool fHasDeferredFirstStroke = false;
216     bool fHasLastControlPoint = false;
217 
218     // Values for the current dynamic state (if any) that will get written out with each instance.
219     StrokeParams fDynamicStroke;
220     GrVertexColor fDynamicColor;
221 };
222 
223 // 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)224 int worst_case_edges_in_join(SkPaint::Join joinType, float numRadialSegmentsPerRadian) {
225     int numEdges = StrokeFixedCountTessellator::NumFixedEdgesInJoin(joinType);
226     if (joinType == SkPaint::kRound_Join) {
227         // For round joins we need to count the radial edges on our own. Account for a worst-case
228         // join of 180 degrees (SK_ScalarPI radians).
229         numEdges += std::max(SkScalarCeilToInt(numRadialSegmentsPerRadian * SK_ScalarPI) - 1, 0);
230     }
231     return numEdges;
232 }
233 
234 }  // namespace
235 
236 
237 GR_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
238 
prepare(GrMeshDrawTarget * target,const SkMatrix & shaderMatrix,std::array<float,2> matrixMinMaxScales,PathStrokeList * pathStrokeList,int totalCombinedVerbCnt)239 int StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target,
240                                          const SkMatrix& shaderMatrix,
241                                          std::array<float,2> matrixMinMaxScales,
242                                          PathStrokeList* pathStrokeList,
243                                          int totalCombinedVerbCnt) {
244     int maxEdgesInJoin = 0;
245     float maxRadialSegmentsPerRadian = 0;
246 
247     // Over-allocate enough patches for each stroke to chop once, and for 8 extra caps. Since we
248     // have to chop at inflections, points of 180 degree rotation, and anywhere a stroke requires
249     // too many parametric segments, many strokes will end up getting choppped.
250     int strokePreallocCount = totalCombinedVerbCnt * 2;
251     int capPreallocCount = 8;
252     int minInstancesPerChunk = strokePreallocCount + capPreallocCount;
253     InstanceWriter instanceWriter(fAttribs,
254                                   target,
255                                   matrixMinMaxScales[1],
256                                   shaderMatrix,
257                                   &fInstanceChunks,
258                                   sizeof(SkPoint) * 5 + PatchAttribsStride(fAttribs),
259                                   minInstancesPerChunk);
260 
261     if (!(fAttribs & PatchAttribs::kStrokeParams)) {
262         // Strokes are static. Calculate tolerances once.
263         const SkStrokeRec& stroke = pathStrokeList->fStroke;
264         float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(),
265                                                                        stroke.getWidth());
266         float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian(
267                 instanceWriter.parametricPrecision(), localStrokeWidth);
268         maxEdgesInJoin = worst_case_edges_in_join(stroke.getJoin(), numRadialSegmentsPerRadian);
269         maxRadialSegmentsPerRadian = numRadialSegmentsPerRadian;
270     }
271 
272     // Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we
273     // have dynamic stroke.
274     StrokeToleranceBuffer toleranceBuffer(instanceWriter.parametricPrecision());
275 
276     for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
277         const SkStrokeRec& stroke = pathStroke->fStroke;
278         if (fAttribs & PatchAttribs::kStrokeParams) {
279             // Strokes are dynamic. Calculate tolerances every time.
280             float numRadialSegmentsPerRadian =
281                     toleranceBuffer.fetchRadialSegmentsPerRadian(pathStroke);
282             maxEdgesInJoin = std::max(
283                     worst_case_edges_in_join(stroke.getJoin(), numRadialSegmentsPerRadian),
284                     maxEdgesInJoin);
285             maxRadialSegmentsPerRadian = std::max(numRadialSegmentsPerRadian,
286                                                   maxRadialSegmentsPerRadian);
287             instanceWriter.updateDynamicStroke(stroke);
288         }
289         if (fAttribs & PatchAttribs::kColor) {
290             instanceWriter.updateDynamicColor(pathStroke->fColor);
291         }
292         StrokeIterator strokeIter(pathStroke->fPath, &pathStroke->fStroke, &shaderMatrix);
293         while (strokeIter.next()) {
294             const SkPoint* p = strokeIter.pts();
295             switch (strokeIter.verb()) {
296                 using Verb = StrokeIterator::Verb;
297                 int numChops;
298                 case Verb::kContourFinished:
299                     instanceWriter.finishContour();
300                     break;
301                 case Verb::kCircle:
302                     // Round cap or else an empty stroke that is specified to be drawn as a circle.
303                     instanceWriter.writeCircle(p[0]);
304                     [[fallthrough]];
305                 case Verb::kMoveWithinContour:
306                     instanceWriter.setLastControlPoint(p[0]);
307                     break;
308                 case Verb::kLine:
309                     instanceWriter.lineTo(p[0], p[1]);
310                     break;
311                 case Verb::kQuad:
312                     if (GrPathUtils::conicHasCusp(p)) {
313                         // The cusp is always at the midtandent.
314                         SkPoint cusp = SkEvalQuadAt(p, SkFindQuadMidTangent(p));
315                         instanceWriter.writeCircle(cusp);
316                         // A quad can only have a cusp if it's flat with a 180-degree turnaround.
317                         instanceWriter.lineTo(p[0], cusp);
318                         instanceWriter.lineTo(cusp, p[2]);
319                     } else {
320                         instanceWriter.quadraticTo(p);
321                     }
322                     break;
323                 case Verb::kConic:
324                     if (GrPathUtils::conicHasCusp(p)) {
325                         // The cusp is always at the midtandent.
326                         SkConic conic(p, strokeIter.w());
327                         SkPoint cusp = conic.evalAt(conic.findMidTangent());
328                         instanceWriter.writeCircle(cusp);
329                         // A conic can only have a cusp if it's flat with a 180-degree turnaround.
330                         instanceWriter.lineTo(p[0], cusp);
331                         instanceWriter.lineTo(cusp, p[2]);
332                     } else {
333                         instanceWriter.conicTo(p, strokeIter.w());
334                     }
335                     break;
336                 case Verb::kCubic:
337                     SkPoint chops[10];
338                     float T[2];
339                     bool areCusps;
340                     numChops = GrPathUtils::findCubicConvex180Chops(p, T, &areCusps);
341                     if (numChops == 0) {
342                         instanceWriter.cubicConvex180To(p);
343                     } else if (numChops == 1) {
344                         SkChopCubicAt(p, chops, T[0]);
345                         if (areCusps) {
346                             instanceWriter.writeCircle(chops[3]);
347                             // In a perfect world, these 3 points would be be equal after chopping
348                             // on a cusp.
349                             chops[2] = chops[4] = chops[3];
350                         }
351                         instanceWriter.cubicConvex180To(chops);
352                         instanceWriter.cubicConvex180To(chops + 3);
353                     } else {
354                         SkASSERT(numChops == 2);
355                         SkChopCubicAt(p, chops, T[0], T[1]);
356                         if (areCusps) {
357                             instanceWriter.writeCircle(chops[3]);
358                             instanceWriter.writeCircle(chops[6]);
359                             // Two cusps are only possible if it's a flat line with two 180-degree
360                             // turnarounds.
361                             instanceWriter.lineTo(chops[0], chops[3]);
362                             instanceWriter.lineTo(chops[3], chops[6]);
363                             instanceWriter.lineTo(chops[6], chops[9]);
364                         } else {
365                             instanceWriter.cubicConvex180To(chops);
366                             instanceWriter.cubicConvex180To(chops + 3);
367                             instanceWriter.cubicConvex180To(chops + 6);
368                         }
369                     }
370                     break;
371             }
372         }
373     }
374 
375     // The maximum rotation we can have in a stroke is 180 degrees (SK_ScalarPI radians).
376     int maxRadialSegmentsInStroke =
377             std::max(SkScalarCeilToInt(maxRadialSegmentsPerRadian * SK_ScalarPI), 1);
378 
379     int maxParametricSegmentsInStroke = SkScalarCeilToInt(sqrtf(sqrtf(
380             instanceWriter.maxParametricSegments_pow4())));
381     SkASSERT(maxParametricSegmentsInStroke >= 1);  // maxParametricSegments_pow4 is always >= 1.
382 
383     // Now calculate the maximum number of edges we will need in the stroke portion of the instance.
384     // The first and last edges in a stroke are shared by both the parametric and radial sets of
385     // edges, so the total number of edges is:
386     //
387     //   numCombinedEdges = numParametricEdges + numRadialEdges - 2
388     //
389     // It's also important to differentiate between the number of edges and segments in a strip:
390     //
391     //   numSegments = numEdges - 1
392     //
393     // So the total number of combined edges in the stroke is:
394     //
395     //   numEdgesInStroke = numParametricSegments + 1 + numRadialSegments + 1 - 2
396     //                    = numParametricSegments + numRadialSegments
397     //
398     int maxEdgesInStroke = maxRadialSegmentsInStroke + maxParametricSegmentsInStroke;
399 
400     // Each triangle strip has two sections: It starts with a join then transitions to a stroke. The
401     // number of edges in an instance is the sum of edges from the join and stroke sections both.
402     // NOTE: The final join edge and the first stroke edge are co-located, however we still need to
403     // emit both because the join's edge is half-width and the stroke's is full-width.
404     fFixedEdgeCount = maxEdgesInJoin + maxEdgesInStroke;
405 
406     // Don't draw more vertices than can be indexed by a signed short. We just have to draw the line
407     // somewhere and this seems reasonable enough. (There are two vertices per edge, so 2^14 edges
408     // make 2^15 vertices.)
409     fFixedEdgeCount = std::min(fFixedEdgeCount, (1 << 14) - 1);
410 
411     if (!target->caps().shaderCaps()->vertexIDSupport()) {
412         // Our shader won't be able to use sk_VertexID. Bind a fallback vertex buffer with the IDs
413         // in it instead.
414         constexpr static int kMaxVerticesInFallbackBuffer = 2048;
415         fFixedEdgeCount = std::min(fFixedEdgeCount, kMaxVerticesInFallbackBuffer/2);
416 
417         GR_DEFINE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
418 
419         fVertexBufferIfNoIDSupport = target->resourceProvider()->findOrMakeStaticBuffer(
420                 GrGpuBufferType::kVertex,
421                 kMaxVerticesInFallbackBuffer * sizeof(float),
422                 gVertexIDFallbackBufferKey,
423                 InitializeVertexIDFallbackBuffer);
424     }
425 
426     return fFixedEdgeCount;
427 }
428 
InitializeVertexIDFallbackBuffer(VertexWriter vertexWriter,size_t bufferSize)429 void StrokeFixedCountTessellator::InitializeVertexIDFallbackBuffer(VertexWriter vertexWriter,
430                                                                    size_t bufferSize) {
431     SkASSERT(bufferSize % (sizeof(float) * 2) == 0);
432     int edgeCount = bufferSize / (sizeof(float) * 2);
433     for (int i = 0; i < edgeCount; ++i) {
434         vertexWriter << (float)i << (float)-i;
435     }
436 }
437 
438 #if SK_GPU_V1
draw(GrOpFlushState * flushState) const439 void StrokeFixedCountTessellator::draw(GrOpFlushState* flushState) const {
440     if (fInstanceChunks.empty() || fFixedEdgeCount <= 0) {
441         return;
442     }
443     if (!flushState->caps().shaderCaps()->vertexIDSupport() &&
444         !fVertexBufferIfNoIDSupport) {
445         return;
446     }
447     for (const auto& instanceChunk : fInstanceChunks) {
448         flushState->bindBuffers(nullptr, instanceChunk.fBuffer, fVertexBufferIfNoIDSupport);
449         flushState->drawInstanced(instanceChunk.fCount,
450                                   instanceChunk.fBase,
451                                   fFixedEdgeCount * 2,
452                                   0);
453     }
454 }
455 #endif
456 
457 }  // namespace skgpu
458