TessellatingPathBatch(const GrColor& color,
                       const GrShape& shape,
                       const SkMatrix& viewMatrix,
                       const SkRect& clipBounds)
   : INHERITED(ClassID())
   , fColor(color)
   , fShape(shape)
   , fViewMatrix(viewMatrix) {
     const SkRect& pathBounds = shape.bounds();
     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);
     const SkRect& srcBounds = shape.inverseFilled() ? fClipBounds : pathBounds;
     this->setTransformedBounds(srcBounds, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
 }
/**
 * Draw a single path element of the clip stack into the accumulation bitmap
 */
void GrSWMaskHelper::drawShape(const GrShape& shape, SkRegion::Op op, GrAA aa, uint8_t alpha) {
    SkPaint paint;
    paint.setPathEffect(shape.style().refPathEffect());
    shape.style().strokeRec().applyToPaint(&paint);
    paint.setAntiAlias(GrAA::kYes == aa);

    SkPath path;
    shape.asPath(&path);
    if (SkRegion::kReplace_Op == op && 0xFF == alpha) {
        SkASSERT(0xFF == paint.getAlpha());
        fDraw.drawPathCoverage(path, paint);
    } else {
        paint.setBlendMode(op_to_mode(op));
        paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
        fDraw.drawPath(path, paint);
    }
}
Example #3
0
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
}
Example #4
0
void GrPath::ComputeKey(const GrShape& shape, GrUniqueKey* key, bool* outIsVolatile) {
    int geoCnt = shape.unstyledKeySize();
    int styleCnt = GrStyle::KeySize(shape.style(), GrStyle::Apply::kPathEffectAndStrokeRec);
    // This should only fail for an arbitrary path effect, and we should not have gotten
    // here with anything other than a dash path effect.
    SkASSERT(styleCnt >= 0);
    if (geoCnt < 0) {
        *outIsVolatile = true;
        return;
    }
    static const GrUniqueKey::Domain kGeneralPathDomain = GrUniqueKey::GenerateDomain();
    GrUniqueKey::Builder builder(key, kGeneralPathDomain, geoCnt + styleCnt, "Path");
    shape.writeUnstyledKey(&builder[0]);
    if (styleCnt) {
        write_style_key(&builder[geoCnt], shape.style());
    }
    *outIsVolatile = false;
}
    AADistanceFieldPathBatch(GrColor color,
                             const GrShape& shape,
                             bool antiAlias,
                             const SkMatrix& viewMatrix,
                             GrBatchAtlas* atlas,
                             ShapeCache* shapeCache, ShapeDataList* shapeList,
                             bool gammaCorrect)
            : INHERITED(ClassID()) {
        SkASSERT(shape.hasUnstyledKey());
        fBatch.fViewMatrix = viewMatrix;
        fGeoData.emplace_back(Geometry{color, shape, antiAlias});

        fAtlas = atlas;
        fShapeCache = shapeCache;
        fShapeList = shapeList;
        fGammaCorrect = gammaCorrect;

        // Compute bounds
        this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
    }
