| /* |
| * 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 "GrTessellatingPathRenderer.h" |
| |
| #include "GrAuditTrail.h" |
| #include "GrClip.h" |
| #include "GrDefaultGeoProcFactory.h" |
| #include "GrDrawOpTest.h" |
| #include "GrMesh.h" |
| #include "GrOpFlushState.h" |
| #include "GrPathUtils.h" |
| #include "GrResourceCache.h" |
| #include "GrResourceProvider.h" |
| #include "GrTessellator.h" |
| #include "SkGeometry.h" |
| |
| #include "GrSimpleMeshDrawOpHelper.h" |
| #include "ops/GrMeshDrawOp.h" |
| |
| #include <stdio.h> |
| |
| /* |
| * This path renderer tessellates the path into triangles using GrTessellator, 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 { |
| |
| struct TessInfo { |
| SkScalar fTolerance; |
| int fCount; |
| }; |
| |
| // When the SkPathRef genID changes, invalidate a corresponding GrResource described by key. |
| class PathInvalidator : public SkPathRef::GenIDChangeListener { |
| public: |
| explicit PathInvalidator(const GrUniqueKey& key) : fMsg(key) {} |
| private: |
| GrUniqueKeyInvalidatedMessage fMsg; |
| |
| void onChange() override { |
| SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(fMsg); |
| } |
| }; |
| |
| bool cache_match(GrBuffer* vertexBuffer, SkScalar tol, int* actualCount) { |
| if (!vertexBuffer) { |
| return false; |
| } |
| const SkData* data = vertexBuffer->getUniqueKey().getCustomData(); |
| SkASSERT(data); |
| const TessInfo* info = static_cast<const TessInfo*>(data->data()); |
| if (info->fTolerance == 0 || info->fTolerance < 3.0f * tol) { |
| *actualCount = info->fCount; |
| return true; |
| } |
| return false; |
| } |
| |
| class StaticVertexAllocator : public GrTessellator::VertexAllocator { |
| public: |
| StaticVertexAllocator(size_t stride, GrResourceProvider* resourceProvider, bool canMapVB) |
| : VertexAllocator(stride) |
| , fResourceProvider(resourceProvider) |
| , fCanMapVB(canMapVB) |
| , fVertices(nullptr) { |
| } |
| void* lock(int vertexCount) override { |
| size_t size = vertexCount * stride(); |
| fVertexBuffer.reset(fResourceProvider->createBuffer( |
| size, kVertex_GrBufferType, kStatic_GrAccessPattern, 0)); |
| if (!fVertexBuffer.get()) { |
| return nullptr; |
| } |
| if (fCanMapVB) { |
| fVertices = fVertexBuffer->map(); |
| } else { |
| fVertices = sk_malloc_throw(vertexCount * stride()); |
| } |
| return fVertices; |
| } |
| void unlock(int actualCount) override { |
| if (fCanMapVB) { |
| fVertexBuffer->unmap(); |
| } else { |
| fVertexBuffer->updateData(fVertices, actualCount * stride()); |
| sk_free(fVertices); |
| } |
| fVertices = nullptr; |
| } |
| GrBuffer* vertexBuffer() { return fVertexBuffer.get(); } |
| private: |
| sk_sp<GrBuffer> fVertexBuffer; |
| GrResourceProvider* fResourceProvider; |
| bool fCanMapVB; |
| void* fVertices; |
| }; |
| |
| class DynamicVertexAllocator : public GrTessellator::VertexAllocator { |
| public: |
| DynamicVertexAllocator(size_t stride, GrMeshDrawOp::Target* target) |
| : VertexAllocator(stride) |
| , fTarget(target) |
| , fVertexBuffer(nullptr) |
| , fVertices(nullptr) {} |
| void* lock(int vertexCount) override { |
| fVertexCount = vertexCount; |
| fVertices = fTarget->makeVertexSpace(stride(), vertexCount, &fVertexBuffer, &fFirstVertex); |
| return fVertices; |
| } |
| void unlock(int actualCount) override { |
| fTarget->putBackVertices(fVertexCount - actualCount, stride()); |
| fVertices = nullptr; |
| } |
| const GrBuffer* vertexBuffer() const { return fVertexBuffer; } |
| int firstVertex() const { return fFirstVertex; } |
| private: |
| GrMeshDrawOp::Target* fTarget; |
| const GrBuffer* fVertexBuffer; |
| int fVertexCount; |
| int fFirstVertex; |
| void* fVertices; |
| }; |
| |
| } // namespace |
| |
| GrTessellatingPathRenderer::GrTessellatingPathRenderer() { |
| } |
| |
| bool GrTessellatingPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { |
| // 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. In |
| // the non-AA case, We skip paths thta don't have a key since the real advantage of this path |
| // renderer comes from caching the tessellated geometry. In the AA case, we do not cache, so we |
| // accept paths without keys. |
| if (!args.fShape->style().isSimpleFill() || args.fShape->knownToBeConvex()) { |
| return false; |
| } |
| if (GrAAType::kCoverage == args.fAAType) { |
| SkPath path; |
| args.fShape->asPath(&path); |
| if (path.countVerbs() > 10) { |
| return false; |
| } |
| } else if (!args.fShape->hasUnstyledKey()) { |
| return false; |
| } |
| return true; |
| } |
| |
| namespace { |
| |
| class TessellatingPathOp final : public GrMeshDrawOp { |
| private: |
| using Helper = GrSimpleMeshDrawOpHelperWithStencil; |
| |
| public: |
| DEFINE_OP_CLASS_ID |
| |
| static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, |
| const GrShape& shape, |
| const SkMatrix& viewMatrix, |
| SkIRect devClipBounds, |
| GrAAType aaType, |
| const GrUserStencilSettings* stencilSettings) { |
| return Helper::FactoryHelper<TessellatingPathOp>(std::move(paint), shape, viewMatrix, |
| devClipBounds, aaType, stencilSettings); |
| } |
| |
| const char* name() const override { return "TessellatingPathOp"; } |
| |
| SkString dumpInfo() const override { |
| SkString string; |
| string.appendf("Color 0x%08x, aa: %d\n", fColor, fAntiAlias); |
| string += fHelper.dumpInfo(); |
| string += INHERITED::dumpInfo(); |
| return string; |
| } |
| |
| TessellatingPathOp(Helper::MakeArgs helperArgs, |
| GrColor color, |
| const GrShape& shape, |
| const SkMatrix& viewMatrix, |
| const SkIRect& devClipBounds, |
| GrAAType aaType, |
| const GrUserStencilSettings* stencilSettings) |
| : INHERITED(ClassID()) |
| , fHelper(helperArgs, 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::kNo, IsZeroArea::kNo); |
| } |
| |
| FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } |
| |
| RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override { |
| GrProcessorAnalysisCoverage coverage = fAntiAlias |
| ? GrProcessorAnalysisCoverage::kSingleChannel |
| : GrProcessorAnalysisCoverage::kNone; |
| return fHelper.xpRequiresDstTexture(caps, clip, coverage, &fColor); |
| } |
| |
| private: |
| SkPath getPath() const { |
| SkASSERT(!fShape.style().applies()); |
| SkPath path; |
| fShape.asPath(&path); |
| return path; |
| } |
| |
| void draw(Target* target, const GrGeometryProcessor* gp) { |
| SkASSERT(!fAntiAlias); |
| GrResourceProvider* rp = target->resourceProvider(); |
| bool inverseFill = fShape.inverseFilled(); |
| // construct a cache key from the path's genID and the view matrix |
| static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); |
| GrUniqueKey key; |
| static constexpr int kClipBoundsCnt = sizeof(fDevClipBounds) / sizeof(uint32_t); |
| int shapeKeyDataCnt = fShape.unstyledKeySize(); |
| SkASSERT(shapeKeyDataCnt >= 0); |
| GrUniqueKey::Builder builder(&key, kDomain, shapeKeyDataCnt + kClipBoundsCnt); |
| fShape.writeUnstyledKey(&builder[0]); |
| // For inverse fills, the tessellation is dependent on clip bounds. |
| if (inverseFill) { |
| memcpy(&builder[shapeKeyDataCnt], &fDevClipBounds, sizeof(fDevClipBounds)); |
| } else { |
| memset(&builder[shapeKeyDataCnt], 0, sizeof(fDevClipBounds)); |
| } |
| builder.finish(); |
| sk_sp<GrBuffer> cachedVertexBuffer(rp->findAndRefTByUniqueKey<GrBuffer>(key)); |
| int actualCount; |
| SkScalar tol = GrPathUtils::kDefaultTolerance; |
| tol = GrPathUtils::scaleToleranceToSrc(tol, fViewMatrix, fShape.bounds()); |
| if (cache_match(cachedVertexBuffer.get(), tol, &actualCount)) { |
| this->drawVertices(target, gp, cachedVertexBuffer.get(), 0, actualCount); |
| return; |
| } |
| |
| SkRect clipBounds = SkRect::Make(fDevClipBounds); |
| |
| SkMatrix vmi; |
| if (!fViewMatrix.invert(&vmi)) { |
| return; |
| } |
| vmi.mapRect(&clipBounds); |
| bool isLinear; |
| bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags(); |
| StaticVertexAllocator allocator(gp->getVertexStride(), rp, canMapVB); |
| int count = GrTessellator::PathToTriangles(getPath(), tol, clipBounds, &allocator, |
| false, GrColor(), false, &isLinear); |
| if (count == 0) { |
| return; |
| } |
| this->drawVertices(target, gp, allocator.vertexBuffer(), 0, count); |
| TessInfo info; |
| info.fTolerance = isLinear ? 0 : tol; |
| info.fCount = count; |
| key.setCustomData(SkData::MakeWithCopy(&info, sizeof(info))); |
| rp->assignUniqueKeyToResource(key, allocator.vertexBuffer()); |
| } |
| |
| void drawAA(Target* target, const GrGeometryProcessor* gp) { |
| SkASSERT(fAntiAlias); |
| SkPath path = getPath(); |
| if (path.isEmpty()) { |
| return; |
| } |
| SkRect clipBounds = SkRect::Make(fDevClipBounds); |
| path.transform(fViewMatrix); |
| SkScalar tol = GrPathUtils::kDefaultTolerance; |
| bool isLinear; |
| DynamicVertexAllocator allocator(gp->getVertexStride(), target); |
| int count = |
| GrTessellator::PathToTriangles(path, tol, clipBounds, &allocator, true, fColor, |
| fHelper.compatibleWithAlphaAsCoverage(), &isLinear); |
| if (count == 0) { |
| return; |
| } |
| this->drawVertices(target, gp, allocator.vertexBuffer(), allocator.firstVertex(), count); |
| } |
| |
| void onPrepareDraws(Target* target) override { |
| sk_sp<GrGeometryProcessor> gp; |
| { |
| using namespace GrDefaultGeoProcFactory; |
| |
| Color color(fColor); |
| LocalCoords::Type localCoordsType = fHelper.usesLocalCoords() |
| ? LocalCoords::kUsePosition_Type |
| : LocalCoords::kUnused_Type; |
| Coverage::Type coverageType; |
| if (fAntiAlias) { |
| color = Color(Color::kPremulGrColorAttribute_Type); |
| if (fHelper.compatibleWithAlphaAsCoverage()) { |
| coverageType = Coverage::kSolid_Type; |
| } else { |
| coverageType = Coverage::kAttribute_Type; |
| } |
| } else { |
| coverageType = Coverage::kSolid_Type; |
| } |
| if (fAntiAlias) { |
| gp = GrDefaultGeoProcFactory::MakeForDeviceSpace(color, coverageType, |
| localCoordsType, fViewMatrix); |
| } else { |
| gp = GrDefaultGeoProcFactory::Make(color, coverageType, localCoordsType, |
| fViewMatrix); |
| } |
| } |
| if (!gp.get()) { |
| return; |
| } |
| if (fAntiAlias) { |
| this->drawAA(target, gp.get()); |
| } else { |
| this->draw(target, gp.get()); |
| } |
| } |
| |
| void drawVertices(Target* target, const GrGeometryProcessor* gp, const GrBuffer* vb, |
| int firstVertex, int count) { |
| GrMesh mesh(TESSELLATOR_WIREFRAME ? GrPrimitiveType::kLines : GrPrimitiveType::kTriangles); |
| mesh.setNonIndexedNonInstanced(count); |
| mesh.setVertexData(vb, firstVertex); |
| target->draw(gp, fHelper.makePipeline(target), mesh); |
| } |
| |
| bool onCombineIfPossible(GrOp*, const GrCaps&) override { return false; } |
| |
| Helper fHelper; |
| GrColor fColor; |
| GrShape fShape; |
| SkMatrix fViewMatrix; |
| SkIRect fDevClipBounds; |
| bool fAntiAlias; |
| |
| typedef GrMeshDrawOp INHERITED; |
| }; |
| |
| } // anonymous namespace |
| |
| bool GrTessellatingPathRenderer::onDrawPath(const DrawPathArgs& args) { |
| GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(), |
| "GrTessellatingPathRenderer::onDrawPath"); |
| SkIRect clipBoundsI; |
| args.fClip->getConservativeBounds(args.fRenderTargetContext->width(), |
| args.fRenderTargetContext->height(), |
| &clipBoundsI); |
| std::unique_ptr<GrDrawOp> op = TessellatingPathOp::Make(std::move(args.fPaint), |
| *args.fShape, |
| *args.fViewMatrix, |
| clipBoundsI, |
| args.fAAType, |
| args.fUserStencilSettings); |
| args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); |
| return true; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| #if GR_TEST_UTILS |
| |
| GR_DRAW_OP_TEST_DEFINE(TesselatingPathOp) { |
| SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); |
| 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(SK_ARRAY_COUNT(kAATypes))]; |
| } while(GrAAType::kMSAA == aaType && GrFSAAType::kUnifiedMSAA != fsaaType); |
| GrStyle style; |
| do { |
| GrTest::TestStyle(random, &style); |
| } while (!style.isSimpleFill()); |
| GrShape shape(path, style); |
| return TessellatingPathOp::Make(std::move(paint), shape, viewMatrix, devClipBounds, aaType, |
| GrGetRandomStencil(random, context)); |
| } |
| |
| #endif |