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 "src/gpu/ops/GrDefaultPathRenderer.h"
  
  #include "include/core/SkString.h"
  #include "include/core/SkStrokeRec.h"
  #include "src/core/SkGeometry.h"
  #include "src/core/SkMatrixPriv.h"
  #include "src/core/SkTLazy.h"
  #include "src/core/SkTraceEvent.h"
  #include "src/gpu/GrAuditTrail.h"
  #include "src/gpu/GrCaps.h"
  #include "src/gpu/GrClip.h"
  #include "src/gpu/GrDefaultGeoProcFactory.h"
  #include "src/gpu/GrDrawOpTest.h"
  #include "src/gpu/GrOpFlushState.h"
  #include "src/gpu/GrProgramInfo.h"
  #include "src/gpu/GrSimpleMesh.h"
  #include "src/gpu/GrStyle.h"
  #include "src/gpu/GrUtil.h"
  #include "src/gpu/effects/GrDisableColorXP.h"
  #include "src/gpu/geometry/GrPathUtils.h"
  #include "src/gpu/geometry/GrStyledShape.h"
  #include "src/gpu/ops/GrMeshDrawOp.h"
  #include "src/gpu/ops/GrSimpleMeshDrawOpHelperWithStencil.h"
  
  GrDefaultPathRenderer::GrDefaultPathRenderer() {
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Helpers for drawPath
  
  #define STENCIL_OFF     0   // Always disable stencil (even when needed)
  
  static inline bool single_pass_shape(const GrStyledShape& 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 GrStyledShape& shape) const {
      if (single_pass_shape(shape)) {
          return GrPathRenderer::kNoRestriction_StencilSupport;
      } else {
          return GrPathRenderer::kStencilOnly_StencilSupport;
      }
  }
  
  namespace {
  
  class PathGeoBuilder {
  public:
      PathGeoBuilder(GrPrimitiveType primitiveType,
                     GrMeshDrawTarget* target,
                     SkTDArray<GrSimpleMesh*>* meshes)
              : fPrimitiveType(primitiveType)
              , fTarget(target)
              , fVertexStride(sizeof(SkPoint))
              , fFirstIndex(0)
              , fIndicesInChunk(0)
              , fIndices(nullptr)
              , fMeshes(meshes) {
          this->allocNewBuffers();
      }
  
      ~PathGeoBuilder() {
          this->createMeshAndPutBackReserve();
      }
  
      /**
       *  Path verbs
       */
      void moveTo(const SkPoint& p) {
          if (!this->ensureSpace(1)) {
              return;
          }
  
          if (!this->isHairline()) {
              fSubpathIndexStart = this->currentIndex();
              fSubpathStartPoint = p;
          }
          *(fCurVert++) = p;
      }
  
      void addLine(const SkPoint pts[]) {
          if (!this->ensureSpace(1, this->indexScale(), &pts[0])) {
              return;
          }
  
          if (this->isIndexed()) {
              uint16_t prevIdx = this->currentIndex() - 1;
              this->appendCountourEdgeIndices(prevIdx);
          }
          *(fCurVert++) = pts[1];
      }
  
      void addQuad(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) {
          if (!this->ensureSpace(GrPathUtils::kMaxPointsPerCurve,
                               GrPathUtils::kMaxPointsPerCurve * this->indexScale(),
                               &pts[0])) {
              return;
          }
  
          // 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) {
                  this->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) {
          if (!this->ensureSpace(GrPathUtils::kMaxPointsPerCurve,
                               GrPathUtils::kMaxPointsPerCurve * this->indexScale(),
                               &pts[0])) {
              return;
          }
  
          // 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) {
                  this->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);
              switch (verb) {
                  case SkPath::kMove_Verb:
                      this->moveTo(pts[0]);
                      break;
                  case SkPath::kLine_Verb:
                      this->addLine(pts);
                      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)) != 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 == fPrimitiveType ||
                 GrPrimitiveType::kTriangles == fPrimitiveType;
      }
      bool isHairline() const {
          return GrPrimitiveType::kLines == fPrimitiveType ||
                 GrPrimitiveType::kLineStrip == fPrimitiveType;
      }
      int indexScale() const {
          switch (fPrimitiveType) {
              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() {
          SkASSERT(fValid);
  
          // 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 (!fVertices) {
              SkDebugf("WARNING: Failed to allocate vertex buffer for GrDefaultPathRenderer.\n");
              fCurVert = nullptr;
              fCurIdx = fIndices = nullptr;
              fSubpathIndexStart = 0;
              fValid = false;
              return;
          }
  
          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);
              if (!fIndices) {
                  SkDebugf("WARNING: Failed to allocate index buffer for GrDefaultPathRenderer.\n");
                  fVertices = nullptr;
                  fValid = false;
              }
          }
  
          fCurVert = fVertices;
          fCurIdx = fIndices;
          fSubpathIndexStart = 0;
      }
  
      void appendCountourEdgeIndices(uint16_t edgeV0Idx) {
          SkASSERT(fCurIdx);
  
          // 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 createMeshAndPutBackReserve() {
          if (!fValid) {
              return;
          }
  
          int vertexCount = fCurVert - fVertices;
          int indexCount = fCurIdx - fIndices;
          SkASSERT(vertexCount <= fVerticesInChunk);
          SkASSERT(indexCount <= fIndicesInChunk);
  
          GrSimpleMesh* mesh = nullptr;
          if (this->isIndexed() ? SkToBool(indexCount) : SkToBool(vertexCount)) {
              mesh = fTarget->allocMesh();
              if (!this->isIndexed()) {
                  mesh->set(std::move(fVertexBuffer), vertexCount, fFirstVertex);
              } else {
                  mesh->setIndexed(std::move(fIndexBuffer), indexCount, fFirstIndex, 0,
                                   vertexCount - 1, GrPrimitiveRestart::kNo, std::move(fVertexBuffer),
                                   fFirstVertex);
              }
          }
  
          fTarget->putBackIndices((size_t)(fIndicesInChunk - indexCount));
          fTarget->putBackVertices((size_t)(fVerticesInChunk - vertexCount), fVertexStride);
  
          if (mesh) {
              fMeshes->push_back(mesh);
          }
      }
  
      bool ensureSpace(int vertsNeeded, int indicesNeeded = 0, const SkPoint* lastPoint = nullptr) {
          if (!fValid) {
              return false;
          }
  
          if (fCurVert + vertsNeeded > fVertices + fVerticesInChunk ||
              fCurIdx + indicesNeeded > fIndices + fIndicesInChunk) {
              // We are about to run out of space (possibly)
  
  #ifdef SK_DEBUG
              // 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.
              SkASSERT(fSubpathIndexStart < fVerticesInChunk);
              // This assert is reading from the gpu buffer fVertices and will be slow, but for debug
              // that is okay.
              if (!this->isHairline()) {
                  SkASSERT(fSubpathStartPoint == fVertices[fSubpathIndexStart]);
              }
              if (lastPoint) {
                  SkASSERT(*(fCurVert - 1) == *lastPoint);
              }
  #endif
  
              // Draw the mesh we've accumulated, and put back any unused space
              this->createMeshAndPutBackReserve();
  
              // Get new buffers
              this->allocNewBuffers();
              if (!fValid) {
                  return false;
              }
  
              // On moves we don't need to copy over any points to the new buffer and we pass in a
              // null lastPoint.
              if (lastPoint) {
                  // Append copies of the points we saved so the two meshes will weld properly
                  if (!this->isHairline()) {
                      *(fCurVert++) = fSubpathStartPoint;
                  }
                  *(fCurVert++) = *lastPoint;
              }
          }
  
          return true;
      }
  
      GrPrimitiveType fPrimitiveType;
      GrMeshDrawTarget* fTarget;
      size_t fVertexStride;
  
      sk_sp<const GrBuffer> fVertexBuffer;
      int fFirstVertex;
      int fVerticesInChunk;
      SkPoint* fVertices;
      SkPoint* fCurVert;
  
      sk_sp<const GrBuffer> fIndexBuffer;
      int fFirstIndex;
      int fIndicesInChunk;
      uint16_t* fIndices;
      uint16_t* fCurIdx;
      uint16_t fSubpathIndexStart;
      SkPoint fSubpathStartPoint;
  
      bool fValid = true;
      SkTDArray<GrSimpleMesh*>* fMeshes;
  };
  
  class DefaultPathOp final : public GrMeshDrawOp {
  private:
      using Helper = GrSimpleMeshDrawOpHelperWithStencil;
  
  public:
      DEFINE_OP_CLASS_ID
  
      static GrOp::Owner Make(GrRecordingContext* context,
                              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>(context, std::move(paint), path, tolerance,
                                                      coverage, viewMatrix, isHairline, aaType,
                                                      devBounds, stencilSettings);
      }
  
      const char* name() const override { return "DefaultPathOp"; }
  
      void visitProxies(const GrVisitProxyFunc& func) const override {
          if (fProgramInfo) {
              fProgramInfo->visitFPProxies(func);
          } else {
              fHelper.visitProxies(func);
          }
      }
  
      DefaultPathOp(GrProcessorSet* processorSet, const SkPMColor4f& 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(processorSet, aaType, stencilSettings)
              , fColor(color)
              , fCoverage(coverage)
              , fViewMatrix(viewMatrix)
              , fIsHairline(isHairline) {
          fPaths.emplace_back(PathData{path, tolerance});
  
          HasAABloat aaBloat = (aaType == GrAAType::kNone) ? HasAABloat ::kNo : HasAABloat::kYes;
          this->setBounds(devBounds, aaBloat,
                          isHairline ? IsHairline::kYes : IsHairline::kNo);
      }
  
      FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
  
      GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
                                        GrClampType clampType) override {
          GrProcessorAnalysisCoverage gpCoverage =
                  this->coverage() == 0xFF ? GrProcessorAnalysisCoverage::kNone
                                           : GrProcessorAnalysisCoverage::kSingleChannel;
          // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
          return fHelper.finalizeProcessors(caps, clip, clampType, gpCoverage, &fColor, nullptr);
      }
  
  private:
      GrPrimitiveType primType() const {
          if (this->isHairline()) {
              int instanceCount = fPaths.count();
  
              // We avoid indices when we have a single hairline contour.
              bool isIndexed = instanceCount > 1 ||
                                  PathGeoBuilder::PathHasMultipleSubpaths(fPaths[0].fPath);
  
              return isIndexed ? GrPrimitiveType::kLines : GrPrimitiveType::kLineStrip;
          }
  
          return GrPrimitiveType::kTriangles;
      }
  
      GrProgramInfo* programInfo() override { return fProgramInfo; }
  
      void onCreateProgramInfo(const GrCaps* caps,
                               SkArenaAlloc* arena,
                               const GrSurfaceProxyView& writeView,
                               bool usesMSAASurface,
                               GrAppliedClip&& appliedClip,
                               const GrDstProxyView& dstProxyView,
                               GrXferBarrierFlags renderPassXferBarriers,
                               GrLoadOp colorLoadOp) override {
          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(arena,
                                                 color,
                                                 coverage,
                                                 localCoords,
                                                 this->viewMatrix());
          }
  
          SkASSERT(gp->vertexStride() == sizeof(SkPoint));
  
          fProgramInfo =  fHelper.createProgramInfoWithStencil(caps, arena, writeView,
                                                               std::move(appliedClip),
                                                               dstProxyView, gp, this->primType(),
                                                               renderPassXferBarriers, colorLoadOp);
  
      }
  
      void onPrepareDraws(GrMeshDrawTarget* target) override {
          PathGeoBuilder pathGeoBuilder(this->primType(), target, &fMeshes);
  
          // fill buffers
          for (int i = 0; i < fPaths.count(); i++) {
              const PathData& args = fPaths[i];
              pathGeoBuilder.addPath(args.fPath, args.fTolerance);
          }
      }
  
      void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
          if (!fProgramInfo) {
              this->createProgramInfo(flushState);
          }
  
          if (!fProgramInfo || !fMeshes.count()) {
              return;
          }
  
          flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
          flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
          for (int i = 0; i < fMeshes.count(); ++i) {
              flushState->drawMesh(*fMeshes[i]);
          }
      }
  
      CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
          DefaultPathOp* that = t->cast<DefaultPathOp>();
          if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
              return CombineResult::kCannotCombine;
          }
  
          if (this->color() != that->color()) {
              return CombineResult::kCannotCombine;
          }
  
          if (this->coverage() != that->coverage()) {
              return CombineResult::kCannotCombine;
          }
  
          if (!SkMatrixPriv::CheapEqual(this->viewMatrix(), that->viewMatrix())) {
              return CombineResult::kCannotCombine;
          }
  
          if (this->isHairline() != that->isHairline()) {
              return CombineResult::kCannotCombine;
          }
  
          fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin());
          return CombineResult::kMerged;
      }
  
  #if GR_TEST_UTILS
      SkString onDumpInfo() const override {
          SkString string = SkStringPrintf("Color: 0x%08x Count: %d\n",
                                           fColor.toBytes_RGBA(), fPaths.count());
          for (const auto& path : fPaths) {
              string.appendf("Tolerance: %.2f\n", path.fTolerance);
          }
          string += fHelper.dumpInfo();
          return string;
      }
  #endif
  
      const SkPMColor4f& 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;
      SkPMColor4f fColor;
      uint8_t fCoverage;
      SkMatrix fViewMatrix;
      bool fIsHairline;
  
      SkTDArray<GrSimpleMesh*> fMeshes;
      GrProgramInfo* fProgramInfo = nullptr;
  
      using INHERITED = GrMeshDrawOp;
  };
  
  }  // anonymous namespace
  
  bool GrDefaultPathRenderer::internalDrawPath(GrSurfaceDrawContext* surfaceDrawContext,
                                               GrPaint&& paint,
                                               GrAAType aaType,
                                               const GrUserStencilSettings& userStencilSettings,
                                               const GrClip* clip,
                                               const SkMatrix& viewMatrix,
                                               const GrStyledShape& shape,
                                               bool stencilOnly) {
      auto context = surfaceDrawContext->recordingContext();
  
      SkASSERT(GrAAType::kCoverage != aaType);
      SkPath path;
      shape.asPath(&path);
  
      SkScalar hairlineCoverage;
      uint8_t newCoverage = 0xff;
      bool isHairline = false;
      if (GrIsStrokeHairlineOrEquivalent(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 SkPathFillType::kInverseEvenOdd:
                      reverse = true;
                      [[fallthrough]];
                  case SkPathFillType::kEvenOdd:
                      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 SkPathFillType::kInverseWinding:
                      reverse = true;
                      [[fallthrough]];
                  case SkPathFillType::kWinding:
                      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, surfaceDrawContext->asRenderTargetProxy()->backingStoreDimensions(),
                       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;
              // This is a non-coverage aa rect op since we assert aaType != kCoverage at the start
              assert_alive(paint);
              surfaceDrawContext->stencilRect(clip, passes[p], std::move(paint),
                                              GrAA(aaType == GrAAType::kMSAA), viewM, bounds,
                                              &localMatrix);
          } else {
              bool stencilPass = stencilOnly || passCount > 1;
              GrOp::Owner op;
              if (stencilPass) {
                  GrPaint stencilPaint;
                  stencilPaint.setXPFactory(GrDisableColorXPFactory::Get());
                  op = DefaultPathOp::Make(context, std::move(stencilPaint), path, srcSpaceTol,
                                           newCoverage, viewMatrix, isHairline, aaType, devBounds,
                                           passes[p]);
              } else {
                  assert_alive(paint);
                  op = DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, newCoverage,
                                           viewMatrix, isHairline, aaType, devBounds, passes[p]);
              }
              surfaceDrawContext->addDrawOp(clip, std::move(op));
          }
      }
      return true;
  }
  
  GrPathRenderer::CanDrawPath
  GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
      bool isHairline = GrIsStrokeHairlineOrEquivalent(
              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.fProxy->canUseStencil(*args.fCaps)) {
          return CanDrawPath::kNo;
      }
      // If antialiasing is required, we only support MSAA.
      if (GrAAType::kNone != args.fAAType && GrAAType::kMSAA != args.fAAType) {
          return CanDrawPath::kNo;
      }
      // This can draw any path with any simple fill style.
      if (!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.fContext->priv().auditTrail(),
                                "GrDefaultPathRenderer::onDrawPath");
      GrAAType aaType = (GrAAType::kNone != args.fAAType) ? GrAAType::kMSAA : GrAAType::kNone;
  
      return this->internalDrawPath(
              args.fSurfaceDrawContext, std::move(args.fPaint), aaType, *args.fUserStencilSettings,
              args.fClip, *args.fViewMatrix, *args.fShape, false);
  }
  
  void GrDefaultPathRenderer::onStencilPath(const StencilPathArgs& args) {
      GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(),
                                "GrDefaultPathRenderer::onStencilPath");
      SkASSERT(!args.fShape->inverseFilled());
  
      GrPaint paint;
      paint.setXPFactory(GrDisableColorXPFactory::Get());
  
      auto aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
  
      this->internalDrawPath(
              args.fSurfaceDrawContext, std::move(paint), aaType, 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);
      const 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 (numSamples > 1 && random->nextBool()) {
          aaType = GrAAType::kMSAA;
      }
      return DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, coverage, viewMatrix,
                                 true, aaType, bounds, GrGetRandomStencil(random, context));
  }
  
  #endif
  