1 /* 2 * Copyright 2015 Google Inc. 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 "GrPLSPathRenderer.h" 9 10 #include "SkChunkAlloc.h" 11 #include "SkGeometry.h" 12 #include "SkPathPriv.h" 13 #include "SkString.h" 14 #include "SkTSort.h" 15 #include "SkTraceEvent.h" 16 #include "GrBatchFlushState.h" 17 #include "GrBatchTest.h" 18 #include "GrCaps.h" 19 #include "GrContext.h" 20 #include "GrDefaultGeoProcFactory.h" 21 #include "GrPLSGeometryProcessor.h" 22 #include "GrInvariantOutput.h" 23 #include "GrPathUtils.h" 24 #include "GrProcessor.h" 25 #include "GrPipelineBuilder.h" 26 #include "GrStrokeInfo.h" 27 #include "GrTessellator.h" 28 #include "batches/GrVertexBatch.h" 29 #include "glsl/GrGLSLGeometryProcessor.h" 30 #include "gl/builders/GrGLProgramBuilder.h" 31 #include "glsl/GrGLSLPLSPathRendering.h" 32 33 GrPLSPathRenderer::GrPLSPathRenderer() { 34 } 35 36 struct PLSVertex { 37 SkPoint fPos; 38 // for triangles, these are the three triangle vertices 39 // for quads, vert1 is the texture UV coords, and vert2 and vert3 are the line segment 40 // comprising the flat edge of the quad 41 SkPoint fVert1; 42 SkPoint fVert2; 43 SkPoint fVert3; 44 int fWinding; 45 }; 46 typedef SkTArray<PLSVertex, true> PLSVertices; 47 48 typedef SkTArray<SkPoint, true> FinishVertices; 49 50 static const float kCubicTolerance = 0.5f; 51 static const float kConicTolerance = 0.5f; 52 53 static const float kBloatSize = 1.0f; 54 55 static const float kBloatLimit = 640000.0f; 56 57 #define kQuadNumVertices 5 58 static void add_quad(SkPoint pts[3], PLSVertices& vertices) { 59 SkPoint normal = SkPoint::Make(pts[0].fY - pts[2].fY, 60 pts[2].fX - pts[0].fX); 61 normal.setLength(kBloatSize); 62 SkScalar cross = (pts[1] - pts[0]).cross(pts[2] - pts[0]); 63 if (cross < 0) { 64 normal = -normal; 65 } 66 PLSVertex quad[kQuadNumVertices]; 67 quad[0].fPos = pts[0] + normal; 68 quad[1].fPos = pts[0] - normal; 69 quad[2].fPos = pts[1] - normal; 70 quad[3].fPos = pts[2] - normal; 71 quad[4].fPos = pts[2] + normal; 72 for (int i = 0; i < kQuadNumVertices; i++) { 73 quad[i].fWinding = cross < 0 ? 1 : -1; 74 if (cross > 0.0) { 75 quad[i].fVert2 = pts[0]; 76 quad[i].fVert3 = pts[2]; 77 } 78 else { 79 quad[i].fVert2 = pts[2]; 80 quad[i].fVert3 = pts[0]; 81 } 82 } 83 GrPathUtils::QuadUVMatrix DevToUV(pts); 84 DevToUV.apply<kQuadNumVertices, sizeof(PLSVertex), sizeof(SkPoint)>(quad); 85 for (int i = 2; i < kQuadNumVertices; i++) { 86 vertices.push_back(quad[0]); 87 vertices.push_back(quad[i - 1]); 88 vertices.push_back(quad[i]); 89 } 90 } 91 92 /* Used by bloat_tri; outsets a single point. */ 93 static bool outset(SkPoint* p1, SkPoint line1, SkPoint line2) { 94 // rotate the two line vectors 90 degrees to form the normals, and compute 95 // the dot product of the normals 96 SkScalar dotProd = line1.fY * line2.fY + line1.fX * line2.fX; 97 SkScalar lengthSq = 1.0f / ((1.0f - dotProd) / 2.0f); 98 if (lengthSq > kBloatLimit) { 99 return false; 100 } 101 SkPoint bisector = line1 + line2; 102 bisector.setLength(SkScalarSqrt(lengthSq) * kBloatSize); 103 *p1 += bisector; 104 return true; 105 } 106 107 /* Bloats a triangle so as to create a border kBloatSize pixels wide all around it. */ 108 static bool bloat_tri(SkPoint pts[3]) { 109 SkPoint line1 = pts[0] - pts[1]; 110 line1.normalize(); 111 SkPoint line2 = pts[0] - pts[2]; 112 line2.normalize(); 113 SkPoint line3 = pts[1] - pts[2]; 114 line3.normalize(); 115 116 SkPoint result[3]; 117 result[0] = pts[0]; 118 if (!outset(&result[0], line1, line2)) { 119 return false; 120 } 121 result[1] = pts[1]; 122 if (!outset(&result[1], -line1, line3)) { 123 return false; 124 } 125 result[2] = pts[2]; 126 if (!outset(&result[2], -line3, -line2)) { 127 return false; 128 } 129 pts[0] = result[0]; 130 pts[1] = result[1]; 131 pts[2] = result[2]; 132 return true; 133 } 134 135 static bool get_geometry(const SkPath& path, const SkMatrix& m, PLSVertices& triVertices, 136 PLSVertices& quadVertices, GrResourceProvider* resourceProvider, 137 SkRect bounds) { 138 SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance; 139 SkScalar tol = GrPathUtils::scaleToleranceToSrc(screenSpaceTol, m, bounds); 140 int contourCnt; 141 int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt, tol); 142 if (maxPts <= 0) { 143 return 0; 144 } 145 SkPath linesOnlyPath; 146 linesOnlyPath.setFillType(path.getFillType()); 147 SkSTArray<15, SkPoint, true> quadPoints; 148 SkPath::Iter iter(path, true); 149 bool done = false; 150 while (!done) { 151 SkPoint pts[4]; 152 SkPath::Verb verb = iter.next(pts); 153 switch (verb) { 154 case SkPath::kMove_Verb: 155 SkASSERT(quadPoints.count() % 3 == 0); 156 for (int i = 0; i < quadPoints.count(); i += 3) { 157 add_quad(&quadPoints[i], quadVertices); 158 } 159 quadPoints.reset(); 160 m.mapPoints(&pts[0], 1); 161 linesOnlyPath.moveTo(pts[0]); 162 break; 163 case SkPath::kLine_Verb: 164 m.mapPoints(&pts[1], 1); 165 linesOnlyPath.lineTo(pts[1]); 166 break; 167 case SkPath::kQuad_Verb: 168 m.mapPoints(pts, 3); 169 linesOnlyPath.lineTo(pts[2]); 170 quadPoints.push_back(pts[0]); 171 quadPoints.push_back(pts[1]); 172 quadPoints.push_back(pts[2]); 173 break; 174 case SkPath::kCubic_Verb: { 175 m.mapPoints(pts, 4); 176 SkSTArray<15, SkPoint, true> quads; 177 GrPathUtils::convertCubicToQuads(pts, kCubicTolerance, &quads); 178 int count = quads.count(); 179 for (int q = 0; q < count; q += 3) { 180 linesOnlyPath.lineTo(quads[q + 2]); 181 quadPoints.push_back(quads[q]); 182 quadPoints.push_back(quads[q + 1]); 183 quadPoints.push_back(quads[q + 2]); 184 } 185 break; 186 } 187 case SkPath::kConic_Verb: { 188 m.mapPoints(pts, 3); 189 SkScalar weight = iter.conicWeight(); 190 SkAutoConicToQuads converter; 191 const SkPoint* quads = converter.computeQuads(pts, weight, kConicTolerance); 192 int count = converter.countQuads(); 193 for (int i = 0; i < count; ++i) { 194 linesOnlyPath.lineTo(quads[2 * i + 2]); 195 quadPoints.push_back(quads[2 * i]); 196 quadPoints.push_back(quads[2 * i + 1]); 197 quadPoints.push_back(quads[2 * i + 2]); 198 } 199 break; 200 } 201 case SkPath::kClose_Verb: 202 linesOnlyPath.close(); 203 break; 204 case SkPath::kDone_Verb: 205 done = true; 206 break; 207 default: SkASSERT(false); 208 } 209 } 210 SkASSERT(quadPoints.count() % 3 == 0); 211 for (int i = 0; i < quadPoints.count(); i += 3) { 212 add_quad(&quadPoints[i], quadVertices); 213 } 214 215 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); 216 GrUniqueKey key; 217 GrUniqueKey::Builder builder(&key, kDomain, 2); 218 builder[0] = path.getGenerationID(); 219 builder[1] = path.getFillType(); 220 builder.finish(); 221 GrTessellator::WindingVertex* windingVertices; 222 int triVertexCount = GrTessellator::PathToVertices(linesOnlyPath, 0, bounds, &windingVertices); 223 if (triVertexCount > 0) { 224 for (int i = 0; i < triVertexCount; i += 3) { 225 SkPoint p1 = windingVertices[i].fPos; 226 SkPoint p2 = windingVertices[i + 1].fPos; 227 SkPoint p3 = windingVertices[i + 2].fPos; 228 int winding = windingVertices[i].fWinding; 229 SkASSERT(windingVertices[i + 1].fWinding == winding); 230 SkASSERT(windingVertices[i + 2].fWinding == winding); 231 SkScalar cross = (p2 - p1).cross(p3 - p1); 232 SkPoint bloated[3] = { p1, p2, p3 }; 233 if (cross < 0.0f) { 234 SkTSwap(p1, p3); 235 } 236 if (bloat_tri(bloated)) { 237 triVertices.push_back({ bloated[0], p1, p2, p3, winding }); 238 triVertices.push_back({ bloated[1], p1, p2, p3, winding }); 239 triVertices.push_back({ bloated[2], p1, p2, p3, winding }); 240 } 241 else { 242 SkScalar minX = SkTMin(p1.fX, SkTMin(p2.fX, p3.fX)) - 1.0f; 243 SkScalar minY = SkTMin(p1.fY, SkTMin(p2.fY, p3.fY)) - 1.0f; 244 SkScalar maxX = SkTMax(p1.fX, SkTMax(p2.fX, p3.fX)) + 1.0f; 245 SkScalar maxY = SkTMax(p1.fY, SkTMax(p2.fY, p3.fY)) + 1.0f; 246 triVertices.push_back({ { minX, minY }, p1, p2, p3, winding }); 247 triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding }); 248 triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding }); 249 triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding }); 250 triVertices.push_back({ { maxX, maxY }, p1, p2, p3, winding }); 251 triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding }); 252 } 253 } 254 delete[] windingVertices; 255 } 256 return triVertexCount > 0 || quadVertices.count() > 0; 257 } 258 259 class PLSAATriangleEffect : public GrPLSGeometryProcessor { 260 public: 261 262 static GrPLSGeometryProcessor* Create(const SkMatrix& localMatrix, 263 bool usesLocalCoords) { 264 return new PLSAATriangleEffect(localMatrix, usesLocalCoords); 265 } 266 267 virtual ~PLSAATriangleEffect() {} 268 269 const char* name() const override { return "PLSAATriangle"; } 270 271 const Attribute* inPosition() const { return fInPosition; } 272 const Attribute* inVertex1() const { return fInVertex1; } 273 const Attribute* inVertex2() const { return fInVertex2; } 274 const Attribute* inVertex3() const { return fInVertex3; } 275 const Attribute* inWindings() const { return fInWindings; } 276 const SkMatrix& localMatrix() const { return fLocalMatrix; } 277 bool usesLocalCoords() const { return fUsesLocalCoords; } 278 279 class GLSLProcessor : public GrGLSLGeometryProcessor { 280 public: 281 GLSLProcessor(const GrGeometryProcessor&) {} 282 283 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 284 const PLSAATriangleEffect& te = args.fGP.cast<PLSAATriangleEffect>(); 285 GrGLSLVertexBuilder* vsBuilder = args.fVertBuilder; 286 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 287 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 288 289 varyingHandler->emitAttributes(te); 290 291 this->setupPosition(vsBuilder, gpArgs, te.inPosition()->fName); 292 293 GrGLSLVertToFrag v1(kVec2f_GrSLType); 294 varyingHandler->addVarying("Vertex1", &v1, kHigh_GrSLPrecision); 295 vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);", 296 v1.vsOut(), 297 te.inVertex1()->fName, 298 te.inVertex1()->fName); 299 300 GrGLSLVertToFrag v2(kVec2f_GrSLType); 301 varyingHandler->addVarying("Vertex2", &v2, kHigh_GrSLPrecision); 302 vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);", 303 v2.vsOut(), 304 te.inVertex2()->fName, 305 te.inVertex2()->fName); 306 307 GrGLSLVertToFrag v3(kVec2f_GrSLType); 308 varyingHandler->addVarying("Vertex3", &v3, kHigh_GrSLPrecision); 309 vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);", 310 v3.vsOut(), 311 te.inVertex3()->fName, 312 te.inVertex3()->fName); 313 314 GrGLSLVertToFrag delta1(kVec2f_GrSLType); 315 varyingHandler->addVarying("delta1", &delta1, kHigh_GrSLPrecision); 316 vsBuilder->codeAppendf("%s = vec2(%s.x - %s.x, %s.y - %s.y) * 0.5;", 317 delta1.vsOut(), v1.vsOut(), v2.vsOut(), v2.vsOut(), v1.vsOut()); 318 319 GrGLSLVertToFrag delta2(kVec2f_GrSLType); 320 varyingHandler->addVarying("delta2", &delta2, kHigh_GrSLPrecision); 321 vsBuilder->codeAppendf("%s = vec2(%s.x - %s.x, %s.y - %s.y) * 0.5;", 322 delta2.vsOut(), v2.vsOut(), v3.vsOut(), v3.vsOut(), v2.vsOut()); 323 324 GrGLSLVertToFrag delta3(kVec2f_GrSLType); 325 varyingHandler->addVarying("delta3", &delta3, kHigh_GrSLPrecision); 326 vsBuilder->codeAppendf("%s = vec2(%s.x - %s.x, %s.y - %s.y) * 0.5;", 327 delta3.vsOut(), v3.vsOut(), v1.vsOut(), v1.vsOut(), v3.vsOut()); 328 329 GrGLSLVertToFrag windings(kInt_GrSLType); 330 varyingHandler->addFlatVarying("windings", &windings, kLow_GrSLPrecision); 331 vsBuilder->codeAppendf("%s = %s;", 332 windings.vsOut(), te.inWindings()->fName); 333 334 // emit transforms 335 this->emitTransforms(vsBuilder, varyingHandler, uniformHandler, gpArgs->fPositionVar, 336 te.inPosition()->fName, te.localMatrix(), args.fTransformsIn, 337 args.fTransformsOut); 338 339 GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder; 340 SkAssertResult(fsBuilder->enableFeature( 341 GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature)); 342 SkAssertResult(fsBuilder->enableFeature( 343 GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature)); 344 fsBuilder->declAppendf(GR_GL_PLS_PATH_DATA_DECL); 345 // Compute four subsamples, each shifted a quarter pixel along x and y from 346 // gl_FragCoord. The oriented box positioning of the subsamples is of course not 347 // optimal, but it greatly simplifies the math and this simplification is necessary for 348 // performance reasons. 349 fsBuilder->codeAppendf("highp vec2 firstSample = %s.xy - vec2(0.25);", 350 fsBuilder->fragmentPosition()); 351 fsBuilder->codeAppendf("highp vec2 delta1 = %s;", delta1.fsIn()); 352 fsBuilder->codeAppendf("highp vec2 delta2 = %s;", delta2.fsIn()); 353 fsBuilder->codeAppendf("highp vec2 delta3 = %s;", delta3.fsIn()); 354 // Check whether first sample is inside the triangle by computing three dot products. If 355 // all are < 0, we're inside. The first vector in each case is half of what it is 356 // "supposed" to be, because we re-use them later as adjustment factors for which half 357 // is the correct value, so we multiply the dots by two to compensate. 358 fsBuilder->codeAppendf("highp float d1 = dot(delta1, (firstSample - %s).yx) * 2.0;", 359 v1.fsIn()); 360 fsBuilder->codeAppendf("highp float d2 = dot(delta2, (firstSample - %s).yx) * 2.0;", 361 v2.fsIn()); 362 fsBuilder->codeAppendf("highp float d3 = dot(delta3, (firstSample - %s).yx) * 2.0;", 363 v3.fsIn()); 364 fsBuilder->codeAppend("highp float dmax = max(d1, max(d2, d3));"); 365 fsBuilder->codeAppendf("pls.windings[0] += (dmax <= 0.0) ? %s : 0;", windings.fsIn()); 366 // for subsequent samples, we don't recalculate the entire dot product -- just adjust it 367 // to the value it would have if we did recompute it. 368 fsBuilder->codeAppend("d1 += delta1.x;"); 369 fsBuilder->codeAppend("d2 += delta2.x;"); 370 fsBuilder->codeAppend("d3 += delta3.x;"); 371 fsBuilder->codeAppend("dmax = max(d1, max(d2, d3));"); 372 fsBuilder->codeAppendf("pls.windings[1] += (dmax <= 0.0) ? %s : 0;", windings.fsIn()); 373 fsBuilder->codeAppend("d1 += delta1.y;"); 374 fsBuilder->codeAppend("d2 += delta2.y;"); 375 fsBuilder->codeAppend("d3 += delta3.y;"); 376 fsBuilder->codeAppend("dmax = max(d1, max(d2, d3));"); 377 fsBuilder->codeAppendf("pls.windings[2] += (dmax <= 0.0) ? %s : 0;", windings.fsIn()); 378 fsBuilder->codeAppend("d1 -= delta1.x;"); 379 fsBuilder->codeAppend("d2 -= delta2.x;"); 380 fsBuilder->codeAppend("d3 -= delta3.x;"); 381 fsBuilder->codeAppend("dmax = max(d1, max(d2, d3));"); 382 fsBuilder->codeAppendf("pls.windings[3] += (dmax <= 0.0) ? %s : 0;", windings.fsIn()); 383 } 384 385 static inline void GenKey(const GrGeometryProcessor& gp, 386 const GrGLSLCaps&, 387 GrProcessorKeyBuilder* b) { 388 const PLSAATriangleEffect& te = gp.cast<PLSAATriangleEffect>(); 389 uint32_t key = 0; 390 key |= te.localMatrix().hasPerspective() ? 0x1 : 0x0; 391 b->add32(key); 392 } 393 394 virtual void setData(const GrGLSLProgramDataManager& pdman, 395 const GrPrimitiveProcessor& gp) override { 396 } 397 398 void setTransformData(const GrPrimitiveProcessor& primProc, 399 const GrGLSLProgramDataManager& pdman, 400 int index, 401 const SkTArray<const GrCoordTransform*, true>& transforms) override { 402 this->setTransformDataHelper<PLSAATriangleEffect>(primProc, pdman, index, transforms); 403 } 404 405 private: 406 typedef GrGLSLGeometryProcessor INHERITED; 407 }; 408 409 virtual void getGLSLProcessorKey(const GrGLSLCaps& caps, 410 GrProcessorKeyBuilder* b) const override { 411 GLSLProcessor::GenKey(*this, caps, b); 412 } 413 414 virtual GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override { 415 return new GLSLProcessor(*this); 416 } 417 418 private: 419 PLSAATriangleEffect(const SkMatrix& localMatrix, bool usesLocalCoords) 420 : fLocalMatrix(localMatrix) 421 , fUsesLocalCoords(usesLocalCoords) { 422 this->initClassID<PLSAATriangleEffect>(); 423 fInPosition = &this->addVertexAttrib(Attribute("inPosition", kVec2f_GrVertexAttribType, 424 kHigh_GrSLPrecision)); 425 fInVertex1 = &this->addVertexAttrib(Attribute("inVertex1", kVec2f_GrVertexAttribType, 426 kHigh_GrSLPrecision)); 427 fInVertex2 = &this->addVertexAttrib(Attribute("inVertex2", kVec2f_GrVertexAttribType, 428 kHigh_GrSLPrecision)); 429 fInVertex3 = &this->addVertexAttrib(Attribute("inVertex3", kVec2f_GrVertexAttribType, 430 kHigh_GrSLPrecision)); 431 fInWindings = &this->addVertexAttrib(Attribute("inWindings", kInt_GrVertexAttribType, 432 kLow_GrSLPrecision)); 433 this->setWillReadFragmentPosition(); 434 } 435 436 const Attribute* fInPosition; 437 const Attribute* fInVertex1; 438 const Attribute* fInVertex2; 439 const Attribute* fInVertex3; 440 const Attribute* fInWindings; 441 SkMatrix fLocalMatrix; 442 bool fUsesLocalCoords; 443 444 GR_DECLARE_GEOMETRY_PROCESSOR_TEST; 445 446 typedef GrGeometryProcessor INHERITED; 447 }; 448 449 /////////////////////////////////////////////////////////////////////////////// 450 451 /* 452 * Quadratic specified by 0=u^2-v canonical coords. u and v are the first 453 * two components of the vertex attribute. Coverage is based on signed 454 * distance with negative being inside, positive outside. The edge is specified in 455 * window space (y-down). If either the third or fourth component of the interpolated 456 * vertex coord is > 0 then the pixel is considered outside the edge. This is used to 457 * attempt to trim to a portion of the infinite quad. 458 * Requires shader derivative instruction support. 459 */ 460 461 class PLSQuadEdgeEffect : public GrPLSGeometryProcessor { 462 public: 463 464 static GrPLSGeometryProcessor* Create(const SkMatrix& localMatrix, 465 bool usesLocalCoords) { 466 return new PLSQuadEdgeEffect(localMatrix, usesLocalCoords); 467 } 468 469 virtual ~PLSQuadEdgeEffect() {} 470 471 const char* name() const override { return "PLSQuadEdge"; } 472 473 const Attribute* inPosition() const { return fInPosition; } 474 const Attribute* inUV() const { return fInUV; } 475 const Attribute* inEndpoint1() const { return fInEndpoint1; } 476 const Attribute* inEndpoint2() const { return fInEndpoint2; } 477 const Attribute* inWindings() const { return fInWindings; } 478 const SkMatrix& localMatrix() const { return fLocalMatrix; } 479 bool usesLocalCoords() const { return fUsesLocalCoords; } 480 481 class GLSLProcessor : public GrGLSLGeometryProcessor { 482 public: 483 GLSLProcessor(const GrGeometryProcessor&) {} 484 485 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 486 const PLSQuadEdgeEffect& qe = args.fGP.cast<PLSQuadEdgeEffect>(); 487 GrGLSLVertexBuilder* vsBuilder = args.fVertBuilder; 488 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 489 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 490 491 // emit attributes 492 varyingHandler->emitAttributes(qe); 493 494 GrGLSLVertToFrag uv(kVec2f_GrSLType); 495 varyingHandler->addVarying("uv", &uv, kHigh_GrSLPrecision); 496 vsBuilder->codeAppendf("%s = %s;", uv.vsOut(), qe.inUV()->fName); 497 498 GrGLSLVertToFrag ep1(kVec2f_GrSLType); 499 varyingHandler->addVarying("endpoint1", &ep1, kHigh_GrSLPrecision); 500 vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);", ep1.vsOut(), 501 qe.inEndpoint1()->fName, qe.inEndpoint1()->fName); 502 503 GrGLSLVertToFrag ep2(kVec2f_GrSLType); 504 varyingHandler->addVarying("endpoint2", &ep2, kHigh_GrSLPrecision); 505 vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);", ep2.vsOut(), 506 qe.inEndpoint2()->fName, qe.inEndpoint2()->fName); 507 508 GrGLSLVertToFrag delta(kVec2f_GrSLType); 509 varyingHandler->addVarying("delta", &delta, kHigh_GrSLPrecision); 510 vsBuilder->codeAppendf("%s = vec2(%s.x - %s.x, %s.y - %s.y) * 0.5;", 511 delta.vsOut(), ep1.vsOut(), ep2.vsOut(), ep2.vsOut(), 512 ep1.vsOut()); 513 514 GrGLSLVertToFrag windings(kInt_GrSLType); 515 varyingHandler->addFlatVarying("windings", &windings, kLow_GrSLPrecision); 516 vsBuilder->codeAppendf("%s = %s;", 517 windings.vsOut(), qe.inWindings()->fName); 518 519 // Setup position 520 this->setupPosition(vsBuilder, gpArgs, qe.inPosition()->fName); 521 522 // emit transforms 523 this->emitTransforms(vsBuilder, varyingHandler, uniformHandler, gpArgs->fPositionVar, 524 qe.inPosition()->fName, qe.localMatrix(), args.fTransformsIn, 525 args.fTransformsOut); 526 527 GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder; 528 SkAssertResult(fsBuilder->enableFeature( 529 GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature)); 530 SkAssertResult(fsBuilder->enableFeature( 531 GrGLSLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature)); 532 static const int QUAD_ARGS = 2; 533 GrGLSLShaderVar inQuadArgs[QUAD_ARGS] = { 534 GrGLSLShaderVar("dot", kFloat_GrSLType, 0, kHigh_GrSLPrecision), 535 GrGLSLShaderVar("uv", kVec2f_GrSLType, 0, kHigh_GrSLPrecision) 536 }; 537 SkString inQuadName; 538 539 const char* inQuadCode = "if (uv.x * uv.x <= uv.y) {" 540 "return dot >= 0.0;" 541 "} else {" 542 "return false;" 543 "}"; 544 fsBuilder->emitFunction(kBool_GrSLType, "in_quad", QUAD_ARGS, inQuadArgs, inQuadCode, 545 &inQuadName); 546 fsBuilder->declAppendf(GR_GL_PLS_PATH_DATA_DECL); 547 // keep the derivative instructions outside the conditional 548 fsBuilder->codeAppendf("highp vec2 uvdX = dFdx(%s);", uv.fsIn()); 549 fsBuilder->codeAppendf("highp vec2 uvdY = dFdy(%s);", uv.fsIn()); 550 fsBuilder->codeAppend("highp vec2 uvIncX = uvdX * 0.45 + uvdY * -0.1;"); 551 fsBuilder->codeAppend("highp vec2 uvIncY = uvdX * 0.1 + uvdY * 0.55;"); 552 fsBuilder->codeAppendf("highp vec2 uv = %s.xy - uvdX * 0.35 - uvdY * 0.25;", 553 uv.fsIn()); 554 fsBuilder->codeAppendf("highp vec2 firstSample = %s.xy - vec2(0.25);", 555 fsBuilder->fragmentPosition()); 556 fsBuilder->codeAppendf("highp float d = dot(%s, (firstSample - %s).yx) * 2.0;", 557 delta.fsIn(), ep1.fsIn()); 558 fsBuilder->codeAppendf("pls.windings[0] += %s(d, uv) ? %s : 0;", inQuadName.c_str(), 559 windings.fsIn()); 560 fsBuilder->codeAppend("uv += uvIncX;"); 561 fsBuilder->codeAppendf("d += %s.x;", delta.fsIn()); 562 fsBuilder->codeAppendf("pls.windings[1] += %s(d, uv) ? %s : 0;", inQuadName.c_str(), 563 windings.fsIn()); 564 fsBuilder->codeAppend("uv += uvIncY;"); 565 fsBuilder->codeAppendf("d += %s.y;", delta.fsIn()); 566 fsBuilder->codeAppendf("pls.windings[2] += %s(d, uv) ? %s : 0;", inQuadName.c_str(), 567 windings.fsIn()); 568 fsBuilder->codeAppend("uv -= uvIncX;"); 569 fsBuilder->codeAppendf("d -= %s.x;", delta.fsIn()); 570 fsBuilder->codeAppendf("pls.windings[3] += %s(d, uv) ? %s : 0;", inQuadName.c_str(), 571 windings.fsIn()); 572 } 573 574 static inline void GenKey(const GrGeometryProcessor& gp, 575 const GrGLSLCaps&, 576 GrProcessorKeyBuilder* b) { 577 const PLSQuadEdgeEffect& qee = gp.cast<PLSQuadEdgeEffect>(); 578 uint32_t key = 0; 579 key |= qee.usesLocalCoords() && qee.localMatrix().hasPerspective() ? 0x1 : 0x0; 580 b->add32(key); 581 } 582 583 virtual void setData(const GrGLSLProgramDataManager& pdman, 584 const GrPrimitiveProcessor& gp) override { 585 } 586 587 void setTransformData(const GrPrimitiveProcessor& primProc, 588 const GrGLSLProgramDataManager& pdman, 589 int index, 590 const SkTArray<const GrCoordTransform*, true>& transforms) override { 591 this->setTransformDataHelper<PLSQuadEdgeEffect>(primProc, pdman, index, transforms); 592 } 593 594 private: 595 typedef GrGLSLGeometryProcessor INHERITED; 596 }; 597 598 virtual void getGLSLProcessorKey(const GrGLSLCaps& caps, 599 GrProcessorKeyBuilder* b) const override { 600 GLSLProcessor::GenKey(*this, caps, b); 601 } 602 603 virtual GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override { 604 return new GLSLProcessor(*this); 605 } 606 607 private: 608 PLSQuadEdgeEffect(const SkMatrix& localMatrix, bool usesLocalCoords) 609 : fLocalMatrix(localMatrix) 610 , fUsesLocalCoords(usesLocalCoords) { 611 this->initClassID<PLSQuadEdgeEffect>(); 612 fInPosition = &this->addVertexAttrib(Attribute("inPosition", kVec2f_GrVertexAttribType, 613 kHigh_GrSLPrecision)); 614 fInUV = &this->addVertexAttrib(Attribute("inUV", kVec2f_GrVertexAttribType, 615 kHigh_GrSLPrecision)); 616 fInEndpoint1 = &this->addVertexAttrib(Attribute("inEndpoint1", kVec2f_GrVertexAttribType, 617 kHigh_GrSLPrecision)); 618 fInEndpoint2 = &this->addVertexAttrib(Attribute("inEndpoint2", kVec2f_GrVertexAttribType, 619 kHigh_GrSLPrecision)); 620 fInWindings = &this->addVertexAttrib(Attribute("inWindings", kInt_GrVertexAttribType, 621 kLow_GrSLPrecision)); 622 this->setWillReadFragmentPosition(); 623 } 624 625 const Attribute* fInPosition; 626 const Attribute* fInUV; 627 const Attribute* fInEndpoint1; 628 const Attribute* fInEndpoint2; 629 const Attribute* fInWindings; 630 SkMatrix fLocalMatrix; 631 bool fUsesLocalCoords; 632 633 GR_DECLARE_GEOMETRY_PROCESSOR_TEST; 634 635 typedef GrGeometryProcessor INHERITED; 636 }; 637 638 class PLSFinishEffect : public GrGeometryProcessor { 639 public: 640 641 static GrGeometryProcessor* Create(GrColor color, bool useEvenOdd, const SkMatrix& localMatrix, 642 bool usesLocalCoords) { 643 return new PLSFinishEffect(color, useEvenOdd, localMatrix, usesLocalCoords); 644 } 645 646 virtual ~PLSFinishEffect() {} 647 648 const char* name() const override { return "PLSFinish"; } 649 650 const Attribute* inPosition() const { return fInPosition; } 651 GrColor color() const { return fColor; } 652 bool colorIgnored() const { return GrColor_ILLEGAL == fColor; } 653 const SkMatrix& localMatrix() const { return fLocalMatrix; } 654 bool usesLocalCoords() const { return fUsesLocalCoords; } 655 656 GrPixelLocalStorageState getPixelLocalStorageState() const override { 657 return GrPixelLocalStorageState::kFinish_GrPixelLocalStorageState; 658 } 659 660 const char* getDestColorOverride() const override { 661 return GR_GL_PLS_DSTCOLOR_NAME; 662 } 663 664 class GLSLProcessor : public GrGLSLGeometryProcessor { 665 public: 666 GLSLProcessor(const GrGeometryProcessor&) {} 667 668 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 669 const PLSFinishEffect& fe = args.fGP.cast<PLSFinishEffect>(); 670 GrGLSLVertexBuilder* vsBuilder = args.fVertBuilder; 671 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 672 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 673 674 fUseEvenOdd = uniformHandler->addUniform(kFragment_GrShaderFlag, 675 kFloat_GrSLType, kLow_GrSLPrecision, 676 "useEvenOdd"); 677 const char* useEvenOdd = uniformHandler->getUniformCStr(fUseEvenOdd); 678 679 varyingHandler->emitAttributes(fe); 680 this->setupPosition(vsBuilder, gpArgs, fe.inPosition()->fName); 681 this->emitTransforms(vsBuilder, varyingHandler, uniformHandler, gpArgs->fPositionVar, 682 fe.inPosition()->fName, fe.localMatrix(), args.fTransformsIn, 683 args.fTransformsOut); 684 685 GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder; 686 SkAssertResult(fsBuilder->enableFeature( 687 GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature)); 688 fsBuilder->declAppendf(GR_GL_PLS_PATH_DATA_DECL); 689 fsBuilder->codeAppend("float coverage;"); 690 fsBuilder->codeAppendf("if (%s != 0.0) {", useEvenOdd); 691 fsBuilder->codeAppend("coverage = float(abs(pls.windings[0]) % 2) * 0.25;"); 692 fsBuilder->codeAppend("coverage += float(abs(pls.windings[1]) % 2) * 0.25;"); 693 fsBuilder->codeAppend("coverage += float(abs(pls.windings[2]) % 2) * 0.25;"); 694 fsBuilder->codeAppend("coverage += float(abs(pls.windings[3]) % 2) * 0.25;"); 695 fsBuilder->codeAppend("} else {"); 696 fsBuilder->codeAppend("coverage = pls.windings[0] != 0 ? 0.25 : 0.0;"); 697 fsBuilder->codeAppend("coverage += pls.windings[1] != 0 ? 0.25 : 0.0;"); 698 fsBuilder->codeAppend("coverage += pls.windings[2] != 0 ? 0.25 : 0.0;"); 699 fsBuilder->codeAppend("coverage += pls.windings[3] != 0 ? 0.25 : 0.0;"); 700 fsBuilder->codeAppend("}"); 701 if (!fe.colorIgnored()) { 702 this->setupUniformColor(fsBuilder, uniformHandler, args.fOutputColor, 703 &fColorUniform); 704 } 705 fsBuilder->codeAppendf("%s = vec4(coverage);", args.fOutputCoverage); 706 fsBuilder->codeAppendf("%s = vec4(1.0, 0.0, 1.0, 1.0);", args.fOutputColor); 707 } 708 709 static inline void GenKey(const GrGeometryProcessor& gp, 710 const GrGLSLCaps&, 711 GrProcessorKeyBuilder* b) { 712 const PLSFinishEffect& fe = gp.cast<PLSFinishEffect>(); 713 uint32_t key = 0; 714 key |= fe.usesLocalCoords() && fe.localMatrix().hasPerspective() ? 0x1 : 0x0; 715 b->add32(key); 716 } 717 718 virtual void setData(const GrGLSLProgramDataManager& pdman, 719 const GrPrimitiveProcessor& gp) override { 720 const PLSFinishEffect& fe = gp.cast<PLSFinishEffect>(); 721 pdman.set1f(fUseEvenOdd, fe.fUseEvenOdd); 722 if (fe.color() != fColor && !fe.colorIgnored()) { 723 GrGLfloat c[4]; 724 GrColorToRGBAFloat(fe.color(), c); 725 pdman.set4fv(fColorUniform, 1, c); 726 fColor = fe.color(); 727 } 728 } 729 730 void setTransformData(const GrPrimitiveProcessor& primProc, 731 const GrGLSLProgramDataManager& pdman, 732 int index, 733 const SkTArray<const GrCoordTransform*, true>& transforms) override { 734 this->setTransformDataHelper<PLSFinishEffect>(primProc, pdman, index, transforms); 735 } 736 737 private: 738 GrColor fColor; 739 UniformHandle fColorUniform; 740 UniformHandle fUseEvenOdd; 741 742 typedef GrGLSLGeometryProcessor INHERITED; 743 }; 744 745 virtual void getGLSLProcessorKey(const GrGLSLCaps& caps, 746 GrProcessorKeyBuilder* b) const override { 747 GLSLProcessor::GenKey(*this, caps, b); 748 } 749 750 virtual GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override { 751 return new GLSLProcessor(*this); 752 } 753 754 private: 755 PLSFinishEffect(GrColor color, bool useEvenOdd, const SkMatrix& localMatrix, 756 bool usesLocalCoords) 757 : fColor(color) 758 , fUseEvenOdd(useEvenOdd) 759 , fLocalMatrix(localMatrix) 760 , fUsesLocalCoords(usesLocalCoords) { 761 this->initClassID<PLSFinishEffect>(); 762 fInPosition = &this->addVertexAttrib(Attribute("inPosition", kVec2f_GrVertexAttribType, 763 kHigh_GrSLPrecision)); 764 } 765 766 const Attribute* fInPosition; 767 GrColor fColor; 768 bool fUseEvenOdd; 769 SkMatrix fLocalMatrix; 770 bool fUsesLocalCoords; 771 772 typedef GrGeometryProcessor INHERITED; 773 }; 774 775 /////////////////////////////////////////////////////////////////////////////// 776 777 bool GrPLSPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { 778 // We have support for even-odd rendering, but are having some troublesome 779 // seams. Disable in the presence of even-odd for now. 780 return args.fShaderCaps->shaderDerivativeSupport() && args.fAntiAlias && 781 args.fStroke->isFillStyle() && !args.fPath->isInverseFillType() && 782 args.fPath->getFillType() == SkPath::FillType::kWinding_FillType; 783 } 784 785 class PLSPathBatch : public GrVertexBatch { 786 public: 787 DEFINE_BATCH_CLASS_ID 788 struct Geometry { 789 GrColor fColor; 790 SkMatrix fViewMatrix; 791 SkPath fPath; 792 }; 793 794 static GrDrawBatch* Create(const Geometry& geometry) { 795 return new PLSPathBatch(geometry); 796 } 797 798 const char* name() const override { return "PLSBatch"; } 799 800 void computePipelineOptimizations(GrInitInvariantOutput* color, 801 GrInitInvariantOutput* coverage, 802 GrBatchToXPOverrides* overrides) const override { 803 // When this is called on a batch, there is only one geometry bundle 804 color->setKnownFourComponents(fGeoData[0].fColor); 805 coverage->setUnknownSingleComponent(); 806 overrides->fUsePLSDstRead = true; 807 } 808 809 void initBatchTracker(const GrXPOverridesForBatch& overrides) override { 810 // Handle any color overrides 811 if (!overrides.readsColor()) { 812 fGeoData[0].fColor = GrColor_ILLEGAL; 813 } 814 overrides.getOverrideColorIfSet(&fGeoData[0].fColor); 815 816 // setup batch properties 817 fBatch.fColorIgnored = !overrides.readsColor(); 818 fBatch.fColor = fGeoData[0].fColor; 819 fBatch.fUsesLocalCoords = overrides.readsLocalCoords(); 820 fBatch.fCoverageIgnored = !overrides.readsCoverage(); 821 fBatch.fCanTweakAlphaForCoverage = overrides.canTweakAlphaForCoverage(); 822 } 823 824 void onPrepareDraws(Target* target) const override { 825 int instanceCount = fGeoData.count(); 826 827 SkMatrix invert; 828 if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) { 829 SkDebugf("Could not invert viewmatrix\n"); 830 return; 831 } 832 833 // Setup GrGeometryProcessors 834 SkAutoTUnref<GrPLSGeometryProcessor> triangleProcessor( 835 PLSAATriangleEffect::Create(invert, this->usesLocalCoords())); 836 SkAutoTUnref<GrPLSGeometryProcessor> quadProcessor( 837 PLSQuadEdgeEffect::Create(invert, this->usesLocalCoords())); 838 839 GrResourceProvider* rp = target->resourceProvider(); 840 for (int i = 0; i < instanceCount; ++i) { 841 const Geometry& args = fGeoData[i]; 842 SkRect bounds = args.fPath.getBounds(); 843 args.fViewMatrix.mapRect(&bounds); 844 bounds.fLeft = SkScalarFloorToScalar(bounds.fLeft); 845 bounds.fTop = SkScalarFloorToScalar(bounds.fTop); 846 bounds.fRight = SkScalarCeilToScalar(bounds.fRight); 847 bounds.fBottom = SkScalarCeilToScalar(bounds.fBottom); 848 triangleProcessor->setBounds(bounds); 849 quadProcessor->setBounds(bounds); 850 851 // We use the fact that SkPath::transform path does subdivision based on 852 // perspective. Otherwise, we apply the view matrix when copying to the 853 // segment representation. 854 const SkMatrix* viewMatrix = &args.fViewMatrix; 855 856 // We avoid initializing the path unless we have to 857 const SkPath* pathPtr = &args.fPath; 858 SkTLazy<SkPath> tmpPath; 859 if (viewMatrix->hasPerspective()) { 860 SkPath* tmpPathPtr = tmpPath.init(*pathPtr); 861 tmpPathPtr->setIsVolatile(true); 862 tmpPathPtr->transform(*viewMatrix); 863 viewMatrix = &SkMatrix::I(); 864 pathPtr = tmpPathPtr; 865 } 866 867 GrVertices grVertices; 868 869 PLSVertices triVertices; 870 PLSVertices quadVertices; 871 if (!get_geometry(*pathPtr, *viewMatrix, triVertices, quadVertices, rp, bounds)) { 872 continue; 873 } 874 875 if (triVertices.count()) { 876 const GrVertexBuffer* triVertexBuffer; 877 int firstTriVertex; 878 size_t triStride = triangleProcessor->getVertexStride(); 879 PLSVertex* triVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace( 880 triStride, triVertices.count(), &triVertexBuffer, &firstTriVertex)); 881 if (!triVerts) { 882 SkDebugf("Could not allocate vertices\n"); 883 return; 884 } 885 for (int i = 0; i < triVertices.count(); ++i) { 886 triVerts[i] = triVertices[i]; 887 } 888 grVertices.init(kTriangles_GrPrimitiveType, triVertexBuffer, firstTriVertex, 889 triVertices.count()); 890 target->initDraw(triangleProcessor, this->pipeline()); 891 target->draw(grVertices); 892 } 893 894 if (quadVertices.count()) { 895 const GrVertexBuffer* quadVertexBuffer; 896 int firstQuadVertex; 897 size_t quadStride = quadProcessor->getVertexStride(); 898 PLSVertex* quadVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace( 899 quadStride, quadVertices.count(), &quadVertexBuffer, &firstQuadVertex)); 900 if (!quadVerts) { 901 SkDebugf("Could not allocate vertices\n"); 902 return; 903 } 904 for (int i = 0; i < quadVertices.count(); ++i) { 905 quadVerts[i] = quadVertices[i]; 906 } 907 grVertices.init(kTriangles_GrPrimitiveType, quadVertexBuffer, firstQuadVertex, 908 quadVertices.count()); 909 target->initDraw(quadProcessor, this->pipeline()); 910 target->draw(grVertices); 911 } 912 913 SkAutoTUnref<GrGeometryProcessor> finishProcessor( 914 PLSFinishEffect::Create(this->color(), 915 pathPtr->getFillType() == 916 SkPath::FillType::kEvenOdd_FillType, 917 invert, 918 this->usesLocalCoords())); 919 const GrVertexBuffer* rectVertexBuffer; 920 size_t finishStride = finishProcessor->getVertexStride(); 921 int firstRectVertex; 922 static const int kRectVertexCount = 6; 923 SkPoint* rectVerts = reinterpret_cast<SkPoint*>(target->makeVertexSpace( 924 finishStride, kRectVertexCount, &rectVertexBuffer, &firstRectVertex)); 925 if (!rectVerts) { 926 SkDebugf("Could not allocate vertices\n"); 927 return; 928 } 929 rectVerts[0] = { bounds.fLeft, bounds.fTop }; 930 rectVerts[1] = { bounds.fLeft, bounds.fBottom }; 931 rectVerts[2] = { bounds.fRight, bounds.fBottom }; 932 rectVerts[3] = { bounds.fLeft, bounds.fTop }; 933 rectVerts[4] = { bounds.fRight, bounds.fTop }; 934 rectVerts[5] = { bounds.fRight, bounds.fBottom }; 935 936 grVertices.init(kTriangles_GrPrimitiveType, rectVertexBuffer, firstRectVertex, 937 kRectVertexCount); 938 target->initDraw(finishProcessor, this->pipeline()); 939 target->draw(grVertices); 940 } 941 } 942 943 SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; } 944 945 private: 946 PLSPathBatch(const Geometry& geometry) : INHERITED(ClassID()) { 947 fGeoData.push_back(geometry); 948 949 // compute bounds 950 fBounds = geometry.fPath.getBounds(); 951 geometry.fViewMatrix.mapRect(&fBounds); 952 } 953 954 bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override { 955 return false; 956 } 957 958 GrColor color() const { return fBatch.fColor; } 959 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; } 960 bool canTweakAlphaForCoverage() const { return fBatch.fCanTweakAlphaForCoverage; } 961 const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; } 962 bool coverageIgnored() const { return fBatch.fCoverageIgnored; } 963 964 struct BatchTracker { 965 GrColor fColor; 966 bool fUsesLocalCoords; 967 bool fColorIgnored; 968 bool fCoverageIgnored; 969 bool fCanTweakAlphaForCoverage; 970 }; 971 972 BatchTracker fBatch; 973 SkSTArray<1, Geometry, true> fGeoData; 974 975 typedef GrVertexBatch INHERITED; 976 }; 977 978 SkDEBUGCODE(bool inPLSDraw = false;) 979 bool GrPLSPathRenderer::onDrawPath(const DrawPathArgs& args) { 980 if (args.fPath->isEmpty()) { 981 return true; 982 } 983 SkASSERT(!inPLSDraw); 984 SkDEBUGCODE(inPLSDraw = true;) 985 PLSPathBatch::Geometry geometry; 986 geometry.fColor = args.fColor; 987 geometry.fViewMatrix = *args.fViewMatrix; 988 geometry.fPath = *args.fPath; 989 990 SkAutoTUnref<GrDrawBatch> batch(PLSPathBatch::Create(geometry)); 991 args.fTarget->drawBatch(*args.fPipelineBuilder, batch); 992 993 SkDEBUGCODE(inPLSDraw = false;) 994 return true; 995 996 } 997 998 /////////////////////////////////////////////////////////////////////////////////////////////////// 999 1000 #ifdef GR_TEST_UTILS 1001 1002 DRAW_BATCH_TEST_DEFINE(PLSPathBatch) { 1003 PLSPathBatch::Geometry geometry; 1004 geometry.fColor = GrRandomColor(random); 1005 geometry.fViewMatrix = GrTest::TestMatrixInvertible(random); 1006 geometry.fPath = GrTest::TestPathConvex(random); 1007 1008 return PLSPathBatch::Create(geometry); 1009 } 1010 1011 #endif 1012