1 /* 2 * Copyright 2011 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 "GrDefaultPathRenderer.h" 9 #include "GrContext.h" 10 #include "GrDefaultGeoProcFactory.h" 11 #include "GrDrawOpTest.h" 12 #include "GrFixedClip.h" 13 #include "GrMesh.h" 14 #include "GrOpFlushState.h" 15 #include "GrPathUtils.h" 16 #include "GrSimpleMeshDrawOpHelper.h" 17 #include "SkGeometry.h" 18 #include "SkString.h" 19 #include "SkStrokeRec.h" 20 #include "SkTLazy.h" 21 #include "SkTraceEvent.h" 22 #include "ops/GrMeshDrawOp.h" 23 #include "ops/GrRectOpFactory.h" 24 25 GrDefaultPathRenderer::GrDefaultPathRenderer() { 26 } 27 28 //////////////////////////////////////////////////////////////////////////////// 29 // Helpers for drawPath 30 31 #define STENCIL_OFF 0 // Always disable stencil (even when needed) 32 33 static inline bool single_pass_shape(const GrShape& shape) { 34 #if STENCIL_OFF 35 return true; 36 #else 37 // Inverse fill is always two pass. 38 if (shape.inverseFilled()) { 39 return false; 40 } 41 // This path renderer only accepts simple fill paths or stroke paths that are either hairline 42 // or have a stroke width small enough to treat as hairline. Hairline paths are always single 43 // pass. Filled paths are single pass if they're convex. 44 if (shape.style().isSimpleFill()) { 45 return shape.knownToBeConvex(); 46 } 47 return true; 48 #endif 49 } 50 51 GrPathRenderer::StencilSupport 52 GrDefaultPathRenderer::onGetStencilSupport(const GrShape& shape) const { 53 if (single_pass_shape(shape)) { 54 return GrPathRenderer::kNoRestriction_StencilSupport; 55 } else { 56 return GrPathRenderer::kStencilOnly_StencilSupport; 57 } 58 } 59 60 namespace { 61 62 class PathGeoBuilder { 63 public: 64 PathGeoBuilder(GrPrimitiveType primitiveType, GrMeshDrawOp::Target* target, 65 GrGeometryProcessor* geometryProcessor, const GrPipeline* pipeline) 66 : fMesh(primitiveType) 67 , fTarget(target) 68 , fVertexStride(sizeof(SkPoint)) 69 , fGeometryProcessor(geometryProcessor) 70 , fPipeline(pipeline) 71 , fIndexBuffer(nullptr) 72 , fFirstIndex(0) 73 , fIndicesInChunk(0) 74 , fIndices(nullptr) { 75 this->allocNewBuffers(); 76 } 77 78 ~PathGeoBuilder() { 79 this->emitMeshAndPutBackReserve(); 80 } 81 82 /** 83 * Path verbs 84 */ 85 void moveTo(const SkPoint& p) { 86 needSpace(1); 87 88 fSubpathIndexStart = this->currentIndex(); 89 *(fCurVert++) = p; 90 } 91 92 void addLine(const SkPoint& p) { 93 needSpace(1, this->indexScale()); 94 95 if (this->isIndexed()) { 96 uint16_t prevIdx = this->currentIndex() - 1; 97 appendCountourEdgeIndices(prevIdx); 98 } 99 *(fCurVert++) = p; 100 } 101 102 void addQuad(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) { 103 this->needSpace(GrPathUtils::kMaxPointsPerCurve, 104 GrPathUtils::kMaxPointsPerCurve * this->indexScale()); 105 106 // First pt of quad is the pt we ended on in previous step 107 uint16_t firstQPtIdx = this->currentIndex() - 1; 108 uint16_t numPts = (uint16_t)GrPathUtils::generateQuadraticPoints( 109 pts[0], pts[1], pts[2], srcSpaceTolSqd, &fCurVert, 110 GrPathUtils::quadraticPointCount(pts, srcSpaceTol)); 111 if (this->isIndexed()) { 112 for (uint16_t i = 0; i < numPts; ++i) { 113 appendCountourEdgeIndices(firstQPtIdx + i); 114 } 115 } 116 } 117 118 void addConic(SkScalar weight, const SkPoint pts[], SkScalar srcSpaceTolSqd, 119 SkScalar srcSpaceTol) { 120 SkAutoConicToQuads converter; 121 const SkPoint* quadPts = converter.computeQuads(pts, weight, srcSpaceTol); 122 for (int i = 0; i < converter.countQuads(); ++i) { 123 this->addQuad(quadPts + i * 2, srcSpaceTolSqd, srcSpaceTol); 124 } 125 } 126 127 void addCubic(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) { 128 this->needSpace(GrPathUtils::kMaxPointsPerCurve, 129 GrPathUtils::kMaxPointsPerCurve * this->indexScale()); 130 131 // First pt of cubic is the pt we ended on in previous step 132 uint16_t firstCPtIdx = this->currentIndex() - 1; 133 uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints( 134 pts[0], pts[1], pts[2], pts[3], srcSpaceTolSqd, &fCurVert, 135 GrPathUtils::cubicPointCount(pts, srcSpaceTol)); 136 if (this->isIndexed()) { 137 for (uint16_t i = 0; i < numPts; ++i) { 138 appendCountourEdgeIndices(firstCPtIdx + i); 139 } 140 } 141 } 142 143 void addPath(const SkPath& path, SkScalar srcSpaceTol) { 144 SkScalar srcSpaceTolSqd = srcSpaceTol * srcSpaceTol; 145 146 SkPath::Iter iter(path, false); 147 SkPoint pts[4]; 148 149 bool done = false; 150 while (!done) { 151 SkPath::Verb verb = iter.next(pts); 152 switch (verb) { 153 case SkPath::kMove_Verb: 154 this->moveTo(pts[0]); 155 break; 156 case SkPath::kLine_Verb: 157 this->addLine(pts[1]); 158 break; 159 case SkPath::kConic_Verb: 160 this->addConic(iter.conicWeight(), pts, srcSpaceTolSqd, srcSpaceTol); 161 break; 162 case SkPath::kQuad_Verb: 163 this->addQuad(pts, srcSpaceTolSqd, srcSpaceTol); 164 break; 165 case SkPath::kCubic_Verb: 166 this->addCubic(pts, srcSpaceTolSqd, srcSpaceTol); 167 break; 168 case SkPath::kClose_Verb: 169 break; 170 case SkPath::kDone_Verb: 171 done = true; 172 } 173 } 174 } 175 176 static bool PathHasMultipleSubpaths(const SkPath& path) { 177 bool first = true; 178 179 SkPath::Iter iter(path, false); 180 SkPath::Verb verb; 181 182 SkPoint pts[4]; 183 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { 184 if (SkPath::kMove_Verb == verb && !first) { 185 return true; 186 } 187 first = false; 188 } 189 return false; 190 } 191 192 private: 193 /** 194 * Derived properties 195 * TODO: Cache some of these for better performance, rather than re-computing? 196 */ 197 bool isIndexed() const { 198 return GrPrimitiveType::kLines == fMesh.primitiveType() || 199 GrPrimitiveType::kTriangles == fMesh.primitiveType(); 200 } 201 bool isHairline() const { 202 return GrPrimitiveType::kLines == fMesh.primitiveType() || 203 GrPrimitiveType::kLineStrip == fMesh.primitiveType(); 204 } 205 int indexScale() const { 206 switch (fMesh.primitiveType()) { 207 case GrPrimitiveType::kLines: 208 return 2; 209 case GrPrimitiveType::kTriangles: 210 return 3; 211 default: 212 return 0; 213 } 214 } 215 216 uint16_t currentIndex() const { return fCurVert - fVertices; } 217 218 // Allocate vertex and (possibly) index buffers 219 void allocNewBuffers() { 220 // Ensure that we always get enough verts for a worst-case quad/cubic, plus leftover points 221 // from previous mesh piece (up to two verts to continue fanning). If we can't get that 222 // many, ask for a much larger number. This needs to be fairly big to handle quads/cubics, 223 // which have a worst-case of 1k points. 224 static const int kMinVerticesPerChunk = GrPathUtils::kMaxPointsPerCurve + 2; 225 static const int kFallbackVerticesPerChunk = 16384; 226 227 fVertices = static_cast<SkPoint*>(fTarget->makeVertexSpaceAtLeast(fVertexStride, 228 kMinVerticesPerChunk, 229 kFallbackVerticesPerChunk, 230 &fVertexBuffer, 231 &fFirstVertex, 232 &fVerticesInChunk)); 233 234 if (this->isIndexed()) { 235 // Similar to above: Ensure we get enough indices for one worst-case quad/cubic. 236 // No extra indices are needed for stitching, though. If we can't get that many, ask 237 // for enough to match our large vertex request. 238 const int kMinIndicesPerChunk = GrPathUtils::kMaxPointsPerCurve * this->indexScale(); 239 const int kFallbackIndicesPerChunk = kFallbackVerticesPerChunk * this->indexScale(); 240 241 fIndices = fTarget->makeIndexSpaceAtLeast(kMinIndicesPerChunk, kFallbackIndicesPerChunk, 242 &fIndexBuffer, &fFirstIndex, 243 &fIndicesInChunk); 244 } 245 246 fCurVert = fVertices; 247 fCurIdx = fIndices; 248 fSubpathIndexStart = 0; 249 } 250 251 void appendCountourEdgeIndices(uint16_t edgeV0Idx) { 252 // When drawing lines we're appending line segments along the countour. When applying the 253 // other fill rules we're drawing triangle fans around the start of the current (sub)path. 254 if (!this->isHairline()) { 255 *(fCurIdx++) = fSubpathIndexStart; 256 } 257 *(fCurIdx++) = edgeV0Idx; 258 *(fCurIdx++) = edgeV0Idx + 1; 259 } 260 261 // Emits a single draw with all accumulated vertex/index data 262 void emitMeshAndPutBackReserve() { 263 int vertexCount = fCurVert - fVertices; 264 int indexCount = fCurIdx - fIndices; 265 SkASSERT(vertexCount <= fVerticesInChunk); 266 SkASSERT(indexCount <= fIndicesInChunk); 267 268 if (vertexCount > 0) { 269 if (!this->isIndexed()) { 270 fMesh.setNonIndexedNonInstanced(vertexCount); 271 } else { 272 fMesh.setIndexed(fIndexBuffer, indexCount, fFirstIndex, 0, vertexCount - 1); 273 } 274 fMesh.setVertexData(fVertexBuffer, fFirstVertex); 275 fTarget->draw(fGeometryProcessor, fPipeline, fMesh); 276 } 277 278 fTarget->putBackIndices((size_t)(fIndicesInChunk - indexCount)); 279 fTarget->putBackVertices((size_t)(fVerticesInChunk - vertexCount), fVertexStride); 280 } 281 282 void needSpace(int vertsNeeded, int indicesNeeded = 0) { 283 if (fCurVert + vertsNeeded > fVertices + fVerticesInChunk || 284 fCurIdx + indicesNeeded > fIndices + fIndicesInChunk) { 285 // We are about to run out of space (possibly) 286 287 // To maintain continuity, we need to remember one or two points from the current mesh. 288 // Lines only need the last point, fills need the first point from the current contour. 289 // We always grab both here, and append the ones we need at the end of this process. 290 SkPoint lastPt = *(fCurVert - 1); 291 SkASSERT(fSubpathIndexStart < fVerticesInChunk); 292 SkPoint subpathStartPt = fVertices[fSubpathIndexStart]; 293 294 // Draw the mesh we've accumulated, and put back any unused space 295 this->emitMeshAndPutBackReserve(); 296 297 // Get new buffers 298 this->allocNewBuffers(); 299 300 // Append copies of the points we saved so the two meshes will weld properly 301 if (!this->isHairline()) { 302 *(fCurVert++) = subpathStartPt; 303 } 304 *(fCurVert++) = lastPt; 305 } 306 } 307 308 GrMesh fMesh; 309 GrMeshDrawOp::Target* fTarget; 310 size_t fVertexStride; 311 GrGeometryProcessor* fGeometryProcessor; 312 const GrPipeline* fPipeline; 313 314 const GrBuffer* fVertexBuffer; 315 int fFirstVertex; 316 int fVerticesInChunk; 317 SkPoint* fVertices; 318 SkPoint* fCurVert; 319 320 const GrBuffer* fIndexBuffer; 321 int fFirstIndex; 322 int fIndicesInChunk; 323 uint16_t* fIndices; 324 uint16_t* fCurIdx; 325 uint16_t fSubpathIndexStart; 326 }; 327 328 class DefaultPathOp final : public GrMeshDrawOp { 329 private: 330 using Helper = GrSimpleMeshDrawOpHelperWithStencil; 331 332 public: 333 DEFINE_OP_CLASS_ID 334 335 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkPath& path, SkScalar tolerance, 336 uint8_t coverage, const SkMatrix& viewMatrix, 337 bool isHairline, GrAAType aaType, const SkRect& devBounds, 338 const GrUserStencilSettings* stencilSettings) { 339 return Helper::FactoryHelper<DefaultPathOp>(std::move(paint), path, tolerance, coverage, 340 viewMatrix, isHairline, aaType, devBounds, 341 stencilSettings); 342 } 343 344 const char* name() const override { return "DefaultPathOp"; } 345 346 SkString dumpInfo() const override { 347 SkString string; 348 string.appendf("Color: 0x%08x Count: %d\n", fColor, fPaths.count()); 349 for (const auto& path : fPaths) { 350 string.appendf("Tolerance: %.2f\n", path.fTolerance); 351 } 352 string += fHelper.dumpInfo(); 353 string += INHERITED::dumpInfo(); 354 return string; 355 } 356 357 DefaultPathOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkPath& path, 358 SkScalar tolerance, uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline, 359 GrAAType aaType, const SkRect& devBounds, 360 const GrUserStencilSettings* stencilSettings) 361 : INHERITED(ClassID()) 362 , fHelper(helperArgs, aaType, stencilSettings) 363 , fColor(color) 364 , fCoverage(coverage) 365 , fViewMatrix(viewMatrix) 366 , fIsHairline(isHairline) { 367 fPaths.emplace_back(PathData{path, tolerance}); 368 369 this->setBounds(devBounds, HasAABloat::kNo, 370 isHairline ? IsZeroArea::kYes : IsZeroArea::kNo); 371 } 372 373 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 374 375 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override { 376 GrProcessorAnalysisCoverage gpCoverage = 377 this->coverage() == 0xFF ? GrProcessorAnalysisCoverage::kNone 378 : GrProcessorAnalysisCoverage::kSingleChannel; 379 return fHelper.xpRequiresDstTexture(caps, clip, gpCoverage, &fColor); 380 } 381 382 private: 383 void onPrepareDraws(Target* target) const override { 384 sk_sp<GrGeometryProcessor> gp; 385 { 386 using namespace GrDefaultGeoProcFactory; 387 Color color(this->color()); 388 Coverage coverage(this->coverage()); 389 LocalCoords localCoords(fHelper.usesLocalCoords() ? LocalCoords::kUsePosition_Type 390 : LocalCoords::kUnused_Type); 391 gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, this->viewMatrix()); 392 } 393 394 SkASSERT(gp->getVertexStride() == sizeof(SkPoint)); 395 396 int instanceCount = fPaths.count(); 397 398 // We will use index buffers if we have multiple paths or one path with multiple contours 399 bool isIndexed = instanceCount > 1; 400 for (int i = 0; !isIndexed && i < instanceCount; i++) { 401 const PathData& args = fPaths[i]; 402 isIndexed = isIndexed || PathGeoBuilder::PathHasMultipleSubpaths(args.fPath); 403 } 404 405 // determine primitiveType 406 GrPrimitiveType primitiveType; 407 if (this->isHairline()) { 408 primitiveType = isIndexed ? GrPrimitiveType::kLines : GrPrimitiveType::kLineStrip; 409 } else { 410 primitiveType = isIndexed ? GrPrimitiveType::kTriangles : GrPrimitiveType::kTriangleFan; 411 } 412 413 PathGeoBuilder pathGeoBuilder(primitiveType, target, gp.get(), 414 fHelper.makePipeline(target)); 415 416 // fill buffers 417 for (int i = 0; i < instanceCount; i++) { 418 const PathData& args = fPaths[i]; 419 pathGeoBuilder.addPath(args.fPath, args.fTolerance); 420 } 421 } 422 423 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override { 424 DefaultPathOp* that = t->cast<DefaultPathOp>(); 425 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 426 return false; 427 } 428 429 if (this->color() != that->color()) { 430 return false; 431 } 432 433 if (this->coverage() != that->coverage()) { 434 return false; 435 } 436 437 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { 438 return false; 439 } 440 441 if (this->isHairline() != that->isHairline()) { 442 return false; 443 } 444 445 fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin()); 446 this->joinBounds(*that); 447 return true; 448 } 449 450 GrColor color() const { return fColor; } 451 uint8_t coverage() const { return fCoverage; } 452 const SkMatrix& viewMatrix() const { return fViewMatrix; } 453 bool isHairline() const { return fIsHairline; } 454 455 struct PathData { 456 SkPath fPath; 457 SkScalar fTolerance; 458 }; 459 460 SkSTArray<1, PathData, true> fPaths; 461 Helper fHelper; 462 GrColor fColor; 463 uint8_t fCoverage; 464 SkMatrix fViewMatrix; 465 bool fIsHairline; 466 467 typedef GrMeshDrawOp INHERITED; 468 }; 469 470 } // anonymous namespace 471 472 bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext, 473 GrPaint&& paint, 474 GrAAType aaType, 475 const GrUserStencilSettings& userStencilSettings, 476 const GrClip& clip, 477 const SkMatrix& viewMatrix, 478 const GrShape& shape, 479 bool stencilOnly) { 480 SkASSERT(GrAAType::kCoverage != aaType); 481 SkPath path; 482 shape.asPath(&path); 483 484 SkScalar hairlineCoverage; 485 uint8_t newCoverage = 0xff; 486 bool isHairline = false; 487 if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) { 488 newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff); 489 isHairline = true; 490 } else { 491 SkASSERT(shape.style().isSimpleFill()); 492 } 493 494 int passCount = 0; 495 const GrUserStencilSettings* passes[2]; 496 bool reverse = false; 497 bool lastPassIsBounds; 498 499 if (isHairline) { 500 passCount = 1; 501 if (stencilOnly) { 502 passes[0] = &gDirectToStencil; 503 } else { 504 passes[0] = &userStencilSettings; 505 } 506 lastPassIsBounds = false; 507 } else { 508 if (single_pass_shape(shape)) { 509 passCount = 1; 510 if (stencilOnly) { 511 passes[0] = &gDirectToStencil; 512 } else { 513 passes[0] = &userStencilSettings; 514 } 515 lastPassIsBounds = false; 516 } else { 517 switch (path.getFillType()) { 518 case SkPath::kInverseEvenOdd_FillType: 519 reverse = true; 520 // fallthrough 521 case SkPath::kEvenOdd_FillType: 522 passes[0] = &gEOStencilPass; 523 if (stencilOnly) { 524 passCount = 1; 525 lastPassIsBounds = false; 526 } else { 527 passCount = 2; 528 lastPassIsBounds = true; 529 if (reverse) { 530 passes[1] = &gInvEOColorPass; 531 } else { 532 passes[1] = &gEOColorPass; 533 } 534 } 535 break; 536 537 case SkPath::kInverseWinding_FillType: 538 reverse = true; 539 // fallthrough 540 case SkPath::kWinding_FillType: 541 passes[0] = &gWindStencilPass; 542 passCount = 2; 543 if (stencilOnly) { 544 lastPassIsBounds = false; 545 --passCount; 546 } else { 547 lastPassIsBounds = true; 548 if (reverse) { 549 passes[passCount-1] = &gInvWindColorPass; 550 } else { 551 passes[passCount-1] = &gWindColorPass; 552 } 553 } 554 break; 555 default: 556 SkDEBUGFAIL("Unknown path fFill!"); 557 return false; 558 } 559 } 560 } 561 562 SkScalar tol = GrPathUtils::kDefaultTolerance; 563 SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds()); 564 565 SkRect devBounds; 566 GetPathDevBounds(path, renderTargetContext->width(), renderTargetContext->height(), viewMatrix, 567 &devBounds); 568 569 for (int p = 0; p < passCount; ++p) { 570 if (lastPassIsBounds && (p == passCount-1)) { 571 SkRect bounds; 572 SkMatrix localMatrix = SkMatrix::I(); 573 if (reverse) { 574 // draw over the dev bounds (which will be the whole dst surface for inv fill). 575 bounds = devBounds; 576 SkMatrix vmi; 577 // mapRect through persp matrix may not be correct 578 if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { 579 vmi.mapRect(&bounds); 580 } else { 581 if (!viewMatrix.invert(&localMatrix)) { 582 return false; 583 } 584 } 585 } else { 586 bounds = path.getBounds(); 587 } 588 const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() : 589 viewMatrix; 590 renderTargetContext->addDrawOp( 591 clip, 592 GrRectOpFactory::MakeNonAAFillWithLocalMatrix( 593 std::move(paint), viewM, localMatrix, bounds, aaType, passes[p])); 594 } else { 595 bool stencilPass = stencilOnly || passCount > 1; 596 GrPaint::MoveOrNew passPaint(paint, stencilPass); 597 if (stencilPass) { 598 passPaint.paint().setXPFactory(GrDisableColorXPFactory::Get()); 599 } 600 std::unique_ptr<GrDrawOp> op = 601 DefaultPathOp::Make(std::move(passPaint), path, srcSpaceTol, newCoverage, 602 viewMatrix, isHairline, aaType, devBounds, passes[p]); 603 renderTargetContext->addDrawOp(clip, std::move(op)); 604 } 605 } 606 return true; 607 } 608 609 bool GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { 610 bool isHairline = IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr); 611 // If we aren't a single_pass_shape or hairline, we require stencil buffers. 612 if (!(single_pass_shape(*args.fShape) || isHairline) && args.fCaps->avoidStencilBuffers()) { 613 return false; 614 } 615 // This can draw any path with any simple fill style but doesn't do coverage-based antialiasing. 616 return GrAAType::kCoverage != args.fAAType && 617 (args.fShape->style().isSimpleFill() || isHairline); 618 } 619 620 bool GrDefaultPathRenderer::onDrawPath(const DrawPathArgs& args) { 621 GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), 622 "GrDefaultPathRenderer::onDrawPath"); 623 return this->internalDrawPath(args.fRenderTargetContext, 624 std::move(args.fPaint), 625 args.fAAType, 626 *args.fUserStencilSettings, 627 *args.fClip, 628 *args.fViewMatrix, 629 *args.fShape, 630 false); 631 } 632 633 void GrDefaultPathRenderer::onStencilPath(const StencilPathArgs& args) { 634 GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), 635 "GrDefaultPathRenderer::onStencilPath"); 636 SkASSERT(!args.fShape->inverseFilled()); 637 638 GrPaint paint; 639 paint.setXPFactory(GrDisableColorXPFactory::Get()); 640 641 this->internalDrawPath(args.fRenderTargetContext, std::move(paint), args.fAAType, 642 GrUserStencilSettings::kUnused, *args.fClip, *args.fViewMatrix, 643 *args.fShape, true); 644 } 645 646 /////////////////////////////////////////////////////////////////////////////////////////////////// 647 648 #if GR_TEST_UTILS 649 650 GR_DRAW_OP_TEST_DEFINE(DefaultPathOp) { 651 SkMatrix viewMatrix = GrTest::TestMatrix(random); 652 653 // For now just hairlines because the other types of draws require two ops. 654 // TODO we should figure out a way to combine the stencil and cover steps into one op. 655 GrStyle style(SkStrokeRec::kHairline_InitStyle); 656 SkPath path = GrTest::TestPath(random); 657 658 // Compute srcSpaceTol 659 SkRect bounds = path.getBounds(); 660 SkScalar tol = GrPathUtils::kDefaultTolerance; 661 SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, bounds); 662 663 viewMatrix.mapRect(&bounds); 664 uint8_t coverage = GrRandomCoverage(random); 665 GrAAType aaType = GrAAType::kNone; 666 if (GrFSAAType::kUnifiedMSAA == fsaaType && random->nextBool()) { 667 aaType = GrAAType::kMSAA; 668 } 669 return DefaultPathOp::Make(std::move(paint), path, srcSpaceTol, coverage, viewMatrix, true, 670 aaType, bounds, GrGetRandomStencil(random, context)); 671 } 672 673 #endif 674