/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/ganesh/ops/DefaultPathRenderer.h" #include "include/core/SkMatrix.h" #include "include/core/SkPath.h" #include "include/core/SkPathTypes.h" #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkScalar.h" #include "include/core/SkString.h" #include "include/core/SkStrokeRec.h" #include "include/gpu/ganesh/GrRecordingContext.h" #include "include/private/SkColorData.h" #include "include/private/base/SkAssert.h" #include "include/private/base/SkDebug.h" #include "include/private/base/SkPoint_impl.h" #include "include/private/base/SkTArray.h" #include "include/private/base/SkTDArray.h" #include "include/private/base/SkTo.h" #include "include/private/gpu/ganesh/GrTypesPriv.h" #include "src/core/SkGeometry.h" #include "src/core/SkMatrixPriv.h" #include "src/gpu/ganesh/GrAppliedClip.h" #include "src/gpu/ganesh/GrAuditTrail.h" #include "src/gpu/ganesh/GrBuffer.h" #include "src/gpu/ganesh/GrCaps.h" #include "src/gpu/ganesh/GrClip.h" #include "src/gpu/ganesh/GrDefaultGeoProcFactory.h" #include "src/gpu/ganesh/GrGeometryProcessor.h" #include "src/gpu/ganesh/GrMeshDrawTarget.h" #include "src/gpu/ganesh/GrOpFlushState.h" #include "src/gpu/ganesh/GrPaint.h" #include "src/gpu/ganesh/GrProcessorAnalysis.h" #include "src/gpu/ganesh/GrProcessorSet.h" #include "src/gpu/ganesh/GrProgramInfo.h" #include "src/gpu/ganesh/GrRecordingContextPriv.h" #include "src/gpu/ganesh/GrRenderTargetProxy.h" #include "src/gpu/ganesh/GrSimpleMesh.h" #include "src/gpu/ganesh/GrStyle.h" #include "src/gpu/ganesh/GrTestUtils.h" #include "src/gpu/ganesh/GrUserStencilSettings.h" #include "src/gpu/ganesh/GrUtil.h" #include "src/gpu/ganesh/SurfaceDrawContext.h" #include "src/gpu/ganesh/effects/GrDisableColorXP.h" #include "src/gpu/ganesh/geometry/GrPathUtils.h" #include "src/gpu/ganesh/geometry/GrStyledShape.h" #include "src/gpu/ganesh/ops/GrMeshDrawOp.h" #include "src/gpu/ganesh/ops/GrOp.h" #include "src/gpu/ganesh/ops/GrPathStencilSettings.h" #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelperWithStencil.h" #if defined(GPU_TEST_UTILS) #include "src/base/SkRandom.h" #include "src/gpu/ganesh/GrDrawOpTest.h" #endif #include #include #include class GrDstProxyView; class GrSurfaceProxyView; class SkArenaAlloc; enum class GrXferBarrierFlags; using namespace skia_private; //////////////////////////////////////////////////////////////////////////////// // Helpers for drawPath namespace { #define STENCIL_OFF 0 // Always disable stencil (even when needed) inline bool single_pass_shape(const GrStyledShape& 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 } class PathGeoBuilder { public: PathGeoBuilder(GrPrimitiveType primitiveType, GrMeshDrawTarget* target, SkTDArray* meshes) : fPrimitiveType(primitiveType) , fTarget(target) , fVertexStride(sizeof(SkPoint)) , fFirstIndex(0) , fIndicesInChunk(0) , fIndices(nullptr) , fMeshes(meshes) { this->allocNewBuffers(); } ~PathGeoBuilder() { this->createMeshAndPutBackReserve(); } /** * Path verbs */ void moveTo(const SkPoint& p) { if (!this->ensureSpace(1)) { return; } if (!this->isHairline()) { fSubpathIndexStart = this->currentIndex(); fSubpathStartPoint = p; } *(fCurVert++) = p; } void addLine(const SkPoint pts[]) { if (!this->ensureSpace(1, this->indexScale(), &pts[0])) { return; } if (this->isIndexed()) { uint16_t prevIdx = this->currentIndex() - 1; this->appendCountourEdgeIndices(prevIdx); } *(fCurVert++) = pts[1]; } void addQuad(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) { if (!this->ensureSpace(GrPathUtils::kMaxPointsPerCurve, GrPathUtils::kMaxPointsPerCurve * this->indexScale(), &pts[0])) { return; } // First pt of quad is the pt we ended on in previous step uint16_t firstQPtIdx = this->currentIndex() - 1; uint16_t numPts = (uint16_t)GrPathUtils::generateQuadraticPoints( pts[0], pts[1], pts[2], srcSpaceTolSqd, &fCurVert, GrPathUtils::quadraticPointCount(pts, srcSpaceTol)); if (this->isIndexed()) { for (uint16_t i = 0; i < numPts; ++i) { this->appendCountourEdgeIndices(firstQPtIdx + i); } } } void addConic(SkScalar weight, const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) { SkAutoConicToQuads converter; const SkPoint* quadPts = converter.computeQuads(pts, weight, srcSpaceTol); for (int i = 0; i < converter.countQuads(); ++i) { this->addQuad(quadPts + i * 2, srcSpaceTolSqd, srcSpaceTol); } } void addCubic(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) { if (!this->ensureSpace(GrPathUtils::kMaxPointsPerCurve, GrPathUtils::kMaxPointsPerCurve * this->indexScale(), &pts[0])) { return; } // First pt of cubic is the pt we ended on in previous step uint16_t firstCPtIdx = this->currentIndex() - 1; uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints( pts[0], pts[1], pts[2], pts[3], srcSpaceTolSqd, &fCurVert, GrPathUtils::cubicPointCount(pts, srcSpaceTol)); if (this->isIndexed()) { for (uint16_t i = 0; i < numPts; ++i) { this->appendCountourEdgeIndices(firstCPtIdx + i); } } } void addPath(const SkPath& path, SkScalar srcSpaceTol) { SkScalar srcSpaceTolSqd = srcSpaceTol * srcSpaceTol; SkPath::Iter iter(path, false); SkPoint pts[4]; bool done = false; while (!done) { SkPath::Verb verb = iter.next(pts); switch (verb) { case SkPath::kMove_Verb: this->moveTo(pts[0]); break; case SkPath::kLine_Verb: this->addLine(pts); break; case SkPath::kConic_Verb: this->addConic(iter.conicWeight(), pts, srcSpaceTolSqd, srcSpaceTol); break; case SkPath::kQuad_Verb: this->addQuad(pts, srcSpaceTolSqd, srcSpaceTol); break; case SkPath::kCubic_Verb: this->addCubic(pts, srcSpaceTolSqd, srcSpaceTol); break; case SkPath::kClose_Verb: break; case SkPath::kDone_Verb: done = true; } } } static bool PathHasMultipleSubpaths(const SkPath& path) { bool first = true; SkPath::Iter iter(path, false); SkPath::Verb verb; SkPoint pts[4]; while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { if (SkPath::kMove_Verb == verb && !first) { return true; } first = false; } return false; } private: /** * Derived properties * TODO: Cache some of these for better performance, rather than re-computing? */ bool isIndexed() const { return GrPrimitiveType::kLines == fPrimitiveType || GrPrimitiveType::kTriangles == fPrimitiveType; } bool isHairline() const { return GrPrimitiveType::kLines == fPrimitiveType || GrPrimitiveType::kLineStrip == fPrimitiveType; } int indexScale() const { switch (fPrimitiveType) { case GrPrimitiveType::kLines: return 2; case GrPrimitiveType::kTriangles: return 3; default: return 0; } } uint16_t currentIndex() const { return fCurVert - fVertices; } // Allocate vertex and (possibly) index buffers void allocNewBuffers() { SkASSERT(fValid); // Ensure that we always get enough verts for a worst-case quad/cubic, plus leftover points // from previous mesh piece (up to two verts to continue fanning). If we can't get that // many, ask for a much larger number. This needs to be fairly big to handle quads/cubics, // which have a worst-case of 1k points. static const int kMinVerticesPerChunk = GrPathUtils::kMaxPointsPerCurve + 2; static const int kFallbackVerticesPerChunk = 16384; fVertices = static_cast(fTarget->makeVertexSpaceAtLeast(fVertexStride, kMinVerticesPerChunk, kFallbackVerticesPerChunk, &fVertexBuffer, &fFirstVertex, &fVerticesInChunk)); if (!fVertices) { SkDebugf("WARNING: Failed to allocate vertex buffer for DefaultPathRenderer.\n"); fCurVert = nullptr; fCurIdx = fIndices = nullptr; fSubpathIndexStart = 0; fValid = false; return; } if (this->isIndexed()) { // Similar to above: Ensure we get enough indices for one worst-case quad/cubic. // No extra indices are needed for stitching, though. If we can't get that many, ask // for enough to match our large vertex request. const int kMinIndicesPerChunk = GrPathUtils::kMaxPointsPerCurve * this->indexScale(); const int kFallbackIndicesPerChunk = kFallbackVerticesPerChunk * this->indexScale(); fIndices = fTarget->makeIndexSpaceAtLeast(kMinIndicesPerChunk, kFallbackIndicesPerChunk, &fIndexBuffer, &fFirstIndex, &fIndicesInChunk); if (!fIndices) { SkDebugf("WARNING: Failed to allocate index buffer for DefaultPathRenderer.\n"); fVertices = nullptr; fValid = false; } } fCurVert = fVertices; fCurIdx = fIndices; fSubpathIndexStart = 0; } void appendCountourEdgeIndices(uint16_t edgeV0Idx) { SkASSERT(fCurIdx); // When drawing lines we're appending line segments along the countour. When applying the // other fill rules we're drawing triangle fans around the start of the current (sub)path. if (!this->isHairline()) { *(fCurIdx++) = fSubpathIndexStart; } *(fCurIdx++) = edgeV0Idx; *(fCurIdx++) = edgeV0Idx + 1; } // Emits a single draw with all accumulated vertex/index data void createMeshAndPutBackReserve() { if (!fValid) { return; } int vertexCount = fCurVert - fVertices; int indexCount = fCurIdx - fIndices; SkASSERT(vertexCount <= fVerticesInChunk); SkASSERT(indexCount <= fIndicesInChunk); GrSimpleMesh* mesh = nullptr; if (this->isIndexed() ? SkToBool(indexCount) : SkToBool(vertexCount)) { mesh = fTarget->allocMesh(); if (!this->isIndexed()) { mesh->set(std::move(fVertexBuffer), vertexCount, fFirstVertex); } else { mesh->setIndexed(std::move(fIndexBuffer), indexCount, fFirstIndex, 0, vertexCount - 1, GrPrimitiveRestart::kNo, std::move(fVertexBuffer), fFirstVertex); } } fTarget->putBackIndices((size_t)(fIndicesInChunk - indexCount)); fTarget->putBackVertices((size_t)(fVerticesInChunk - vertexCount), fVertexStride); if (mesh) { fMeshes->push_back(mesh); } } bool ensureSpace(int vertsNeeded, int indicesNeeded = 0, const SkPoint* lastPoint = nullptr) { if (!fValid) { return false; } if (fCurVert + vertsNeeded > fVertices + fVerticesInChunk || fCurIdx + indicesNeeded > fIndices + fIndicesInChunk) { // We are about to run out of space (possibly) #ifdef SK_DEBUG // To maintain continuity, we need to remember one or two points from the current mesh. // Lines only need the last point, fills need the first point from the current contour. // We always grab both here, and append the ones we need at the end of this process. SkASSERT(fSubpathIndexStart < fVerticesInChunk); // This assert is reading from the gpu buffer fVertices and will be slow, but for debug // that is okay. if (!this->isHairline()) { SkASSERT(fSubpathStartPoint == fVertices[fSubpathIndexStart]); } if (lastPoint) { SkASSERT(*(fCurVert - 1) == *lastPoint); } #endif // Draw the mesh we've accumulated, and put back any unused space this->createMeshAndPutBackReserve(); // Get new buffers this->allocNewBuffers(); if (!fValid) { return false; } // On moves we don't need to copy over any points to the new buffer and we pass in a // null lastPoint. if (lastPoint) { // Append copies of the points we saved so the two meshes will weld properly if (!this->isHairline()) { *(fCurVert++) = fSubpathStartPoint; } *(fCurVert++) = *lastPoint; } } return true; } GrPrimitiveType fPrimitiveType; GrMeshDrawTarget* fTarget; size_t fVertexStride; sk_sp fVertexBuffer; int fFirstVertex; int fVerticesInChunk; SkPoint* fVertices; SkPoint* fCurVert; sk_sp fIndexBuffer; int fFirstIndex; int fIndicesInChunk; uint16_t* fIndices; uint16_t* fCurIdx; uint16_t fSubpathIndexStart; SkPoint fSubpathStartPoint; bool fValid = true; SkTDArray* fMeshes; }; class DefaultPathOp final : public GrMeshDrawOp { private: using Helper = GrSimpleMeshDrawOpHelperWithStencil; public: DEFINE_OP_CLASS_ID static GrOp::Owner Make(GrRecordingContext* context, GrPaint&& paint, const SkPath& path, SkScalar tolerance, uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline, GrAAType aaType, const SkRect& devBounds, const GrUserStencilSettings* stencilSettings) { return Helper::FactoryHelper(context, std::move(paint), path, tolerance, coverage, viewMatrix, isHairline, aaType, devBounds, stencilSettings); } const char* name() const override { return "DefaultPathOp"; } void visitProxies(const GrVisitProxyFunc& func) const override { if (fProgramInfo) { fProgramInfo->visitFPProxies(func); } else { fHelper.visitProxies(func); } } DefaultPathOp(GrProcessorSet* processorSet, const SkPMColor4f& color, const SkPath& path, SkScalar tolerance, uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline, GrAAType aaType, const SkRect& devBounds, const GrUserStencilSettings* stencilSettings) : INHERITED(ClassID()) , fHelper(processorSet, aaType, stencilSettings) , fColor(color) , fCoverage(coverage) , fViewMatrix(viewMatrix) , fIsHairline(isHairline) { fPaths.emplace_back(PathData{path, tolerance}); HasAABloat aaBloat = (aaType == GrAAType::kNone) ? HasAABloat ::kNo : HasAABloat::kYes; this->setBounds(devBounds, aaBloat, isHairline ? IsHairline::kYes : IsHairline::kNo); } FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, GrClampType clampType) override { GrProcessorAnalysisCoverage gpCoverage = this->coverage() == 0xFF ? GrProcessorAnalysisCoverage::kNone : GrProcessorAnalysisCoverage::kSingleChannel; // This Op uses uniform (not vertex) color, so doesn't need to track wide color. return fHelper.finalizeProcessors(caps, clip, clampType, gpCoverage, &fColor, nullptr); } private: GrPrimitiveType primType() const { if (this->isHairline()) { int instanceCount = fPaths.size(); // We avoid indices when we have a single hairline contour. bool isIndexed = instanceCount > 1 || PathGeoBuilder::PathHasMultipleSubpaths(fPaths[0].fPath); return isIndexed ? GrPrimitiveType::kLines : GrPrimitiveType::kLineStrip; } return GrPrimitiveType::kTriangles; } GrProgramInfo* programInfo() override { return fProgramInfo; } void onCreateProgramInfo(const GrCaps* caps, SkArenaAlloc* arena, const GrSurfaceProxyView& writeView, bool usesMSAASurface, GrAppliedClip&& appliedClip, const GrDstProxyView& dstProxyView, GrXferBarrierFlags renderPassXferBarriers, GrLoadOp colorLoadOp) override { GrGeometryProcessor* gp; { using namespace GrDefaultGeoProcFactory; Color color(this->color()); Coverage coverage(this->coverage()); LocalCoords localCoords(fHelper.usesLocalCoords() ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type); gp = GrDefaultGeoProcFactory::Make(arena, color, coverage, localCoords, this->viewMatrix()); } SkASSERT(gp->vertexStride() == sizeof(SkPoint)); fProgramInfo = fHelper.createProgramInfoWithStencil(caps, arena, writeView, usesMSAASurface, std::move(appliedClip), dstProxyView, gp, this->primType(), renderPassXferBarriers, colorLoadOp); } void onPrepareDraws(GrMeshDrawTarget* target) override { PathGeoBuilder pathGeoBuilder(this->primType(), target, &fMeshes); // fill buffers for (int i = 0; i < fPaths.size(); i++) { const PathData& args = fPaths[i]; pathGeoBuilder.addPath(args.fPath, args.fTolerance); } } void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { if (!fProgramInfo) { this->createProgramInfo(flushState); } if (!fProgramInfo || fMeshes.empty()) { return; } flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); for (int i = 0; i < fMeshes.size(); ++i) { flushState->drawMesh(*fMeshes[i]); } } CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override { DefaultPathOp* that = t->cast(); if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { return CombineResult::kCannotCombine; } if (this->color() != that->color()) { return CombineResult::kCannotCombine; } if (this->coverage() != that->coverage()) { return CombineResult::kCannotCombine; } if (!SkMatrixPriv::CheapEqual(this->viewMatrix(), that->viewMatrix())) { return CombineResult::kCannotCombine; } if (this->isHairline() != that->isHairline()) { return CombineResult::kCannotCombine; } fPaths.push_back_n(that->fPaths.size(), that->fPaths.begin()); return CombineResult::kMerged; } #if defined(GPU_TEST_UTILS) SkString onDumpInfo() const override { SkString string = SkStringPrintf("Color: 0x%08x Count: %d\n", fColor.toBytes_RGBA(), fPaths.size()); for (const auto& path : fPaths) { string.appendf("Tolerance: %.2f\n", path.fTolerance); } string += fHelper.dumpInfo(); return string; } #endif const SkPMColor4f& color() const { return fColor; } uint8_t coverage() const { return fCoverage; } const SkMatrix& viewMatrix() const { return fViewMatrix; } bool isHairline() const { return fIsHairline; } struct PathData { SkPath fPath; SkScalar fTolerance; }; STArray<1, PathData, true> fPaths; Helper fHelper; SkPMColor4f fColor; uint8_t fCoverage; SkMatrix fViewMatrix; bool fIsHairline; SkTDArray fMeshes; GrProgramInfo* fProgramInfo = nullptr; using INHERITED = GrMeshDrawOp; }; } // anonymous namespace /////////////////////////////////////////////////////////////////////////////////////////////////// #if defined(GPU_TEST_UTILS) GR_DRAW_OP_TEST_DEFINE(DefaultPathOp) { SkMatrix viewMatrix = GrTest::TestMatrix(random); // For now just hairlines because the other types of draws require two ops. // TODO we should figure out a way to combine the stencil and cover steps into one op. GrStyle style(SkStrokeRec::kHairline_InitStyle); const SkPath& path = GrTest::TestPath(random); // Compute srcSpaceTol SkRect bounds = path.getBounds(); SkScalar tol = GrPathUtils::kDefaultTolerance; SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, bounds); viewMatrix.mapRect(&bounds); uint8_t coverage = GrTest::RandomCoverage(random); GrAAType aaType = GrAAType::kNone; if (numSamples > 1 && random->nextBool()) { aaType = GrAAType::kMSAA; } return DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, coverage, viewMatrix, true, aaType, bounds, GrGetRandomStencil(random, context)); } #endif /////////////////////////////////////////////////////////////////////////////////////////////////// namespace skgpu::ganesh { bool DefaultPathRenderer::internalDrawPath(skgpu::ganesh::SurfaceDrawContext* sdc, GrPaint&& paint, GrAAType aaType, const GrUserStencilSettings& userStencilSettings, const GrClip* clip, const SkMatrix& viewMatrix, const GrStyledShape& shape, bool stencilOnly) { auto context = sdc->recordingContext(); SkASSERT(GrAAType::kCoverage != aaType); SkPath path; shape.asPath(&path); SkScalar hairlineCoverage; uint8_t newCoverage = 0xff; bool isHairline = false; if (GrIsStrokeHairlineOrEquivalent(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 SkPathFillType::kInverseEvenOdd: reverse = true; [[fallthrough]]; case SkPathFillType::kEvenOdd: 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 SkPathFillType::kInverseWinding: reverse = true; [[fallthrough]]; case SkPathFillType::kWinding: 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, sdc->asRenderTargetProxy()->backingStoreDimensions(), 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); sdc->stencilRect(clip, passes[p], std::move(paint), GrAA(aaType == GrAAType::kMSAA), viewM, bounds, &localMatrix); } else { bool stencilPass = stencilOnly || passCount > 1; GrOp::Owner 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]); } sdc->addDrawOp(clip, std::move(op)); } } return true; } PathRenderer::StencilSupport DefaultPathRenderer::onGetStencilSupport(const GrStyledShape& shape) const { if (single_pass_shape(shape)) { return kNoRestriction_StencilSupport; } else { return kStencilOnly_StencilSupport; } } PathRenderer::CanDrawPath DefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { bool isHairline = GrIsStrokeHairlineOrEquivalent( args.fShape->style(), *args.fViewMatrix, nullptr); // If we aren't a single_pass_shape or hairline, we require stencil buffers. if (!(single_pass_shape(*args.fShape) || isHairline) && !args.fProxy->canUseStencil(*args.fCaps)) { return CanDrawPath::kNo; } // If antialiasing is required, we only support MSAA. if (GrAAType::kNone != args.fAAType && GrAAType::kMSAA != args.fAAType) { return CanDrawPath::kNo; } // This can draw any path with any simple fill style. if (!args.fShape->style().isSimpleFill() && !isHairline) { return CanDrawPath::kNo; } // Don't try to draw hairlines with DefaultPathRenderer if avoidLineDraws is true. // Alternatively, we could try to implement hairline draws without line primitives in // DefaultPathRenderer, but this is simpler. if (args.fCaps->avoidLineDraws() && isHairline) { return CanDrawPath::kNo; } // This is the fallback renderer for when a path is too complicated for the others to draw. return CanDrawPath::kAsBackup; } bool DefaultPathRenderer::onDrawPath(const DrawPathArgs& args) { GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(), "DefaultPathRenderer::onDrawPath"); GrAAType aaType = (GrAAType::kNone != args.fAAType) ? GrAAType::kMSAA : GrAAType::kNone; return this->internalDrawPath( args.fSurfaceDrawContext, std::move(args.fPaint), aaType, *args.fUserStencilSettings, args.fClip, *args.fViewMatrix, *args.fShape, false); } void DefaultPathRenderer::onStencilPath(const StencilPathArgs& args) { GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(), "DefaultPathRenderer::onStencilPath"); SkASSERT(!args.fShape->inverseFilled()); GrPaint paint; paint.setXPFactory(GrDisableColorXPFactory::Get()); auto aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone; this->internalDrawPath( args.fSurfaceDrawContext, std::move(paint), aaType, GrUserStencilSettings::kUnused, args.fClip, *args.fViewMatrix, *args.fShape, true); } } // namespace skgpu::ganesh