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/shaders/GrStrokeTessellationShader.h" 9 10#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" 11#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" 12#include "src/gpu/tessellate/StrokeFixedCountTessellator.h" 13#include "src/gpu/tessellate/WangsFormula.h" 14 15using skgpu::VertexWriter; 16 17void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { 18 const auto& shader = args.fGeomProc.cast<GrStrokeTessellationShader>(); 19 SkPaint::Join joinType = shader.stroke().getJoin(); 20 args.fVaryingHandler->emitAttributes(shader); 21 22 args.fVertBuilder->defineConstant("float", "PI", "3.141592653589793238"); 23 24 // Helper functions. 25 if (shader.hasDynamicStroke()) { 26 args.fVertBuilder->insertFunction(kNumRadialSegmentsPerRadianFn); 27 } 28 args.fVertBuilder->insertFunction(kCosineBetweenVectorsFn); 29 args.fVertBuilder->insertFunction(kMiterExtentFn); 30 args.fVertBuilder->insertFunction(kUncheckedMixFn); 31 args.fVertBuilder->insertFunction(skgpu::wangs_formula::as_sksl().c_str()); 32 33 // Tessellation control uniforms and/or dynamic attributes. 34 if (!shader.hasDynamicStroke()) { 35 // [PARAMETRIC_PRECISION, NUM_RADIAL_SEGMENTS_PER_RADIAN, JOIN_TYPE, STROKE_RADIUS] 36 const char* tessArgsName; 37 fTessControlArgsUniform = args.fUniformHandler->addUniform( 38 nullptr, kVertex_GrShaderFlag, kFloat4_GrSLType, "tessControlArgs", 39 &tessArgsName); 40 args.fVertBuilder->codeAppendf(R"( 41 float PARAMETRIC_PRECISION = %s.x; 42 float NUM_RADIAL_SEGMENTS_PER_RADIAN = %s.y; 43 float JOIN_TYPE = %s.z; 44 float STROKE_RADIUS = %s.w;)", tessArgsName, tessArgsName, tessArgsName, tessArgsName); 45 } else { 46 const char* parametricPrecisionName; 47 fTessControlArgsUniform = args.fUniformHandler->addUniform( 48 nullptr, kVertex_GrShaderFlag, kFloat_GrSLType, "parametricPrecision", 49 ¶metricPrecisionName); 50 args.fVertBuilder->codeAppendf(R"( 51 float PARAMETRIC_PRECISION = %s; 52 float STROKE_RADIUS = dynamicStrokeAttr.x; 53 float NUM_RADIAL_SEGMENTS_PER_RADIAN = num_radial_segments_per_radian( 54 PARAMETRIC_PRECISION, STROKE_RADIUS); 55 float JOIN_TYPE = dynamicStrokeAttr.y;)", parametricPrecisionName); 56 } 57 58 if (shader.hasDynamicColor()) { 59 // Create a varying for color to get passed in through. 60 GrGLSLVarying dynamicColor{kHalf4_GrSLType}; 61 args.fVaryingHandler->addVarying("dynamicColor", &dynamicColor); 62 args.fVertBuilder->codeAppendf("%s = dynamicColorAttr;", dynamicColor.vsOut()); 63 fDynamicColorName = dynamicColor.fsIn(); 64 } 65 66 if (shader.mode() == GrStrokeTessellationShader::Mode::kLog2Indirect) { 67 args.fVertBuilder->codeAppend(R"( 68 float NUM_TOTAL_EDGES = abs(argsAttr.z);)"); 69 } else { 70 SkASSERT(shader.mode() == GrStrokeTessellationShader::Mode::kFixedCount); 71 const char* edgeCountName; 72 fEdgeCountUniform = args.fUniformHandler->addUniform( 73 nullptr, kVertex_GrShaderFlag, kFloat_GrSLType, "edgeCount", &edgeCountName); 74 args.fVertBuilder->codeAppendf(R"( 75 float NUM_TOTAL_EDGES = %s;)", edgeCountName); 76 } 77 78 // View matrix uniforms. 79 const char* translateName, *affineMatrixName; 80 fAffineMatrixUniform = args.fUniformHandler->addUniform(nullptr, kVertex_GrShaderFlag, 81 kFloat4_GrSLType, "affineMatrix", 82 &affineMatrixName); 83 fTranslateUniform = args.fUniformHandler->addUniform(nullptr, kVertex_GrShaderFlag, 84 kFloat2_GrSLType, "translate", 85 &translateName); 86 args.fVertBuilder->codeAppendf("float2x2 AFFINE_MATRIX = float2x2(%s);\n", affineMatrixName); 87 args.fVertBuilder->codeAppendf("float2 TRANSLATE = %s;\n", translateName); 88 89 if (shader.hasExplicitCurveType()) { 90 args.fVertBuilder->insertFunction(SkStringPrintf(R"( 91 bool is_conic_curve() { return curveTypeAttr != %g; })", kCubicCurveType).c_str()); 92 } else { 93 args.fVertBuilder->insertFunction(R"( 94 bool is_conic_curve() { return isinf(pts23Attr.w); })"); 95 } 96 97 // Tessellation code. 98 args.fVertBuilder->codeAppend(R"( 99 float2 p0=pts01Attr.xy, p1=pts01Attr.zw, p2=pts23Attr.xy, p3=pts23Attr.zw; 100 float2 lastControlPoint = argsAttr.xy; 101 float w = -1; // w<0 means the curve is an integral cubic. 102 if (is_conic_curve()) { 103 // Conics are 3 points, with the weight in p3. 104 w = p3.x; 105 p3 = p2; // Setting p3 equal to p2 works for the remaining rotational logic. 106 })"); 107 if (shader.stroke().isHairlineStyle()) { 108 // Hairline case. Transform the points before tessellation. We can still hold off on the 109 // translate until the end; we just need to perform the scale and skew right now. 110 args.fVertBuilder->codeAppend(R"( 111 p0 = AFFINE_MATRIX * p0; 112 p1 = AFFINE_MATRIX * p1; 113 p2 = AFFINE_MATRIX * p2; 114 p3 = AFFINE_MATRIX * p3; 115 lastControlPoint = AFFINE_MATRIX * lastControlPoint;)"); 116 } 117 118 args.fVertBuilder->codeAppend(R"( 119 // Find how many parametric segments this stroke requires. 120 float numParametricSegments; 121 if (w < 0) { 122 numParametricSegments = wangs_formula_cubic(PARAMETRIC_PRECISION, p0, p1, p2, p3, 123 float2x2(1)); 124 } else { 125 numParametricSegments = wangs_formula_conic(PARAMETRIC_PRECISION, p0, p1, p2, w); 126 } 127 if (p0 == p1 && p2 == p3) { 128 // This is how we describe lines, but Wang's formula does not return 1 in this case. 129 numParametricSegments = 1; 130 } 131 132 // Find the starting and ending tangents. 133 float2 tan0 = ((p0 == p1) ? (p1 == p2) ? p3 : p2 : p1) - p0; 134 float2 tan1 = p3 - ((p3 == p2) ? (p2 == p1) ? p0 : p1 : p2); 135 if (tan0 == float2(0)) { 136 // The stroke is a point. This special case tells us to draw a stroke-width circle as a 137 // 180 degree point stroke instead. 138 tan0 = float2(1,0); 139 tan1 = float2(-1,0); 140 })"); 141 142 if (args.fShaderCaps->vertexIDSupport()) { 143 // If we don't have sk_VertexID support then "edgeID" already came in as a vertex attrib. 144 args.fVertBuilder->codeAppend(R"( 145 float edgeID = float(sk_VertexID >> 1); 146 if ((sk_VertexID & 1) != 0) { 147 edgeID = -edgeID; 148 })"); 149 } 150 151 // Potential optimization: (shader.hasDynamicStroke() && shader.hasRoundJoins())? 152 if (shader.stroke().getJoin() == SkPaint::kRound_Join || shader.hasDynamicStroke()) { 153 args.fVertBuilder->codeAppend(R"( 154 // Determine how many edges to give to the round join. We emit the first and final edges 155 // of the join twice: once full width and once restricted to half width. This guarantees 156 // perfect seaming by matching the vertices from the join as well as from the strokes on 157 // either side. 158 float joinRads = acos(cosine_between_vectors(p0 - lastControlPoint, tan0)); 159 float numRadialSegmentsInJoin = max(ceil(joinRads * NUM_RADIAL_SEGMENTS_PER_RADIAN), 1); 160 // +2 because we emit the beginning and ending edges twice (see above comment). 161 float numEdgesInJoin = numRadialSegmentsInJoin + 2; 162 // The stroke section needs at least two edges. Don't assign more to the join than 163 // "NUM_TOTAL_EDGES - 2". 164 numEdgesInJoin = min(numEdgesInJoin, NUM_TOTAL_EDGES - 2);)"); 165 if (shader.mode() == GrStrokeTessellationShader::Mode::kLog2Indirect) { 166 args.fVertBuilder->codeAppend(R"( 167 // Negative argsAttr.z means the join is an internal chop or circle, and both of 168 // those have empty joins. All we need is a bevel join. 169 if (argsAttr.z < 0) { 170 // +2 because we emit the beginning and ending edges twice (see above comment). 171 numEdgesInJoin = 1 + 2; 172 })"); 173 } 174 if (shader.hasDynamicStroke()) { 175 args.fVertBuilder->codeAppend(R"( 176 if (JOIN_TYPE >= 0 /*Is the join not a round type?*/) { 177 // Bevel and miter joins get 1 and 2 segments respectively. 178 // +2 because we emit the beginning and ending edges twice (see above comments). 179 numEdgesInJoin = sign(JOIN_TYPE) + 1 + 2; 180 })"); 181 } 182 } else { 183 args.fVertBuilder->codeAppendf(R"( 184 float numEdgesInJoin = %i;)", 185 skgpu::StrokeFixedCountTessellator::NumFixedEdgesInJoin(joinType)); 186 } 187 188 args.fVertBuilder->codeAppend(R"( 189 // Find which direction the curve turns. 190 // NOTE: Since the curve is not allowed to inflect, we can just check F'(.5) x F''(.5). 191 // NOTE: F'(.5) x F''(.5) has the same sign as (P2 - P0) x (P3 - P1) 192 float turn = cross(p2 - p0, p3 - p1); 193 float combinedEdgeID = abs(edgeID) - numEdgesInJoin; 194 if (combinedEdgeID < 0) { 195 tan1 = tan0; 196 // Don't let tan0 become zero. The code as-is isn't built to handle that case. tan0=0 197 // means the join is disabled, and to disable it with the existing code we can leave 198 // tan0 equal to tan1. 199 if (lastControlPoint != p0) { 200 tan0 = p0 - lastControlPoint; 201 } 202 turn = cross(tan0, tan1); 203 } 204 205 // Calculate the curve's starting angle and rotation. 206 float cosTheta = cosine_between_vectors(tan0, tan1); 207 float rotation = acos(cosTheta); 208 if (turn < 0) { 209 // Adjust sign of rotation to match the direction the curve turns. 210 rotation = -rotation; 211 } 212 213 float numRadialSegments; 214 float strokeOutset = sign(edgeID); 215 if (combinedEdgeID < 0) { 216 // We belong to the preceding join. The first and final edges get duplicated, so we only 217 // have "numEdgesInJoin - 2" segments. 218 numRadialSegments = numEdgesInJoin - 2; 219 numParametricSegments = 1; // Joins don't have parametric segments. 220 p3 = p2 = p1 = p0; // Colocate all points on the junction point. 221 // Shift combinedEdgeID to the range [-1, numRadialSegments]. This duplicates the first 222 // edge and lands one edge at the very end of the join. (The duplicated final edge will 223 // actually come from the section of our strip that belongs to the stroke.) 224 combinedEdgeID += numRadialSegments + 1; 225 // We normally restrict the join on one side of the junction, but if the tangents are 226 // nearly equivalent this could theoretically result in bad seaming and/or cracks on the 227 // side we don't put it on. If the tangents are nearly equivalent then we leave the join 228 // double-sided. 229 float sinEpsilon = 1e-2; // ~= sin(180deg / 3000) 230 bool tangentsNearlyParallel = 231 (abs(turn) * inversesqrt(dot(tan0, tan0) * dot(tan1, tan1))) < sinEpsilon; 232 if (!tangentsNearlyParallel || dot(tan0, tan1) < 0) { 233 // There are two edges colocated at the beginning. Leave the first one double sided 234 // for seaming with the previous stroke. (The double sided edge at the end will 235 // actually come from the section of our strip that belongs to the stroke.) 236 if (combinedEdgeID >= 0) { 237 strokeOutset = (turn < 0) ? min(strokeOutset, 0) : max(strokeOutset, 0); 238 } 239 } 240 combinedEdgeID = max(combinedEdgeID, 0); 241 } else { 242 // We belong to the stroke. 243 float maxCombinedSegments = NUM_TOTAL_EDGES - numEdgesInJoin - 1; 244 numRadialSegments = max(ceil(abs(rotation) * NUM_RADIAL_SEGMENTS_PER_RADIAN), 1); 245 numRadialSegments = min(numRadialSegments, maxCombinedSegments); 246 numParametricSegments = min(numParametricSegments, 247 maxCombinedSegments - numRadialSegments + 1); 248 } 249 250 // Additional parameters for emitTessellationCode(). 251 float radsPerSegment = rotation / numRadialSegments; 252 float numCombinedSegments = numParametricSegments + numRadialSegments - 1; 253 bool isFinalEdge = (combinedEdgeID >= numCombinedSegments); 254 if (combinedEdgeID > numCombinedSegments) { 255 strokeOutset = 0; // The strip has more edges than we need. Drop this one. 256 })"); 257 258 if (joinType == SkPaint::kMiter_Join || shader.hasDynamicStroke()) { 259 args.fVertBuilder->codeAppendf(R"( 260 // Edge #2 extends to the miter point. 261 if (abs(edgeID) == 2 && %s) { 262 strokeOutset *= miter_extent(cosTheta, JOIN_TYPE/*miterLimit*/); 263 })", shader.hasDynamicStroke() ? "JOIN_TYPE > 0/*Is the join a miter type?*/" : "true"); 264 } 265 266 this->emitTessellationCode(shader, &args.fVertBuilder->code(), gpArgs, *args.fShaderCaps); 267 268 this->emitFragmentCode(shader, args); 269} 270