| /* |
| * Copyright 2015 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/TriangulatingPathRenderer.h" |
| |
| #include "include/private/SkIDChangeListener.h" |
| #include "src/core/SkGeometry.h" |
| #include "src/gpu/ganesh/GrAuditTrail.h" |
| #include "src/gpu/ganesh/GrCaps.h" |
| #include "src/gpu/ganesh/GrDefaultGeoProcFactory.h" |
| #include "src/gpu/ganesh/GrDrawOpTest.h" |
| #include "src/gpu/ganesh/GrEagerVertexAllocator.h" |
| #include "src/gpu/ganesh/GrOpFlushState.h" |
| #include "src/gpu/ganesh/GrProgramInfo.h" |
| #include "src/gpu/ganesh/GrRecordingContextPriv.h" |
| #include "src/gpu/ganesh/GrResourceCache.h" |
| #include "src/gpu/ganesh/GrResourceProvider.h" |
| #include "src/gpu/ganesh/GrSimpleMesh.h" |
| #include "src/gpu/ganesh/GrStyle.h" |
| #include "src/gpu/ganesh/GrThreadSafeCache.h" |
| #include "src/gpu/ganesh/SurfaceDrawContext.h" |
| #include "src/gpu/ganesh/geometry/GrAATriangulator.h" |
| #include "src/gpu/ganesh/geometry/GrPathUtils.h" |
| #include "src/gpu/ganesh/geometry/GrStyledShape.h" |
| #include "src/gpu/ganesh/geometry/GrTriangulator.h" |
| #include "src/gpu/ganesh/ops/GrMeshDrawOp.h" |
| #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelperWithStencil.h" |
| |
| #include <cstdio> |
| |
| #if !defined(SK_ENABLE_OPTIMIZE_SIZE) |
| |
| #ifndef GR_AA_TESSELLATOR_MAX_VERB_COUNT |
| #define GR_AA_TESSELLATOR_MAX_VERB_COUNT 10 |
| #endif |
| |
| /* |
| * This path renderer linearizes and decomposes the path into triangles using GrTriangulator, |
| * uploads the triangles to a vertex buffer, and renders them with a single draw call. It can do |
| * screenspace antialiasing with a one-pixel coverage ramp. |
| */ |
| namespace { |
| |
| // The TessInfo struct contains ancillary data not specifically required for the triangle |
| // data (which is stored in a GrThreadSafeCache::VertexData object). |
| // The 'fNumVertices' field is a temporary exception. It is still needed to support the |
| // AA triangulated path case - which doesn't use the GrThreadSafeCache nor the VertexData object). |
| // When there is an associated VertexData, its numVertices should always match the TessInfo's |
| // value. |
| struct TessInfo { |
| int fNumVertices; |
| bool fIsLinear; |
| SkScalar fTolerance; |
| }; |
| |
| static sk_sp<SkData> create_data(int numVertices, bool isLinear, SkScalar tol) { |
| TessInfo info { numVertices, isLinear, tol }; |
| return SkData::MakeWithCopy(&info, sizeof(info)); |
| } |
| |
| bool cache_match(const SkData* data, SkScalar tol) { |
| SkASSERT(data); |
| |
| const TessInfo* info = static_cast<const TessInfo*>(data->data()); |
| |
| return info->fIsLinear || info->fTolerance < 3.0f * tol; |
| } |
| |
| // Should 'challenger' replace 'incumbent' in the cache if there is a collision? |
| bool is_newer_better(SkData* incumbent, SkData* challenger) { |
| const TessInfo* i = static_cast<const TessInfo*>(incumbent->data()); |
| const TessInfo* c = static_cast<const TessInfo*>(challenger->data()); |
| |
| if (i->fIsLinear || i->fTolerance <= c->fTolerance) { |
| return false; // prefer the incumbent |
| } |
| |
| return true; |
| } |
| |
| // When the SkPathRef genID changes, invalidate a corresponding GrResource described by key. |
| class UniqueKeyInvalidator : public SkIDChangeListener { |
| public: |
| UniqueKeyInvalidator(const skgpu::UniqueKey& key, uint32_t contextUniqueID) |
| : fMsg(key, contextUniqueID, /* inThreadSafeCache */ true) {} |
| |
| private: |
| skgpu::UniqueKeyInvalidatedMessage fMsg; |
| |
| void changed() override { |
| SkMessageBus<skgpu::UniqueKeyInvalidatedMessage, uint32_t>::Post(fMsg); |
| } |
| }; |
| |
| class StaticVertexAllocator : public GrEagerVertexAllocator { |
| public: |
| StaticVertexAllocator(GrResourceProvider* resourceProvider, bool canMapVB) |
| : fResourceProvider(resourceProvider) |
| , fCanMapVB(canMapVB) { |
| } |
| |
| #ifdef SK_DEBUG |
| ~StaticVertexAllocator() override { |
| SkASSERT(!fLockStride && !fVertices && !fVertexBuffer && !fVertexData); |
| } |
| #endif |
| |
| void* lock(size_t stride, int eagerCount) override { |
| SkASSERT(!fLockStride && !fVertices && !fVertexBuffer && !fVertexData); |
| SkASSERT(stride && eagerCount); |
| |
| size_t size = eagerCount * stride; |
| fVertexBuffer = fResourceProvider->createBuffer(size, |
| GrGpuBufferType::kVertex, |
| kStatic_GrAccessPattern, |
| GrResourceProvider::ZeroInit::kNo); |
| if (!fVertexBuffer) { |
| return nullptr; |
| } |
| if (fCanMapVB) { |
| fVertices = fVertexBuffer->map(); |
| } |
| if (!fVertices) { |
| fVertices = sk_malloc_throw(eagerCount * stride); |
| fCanMapVB = false; |
| } |
| fLockStride = stride; |
| return fVertices; |
| } |
| |
| void unlock(int actualCount) override { |
| SkASSERT(fLockStride && fVertices && fVertexBuffer && !fVertexData); |
| |
| if (fCanMapVB) { |
| fVertexBuffer->unmap(); |
| } else { |
| fVertexBuffer->updateData(fVertices, |
| /*offset=*/0, |
| /*size=*/actualCount*fLockStride, |
| /*preserve=*/false); |
| sk_free(fVertices); |
| } |
| |
| fVertexData = GrThreadSafeCache::MakeVertexData(std::move(fVertexBuffer), |
| actualCount, fLockStride); |
| |
| fVertices = nullptr; |
| fLockStride = 0; |
| } |
| |
| sk_sp<GrThreadSafeCache::VertexData> detachVertexData() { |
| SkASSERT(!fLockStride && !fVertices && !fVertexBuffer && fVertexData); |
| |
| return std::move(fVertexData); |
| } |
| |
| private: |
| sk_sp<GrThreadSafeCache::VertexData> fVertexData; |
| sk_sp<GrGpuBuffer> fVertexBuffer; |
| GrResourceProvider* fResourceProvider; |
| bool fCanMapVB; |
| void* fVertices = nullptr; |
| size_t fLockStride = 0; |
| }; |
| |
| class TriangulatingPathOp final : public GrMeshDrawOp { |
| private: |
| using Helper = GrSimpleMeshDrawOpHelperWithStencil; |
| |
| public: |
| DEFINE_OP_CLASS_ID |
| |
| static GrOp::Owner Make(GrRecordingContext* context, |
| GrPaint&& paint, |
| const GrStyledShape& shape, |
| const SkMatrix& viewMatrix, |
| SkIRect devClipBounds, |
| GrAAType aaType, |
| const GrUserStencilSettings* stencilSettings) { |
| return Helper::FactoryHelper<TriangulatingPathOp>(context, std::move(paint), shape, |
| viewMatrix, devClipBounds, aaType, |
| stencilSettings); |
| } |
| |
| const char* name() const override { return "TriangulatingPathOp"; } |
| |
| void visitProxies(const GrVisitProxyFunc& func) const override { |
| if (fProgramInfo) { |
| fProgramInfo->visitFPProxies(func); |
| } else { |
| fHelper.visitProxies(func); |
| } |
| } |
| |
| TriangulatingPathOp(GrProcessorSet* processorSet, |
| const SkPMColor4f& color, |
| const GrStyledShape& shape, |
| const SkMatrix& viewMatrix, |
| const SkIRect& devClipBounds, |
| GrAAType aaType, |
| const GrUserStencilSettings* stencilSettings) |
| : INHERITED(ClassID()) |
| , fHelper(processorSet, aaType, stencilSettings) |
| , fColor(color) |
| , fShape(shape) |
| , fViewMatrix(viewMatrix) |
| , fDevClipBounds(devClipBounds) |
| , fAntiAlias(GrAAType::kCoverage == aaType) { |
| SkRect devBounds; |
| viewMatrix.mapRect(&devBounds, shape.bounds()); |
| if (shape.inverseFilled()) { |
| // Because the clip bounds are used to add a contour for inverse fills, they must also |
| // include the path bounds. |
| devBounds.join(SkRect::Make(fDevClipBounds)); |
| } |
| this->setBounds(devBounds, HasAABloat(fAntiAlias), IsHairline::kNo); |
| } |
| |
| FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } |
| |
| GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, |
| GrClampType clampType) override { |
| GrProcessorAnalysisCoverage coverage = fAntiAlias |
| ? GrProcessorAnalysisCoverage::kSingleChannel |
| : GrProcessorAnalysisCoverage::kNone; |
| // This Op uses uniform (not vertex) color, so doesn't need to track wide color. |
| return fHelper.finalizeProcessors(caps, clip, clampType, coverage, &fColor, nullptr); |
| } |
| |
| private: |
| SkPath getPath() const { |
| SkASSERT(!fShape.style().applies()); |
| SkPath path; |
| fShape.asPath(&path); |
| return path; |
| } |
| |
| static void CreateKey(skgpu::UniqueKey* key, |
| const GrStyledShape& shape, |
| const SkIRect& devClipBounds) { |
| static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain(); |
| |
| bool inverseFill = shape.inverseFilled(); |
| |
| static constexpr int kClipBoundsCnt = sizeof(devClipBounds) / sizeof(uint32_t); |
| int shapeKeyDataCnt = shape.unstyledKeySize(); |
| SkASSERT(shapeKeyDataCnt >= 0); |
| skgpu::UniqueKey::Builder builder(key, kDomain, shapeKeyDataCnt + kClipBoundsCnt, "Path"); |
| shape.writeUnstyledKey(&builder[0]); |
| // For inverse fills, the tessellation is dependent on clip bounds. |
| if (inverseFill) { |
| memcpy(&builder[shapeKeyDataCnt], &devClipBounds, sizeof(devClipBounds)); |
| } else { |
| memset(&builder[shapeKeyDataCnt], 0, sizeof(devClipBounds)); |
| } |
| |
| builder.finish(); |
| } |
| |
| // Triangulate the provided 'shape' in the shape's coordinate space. 'tol' should already |
| // have been mapped back from device space. |
| static int Triangulate(GrEagerVertexAllocator* allocator, |
| const SkMatrix& viewMatrix, |
| const GrStyledShape& shape, |
| const SkIRect& devClipBounds, |
| SkScalar tol, |
| bool* isLinear) { |
| SkRect clipBounds = SkRect::Make(devClipBounds); |
| |
| SkMatrix vmi; |
| if (!viewMatrix.invert(&vmi)) { |
| return 0; |
| } |
| vmi.mapRect(&clipBounds); |
| |
| SkASSERT(!shape.style().applies()); |
| SkPath path; |
| shape.asPath(&path); |
| |
| return GrTriangulator::PathToTriangles(path, tol, clipBounds, allocator, isLinear); |
| } |
| |
| void createNonAAMesh(GrMeshDrawTarget* target) { |
| SkASSERT(!fAntiAlias); |
| GrResourceProvider* rp = target->resourceProvider(); |
| auto threadSafeCache = target->threadSafeCache(); |
| |
| skgpu::UniqueKey key; |
| CreateKey(&key, fShape, fDevClipBounds); |
| |
| SkScalar tol = GrPathUtils::scaleToleranceToSrc(GrPathUtils::kDefaultTolerance, |
| fViewMatrix, fShape.bounds()); |
| |
| if (!fVertexData) { |
| auto [cachedVerts, data] = threadSafeCache->findVertsWithData(key); |
| if (cachedVerts && cache_match(data.get(), tol)) { |
| fVertexData = std::move(cachedVerts); |
| } |
| } |
| |
| if (fVertexData) { |
| if (!fVertexData->gpuBuffer()) { |
| sk_sp<GrGpuBuffer> buffer = rp->createBuffer(fVertexData->vertices(), |
| fVertexData->size(), |
| GrGpuBufferType::kVertex, |
| kStatic_GrAccessPattern); |
| if (!buffer) { |
| return; |
| } |
| |
| // Since we have a direct context and a ref on 'fVertexData' we need not worry |
| // about any threading issues in this call. |
| fVertexData->setGpuBuffer(std::move(buffer)); |
| } |
| |
| fMesh = CreateMesh(target, fVertexData->refGpuBuffer(), 0, fVertexData->numVertices()); |
| return; |
| } |
| |
| bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags(); |
| StaticVertexAllocator allocator(rp, canMapVB); |
| |
| bool isLinear; |
| int vertexCount = Triangulate(&allocator, fViewMatrix, fShape, fDevClipBounds, tol, |
| &isLinear); |
| if (vertexCount == 0) { |
| return; |
| } |
| |
| fVertexData = allocator.detachVertexData(); |
| |
| key.setCustomData(create_data(vertexCount, isLinear, tol)); |
| |
| auto [tmpV, tmpD] = threadSafeCache->addVertsWithData(key, fVertexData, is_newer_better); |
| if (tmpV != fVertexData) { |
| SkASSERT(!tmpV->gpuBuffer()); |
| // In this case, although the different triangulation found in the cache is better, |
| // we will continue on with the current triangulation since it is already on the gpu. |
| } else { |
| // This isn't perfect. The current triangulation is in the cache but it may have |
| // replaced a pre-existing one. A duplicated listener is unlikely and not that |
| // expensive so we just roll with it. |
| fShape.addGenIDChangeListener( |
| sk_make_sp<UniqueKeyInvalidator>(key, target->contextUniqueID())); |
| } |
| |
| fMesh = CreateMesh(target, fVertexData->refGpuBuffer(), 0, fVertexData->numVertices()); |
| } |
| |
| void createAAMesh(GrMeshDrawTarget* target) { |
| SkASSERT(!fVertexData); |
| SkASSERT(fAntiAlias); |
| SkPath path = this->getPath(); |
| if (path.isEmpty()) { |
| return; |
| } |
| SkRect clipBounds = SkRect::Make(fDevClipBounds); |
| path.transform(fViewMatrix); |
| SkScalar tol = GrPathUtils::kDefaultTolerance; |
| sk_sp<const GrBuffer> vertexBuffer; |
| int firstVertex; |
| GrEagerDynamicVertexAllocator allocator(target, &vertexBuffer, &firstVertex); |
| int vertexCount = GrAATriangulator::PathToAATriangles(path, tol, clipBounds, &allocator); |
| if (vertexCount == 0) { |
| return; |
| } |
| fMesh = CreateMesh(target, std::move(vertexBuffer), firstVertex, vertexCount); |
| } |
| |
| 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(fColor); |
| LocalCoords::Type localCoordsType = fHelper.usesLocalCoords() |
| ? LocalCoords::kUsePosition_Type |
| : LocalCoords::kUnused_Type; |
| Coverage::Type coverageType; |
| if (fAntiAlias) { |
| if (fHelper.compatibleWithCoverageAsAlpha()) { |
| coverageType = Coverage::kAttributeTweakAlpha_Type; |
| } else { |
| coverageType = Coverage::kAttribute_Type; |
| } |
| } else { |
| coverageType = Coverage::kSolid_Type; |
| } |
| if (fAntiAlias) { |
| gp = GrDefaultGeoProcFactory::MakeForDeviceSpace(arena, color, coverageType, |
| localCoordsType, fViewMatrix); |
| } else { |
| gp = GrDefaultGeoProcFactory::Make(arena, color, coverageType, localCoordsType, |
| fViewMatrix); |
| } |
| } |
| if (!gp) { |
| return; |
| } |
| |
| #ifdef SK_DEBUG |
| auto vertexStride = sizeof(SkPoint); |
| if (fAntiAlias) { |
| vertexStride += sizeof(float); |
| } |
| SkASSERT(vertexStride == gp->vertexStride()); |
| #endif |
| |
| GrPrimitiveType primitiveType = TRIANGULATOR_WIREFRAME ? GrPrimitiveType::kLines |
| : GrPrimitiveType::kTriangles; |
| |
| fProgramInfo = fHelper.createProgramInfoWithStencil(caps, arena, writeView, |
| usesMSAASurface, |
| std::move(appliedClip), dstProxyView, |
| gp, primitiveType, |
| renderPassXferBarriers, colorLoadOp); |
| } |
| |
| void onPrePrepareDraws(GrRecordingContext* rContext, |
| const GrSurfaceProxyView& writeView, |
| GrAppliedClip* clip, |
| const GrDstProxyView& dstProxyView, |
| GrXferBarrierFlags renderPassXferBarriers, |
| GrLoadOp colorLoadOp) override { |
| TRACE_EVENT0("skia.gpu", TRACE_FUNC); |
| |
| INHERITED::onPrePrepareDraws(rContext, writeView, clip, dstProxyView, |
| renderPassXferBarriers, colorLoadOp); |
| |
| if (fAntiAlias) { |
| // TODO: pull the triangulation work forward to the recording thread for the AA case |
| // too. |
| return; |
| } |
| |
| auto threadSafeViewCache = rContext->priv().threadSafeCache(); |
| |
| skgpu::UniqueKey key; |
| CreateKey(&key, fShape, fDevClipBounds); |
| |
| SkScalar tol = GrPathUtils::scaleToleranceToSrc(GrPathUtils::kDefaultTolerance, |
| fViewMatrix, fShape.bounds()); |
| |
| auto [cachedVerts, data] = threadSafeViewCache->findVertsWithData(key); |
| if (cachedVerts && cache_match(data.get(), tol)) { |
| fVertexData = std::move(cachedVerts); |
| return; |
| } |
| |
| GrCpuVertexAllocator allocator; |
| |
| bool isLinear; |
| int vertexCount = Triangulate(&allocator, fViewMatrix, fShape, fDevClipBounds, tol, |
| &isLinear); |
| if (vertexCount == 0) { |
| return; |
| } |
| |
| fVertexData = allocator.detachVertexData(); |
| |
| key.setCustomData(create_data(vertexCount, isLinear, tol)); |
| |
| // If some other thread created and cached its own triangulation, the 'is_newer_better' |
| // predicate will replace the version in the cache if 'fVertexData' is a more accurate |
| // triangulation. This will leave some other recording threads using a poorer triangulation |
| // but will result in a version with greater applicability being in the cache. |
| auto [tmpV, tmpD] = threadSafeViewCache->addVertsWithData(key, fVertexData, |
| is_newer_better); |
| if (tmpV != fVertexData) { |
| // Someone beat us to creating the triangulation (and it is better than ours) so |
| // just go ahead and use it. |
| SkASSERT(cache_match(tmpD.get(), tol)); |
| fVertexData = std::move(tmpV); |
| } else { |
| // This isn't perfect. The current triangulation is in the cache but it may have |
| // replaced a pre-existing one. A duplicated listener is unlikely and not that |
| // expensive so we just roll with it. |
| fShape.addGenIDChangeListener( |
| sk_make_sp<UniqueKeyInvalidator>(key, rContext->priv().contextID())); |
| } |
| } |
| |
| void onPrepareDraws(GrMeshDrawTarget* target) override { |
| if (fAntiAlias) { |
| this->createAAMesh(target); |
| } else { |
| this->createNonAAMesh(target); |
| } |
| } |
| |
| static GrSimpleMesh* CreateMesh(GrMeshDrawTarget* target, |
| sk_sp<const GrBuffer> vb, |
| int firstVertex, |
| int count) { |
| auto mesh = target->allocMesh(); |
| mesh->set(std::move(vb), count, firstVertex); |
| return mesh; |
| } |
| |
| void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { |
| if (!fProgramInfo) { |
| this->createProgramInfo(flushState); |
| } |
| |
| if (!fProgramInfo || !fMesh) { |
| return; |
| } |
| |
| flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); |
| flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); |
| flushState->drawMesh(*fMesh); |
| } |
| |
| #if defined(GR_TEST_UTILS) |
| SkString onDumpInfo() const override { |
| return SkStringPrintf("Color 0x%08x, aa: %d\n%s", |
| fColor.toBytes_RGBA(), fAntiAlias, fHelper.dumpInfo().c_str()); |
| } |
| #endif |
| |
| Helper fHelper; |
| SkPMColor4f fColor; |
| GrStyledShape fShape; |
| SkMatrix fViewMatrix; |
| SkIRect fDevClipBounds; |
| bool fAntiAlias; |
| |
| GrSimpleMesh* fMesh = nullptr; |
| GrProgramInfo* fProgramInfo = nullptr; |
| |
| sk_sp<GrThreadSafeCache::VertexData> fVertexData; |
| |
| using INHERITED = GrMeshDrawOp; |
| }; |
| |
| } // anonymous namespace |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| #if defined(GR_TEST_UTILS) |
| |
| GR_DRAW_OP_TEST_DEFINE(TriangulatingPathOp) { |
| SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); |
| const SkPath& path = GrTest::TestPath(random); |
| SkIRect devClipBounds = SkIRect::MakeLTRB( |
| random->nextU(), random->nextU(), random->nextU(), random->nextU()); |
| devClipBounds.sort(); |
| static constexpr GrAAType kAATypes[] = {GrAAType::kNone, GrAAType::kMSAA, GrAAType::kCoverage}; |
| GrAAType aaType; |
| do { |
| aaType = kAATypes[random->nextULessThan(std::size(kAATypes))]; |
| } while(GrAAType::kMSAA == aaType && numSamples <= 1); |
| GrStyle style; |
| do { |
| GrTest::TestStyle(random, &style); |
| } while (!style.isSimpleFill()); |
| GrStyledShape shape(path, style); |
| return TriangulatingPathOp::Make(context, std::move(paint), shape, viewMatrix, devClipBounds, |
| aaType, GrGetRandomStencil(random, context)); |
| } |
| |
| #endif |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| namespace skgpu::ganesh { |
| |
| TriangulatingPathRenderer::TriangulatingPathRenderer() |
| : fMaxVerbCount(GR_AA_TESSELLATOR_MAX_VERB_COUNT) { |
| } |
| |
| PathRenderer::CanDrawPath TriangulatingPathRenderer::onCanDrawPath( |
| const CanDrawPathArgs& args) const { |
| |
| // Don't use this path renderer with dynamic MSAA. DMSAA tries to not rely on caching. |
| if (args.fSurfaceProps->flags() & SkSurfaceProps::kDynamicMSAA_Flag) { |
| return CanDrawPath::kNo; |
| } |
| // This path renderer can draw fill styles, and can do screenspace antialiasing via a |
| // one-pixel coverage ramp. It can do convex and concave paths, but we'll leave the convex |
| // ones to simpler algorithms. We pass on paths that have styles, though they may come back |
| // around after applying the styling information to the geometry to create a filled path. |
| if (!args.fShape->style().isSimpleFill() || args.fShape->knownToBeConvex()) { |
| return CanDrawPath::kNo; |
| } |
| switch (args.fAAType) { |
| case GrAAType::kNone: |
| case GrAAType::kMSAA: |
| // Prefer MSAA, if any antialiasing. In the non-analytic-AA case, We skip paths that |
| // don't have a key since the real advantage of this path renderer comes from caching |
| // the tessellated geometry. |
| if (!args.fShape->hasUnstyledKey()) { |
| return CanDrawPath::kNo; |
| } |
| break; |
| case GrAAType::kCoverage: |
| // Use analytic AA if we don't have MSAA. In this case, we do not cache, so we accept |
| // paths without keys. |
| SkPath path; |
| args.fShape->asPath(&path); |
| if (path.countVerbs() > fMaxVerbCount) { |
| return CanDrawPath::kNo; |
| } |
| break; |
| } |
| return CanDrawPath::kYes; |
| } |
| |
| bool TriangulatingPathRenderer::onDrawPath(const DrawPathArgs& args) { |
| GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(), |
| "GrTriangulatingPathRenderer::onDrawPath"); |
| |
| GrOp::Owner op = TriangulatingPathOp::Make( |
| args.fContext, std::move(args.fPaint), *args.fShape, *args.fViewMatrix, |
| *args.fClipConservativeBounds, args.fAAType, args.fUserStencilSettings); |
| args.fSurfaceDrawContext->addDrawOp(args.fClip, std::move(op)); |
| return true; |
| } |
| |
| } // namespace skgpu::ganesh |
| |
| #endif // SK_ENABLE_OPTIMIZE_SIZE |