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