Example #6
0
GrShape GrShape::MakeFilled(const GrShape& original, FillInversion inversion) {
    if (original.style().isSimpleFill() && !flip_inversion(original.inverseFilled(), inversion)) {
        // By returning the original rather than falling through we can preserve any inherited style
        // key. Otherwise, we wipe it out below since the style change invalidates it.
        return original;
    }
    GrShape result;
    if (original.fInheritedPathForListeners.isValid()) {
        result.fInheritedPathForListeners.set(*original.fInheritedPathForListeners.get());
    }
    switch (original.fType) {
        case Type::kRRect:
            result.fType = original.fType;
            result.fRRectData.fRRect = original.fRRectData.fRRect;
            result.fRRectData.fDir = kDefaultRRectDir;
            result.fRRectData.fStart = kDefaultRRectStart;
            result.fRRectData.fInverted = is_inverted(original.fRRectData.fInverted, inversion);
            break;
        case Type::kArc:
            result.fType = original.fType;
            result.fArcData.fOval = original.fArcData.fOval;
            result.fArcData.fStartAngleDegrees = original.fArcData.fStartAngleDegrees;
            result.fArcData.fSweepAngleDegrees = original.fArcData.fSweepAngleDegrees;
            result.fArcData.fUseCenter = original.fArcData.fUseCenter;
            result.fArcData.fInverted = is_inverted(original.fArcData.fInverted, inversion);
            break;
        case Type::kLine:
            // Lines don't fill.
            if (is_inverted(original.fLineData.fInverted, inversion)) {
                result.fType = Type::kInvertedEmpty;
            } else {
                result.fType = Type::kEmpty;
            }
            break;
        case Type::kEmpty:
            result.fType = is_inverted(false, inversion) ? Type::kInvertedEmpty :  Type::kEmpty;
            break;
        case Type::kInvertedEmpty:
            result.fType = is_inverted(true, inversion) ? Type::kInvertedEmpty :  Type::kEmpty;
            break;
        case Type::kPath:
            result.initType(Type::kPath, &original.fPathData.fPath);
            result.fPathData.fGenID = original.fPathData.fGenID;
            if (flip_inversion(original.fPathData.fPath.isInverseFillType(), inversion)) {
                result.fPathData.fPath.toggleInverseFillType();
            }
            if (!original.style().isSimpleFill()) {
                // Going from a non-filled style to fill may allow additional simplifications (e.g.
                // closing an open rect that wasn't closed in the original shape because it had
                // stroke style).
                result.attemptToSimplifyPath();
            }
            break;
    }
    // We don't copy the inherited key since it can contain path effect information that we just
    // stripped.
    return result;
}
Example #7
0
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 addPathToAtlas(GrVertexBatch::Target* target,
                        FlushInfo* flushInfo,
                        GrBatchAtlas* atlas,
                        ShapeData* shapeData,
                        const GrShape& shape,
                        bool antiAlias,
                        uint32_t dimension,
                        SkScalar scale) const {
        const SkRect& bounds = shape.bounds();

        // generate bounding rect for bitmap draw
        SkRect scaledBounds = bounds;
        // scale to mip level size
        scaledBounds.fLeft *= scale;
        scaledBounds.fTop *= scale;
        scaledBounds.fRight *= scale;
        scaledBounds.fBottom *= scale;
        // move the origin to an integer boundary (gives better results)
        SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
        SkScalar dy = SkScalarFraction(scaledBounds.fTop);
        scaledBounds.offset(-dx, -dy);
        // get integer boundary
        SkIRect devPathBounds;
        scaledBounds.roundOut(&devPathBounds);
        // pad to allow room for antialiasing
        const int intPad = SkScalarCeilToInt(kAntiAliasPad);
        // pre-move origin (after outset, will be 0,0)
        int width = devPathBounds.width();
        int height = devPathBounds.height();
        devPathBounds.fLeft = intPad;
        devPathBounds.fTop = intPad;
        devPathBounds.fRight = intPad + width;
        devPathBounds.fBottom = intPad + height;
        devPathBounds.outset(intPad, intPad);

        // draw path to bitmap
        SkMatrix drawMatrix;
        drawMatrix.setTranslate(-bounds.left(), -bounds.top());
        drawMatrix.postScale(scale, scale);
        drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);

        // setup bitmap backing
        SkASSERT(devPathBounds.fLeft == 0);
        SkASSERT(devPathBounds.fTop == 0);
        SkAutoPixmapStorage dst;
        if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
                                              devPathBounds.height()))) {
            return false;
        }
        sk_bzero(dst.writable_addr(), dst.getSafeSize());

        // rasterize path
        SkPaint paint;
        paint.setStyle(SkPaint::kFill_Style);
        paint.setAntiAlias(antiAlias);

        SkDraw draw;
        sk_bzero(&draw, sizeof(draw));

        SkRasterClip rasterClip;
        rasterClip.setRect(devPathBounds);
        draw.fRC = &rasterClip;
        draw.fMatrix = &drawMatrix;
        draw.fDst = dst;

        SkPath path;
        shape.asPath(&path);
        draw.drawPathCoverage(path, paint);

        // generate signed distance field
        devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
        width = devPathBounds.width();
        height = devPathBounds.height();
        // TODO We should really generate this directly into the plot somehow
        SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));

        // Generate signed distance field
        SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
                                           (const unsigned char*)dst.addr(),
                                           dst.width(), dst.height(), dst.rowBytes());

        // add to atlas
        SkIPoint16 atlasLocation;
        GrBatchAtlas::AtlasID id;
       if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
            this->flush(target, flushInfo);
            if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
                return false;
            }
        }

        // add to cache
        shapeData->fKey.set(shape, dimension);
        shapeData->fScale = scale;
        shapeData->fID = id;
        // change the scaled rect to match the size of the inset distance field
        scaledBounds.fRight = scaledBounds.fLeft +
            SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
        scaledBounds.fBottom = scaledBounds.fTop +
            SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
        // shift the origin to the correct place relative to the distance field
        // need to also restore the fractional translation
        scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
                            -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
        shapeData->fBounds = scaledBounds;
        // origin we render from is inset from distance field edge
        atlasLocation.fX += SK_DistanceFieldInset;
        atlasLocation.fY += SK_DistanceFieldInset;
        shapeData->fAtlasLocation = atlasLocation;

        fShapeCache->add(shapeData);
        fShapeList->addToTail(shapeData);
