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