static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p, const SkRect& bounds) { REPORTER_ASSERT(reporter, p.isConvex()); REPORTER_ASSERT(reporter, p.getBounds() == bounds); SkPath p2(p); REPORTER_ASSERT(reporter, p2.isConvex()); REPORTER_ASSERT(reporter, p2.getBounds() == bounds); SkPath other; other.swap(p2); REPORTER_ASSERT(reporter, other.isConvex()); REPORTER_ASSERT(reporter, other.getBounds() == bounds); }
virtual void onDraw(int loops, SkCanvas* canvas) { SkPaint paint; this->setupPaint(&paint); for (int i = 0; i < loops; ++i) { // jostle the clip regions each time to prevent caching fClipRect.offset((i % 2) == 0 ? SkIntToScalar(10) : SkIntToScalar(-10), 0); fClipPath.reset(); fClipPath.addRoundRect(fClipRect, SkIntToScalar(5), SkIntToScalar(5)); SkASSERT(fClipPath.isConvex()); canvas->save(); #if 1 if (fDoPath) { canvas->clipPath(fClipPath, kReplace_SkClipOp, fDoAA); } else { canvas->clipRect(fClipRect, kReplace_SkClipOp, fDoAA); } canvas->drawRect(fDrawRect, paint); #else // this path tests out directly draw the clip primitive // use it to comparing just drawing the clip vs. drawing using // the clip if (fDoPath) { canvas->drawPath(fClipPath, paint); } else { canvas->drawRect(fClipRect, paint); } #endif canvas->restore(); } }
bool GrAndroidPathRenderer::canDrawPath(const SkPath& path, const SkStrokeRec& stroke, const GrDrawTarget* target, bool antiAlias) const { return ((stroke.isFillStyle() || stroke.getStyle() == SkStrokeRec::kStroke_Style) && !path.isInverseFillType() && path.isConvex()); }
bool GrAAConvexPathRenderer::canDrawPath(const GrDrawTarget* target, const GrPipelineBuilder*, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) const { return (target->caps()->shaderCaps()->shaderDerivativeSupport() && antiAlias && stroke.isFillStyle() && !path.isInverseFillType() && path.isConvex()); }
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 }
static inline bool single_pass_path(const SkPath& path, GrPathFill fill) { #if STENCIL_OFF return true; #else if (kEvenOdd_GrPathFill == fill || kWinding_GrPathFill == fill) { return path.isConvex(); } return false; #endif }
bool GrAAConvexPathRenderer::canDrawPath(const SkPath& path, GrPathFill fill, const GrDrawTarget* target, bool antiAlias) const { if (!target->getCaps().shaderDerivativeSupport() || !antiAlias || kHairLine_GrPathFill == fill || GrIsFillInverted(fill) || !path.isConvex()) { return false; } else { return true; } }
void draw(SkCanvas* canvas) { SkPaint paint; paint.setAntiAlias(true); for (auto xradius : { 0, 7, 13, 20 } ) { for (auto yradius : { 0, 9, 18, 40 } ) { SkPath path; path.addRoundRect({10, 10, 36, 46}, xradius, yradius); paint.setColor(path.isRect(nullptr) ? SK_ColorRED : path.isOval(nullptr) ? SK_ColorBLUE : path.isConvex() ? SK_ColorGRAY : SK_ColorGREEN); canvas->drawPath(path, paint); canvas->translate(64, 0); } canvas->translate(-256, 64); } }
AAClipBench(bool doPath, bool doAA) : fDoPath(doPath) , fDoAA(doAA) { fName.printf("aaclip_%s_%s", doPath ? "path" : "rect", doAA ? "AA" : "BW"); fClipRect.set(10.5f, 10.5f, 50.5f, 50.5f); fClipPath.addRoundRect(fClipRect, SkIntToScalar(10), SkIntToScalar(10)); fDrawRect.set(SkIntToScalar(0), SkIntToScalar(0), SkIntToScalar(100), SkIntToScalar(100)); SkASSERT(fClipPath.isConvex()); }
// Creates a star type shape using a SkPath static SkPath create_star() { static const int kNumPoints = 5; SkPath concavePath; SkPoint points[kNumPoints] = {{0, SkIntToScalar(-50)} }; SkMatrix rot; rot.setRotate(SkIntToScalar(360) / kNumPoints); for (int i = 1; i < kNumPoints; ++i) { rot.mapPoints(points + i, points + i - 1, 1); } concavePath.moveTo(points[0]); for (int i = 0; i < kNumPoints; ++i) { concavePath.lineTo(points[(2 * i) % kNumPoints]); } concavePath.setFillType(SkPath::kEvenOdd_FillType); SkASSERT(!concavePath.isConvex()); concavePath.close(); return concavePath; }
void recurse(SkCanvas* canvas, int depth, const SkPoint& offset) { canvas->save(); SkRect temp = SkRect::MakeLTRB(0, 0, fSizes[depth].fX, fSizes[depth].fY); temp.offset(offset); SkPath path; path.addRoundRect(temp, SkIntToScalar(3), SkIntToScalar(3)); SkASSERT(path.isConvex()); canvas->clipPath(path, 0 == depth ? SkRegion::kReplace_Op : SkRegion::kIntersect_Op, fDoAA); if (kNestingDepth == depth) { // we only draw the draw rect at the lowest nesting level SkPaint paint; paint.setColor(0xff000000 | fRandom.nextU()); canvas->drawRect(fDrawRect, paint); } else { SkPoint childOffset = offset; this->recurse(canvas, depth+1, childOffset); childOffset += fSizes[depth+1]; this->recurse(canvas, depth+1, childOffset); childOffset.fX = offset.fX + fSizes[depth+1].fX; childOffset.fY = offset.fY; this->recurse(canvas, depth+1, childOffset); childOffset.fX = offset.fX; childOffset.fY = offset.fY + fSizes[depth+1].fY; this->recurse(canvas, depth+1, childOffset); } canvas->restore(); }
bool GrAALinearizingConvexPathRenderer::canDrawPath(const GrDrawTarget* target, const GrPipelineBuilder*, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) const { if (!antiAlias) { return false; } if (path.isInverseFillType()) { return false; } if (!path.isConvex()) { return false; } if (stroke.getStyle() == SkStrokeRec::kStroke_Style) { return viewMatrix.isSimilarity() && stroke.getWidth() >= 1.0f && stroke.getWidth() <= kMaxStrokeWidth && !stroke.isDashed() && SkPathPriv::LastVerbIsClose(path) && stroke.getJoin() != SkPaint::Join::kRound_Join; } return stroke.getStyle() == SkStrokeRec::kFill_Style; }
static GrConvexHint getConvexHint(const SkPath& path) { return path.isConvex() ? kConvex_ConvexHint : kConcave_ConvexHint; }
// clipRect has not been shifted up void sk_fill_path(const SkPath& path, const SkIRect& clipRect, SkBlitter* blitter, int start_y, int stop_y, int shiftEdgesUp, bool pathContainedInClip) { SkASSERT(blitter); SkIRect shiftedClip = clipRect; shiftedClip.fLeft = SkLeftShift(shiftedClip.fLeft, shiftEdgesUp); shiftedClip.fRight = SkLeftShift(shiftedClip.fRight, shiftEdgesUp); shiftedClip.fTop = SkLeftShift(shiftedClip.fTop, shiftEdgesUp); shiftedClip.fBottom = SkLeftShift(shiftedClip.fBottom, shiftEdgesUp); SkEdgeBuilder builder; int count = builder.build_edges(path, &shiftedClip, shiftEdgesUp, pathContainedInClip); SkEdge** list = builder.edgeList(); if (0 == count) { if (path.isInverseFillType()) { /* * Since we are in inverse-fill, our caller has already drawn above * our top (start_y) and will draw below our bottom (stop_y). Thus * we need to restrict our drawing to the intersection of the clip * and those two limits. */ SkIRect rect = clipRect; if (rect.fTop < start_y) { rect.fTop = start_y; } if (rect.fBottom > stop_y) { rect.fBottom = stop_y; } if (!rect.isEmpty()) { blitter->blitRect(rect.fLeft << shiftEdgesUp, rect.fTop << shiftEdgesUp, rect.width() << shiftEdgesUp, rect.height() << shiftEdgesUp); } } return; } SkEdge headEdge, tailEdge, *last; // this returns the first and last edge after they're sorted into a dlink list SkEdge* edge = sort_edges(list, count, &last); headEdge.fPrev = nullptr; headEdge.fNext = edge; headEdge.fFirstY = kEDGE_HEAD_Y; headEdge.fX = SK_MinS32; edge->fPrev = &headEdge; tailEdge.fPrev = last; tailEdge.fNext = nullptr; tailEdge.fFirstY = kEDGE_TAIL_Y; last->fNext = &tailEdge; // now edge is the head of the sorted linklist start_y = SkLeftShift(start_y, shiftEdgesUp); stop_y = SkLeftShift(stop_y, shiftEdgesUp); if (!pathContainedInClip && start_y < shiftedClip.fTop) { start_y = shiftedClip.fTop; } if (!pathContainedInClip && stop_y > shiftedClip.fBottom) { stop_y = shiftedClip.fBottom; } InverseBlitter ib; PrePostProc proc = nullptr; if (path.isInverseFillType()) { ib.setBlitter(blitter, clipRect, shiftEdgesUp); blitter = &ib; proc = PrePostInverseBlitterProc; } // count >= 2 is required as the convex walker does not handle missing right edges if (path.isConvex() && (nullptr == proc) && count >= 2) { walk_simple_edges(&headEdge, blitter, start_y, stop_y); } else { walk_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, proc, shiftedClip.right()); } }
/* OPTIMIZATION: Union doesn't need to be all-or-nothing. A run of three or more convex paths with union ops could be locally resolved and still improve over doing the ops one at a time. */ bool SkOpBuilder::resolve(SkPath* result) { SkPath original = *result; int count = fOps.count(); bool allUnion = true; SkPathPriv::FirstDirection firstDir = SkPathPriv::kUnknown_FirstDirection; for (int index = 0; index < count; ++index) { SkPath* test = &fPathRefs[index]; if (kUnion_SkPathOp != fOps[index] || test->isInverseFillType()) { allUnion = false; break; } // If all paths are convex, track direction, reversing as needed. if (test->isConvex()) { SkPathPriv::FirstDirection dir; if (!SkPathPriv::CheapComputeFirstDirection(*test, &dir)) { allUnion = false; break; } if (firstDir == SkPathPriv::kUnknown_FirstDirection) { firstDir = dir; } else if (firstDir != dir) { SkPath temp; temp.reverseAddPath(*test); *test = temp; } continue; } // If the path is not convex but its bounds do not intersect the others, simplify is enough. const SkRect& testBounds = test->getBounds(); for (int inner = 0; inner < index; ++inner) { // OPTIMIZE: check to see if the contour bounds do not intersect other contour bounds? if (SkRect::Intersects(fPathRefs[inner].getBounds(), testBounds)) { allUnion = false; break; } } } if (!allUnion) { *result = fPathRefs[0]; for (int index = 1; index < count; ++index) { if (!Op(*result, fPathRefs[index], fOps[index], result)) { reset(); *result = original; return false; } } reset(); return true; } SkPath sum; for (int index = 0; index < count; ++index) { if (!Simplify(fPathRefs[index], &fPathRefs[index])) { reset(); *result = original; return false; } if (!fPathRefs[index].isEmpty()) { // convert the even odd result back to winding form before accumulating it if (!FixWinding(&fPathRefs[index])) { *result = original; return false; } sum.addPath(fPathRefs[index]); } } reset(); bool success = Simplify(sum, result); if (!success) { *result = original; } return success; }
bool GrTesselatedPathRenderer::onDrawPath(const SkPath& path, GrPathFill fill, const GrVec* translate, GrDrawTarget* target, GrDrawState::StageMask stageMask, bool antiAlias) { GrDrawTarget::AutoStateRestore asr(target); GrDrawState* drawState = target->drawState(); // face culling doesn't make sense here GrAssert(GrDrawState::kBoth_DrawFace == drawState->getDrawFace()); GrMatrix viewM = drawState->getViewMatrix(); GrScalar tol = GR_Scalar1; tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, path.getBounds()); GrScalar tolSqd = GrMul(tol, tol); int subpathCnt; int maxPts = GrPathUtils::worstCasePointCount(path, &subpathCnt, tol); GrVertexLayout layout = 0; for (int s = 0; s < GrDrawState::kNumStages; ++s) { if ((1 << s) & stageMask) { layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s); } } bool inverted = GrIsFillInverted(fill); if (inverted) { maxPts += 4; subpathCnt++; } if (maxPts > USHRT_MAX) { return false; } SkAutoSTMalloc<8, GrPoint> baseMem(maxPts); GrPoint* base = baseMem; GrPoint* vert = base; GrPoint* subpathBase = base; SkAutoSTMalloc<8, uint16_t> subpathVertCount(subpathCnt); GrPoint pts[4]; SkPath::Iter iter(path, false); bool first = true; int subpath = 0; for (;;) { switch (iter.next(pts)) { case kMove_PathCmd: if (!first) { subpathVertCount[subpath] = vert-subpathBase; subpathBase = vert; ++subpath; } *vert = pts[0]; vert++; break; case kLine_PathCmd: *vert = pts[1]; vert++; break; case kQuadratic_PathCmd: { GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2], tolSqd, &vert, GrPathUtils::quadraticPointCount(pts, tol)); break; } case kCubic_PathCmd: { GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3], tolSqd, &vert, GrPathUtils::cubicPointCount(pts, tol)); break; } case kClose_PathCmd: break; case kEnd_PathCmd: subpathVertCount[subpath] = vert-subpathBase; ++subpath; // this could be only in debug goto FINISHED; } first = false; } FINISHED: if (NULL != translate && 0 != translate->fX && 0 != translate->fY) { for (int i = 0; i < vert - base; i++) { base[i].offset(translate->fX, translate->fY); } } if (inverted) { GrRect bounds; GrAssert(NULL != drawState->getRenderTarget()); bounds.setLTRB(0, 0, GrIntToScalar(drawState->getRenderTarget()->width()), GrIntToScalar(drawState->getRenderTarget()->height())); GrMatrix vmi; if (drawState->getViewInverse(&vmi)) { vmi.mapRect(&bounds); } *vert++ = GrPoint::Make(bounds.fLeft, bounds.fTop); *vert++ = GrPoint::Make(bounds.fLeft, bounds.fBottom); *vert++ = GrPoint::Make(bounds.fRight, bounds.fBottom); *vert++ = GrPoint::Make(bounds.fRight, bounds.fTop); subpathVertCount[subpath++] = 4; } GrAssert(subpath == subpathCnt); GrAssert((vert - base) <= maxPts); size_t count = vert - base; if (count < 3) { return true; } if (subpathCnt == 1 && !inverted && path.isConvex()) { if (antiAlias) { GrEdgeArray edges; GrMatrix inverse, matrix = drawState->getViewMatrix(); drawState->getViewInverse(&inverse); count = computeEdgesAndIntersect(matrix, inverse, base, count, &edges, 0.0f); size_t maxEdges = target->getMaxEdges(); if (count == 0) { return true; } if (count <= maxEdges) { // All edges fit; upload all edges and draw all verts as a fan target->setVertexSourceToArray(layout, base, count); drawState->setEdgeAAData(&edges[0], count); target->drawNonIndexed(kTriangleFan_PrimitiveType, 0, count); } else { // Upload "maxEdges" edges and verts at a time, and draw as // separate fans for (size_t i = 0; i < count - 2; i += maxEdges - 2) { edges[i] = edges[0]; base[i] = base[0]; int size = GR_CT_MIN(count - i, maxEdges); target->setVertexSourceToArray(layout, &base[i], size); drawState->setEdgeAAData(&edges[i], size); target->drawNonIndexed(kTriangleFan_PrimitiveType, 0, size); } } drawState->setEdgeAAData(NULL, 0); } else { target->setVertexSourceToArray(layout, base, count); target->drawNonIndexed(kTriangleFan_PrimitiveType, 0, count); } return true; } if (antiAlias) { // Run the tesselator once to get the boundaries. GrBoundaryTess btess(count, fill_type_to_glu_winding_rule(fill)); btess.addVertices(base, subpathVertCount, subpathCnt); GrMatrix inverse, matrix = drawState->getViewMatrix(); if (!drawState->getViewInverse(&inverse)) { return false; } if (btess.vertices().count() > USHRT_MAX) { return false; } // Inflate the boundary, and run the tesselator again to generate // interior polys. const GrPointArray& contourPoints = btess.contourPoints(); const GrIndexArray& contours = btess.contours(); GrEdgePolygonTess ptess(contourPoints.count(), GLU_TESS_WINDING_NONZERO, matrix); size_t i = 0; Sk_gluTessBeginPolygon(ptess.tess(), &ptess); for (int contour = 0; contour < contours.count(); ++contour) { int count = contours[contour]; GrEdgeArray edges; int newCount = computeEdgesAndIntersect(matrix, inverse, &btess.contourPoints()[i], count, &edges, 1.0f); Sk_gluTessBeginContour(ptess.tess()); for (int j = 0; j < newCount; j++) { ptess.addVertex(contourPoints[i + j], ptess.vertices().count()); } i += count; Sk_gluTessEndContour(ptess.tess()); } Sk_gluTessEndPolygon(ptess.tess()); if (ptess.vertices().count() > USHRT_MAX) { return false; } // Draw the resulting polys and upload their edge data. drawState->enableState(GrDrawState::kEdgeAAConcave_StateBit); const GrPointArray& vertices = ptess.vertices(); const GrIndexArray& indices = ptess.indices(); const GrDrawState::Edge* edges = ptess.edges(); GR_DEBUGASSERT(indices.count() % 3 == 0); for (int i = 0; i < indices.count(); i += 3) { GrPoint tri_verts[3]; int index0 = indices[i]; int index1 = indices[i + 1]; int index2 = indices[i + 2]; tri_verts[0] = vertices[index0]; tri_verts[1] = vertices[index1]; tri_verts[2] = vertices[index2]; GrDrawState::Edge tri_edges[6]; int t = 0; const GrDrawState::Edge& edge0 = edges[index0 * 2]; const GrDrawState::Edge& edge1 = edges[index0 * 2 + 1]; const GrDrawState::Edge& edge2 = edges[index1 * 2]; const GrDrawState::Edge& edge3 = edges[index1 * 2 + 1]; const GrDrawState::Edge& edge4 = edges[index2 * 2]; const GrDrawState::Edge& edge5 = edges[index2 * 2 + 1]; if (validEdge(edge0) && validEdge(edge1)) { tri_edges[t++] = edge0; tri_edges[t++] = edge1; } if (validEdge(edge2) && validEdge(edge3)) { tri_edges[t++] = edge2; tri_edges[t++] = edge3; } if (validEdge(edge4) && validEdge(edge5)) { tri_edges[t++] = edge4; tri_edges[t++] = edge5; } drawState->setEdgeAAData(&tri_edges[0], t); target->setVertexSourceToArray(layout, &tri_verts[0], 3); target->drawNonIndexed(kTriangles_PrimitiveType, 0, 3); } drawState->setEdgeAAData(NULL, 0); drawState->disableState(GrDrawState::kEdgeAAConcave_StateBit); return true; } GrPolygonTess ptess(count, fill_type_to_glu_winding_rule(fill)); ptess.addVertices(base, subpathVertCount, subpathCnt); const GrPointArray& vertices = ptess.vertices(); const GrIndexArray& indices = ptess.indices(); if (indices.count() > 0) { target->setVertexSourceToArray(layout, vertices.begin(), vertices.count()); target->setIndexSourceToArray(indices.begin(), indices.count()); target->drawIndexed(kTriangles_PrimitiveType, 0, 0, vertices.count(), indices.count()); } return true; }
static void test_gpu_veto(skiatest::Reporter* reporter) { SkPictureRecorder recorder; SkCanvas* canvas = recorder.beginRecording(100, 100); { SkPath path; path.moveTo(0, 0); path.lineTo(50, 50); SkScalar intervals[] = { 1.0f, 1.0f }; sk_sp<SkPathEffect> dash(SkDashPathEffect::Make(intervals, 2, 0)); SkPaint paint; paint.setStyle(SkPaint::kStroke_Style); paint.setPathEffect(dash); for (int i = 0; i < 50; ++i) { canvas->drawPath(path, paint); } } sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); // path effects currently render an SkPicture undesireable for GPU rendering const char *reason = nullptr; REPORTER_ASSERT(reporter, !picture->suitableForGpuRasterization(nullptr, &reason)); REPORTER_ASSERT(reporter, reason); canvas = recorder.beginRecording(100, 100); { SkPath path; path.moveTo(0, 0); path.lineTo(0, 50); path.lineTo(25, 25); path.lineTo(50, 50); path.lineTo(50, 0); path.close(); REPORTER_ASSERT(reporter, !path.isConvex()); SkPaint paint; paint.setAntiAlias(true); for (int i = 0; i < 50; ++i) { canvas->drawPath(path, paint); } } picture = recorder.finishRecordingAsPicture(); // A lot of small AA concave paths should be fine for GPU rendering REPORTER_ASSERT(reporter, picture->suitableForGpuRasterization(nullptr)); canvas = recorder.beginRecording(100, 100); { SkPath path; path.moveTo(0, 0); path.lineTo(0, 100); path.lineTo(50, 50); path.lineTo(100, 100); path.lineTo(100, 0); path.close(); REPORTER_ASSERT(reporter, !path.isConvex()); SkPaint paint; paint.setAntiAlias(true); for (int i = 0; i < 50; ++i) { canvas->drawPath(path, paint); } } picture = recorder.finishRecordingAsPicture(); // A lot of large AA concave paths currently render an SkPicture undesireable for GPU rendering REPORTER_ASSERT(reporter, !picture->suitableForGpuRasterization(nullptr)); canvas = recorder.beginRecording(100, 100); { SkPath path; path.moveTo(0, 0); path.lineTo(0, 50); path.lineTo(25, 25); path.lineTo(50, 50); path.lineTo(50, 0); path.close(); REPORTER_ASSERT(reporter, !path.isConvex()); SkPaint paint; paint.setAntiAlias(true); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(0); for (int i = 0; i < 50; ++i) { canvas->drawPath(path, paint); } } picture = recorder.finishRecordingAsPicture(); // hairline stroked AA concave paths are fine for GPU rendering REPORTER_ASSERT(reporter, picture->suitableForGpuRasterization(nullptr)); canvas = recorder.beginRecording(100, 100); { SkPaint paint; SkScalar intervals [] = { 10, 20 }; paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25)); SkPoint points [2] = { { 0, 0 }, { 100, 0 } }; for (int i = 0; i < 50; ++i) { canvas->drawPoints(SkCanvas::kLines_PointMode, 2, points, paint); } } picture = recorder.finishRecordingAsPicture(); // fast-path dashed effects are fine for GPU rendering ... REPORTER_ASSERT(reporter, picture->suitableForGpuRasterization(nullptr)); canvas = recorder.beginRecording(100, 100); { SkPaint paint; SkScalar intervals [] = { 10, 20 }; paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25)); for (int i = 0; i < 50; ++i) { canvas->drawRect(SkRect::MakeWH(10, 10), paint); } } picture = recorder.finishRecordingAsPicture(); // ... but only when applied to drawPoint() calls REPORTER_ASSERT(reporter, !picture->suitableForGpuRasterization(nullptr)); // Nest the previous picture inside a new one. canvas = recorder.beginRecording(100, 100); { canvas->drawPicture(picture.get()); } picture = recorder.finishRecordingAsPicture(); REPORTER_ASSERT(reporter, !picture->suitableForGpuRasterization(nullptr)); }
static inline bool single_pass_path(const SkPath& path, const SkStrokeRec& stroke) { if (!path.isInverseFillType()) { return path.isConvex(); } return false; }
bool GrAAConvexPathRenderer::canDrawPath(const SkPath& path, GrPathFill fill, const GrDrawTarget* target, bool antiAlias) const { return staticCanDrawPath(path.isConvex(), fill, target, antiAlias); }