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 | |
16 | class GrStrokeTessellateShader::Impl : public GrGLSLGeometryProcessor { |
17 | public: |
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 | |
26 | private: |
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 | |
84 | SkString 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 | |
195 | SkString 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 | |
277 | GrGLSLPrimitiveProcessor* GrStrokeTessellateShader::createGLSLInstance( |
278 | const GrShaderCaps&) const { |
279 | return new Impl; |
280 | } |
281 | |