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/GrStrokeTessellateShader.h"
9
10#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
11#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
12#include "src/gpu/glsl/GrGLSLVarying.h"
13#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
14#include "src/gpu/tessellate/GrWangsFormula.h"
15
16class GrStrokeTessellateShader::Impl : public GrGLSLGeometryProcessor {
17public:
18 const char* getMiterLimitUniformName(const GrGLSLUniformHandler& uniformHandler) const {
19 return uniformHandler.getUniformCStr(fMiterLimitUniform);
20 }
21
22 const char* getSkewMatrixUniformName(const GrGLSLUniformHandler& uniformHandler) const {
23 return uniformHandler.getUniformCStr(fSkewMatrixUniform);
24 }
25
26private:
27 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
28 const auto& shader = args.fGP.cast<GrStrokeTessellateShader>();
29 args.fVaryingHandler->emitAttributes(shader);
30
31 fMiterLimitUniform = args.fUniformHandler->addUniform(nullptr, kTessControl_GrShaderFlag,
32 kFloat_GrSLType, "miterLimit",
33 nullptr);
34
35 if (!shader.viewMatrix().isIdentity()) {
36 fSkewMatrixUniform = args.fUniformHandler->addUniform(nullptr,
37 kTessEvaluation_GrShaderFlag,
38 kFloat3x3_GrSLType, "skewMatrix",
39 nullptr);
40 }
41
42 const char* colorUniformName;
43 fColorUniform = args.fUniformHandler->addUniform(nullptr, kFragment_GrShaderFlag,
44 kHalf4_GrSLType, "color",
45 &colorUniformName);
46
47 // The vertex shader is pure pass-through. Stroke widths and normals are defined in local
48 // path space, so we don't apply the view matrix until after tessellation.
49 args.fVertBuilder->declareGlobal(GrShaderVar("P", kFloat2_GrSLType,
50 GrShaderVar::TypeModifier::Out));
51 args.fVertBuilder->codeAppendf("P = inputPoint;");
52
53 // The fragment shader just outputs a uniform color.
54 args.fFragBuilder->codeAppendf("%s = %s;", args.fOutputColor, colorUniformName);
55 args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
56 }
57
58 void setData(const GrGLSLProgramDataManager& pdman,
59 const GrPrimitiveProcessor& primProc) override {
60 const auto& shader = primProc.cast<GrStrokeTessellateShader>();
61
62 if (shader.fMiterLimitOrZero != 0 && fCachedMiterLimitValue != shader.fMiterLimitOrZero) {
63 pdman.set1f(fMiterLimitUniform, shader.fMiterLimitOrZero);
64 fCachedMiterLimitValue = shader.fMiterLimitOrZero;
65 }
66
67 if (!shader.viewMatrix().isIdentity()) {
68 // Since the view matrix is applied after tessellation, it cannot expand the geometry in
69 // any direction.
70 SkASSERT(shader.viewMatrix().getMaxScale() < 1 + SK_ScalarNearlyZero);
71 pdman.setSkMatrix(fSkewMatrixUniform, shader.viewMatrix());
72 }
73
74 pdman.set4fv(fColorUniform, 1, shader.fColor.vec());
75 }
76
77 GrGLSLUniformHandler::UniformHandle fMiterLimitUniform;
78 GrGLSLUniformHandler::UniformHandle fSkewMatrixUniform;
79 GrGLSLUniformHandler::UniformHandle fColorUniform;
80
81 float fCachedMiterLimitValue = -1;
82};
83
84SkString GrStrokeTessellateShader::getTessControlShaderGLSL(
85 const GrGLSLPrimitiveProcessor* glslPrimProc, const char* versionAndExtensionDecls,
86 const GrGLSLUniformHandler& uniformHandler, const GrShaderCaps& shaderCaps) const {
87 auto impl = static_cast<const GrStrokeTessellateShader::Impl*>(glslPrimProc);
88
89 SkString code(versionAndExtensionDecls);
90 code.append("layout(vertices = 1) out;\n");
91
92 // TODO: CCPR stroking was written with a linearization tolerance of 1/8 pixel. Readdress this
93 // ASAP to see if we can use GrTessellationPathRenderer::kLinearizationIntolerance (1/4 pixel)
94 // instead.
95 constexpr static float kIntolerance = 8; // 1/8 pixel.
96 code.appendf("const float kTolerance = %f;\n", 1/kIntolerance);
97 code.appendf("const float kCubicK = %f;\n", GrWangsFormula::cubic_k(kIntolerance));
98
99 const char* miterLimitName = impl->getMiterLimitUniformName(uniformHandler);
100 code.appendf("uniform float %s;\n", miterLimitName);
101 code.appendf("#define uMiterLimit %s\n", miterLimitName);
102
103 code.append(R"(
104 in vec2 P[];
105
106 out vec4 X[];
107 out vec4 Y[];
108 out vec2 fanAngles[];
109 out vec2 strokeRadii[];
110 out vec2 outsetClamp[];
111
112 void main() {
113 // The 5th point contains the patch type and stroke radius.
114 float strokeRadius = P[4].y;
115
116 X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x);
117 Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y);
118 fanAngles[gl_InvocationID /*== 0*/] = vec2(0);
119 strokeRadii[gl_InvocationID /*== 0*/] = vec2(strokeRadius);
120 outsetClamp[gl_InvocationID /*== 0*/] = vec2(-1, 1);
121
122 // Calculate how many linear segments to chop this curve into.
123 // (See GrWangsFormula::cubic().)
124 float numSegments = sqrt(kCubicK * length(max(abs(P[2] - P[1]*2.0 + P[0]),
125 abs(P[3] - P[2]*2.0 + P[1]))));
126
127 // A patch can override the number of segments it gets chopped into by passing a
128 // positive value as P[4].x.
129 if (P[4].x > 0) {
130 numSegments = P[4].x;
131 }
132
133 // A negative value in P[4].x means this patch actually represents a join instead
134 // of a stroked cubic. Joins are implemented as radial fans from the junction point.
135 if (P[4].x < 0) {
136 // Start by finding the angle between the tangents coming in and out of the
137 // join.
138 vec2 c0 = P[1] - P[0];
139 vec2 c1 = P[3] - P[2];
140 float theta = atan(determinant(mat2(c0, c1)), dot(c0, c1));
141
142 // Determine the beginning and end angles of our join.
143 fanAngles[gl_InvocationID /*== 0*/] = atan(c0.y, c0.x) + vec2(0, theta);
144
145 float joinType = P[4].x;
146 if (joinType <= -3) {
147 // Round join. Decide how many fan segments we need in order to be smooth.
148 numSegments = abs(theta) / (2 * acos(1 - kTolerance/strokeRadius));
149 } else if (joinType == -2) {
150 // Miter join. Draw a fan with 2 segments and lengthen the interior radius
151 // so it matches the miter point.
152 // (Or draw a 1-segment fan if we exceed the miter limit.)
153 float miterRatio = 1.0 / cos(.5 * theta);
154 strokeRadii[gl_InvocationID /*== 0*/] = strokeRadius * vec2(1, miterRatio);
155 numSegments = (miterRatio <= uMiterLimit) ? 2.0 : 1.0;
156 } else {
157 // Bevel join. Make a fan with only one segment.
158 numSegments = 1;
159 }
160
161 if (strokeRadius * abs(theta) < kTolerance) {
162 // The join angle is too tight to guarantee there won't be gaps on the
163 // inside of the junction. Just in case our join was supposed to only go on
164 // the outside, switch to an internal bevel that ties all 4 incoming
165 // vertices together. The join angle is so tight that bevels, miters, and
166 // rounds will all look the same anyway.
167 numSegments = 1;
168 // Paranoia. The next shader uses "fanAngles.x != fanAngles.y" as the test
169 // to decide whether it is emitting a cubic or a fan. But if theta is close
170 // enough to zero, that might fail. Assign arbitrary, nonequal values. This
171 // is fine because we will only draw one segment with vertices at T=0 and
172 // T=1, and the shader won't use fanAngles on the two outer vertices.
173 fanAngles[gl_InvocationID /*== 0*/] = vec2(1, 0);
174 } else if (joinType != -4) {
175 // This is a standard join. Restrict it to the outside of the junction.
176 outsetClamp[gl_InvocationID /*== 0*/] = mix(
177 vec2(-1, 1), vec2(0), lessThan(vec2(-theta, theta), vec2(0)));
178 }
179 }
180
181 // Tessellate a "strip" of numSegments quads.
182 numSegments = max(1, numSegments);
183 gl_TessLevelInner[0] = numSegments;
184 gl_TessLevelInner[1] = 2.0;
185 gl_TessLevelOuter[0] = 2.0;
186 gl_TessLevelOuter[1] = numSegments;
187 gl_TessLevelOuter[2] = 2.0;
188 gl_TessLevelOuter[3] = numSegments;
189 }
190 )");
191
192 return code;
193}
194
195SkString GrStrokeTessellateShader::getTessEvaluationShaderGLSL(
196 const GrGLSLPrimitiveProcessor* glslPrimProc, const char* versionAndExtensionDecls,
197 const GrGLSLUniformHandler& uniformHandler, const GrShaderCaps&) const {
198 auto impl = static_cast<const GrStrokeTessellateShader::Impl*>(glslPrimProc);
199
200 SkString code(versionAndExtensionDecls);
201 code.append("layout(quads, equal_spacing, ccw) in;\n");
202
203 const char* skewMatrixName = nullptr;
204 if (!this->viewMatrix().isIdentity()) {
205 skewMatrixName = impl->getSkewMatrixUniformName(uniformHandler);
206 code.appendf("uniform mat3x3 %s;\n", skewMatrixName);
207 }
208
209 code.append(R"(
210 in vec4 X[];
211 in vec4 Y[];
212 in vec2 fanAngles[];
213 in vec2 strokeRadii[];
214 in vec2 outsetClamp[];
215
216 uniform vec4 sk_RTAdjust;
217
218 void main() {
219 float strokeRadius = strokeRadii[0].x;
220
221 mat4x2 P = transpose(mat2x4(X[0], Y[0]));
222 float T = gl_TessCoord.x;
223
224 // Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability.
225 vec2 ab = mix(P[0], P[1], T);
226 vec2 bc = mix(P[1], P[2], T);
227 vec2 cd = mix(P[2], P[3], T);
228 vec2 abc = mix(ab, bc, T);
229 vec2 bcd = mix(bc, cd, T);
230 vec2 position = mix(abc, bcd, T);
231
232 // Find the normalized tangent vector at T.
233 vec2 tangent = bcd - abc;
234 if (tangent == vec2(0)) {
235 // We get tangent=0 if (P0 == P1 and T == 0), of if (P2 == P3 and T == 1).
236 tangent = (T == 0) ? P[2] - P[0] : P[3] - P[1];
237 }
238 tangent = normalize(tangent);
239
240 // If the fanAngles are not equal, it means this patch actually represents a join
241 // instead of a stroked cubic. Joins are implemented as radial fans from the
242 // junction point.
243 //
244 // The caller carefully sets up the control points on junctions so the above math
245 // lines up exactly with the incoming stroke vertices at T=0 and T=1, but for
246 // interior T values we fall back on the fan's arc equation instead.
247 if (fanAngles[0].x != fanAngles[0].y && T != 0 && T != 1) {
248 position = P[0];
249 float theta = mix(fanAngles[0].x, fanAngles[0].y, T);
250 tangent = vec2(cos(theta), sin(theta));
251 // Miters use a larger radius for the internal vertex.
252 strokeRadius = strokeRadii[0].y;
253 }
254
255 // Determine how far to outset our vertex orthogonally from the curve.
256 float outset = gl_TessCoord.y * 2 - 1;
257 outset = clamp(outset, outsetClamp[0].x, outsetClamp[0].y);
258 outset *= strokeRadius;
259
260 vec2 vertexpos = position + vec2(-tangent.y, tangent.x) * outset;
261 )");
262
263 // Transform after tessellation. Stroke widths and normals are defined in (pre-transform) local
264 // path space.
265 if (!this->viewMatrix().isIdentity()) {
266 code.appendf("vertexpos = (%s * vec3(vertexpos, 1)).xy;\n", skewMatrixName);
267 }
268
269 code.append(R"(
270 gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
271 }
272 )");
273
274 return code;
275}
276
277GrGLSLPrimitiveProcessor* GrStrokeTessellateShader::createGLSLInstance(
278 const GrShaderCaps&) const {
279 return new Impl;
280}
281