| /* | |
| * 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 "GrBatchFlushState.h" | |
| #include "GrBatchTest.h" | |
| #include "GrContext.h" | |
| #include "GrDefaultGeoProcFactory.h" | |
| #include "GrFixedClip.h" | |
| #include "GrMesh.h" | |
| #include "GrPathUtils.h" | |
| #include "GrPipelineBuilder.h" | |
| #include "SkGeometry.h" | |
| #include "SkString.h" | |
| #include "SkStrokeRec.h" | |
| #include "SkTLazy.h" | |
| #include "SkTraceEvent.h" | |
| #include "batches/GrRectBatchFactory.h" | |
| #include "batches/GrVertexBatch.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 DefaultPathBatch : public GrVertexBatch { | |
| public: | |
| DEFINE_BATCH_CLASS_ID | |
| DefaultPathBatch(GrColor color, const SkPath& path, SkScalar tolerance, | |
| uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline, | |
| const SkRect& devBounds) | |
| : INHERITED(ClassID()) { | |
| fBatch.fCoverage = coverage; | |
| fBatch.fIsHairline = isHairline; | |
| fBatch.fViewMatrix = viewMatrix; | |
| fGeoData.emplace_back(Geometry{color, path, tolerance}); | |
| this->setBounds(devBounds, HasAABloat::kNo, | |
| isHairline ? IsZeroArea::kYes : IsZeroArea::kNo); | |
| } | |
| const char* name() const override { return "DefaultPathBatch"; } | |
| void computePipelineOptimizations(GrInitInvariantOutput* color, | |
| GrInitInvariantOutput* coverage, | |
| GrBatchToXPOverrides* overrides) const override { | |
| // When this is called on a batch, there is only one geometry bundle | |
| color->setKnownFourComponents(fGeoData[0].fColor); | |
| coverage->setKnownSingleComponent(this->coverage()); | |
| } | |
| private: | |
| void initBatchTracker(const GrXPOverridesForBatch& overrides) override { | |
| // Handle any color overrides | |
| if (!overrides.readsColor()) { | |
| fGeoData[0].fColor = GrColor_ILLEGAL; | |
| } | |
| overrides.getOverrideColorIfSet(&fGeoData[0].fColor); | |
| // setup batch properties | |
| fBatch.fColorIgnored = !overrides.readsColor(); | |
| fBatch.fColor = fGeoData[0].fColor; | |
| fBatch.fUsesLocalCoords = overrides.readsLocalCoords(); | |
| fBatch.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 = 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++) { | |
| const 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 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 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); | |
| } | |
| 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(GrBatch* t, const GrCaps& caps) override { | |
| DefaultPathBatch* that = t->cast<DefaultPathBatch>(); | |
| 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; | |
| } | |
| fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.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 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; } | |
| bool coverageIgnored() const { return fBatch.fCoverageIgnored; } | |
| struct BatchTracker { | |
| GrColor fColor; | |
| uint8_t fCoverage; | |
| SkMatrix fViewMatrix; | |
| bool fUsesLocalCoords; | |
| bool fColorIgnored; | |
| bool fCoverageIgnored; | |
| bool fIsHairline; | |
| }; | |
| struct Geometry { | |
| GrColor fColor; | |
| SkPath fPath; | |
| SkScalar fTolerance; | |
| }; | |
| BatchTracker fBatch; | |
| SkSTArray<1, Geometry, true> fGeoData; | |
| typedef GrVertexBatch INHERITED; | |
| }; | |
| bool GrDefaultPathRenderer::internalDrawPath(GrDrawContext* drawContext, | |
| const GrPaint& paint, | |
| const GrUserStencilSettings& userStencilSettings, | |
| const GrClip& clip, | |
| const SkMatrix& viewMatrix, | |
| const GrShape& shape, | |
| bool stencilOnly) { | |
| 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, drawContext->width(), drawContext->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; | |
| SkAutoTUnref<GrDrawBatch> batch( | |
| GrRectBatchFactory::CreateNonAAFill(paint.getColor(), viewM, bounds, nullptr, | |
| &localMatrix)); | |
| SkASSERT(GrDrawFace::kBoth == drawFace[p]); | |
| GrPipelineBuilder pipelineBuilder(paint, drawContext->mustUseHWAA(paint)); | |
| pipelineBuilder.setDrawFace(drawFace[p]); | |
| pipelineBuilder.setUserStencil(passes[p]); | |
| drawContext->drawBatch(pipelineBuilder, clip, batch); | |
| } else { | |
| SkAutoTUnref<GrDrawBatch> batch(new DefaultPathBatch(paint.getColor(), path, | |
| srcSpaceTol, | |
| newCoverage, viewMatrix, | |
| isHairline, devBounds)); | |
| GrPipelineBuilder pipelineBuilder(paint, drawContext->mustUseHWAA(paint)); | |
| pipelineBuilder.setDrawFace(drawFace[p]); | |
| pipelineBuilder.setUserStencil(passes[p]); | |
| if (passCount > 1) { | |
| pipelineBuilder.setDisableColorXPFactory(); | |
| } | |
| drawContext->drawBatch(pipelineBuilder, clip, batch); | |
| } | |
| } | |
| return true; | |
| } | |
| bool GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { | |
| // this class can draw any path with any simple fill style but doesn't do any anti-aliasing. | |
| return !args.fAntiAlias && | |
| (args.fShape->style().isSimpleFill() || | |
| IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr)); | |
| } | |
| bool GrDefaultPathRenderer::onDrawPath(const DrawPathArgs& args) { | |
| GR_AUDIT_TRAIL_AUTO_FRAME(args.fDrawContext->auditTrail(), | |
| "GrDefaultPathRenderer::onDrawPath"); | |
| return this->internalDrawPath(args.fDrawContext, | |
| *args.fPaint, | |
| *args.fUserStencilSettings, | |
| *args.fClip, | |
| *args.fViewMatrix, | |
| *args.fShape, | |
| false); | |
| } | |
| void GrDefaultPathRenderer::onStencilPath(const StencilPathArgs& args) { | |
| GR_AUDIT_TRAIL_AUTO_FRAME(args.fDrawContext->auditTrail(), | |
| "GrDefaultPathRenderer::onStencilPath"); | |
| SkASSERT(!args.fShape->inverseFilled()); | |
| GrPaint paint; | |
| paint.setXPFactory(GrDisableColorXPFactory::Make()); | |
| paint.setAntiAlias(args.fIsAA); | |
| this->internalDrawPath(args.fDrawContext, paint, GrUserStencilSettings::kUnused, *args.fClip, | |
| *args.fViewMatrix, *args.fShape, true); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| #ifdef GR_TEST_UTILS | |
| DRAW_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 | |
| 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 new DefaultPathBatch(color, path, srcSpaceTol, coverage, viewMatrix, true, bounds); | |
| } | |
| #endif |