| /* |
| * Copyright 2020 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/gpu/tessellate/GrStrokeTessellateShader.h" |
| |
| #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" |
| #include "src/gpu/glsl/GrGLSLGeometryProcessor.h" |
| #include "src/gpu/glsl/GrGLSLVarying.h" |
| #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" |
| #include "src/gpu/tessellate/GrWangsFormula.h" |
| |
| constexpr static float kLinearizationIntolerance = |
| GrTessellationPathRenderer::kLinearizationIntolerance; |
| |
| class GrStrokeTessellateShader::Impl : public GrGLSLGeometryProcessor { |
| public: |
| const char* getTessControlArgsUniformName(const GrGLSLUniformHandler& uniformHandler) const { |
| return uniformHandler.getUniformCStr(fTessControlArgsUniform); |
| } |
| const char* getTranslateUniformName(const GrGLSLUniformHandler& uniformHandler) const { |
| return uniformHandler.getUniformCStr(fTranslateUniform); |
| } |
| const char* getAffineMatrixUniformName(const GrGLSLUniformHandler& uniformHandler) const { |
| return uniformHandler.getUniformCStr(fAffineMatrixUniform); |
| } |
| |
| private: |
| void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { |
| const auto& shader = args.fGP.cast<GrStrokeTessellateShader>(); |
| args.fVaryingHandler->emitAttributes(shader); |
| |
| auto* uniHandler = args.fUniformHandler; |
| fTessControlArgsUniform = uniHandler->addUniform(nullptr, kTessControl_GrShaderFlag, |
| kFloat2_GrSLType, "tessControlArgs", |
| nullptr); |
| if (!shader.viewMatrix().isIdentity()) { |
| fTranslateUniform = uniHandler->addUniform(nullptr, kTessEvaluation_GrShaderFlag, |
| kFloat2_GrSLType, "translate", nullptr); |
| fAffineMatrixUniform = uniHandler->addUniform(nullptr, kTessEvaluation_GrShaderFlag, |
| kFloat2x2_GrSLType, "affineMatrix", |
| nullptr); |
| } |
| const char* colorUniformName; |
| fColorUniform = uniHandler->addUniform(nullptr, kFragment_GrShaderFlag, kHalf4_GrSLType, |
| "color", &colorUniformName); |
| |
| // The vertex shader chops the curve into 3 sections in order to meet our tessellation |
| // requirements. The stroke tessellator does not allow curve sections to inflect or to |
| // rotate more than 180 degrees. |
| // |
| // We start by chopping at inflections (if the curve has any), or else at midtangent. If we |
| // still don't have 3 sections after that then we just subdivide uniformly in parametric |
| // space. |
| using TypeModifier = GrShaderVar::TypeModifier; |
| auto* v = args.fVertBuilder; |
| v->defineConstantf("float", "kParametricEpsilon", "1.0 / (%i * 128)", |
| args.fShaderCaps->maxTessellationSegments()); // 1/128 of a segment. |
| v->defineConstantf("float", "kStandardCubicType", "%.0f", Patch::kStandardCubicType); |
| // Declare outputs to the tessellation control shader. |
| v->declareGlobal(GrShaderVar("vsPts01", kFloat4_GrSLType, TypeModifier::Out)); |
| v->declareGlobal(GrShaderVar("vsPts23", kFloat4_GrSLType, TypeModifier::Out)); |
| v->declareGlobal(GrShaderVar("vsPts45", kFloat4_GrSLType, TypeModifier::Out)); |
| v->declareGlobal(GrShaderVar("vsPts67", kFloat4_GrSLType, TypeModifier::Out)); |
| v->declareGlobal(GrShaderVar("vsPts89", kFloat4_GrSLType, TypeModifier::Out)); |
| v->declareGlobal(GrShaderVar("vsTans01", kFloat4_GrSLType, TypeModifier::Out)); |
| v->declareGlobal(GrShaderVar("vsTans23", kFloat4_GrSLType, TypeModifier::Out)); |
| v->declareGlobal(GrShaderVar("vsTurnDirsAndPatchType", kHalf4_GrSLType, TypeModifier::Out)); |
| v->declareGlobal(GrShaderVar("vsStrokeRadius", kFloat_GrSLType, TypeModifier::Out)); |
| v->codeAppendf(R"( |
| // Unpack the control points. |
| float4x2 P = float4x2(inputPts01, inputPts23); |
| half patchType = half(inputArgs.x); |
| |
| // Find the beginning and ending tangents. It's imperative that we compute these tangents |
| // form the original input points or else the seams might crack. |
| // (This works for now because P0=P1=P2 and/or P1=P2=P3 are both illegal.) |
| float2 tan0 = (P[1] == P[0]) ? P[2] - P[0] : P[1] - P[0]; |
| float2 tan1 = (P[3] == P[2]) ? P[3] - P[1] : P[3] - P[2]; |
| |
| // Find the cubic's power basis coefficient matrix "C": |
| // |
| // |Cx Cy| |
| // Cubic(T) = x,y = |T^3 T^2 T 1| * |. . | |
| // |. . | |
| // |. . | |
| // |
| float2x4 C = float4x4(-1, 3, -3, 1, |
| 3, -6, 3, 0, |
| -3, 3, 0, 0, |
| 1, 0, 0, 0) * transpose(P); |
| |
| // Find the curve's inflection function. There are inflections at I==0. |
| // See: https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf |
| // |
| // Inflections are found where: dot(|T^2 T 1|, I) == 0 |
| // |
| float3 I = float3(-3*determinant(float2x2(C)), |
| -3*determinant(float2x2(C[0].xz, C[1].xz)), |
| -determinant(float2x2(C[0].yz, C[1].yz))); |
| |
| // Formulate a quadratic that finds the infection points. |
| float a=I.x, b=I.y, c=I.z; |
| float discr = b*b - 4*a*c; |
| |
| float2 midtangent = float2(0); |
| if (discr <= 0) { |
| // The curve does not inflect. Still chop, but at midtangent instead. This guarantees |
| // the curve does not rotate more that 180 degrees. |
| // |
| // Start by finding u=tan0, v=-tan1. The midtangent is orthogonal to their bisector. |
| float2 u=normalize(tan0), v=-normalize(tan1); |
| |
| if (dot(u,v) < 0) { |
| // u,v are >90 degrees apart. Find the bisector of their normals instead. (After 90 |
| // degrees, the normals start cancelling each other out.) |
| u = float2(-u.y, +u.x); |
| v = float2(+v.y, -v.x); |
| } |
| |
| float2 bisector = u + v; |
| |
| // The midtangent is orthogonal to the bisector. Its T value can be found as: |
| // |
| // dot(midtangent, bisector) == 0: |3*T^2 2*T 1| * float2x3(C) * bisector == 0 |
| // |
| // |3 | |
| // The quadratic formula coefficients are therefore: | 2 | * float2x3(C) * bisector. |
| // | 1| |
| float3 coeffs = float2x3(C) * bisector; |
| if (coeffs == float3(0)) { |
| // The control points are exactly collinear and rotate either 0 or 360 degrees: |
| // tangent is perpendicular to the bisector at every parametric point for which a |
| // tangent is defined. Search instead for any locations where the flat curve does a |
| // 180 degree turn. |
| coeffs = float2x3(C) * tan0; |
| } |
| a=3*coeffs.x, b=2*coeffs.y, c=coeffs.z; |
| |
| // Every curve has a midtangent. If discr is less than zero it's due to FP error. Don't |
| // let it fall below zero. |
| discr = max(b*b - 4*a*c, 0); |
| |
| midtangent = float2(-bisector.y, bisector.x); |
| } |
| |
| // We now have a quadratic formulated to chop the curve at the T values that meet our |
| // tessellation requirements: no inflections, no rotations >180 degrees. Solve for T. |
| float x = sqrt(discr); |
| if (b < 0) { |
| x = -x; |
| } |
| float q = -.5 * (b + x); |
| float2 roots = float2((a != 0) ? q/a : 0, |
| (q != 0) ? c/q : 0); |
| roots = (roots[0] <= roots[1]) ? roots : roots.ts; // Sort. |
| |
| // Decide on two chop points for the curve that are inside 0..1. Start with the roots we |
| // just found, then subdivide uniformly in parametric space if needed. |
| float2 chopT = roots; |
| if (chopT[0] <= kParametricEpsilon) { |
| if (chopT[1] <= kParametricEpsilon) { |
| chopT[1] = 2/3.0; |
| } |
| chopT[0] = chopT[1]; |
| } |
| if (chopT[1] >= 1 - kParametricEpsilon) { |
| if (chopT[0] >= 1 - kParametricEpsilon) { |
| chopT[0] = 1/3.0; |
| } |
| chopT[1] = chopT[0]; |
| } |
| if (chopT[0] == chopT[1]) { |
| // The chop points are colocated. Split the larger section in half. |
| if (chopT[0] > .5) { |
| chopT[0] *= .5; |
| } else { |
| chopT[1] = fma(chopT[1], .5, .5); |
| } |
| } |
| if (patchType != kStandardCubicType) { |
| // The patch is actually a flat line or join. Don't chop it into sections after all. |
| chopT = float2(0); |
| } |
| |
| // Chop the curve at T0. |
| float2 ab = mix(P[0], P[1], chopT[0]); |
| float2 bc = mix(P[1], P[2], chopT[0]); |
| float2 cd = mix(P[2], P[3], chopT[0]); |
| float2 abc = mix(ab, bc, chopT[0]); |
| float2 bcd = mix(bc, cd, chopT[0]); |
| float2 abcd = mix(abc, bcd, chopT[0]); |
| |
| // Chop the curve in reverse at T1. |
| float2 wz = mix(P[2], P[3], chopT[1]); |
| float2 zy = mix(P[1], P[2], chopT[1]); |
| float2 yx = mix(P[0], P[1], chopT[1]); |
| float2 wzy = mix(zy, wz, chopT[1]); |
| float2 zyx = mix(yx, zy, chopT[1]); |
| float2 wzyx = mix(zyx, wzy, chopT[1]); |
| |
| // Find tangents at the chop points. |
| float2 innerTan0 = (chopT[0] != 0) ? bcd - abc : tan0; |
| float2 innerTan1 = (chopT[1] != 0) ? wzy - zyx : tan0; |
| |
| // Figure out which direction the curve turns as T approaches infinity. |
| float inflectValAtInf = (I[0] != 0) ? I[0] : (I[1] != 0) ? I[1] : I[2]; |
| half turnDirAtInf = (inflectValAtInf >= 0) ? +1 : -1; |
| half3 turnDirs; // Will hold the turn directions for each section we just chopped. |
| |
| if (patchType != kStandardCubicType) { |
| // The patch is a flat line or join. We didn't actually subdivide it. |
| innerTan0 = innerTan1 = tan0; |
| // The input points for joins aren't actually a cubic, but the math still works out that |
| // we can use the inflection function to decide which direction the join turns. |
| turnDirs = half3(-turnDirAtInf); |
| } else if (midtangent != float2(0)) { |
| // The curve did not inflect so we chopped at midtangent. The parametric definition of |
| // tangent can be undefined at points that divide rotation in half, so use midtangent |
| // instead at these locations. |
| if (chopT[0] == roots[0] || chopT[0] == roots[1]) { |
| float2 secondDerivate = float2(3*chopT[0], 1) * float2x2(C); |
| float midtangentTurn = determinant(float2x2(midtangent, secondDerivate)); |
| innerTan0 = (midtangentTurn * turnDirAtInf >= 0) ? +midtangent : -midtangent; |
| } |
| if (chopT[1] == roots[0] || chopT[1] == roots[1]) { |
| float2 secondDerivate = float2(3*chopT[1], 1) * float2x2(C); |
| float midtangentTurn = determinant(float2x2(midtangent, secondDerivate)); |
| innerTan1 = (midtangentTurn * turnDirAtInf >= 0) ? +midtangent : -midtangent; |
| } |
| // Non-inflecting curves always turn in the same direction. |
| turnDirs = half3(turnDirAtInf); |
| } else { |
| // We just chopped at inflection points so it is most stable to actually evaluate the |
| // inflection function at the center of each section. |
| float3 turnT = mix(float3(0, chopT), float3(chopT, 1), .5); |
| turnDirs = half3(sign(float3x3(turnT*turnT, turnT, 1,1,1) * I)); |
| } |
| |
| // Package arguments for the tessellation control stage. |
| vsPts01 = float4(P[0], ab); |
| vsPts23 = float4(abc, abcd); |
| vsPts45 = float4(mix(abcd, bcd, (chopT[1] - chopT[0]) / (1 - chopT[0])), |
| mix(zyx, wzyx, chopT[0] / chopT[1])); |
| vsPts67 = float4(wzyx, wzy); |
| vsPts89 = float4(wz, P[3]); |
| vsTans01 = float4(tan0, innerTan0); |
| vsTans23 = float4(innerTan1, tan1); |
| vsTurnDirsAndPatchType = half4(turnDirs, patchType); |
| vsStrokeRadius = inputArgs.y; |
| )"); |
| |
| // The fragment shader just outputs a uniform color. |
| args.fFragBuilder->codeAppendf("%s = %s;", args.fOutputColor, colorUniformName); |
| args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage); |
| } |
| |
| void setData(const GrGLSLProgramDataManager& pdman, |
| const GrPrimitiveProcessor& primProc) override { |
| const auto& shader = primProc.cast<GrStrokeTessellateShader>(); |
| // tessControlArgs.x is the tolerance in pixels. |
| pdman.set2f(fTessControlArgsUniform, 1 / (kLinearizationIntolerance * shader.fMatrixScale), |
| shader.fMiterLimit); |
| const SkMatrix& m = shader.viewMatrix(); |
| if (!m.isIdentity()) { |
| pdman.set2f(fTranslateUniform, m.getTranslateX(), m.getTranslateY()); |
| float affineMatrix[4] = {m.getScaleX(), m.getSkewY(), m.getSkewX(), m.getScaleY()}; |
| pdman.setMatrix2f(fAffineMatrixUniform, affineMatrix); |
| } |
| pdman.set4fv(fColorUniform, 1, shader.fColor.vec()); |
| } |
| |
| GrGLSLUniformHandler::UniformHandle fTessControlArgsUniform; |
| GrGLSLUniformHandler::UniformHandle fTranslateUniform; |
| GrGLSLUniformHandler::UniformHandle fAffineMatrixUniform; |
| GrGLSLUniformHandler::UniformHandle fColorUniform; |
| }; |
| |
| SkString GrStrokeTessellateShader::getTessControlShaderGLSL( |
| const GrGLSLPrimitiveProcessor* glslPrimProc, const char* versionAndExtensionDecls, |
| const GrGLSLUniformHandler& uniformHandler, const GrShaderCaps& shaderCaps) const { |
| auto impl = static_cast<const GrStrokeTessellateShader::Impl*>(glslPrimProc); |
| |
| SkString code(versionAndExtensionDecls); |
| // Run 3 invocations: 1 for each section that the vertex shader chopped the curve into. |
| code.append("layout(vertices = 3) out;\n"); |
| |
| code.appendf("const float kPI = 3.141592653589793238;\n"); |
| code.appendf("const float kMaxTessellationSegments = %i;\n", |
| shaderCaps.maxTessellationSegments()); |
| code.appendf("const float kStandardCubicType = %f;\n", Patch::kStandardCubicType); |
| code.appendf("const float kMiterJoinType = %.0f;\n", Patch::kMiterJoinType); |
| code.appendf("const float kBevelJoinType = %.0f;\n", Patch::kBevelJoinType); |
| |
| const char* tessControlArgsName = impl->getTessControlArgsUniformName(uniformHandler); |
| code.appendf("uniform vec2 %s;\n", tessControlArgsName); |
| code.appendf("#define uTolerance %s.x\n", tessControlArgsName); |
| code.appendf("#define uMiterLimit %s.y\n", tessControlArgsName); |
| |
| code.append(R"( |
| in vec4 vsPts01[]; |
| in vec4 vsPts23[]; |
| in vec4 vsPts45[]; |
| in vec4 vsPts67[]; |
| in vec4 vsPts89[]; |
| in vec4 vsTans01[]; |
| in vec4 vsTans23[]; |
| in mediump vec4 vsTurnDirsAndPatchType[]; |
| in float vsStrokeRadius[]; |
| |
| out vec4 tcsPts01[]; |
| out vec4 tcsPt2Tan0[]; |
| out vec4 tcsTessArgs[]; |
| patch out vec4 tcsEndPtEndTan; |
| patch out vec4 tcsStrokeArgs; |
| |
| // The built-in atan() is undefined when x==0. This method relieves that restriction, but also |
| // can return values larger than 2*kPI. This shouldn't matter for our purposes. |
| float atan2(vec2 v) { |
| float bias = 0; |
| if (abs(v.y) > abs(v.x)) { |
| v = vec2(v.y, -v.x); |
| bias = kPI/2; |
| } |
| return atan(v.y, v.x) + bias; |
| } |
| |
| void main() { |
| // Unpack the input arguments from the vertex shader. |
| mat4x2 P; |
| mat2 tangents; |
| mediump float turnDir; |
| if (gl_InvocationID == 0) { |
| P = mat4x2(vsPts01[0], vsPts23[0]); |
| tangents = mat2(vsTans01[0]); |
| turnDir = vsTurnDirsAndPatchType[0].x; |
| } else if (gl_InvocationID == 1) { |
| P = mat4x2(vsPts23[0].zw, vsPts45[0], vsPts67[0].xy); |
| tangents = mat2(vsTans01[0].zw, vsTans23[0].xy); |
| turnDir = vsTurnDirsAndPatchType[0].y; |
| } else { |
| P = mat4x2(vsPts67[0], vsPts89[0]); |
| tangents = mat2(vsTans23[0]); |
| turnDir = vsTurnDirsAndPatchType[0].z; |
| } |
| mediump float patchType = vsTurnDirsAndPatchType[0].w; |
| float strokeRadius = vsStrokeRadius[0]; |
| |
| // Calculate the number of evenly spaced (in the parametric sense) segments to chop this |
| // section of the curve into. (See GrWangsFormula::cubic() for more documentation on this |
| // formula.) The final tessellated strip will be a composition of these parametric segments |
| // as well as radial segments. |
| float numParametricSegments = sqrt( |
| .75/uTolerance * length(max(abs(P[2] - P[1]*2.0 + P[0]), |
| abs(P[3] - P[2]*2.0 + P[1])))); |
| numParametricSegments = max(ceil(numParametricSegments), 1); |
| if (patchType != kStandardCubicType) { |
| // Joins and flat lines don't need parametric segments. |
| numParametricSegments = 1; |
| } |
| |
| // Determine the curve's start angle. |
| float angle0 = atan2(tangents[0]); |
| |
| // Determine the curve's total rotation. The vertex shader ensures our curve does not rotate |
| // more than 180 degrees or inflect, so the inverse cosine has enough range. |
| vec2 tan0norm = normalize(tangents[0]); |
| vec2 tan1norm = normalize(tangents[1]); |
| float cosTheta = dot(tan1norm, tan0norm); |
| float rotation = acos(clamp(cosTheta, -1, +1)); |
| // Adjust sign of rotation to match the direction the curve turns. |
| rotation *= turnDir; |
| |
| // Calculate the number of evenly spaced radial segments to chop this section of the curve |
| // into. Radial segments divide the curve's rotation into even steps. The final tessellated |
| // strip will be a composition of both parametric and radial segments. |
| float numRadialSegments = abs(rotation) / (2 * acos(max(1 - uTolerance/strokeRadius, -1))); |
| numRadialSegments = max(ceil(numRadialSegments), 1); |
| |
| // Set up joins. |
| float innerStrokeRadius = 0; // Used for miter joins. |
| vec2 strokeOutsetClamp = vec2(-1, 1); |
| float joinType = abs(patchType); |
| if (joinType >= kBevelJoinType) { |
| innerStrokeRadius = strokeRadius; // A non-zero innerStrokeRadius designates a join. |
| if (joinType == kMiterJoinType) { |
| // Miter join. Draw a fan with 2 segments and lengthen the interior radius |
| // so it reaches the miter point. |
| // (Or draw a 1-segment fan if we exceed the miter limit.) |
| float miterRatio = 1.0 / cos(.5 * rotation); |
| numRadialSegments = (miterRatio <= uMiterLimit) ? 2.0 : 1.0; |
| innerStrokeRadius = strokeRadius * miterRatio; |
| } else if (joinType == kBevelJoinType) { |
| // Bevel join. Make a fan with only one segment. |
| numRadialSegments = 1; |
| } |
| if (length(tan0norm - tan1norm) * strokeRadius < uTolerance) { |
| // The join angle is too tight to guarantee there won't be cracks on the interior |
| // side of the junction. In case our join was intended to go on the exterior side |
| // only, switch to a double sided bevel that ties all 4 incoming vertices together |
| // like a bowtie. The join angle is so tight that bevels, miters, and rounds will |
| // all look the same anyway. |
| numRadialSegments = 1; |
| } else if (patchType > 0) { |
| // This join is single-sided. Clamp it to the outer side of the junction. |
| strokeOutsetClamp = (rotation > 0) ? vec2(-1,0) : vec2(0,1); |
| } |
| } |
| |
| // The first and last edges are shared by both the parametric and radial sets of edges, so |
| // the total number of edges is: |
| // |
| // numCombinedEdges = numParametricEdges + numRadialEdges - 2 |
| // |
| // It's also important to differentiate between the number of edges and segments in a strip: |
| // |
| // numCombinedSegments = numCombinedEdges - 1 |
| // |
| // So the total number of segments in the combined strip is: |
| // |
| // numCombinedSegments = numParametricEdges + numRadialEdges - 2 - 1 |
| // = numParametricSegments + 1 + numRadialSegments + 1 - 2 - 1 |
| // = numParametricSegments + numRadialSegments - 1 |
| // |
| float numCombinedSegments = numParametricSegments + numRadialSegments - 1; |
| |
| // Pack the arguments for the evaluation stage. |
| tcsPts01[gl_InvocationID] = vec4(P[0], P[1]); |
| tcsPt2Tan0[gl_InvocationID] = vec4(P[2], tangents[0]); |
| tcsTessArgs[gl_InvocationID] = vec4(numCombinedSegments, numParametricSegments, angle0, |
| rotation / numRadialSegments); |
| if (gl_InvocationID == 2) { |
| tcsEndPtEndTan = vec4(P[3], tangents[1]); |
| tcsStrokeArgs = vec4(strokeRadius, innerStrokeRadius, strokeOutsetClamp); |
| } else if (patchType != kStandardCubicType) { |
| // We don't actually chop flat lines or joins. Disable their 0th & 1st curve sections by |
| // marking their number of segments as 0. |
| tcsTessArgs[gl_InvocationID].x = 0; |
| } |
| |
| barrier(); |
| |
| // Tessellate a quad strip with enough segments for all 3 curve sections combined. |
| float numTotalCombinedSegments = tcsTessArgs[0].x + tcsTessArgs[1].x + tcsTessArgs[2].x; |
| gl_TessLevelInner[0] = numTotalCombinedSegments; |
| gl_TessLevelInner[1] = 2.0; |
| gl_TessLevelOuter[0] = 2.0; |
| gl_TessLevelOuter[1] = numTotalCombinedSegments; |
| gl_TessLevelOuter[2] = 2.0; |
| gl_TessLevelOuter[3] = numTotalCombinedSegments; |
| } |
| )"); |
| |
| return code; |
| } |
| |
| SkString GrStrokeTessellateShader::getTessEvaluationShaderGLSL( |
| const GrGLSLPrimitiveProcessor* glslPrimProc, const char* versionAndExtensionDecls, |
| const GrGLSLUniformHandler& uniformHandler, const GrShaderCaps& shaderCaps) const { |
| auto impl = static_cast<const GrStrokeTessellateShader::Impl*>(glslPrimProc); |
| |
| SkString code(versionAndExtensionDecls); |
| code.append("layout(quads, equal_spacing, ccw) in;\n"); |
| |
| // Use a #define to make extra sure we don't prevent the loop from unrolling. |
| code.appendf("#define MAX_TESSELLATION_SEGMENTS_LOG2 %i\n", |
| SkNextLog2(shaderCaps.maxTessellationSegments())); |
| |
| code.appendf("const float kPI = 3.141592653589793238;\n"); |
| |
| if (!this->viewMatrix().isIdentity()) { |
| const char* translateName = impl->getTranslateUniformName(uniformHandler); |
| code.appendf("uniform vec2 %s;\n", translateName); |
| code.appendf("#define uTranslate %s\n", translateName); |
| const char* affineMatrixName = impl->getAffineMatrixUniformName(uniformHandler); |
| code.appendf("uniform mat2x2 %s;\n", affineMatrixName); |
| code.appendf("#define uAffineMatrix %s\n", affineMatrixName); |
| } |
| |
| code.append(R"( |
| in vec4 tcsPts01[]; |
| in vec4 tcsPt2Tan0[]; |
| in vec4 tcsTessArgs[]; |
| patch in vec4 tcsEndPtEndTan; |
| patch in vec4 tcsStrokeArgs; |
| |
| uniform vec4 sk_RTAdjust; |
| |
| void main() { |
| // Our patch is composed of exactly "numTotalCombinedSegments + 1" stroke-width edges that |
| // run orthogonal to the curve and make a strip of "numTotalCombinedSegments" quads. |
| // Determine which discrete edge belongs to this invocation. An edge can either come from a |
| // parametric segment or a radial one. |
| float numTotalCombinedSegments = tcsTessArgs[0].x + tcsTessArgs[1].x + tcsTessArgs[2].x; |
| float totalEdgeID = round(gl_TessCoord.x * numTotalCombinedSegments); |
| |
| // Furthermore, the vertex shader may have chopped the curve into 3 different sections. |
| // Determine which section we belong to, and where we fall relative to its first edge. |
| float localEdgeID = totalEdgeID; |
| mat4x2 P; |
| vec2 tan0; |
| vec3 tessellationArgs; |
| if (localEdgeID >= tcsTessArgs[0].x + tcsTessArgs[1].x) { |
| localEdgeID -= tcsTessArgs[0].x + tcsTessArgs[1].x; |
| P = mat4x2(tcsPts01[2], tcsPt2Tan0[2].xy, tcsEndPtEndTan.xy); |
| tan0 = tcsPt2Tan0[2].zw; |
| tessellationArgs = tcsTessArgs[2].yzw; |
| } else if (localEdgeID >= tcsTessArgs[0].x) { |
| localEdgeID -= tcsTessArgs[0].x; |
| P = mat4x2(tcsPts01[1], tcsPt2Tan0[1].xy, tcsPts01[2].xy); |
| tan0 = tcsPt2Tan0[1].zw; |
| tessellationArgs = tcsTessArgs[1].yzw; |
| } else { |
| P = mat4x2(tcsPts01[0], tcsPt2Tan0[0].xy, tcsPts01[1].xy); |
| tan0 = tcsPt2Tan0[0].zw; |
| tessellationArgs = tcsTessArgs[0].yzw; |
| } |
| float numParametricSegments = tessellationArgs.x; |
| float angle0 = tessellationArgs.y; |
| float radsPerSegment = tessellationArgs.z; |
| |
| // Find a tangent matrix C' in power basis form. (This gives the derivative scaled by 1/3.) |
| // |
| // |T^2| |
| // dx,dy (divided by 3) = tangent = C_ * | T| |
| // | 1| |
| mat3x2 C_ = P * mat3x4(-1, 3, -3, 1, |
| 2, -4, 2, 0, |
| -1, 1, 0, 0); |
| |
| // Find the matrix C_s that produces an (arbitrarily scaled) tangent vector from a |
| // parametric edge ID: |
| // |
| // |parametricEdgeId^2| |
| // C_s * | parametricEdgeId| = tangent * s |
| // | 1| |
| // |
| mat3x2 C_s = mat3x2(C_[0], C_[1] * numParametricSegments, |
| C_[2] * numParametricSegments * numParametricSegments); |
| |
| // Run an O(log N) search to determine the highest parametric edge that is located on or |
| // before the localEdgeID. A local edge ID is determined by the sum of complete parametric |
| // and radial segments behind it. i.e., find the highest parametric edge where: |
| // |
| // parametricEdgeID + floor(numRadialSegmentsAtParametricT) <= localEdgeID |
| // |
| float lastParametricEdgeID = 0; |
| float maxParametricEdgeID = min(numParametricSegments - 1, localEdgeID); |
| vec2 tan0norm = normalize(tan0); |
| float negAbsRadsPerSegment = -abs(radsPerSegment); |
| float maxRotation0 = (1 + localEdgeID) * abs(radsPerSegment); |
| for (int exp = MAX_TESSELLATION_SEGMENTS_LOG2 - 1; exp >= 0; --exp) { |
| // Test the parametric edge at lastParametricEdgeID + 2^exp. |
| float testParametricID = lastParametricEdgeID + (1 << exp); |
| if (testParametricID <= maxParametricEdgeID) { |
| vec2 testTan = fma(vec2(testParametricID), C_s[0], C_s[1]); |
| testTan = fma(vec2(testParametricID), testTan, C_s[2]); |
| float cosRotation = dot(normalize(testTan), tan0norm); |
| float maxRotation = fma(testParametricID, negAbsRadsPerSegment, maxRotation0); |
| maxRotation = min(maxRotation, kPI); |
| // Is rotation <= maxRotation? (i.e., is the number of complete radial segments |
| // behind testT, + testParametricID <= localEdgeID?) |
| // NOTE: We bias cos(maxRotation) downward for fp32 error. Otherwise a flat section |
| // following a 180 degree turn might not render properly. |
| if (cosRotation >= cos(maxRotation) - 1e-5) { |
| // testParametricID is on or before the localEdgeID. Keep it! |
| lastParametricEdgeID = testParametricID; |
| } |
| } |
| } |
| |
| // Find the T value of the parametric edge at lastParametricEdgeID. |
| float parametricT = lastParametricEdgeID / numParametricSegments; |
| |
| // Now that we've identified the highest parametric edge on or before the localEdgeID, the |
| // highest radial edge is easy: |
| float lastRadialEdgeID = localEdgeID - lastParametricEdgeID; |
| float radialAngle = fma(lastRadialEdgeID, radsPerSegment, angle0); |
| |
| // Find the T value of the edge at lastRadialEdgeID. This is the point whose tangent angle |
| // is equal to radialAngle, or whose tangent vector is orthogonal to "norm". |
| vec2 tangent = vec2(cos(radialAngle), sin(radialAngle)); |
| vec2 norm = vec2(-tangent.y, tangent.x); |
| |
| // Find the T value where the cubic's tangent is orthogonal to norm: |
| // |
| // |T^2| |
| // dot(tangent, norm) == 0: norm * C_ * | T| == 0 |
| // | 1| |
| // |
| // The coeffs for the quadratic equation we need to solve are therefore: norm * C_. |
| vec3 coeffs = norm * C_; |
| float a=coeffs.x, b=coeffs.y, c=coeffs.z; |
| |
| // Quadratic formula from Numerical Recipes in C. |
| float x = sqrt(max(b*b - 4*a*c, 0)); |
| if (b < 0) { |
| x = -x; |
| } |
| float q = -.5 * (b + x); |
| |
| // Roots are q/a and c/q. Since each curve section does not inflect or rotate more than 180 |
| // degrees, there can only be one tangent orthogonal to "norm" inside 0..1. Pick the root |
| // nearest 0.5. |
| float _5qa = .5*q*a; |
| vec2 root = (abs(q*q - _5qa) < abs(a*c - _5qa)) ? vec2(q,a) : vec2(c,q); |
| float radialT = (root.t != 0) ? root.s / root.t : 0; |
| radialT = clamp(radialT, 0, 1); |
| |
| if (lastRadialEdgeID == 0) { |
| // The root finder above can become unstable when lastRadialEdgeID == 0 (e.g., if there |
| // are roots at exatly 0 and 1 both). radialT should always == 0 in this case. |
| radialT = 0; |
| } |
| |
| // Now that we've identified the T values of the last parametric and radial edges, our final |
| // T value for localEdgeID is whichever is larger. |
| float T = max(parametricT, radialT); |
| |
| // Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability. |
| vec2 ab = mix(P[0], P[1], T); |
| vec2 bc = mix(P[1], P[2], T); |
| vec2 cd = mix(P[2], P[3], T); |
| vec2 abc = mix(ab, bc, T); |
| vec2 bcd = mix(bc, cd, T); |
| vec2 position = mix(abc, bcd, T); |
| |
| // If we went with T=parametricT, then update the tangent. Otherwise leave it at the radial |
| // tangent found previously. (In the event that parametricT == radialT, we keep the radial |
| // tangent.) |
| if (T != radialT) { |
| tangent = bcd - abc; |
| } |
| |
| float strokeRadius = tcsStrokeArgs.x; |
| float innerStrokeRadius = tcsStrokeArgs.y; |
| vec2 strokeOutsetClamp = tcsStrokeArgs.zw; |
| |
| if (localEdgeID == 0) { |
| // The first local edge of each section uses the provided P[0] and tan0. This ensures |
| // continuous rotation across chops made by the vertex shader as well as crack-free |
| // seaming between patches. |
| position = P[0]; |
| tangent = tan0; |
| } else if (gl_TessCoord.x == 1) { |
| // The final edge of the quad strip always uses the provided endPt and endTan. This |
| // ensures crack-free seaming between patches. |
| position = tcsEndPtEndTan.xy; |
| tangent = tcsEndPtEndTan.zw; |
| } else if (innerStrokeRadius != 0) { |
| // Miter joins use a larger radius for the internal vertex in order to reach the miter |
| // point. |
| strokeRadius = innerStrokeRadius; |
| } |
| |
| // If innerStrokeRadius != 0 then this patch is a join. |
| if (innerStrokeRadius != 0) { |
| // ... Aaaand nevermind again if we are a join. Those all rotate around P[1]. |
| position = P[1]; |
| } |
| |
| // Determine how far to outset our vertex orthogonally from the curve. |
| float outset = gl_TessCoord.y * 2 - 1; |
| outset = clamp(outset, strokeOutsetClamp.x, strokeOutsetClamp.y); |
| outset *= strokeRadius; |
| |
| vec2 vertexPos = position + normalize(vec2(-tangent.y, tangent.x)) * outset; |
| )"); |
| |
| // Transform after tessellation. Stroke widths and normals are defined in (pre-transform) local |
| // path space. |
| if (!this->viewMatrix().isIdentity()) { |
| code.append("vertexPos = uAffineMatrix * vertexPos + uTranslate;"); |
| } |
| |
| code.append(R"( |
| gl_Position = vec4(vertexPos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0); |
| } |
| )"); |
| |
| return code; |
| } |
| |
| GrGLSLPrimitiveProcessor* GrStrokeTessellateShader::createGLSLInstance( |
| const GrShaderCaps&) const { |
| return new Impl; |
| } |