• 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, SkSLType::kFloat4, "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, SkSLType::kFloat, "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{SkSLType::kHalf4};
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, SkSLType::kFloat, "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                                                             SkSLType::kFloat4, "affineMatrix",
82                                                             &affineMatrixName);
83     fTranslateUniform = args.fUniformHandler->addUniform(nullptr, kVertex_GrShaderFlag,
84                                                          SkSLType::kFloat2, "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; })", skgpu::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 
128     // Find the starting and ending tangents.
129     float2 tan0 = ((p0 == p1) ? (p1 == p2) ? p3 : p2 : p1) - p0;
130     float2 tan1 = p3 - ((p3 == p2) ? (p2 == p1) ? p0 : p1 : p2);
131     if (tan0 == float2(0)) {
132         // The stroke is a point. This special case tells us to draw a stroke-width circle as a
133         // 180 degree point stroke instead.
134         tan0 = float2(1,0);
135         tan1 = float2(-1,0);
136     })");
137 
138     if (args.fShaderCaps->vertexIDSupport()) {
139         // If we don't have sk_VertexID support then "edgeID" already came in as a vertex attrib.
140         args.fVertBuilder->codeAppend(R"(
141         float edgeID = float(sk_VertexID >> 1);
142         if ((sk_VertexID & 1) != 0) {
143             edgeID = -edgeID;
144         })");
145     }
146 
147     // Potential optimization: (shader.hasDynamicStroke() && shader.hasRoundJoins())?
148     if (shader.stroke().getJoin() == SkPaint::kRound_Join || shader.hasDynamicStroke()) {
149         args.fVertBuilder->codeAppend(R"(
150         // Determine how many edges to give to the round join. We emit the first and final edges
151         // of the join twice: once full width and once restricted to half width. This guarantees
152         // perfect seaming by matching the vertices from the join as well as from the strokes on
153         // either side.
154         float joinRads = acos(cosine_between_vectors(p0 - lastControlPoint, tan0));
155         float numRadialSegmentsInJoin = max(ceil(joinRads * NUM_RADIAL_SEGMENTS_PER_RADIAN), 1);
156         // +2 because we emit the beginning and ending edges twice (see above comment).
157         float numEdgesInJoin = numRadialSegmentsInJoin + 2;
158         // The stroke section needs at least two edges. Don't assign more to the join than
159         // "NUM_TOTAL_EDGES - 2".
160         numEdgesInJoin = min(numEdgesInJoin, NUM_TOTAL_EDGES - 2);)");
161         if (shader.mode() == GrStrokeTessellationShader::Mode::kLog2Indirect) {
162             args.fVertBuilder->codeAppend(R"(
163             // Negative argsAttr.z means the join is an internal chop or circle, and both of
164             // those have empty joins. All we need is a bevel join.
165             if (argsAttr.z < 0) {
166                 // +2 because we emit the beginning and ending edges twice (see above comment).
167                 numEdgesInJoin = 1 + 2;
168             })");
169         }
170         if (shader.hasDynamicStroke()) {
171             args.fVertBuilder->codeAppend(R"(
172             if (JOIN_TYPE >= 0 /*Is the join not a round type?*/) {
173                 // Bevel and miter joins get 1 and 2 segments respectively.
174                 // +2 because we emit the beginning and ending edges twice (see above comments).
175                 numEdgesInJoin = sign(JOIN_TYPE) + 1 + 2;
176             })");
177         }
178     } else {
179         args.fVertBuilder->codeAppendf(R"(
180         float numEdgesInJoin = %i;)",
181         skgpu::StrokeFixedCountTessellator::NumFixedEdgesInJoin(joinType));
182     }
183 
184     args.fVertBuilder->codeAppend(R"(
185     // Find which direction the curve turns.
186     // NOTE: Since the curve is not allowed to inflect, we can just check F'(.5) x F''(.5).
187     // NOTE: F'(.5) x F''(.5) has the same sign as (P2 - P0) x (P3 - P1)
188     float turn = cross_length_2d(p2 - p0, p3 - p1);
189     float combinedEdgeID = abs(edgeID) - numEdgesInJoin;
190     if (combinedEdgeID < 0) {
191         tan1 = tan0;
192         // Don't let tan0 become zero. The code as-is isn't built to handle that case. tan0=0
193         // means the join is disabled, and to disable it with the existing code we can leave
194         // tan0 equal to tan1.
195         if (lastControlPoint != p0) {
196             tan0 = p0 - lastControlPoint;
197         }
198         turn = cross_length_2d(tan0, tan1);
199     }
200 
201     // Calculate the curve's starting angle and rotation.
202     float cosTheta = cosine_between_vectors(tan0, tan1);
203     float rotation = acos(cosTheta);
204     if (turn < 0) {
205         // Adjust sign of rotation to match the direction the curve turns.
206         rotation = -rotation;
207     }
208 
209     float numRadialSegments;
210     float strokeOutset = sign(edgeID);
211     if (combinedEdgeID < 0) {
212         // We belong to the preceding join. The first and final edges get duplicated, so we only
213         // have "numEdgesInJoin - 2" segments.
214         numRadialSegments = numEdgesInJoin - 2;
215         numParametricSegments = 1;  // Joins don't have parametric segments.
216         p3 = p2 = p1 = p0;  // Colocate all points on the junction point.
217         // Shift combinedEdgeID to the range [-1, numRadialSegments]. This duplicates the first
218         // edge and lands one edge at the very end of the join. (The duplicated final edge will
219         // actually come from the section of our strip that belongs to the stroke.)
220         combinedEdgeID += numRadialSegments + 1;
221         // We normally restrict the join on one side of the junction, but if the tangents are
222         // nearly equivalent this could theoretically result in bad seaming and/or cracks on the
223         // side we don't put it on. If the tangents are nearly equivalent then we leave the join
224         // double-sided.
225         float sinEpsilon = 1e-2;  // ~= sin(180deg / 3000)
226         bool tangentsNearlyParallel =
227                 (abs(turn) * inversesqrt(dot(tan0, tan0) * dot(tan1, tan1))) < sinEpsilon;
228         if (!tangentsNearlyParallel || dot(tan0, tan1) < 0) {
229             // There are two edges colocated at the beginning. Leave the first one double sided
230             // for seaming with the previous stroke. (The double sided edge at the end will
231             // actually come from the section of our strip that belongs to the stroke.)
232             if (combinedEdgeID >= 0) {
233                 strokeOutset = (turn < 0) ? min(strokeOutset, 0) : max(strokeOutset, 0);
234             }
235         }
236         combinedEdgeID = max(combinedEdgeID, 0);
237     } else {
238         // We belong to the stroke.
239         float maxCombinedSegments = NUM_TOTAL_EDGES - numEdgesInJoin - 1;
240         numRadialSegments = max(ceil(abs(rotation) * NUM_RADIAL_SEGMENTS_PER_RADIAN), 1);
241         numRadialSegments = min(numRadialSegments, maxCombinedSegments);
242         numParametricSegments = min(numParametricSegments,
243                                     maxCombinedSegments - numRadialSegments + 1);
244     }
245 
246     // Additional parameters for emitTessellationCode().
247     float radsPerSegment = rotation / numRadialSegments;
248     float numCombinedSegments = numParametricSegments + numRadialSegments - 1;
249     bool isFinalEdge = (combinedEdgeID >= numCombinedSegments);
250     if (combinedEdgeID > numCombinedSegments) {
251         strokeOutset = 0;  // The strip has more edges than we need. Drop this one.
252     })");
253 
254     if (joinType == SkPaint::kMiter_Join || shader.hasDynamicStroke()) {
255         args.fVertBuilder->codeAppendf(R"(
256         // Edge #2 extends to the miter point.
257         if (abs(edgeID) == 2 && %s) {
258             strokeOutset *= miter_extent(cosTheta, JOIN_TYPE/*miterLimit*/);
259         })", shader.hasDynamicStroke() ? "JOIN_TYPE > 0/*Is the join a miter type?*/" : "true");
260     }
261 
262     this->emitTessellationCode(shader, &args.fVertBuilder->code(), gpArgs, *args.fShaderCaps);
263 
264     this->emitFragmentCode(shader, args);
265 }
266