aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/ops/GrDefaultPathRenderer.cpp
diff options
context:
space:
mode:
authorGravatar Brian Salomon <bsalomon@google.com>2016-12-16 09:52:16 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2016-12-16 16:58:24 +0000
commit895274391db8df7357334aec260edca2e1735626 (patch)
tree6c447a64c6e376d67503ef18e20cfdb655b6a312 /src/gpu/ops/GrDefaultPathRenderer.cpp
parent6b316e9e20d67f0f13021ca62f4270105c6151b1 (diff)
move src/gpu/batches -> src/gpu/ops
Change-Id: I6410eae41f051ce38bef6f38d670924c3483c325 Reviewed-on: https://skia-review.googlesource.com/6163 Commit-Queue: Brian Salomon <bsalomon@google.com> Reviewed-by: Brian Osman <brianosman@google.com>
Diffstat (limited to 'src/gpu/ops/GrDefaultPathRenderer.cpp')
-rw-r--r--src/gpu/ops/GrDefaultPathRenderer.cpp641
1 files changed, 641 insertions, 0 deletions
diff --git a/src/gpu/ops/GrDefaultPathRenderer.cpp b/src/gpu/ops/GrDefaultPathRenderer.cpp
new file mode 100644
index 0000000000..b979d9171d
--- /dev/null
+++ b/src/gpu/ops/GrDefaultPathRenderer.cpp
@@ -0,0 +1,641 @@
+/*
+ * 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 "GrBatchTest.h"
+#include "GrContext.h"
+#include "GrDefaultGeoProcFactory.h"
+#include "GrFixedClip.h"
+#include "GrMesh.h"
+#include "GrOpFlushState.h"
+#include "GrPathUtils.h"
+#include "GrPipelineBuilder.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(bool separateStencilSupport,
+ bool stencilWrapOpsSupport)
+ : fSeparateStencil(separateStencilSupport)
+ , fStencilWrapOps(stencilWrapOpsSupport) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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;
+ }
+}
+
+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 DefaultPathOp final : public GrMeshDrawOp {
+public:
+ DEFINE_OP_CLASS_ID
+
+ static sk_sp<GrDrawOp> Make(GrColor color, const SkPath& path, SkScalar tolerance,
+ uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline,
+ const SkRect& devBounds) {
+ return sk_sp<GrDrawOp>(new DefaultPathOp(color, path, tolerance, coverage, viewMatrix,
+ isHairline, devBounds));
+ }
+
+ const char* name() const override { return "DefaultPathOp"; }
+
+ 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.append(DumpPipelineInfo(*this->pipeline()));
+ string.append(INHERITED::dumpInfo());
+ return string;
+ }
+
+ void computePipelineOptimizations(GrInitInvariantOutput* color,
+ GrInitInvariantOutput* coverage,
+ GrBatchToXPOverrides* overrides) const override {
+ color->setKnownFourComponents(fColor);
+ coverage->setKnownSingleComponent(this->coverage());
+ }
+
+private:
+ DefaultPathOp(GrColor color, const SkPath& path, SkScalar tolerance, uint8_t coverage,
+ const SkMatrix& viewMatrix, bool isHairline, const SkRect& devBounds)
+ : INHERITED(ClassID())
+ , fColor(color)
+ , fCoverage(coverage)
+ , fViewMatrix(viewMatrix)
+ , fIsHairline(isHairline) {
+ fPaths.emplace_back(PathData{path, tolerance});
+
+ this->setBounds(devBounds, HasAABloat::kNo,
+ isHairline ? IsZeroArea::kYes : IsZeroArea::kNo);
+ }
+
+ void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
+ if (!overrides.readsColor()) {
+ fColor = GrColor_ILLEGAL;
+ }
+ overrides.getOverrideColorIfSet(&fColor);
+ fUsesLocalCoords = overrides.readsLocalCoords();
+ fCoverageIgnored = !overrides.readsCoverage();
+ }
+
+ void onPrepareDraws(Target* target) const override {
+ sk_sp<GrGeometryProcessor> gp;
+ {
+ using namespace GrDefaultGeoProcFactory;
+ Color color(this->color());
+ Coverage coverage(this->coverage());
+ if (this->coverageIgnored()) {
+ coverage.fType = Coverage::kNone_Type;
+ }
+ LocalCoords localCoords(this->usesLocalCoords() ? LocalCoords::kUsePosition_Type :
+ LocalCoords::kUnused_Type);
+ gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, this->viewMatrix());
+ }
+
+ size_t vertexStride = gp->getVertexStride();
+ SkASSERT(vertexStride == sizeof(SkPoint));
+
+ int instanceCount = fPaths.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++) {
+ const PathData& args = fPaths[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 GrBuffer* vertexBuffer;
+ int firstVertex;
+
+ void* verts = target->makeVertexSpace(vertexStride, maxVertices,
+ &vertexBuffer, &firstVertex);
+
+ if (!verts) {
+ SkDebugf("Could not allocate vertices\n");
+ return;
+ }
+
+ const GrBuffer* indexBuffer = nullptr;
+ int firstIndex = 0;
+
+ void* indices = nullptr;
+ if (isIndexed) {
+ indices = target->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++) {
+ const PathData& args = fPaths[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);
+ }
+
+ GrMesh mesh;
+ if (isIndexed) {
+ mesh.initIndexed(primitiveType, vertexBuffer, indexBuffer, firstVertex, firstIndex,
+ vertexOffset, indexOffset);
+ } else {
+ mesh.init(primitiveType, vertexBuffer, firstVertex, vertexOffset);
+ }
+ target->draw(gp.get(), mesh);
+
+ // put back reserves
+ target->putBackIndices((size_t)(maxIndices - indexOffset));
+ target->putBackVertices((size_t)(maxVertices - vertexOffset), (size_t)vertexStride);
+ }
+
+ bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
+ DefaultPathOp* that = t->cast<DefaultPathOp>();
+ if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
+ that->bounds(), caps)) {
+ 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;
+ }
+
+ bool createGeom(void* vertices,
+ size_t vertexOffset,
+ void* indices,
+ size_t indexOffset,
+ int* vertexCnt,
+ int* indexCnt,
+ const SkPath& path,
+ SkScalar srcSpaceTol,
+ bool isIndexed) const {
+ 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 fColor; }
+ uint8_t coverage() const { return fCoverage; }
+ bool usesLocalCoords() const { return fUsesLocalCoords; }
+ const SkMatrix& viewMatrix() const { return fViewMatrix; }
+ bool isHairline() const { return fIsHairline; }
+ bool coverageIgnored() const { return fCoverageIgnored; }
+
+ struct PathData {
+ SkPath fPath;
+ SkScalar fTolerance;
+ };
+
+ GrColor fColor;
+ uint8_t fCoverage;
+ SkMatrix fViewMatrix;
+ bool fUsesLocalCoords;
+ bool fCoverageIgnored;
+ bool fIsHairline;
+ SkSTArray<1, PathData, true> fPaths;
+
+ typedef GrMeshDrawOp INHERITED;
+};
+
+bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext,
+ const 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[3];
+ GrDrawFace drawFace[3];
+ bool reverse = false;
+ bool lastPassIsBounds;
+
+ if (isHairline) {
+ passCount = 1;
+ if (stencilOnly) {
+ passes[0] = &gDirectToStencil;
+ } else {
+ passes[0] = &userStencilSettings;
+ }
+ lastPassIsBounds = false;
+ drawFace[0] = GrDrawFace::kBoth;
+ } else {
+ if (single_pass_shape(shape)) {
+ passCount = 1;
+ if (stencilOnly) {
+ passes[0] = &gDirectToStencil;
+ } else {
+ passes[0] = &userStencilSettings;
+ }
+ drawFace[0] = GrDrawFace::kBoth;
+ 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] = GrDrawFace::kBoth;
+ 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] = GrDrawFace::kBoth;
+ } 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] = GrDrawFace::kCW;
+ drawFace[1] = GrDrawFace::kCCW;
+ passCount = 3;
+ }
+ if (stencilOnly) {
+ lastPassIsBounds = false;
+ --passCount;
+ } else {
+ lastPassIsBounds = true;
+ drawFace[passCount-1] = GrDrawFace::kBoth;
+ 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->width(), renderTargetContext->height(), 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;
+ sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(paint.getColor(), viewM, bounds,
+ nullptr, &localMatrix));
+
+ SkASSERT(GrDrawFace::kBoth == drawFace[p]);
+ GrPipelineBuilder pipelineBuilder(paint, aaType);
+ pipelineBuilder.setDrawFace(drawFace[p]);
+ pipelineBuilder.setUserStencil(passes[p]);
+ renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
+ } else {
+ sk_sp<GrDrawOp> op =
+ DefaultPathOp::Make(paint.getColor(), path, srcSpaceTol, newCoverage,
+ viewMatrix, isHairline, devBounds);
+ GrPipelineBuilder pipelineBuilder(paint, aaType);
+ pipelineBuilder.setDrawFace(drawFace[p]);
+ pipelineBuilder.setUserStencil(passes[p]);
+ if (passCount > 1) {
+ pipelineBuilder.setDisableColorXPFactory();
+ }
+ renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
+ }
+ }
+ return true;
+}
+
+bool GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
+ // This can draw any path with any simple fill style but doesn't do coverage-based antialiasing.
+ return GrAAType::kCoverage != args.fAAType &&
+ (args.fShape->style().isSimpleFill() ||
+ IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr));
+}
+
+bool GrDefaultPathRenderer::onDrawPath(const DrawPathArgs& args) {
+ GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
+ "GrDefaultPathRenderer::onDrawPath");
+ return this->internalDrawPath(args.fRenderTargetContext,
+ *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::Make());
+
+ this->internalDrawPath(args.fRenderTargetContext, paint, args.fAAType,
+ GrUserStencilSettings::kUnused, *args.fClip, *args.fViewMatrix,
+ *args.fShape, true);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef GR_TEST_UTILS
+
+DRAW_BATCH_TEST_DEFINE(DefaultPathOp) {
+ 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
+ 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);
+ return DefaultPathOp::Make(color, path, srcSpaceTol, coverage, viewMatrix, true, bounds)
+ .release();
+}
+
+#endif