• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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/shaders/GrStrokeTessellationShader.h"
9 
10 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
11 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
12 #include "src/gpu/tessellate/StrokeFixedCountTessellator.h"
13 #include "src/gpu/tessellate/WangsFormula.h"
14 
15 using skgpu::VertexWriter;
16 
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)17 void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
18     const auto& shader = args.fGeomProc.cast<GrStrokeTessellationShader>();
19     SkPaint::Join joinType = shader.stroke().getJoin();
20     args.fVaryingHandler->emitAttributes(shader);
21 
22     args.fVertBuilder->defineConstant("float", "PI", "3.141592653589793238");
23 
24     // Helper functions.
25     if (shader.hasDynamicStroke()) {
26         args.fVertBuilder->insertFunction(kNumRadialSegmentsPerRadianFn);
27     }
28     args.fVertBuilder->insertFunction(kCosineBetweenVectorsFn);
29     args.fVertBuilder->insertFunction(kMiterExtentFn);
30     args.fVertBuilder->insertFunction(kUncheckedMixFn);
31     args.fVertBuilder->insertFunction(skgpu::wangs_formula::as_sksl().c_str());
32 
33     // Tessellation control uniforms and/or dynamic attributes.
34     if (!shader.hasDynamicStroke()) {
35         // [PARAMETRIC_PRECISION, NUM_RADIAL_SEGMENTS_PER_RADIAN, JOIN_TYPE, STROKE_RADIUS]
36         const char* tessArgsName;
37         fTessControlArgsUniform = args.fUniformHandler->addUniform(
38                 nullptr, kVertex_GrShaderFlag, kFloat4_GrSLType, "tessControlArgs",
39                 &tessArgsName);
40         args.fVertBuilder->codeAppendf(R"(
41         float PARAMETRIC_PRECISION = %s.x;
42         float NUM_RADIAL_SEGMENTS_PER_RADIAN = %s.y;
43         float JOIN_TYPE = %s.z;
44         float STROKE_RADIUS = %s.w;)", tessArgsName, tessArgsName, tessArgsName, tessArgsName);
45     } else {
46         const char* parametricPrecisionName;
47         fTessControlArgsUniform = args.fUniformHandler->addUniform(
48                 nullptr, kVertex_GrShaderFlag, kFloat_GrSLType, "parametricPrecision",
49                 &parametricPrecisionName);
50         args.fVertBuilder->codeAppendf(R"(
51         float PARAMETRIC_PRECISION = %s;
52         float STROKE_RADIUS = dynamicStrokeAttr.x;
53         float NUM_RADIAL_SEGMENTS_PER_RADIAN = num_radial_segments_per_radian(
54                 PARAMETRIC_PRECISION, STROKE_RADIUS);
55         float JOIN_TYPE = dynamicStrokeAttr.y;)", parametricPrecisionName);
56     }
57 
58     if (shader.hasDynamicColor()) {
59         // Create a varying for color to get passed in through.
60         GrGLSLVarying dynamicColor{kHalf4_GrSLType};
61         args.fVaryingHandler->addVarying("dynamicColor", &dynamicColor);
62         args.fVertBuilder->codeAppendf("%s = dynamicColorAttr;", dynamicColor.vsOut());
63         fDynamicColorName = dynamicColor.fsIn();
64     }
65 
66     if (shader.mode() == GrStrokeTessellationShader::Mode::kLog2Indirect) {
67         args.fVertBuilder->codeAppend(R"(
68         float NUM_TOTAL_EDGES = abs(argsAttr.z);)");
69     } else {
70         SkASSERT(shader.mode() == GrStrokeTessellationShader::Mode::kFixedCount);
71         const char* edgeCountName;
72         fEdgeCountUniform = args.fUniformHandler->addUniform(
73                 nullptr, kVertex_GrShaderFlag, kFloat_GrSLType, "edgeCount", &edgeCountName);
74         args.fVertBuilder->codeAppendf(R"(
75         float NUM_TOTAL_EDGES = %s;)", edgeCountName);
76     }
77 
78     // View matrix uniforms.
79     const char* translateName, *affineMatrixName;
80     fAffineMatrixUniform = args.fUniformHandler->addUniform(nullptr, kVertex_GrShaderFlag,
81                                                             kFloat4_GrSLType, "affineMatrix",
82                                                             &affineMatrixName);
83     fTranslateUniform = args.fUniformHandler->addUniform(nullptr, kVertex_GrShaderFlag,
84                                                          kFloat2_GrSLType, "translate",
85                                                          &translateName);
86     args.fVertBuilder->codeAppendf("float2x2 AFFINE_MATRIX = float2x2(%s);\n", affineMatrixName);
87     args.fVertBuilder->codeAppendf("float2 TRANSLATE = %s;\n", translateName);
88 
89     if (shader.hasExplicitCurveType()) {
90         args.fVertBuilder->insertFunction(SkStringPrintf(R"(
91         bool is_conic_curve() { return curveTypeAttr != %g; })", kCubicCurveType).c_str());
92     } else {
93         args.fVertBuilder->insertFunction(R"(
94         bool is_conic_curve() { return isinf(pts23Attr.w); })");
95     }
96 
97     // Tessellation code.
98     args.fVertBuilder->codeAppend(R"(
99     float2 p0=pts01Attr.xy, p1=pts01Attr.zw, p2=pts23Attr.xy, p3=pts23Attr.zw;
100     float2 lastControlPoint = argsAttr.xy;
101     float w = -1;  // w<0 means the curve is an integral cubic.
102     if (is_conic_curve()) {
103         // Conics are 3 points, with the weight in p3.
104         w = p3.x;
105         p3 = p2;  // Setting p3 equal to p2 works for the remaining rotational logic.
106     })");
107     if (shader.stroke().isHairlineStyle()) {
108         // Hairline case. Transform the points before tessellation. We can still hold off on the
109         // translate until the end; we just need to perform the scale and skew right now.
110         args.fVertBuilder->codeAppend(R"(
111         p0 = AFFINE_MATRIX * p0;
112         p1 = AFFINE_MATRIX * p1;
113         p2 = AFFINE_MATRIX * p2;
114         p3 = AFFINE_MATRIX * p3;
115         lastControlPoint = AFFINE_MATRIX * lastControlPoint;)");
116     }
117 
118     args.fVertBuilder->codeAppend(R"(
119     // Find how many parametric segments this stroke requires.
120     float numParametricSegments;
121     if (w < 0) {
122         numParametricSegments = wangs_formula_cubic(PARAMETRIC_PRECISION, p0, p1, p2, p3,
123                                                     float2x2(1));
124     } else {
125         numParametricSegments = wangs_formula_conic(PARAMETRIC_PRECISION, p0, p1, p2, w);
126     }
127     if (p0 == p1 && p2 == p3) {
128         // This is how we describe lines, but Wang's formula does not return 1 in this case.
129         numParametricSegments = 1;
130     }
131 
132     // Find the starting and ending tangents.
133     float2 tan0 = ((p0 == p1) ? (p1 == p2) ? p3 : p2 : p1) - p0;
134     float2 tan1 = p3 - ((p3 == p2) ? (p2 == p1) ? p0 : p1 : p2);
135     if (tan0 == float2(0)) {
136         // The stroke is a point. This special case tells us to draw a stroke-width circle as a
137         // 180 degree point stroke instead.
138         tan0 = float2(1,0);
139         tan1 = float2(-1,0);
140     })");
141 
142     if (args.fShaderCaps->vertexIDSupport()) {
143         // If we don't have sk_VertexID support then "edgeID" already came in as a vertex attrib.
144         args.fVertBuilder->codeAppend(R"(
145         float edgeID = float(sk_VertexID >> 1);
146         if ((sk_VertexID & 1) != 0) {
147             edgeID = -edgeID;
148         })");
149     }
150 
151     // Potential optimization: (shader.hasDynamicStroke() && shader.hasRoundJoins())?
152     if (shader.stroke().getJoin() == SkPaint::kRound_Join || shader.hasDynamicStroke()) {
153         args.fVertBuilder->codeAppend(R"(
154         // Determine how many edges to give to the round join. We emit the first and final edges
155         // of the join twice: once full width and once restricted to half width. This guarantees
156         // perfect seaming by matching the vertices from the join as well as from the strokes on
157         // either side.
158         float joinRads = acos(cosine_between_vectors(p0 - lastControlPoint, tan0));
159         float numRadialSegmentsInJoin = max(ceil(joinRads * NUM_RADIAL_SEGMENTS_PER_RADIAN), 1);
160         // +2 because we emit the beginning and ending edges twice (see above comment).
161         float numEdgesInJoin = numRadialSegmentsInJoin + 2;
162         // The stroke section needs at least two edges. Don't assign more to the join than
163         // "NUM_TOTAL_EDGES - 2".
164         numEdgesInJoin = min(numEdgesInJoin, NUM_TOTAL_EDGES - 2);)");
165         if (shader.mode() == GrStrokeTessellationShader::Mode::kLog2Indirect) {
166             args.fVertBuilder->codeAppend(R"(
167             // Negative argsAttr.z means the join is an internal chop or circle, and both of
168             // those have empty joins. All we need is a bevel join.
169             if (argsAttr.z < 0) {
170                 // +2 because we emit the beginning and ending edges twice (see above comment).
171                 numEdgesInJoin = 1 + 2;
172             })");
173         }
174         if (shader.hasDynamicStroke()) {
175             args.fVertBuilder->codeAppend(R"(
176             if (JOIN_TYPE >= 0 /*Is the join not a round type?*/) {
177                 // Bevel and miter joins get 1 and 2 segments respectively.
178                 // +2 because we emit the beginning and ending edges twice (see above comments).
179                 numEdgesInJoin = sign(JOIN_TYPE) + 1 + 2;
180             })");
181         }
182     } else {
183         args.fVertBuilder->codeAppendf(R"(
184         float numEdgesInJoin = %i;)",
185         skgpu::StrokeFixedCountTessellator::NumFixedEdgesInJoin(joinType));
186     }
187 
188     args.fVertBuilder->codeAppend(R"(
189     // Find which direction the curve turns.
190     // NOTE: Since the curve is not allowed to inflect, we can just check F'(.5) x F''(.5).
191     // NOTE: F'(.5) x F''(.5) has the same sign as (P2 - P0) x (P3 - P1)
192     float turn = cross(p2 - p0, p3 - p1);
193     float combinedEdgeID = abs(edgeID) - numEdgesInJoin;
194     if (combinedEdgeID < 0) {
195         tan1 = tan0;
196         // Don't let tan0 become zero. The code as-is isn't built to handle that case. tan0=0
197         // means the join is disabled, and to disable it with the existing code we can leave
198         // tan0 equal to tan1.
199         if (lastControlPoint != p0) {
200             tan0 = p0 - lastControlPoint;
201         }
202         turn = cross(tan0, tan1);
203     }
204 
205     // Calculate the curve's starting angle and rotation.
206     float cosTheta = cosine_between_vectors(tan0, tan1);
207     float rotation = acos(cosTheta);
208     if (turn < 0) {
209         // Adjust sign of rotation to match the direction the curve turns.
210         rotation = -rotation;
211     }
212 
213     float numRadialSegments;
214     float strokeOutset = sign(edgeID);
215     if (combinedEdgeID < 0) {
216         // We belong to the preceding join. The first and final edges get duplicated, so we only
217         // have "numEdgesInJoin - 2" segments.
218         numRadialSegments = numEdgesInJoin - 2;
219         numParametricSegments = 1;  // Joins don't have parametric segments.
220         p3 = p2 = p1 = p0;  // Colocate all points on the junction point.
221         // Shift combinedEdgeID to the range [-1, numRadialSegments]. This duplicates the first
222         // edge and lands one edge at the very end of the join. (The duplicated final edge will
223         // actually come from the section of our strip that belongs to the stroke.)
224         combinedEdgeID += numRadialSegments + 1;
225         // We normally restrict the join on one side of the junction, but if the tangents are
226         // nearly equivalent this could theoretically result in bad seaming and/or cracks on the
227         // side we don't put it on. If the tangents are nearly equivalent then we leave the join
228         // double-sided.
229         float sinEpsilon = 1e-2;  // ~= sin(180deg / 3000)
230         bool tangentsNearlyParallel =
231                 (abs(turn) * inversesqrt(dot(tan0, tan0) * dot(tan1, tan1))) < sinEpsilon;
232         if (!tangentsNearlyParallel || dot(tan0, tan1) < 0) {
233             // There are two edges colocated at the beginning. Leave the first one double sided
234             // for seaming with the previous stroke. (The double sided edge at the end will
235             // actually come from the section of our strip that belongs to the stroke.)
236             if (combinedEdgeID >= 0) {
237                 strokeOutset = (turn < 0) ? min(strokeOutset, 0) : max(strokeOutset, 0);
238             }
239         }
240         combinedEdgeID = max(combinedEdgeID, 0);
241     } else {
242         // We belong to the stroke.
243         float maxCombinedSegments = NUM_TOTAL_EDGES - numEdgesInJoin - 1;
244         numRadialSegments = max(ceil(abs(rotation) * NUM_RADIAL_SEGMENTS_PER_RADIAN), 1);
245         numRadialSegments = min(numRadialSegments, maxCombinedSegments);
246         numParametricSegments = min(numParametricSegments,
247                                     maxCombinedSegments - numRadialSegments + 1);
248     }
249 
250     // Additional parameters for emitTessellationCode().
251     float radsPerSegment = rotation / numRadialSegments;
252     float numCombinedSegments = numParametricSegments + numRadialSegments - 1;
253     bool isFinalEdge = (combinedEdgeID >= numCombinedSegments);
254     if (combinedEdgeID > numCombinedSegments) {
255         strokeOutset = 0;  // The strip has more edges than we need. Drop this one.
256     })");
257 
258     if (joinType == SkPaint::kMiter_Join || shader.hasDynamicStroke()) {
259         args.fVertBuilder->codeAppendf(R"(
260         // Edge #2 extends to the miter point.
261         if (abs(edgeID) == 2 && %s) {
262             strokeOutset *= miter_extent(cosTheta, JOIN_TYPE/*miterLimit*/);
263         })", shader.hasDynamicStroke() ? "JOIN_TYPE > 0/*Is the join a miter type?*/" : "true");
264     }
265 
266     this->emitTessellationCode(shader, &args.fVertBuilder->code(), gpArgs, *args.fShaderCaps);
267 
268     this->emitFragmentCode(shader, args);
269 }
270