| 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 |  | 
|---|