#ifdef DF_PATH_TRACKING
        ++g_NumCachedPaths;
#endif
        return true;
    }
Example #9
0
GrCCPathCache::OnFlushEntryRef GrCCPathCache::find(
        GrOnFlushResourceProvider* onFlushRP, const GrShape& shape,
        const SkIRect& clippedDrawBounds, const SkMatrix& viewMatrix, SkIVector* maskShift) {
    if (!shape.hasUnstyledKey()) {
        return OnFlushEntryRef();
    }

    WriteKeyHelper writeKeyHelper(shape);
    if (writeKeyHelper.allocCountU32() > kMaxKeyDataCountU32) {
        return OnFlushEntryRef();
    }

    SkASSERT(fScratchKey->unique());
    fScratchKey->resetDataCountU32(writeKeyHelper.allocCountU32());
    writeKeyHelper.write(shape, fScratchKey->data());

    MaskTransform m(viewMatrix, maskShift);
    GrCCPathCacheEntry* entry = nullptr;
    if (HashNode* node = fHashTable.find(*fScratchKey)) {
        entry = node->entry();
        SkASSERT(fLRU.isInList(entry));

        if (!fuzzy_equals(m, entry->fMaskTransform)) {
            // The path was reused with an incompatible matrix.
            if (entry->unique()) {
                // This entry is unique: recycle it instead of deleting and malloc-ing a new one.
                SkASSERT(0 == entry->fOnFlushRefCnt);  // Because we are unique.
                entry->fMaskTransform = m;
                entry->fHitCount = 0;
                entry->fHitRect = SkIRect::MakeEmpty();
                entry->releaseCachedAtlas(this);
            } else {
                this->evict(*fScratchKey);
                entry = nullptr;
            }
        }
    }

    if (!entry) {
        if (fHashTable.count() >= kMaxCacheCount) {
            SkDEBUGCODE(HashNode* node = fHashTable.find(*fLRU.tail()->fCacheKey));
            SkASSERT(node && node->entry() == fLRU.tail());
            this->evict(*fLRU.tail()->fCacheKey);  // We've exceeded our limit.
        }

        // Create a new entry in the cache.
        sk_sp<Key> permanentKey = Key::Make(fInvalidatedKeysInbox.uniqueID(),
                                            writeKeyHelper.allocCountU32(), fScratchKey->data());
        SkASSERT(*permanentKey == *fScratchKey);
        SkASSERT(!fHashTable.find(*permanentKey));
        entry = fHashTable.set(HashNode(this, std::move(permanentKey), m, shape))->entry();

        SkASSERT(fHashTable.count() <= kMaxCacheCount);
    } else {
        fLRU.remove(entry);  // Will be re-added at head.
    }

    SkDEBUGCODE(HashNode* node = fHashTable.find(*fScratchKey));
    SkASSERT(node && node->entry() == entry);
    fLRU.addToHead(entry);

    if (0 == entry->fOnFlushRefCnt) {
        // Only update the time stamp and hit count if we haven't seen this entry yet during the
        // current flush.
        entry->fTimestamp = this->quickPerFlushTimestamp();
        ++entry->fHitCount;

        if (entry->fCachedAtlas) {
            SkASSERT(SkToBool(entry->fCachedAtlas->peekOnFlushRefCnt()) ==
                     SkToBool(entry->fCachedAtlas->getOnFlushProxy()));
            if (!entry->fCachedAtlas->getOnFlushProxy()) {
                if (sk_sp<GrTextureProxy> onFlushProxy = onFlushRP->findOrCreateProxyByUniqueKey(
                        entry->fCachedAtlas->textureKey(), GrCCAtlas::kTextureOrigin)) {
                    onFlushProxy->priv().setIgnoredByResourceAllocator();
                    entry->fCachedAtlas->setOnFlushProxy(std::move(onFlushProxy));
                }
            }
            if (!entry->fCachedAtlas->getOnFlushProxy()) {
                // Our atlas's backing texture got purged from the GrResourceCache. Release the
                // cached atlas.
                entry->releaseCachedAtlas(this);
            }
        }
    }
    entry->fHitRect.join(clippedDrawBounds.makeOffset(-maskShift->x(), -maskShift->y()));
    SkASSERT(!entry->fCachedAtlas || entry->fCachedAtlas->getOnFlushProxy());
    return OnFlushEntryRef::OnFlushRef(entry);
}
Example #10
0
bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext,
                                             GrPaint&& paint,
                                             GrAAType aaType,
                                             const GrUserStencilSettings& userStencilSettings,
                                             const GrClip& clip,
                                             const SkMatrix& viewMatrix,
                                             const GrShape& shape,
                                             bool stencilOnly) {
    auto context = renderTargetContext->surfPriv().getContext();

    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[2];
    bool                         reverse = false;
    bool                         lastPassIsBounds;

    if (isHairline) {
        passCount = 1;
        if (stencilOnly) {
            passes[0] = &gDirectToStencil;
        } else {
            passes[0] = &userStencilSettings;
        }
        lastPassIsBounds = false;
    } else {
        if (single_pass_shape(shape)) {
            passCount = 1;
            if (stencilOnly) {
                passes[0] = &gDirectToStencil;
            } else {
                passes[0] = &userStencilSettings;
            }
            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;
                        }
                    }
                    break;

                case SkPath::kInverseWinding_FillType:
                    reverse = true;
                    // fallthrough
                case SkPath::kWinding_FillType:
                    passes[0] = &gWindStencilPass;
                    passCount = 2;
                    if (stencilOnly) {
                        lastPassIsBounds = false;
                        --passCount;
                    } else {
                        lastPassIsBounds = true;
                        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->asRenderTargetProxy()->worstCaseWidth(),
                     renderTargetContext->asRenderTargetProxy()->worstCaseHeight(),
                     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;
            // This is a non-coverage aa rect op since we assert aaType != kCoverage at the start
            assert_alive(paint);
            renderTargetContext->priv().stencilRect(clip, passes[p], std::move(paint),
                    GrAA(aaType == GrAAType::kMSAA), viewM, bounds, &localMatrix);
        } else {
            bool stencilPass = stencilOnly || passCount > 1;
            std::unique_ptr<GrDrawOp> op;
            if (stencilPass) {
                GrPaint stencilPaint;
                stencilPaint.setXPFactory(GrDisableColorXPFactory::Get());
                op = DefaultPathOp::Make(context, std::move(stencilPaint), path, srcSpaceTol,
                                         newCoverage, viewMatrix, isHairline, aaType, devBounds,
                                         passes[p]);
            } else {
                assert_alive(paint);
                op = DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, newCoverage,
                                         viewMatrix, isHairline, aaType, devBounds, passes[p]);
            }
            renderTargetContext->addDrawOp(clip, std::move(op));
        }
    }
    return true;
}