text: /*
   * Copyright 2011 Google Inc.
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include "GrDefaultPathRenderer.h"
  #include "GrContext.h"
  #include "GrDefaultGeoProcFactory.h"
  #include "GrDrawOpTest.h"
  #include "GrFixedClip.h"
  #include "GrMesh.h"
  #include "GrOpFlushState.h"
  #include "GrPathUtils.h"
  #include "GrSimpleMeshDrawOpHelper.h"
  #include "SkGeometry.h"
  #include "SkString.h"
  #include "SkStrokeRec.h"
  #include "SkTLazy.h"
  #include "SkTraceEvent.h"
  #include "ops/GrMeshDrawOp.h"
  #include "ops/GrRectOpFactory.h"
  
  GrDefaultPathRenderer::GrDefaultPathRenderer() {
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Helpers for drawPath
  
  #define STENCIL_OFF     0   // Always disable stencil (even when needed)
  
  static inline bool single_pass_shape(const GrShape& shape) {
  #if STENCIL_OFF
      return true;
  #else
      // Inverse fill is always two pass.
      if (shape.inverseFilled()) {
          return false;
      }
      // This path renderer only accepts simple fill paths or stroke paths that are either hairline
      // or have a stroke width small enough to treat as hairline. Hairline paths are always single
      // pass. Filled paths are single pass if they're convex.
      if (shape.style().isSimpleFill()) {
          return shape.knownToBeConvex();
      }
      return true;
  #endif
  }
  
  GrPathRenderer::StencilSupport
  GrDefaultPathRenderer::onGetStencilSupport(const GrShape& shape) const {
      if (single_pass_shape(shape)) {
          return GrPathRenderer::kNoRestriction_StencilSupport;
      } else {
          return GrPathRenderer::kStencilOnly_StencilSupport;
      }
  }
  
  namespace {
  
  class PathGeoBuilder {
  public:
      PathGeoBuilder(GrPrimitiveType primitiveType, GrMeshDrawOp::Target* target,
                     GrGeometryProcessor* geometryProcessor, const GrPipeline* pipeline)
              : fMesh(primitiveType)
              , fTarget(target)
              , fVertexStride(sizeof(SkPoint))
              , fGeometryProcessor(geometryProcessor)
              , fPipeline(pipeline)
              , fIndexBuffer(nullptr)
              , fFirstIndex(0)
              , fIndicesInChunk(0)
              , fIndices(nullptr) {
          this->allocNewBuffers();
      }
  
      ~PathGeoBuilder() {
          this->emitMeshAndPutBackReserve();
      }
  
      /**
       *  Path verbs
       */
      void moveTo(const SkPoint& p) {
          needSpace(1);
  
          fSubpathIndexStart = this->currentIndex();
          *(fCurVert++) = p;
      }
  
      void addLine(const SkPoint& p) {
          needSpace(1, this->indexScale());
  
          if (this->isIndexed()) {
              uint16_t prevIdx = this->currentIndex() - 1;
              appendCountourEdgeIndices(prevIdx);
          }
          *(fCurVert++) = p;
      }
  
      void addQuad(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) {
          this->needSpace(GrPathUtils::kMaxPointsPerCurve,
                          GrPathUtils::kMaxPointsPerCurve * this->indexScale());
  
          // First pt of quad is the pt we ended on in previous step
          uint16_t firstQPtIdx = this->currentIndex() - 1;
          uint16_t numPts = (uint16_t)GrPathUtils::generateQuadraticPoints(
                  pts[0], pts[1], pts[2], srcSpaceTolSqd, &fCurVert,
                  GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
          if (this->isIndexed()) {
              for (uint16_t i = 0; i < numPts; ++i) {
                  appendCountourEdgeIndices(firstQPtIdx + i);
              }
          }
      }
  
      void addConic(SkScalar weight, const SkPoint pts[], SkScalar srcSpaceTolSqd,
                    SkScalar srcSpaceTol) {
          SkAutoConicToQuads converter;
          const SkPoint* quadPts = converter.computeQuads(pts, weight, srcSpaceTol);
          for (int i = 0; i < converter.countQuads(); ++i) {
              this->addQuad(quadPts + i * 2, srcSpaceTolSqd, srcSpaceTol);
          }
      }
  
      void addCubic(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) {
          this->needSpace(GrPathUtils::kMaxPointsPerCurve,
                          GrPathUtils::kMaxPointsPerCurve * this->indexScale());
  
          // First pt of cubic is the pt we ended on in previous step
          uint16_t firstCPtIdx = this->currentIndex() - 1;
          uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints(
                  pts[0], pts[1], pts[2], pts[3], srcSpaceTolSqd, &fCurVert,
                  GrPathUtils::cubicPointCount(pts, srcSpaceTol));
          if (this->isIndexed()) {
              for (uint16_t i = 0; i < numPts; ++i) {
                  appendCountourEdgeIndices(firstCPtIdx + i);
              }
          }
      }
  
      void addPath(const SkPath& path, SkScalar srcSpaceTol) {
          SkScalar srcSpaceTolSqd = srcSpaceTol * srcSpaceTol;
  
          SkPath::Iter iter(path, false);
          SkPoint pts[4];
  
          bool done = false;
          while (!done) {
              SkPath::Verb verb = iter.next(pts, false);
              switch (verb) {
                  case SkPath::kMove_Verb:
                      this->moveTo(pts[0]);
                      break;
                  case SkPath::kLine_Verb:
                      this->addLine(pts[1]);
                      break;
                  case SkPath::kConic_Verb:
                      this->addConic(iter.conicWeight(), pts, srcSpaceTolSqd, srcSpaceTol);
                      break;
                  case SkPath::kQuad_Verb:
                      this->addQuad(pts, srcSpaceTolSqd, srcSpaceTol);
                      break;
                  case SkPath::kCubic_Verb:
                      this->addCubic(pts, srcSpaceTolSqd, srcSpaceTol);
                      break;
                  case SkPath::kClose_Verb:
                      break;
                  case SkPath::kDone_Verb:
                      done = true;
              }
          }
      }
  
      static bool PathHasMultipleSubpaths(const SkPath& path) {
          bool first = true;
  
          SkPath::Iter iter(path, false);
          SkPath::Verb verb;
  
          SkPoint pts[4];
          while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
              if (SkPath::kMove_Verb == verb && !first) {
                  return true;
              }
              first = false;
          }
          return false;
      }
  
  private:
      /**
       *  Derived properties
       *  TODO: Cache some of these for better performance, rather than re-computing?
       */
      bool isIndexed() const {
          return GrPrimitiveType::kLines == fMesh.primitiveType() ||
                 GrPrimitiveType::kTriangles == fMesh.primitiveType();
      }
      bool isHairline() const {
          return GrPrimitiveType::kLines == fMesh.primitiveType() ||
                 GrPrimitiveType::kLineStrip == fMesh.primitiveType();
      }
      int indexScale() const {
          switch (fMesh.primitiveType()) {
              case GrPrimitiveType::kLines:
                  return 2;
              case GrPrimitiveType::kTriangles:
                  return 3;
              default:
                  return 0;
          }
      }
  
      uint16_t currentIndex() const { return fCurVert - fVertices; }
  
      // Allocate vertex and (possibly) index buffers
      void allocNewBuffers() {
          // Ensure that we always get enough verts for a worst-case quad/cubic, plus leftover points
          // from previous mesh piece (up to two verts to continue fanning). If we can't get that
          // many, ask for a much larger number. This needs to be fairly big to handle  quads/cubics,
          // which have a worst-case of 1k points.
          static const int kMinVerticesPerChunk = GrPathUtils::kMaxPointsPerCurve + 2;
          static const int kFallbackVerticesPerChunk = 16384;
  
          fVertices = static_cast<SkPoint*>(fTarget->makeVertexSpaceAtLeast(fVertexStride,
                                                                            kMinVerticesPerChunk,
                                                                            kFallbackVerticesPerChunk,
                                                                            &fVertexBuffer,
                                                                            &fFirstVertex,
                                                                            &fVerticesInChunk));
  
          if (this->isIndexed()) {
              // Similar to above: Ensure we get enough indices for one worst-case quad/cubic.
              // No extra indices are needed for stitching, though. If we can't get that many, ask
              // for enough to match our large vertex request.
              const int kMinIndicesPerChunk = GrPathUtils::kMaxPointsPerCurve * this->indexScale();
              const int kFallbackIndicesPerChunk = kFallbackVerticesPerChunk * this->indexScale();
  
              fIndices = fTarget->makeIndexSpaceAtLeast(kMinIndicesPerChunk, kFallbackIndicesPerChunk,
                                                        &fIndexBuffer, &fFirstIndex,
                                                        &fIndicesInChunk);
          }
  
          fCurVert = fVertices;
          fCurIdx = fIndices;
          fSubpathIndexStart = 0;
      }
  
      void appendCountourEdgeIndices(uint16_t edgeV0Idx) {
          // When drawing lines we're appending line segments along the countour. When applying the
          // other fill rules we're drawing triangle fans around the start of the current (sub)path.
          if (!this->isHairline()) {
              *(fCurIdx++) = fSubpathIndexStart;
          }
          *(fCurIdx++) = edgeV0Idx;
          *(fCurIdx++) = edgeV0Idx + 1;
      }
  
      // Emits a single draw with all accumulated vertex/index data
      void emitMeshAndPutBackReserve() {
          int vertexCount = fCurVert - fVertices;
          int indexCount = fCurIdx - fIndices;
          SkASSERT(vertexCount <= fVerticesInChunk);
          SkASSERT(indexCount <= fIndicesInChunk);
  
          if (this->isIndexed() ? SkToBool(indexCount) : SkToBool(vertexCount)) {
              if (!this->isIndexed()) {
                  fMesh.setNonIndexedNonInstanced(vertexCount);
              } else {
                  fMesh.setIndexed(fIndexBuffer, indexCount, fFirstIndex, 0, vertexCount - 1);
              }
              fMesh.setVertexData(fVertexBuffer, fFirstVertex);
              fTarget->draw(fGeometryProcessor, fPipeline, fMesh);
          }
  
          fTarget->putBackIndices((size_t)(fIndicesInChunk - indexCount));
          fTarget->putBackVertices((size_t)(fVerticesInChunk - vertexCount), fVertexStride);
      }
  
      void needSpace(int vertsNeeded, int indicesNeeded = 0) {
          if (fCurVert + vertsNeeded > fVertices + fVerticesInChunk ||
              fCurIdx + indicesNeeded > fIndices + fIndicesInChunk) {
              // We are about to run out of space (possibly)
  
              // To maintain continuity, we need to remember one or two points from the current mesh.
              // Lines only need the last point, fills need the first point from the current contour.
              // We always grab both here, and append the ones we need at the end of this process.
              SkPoint lastPt = *(fCurVert - 1);
              SkASSERT(fSubpathIndexStart < fVerticesInChunk);
              SkPoint subpathStartPt = fVertices[fSubpathIndexStart];
  
              // Draw the mesh we've accumulated, and put back any unused space
              this->emitMeshAndPutBackReserve();
  
              // Get new buffers
              this->allocNewBuffers();
  
              // Append copies of the points we saved so the two meshes will weld properly
              if (!this->isHairline()) {
                  *(fCurVert++) = subpathStartPt;
              }
              *(fCurVert++) = lastPt;
          }
      }
  
      GrMesh fMesh;
      GrMeshDrawOp::Target* fTarget;
      size_t fVertexStride;
      GrGeometryProcessor* fGeometryProcessor;
      const GrPipeline* fPipeline;
  
      const GrBuffer* fVertexBuffer;
      int fFirstVertex;
      int fVerticesInChunk;
      SkPoint* fVertices;
      SkPoint* fCurVert;
  
      const GrBuffer* fIndexBuffer;
      int fFirstIndex;
      int fIndicesInChunk;
      uint16_t* fIndices;
      uint16_t* fCurIdx;
      uint16_t fSubpathIndexStart;
  };
  
  class DefaultPathOp final : public GrMeshDrawOp {
  private:
      using Helper = GrSimpleMeshDrawOpHelperWithStencil;
  
  public:
      DEFINE_OP_CLASS_ID
  
      static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkPath& path, SkScalar tolerance,
                                            uint8_t coverage, const SkMatrix& viewMatrix,
                                            bool isHairline, GrAAType aaType, const SkRect& devBounds,
                                            const GrUserStencilSettings* stencilSettings) {
          return Helper::FactoryHelper<DefaultPathOp>(std::move(paint), path, tolerance, coverage,
                                                      viewMatrix, isHairline, aaType, devBounds,
                                                      stencilSettings);
      }
  
      const char* name() const override { return "DefaultPathOp"; }
  
      void visitProxies(const VisitProxyFunc& func) const override {
          fHelper.visitProxies(func);
      }
  
      SkString dumpInfo() const override {
          SkString string;
          string.appendf("Color: 0x%08x Count: %d\n", fColor, fPaths.count());
          for (const auto& path : fPaths) {
              string.appendf("Tolerance: %.2f\n", path.fTolerance);
          }
          string += fHelper.dumpInfo();
          string += INHERITED::dumpInfo();
          return string;
      }
  
      DefaultPathOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkPath& path,
                    SkScalar tolerance, uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline,
                    GrAAType aaType, const SkRect& devBounds,
                    const GrUserStencilSettings* stencilSettings)
              : INHERITED(ClassID())
              , fHelper(helperArgs, aaType, stencilSettings)
              , fColor(color)
              , fCoverage(coverage)
              , fViewMatrix(viewMatrix)
              , fIsHairline(isHairline) {
          fPaths.emplace_back(PathData{path, tolerance});
  
          this->setBounds(devBounds, HasAABloat::kNo,
                          isHairline ? IsZeroArea::kYes : IsZeroArea::kNo);
      }
  
      FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
  
      RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
                                  GrPixelConfigIsClamped dstIsClamped) override {
          GrProcessorAnalysisCoverage gpCoverage =
                  this->coverage() == 0xFF ? GrProcessorAnalysisCoverage::kNone
                                           : GrProcessorAnalysisCoverage::kSingleChannel;
          return fHelper.xpRequiresDstTexture(caps, clip, dstIsClamped, gpCoverage, &fColor);
      }
  
  private:
      void onPrepareDraws(Target* target) override {
          sk_sp<GrGeometryProcessor> gp;
          {
              using namespace GrDefaultGeoProcFactory;
              Color color(this->color());
              Coverage coverage(this->coverage());
              LocalCoords localCoords(fHelper.usesLocalCoords() ? LocalCoords::kUsePosition_Type
                                                                : LocalCoords::kUnused_Type);
              gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, this->viewMatrix());
          }
  
          SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
  
          int instanceCount = fPaths.count();
  
          // We will use index buffers if we have multiple paths or one path with multiple contours
          bool isIndexed = instanceCount > 1;
          for (int i = 0; !isIndexed && i < instanceCount; i++) {
              const PathData& args = fPaths[i];
              isIndexed = isIndexed || PathGeoBuilder::PathHasMultipleSubpaths(args.fPath);
          }
  
          // determine primitiveType
          GrPrimitiveType primitiveType;
          if (this->isHairline()) {
              primitiveType = isIndexed ? GrPrimitiveType::kLines : GrPrimitiveType::kLineStrip;
          } else {
              primitiveType = isIndexed ? GrPrimitiveType::kTriangles : GrPrimitiveType::kTriangleFan;
          }
  
          PathGeoBuilder pathGeoBuilder(primitiveType, target, gp.get(),
                                        fHelper.makePipeline(target));
  
          // fill buffers
          for (int i = 0; i < instanceCount; i++) {
              const PathData& args = fPaths[i];
              pathGeoBuilder.addPath(args.fPath, args.fTolerance);
          }
      }
  
      bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
          DefaultPathOp* that = t->cast<DefaultPathOp>();
          if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
              return false;
          }
  
          if (this->color() != that->color()) {
              return false;
          }
  
          if (this->coverage() != that->coverage()) {
              return false;
          }
  
          if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
              return false;
          }
  
          if (this->isHairline() != that->isHairline()) {
              return false;
          }
  
          fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin());
          this->joinBounds(*that);
          return true;
      }
  
      GrColor color() const { return fColor; }
      uint8_t coverage() const { return fCoverage; }
      const SkMatrix& viewMatrix() const { return fViewMatrix; }
      bool isHairline() const { return fIsHairline; }
  
      struct PathData {
          SkPath fPath;
          SkScalar fTolerance;
      };
  
      SkSTArray<1, PathData, true> fPaths;
      Helper fHelper;
      GrColor fColor;
      uint8_t fCoverage;
      SkMatrix fViewMatrix;
      bool fIsHairline;
  
      typedef GrMeshDrawOp INHERITED;
  };
  
  }  // anonymous namespace
  
  bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext,
                                               GrPaint&& paint,
                                               GrAAType aaType,
                                               const GrUserStencilSettings& userStencilSettings,
                                               const GrClip& clip,
                                               const SkMatrix& viewMatrix,
                                               const GrShape& shape,
                                               bool stencilOnly) {
      SkASSERT(GrAAType::kCoverage != aaType);
      SkPath path;
      shape.asPath(&path);
  
      SkScalar hairlineCoverage;
      uint8_t newCoverage = 0xff;
      bool isHairline = false;
      if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) {
          newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);
          isHairline = true;
      } else {
          SkASSERT(shape.style().isSimpleFill());
      }
  
      int                          passCount = 0;
      const GrUserStencilSettings* passes[2];
      bool                         reverse = false;
      bool                         lastPassIsBounds;
  
      if (isHairline) {
          passCount = 1;
          if (stencilOnly) {
              passes[0] = &gDirectToStencil;
          } else {
              passes[0] = &userStencilSettings;
          }
          lastPassIsBounds = false;
      } else {
          if (single_pass_shape(shape)) {
              passCount = 1;
              if (stencilOnly) {
                  passes[0] = &gDirectToStencil;
              } else {
                  passes[0] = &userStencilSettings;
              }
              lastPassIsBounds = false;
          } else {
              switch (path.getFillType()) {
                  case SkPath::kInverseEvenOdd_FillType:
                      reverse = true;
                      // fallthrough
                  case SkPath::kEvenOdd_FillType:
                      passes[0] = &gEOStencilPass;
                      if (stencilOnly) {
                          passCount = 1;
                          lastPassIsBounds = false;
                      } else {
                          passCount = 2;
                          lastPassIsBounds = true;
                          if (reverse) {
                              passes[1] = &gInvEOColorPass;
                          } else {
                              passes[1] = &gEOColorPass;
                          }
                      }
                      break;
  
                  case SkPath::kInverseWinding_FillType:
                      reverse = true;
                      // fallthrough
                  case SkPath::kWinding_FillType:
                      passes[0] = &gWindStencilPass;
                      passCount = 2;
                      if (stencilOnly) {
                          lastPassIsBounds = false;
                          --passCount;
                      } else {
                          lastPassIsBounds = true;
                          if (reverse) {
                              passes[passCount-1] = &gInvWindColorPass;
                          } else {
                              passes[passCount-1] = &gWindColorPass;
                          }
                      }
                      break;
                  default:
                      SkDEBUGFAIL("Unknown path fFill!");
                      return false;
              }
          }
      }
  
      SkScalar tol = GrPathUtils::kDefaultTolerance;
      SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds());
  
      SkRect devBounds;
      GetPathDevBounds(path,
                       renderTargetContext->asRenderTargetProxy()->worstCaseWidth(),
                       renderTargetContext->asRenderTargetProxy()->worstCaseHeight(),
                       viewMatrix, &devBounds);
  
      for (int p = 0; p < passCount; ++p) {
          if (lastPassIsBounds && (p == passCount-1)) {
              SkRect bounds;
              SkMatrix localMatrix = SkMatrix::I();
              if (reverse) {
                  // draw over the dev bounds (which will be the whole dst surface for inv fill).
                  bounds = devBounds;
                  SkMatrix vmi;
                  // mapRect through persp matrix may not be correct
                  if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
                      vmi.mapRect(&bounds);
                  } else {
                      if (!viewMatrix.invert(&localMatrix)) {
                          return false;
                      }
                  }
              } else {
                  bounds = path.getBounds();
              }
              const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
                                                                                 viewMatrix;
              renderTargetContext->addDrawOp(
                      clip,
                      GrRectOpFactory::MakeNonAAFillWithLocalMatrix(
                              std::move(paint), viewM, localMatrix, bounds, aaType, passes[p]));
          } else {
              bool stencilPass = stencilOnly || passCount > 1;
              std::unique_ptr<GrDrawOp> op;
              if (stencilPass) {
                  GrPaint stencilPaint;
                  stencilPaint.setXPFactory(GrDisableColorXPFactory::Get());
                  op = DefaultPathOp::Make(std::move(stencilPaint), path, srcSpaceTol, newCoverage,
                                           viewMatrix, isHairline, aaType, devBounds, passes[p]);
              } else {
                  op = DefaultPathOp::Make(std::move(paint), path, srcSpaceTol, newCoverage,
                                           viewMatrix, isHairline, aaType, devBounds, passes[p]);
              }
              renderTargetContext->addDrawOp(clip, std::move(op));
          }
      }
      return true;
  }
  
  GrPathRenderer::CanDrawPath
  GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
      bool isHairline = IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr);
      // If we aren't a single_pass_shape or hairline, we require stencil buffers.
      if (!(single_pass_shape(*args.fShape) || isHairline) && args.fCaps->avoidStencilBuffers()) {
          return CanDrawPath::kNo;
      }
      // This can draw any path with any simple fill style but doesn't do coverage-based antialiasing.
      if (GrAAType::kCoverage == args.fAAType ||
          (!args.fShape->style().isSimpleFill() && !isHairline)) {
          return CanDrawPath::kNo;
      }
      // This is the fallback renderer for when a path is too complicated for the others to draw.
      return CanDrawPath::kAsBackup;
  }
  
  bool GrDefaultPathRenderer::onDrawPath(const DrawPathArgs& args) {
      GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
                                "GrDefaultPathRenderer::onDrawPath");
      return this->internalDrawPath(args.fRenderTargetContext,
                                    std::move(args.fPaint),
                                    args.fAAType,
                                    *args.fUserStencilSettings,
                                    *args.fClip,
                                    *args.fViewMatrix,
                                    *args.fShape,
                                    false);
  }
  
  void GrDefaultPathRenderer::onStencilPath(const StencilPathArgs& args) {
      GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
                                "GrDefaultPathRenderer::onStencilPath");
      SkASSERT(!args.fShape->inverseFilled());
  
      GrPaint paint;
      paint.setXPFactory(GrDisableColorXPFactory::Get());
  
      this->internalDrawPath(args.fRenderTargetContext, std::move(paint), args.fAAType,
                             GrUserStencilSettings::kUnused, *args.fClip, *args.fViewMatrix,
                             *args.fShape, true);
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  
  #if GR_TEST_UTILS
  
  GR_DRAW_OP_TEST_DEFINE(DefaultPathOp) {
      SkMatrix viewMatrix = GrTest::TestMatrix(random);
  
      // For now just hairlines because the other types of draws require two ops.
      // TODO we should figure out a way to combine the stencil and cover steps into one op.
      GrStyle style(SkStrokeRec::kHairline_InitStyle);
      SkPath path = GrTest::TestPath(random);
  
      // Compute srcSpaceTol
      SkRect bounds = path.getBounds();
      SkScalar tol = GrPathUtils::kDefaultTolerance;
      SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, bounds);
  
      viewMatrix.mapRect(&bounds);
      uint8_t coverage = GrRandomCoverage(random);
      GrAAType aaType = GrAAType::kNone;
      if (GrFSAAType::kUnifiedMSAA == fsaaType && random->nextBool()) {
          aaType = GrAAType::kMSAA;
      }
      return DefaultPathOp::Make(std::move(paint), path, srcSpaceTol, coverage, viewMatrix, true,
                                 aaType, bounds, GrGetRandomStencil(random, context));
  }
  
  #endif
  