Home | History | Annotate | Download | only in ops
      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 
     10 #include "GrContext.h"
     11 #include "GrDefaultGeoProcFactory.h"
     12 #include "GrDrawOpTest.h"
     13 #include "GrFixedClip.h"
     14 #include "GrMesh.h"
     15 #include "GrOpFlushState.h"
     16 #include "GrPathUtils.h"
     17 #include "GrPipelineBuilder.h"
     18 #include "SkGeometry.h"
     19 #include "SkString.h"
     20 #include "SkStrokeRec.h"
     21 #include "SkTLazy.h"
     22 #include "SkTraceEvent.h"
     23 
     24 #include "ops/GrMeshDrawOp.h"
     25 #include "ops/GrRectOpFactory.h"
     26 
     27 GrDefaultPathRenderer::GrDefaultPathRenderer(bool separateStencilSupport,
     28                                              bool stencilWrapOpsSupport)
     29     : fSeparateStencil(separateStencilSupport)
     30     , fStencilWrapOps(stencilWrapOpsSupport) {
     31 }
     32 
     33 ////////////////////////////////////////////////////////////////////////////////
     34 // Helpers for drawPath
     35 
     36 #define STENCIL_OFF     0   // Always disable stencil (even when needed)
     37 
     38 static inline bool single_pass_shape(const GrShape& shape) {
     39 #if STENCIL_OFF
     40     return true;
     41 #else
     42     // Inverse fill is always two pass.
     43     if (shape.inverseFilled()) {
     44         return false;
     45     }
     46     // This path renderer only accepts simple fill paths or stroke paths that are either hairline
     47     // or have a stroke width small enough to treat as hairline. Hairline paths are always single
     48     // pass. Filled paths are single pass if they're convex.
     49     if (shape.style().isSimpleFill()) {
     50         return shape.knownToBeConvex();
     51     }
     52     return true;
     53 #endif
     54 }
     55 
     56 GrPathRenderer::StencilSupport
     57 GrDefaultPathRenderer::onGetStencilSupport(const GrShape& shape) const {
     58     if (single_pass_shape(shape)) {
     59         return GrPathRenderer::kNoRestriction_StencilSupport;
     60     } else {
     61         return GrPathRenderer::kStencilOnly_StencilSupport;
     62     }
     63 }
     64 
     65 static inline void append_countour_edge_indices(bool hairLine,
     66                                                 uint16_t fanCenterIdx,
     67                                                 uint16_t edgeV0Idx,
     68                                                 uint16_t** indices) {
     69     // when drawing lines we're appending line segments along
     70     // the contour. When applying the other fill rules we're
     71     // drawing triangle fans around fanCenterIdx.
     72     if (!hairLine) {
     73         *((*indices)++) = fanCenterIdx;
     74     }
     75     *((*indices)++) = edgeV0Idx;
     76     *((*indices)++) = edgeV0Idx + 1;
     77 }
     78 
     79 static inline void add_quad(SkPoint** vert, const SkPoint* base, const SkPoint pts[],
     80                             SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol, bool indexed,
     81                             bool isHairline, uint16_t subpathIdxStart, int offset, uint16_t** idx) {
     82     // first pt of quad is the pt we ended on in previous step
     83     uint16_t firstQPtIdx = (uint16_t)(*vert - base) - 1 + offset;
     84     uint16_t numPts =  (uint16_t)
     85         GrPathUtils::generateQuadraticPoints(
     86             pts[0], pts[1], pts[2],
     87             srcSpaceTolSqd, vert,
     88             GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
     89     if (indexed) {
     90         for (uint16_t i = 0; i < numPts; ++i) {
     91             append_countour_edge_indices(isHairline, subpathIdxStart,
     92                                          firstQPtIdx + i, idx);
     93         }
     94     }
     95 }
     96 
     97 class DefaultPathOp final : public GrMeshDrawOp {
     98 public:
     99     DEFINE_OP_CLASS_ID
    100 
    101     static std::unique_ptr<GrMeshDrawOp> Make(GrColor color, const SkPath& path, SkScalar tolerance,
    102                                               uint8_t coverage, const SkMatrix& viewMatrix,
    103                                               bool isHairline, const SkRect& devBounds) {
    104         return std::unique_ptr<GrMeshDrawOp>(new DefaultPathOp(color, path, tolerance, coverage,
    105                                                                viewMatrix, isHairline, devBounds));
    106     }
    107 
    108     const char* name() const override { return "DefaultPathOp"; }
    109 
    110     SkString dumpInfo() const override {
    111         SkString string;
    112         string.appendf("Color: 0x%08x Count: %d\n", fColor, fPaths.count());
    113         for (const auto& path : fPaths) {
    114             string.appendf("Tolerance: %.2f\n", path.fTolerance);
    115         }
    116         string.append(DumpPipelineInfo(*this->pipeline()));
    117         string.append(INHERITED::dumpInfo());
    118         return string;
    119     }
    120 
    121 private:
    122     DefaultPathOp(GrColor color, const SkPath& path, SkScalar tolerance, uint8_t coverage,
    123                   const SkMatrix& viewMatrix, bool isHairline, const SkRect& devBounds)
    124             : INHERITED(ClassID())
    125             , fColor(color)
    126             , fCoverage(coverage)
    127             , fViewMatrix(viewMatrix)
    128             , fIsHairline(isHairline) {
    129         fPaths.emplace_back(PathData{path, tolerance});
    130 
    131         this->setBounds(devBounds, HasAABloat::kNo,
    132                         isHairline ? IsZeroArea::kYes : IsZeroArea::kNo);
    133     }
    134 
    135     void getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor* color,
    136                                             GrPipelineAnalysisCoverage* coverage) const override {
    137         color->setToConstant(fColor);
    138         *coverage = this->coverage() == 0xff ? GrPipelineAnalysisCoverage::kNone
    139                                              : GrPipelineAnalysisCoverage::kSingleChannel;
    140     }
    141 
    142     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
    143         optimizations.getOverrideColorIfSet(&fColor);
    144         fUsesLocalCoords = optimizations.readsLocalCoords();
    145     }
    146 
    147     void onPrepareDraws(Target* target) const override {
    148         sk_sp<GrGeometryProcessor> gp;
    149         {
    150             using namespace GrDefaultGeoProcFactory;
    151             Color color(this->color());
    152             Coverage coverage(this->coverage());
    153             LocalCoords localCoords(this->usesLocalCoords() ? LocalCoords::kUsePosition_Type :
    154                                                               LocalCoords::kUnused_Type);
    155             gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, this->viewMatrix());
    156         }
    157 
    158         size_t vertexStride = gp->getVertexStride();
    159         SkASSERT(vertexStride == sizeof(SkPoint));
    160 
    161         int instanceCount = fPaths.count();
    162 
    163         // compute number of vertices
    164         int maxVertices = 0;
    165 
    166         // We will use index buffers if we have multiple paths or one path with multiple contours
    167         bool isIndexed = instanceCount > 1;
    168         for (int i = 0; i < instanceCount; i++) {
    169             const PathData& args = fPaths[i];
    170 
    171             int contourCount;
    172             maxVertices += GrPathUtils::worstCasePointCount(args.fPath, &contourCount,
    173                                                             args.fTolerance);
    174 
    175             isIndexed = isIndexed || contourCount > 1;
    176         }
    177 
    178         if (maxVertices == 0 || maxVertices > ((int)SK_MaxU16 + 1)) {
    179             //SkDebugf("Cannot render path (%d)\n", maxVertices);
    180             return;
    181         }
    182 
    183         // determine primitiveType
    184         int maxIndices = 0;
    185         GrPrimitiveType primitiveType;
    186         if (this->isHairline()) {
    187             if (isIndexed) {
    188                 maxIndices = 2 * maxVertices;
    189                 primitiveType = kLines_GrPrimitiveType;
    190             } else {
    191                 primitiveType = kLineStrip_GrPrimitiveType;
    192             }
    193         } else {
    194             if (isIndexed) {
    195                 maxIndices = 3 * maxVertices;
    196                 primitiveType = kTriangles_GrPrimitiveType;
    197             } else {
    198                 primitiveType = kTriangleFan_GrPrimitiveType;
    199             }
    200         }
    201 
    202         // allocate vertex / index buffers
    203         const GrBuffer* vertexBuffer;
    204         int firstVertex;
    205 
    206         void* verts = target->makeVertexSpace(vertexStride, maxVertices,
    207                                               &vertexBuffer, &firstVertex);
    208 
    209         if (!verts) {
    210             SkDebugf("Could not allocate vertices\n");
    211             return;
    212         }
    213 
    214         const GrBuffer* indexBuffer = nullptr;
    215         int firstIndex = 0;
    216 
    217         void* indices = nullptr;
    218         if (isIndexed) {
    219             indices = target->makeIndexSpace(maxIndices, &indexBuffer, &firstIndex);
    220 
    221             if (!indices) {
    222                 SkDebugf("Could not allocate indices\n");
    223                 return;
    224             }
    225         }
    226 
    227         // fill buffers
    228         int vertexOffset = 0;
    229         int indexOffset = 0;
    230         for (int i = 0; i < instanceCount; i++) {
    231             const PathData& args = fPaths[i];
    232 
    233             int vertexCnt = 0;
    234             int indexCnt = 0;
    235             if (!this->createGeom(verts,
    236                                   vertexOffset,
    237                                   indices,
    238                                   indexOffset,
    239                                   &vertexCnt,
    240                                   &indexCnt,
    241                                   args.fPath,
    242                                   args.fTolerance,
    243                                   isIndexed)) {
    244                 return;
    245             }
    246 
    247             vertexOffset += vertexCnt;
    248             indexOffset += indexCnt;
    249             SkASSERT(vertexOffset <= maxVertices && indexOffset <= maxIndices);
    250         }
    251 
    252         GrMesh mesh;
    253         if (isIndexed) {
    254             mesh.initIndexed(primitiveType, vertexBuffer, indexBuffer, firstVertex, firstIndex,
    255                              vertexOffset, indexOffset);
    256         } else {
    257             mesh.init(primitiveType, vertexBuffer, firstVertex, vertexOffset);
    258         }
    259         target->draw(gp.get(), mesh);
    260 
    261         // put back reserves
    262         target->putBackIndices((size_t)(maxIndices - indexOffset));
    263         target->putBackVertices((size_t)(maxVertices - vertexOffset), (size_t)vertexStride);
    264     }
    265 
    266     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
    267         DefaultPathOp* that = t->cast<DefaultPathOp>();
    268         if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
    269                                     that->bounds(), caps)) {
    270             return false;
    271         }
    272 
    273         if (this->color() != that->color()) {
    274             return false;
    275         }
    276 
    277         if (this->coverage() != that->coverage()) {
    278             return false;
    279         }
    280 
    281         if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
    282             return false;
    283         }
    284 
    285         if (this->isHairline() != that->isHairline()) {
    286             return false;
    287         }
    288 
    289         fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin());
    290         this->joinBounds(*that);
    291         return true;
    292     }
    293 
    294     bool createGeom(void* vertices,
    295                     size_t vertexOffset,
    296                     void* indices,
    297                     size_t indexOffset,
    298                     int* vertexCnt,
    299                     int* indexCnt,
    300                     const SkPath& path,
    301                     SkScalar srcSpaceTol,
    302                     bool isIndexed) const {
    303             SkScalar srcSpaceTolSqd = srcSpaceTol * srcSpaceTol;
    304 
    305             uint16_t indexOffsetU16 = (uint16_t)indexOffset;
    306             uint16_t vertexOffsetU16 = (uint16_t)vertexOffset;
    307 
    308             uint16_t* idxBase = reinterpret_cast<uint16_t*>(indices) + indexOffsetU16;
    309             uint16_t* idx = idxBase;
    310             uint16_t subpathIdxStart = vertexOffsetU16;
    311 
    312             SkPoint* base = reinterpret_cast<SkPoint*>(vertices) + vertexOffset;
    313             SkPoint* vert = base;
    314 
    315             SkPoint pts[4];
    316 
    317             bool first = true;
    318             int subpath = 0;
    319 
    320             SkPath::Iter iter(path, false);
    321 
    322             bool done = false;
    323             while (!done) {
    324                 SkPath::Verb verb = iter.next(pts);
    325                 switch (verb) {
    326                     case SkPath::kMove_Verb:
    327                         if (!first) {
    328                             uint16_t currIdx = (uint16_t) (vert - base) + vertexOffsetU16;
    329                             subpathIdxStart = currIdx;
    330                             ++subpath;
    331                         }
    332                         *vert = pts[0];
    333                         vert++;
    334                         break;
    335                     case SkPath::kLine_Verb:
    336                         if (isIndexed) {
    337                             uint16_t prevIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16;
    338                             append_countour_edge_indices(this->isHairline(), subpathIdxStart,
    339                                                          prevIdx, &idx);
    340                         }
    341                         *(vert++) = pts[1];
    342                         break;
    343                     case SkPath::kConic_Verb: {
    344                         SkScalar weight = iter.conicWeight();
    345                         SkAutoConicToQuads converter;
    346                         // Converting in src-space, hance the finer tolerance (0.25)
    347                         // TODO: find a way to do this in dev-space so the tolerance means something
    348                         const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.25f);
    349                         for (int i = 0; i < converter.countQuads(); ++i) {
    350                             add_quad(&vert, base, quadPts + i*2, srcSpaceTolSqd, srcSpaceTol,
    351                                      isIndexed, this->isHairline(), subpathIdxStart,
    352                                      (int)vertexOffset, &idx);
    353                         }
    354                         break;
    355                     }
    356                     case SkPath::kQuad_Verb:
    357                         add_quad(&vert, base, pts, srcSpaceTolSqd, srcSpaceTol, isIndexed,
    358                                  this->isHairline(), subpathIdxStart, (int)vertexOffset, &idx);
    359                         break;
    360                     case SkPath::kCubic_Verb: {
    361                         // first pt of cubic is the pt we ended on in previous step
    362                         uint16_t firstCPtIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16;
    363                         uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints(
    364                                         pts[0], pts[1], pts[2], pts[3],
    365                                         srcSpaceTolSqd, &vert,
    366                                         GrPathUtils::cubicPointCount(pts, srcSpaceTol));
    367                         if (isIndexed) {
    368                             for (uint16_t i = 0; i < numPts; ++i) {
    369                                 append_countour_edge_indices(this->isHairline(), subpathIdxStart,
    370                                                              firstCPtIdx + i, &idx);
    371                             }
    372                         }
    373                         break;
    374                     }
    375                     case SkPath::kClose_Verb:
    376                         break;
    377                     case SkPath::kDone_Verb:
    378                         done = true;
    379                 }
    380                 first = false;
    381             }
    382 
    383             *vertexCnt = static_cast<int>(vert - base);
    384             *indexCnt = static_cast<int>(idx - idxBase);
    385         return true;
    386     }
    387 
    388     GrColor color() const { return fColor; }
    389     uint8_t coverage() const { return fCoverage; }
    390     bool usesLocalCoords() const { return fUsesLocalCoords; }
    391     const SkMatrix& viewMatrix() const { return fViewMatrix; }
    392     bool isHairline() const { return fIsHairline; }
    393 
    394     struct PathData {
    395         SkPath fPath;
    396         SkScalar fTolerance;
    397     };
    398 
    399     GrColor fColor;
    400     uint8_t fCoverage;
    401     SkMatrix fViewMatrix;
    402     bool fUsesLocalCoords;
    403     bool fIsHairline;
    404     SkSTArray<1, PathData, true> fPaths;
    405 
    406     typedef GrMeshDrawOp INHERITED;
    407 };
    408 
    409 bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext,
    410                                              GrPaint&& paint,
    411                                              GrAAType aaType,
    412                                              const GrUserStencilSettings& userStencilSettings,
    413                                              const GrClip& clip,
    414                                              const SkMatrix& viewMatrix,
    415                                              const GrShape& shape,
    416                                              bool stencilOnly) {
    417     SkASSERT(GrAAType::kCoverage != aaType);
    418     SkPath path;
    419     shape.asPath(&path);
    420 
    421     SkScalar hairlineCoverage;
    422     uint8_t newCoverage = 0xff;
    423     bool isHairline = false;
    424     if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) {
    425         newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);
    426         isHairline = true;
    427     } else {
    428         SkASSERT(shape.style().isSimpleFill());
    429     }
    430 
    431     int                          passCount = 0;
    432     const GrUserStencilSettings* passes[3];
    433     GrDrawFace                   drawFace[3];
    434     bool                         reverse = false;
    435     bool                         lastPassIsBounds;
    436 
    437     if (isHairline) {
    438         passCount = 1;
    439         if (stencilOnly) {
    440             passes[0] = &gDirectToStencil;
    441         } else {
    442             passes[0] = &userStencilSettings;
    443         }
    444         lastPassIsBounds = false;
    445         drawFace[0] = GrDrawFace::kBoth;
    446     } else {
    447         if (single_pass_shape(shape)) {
    448             passCount = 1;
    449             if (stencilOnly) {
    450                 passes[0] = &gDirectToStencil;
    451             } else {
    452                 passes[0] = &userStencilSettings;
    453             }
    454             drawFace[0] = GrDrawFace::kBoth;
    455             lastPassIsBounds = false;
    456         } else {
    457             switch (path.getFillType()) {
    458                 case SkPath::kInverseEvenOdd_FillType:
    459                     reverse = true;
    460                     // fallthrough
    461                 case SkPath::kEvenOdd_FillType:
    462                     passes[0] = &gEOStencilPass;
    463                     if (stencilOnly) {
    464                         passCount = 1;
    465                         lastPassIsBounds = false;
    466                     } else {
    467                         passCount = 2;
    468                         lastPassIsBounds = true;
    469                         if (reverse) {
    470                             passes[1] = &gInvEOColorPass;
    471                         } else {
    472                             passes[1] = &gEOColorPass;
    473                         }
    474                     }
    475                     drawFace[0] = drawFace[1] = GrDrawFace::kBoth;
    476                     break;
    477 
    478                 case SkPath::kInverseWinding_FillType:
    479                     reverse = true;
    480                     // fallthrough
    481                 case SkPath::kWinding_FillType:
    482                     if (fSeparateStencil) {
    483                         if (fStencilWrapOps) {
    484                             passes[0] = &gWindStencilSeparateWithWrap;
    485                         } else {
    486                             passes[0] = &gWindStencilSeparateNoWrap;
    487                         }
    488                         passCount = 2;
    489                         drawFace[0] = GrDrawFace::kBoth;
    490                     } else {
    491                         if (fStencilWrapOps) {
    492                             passes[0] = &gWindSingleStencilWithWrapInc;
    493                             passes[1] = &gWindSingleStencilWithWrapDec;
    494                         } else {
    495                             passes[0] = &gWindSingleStencilNoWrapInc;
    496                             passes[1] = &gWindSingleStencilNoWrapDec;
    497                         }
    498                         // which is cw and which is ccw is arbitrary.
    499                         drawFace[0] = GrDrawFace::kCW;
    500                         drawFace[1] = GrDrawFace::kCCW;
    501                         passCount = 3;
    502                     }
    503                     if (stencilOnly) {
    504                         lastPassIsBounds = false;
    505                         --passCount;
    506                     } else {
    507                         lastPassIsBounds = true;
    508                         drawFace[passCount-1] = GrDrawFace::kBoth;
    509                         if (reverse) {
    510                             passes[passCount-1] = &gInvWindColorPass;
    511                         } else {
    512                             passes[passCount-1] = &gWindColorPass;
    513                         }
    514                     }
    515                     break;
    516                 default:
    517                     SkDEBUGFAIL("Unknown path fFill!");
    518                     return false;
    519             }
    520         }
    521     }
    522 
    523     SkScalar tol = GrPathUtils::kDefaultTolerance;
    524     SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds());
    525 
    526     SkRect devBounds;
    527     GetPathDevBounds(path, renderTargetContext->width(), renderTargetContext->height(), viewMatrix,
    528                      &devBounds);
    529 
    530     for (int p = 0; p < passCount; ++p) {
    531         if (lastPassIsBounds && (p == passCount-1)) {
    532             SkRect bounds;
    533             SkMatrix localMatrix = SkMatrix::I();
    534             if (reverse) {
    535                 // draw over the dev bounds (which will be the whole dst surface for inv fill).
    536                 bounds = devBounds;
    537                 SkMatrix vmi;
    538                 // mapRect through persp matrix may not be correct
    539                 if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
    540                     vmi.mapRect(&bounds);
    541                 } else {
    542                     if (!viewMatrix.invert(&localMatrix)) {
    543                         return false;
    544                     }
    545                 }
    546             } else {
    547                 bounds = path.getBounds();
    548             }
    549             const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
    550                                                                                viewMatrix;
    551             std::unique_ptr<GrMeshDrawOp> op(GrRectOpFactory::MakeNonAAFill(
    552                     paint.getColor(), viewM, bounds, nullptr, &localMatrix));
    553 
    554             SkASSERT(GrDrawFace::kBoth == drawFace[p]);
    555             GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
    556             pipelineBuilder.setDrawFace(drawFace[p]);
    557             pipelineBuilder.setUserStencil(passes[p]);
    558             renderTargetContext->addMeshDrawOp(pipelineBuilder, clip, std::move(op));
    559         } else {
    560             std::unique_ptr<GrMeshDrawOp> op =
    561                     DefaultPathOp::Make(paint.getColor(), path, srcSpaceTol, newCoverage,
    562                                         viewMatrix, isHairline, devBounds);
    563             bool stencilPass = stencilOnly || passCount > 1;
    564             GrPaint::MoveOrNew passPaint(paint, stencilPass);
    565             if (stencilPass) {
    566                 passPaint.paint().setXPFactory(GrDisableColorXPFactory::Get());
    567             }
    568             GrPipelineBuilder pipelineBuilder(std::move(passPaint), aaType);
    569             pipelineBuilder.setDrawFace(drawFace[p]);
    570             pipelineBuilder.setUserStencil(passes[p]);
    571             renderTargetContext->addMeshDrawOp(pipelineBuilder, clip, std::move(op));
    572         }
    573     }
    574     return true;
    575 }
    576 
    577 bool GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
    578     // This can draw any path with any simple fill style but doesn't do coverage-based antialiasing.
    579     return GrAAType::kCoverage != args.fAAType &&
    580            (args.fShape->style().isSimpleFill() ||
    581             IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr));
    582 }
    583 
    584 bool GrDefaultPathRenderer::onDrawPath(const DrawPathArgs& args) {
    585     GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
    586                               "GrDefaultPathRenderer::onDrawPath");
    587     return this->internalDrawPath(args.fRenderTargetContext,
    588                                   std::move(args.fPaint),
    589                                   args.fAAType,
    590                                   *args.fUserStencilSettings,
    591                                   *args.fClip,
    592                                   *args.fViewMatrix,
    593                                   *args.fShape,
    594                                   false);
    595 }
    596 
    597 void GrDefaultPathRenderer::onStencilPath(const StencilPathArgs& args) {
    598     GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
    599                               "GrDefaultPathRenderer::onStencilPath");
    600     SkASSERT(!args.fShape->inverseFilled());
    601 
    602     GrPaint paint;
    603     paint.setXPFactory(GrDisableColorXPFactory::Get());
    604 
    605     this->internalDrawPath(args.fRenderTargetContext, std::move(paint), args.fAAType,
    606                            GrUserStencilSettings::kUnused, *args.fClip, *args.fViewMatrix,
    607                            *args.fShape, true);
    608 }
    609 
    610 ///////////////////////////////////////////////////////////////////////////////////////////////////
    611 
    612 #if GR_TEST_UTILS
    613 
    614 DRAW_OP_TEST_DEFINE(DefaultPathOp) {
    615     GrColor color = GrRandomColor(random);
    616     SkMatrix viewMatrix = GrTest::TestMatrix(random);
    617 
    618     // For now just hairlines because the other types of draws require two ops.
    619     // TODO we should figure out a way to combine the stencil and cover steps into one op.
    620     GrStyle style(SkStrokeRec::kHairline_InitStyle);
    621     SkPath path = GrTest::TestPath(random);
    622 
    623     // Compute srcSpaceTol
    624     SkRect bounds = path.getBounds();
    625     SkScalar tol = GrPathUtils::kDefaultTolerance;
    626     SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, bounds);
    627 
    628     viewMatrix.mapRect(&bounds);
    629     uint8_t coverage = GrRandomCoverage(random);
    630     return DefaultPathOp::Make(color, path, srcSpaceTol, coverage, viewMatrix, true, bounds);
    631 }
    632 
    633 #endif
    634