/* * 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/SkColorData.h" #include "include/private/SkIDChangeListener.h" #include "include/private/base/SkMalloc.h" #include "include/private/gpu/ganesh/GrTypesPriv.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/base/SkRandom.h" #include "src/gpu/ganesh/GrTestUtils.h" #endif #include #include #include #include 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 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(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(incumbent->data()); const TessInfo* c = static_cast(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::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 detachVertexData() { SkASSERT(!fLockStride && !fVertices && !fVertexBuffer && fVertexData); return std::move(fVertexData); } private: sk_sp fVertexData; sk_sp 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(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 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(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 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(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 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 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; } 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