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, false); 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, false)) != 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 (this->isIndexed() ? SkToBool(indexCount) : SkToBool(vertexCount)) { 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 void visitProxies(const VisitProxyFunc& func) const override { 347 fHelper.visitProxies(func); 348 } 349 350 SkString dumpInfo() const override { 351 SkString string; 352 string.appendf("Color: 0x%08x Count: %d\n", fColor, fPaths.count()); 353 for (const auto& path : fPaths) { 354 string.appendf("Tolerance: %.2f\n", path.fTolerance); 355 } 356 string += fHelper.dumpInfo(); 357 string += INHERITED::dumpInfo(); 358 return string; 359 } 360 361 DefaultPathOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkPath& path, 362 SkScalar tolerance, uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline, 363 GrAAType aaType, const SkRect& devBounds, 364 const GrUserStencilSettings* stencilSettings) 365 : INHERITED(ClassID()) 366 , fHelper(helperArgs, aaType, stencilSettings) 367 , fColor(color) 368 , fCoverage(coverage) 369 , fViewMatrix(viewMatrix) 370 , fIsHairline(isHairline) { 371 fPaths.emplace_back(PathData{path, tolerance}); 372 373 this->setBounds(devBounds, HasAABloat::kNo, 374 isHairline ? IsZeroArea::kYes : IsZeroArea::kNo); 375 } 376 377 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 378 379 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip, 380 GrPixelConfigIsClamped dstIsClamped) override { 381 GrProcessorAnalysisCoverage gpCoverage = 382 this->coverage() == 0xFF ? GrProcessorAnalysisCoverage::kNone 383 : GrProcessorAnalysisCoverage::kSingleChannel; 384 return fHelper.xpRequiresDstTexture(caps, clip, dstIsClamped, gpCoverage, &fColor); 385 } 386 387 private: 388 void onPrepareDraws(Target* target) override { 389 sk_sp<GrGeometryProcessor> gp; 390 { 391 using namespace GrDefaultGeoProcFactory; 392 Color color(this->color()); 393 Coverage coverage(this->coverage()); 394 LocalCoords localCoords(fHelper.usesLocalCoords() ? LocalCoords::kUsePosition_Type 395 : LocalCoords::kUnused_Type); 396 gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, this->viewMatrix()); 397 } 398 399 SkASSERT(gp->getVertexStride() == sizeof(SkPoint)); 400 401 int instanceCount = fPaths.count(); 402 403 // We will use index buffers if we have multiple paths or one path with multiple contours 404 bool isIndexed = instanceCount > 1; 405 for (int i = 0; !isIndexed && i < instanceCount; i++) { 406 const PathData& args = fPaths[i]; 407 isIndexed = isIndexed || PathGeoBuilder::PathHasMultipleSubpaths(args.fPath); 408 } 409 410 // determine primitiveType 411 GrPrimitiveType primitiveType; 412 if (this->isHairline()) { 413 primitiveType = isIndexed ? GrPrimitiveType::kLines : GrPrimitiveType::kLineStrip; 414 } else { 415 primitiveType = isIndexed ? GrPrimitiveType::kTriangles : GrPrimitiveType::kTriangleFan; 416 } 417 418 PathGeoBuilder pathGeoBuilder(primitiveType, target, gp.get(), 419 fHelper.makePipeline(target)); 420 421 // fill buffers 422 for (int i = 0; i < instanceCount; i++) { 423 const PathData& args = fPaths[i]; 424 pathGeoBuilder.addPath(args.fPath, args.fTolerance); 425 } 426 } 427 428 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override { 429 DefaultPathOp* that = t->cast<DefaultPathOp>(); 430 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 431 return false; 432 } 433 434 if (this->color() != that->color()) { 435 return false; 436 } 437 438 if (this->coverage() != that->coverage()) { 439 return false; 440 } 441 442 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { 443 return false; 444 } 445 446 if (this->isHairline() != that->isHairline()) { 447 return false; 448 } 449 450 fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin()); 451 this->joinBounds(*that); 452 return true; 453 } 454 455 GrColor color() const { return fColor; } 456 uint8_t coverage() const { return fCoverage; } 457 const SkMatrix& viewMatrix() const { return fViewMatrix; } 458 bool isHairline() const { return fIsHairline; } 459 460 struct PathData { 461 SkPath fPath; 462 SkScalar fTolerance; 463 }; 464 465 SkSTArray<1, PathData, true> fPaths; 466 Helper fHelper; 467 GrColor fColor; 468 uint8_t fCoverage; 469 SkMatrix fViewMatrix; 470 bool fIsHairline; 471 472 typedef GrMeshDrawOp INHERITED; 473 }; 474 475 } // anonymous namespace 476 477 bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext, 478 GrPaint&& paint, 479 GrAAType aaType, 480 const GrUserStencilSettings& userStencilSettings, 481 const GrClip& clip, 482 const SkMatrix& viewMatrix, 483 const GrShape& shape, 484 bool stencilOnly) { 485 SkASSERT(GrAAType::kCoverage != aaType); 486 SkPath path; 487 shape.asPath(&path); 488 489 SkScalar hairlineCoverage; 490 uint8_t newCoverage = 0xff; 491 bool isHairline = false; 492 if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) { 493 newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff); 494 isHairline = true; 495 } else { 496 SkASSERT(shape.style().isSimpleFill()); 497 } 498 499 int passCount = 0; 500 const GrUserStencilSettings* passes[2]; 501 bool reverse = false; 502 bool lastPassIsBounds; 503 504 if (isHairline) { 505 passCount = 1; 506 if (stencilOnly) { 507 passes[0] = &gDirectToStencil; 508 } else { 509 passes[0] = &userStencilSettings; 510 } 511 lastPassIsBounds = false; 512 } else { 513 if (single_pass_shape(shape)) { 514 passCount = 1; 515 if (stencilOnly) { 516 passes[0] = &gDirectToStencil; 517 } else { 518 passes[0] = &userStencilSettings; 519 } 520 lastPassIsBounds = false; 521 } else { 522 switch (path.getFillType()) { 523 case SkPath::kInverseEvenOdd_FillType: 524 reverse = true; 525 // fallthrough 526 case SkPath::kEvenOdd_FillType: 527 passes[0] = &gEOStencilPass; 528 if (stencilOnly) { 529 passCount = 1; 530 lastPassIsBounds = false; 531 } else { 532 passCount = 2; 533 lastPassIsBounds = true; 534 if (reverse) { 535 passes[1] = &gInvEOColorPass; 536 } else { 537 passes[1] = &gEOColorPass; 538 } 539 } 540 break; 541 542 case SkPath::kInverseWinding_FillType: 543 reverse = true; 544 // fallthrough 545 case SkPath::kWinding_FillType: 546 passes[0] = &gWindStencilPass; 547 passCount = 2; 548 if (stencilOnly) { 549 lastPassIsBounds = false; 550 --passCount; 551 } else { 552 lastPassIsBounds = true; 553 if (reverse) { 554 passes[passCount-1] = &gInvWindColorPass; 555 } else { 556 passes[passCount-1] = &gWindColorPass; 557 } 558 } 559 break; 560 default: 561 SkDEBUGFAIL("Unknown path fFill!"); 562 return false; 563 } 564 } 565 } 566 567 SkScalar tol = GrPathUtils::kDefaultTolerance; 568 SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds()); 569 570 SkRect devBounds; 571 GetPathDevBounds(path, 572 renderTargetContext->asRenderTargetProxy()->worstCaseWidth(), 573 renderTargetContext->asRenderTargetProxy()->worstCaseHeight(), 574 viewMatrix, &devBounds); 575 576 for (int p = 0; p < passCount; ++p) { 577 if (lastPassIsBounds && (p == passCount-1)) { 578 SkRect bounds; 579 SkMatrix localMatrix = SkMatrix::I(); 580 if (reverse) { 581 // draw over the dev bounds (which will be the whole dst surface for inv fill). 582 bounds = devBounds; 583 SkMatrix vmi; 584 // mapRect through persp matrix may not be correct 585 if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { 586 vmi.mapRect(&bounds); 587 } else { 588 if (!viewMatrix.invert(&localMatrix)) { 589 return false; 590 } 591 } 592 } else { 593 bounds = path.getBounds(); 594 } 595 const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() : 596 viewMatrix; 597 renderTargetContext->addDrawOp( 598 clip, 599 GrRectOpFactory::MakeNonAAFillWithLocalMatrix( 600 std::move(paint), viewM, localMatrix, bounds, aaType, passes[p])); 601 } else { 602 bool stencilPass = stencilOnly || passCount > 1; 603 std::unique_ptr<GrDrawOp> op; 604 if (stencilPass) { 605 GrPaint stencilPaint; 606 stencilPaint.setXPFactory(GrDisableColorXPFactory::Get()); 607 op = DefaultPathOp::Make(std::move(stencilPaint), path, srcSpaceTol, newCoverage, 608 viewMatrix, isHairline, aaType, devBounds, passes[p]); 609 } else { 610 op = DefaultPathOp::Make(std::move(paint), path, srcSpaceTol, newCoverage, 611 viewMatrix, isHairline, aaType, devBounds, passes[p]); 612 } 613 renderTargetContext->addDrawOp(clip, std::move(op)); 614 } 615 } 616 return true; 617 } 618 619 GrPathRenderer::CanDrawPath 620 GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { 621 bool isHairline = IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr); 622 // If we aren't a single_pass_shape or hairline, we require stencil buffers. 623 if (!(single_pass_shape(*args.fShape) || isHairline) && args.fCaps->avoidStencilBuffers()) { 624 return CanDrawPath::kNo; 625 } 626 // This can draw any path with any simple fill style but doesn't do coverage-based antialiasing. 627 if (GrAAType::kCoverage == args.fAAType || 628 (!args.fShape->style().isSimpleFill() && !isHairline)) { 629 return CanDrawPath::kNo; 630 } 631 // This is the fallback renderer for when a path is too complicated for the others to draw. 632 return CanDrawPath::kAsBackup; 633 } 634 635 bool GrDefaultPathRenderer::onDrawPath(const DrawPathArgs& args) { 636 GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), 637 "GrDefaultPathRenderer::onDrawPath"); 638 return this->internalDrawPath(args.fRenderTargetContext, 639 std::move(args.fPaint), 640 args.fAAType, 641 *args.fUserStencilSettings, 642 *args.fClip, 643 *args.fViewMatrix, 644 *args.fShape, 645 false); 646 } 647 648 void GrDefaultPathRenderer::onStencilPath(const StencilPathArgs& args) { 649 GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), 650 "GrDefaultPathRenderer::onStencilPath"); 651 SkASSERT(!args.fShape->inverseFilled()); 652 653 GrPaint paint; 654 paint.setXPFactory(GrDisableColorXPFactory::Get()); 655 656 this->internalDrawPath(args.fRenderTargetContext, std::move(paint), args.fAAType, 657 GrUserStencilSettings::kUnused, *args.fClip, *args.fViewMatrix, 658 *args.fShape, true); 659 } 660 661 /////////////////////////////////////////////////////////////////////////////////////////////////// 662 663 #if GR_TEST_UTILS 664 665 GR_DRAW_OP_TEST_DEFINE(DefaultPathOp) { 666 SkMatrix viewMatrix = GrTest::TestMatrix(random); 667 668 // For now just hairlines because the other types of draws require two ops. 669 // TODO we should figure out a way to combine the stencil and cover steps into one op. 670 GrStyle style(SkStrokeRec::kHairline_InitStyle); 671 SkPath path = GrTest::TestPath(random); 672 673 // Compute srcSpaceTol 674 SkRect bounds = path.getBounds(); 675 SkScalar tol = GrPathUtils::kDefaultTolerance; 676 SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, bounds); 677 678 viewMatrix.mapRect(&bounds); 679 uint8_t coverage = GrRandomCoverage(random); 680 GrAAType aaType = GrAAType::kNone; 681 if (GrFSAAType::kUnifiedMSAA == fsaaType && random->nextBool()) { 682 aaType = GrAAType::kMSAA; 683 } 684 return DefaultPathOp::Make(std::move(paint), path, srcSpaceTol, coverage, viewMatrix, true, 685 aaType, bounds, GrGetRandomStencil(random, context)); 686 } 687 688 #endif 689