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); } }
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 }
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); }
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; }
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; }
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); }
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; }