| /* | |
| * 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/core/SkData.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkSurfaceProps.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/gpu/ganesh/GrRecordingContext.h" | |
| #include "include/private/SkIDChangeListener.h" | |
| #include "include/private/SkMalloc.h" | |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" | |
| #include "src/core/SkColorData.h" | |
| #include "src/core/SkMessageBus.h" | |
| #include "src/core/SkTraceEvent.h" | |
| #include "src/gpu/ResourceKey.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/GrDefaultGeoProcFactory.h" | |
| #include "src/gpu/ganesh/GrDrawOpTest.h" | |
| #include "src/gpu/ganesh/GrEagerVertexAllocator.h" | |
| #include "src/gpu/ganesh/GrGeometryProcessor.h" | |
| #include "src/gpu/ganesh/GrGpuBuffer.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/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/GrOp.h" | |
| #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelperWithStencil.h" | |
| #if defined(GPU_TEST_UTILS) | |
| #include "src/core/SkRandom.h" | |
| #include "src/gpu/ganesh/GrTestUtils.h" | |
| #endif | |
| #include <array> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <utility> | |
| class GrDstProxyView; | |
| class GrSurfaceProxyView; | |
| class SkArenaAlloc; | |
| enum class GrXferBarrierFlags; | |
| struct GrUserStencilSettings; | |
| #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); | |
| // Matches sk_malloc_throw(stride, count) but lets use the size earlier. | |
| size_t size = SkSafeMath::Mul(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(size); | |
| 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()); | |
| return fShape.asPath(); | |
| } | |
| 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); | |
| uint16_t shapeKeyDataCnt = shape.unstyledKeySize(); | |
| 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(); | |
| 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().makeTransform(fViewMatrix); | |
| if (path.isEmpty()) { | |
| return; | |
| } | |
| SkRect clipBounds = SkRect::Make(fDevClipBounds); | |
| 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(GPU_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(GPU_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; | |
| } | |
| SkPath path = args.fShape->asPath(); | |
| int verbCount = path.countVerbs(); | |
| // Don't use this path renderer if we exceed the max verb count. | |
| if (verbCount > kMaxGPUPathRendererVerbs) { | |
| 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. | |
| if (verbCount > 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 |