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 "GrBatch.h"
  #include "GrBatchTarget.h"
  #include "GrBatchTest.h"
  #include "GrContext.h"
  #include "GrDefaultGeoProcFactory.h"
  #include "GrPathUtils.h"
  #include "GrPipelineBuilder.h"
  #include "GrVertices.h"
  #include "SkGeometry.h"
  #include "SkString.h"
  #include "SkStrokeRec.h"
  #include "SkTLazy.h"
  #include "SkTraceEvent.h"
  
  GrDefaultPathRenderer::GrDefaultPathRenderer(bool separateStencilSupport,
                                               bool stencilWrapOpsSupport)
      : fSeparateStencil(separateStencilSupport)
      , fStencilWrapOps(stencilWrapOpsSupport) {
  }
  
  
  ////////////////////////////////////////////////////////////////////////////////
  // Stencil rules for paths
  
  ////// Even/Odd
  
  GR_STATIC_CONST_SAME_STENCIL(gEOStencilPass,
      kInvert_StencilOp,
      kKeep_StencilOp,
      kAlwaysIfInClip_StencilFunc,
      0xffff,
      0xffff,
      0xffff);
  
  // ok not to check clip b/c stencil pass only wrote inside clip
  GR_STATIC_CONST_SAME_STENCIL(gEOColorPass,
      kZero_StencilOp,
      kZero_StencilOp,
      kNotEqual_StencilFunc,
      0xffff,
      0x0000,
      0xffff);
  
  // have to check clip b/c outside clip will always be zero.
  GR_STATIC_CONST_SAME_STENCIL(gInvEOColorPass,
      kZero_StencilOp,
      kZero_StencilOp,
      kEqualIfInClip_StencilFunc,
      0xffff,
      0x0000,
      0xffff);
  
  ////// Winding
  
  // when we have separate stencil we increment front faces / decrement back faces
  // when we don't have wrap incr and decr we use the stencil test to simulate
  // them.
  
  GR_STATIC_CONST_STENCIL(gWindStencilSeparateWithWrap,
      kIncWrap_StencilOp,             kDecWrap_StencilOp,
      kKeep_StencilOp,                kKeep_StencilOp,
      kAlwaysIfInClip_StencilFunc,    kAlwaysIfInClip_StencilFunc,
      0xffff,                         0xffff,
      0xffff,                         0xffff,
      0xffff,                         0xffff);
  
  // if inc'ing the max value, invert to make 0
  // if dec'ing zero invert to make all ones.
  // we can't avoid touching the stencil on both passing and
  // failing, so we can't resctrict ourselves to the clip.
  GR_STATIC_CONST_STENCIL(gWindStencilSeparateNoWrap,
      kInvert_StencilOp,              kInvert_StencilOp,
      kIncClamp_StencilOp,            kDecClamp_StencilOp,
      kEqual_StencilFunc,             kEqual_StencilFunc,
      0xffff,                         0xffff,
      0xffff,                         0x0000,
      0xffff,                         0xffff);
  
  // When there are no separate faces we do two passes to setup the winding rule
  // stencil. First we draw the front faces and inc, then we draw the back faces
  // and dec. These are same as the above two split into the incrementing and
  // decrementing passes.
  GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilWithWrapInc,
      kIncWrap_StencilOp,
      kKeep_StencilOp,
      kAlwaysIfInClip_StencilFunc,
      0xffff,
      0xffff,
      0xffff);
  
  GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilWithWrapDec,
      kDecWrap_StencilOp,
      kKeep_StencilOp,
      kAlwaysIfInClip_StencilFunc,
      0xffff,
      0xffff,
      0xffff);
  
  GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilNoWrapInc,
      kInvert_StencilOp,
      kIncClamp_StencilOp,
      kEqual_StencilFunc,
      0xffff,
      0xffff,
      0xffff);
  
  GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilNoWrapDec,
      kInvert_StencilOp,
      kDecClamp_StencilOp,
      kEqual_StencilFunc,
      0xffff,
      0x0000,
      0xffff);
  
  // Color passes are the same whether we use the two-sided stencil or two passes
  
  GR_STATIC_CONST_SAME_STENCIL(gWindColorPass,
      kZero_StencilOp,
      kZero_StencilOp,
      kNonZeroIfInClip_StencilFunc,
      0xffff,
      0x0000,
      0xffff);
  
  GR_STATIC_CONST_SAME_STENCIL(gInvWindColorPass,
      kZero_StencilOp,
      kZero_StencilOp,
      kEqualIfInClip_StencilFunc,
      0xffff,
      0x0000,
      0xffff);
  
  ////// Normal render to stencil
  
  // Sometimes the default path renderer can draw a path directly to the stencil
  // buffer without having to first resolve the interior / exterior.
  GR_STATIC_CONST_SAME_STENCIL(gDirectToStencil,
      kZero_StencilOp,
      kIncClamp_StencilOp,
      kAlwaysIfInClip_StencilFunc,
      0xffff,
      0x0000,
      0xffff);
  
  ////////////////////////////////////////////////////////////////////////////////
  // Helpers for drawPath
  
  #define STENCIL_OFF     0   // Always disable stencil (even when needed)
  
  static inline bool single_pass_path(const SkPath& path, const SkStrokeRec& stroke) {
  #if STENCIL_OFF
      return true;
  #else
      if (!stroke.isHairlineStyle() && !path.isInverseFillType()) {
          return path.isConvex();
      }
      return false;
  #endif
  }
  
  GrPathRenderer::StencilSupport
  GrDefaultPathRenderer::onGetStencilSupport(const GrDrawTarget*,
                                             const GrPipelineBuilder*,
                                             const SkPath& path,
                                             const GrStrokeInfo& stroke) const {
      if (single_pass_path(path, stroke.getStrokeRec())) {
          return GrPathRenderer::kNoRestriction_StencilSupport;
      } else {
          return GrPathRenderer::kStencilOnly_StencilSupport;
      }
  }
  
  static inline void append_countour_edge_indices(bool hairLine,
                                                  uint16_t fanCenterIdx,
                                                  uint16_t edgeV0Idx,
                                                  uint16_t** indices) {
      // when drawing lines we're appending line segments along
      // the contour. When applying the other fill rules we're
      // drawing triangle fans around fanCenterIdx.
      if (!hairLine) {
          *((*indices)++) = fanCenterIdx;
      }
      *((*indices)++) = edgeV0Idx;
      *((*indices)++) = edgeV0Idx + 1;
  }
  
  static inline void add_quad(SkPoint** vert, const SkPoint* base, const SkPoint pts[],
                              SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol, bool indexed,
                              bool isHairline, uint16_t subpathIdxStart, int offset, uint16_t** idx) {
      // first pt of quad is the pt we ended on in previous step
      uint16_t firstQPtIdx = (uint16_t)(*vert - base) - 1 + offset;
      uint16_t numPts =  (uint16_t)
          GrPathUtils::generateQuadraticPoints(
              pts[0], pts[1], pts[2],
              srcSpaceTolSqd, vert,
              GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
      if (indexed) {
          for (uint16_t i = 0; i < numPts; ++i) {
              append_countour_edge_indices(isHairline, subpathIdxStart,
                                           firstQPtIdx + i, idx);
          }
      }
  }
  
  class DefaultPathBatch : public GrBatch {
  public:
      struct Geometry {
          GrColor fColor;
          SkPath fPath;
          SkScalar fTolerance;
      };
  
      static GrBatch* Create(const Geometry& geometry, uint8_t coverage, const SkMatrix& viewMatrix,
                             bool isHairline, const SkRect& devBounds) {
          return SkNEW_ARGS(DefaultPathBatch, (geometry, coverage, viewMatrix, isHairline,
                                               devBounds));
      }
  
      const char* name() const override { return "DefaultPathBatch"; }
  
      void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
          // When this is called on a batch, there is only one geometry bundle
          out->setKnownFourComponents(fGeoData[0].fColor);
      }
      void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
          out->setKnownSingleComponent(this->coverage());
      }
  
      void initBatchTracker(const GrPipelineInfo& init) override {
          // Handle any color overrides
          if (init.fColorIgnored) {
              fGeoData[0].fColor = GrColor_ILLEGAL;
          } else if (GrColor_ILLEGAL != init.fOverrideColor) {
              fGeoData[0].fColor = init.fOverrideColor;
          }
  
          // setup batch properties
          fBatch.fColorIgnored = init.fColorIgnored;
          fBatch.fColor = fGeoData[0].fColor;
          fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
          fBatch.fCoverageIgnored = init.fCoverageIgnored;
      }
  
      void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override {
          SkAutoTUnref<const GrGeometryProcessor> gp(
                  GrDefaultGeoProcFactory::Create(GrDefaultGeoProcFactory::kPosition_GPType,
                                                  this->color(),
                                                  this->viewMatrix(),
                                                  SkMatrix::I(),
                                                  this->coverage()));
  
          size_t vertexStride = gp->getVertexStride();
          SkASSERT(vertexStride == sizeof(SkPoint));
  
          batchTarget->initDraw(gp, pipeline);
  
          // TODO this is hacky, but the only way we have to initialize the GP is to use the
          // GrPipelineInfo struct so we can generate the correct shader.  Once we have GrBatch
          // everywhere we can remove this nastiness
          GrPipelineInfo init;
          init.fColorIgnored = fBatch.fColorIgnored;
          init.fOverrideColor = GrColor_ILLEGAL;
          init.fCoverageIgnored = fBatch.fCoverageIgnored;
          init.fUsesLocalCoords = this->usesLocalCoords();
          gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
  
          int instanceCount = fGeoData.count();
  
          // compute number of vertices
          int maxVertices = 0;
  
          // We will use index buffers if we have multiple paths or one path with multiple contours
          bool isIndexed = instanceCount > 1;
          for (int i = 0; i < instanceCount; i++) {
              Geometry& args = fGeoData[i];
  
              int contourCount;
              maxVertices += GrPathUtils::worstCasePointCount(args.fPath, &contourCount,
                                                              args.fTolerance);
  
              isIndexed = isIndexed || contourCount > 1;
          }
  
          if (maxVertices == 0 || maxVertices > ((int)SK_MaxU16 + 1)) {
              SkDebugf("Cannot render path (%d)\n", maxVertices);
              return;
          }
  
          // determine primitiveType
          int maxIndices = 0;
          GrPrimitiveType primitiveType;
          if (this->isHairline()) {
              if (isIndexed) {
                  maxIndices = 2 * maxVertices;
                  primitiveType = kLines_GrPrimitiveType;
              } else {
                  primitiveType = kLineStrip_GrPrimitiveType;
              }
          } else {
              if (isIndexed) {
                  maxIndices = 3 * maxVertices;
                  primitiveType = kTriangles_GrPrimitiveType;
              } else {
                  primitiveType = kTriangleFan_GrPrimitiveType;
              }
          }
  
          // allocate vertex / index buffers
          const GrVertexBuffer* vertexBuffer;
          int firstVertex;
  
          void* verts = batchTarget->makeVertSpace(vertexStride, maxVertices,
                                                   &vertexBuffer, &firstVertex);
  
          if (!verts) {
              SkDebugf("Could not allocate vertices\n");
              return;
          }
  
          const GrIndexBuffer* indexBuffer = NULL;
          int firstIndex = 0;
  
          void* indices = NULL;
          if (isIndexed) {
              indices = batchTarget->makeIndexSpace(maxIndices, &indexBuffer, &firstIndex);
  
              if (!indices) {
                  SkDebugf("Could not allocate indices\n");
                  return;
              }
          }
  
          // fill buffers
          int vertexOffset = 0;
          int indexOffset = 0;
          for (int i = 0; i < instanceCount; i++) {
              Geometry& args = fGeoData[i];
  
              int vertexCnt = 0;
              int indexCnt = 0;
              if (!this->createGeom(verts,
                                    vertexOffset,
                                    indices,
                                    indexOffset,
                                    &vertexCnt,
                                    &indexCnt,
                                    args.fPath,
                                    args.fTolerance,
                                    isIndexed)) {
                  return;
              }
  
              vertexOffset += vertexCnt;
              indexOffset += indexCnt;
              SkASSERT(vertexOffset <= maxVertices && indexOffset <= maxIndices);
          }
  
          GrVertices vertices;
          if (isIndexed) {
              vertices.initIndexed(primitiveType, vertexBuffer, indexBuffer, firstVertex, firstIndex,
                                   vertexOffset, indexOffset);
          } else {
              vertices.init(primitiveType, vertexBuffer, firstVertex, vertexOffset);
          }
          batchTarget->draw(vertices);
  
          // put back reserves
          batchTarget->putBackIndices((size_t)(maxIndices - indexOffset));
          batchTarget->putBackVertices((size_t)(maxVertices - vertexOffset), (size_t)vertexStride);
      }
  
      SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
  
  private:
      DefaultPathBatch(const Geometry& geometry, uint8_t coverage, const SkMatrix& viewMatrix,
                       bool isHairline, const SkRect& devBounds) {
          this->initClassID<DefaultPathBatch>();
          fBatch.fCoverage = coverage;
          fBatch.fIsHairline = isHairline;
          fBatch.fViewMatrix = viewMatrix;
          fGeoData.push_back(geometry);
  
          this->setBounds(devBounds);
      }
  
      bool onCombineIfPossible(GrBatch* t) override {
          DefaultPathBatch* that = t->cast<DefaultPathBatch>();
  
          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;
          }
  
          fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
          this->joinBounds(that->bounds());
          return true;
      }
  
      bool createGeom(void* vertices,
                      size_t vertexOffset,
                      void* indices,
                      size_t indexOffset,
                      int* vertexCnt,
                      int* indexCnt,
                      const SkPath& path,
                      SkScalar srcSpaceTol,
                      bool isIndexed)  {
          {
              SkScalar srcSpaceTolSqd = SkScalarMul(srcSpaceTol, srcSpaceTol);
  
              uint16_t indexOffsetU16 = (uint16_t)indexOffset;
              uint16_t vertexOffsetU16 = (uint16_t)vertexOffset;
  
              uint16_t* idxBase = reinterpret_cast<uint16_t*>(indices) + indexOffsetU16;
              uint16_t* idx = idxBase;
              uint16_t subpathIdxStart = vertexOffsetU16;
  
              SkPoint* base = reinterpret_cast<SkPoint*>(vertices) + vertexOffset;
              SkPoint* vert = base;
  
              SkPoint pts[4];
  
              bool first = true;
              int subpath = 0;
  
              SkPath::Iter iter(path, false);
  
              bool done = false;
              while (!done) {
                  SkPath::Verb verb = iter.next(pts);
                  switch (verb) {
                      case SkPath::kMove_Verb:
                          if (!first) {
                              uint16_t currIdx = (uint16_t) (vert - base) + vertexOffsetU16;
                              subpathIdxStart = currIdx;
                              ++subpath;
                          }
                          *vert = pts[0];
                          vert++;
                          break;
                      case SkPath::kLine_Verb:
                          if (isIndexed) {
                              uint16_t prevIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16;
                              append_countour_edge_indices(this->isHairline(), subpathIdxStart,
                                                           prevIdx, &idx);
                          }
                          *(vert++) = pts[1];
                          break;
                      case SkPath::kConic_Verb: {
                          SkScalar weight = iter.conicWeight();
                          SkAutoConicToQuads converter;
                          // Converting in src-space, hance the finer tolerance (0.25)
                          // TODO: find a way to do this in dev-space so the tolerance means something
                          const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.25f);
                          for (int i = 0; i < converter.countQuads(); ++i) {
                              add_quad(&vert, base, quadPts + i*2, srcSpaceTolSqd, srcSpaceTol,
                                       isIndexed, this->isHairline(), subpathIdxStart,
                                       (int)vertexOffset, &idx);
                          }
                          break;
                      }
                      case SkPath::kQuad_Verb:
                          add_quad(&vert, base, pts, srcSpaceTolSqd, srcSpaceTol, isIndexed,
                                   this->isHairline(), subpathIdxStart, (int)vertexOffset, &idx);
                          break;
                      case SkPath::kCubic_Verb: {
                          // first pt of cubic is the pt we ended on in previous step
                          uint16_t firstCPtIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16;
                          uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints(
                                          pts[0], pts[1], pts[2], pts[3],
                                          srcSpaceTolSqd, &vert,
                                          GrPathUtils::cubicPointCount(pts, srcSpaceTol));
                          if (isIndexed) {
                              for (uint16_t i = 0; i < numPts; ++i) {
                                  append_countour_edge_indices(this->isHairline(), subpathIdxStart,
                                                               firstCPtIdx + i, &idx);
                              }
                          }
                          break;
                      }
                      case SkPath::kClose_Verb:
                          break;
                      case SkPath::kDone_Verb:
                          done = true;
                  }
                  first = false;
              }
  
              *vertexCnt = static_cast<int>(vert - base);
              *indexCnt = static_cast<int>(idx - idxBase);
  
          }
          return true;
      }
  
      GrColor color() const { return fBatch.fColor; }
      uint8_t coverage() const { return fBatch.fCoverage; }
      bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
      const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
      bool isHairline() const { return fBatch.fIsHairline; }
  
      struct BatchTracker {
          GrColor fColor;
          uint8_t fCoverage;
          SkMatrix fViewMatrix;
          bool fUsesLocalCoords;
          bool fColorIgnored;
          bool fCoverageIgnored;
          bool fIsHairline;
      };
  
      BatchTracker fBatch;
      SkSTArray<1, Geometry, true> fGeoData;
  };
  
  bool GrDefaultPathRenderer::internalDrawPath(GrDrawTarget* target,
                                               GrPipelineBuilder* pipelineBuilder,
                                               GrColor color,
                                               const SkMatrix& viewMatrix,
                                               const SkPath& path,
                                               const GrStrokeInfo& origStroke,
                                               bool stencilOnly) {
      SkTCopyOnFirstWrite<GrStrokeInfo> stroke(origStroke);
  
      SkScalar hairlineCoverage;
      uint8_t newCoverage = 0xff;
      if (IsStrokeHairlineOrEquivalent(*stroke, viewMatrix, &hairlineCoverage)) {
          newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);
  
          if (!stroke->getStrokeRec().isHairlineStyle()) {
              stroke.writable()->getStrokeRecPtr()->setHairlineStyle();
          }
      }
  
      const bool isHairline = stroke->getStrokeRec().isHairlineStyle();
  
      // Save the current xp on the draw state so we can reset it if needed
      SkAutoTUnref<const GrXPFactory> backupXPFactory(SkRef(pipelineBuilder->getXPFactory()));
      // face culling doesn't make sense here
      SkASSERT(GrPipelineBuilder::kBoth_DrawFace == pipelineBuilder->getDrawFace());
  
      int                         passCount = 0;
      const GrStencilSettings*    passes[3];
      GrPipelineBuilder::DrawFace drawFace[3];
      bool                        reverse = false;
      bool                        lastPassIsBounds;
  
      if (isHairline) {
          passCount = 1;
          if (stencilOnly) {
              passes[0] = &gDirectToStencil;
          } else {
              passes[0] = NULL;
          }
          lastPassIsBounds = false;
          drawFace[0] = GrPipelineBuilder::kBoth_DrawFace;
      } else {
          if (single_pass_path(path, stroke->getStrokeRec())) {
              passCount = 1;
              if (stencilOnly) {
                  passes[0] = &gDirectToStencil;
              } else {
                  passes[0] = NULL;
              }
              drawFace[0] = GrPipelineBuilder::kBoth_DrawFace;
              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;
                          }
                      }
                      drawFace[0] = drawFace[1] = GrPipelineBuilder::kBoth_DrawFace;
                      break;
  
                  case SkPath::kInverseWinding_FillType:
                      reverse = true;
                      // fallthrough
                  case SkPath::kWinding_FillType:
                      if (fSeparateStencil) {
                          if (fStencilWrapOps) {
                              passes[0] = &gWindStencilSeparateWithWrap;
                          } else {
                              passes[0] = &gWindStencilSeparateNoWrap;
                          }
                          passCount = 2;
                          drawFace[0] = GrPipelineBuilder::kBoth_DrawFace;
                      } else {
                          if (fStencilWrapOps) {
                              passes[0] = &gWindSingleStencilWithWrapInc;
                              passes[1] = &gWindSingleStencilWithWrapDec;
                          } else {
                              passes[0] = &gWindSingleStencilNoWrapInc;
                              passes[1] = &gWindSingleStencilNoWrapDec;
                          }
                          // which is cw and which is ccw is arbitrary.
                          drawFace[0] = GrPipelineBuilder::kCW_DrawFace;
                          drawFace[1] = GrPipelineBuilder::kCCW_DrawFace;
                          passCount = 3;
                      }
                      if (stencilOnly) {
                          lastPassIsBounds = false;
                          --passCount;
                      } else {
                          lastPassIsBounds = true;
                          drawFace[passCount-1] = GrPipelineBuilder::kBoth_DrawFace;
                          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, pipelineBuilder->getRenderTarget(), viewMatrix, &devBounds);
  
      for (int p = 0; p < passCount; ++p) {
          pipelineBuilder->setDrawFace(drawFace[p]);
          if (passes[p]) {
              *pipelineBuilder->stencil() = *passes[p];
          }
  
          if (lastPassIsBounds && (p == passCount-1)) {
              // Reset the XP Factory on pipelineBuilder
              pipelineBuilder->setXPFactory(backupXPFactory);
              SkRect bounds;
              SkMatrix localMatrix = SkMatrix::I();
              if (reverse) {
                  SkASSERT(pipelineBuilder->getRenderTarget());
                  // 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;
              target->drawRect(pipelineBuilder, color, viewM, bounds, NULL, &localMatrix);
          } else {
              if (passCount > 1) {
                  pipelineBuilder->setDisableColorXPFactory();
              }
  
              DefaultPathBatch::Geometry geometry;
              geometry.fColor = color;
              geometry.fPath = path;
              geometry.fTolerance = srcSpaceTol;
  
              SkAutoTUnref<GrBatch> batch(DefaultPathBatch::Create(geometry, newCoverage, viewMatrix,
                                                                   isHairline, devBounds));
  
              target->drawBatch(pipelineBuilder, batch);
          }
      }
      return true;
  }
  
  bool GrDefaultPathRenderer::canDrawPath(const GrDrawTarget* target,
                                          const GrPipelineBuilder* pipelineBuilder,
                                          const SkMatrix& viewMatrix,
                                          const SkPath& path,
                                          const GrStrokeInfo& stroke,
                                          bool antiAlias) const {
      // this class can draw any path with any fill but doesn't do any anti-aliasing.
      return !antiAlias && (stroke.isFillStyle() || IsStrokeHairlineOrEquivalent(stroke,
                                                                                 viewMatrix,
                                                                                 NULL));
  }
  
  bool GrDefaultPathRenderer::onDrawPath(GrDrawTarget* target,
                                         GrPipelineBuilder* pipelineBuilder,
                                         GrColor color,
                                         const SkMatrix& viewMatrix,
                                         const SkPath& path,
                                         const GrStrokeInfo& stroke,
                                         bool antiAlias) {
      return this->internalDrawPath(target,
                                    pipelineBuilder,
                                    color,
                                    viewMatrix,
                                    path,
                                    stroke,
                                    false);
  }
  
  void GrDefaultPathRenderer::onStencilPath(GrDrawTarget* target,
                                            GrPipelineBuilder* pipelineBuilder,
                                            const SkMatrix& viewMatrix,
                                            const SkPath& path,
                                            const GrStrokeInfo& stroke) {
      SkASSERT(SkPath::kInverseEvenOdd_FillType != path.getFillType());
      SkASSERT(SkPath::kInverseWinding_FillType != path.getFillType());
      this->internalDrawPath(target, pipelineBuilder, GrColor_WHITE, viewMatrix, path, stroke, true);
  }
  
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  
  #ifdef GR_TEST_UTILS
  
  BATCH_TEST_DEFINE(DefaultPathBatch) {
      GrColor color = GrRandomColor(random);
      SkMatrix viewMatrix = GrTest::TestMatrix(random);
  
      // For now just hairlines because the other types of draws require two batches.
      // TODO we should figure out a way to combine the stencil and cover steps into one batch
      GrStrokeInfo stroke(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);
  
      DefaultPathBatch::Geometry geometry;
      geometry.fColor = color;
      geometry.fPath = path;
      geometry.fTolerance = srcSpaceTol;
  
      viewMatrix.mapRect(&bounds);
      uint8_t coverage = GrRandomCoverage(random);
      return DefaultPathBatch::Create(geometry, coverage, viewMatrix, true, bounds);
  }
  
  #endif
  