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