void SkScan::FillPath(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter) { if (origClip.isEmpty()) { return; } // Our edges are fixed-point, and don't like the bounds of the clip to // exceed that. Here we trim the clip just so we don't overflow later on const SkRegion* clipPtr = &origClip; SkRegion finiteClip; if (clip_to_limit(origClip, &finiteClip)) { if (finiteClip.isEmpty()) { return; } clipPtr = &finiteClip; } // don't reference "origClip" any more, just use clipPtr SkRect bounds = path.getBounds(); bool irPreClipped = false; if (!SkRectPriv::MakeLargeS32().contains(bounds)) { if (!bounds.intersect(SkRectPriv::MakeLargeS32())) { bounds.setEmpty(); } irPreClipped = true; } SkIRect ir = conservative_round_to_int(bounds); if (ir.isEmpty()) { if (path.isInverseFillType()) { blitter->blitRegion(*clipPtr); } return; } SkScanClipper clipper(blitter, clipPtr, ir, path.isInverseFillType(), irPreClipped); blitter = clipper.getBlitter(); if (blitter) { // we have to keep our calls to blitter in sorted order, so we // must blit the above section first, then the middle, then the bottom. if (path.isInverseFillType()) { sk_blit_above(blitter, ir, *clipPtr); } SkASSERT(clipper.getClipRect() == nullptr || *clipper.getClipRect() == clipPtr->getBounds()); sk_fill_path(path, clipPtr->getBounds(), blitter, ir.fTop, ir.fBottom, 0, clipper.getClipRect() == nullptr); if (path.isInverseFillType()) { sk_blit_below(blitter, ir, *clipPtr); } } else { // what does it mean to not have a blitter if path.isInverseFillType??? } }
bool GrClipMaskManager::canStencilAndDrawElement(GrPipelineBuilder* pipelineBuilder, GrTexture* target, GrPathRenderer** pr, const SkClipStack::Element* element) { pipelineBuilder->setRenderTarget(target->asRenderTarget()); if (Element::kRect_Type == element->getType()) { return true; } else { // We shouldn't get here with an empty clip element. SkASSERT(Element::kEmpty_Type != element->getType()); SkPath path; element->asPath(&path); if (path.isInverseFillType()) { path.toggleInverseFillType(); } GrStrokeInfo stroke(SkStrokeRec::kFill_InitStyle); GrPathRendererChain::DrawType type = element->isAA() ? GrPathRendererChain::kStencilAndColorAntiAlias_DrawType : GrPathRendererChain::kStencilAndColor_DrawType; *pr = this->getContext()->getPathRenderer(fDrawTarget, pipelineBuilder, SkMatrix::I(), path, stroke, false, type); return SkToBool(*pr); } }
bool GrClipMaskManager::drawElement(GrPipelineBuilder* pipelineBuilder, const SkMatrix& viewMatrix, GrTexture* target, const SkClipStack::Element* element, GrPathRenderer* pr) { GrDrawTarget::AutoGeometryPush agp(fClipTarget); pipelineBuilder->setRenderTarget(target->asRenderTarget()); // The color we use to draw does not matter since we will always be using a GrCoverageSetOpXP // which ignores color. GrColor color = GrColor_WHITE; // TODO: Draw rrects directly here. switch (element->getType()) { case Element::kEmpty_Type: SkDEBUGFAIL("Should never get here with an empty element."); break; case Element::kRect_Type: // TODO: Do rects directly to the accumulator using a aa-rect GrProcessor that covers // the entire mask bounds and writes 0 outside the rect. if (element->isAA()) { SkRect devRect = element->getRect(); viewMatrix.mapRect(&devRect); this->getContext()->getAARectRenderer()->fillAARect(fClipTarget, pipelineBuilder, color, viewMatrix, element->getRect(), devRect); } else { fClipTarget->drawSimpleRect(pipelineBuilder, color, viewMatrix, element->getRect()); } return true; default: { SkPath path; element->asPath(&path); path.setIsVolatile(true); if (path.isInverseFillType()) { path.toggleInverseFillType(); } SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle); if (NULL == pr) { GrPathRendererChain::DrawType type; type = element->isAA() ? GrPathRendererChain::kColorAntiAlias_DrawType : GrPathRendererChain::kColor_DrawType; pr = this->getContext()->getPathRenderer(fClipTarget, pipelineBuilder, viewMatrix, path, stroke, false, type); } if (NULL == pr) { return false; } pr->drawPath(fClipTarget, pipelineBuilder, color, viewMatrix, path, stroke, element->isAA()); break; } } return true; }
bool GrStrokePathRenderer::canDrawPath(const SkPath& path, const SkStrokeRec& stroke, const GrDrawTarget* target, bool antiAlias) const { // FIXME : put the proper condition once GrDrawTarget::isOpaque is implemented const bool isOpaque = true; // target->isOpaque(); // FIXME : remove this requirement once we have AA circles and implement the // circle joins/caps appropriately in the ::onDrawPath() function. const bool requiresAACircle = (stroke.getCap() == SkPaint::kRound_Cap) || (stroke.getJoin() == SkPaint::kRound_Join); // Indices being stored in uint16, we don't want to overflow the indices capacity static const int maxVBSize = 1 << 16; const int maxNbVerts = (path.countPoints() + 1) * 5; // Check that the path contains no curved lines, only straight lines static const uint32_t unsupportedMask = SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask; // Must not be filled nor hairline nor semi-transparent // Note : May require a check to path.isConvex() if AA is supported return ((stroke.getStyle() == SkStrokeRec::kStroke_Style) && (maxNbVerts < maxVBSize) && !path.isInverseFillType() && isOpaque && !requiresAACircle && !antiAlias && ((path.getSegmentMasks() & unsupportedMask) == 0)); }
static bool stencil_element(GrDrawContext* dc, const GrFixedClip& clip, const GrUserStencilSettings* ss, const SkMatrix& viewMatrix, const SkClipStack::Element* element) { // TODO: Draw rrects directly here. switch (element->getType()) { case Element::kEmpty_Type: SkDEBUGFAIL("Should never get here with an empty element."); break; case Element::kRect_Type: return dc->drawContextPriv().drawAndStencilRect(clip, ss, element->getOp(), element->isInverseFilled(), element->isAA(), viewMatrix, element->getRect()); break; default: { SkPath path; element->asPath(&path); if (path.isInverseFillType()) { path.toggleInverseFillType(); } return dc->drawContextPriv().drawAndStencilPath(clip, ss, element->getOp(), element->isInverseFilled(), element->isAA(), viewMatrix, path); break; } } return false; }
static void draw_element(GrDrawContext* dc, const GrClip& clip, // TODO: can this just always be WideOpen? const GrPaint &paint, const SkMatrix& viewMatrix, const SkClipStack::Element* element) { // TODO: Draw rrects directly here. switch (element->getType()) { case Element::kEmpty_Type: SkDEBUGFAIL("Should never get here with an empty element."); break; case Element::kRect_Type: dc->drawRect(clip, paint, viewMatrix, element->getRect()); break; default: { SkPath path; element->asPath(&path); if (path.isInverseFillType()) { path.toggleInverseFillType(); } dc->drawPath(clip, paint, viewMatrix, path, GrStyle::SimpleFill()); break; } } }
void GrStencilAndCoverPathRenderer::onStencilPath(const SkPath& path, const SkStrokeRec& stroke, GrDrawTarget* target) { SkASSERT(!path.isInverseFillType()); SkAutoTUnref<GrPath> p(fGpu->createPath(path)); target->stencilPath(p, stroke, path.getFillType()); }
bool SkRasterClip::op(const SkPath& path, const SkMatrix& matrix, const SkIRect& bounds, SkRegion::Op op, bool doAA) { AUTO_RASTERCLIP_VALIDATE(*this); if (fForceConservativeRects) { SkIRect ir; switch (mutate_conservative_op(&op, path.isInverseFillType())) { case kDoNothing_MutateResult: return !this->isEmpty(); case kReplaceClippedAgainstGlobalBounds_MutateResult: ir = bounds; break; case kContinue_MutateResult: { SkRect bounds = path.getBounds(); matrix.mapRect(&bounds); ir = bounds.roundOut(); break; } } return this->op(ir, op); } // base is used to limit the size (and therefore memory allocation) of the // region that results from scan converting devPath. SkRegion base; SkPath devPath; if (matrix.isIdentity()) { devPath = path; } else { path.transform(matrix, &devPath); devPath.setIsVolatile(true); } if (SkRegion::kIntersect_Op == op) { // since we are intersect, we can do better (tighter) with currRgn's // bounds, than just using the device. However, if currRgn is complex, // our region blitter may hork, so we do that case in two steps. if (this->isRect()) { // FIXME: we should also be able to do this when this->isBW(), // but relaxing the test above triggers GM asserts in // SkRgnBuilder::blitH(). We need to investigate what's going on. return this->setPath(devPath, this->bwRgn(), doAA); } else { base.setRect(this->getBounds()); SkRasterClip clip(fForceConservativeRects); clip.setPath(devPath, base, doAA); return this->op(clip, op); } } else { base.setRect(bounds); if (SkRegion::kReplace_Op == op) { return this->setPath(devPath, base, doAA); } else { SkRasterClip clip(fForceConservativeRects); clip.setPath(devPath, base, doAA); return this->op(clip, op); } } }
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 GrAADistanceFieldPathRenderer::canDrawPath(const GrDrawTarget* target, const GrPipelineBuilder* pipelineBuilder, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) const { // TODO: Support inverse fill // TODO: Support strokes if (!target->caps()->shaderCaps()->shaderDerivativeSupport() || !antiAlias || path.isInverseFillType() || path.isVolatile() || !stroke.isFillStyle()) { return false; } // currently don't support perspective if (viewMatrix.hasPerspective()) { return false; } // only support paths smaller than 64x64, scaled to less than 256x256 // the goal is to accelerate rendering of lots of small paths that may be scaling SkScalar maxScale = viewMatrix.getMaxScale(); const SkRect& bounds = path.getBounds(); SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height()); return maxDim < 64.f && maxDim * maxScale < 256.f; }
void GrStencilAndCoverPathRenderer::onStencilPath(const SkPath& path, const SkStrokeRec& stroke, GrDrawTarget* target) { SkASSERT(!path.isInverseFillType()); SkAutoTUnref<GrPath> p(get_gr_path(fGpu, path, stroke)); target->stencilPath(p, convert_skpath_filltype(path.getFillType())); }
/* Two invariants are tested: How does an empty/degenerate path draw? * - if the path is drawn inverse, it should draw everywhere * - if the path is drawn non-inverse, it should draw nowhere * * Things to iterate on: * - path (empty, degenerate line/quad/cubic w/ and w/o close * - paint style * - path filltype * - path stroke variants (e.g. caps, joins, width) */ static void test_emptydrawing(skiatest::Reporter* reporter) { static void (*gMakeProc[])(SkPath*) = { make_empty, make_M, make_MM, make_MZM, make_L, make_Q, make_C }; static SkPath::FillType gFills[] = { SkPath::kWinding_FillType, SkPath::kEvenOdd_FillType, SkPath::kInverseWinding_FillType, SkPath::kInverseEvenOdd_FillType }; for (int doClose = 0; doClose < 2; ++doClose) { for (size_t i = 0; i < SK_ARRAY_COUNT(gMakeProc); ++i) { SkPath path; gMakeProc[i](&path); if (doClose) { path.close(); } for (size_t fill = 0; fill < SK_ARRAY_COUNT(gFills); ++fill) { path.setFillType(gFills[fill]); bool shouldDraw = path.isInverseFillType(); iter_paint(reporter, path, shouldDraw); } } } }
TessellatingPathBatch(const GrColor& color, const SkPath& path, const GrStrokeInfo& stroke, const SkMatrix& viewMatrix, const SkRect& clipBounds) : INHERITED(ClassID()) , fColor(color) , fPath(path) , fStroke(stroke) , fViewMatrix(viewMatrix) { const SkRect& pathBounds = path.getBounds(); fClipBounds = clipBounds; // Because the clip bounds are used to add a contour for inverse fills, they must also // include the path bounds. fClipBounds.join(pathBounds); if (path.isInverseFillType()) { fBounds = fClipBounds; } else { fBounds = path.getBounds(); } if (!stroke.isFillStyle()) { SkScalar radius = SkScalarHalf(stroke.getWidth()); if (stroke.getJoin() == SkPaint::kMiter_Join) { SkScalar scale = stroke.getMiter(); if (scale > SK_Scalar1) { radius = SkScalarMul(radius, scale); } } fBounds.outset(radius, radius); } viewMatrix.mapRect(&fBounds); }
bool SkHitTestPath(const SkPath& path, SkRect& target, bool hires) { if (target.isEmpty()) { return false; } bool isInverse = path.isInverseFillType(); if (path.isEmpty()) { return isInverse; } SkRect bounds = path.getBounds(); bool sects = SkRect::Intersects(target, bounds); if (isInverse) { if (!sects) { return true; } } else { if (!sects) { return false; } if (target.contains(bounds)) { return true; } } SkPath devPath; const SkPath* pathPtr; SkRect devTarget; if (hires) { const SkScalar coordLimit = SkIntToScalar(16384); const SkRect limit = { 0, 0, coordLimit, coordLimit }; SkMatrix matrix; matrix.setRectToRect(bounds, limit, SkMatrix::kFill_ScaleToFit); path.transform(matrix, &devPath); matrix.mapRect(&devTarget, target); pathPtr = &devPath; } else { devTarget = target; pathPtr = &path; } SkIRect iTarget; devTarget.round(&iTarget); if (iTarget.isEmpty()) { iTarget.fLeft = SkScalarFloorToInt(devTarget.fLeft); iTarget.fTop = SkScalarFloorToInt(devTarget.fTop); iTarget.fRight = iTarget.fLeft + 1; iTarget.fBottom = iTarget.fTop + 1; } SkRegion clip(iTarget); SkRegion rgn; return rgn.setPath(*pathPtr, clip) ^ isInverse; }
bool GrPLSPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { // We have support for even-odd rendering, but are having some troublesome // seams. Disable in the presence of even-odd for now. SkPath path; args.fShape->asPath(&path); return args.fShaderCaps->shaderDerivativeSupport() && args.fAntiAlias && args.fShape->style().isSimpleFill() && !path.isInverseFillType() && path.getFillType() == SkPath::FillType::kWinding_FillType; }
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()); }
void SkDeferredCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) { if (path.isInverseFillType()) { this->flush_before_saves(); } else { SkRect modRect = path.getBounds(); this->flush_check(&modRect, &paint, kNoClip_Flag | kNoTranslate_Flag | kNoScale_Flag); } fCanvas->drawPath(path, paint); }
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 }
void GrStencilAndCoverPathRenderer::onStencilPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke) { SkASSERT(!path.isInverseFillType()); SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(GrColor_WHITE, viewMatrix)); SkAutoTUnref<GrPath> p(get_gr_path(fGpu, path, stroke.getStrokeRec())); target->stencilPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType())); }
// Does the path in 'element' require SW rendering? If so, return true (and, // optionally, set 'prOut' to NULL. If not, return false (and, optionally, set // 'prOut' to the non-SW path renderer that will do the job). bool GrClipMaskManager::PathNeedsSWRenderer(GrContext* context, bool isStencilDisabled, const GrRenderTarget* rt, const SkMatrix& viewMatrix, const Element* element, GrPathRenderer** prOut, bool needsStencil) { if (Element::kRect_Type == element->getType()) { // rects can always be drawn directly w/o using the software path // TODO: skip rrects once we're drawing them directly. if (prOut) { *prOut = nullptr; } return false; } else { // We shouldn't get here with an empty clip element. SkASSERT(Element::kEmpty_Type != element->getType()); // the gpu alpha mask will draw the inverse paths as non-inverse to a temp buffer SkPath path; element->asPath(&path); if (path.isInverseFillType()) { path.toggleInverseFillType(); } GrStrokeInfo stroke(SkStrokeRec::kFill_InitStyle); GrPathRendererChain::DrawType type; if (needsStencil) { type = element->isAA() ? GrPathRendererChain::kStencilAndColorAntiAlias_DrawType : GrPathRendererChain::kStencilAndColor_DrawType; } else { type = element->isAA() ? GrPathRendererChain::kColorAntiAlias_DrawType : GrPathRendererChain::kColor_DrawType; } GrPathRenderer::CanDrawPathArgs canDrawArgs; canDrawArgs.fShaderCaps = context->caps()->shaderCaps(); canDrawArgs.fViewMatrix = &viewMatrix; canDrawArgs.fPath = &path; canDrawArgs.fStroke = &stroke; canDrawArgs.fAntiAlias = element->isAA(); canDrawArgs.fIsStencilDisabled = isStencilDisabled; canDrawArgs.fIsStencilBufferMSAA = rt->isStencilBufferMultisampled(); // the 'false' parameter disallows use of the SW path renderer GrPathRenderer* pr = context->drawingManager()->getPathRenderer(canDrawArgs, false, type); if (prOut) { *prOut = pr; } return SkToBool(!pr); } }
void GrPathRenderer::GetPathDevBounds(const SkPath& path, int devW, int devH, const SkMatrix& matrix, SkRect* bounds) { if (path.isInverseFillType()) { *bounds = SkRect::MakeWH(SkIntToScalar(devW), SkIntToScalar(devH)); return; } *bounds = path.getBounds(); matrix.mapRect(bounds); }
//////////////////////////////////////////////////////////////////////////////// // return true on success; false on failure bool GrSoftwarePathRenderer::onDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) { if (NULL == fContext) { return false; } SkIRect devPathBounds, devClipBounds; if (!get_path_and_clip_bounds(target, pipelineBuilder, path, viewMatrix, &devPathBounds, &devClipBounds)) { if (path.isInverseFillType()) { draw_around_inv_path(target, pipelineBuilder, color, viewMatrix, devClipBounds, devPathBounds); } return true; } SkAutoTUnref<GrTexture> texture( GrSWMaskHelper::DrawPathMaskToTexture(fContext, path, stroke, devPathBounds, antiAlias, &viewMatrix)); if (NULL == texture) { return false; } GrPipelineBuilder copy = *pipelineBuilder; GrSWMaskHelper::DrawToTargetWithPathMask(texture, target, ©, color, viewMatrix, devPathBounds); if (path.isInverseFillType()) { draw_around_inv_path(target, pipelineBuilder, color, viewMatrix, devClipBounds, devPathBounds); } return true; }
void SkBBoxRecord::drawPath(const SkPath& path, const SkPaint& paint) { if (path.isInverseFillType()) { // If path is inverse filled, use the current clip bounds as the // path's device-space bounding box. SkIRect clipBounds; if (this->getClipDeviceBounds(&clipBounds)) { this->handleBBox(SkRect::Make(clipBounds)); INHERITED::drawPath(path, paint); } } else if (this->transformBounds(path.getBounds(), &paint)) { INHERITED::drawPath(path, paint); } }
//////////////////////////////////////////////////////////////////////////////// // return true on success; false on failure bool GrSoftwarePathRenderer::onDrawPath(const SkPath& path, const SkStrokeRec& stroke, GrDrawTarget* target, bool antiAlias) { if (NULL == fContext) { return false; } GrDrawState* drawState = target->drawState(); SkMatrix vm = drawState->getViewMatrix(); GrIRect devPathBounds, devClipBounds; if (!get_path_and_clip_bounds(target, path, vm, &devPathBounds, &devClipBounds)) { if (path.isInverseFillType()) { draw_around_inv_path(target, devClipBounds, devPathBounds); } return true; } SkAutoTUnref<GrTexture> texture( GrSWMaskHelper::DrawPathMaskToTexture(fContext, path, stroke, devPathBounds, antiAlias, &vm)); if (NULL == texture) { return false; } GrSWMaskHelper::DrawToTargetWithPathMask(texture, target, devPathBounds); if (path.isInverseFillType()) { draw_around_inv_path(target, devClipBounds, devPathBounds); } return true; }
bool GrClipMaskManager::drawElement(GrTexture* target, const SkClipStack::Element* element, GrPathRenderer* pr) { GrDrawState* drawState = fGpu->drawState(); drawState->setRenderTarget(target->asRenderTarget()); // TODO: Draw rrects directly here. switch (element->getType()) { case Element::kEmpty_Type: SkDEBUGFAIL("Should never get here with an empty element."); break; case Element::kRect_Type: // TODO: Do rects directly to the accumulator using a aa-rect GrEffect that covers the // entire mask bounds and writes 0 outside the rect. if (element->isAA()) { getContext()->getAARectRenderer()->fillAARect(fGpu, fGpu, element->getRect(), SkMatrix::I(), element->getRect(), false); } else { fGpu->drawSimpleRect(element->getRect(), NULL); } return true; default: { SkPath path; element->asPath(&path); if (path.isInverseFillType()) { path.toggleInverseFillType(); } SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle); if (NULL == pr) { GrPathRendererChain::DrawType type; type = element->isAA() ? GrPathRendererChain::kColorAntiAlias_DrawType : GrPathRendererChain::kColor_DrawType; pr = this->getContext()->getPathRenderer(path, stroke, fGpu, false, type); } if (NULL == pr) { return false; } pr->drawPath(path, stroke, fGpu, element->isAA()); break; } } return true; }
bool SkRasterClip::op(const SkPath& path, const SkISize& size, SkRegion::Op op, bool doAA) { // base is used to limit the size (and therefore memory allocation) of the // region that results from scan converting devPath. SkRegion base; if (fForceConservativeRects) { SkIRect ir; switch (mutate_conservative_op(&op, path.isInverseFillType())) { case kDoNothing_MutateResult: return !this->isEmpty(); case kReplaceClippedAgainstGlobalBounds_MutateResult: ir = SkIRect::MakeSize(size); break; case kContinue_MutateResult: ir = path.getBounds().roundOut(); break; } return this->op(ir, op); } if (SkRegion::kIntersect_Op == op) { // since we are intersect, we can do better (tighter) with currRgn's // bounds, than just using the device. However, if currRgn is complex, // our region blitter may hork, so we do that case in two steps. if (this->isRect()) { // FIXME: we should also be able to do this when this->isBW(), // but relaxing the test above triggers GM asserts in // SkRgnBuilder::blitH(). We need to investigate what's going on. return this->setPath(path, this->bwRgn(), doAA); } else { base.setRect(this->getBounds()); SkRasterClip clip(fForceConservativeRects); clip.setPath(path, base, doAA); return this->op(clip, op); } } else { base.setRect(0, 0, size.width(), size.height()); if (SkRegion::kReplace_Op == op) { return this->setPath(path, base, doAA); } else { SkRasterClip clip(fForceConservativeRects); clip.setPath(path, base, doAA); return this->op(clip, op); } } }
void draw(Target* target, const GrGeometryProcessor* gp) const { GrResourceProvider* rp = target->resourceProvider(); SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance; SkScalar tol = GrPathUtils::scaleToleranceToSrc(screenSpaceTol, fViewMatrix, fShape.bounds()); SkPath path; fShape.asPath(&path); bool inverseFill = path.isInverseFillType(); // construct a cache key from the path's genID and the view matrix static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); GrUniqueKey key; static constexpr int kClipBoundsCnt = sizeof(fClipBounds) / sizeof(uint32_t); int shapeKeyDataCnt = fShape.unstyledKeySize(); SkASSERT(shapeKeyDataCnt >= 0); GrUniqueKey::Builder builder(&key, kDomain, shapeKeyDataCnt + kClipBoundsCnt); fShape.writeUnstyledKey(&builder[0]); // For inverse fills, the tessellation is dependent on clip bounds. if (inverseFill) { memcpy(&builder[shapeKeyDataCnt], &fClipBounds, sizeof(fClipBounds)); } else { memset(&builder[shapeKeyDataCnt], 0, sizeof(fClipBounds)); } builder.finish(); SkAutoTUnref<GrBuffer> cachedVertexBuffer(rp->findAndRefTByUniqueKey<GrBuffer>(key)); int actualCount; if (cache_match(cachedVertexBuffer.get(), tol, &actualCount)) { this->drawVertices(target, gp, cachedVertexBuffer.get(), 0, actualCount); return; } bool isLinear; bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags(); StaticVertexAllocator allocator(rp, canMapVB); int count = GrTessellator::PathToTriangles(path, tol, fClipBounds, &allocator, &isLinear); if (count == 0) { return; } this->drawVertices(target, gp, allocator.vertexBuffer(), 0, count); TessInfo info; info.fTolerance = isLinear ? 0 : tol; info.fCount = count; SkAutoTUnref<SkData> data(SkData::NewWithCopy(&info, sizeof(info))); key.setCustomData(data.get()); rp->assignUniqueKeyToResource(key, allocator.vertexBuffer()); }
bool SkHitTestPathEx(const SkPath& path, SkScalar x, SkScalar y) { bool isInverse = path.isInverseFillType(); if (path.isEmpty()) { return isInverse; } const SkRect& bounds = path.getBounds(); if (!bounds.contains(x, y)) { return isInverse; } SkPath::Iter iter(path, true); bool done = false; int w = 0; do { SkPoint pts[4]; switch (iter.next(pts, false)) { case SkPath::kMove_Verb: case SkPath::kClose_Verb: break; case SkPath::kLine_Verb: w += winding_line(pts, x, y); break; case SkPath::kQuad_Verb: w += winding_quad(pts, x, y); break; case SkPath::kCubic_Verb: w += winding_cubic(pts, x, y); break; case SkPath::kDone_Verb: done = true; break; } } while (!done); switch (path.getFillType()) { case SkPath::kEvenOdd_FillType: case SkPath::kInverseEvenOdd_FillType: w &= 1; break; default: break; } return SkToBool(w); }
void SkConservativeClip::opPath(const SkPath& path, const SkMatrix& ctm, const SkIRect& devBounds, SkRegion::Op op, bool doAA) { SkIRect ir; switch (mutate_conservative_op(&op, path.isInverseFillType())) { case kDoNothing_MutateResult: return; case kReplaceClippedAgainstGlobalBounds_MutateResult: ir = devBounds; break; case kContinue_MutateResult: { SkRect bounds = path.getBounds(); ctm.mapRect(&bounds); ir = bounds.roundOut(); break; } } return this->opIRect(ir, op); }
bool SkRasterClip::setPath(const SkPath& path, const SkRegion& clip, bool doAA) { AUTO_RASTERCLIP_VALIDATE(*this); if (fForceConservativeRects) { return this->setConservativeRect(path.getBounds(), clip.getBounds(), path.isInverseFillType()); } if (this->isBW() && !doAA) { (void)fBW.setPath(path, clip); } else { // TODO: since we are going to over-write fAA completely (aren't we?) // we should just clear our BW data (if any) and set fIsAA=true if (this->isBW()) { this->convertToAA(); } (void)fAA.setPath(path, &clip, doAA); } return this->updateCacheAndReturnNonEmpty(); }