/* * Copyright 2019 Google LLC. * * 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/TessellationPathRenderer.h" #include "include/core/SkMatrix.h" #include "include/core/SkPath.h" #include "include/core/SkRect.h" #include "include/core/SkStrokeRec.h" #include "include/private/base/SkAssert.h" #include "include/private/gpu/ganesh/GrTypesPriv.h" #include "src/base/SkMathPriv.h" #include "src/gpu/ganesh/GrCaps.h" #include "src/gpu/ganesh/GrClip.h" #include "src/gpu/ganesh/GrPaint.h" #include "src/gpu/ganesh/GrRenderTargetProxy.h" #include "src/gpu/ganesh/GrStyle.h" #include "src/gpu/ganesh/GrSurfaceProxy.h" #include "src/gpu/ganesh/GrUserStencilSettings.h" #include "src/gpu/ganesh/SurfaceDrawContext.h" #include "src/gpu/ganesh/effects/GrDisableColorXP.h" #include "src/gpu/ganesh/geometry/GrStyledShape.h" #include "src/gpu/ganesh/ops/FillPathFlags.h" #include "src/gpu/ganesh/ops/GrOp.h" #include "src/gpu/ganesh/ops/PathInnerTriangulateOp.h" #include "src/gpu/ganesh/ops/PathStencilCoverOp.h" #include "src/gpu/ganesh/ops/PathTessellateOp.h" #include "src/gpu/ganesh/ops/StrokeTessellateOp.h" #include "src/gpu/tessellate/Tessellation.h" #include "src/gpu/tessellate/WangsFormula.h" #include class GrRecordingContext; class SkArenaAlloc; namespace { using namespace skgpu::tess; GrOp::Owner make_non_convex_fill_op(GrRecordingContext* rContext, SkArenaAlloc* arena, skgpu::ganesh::FillPathFlags fillPathFlags, GrAAType aaType, const SkRect& drawBounds, const SkIRect& clipBounds, const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint) { SkASSERT(!path.isConvex() || path.isInverseFillType()); #if !defined(SK_ENABLE_OPTIMIZE_SIZE) int numVerbs = path.countVerbs(); if (numVerbs > 0 && !path.isInverseFillType()) { // Check if the path is large and/or simple enough that we can triangulate the inner fan // on the CPU. This is our fastest approach. It allows us to stencil only the curves, // and then fill the inner fan directly to the final render target, thus drawing the // majority of pixels in a single render pass. SkRect clippedDrawBounds = SkRect::Make(clipBounds); if (clippedDrawBounds.intersect(drawBounds)) { float gpuFragmentWork = clippedDrawBounds.height() * clippedDrawBounds.width(); float cpuTessellationWork = numVerbs * SkNextLog2(numVerbs); // N log N. constexpr static float kCpuWeight = 512; constexpr static float kMinNumPixelsToTriangulate = 256 * 256; if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) { return GrOp::Make(rContext, viewMatrix, path, std::move(paint), aaType, fillPathFlags, drawBounds); } } // we should be clipped out when the GrClip is analyzed, so just return the default op } #endif return GrOp::Make( rContext, arena, viewMatrix, path, std::move(paint), aaType, fillPathFlags, drawBounds); } } // anonymous namespace namespace skgpu::ganesh { namespace { // `chopped_path` may be null, in which case no chopping actually happens. Returns true on success, // false on failure (chopping not allowed). bool ChopPathIfNecessary(const SkMatrix& viewMatrix, const GrStyledShape& shape, const SkIRect& clipConservativeBounds, const SkStrokeRec& stroke, SkPath* chopped_path) { const SkRect pathDevBounds = viewMatrix.mapRect(shape.bounds()); float n4 = wangs_formula::worst_case_cubic_p4(tess::kPrecision, pathDevBounds.width(), pathDevBounds.height()); if (n4 > tess::kMaxSegmentsPerCurve_p4 && shape.segmentMask() != SkPath::kLine_SegmentMask) { // The path is extremely large. Pre-chop its curves to keep the number of tessellation // segments tractable. This will also flatten curves that fall completely outside the // viewport. SkRect viewport = SkRect::Make(clipConservativeBounds); if (!shape.style().isSimpleFill()) { // Outset the viewport to pad for the stroke width. float inflationRadius; if (stroke.isHairlineStyle()) { // SkStrokeRec::getInflationRadius() doesn't handle hairlines robustly. Instead // find the inflation of an equivalent stroke in device space with a width of 1. inflationRadius = SkStrokeRec::GetInflationRadius(stroke.getJoin(), stroke.getMiter(), stroke.getCap(), 1); } else { inflationRadius = stroke.getInflationRadius() * viewMatrix.getMaxScale(); } viewport.outset(inflationRadius, inflationRadius); } if (wangs_formula::worst_case_cubic( tess::kPrecision, viewport.width(), viewport.height()) > kMaxSegmentsPerCurve) { return false; } if (chopped_path) { *chopped_path = PreChopPathCurves(tess::kPrecision, *chopped_path, viewMatrix, viewport); } } return true; } } // anonymous namespace bool TessellationPathRenderer::IsSupported(const GrCaps& caps) { return !caps.avoidStencilBuffers() && caps.drawInstancedSupport() && !caps.disableTessellationPathRenderer(); } PathRenderer::StencilSupport TessellationPathRenderer::onGetStencilSupport( const GrStyledShape& shape) const { if (!shape.style().isSimpleFill() || shape.inverseFilled()) { // Don't bother with stroke stencilling or inverse fills yet. The Skia API doesn't support // clipping by a stroke, and the stencilling code already knows how to invert a fill. return kNoSupport_StencilSupport; } return shape.knownToBeConvex() ? kNoRestriction_StencilSupport : kStencilOnly_StencilSupport; } PathRenderer::CanDrawPath TessellationPathRenderer::onCanDrawPath( const CanDrawPathArgs& args) const { const GrStyledShape& shape = *args.fShape; if (args.fAAType == GrAAType::kCoverage || shape.style().hasPathEffect() || args.fViewMatrix->hasPerspective() || shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style || !args.fProxy->canUseStencil(*args.fCaps)) { return CanDrawPath::kNo; } if (!shape.style().isSimpleFill()) { if (shape.inverseFilled()) { return CanDrawPath::kNo; } if (shape.style().strokeRec().getWidth() * args.fViewMatrix->getMaxScale() > 10000) { // crbug.com/1266446 -- Don't draw massively wide strokes with the tessellator. Since we // outset the viewport by stroke width for pre-chopping, astronomically wide strokes can // result in an astronomical viewport size, and therefore an exponential explosion chops // and memory usage. It is also simply inefficient to tessellate these strokes due to // the number of radial edges required. We're better off just converting them to a path // after a certain point. return CanDrawPath::kNo; } } if (args.fHasUserStencilSettings) { // Non-convex paths and strokes use the stencil buffer internally, so they can't support // draws with stencil settings. if (!shape.style().isSimpleFill() || !shape.knownToBeConvex() || shape.inverseFilled()) { return CanDrawPath::kNo; } } // By passing in null for the chopped-path no chopping happens. Rather this returns whether // chopping is possible. if (!ChopPathIfNecessary(*args.fViewMatrix, shape, *args.fClipConservativeBounds, shape.style().strokeRec(), nullptr)) { return CanDrawPath::kNo; } return CanDrawPath::kYes; } bool TessellationPathRenderer::onDrawPath(const DrawPathArgs& args) { auto sdc = args.fSurfaceDrawContext; SkPath path; args.fShape->asPath(&path); // onDrawPath() should only be called if ChopPathIfNecessary() succeeded. SkAssertResult(ChopPathIfNecessary(*args.fViewMatrix, *args.fShape, *args.fClipConservativeBounds, args.fShape->style().strokeRec(), &path)); // Handle strokes first. if (!args.fShape->style().isSimpleFill()) { SkASSERT(!path.isInverseFillType()); // See onGetStencilSupport(). SkASSERT(args.fUserStencilSettings->isUnused()); const SkStrokeRec& stroke = args.fShape->style().strokeRec(); SkASSERT(stroke.getStyle() != SkStrokeRec::kStrokeAndFill_Style); auto op = GrOp::Make(args.fContext, args.fAAType, *args.fViewMatrix, path, stroke, std::move(args.fPaint)); sdc->addDrawOp(args.fClip, std::move(op)); return true; } // Handle empty paths. const SkRect pathDevBounds = args.fViewMatrix->mapRect(args.fShape->bounds()); if (pathDevBounds.isEmpty()) { if (path.isInverseFillType()) { args.fSurfaceDrawContext->drawPaint(args.fClip, std::move(args.fPaint), *args.fViewMatrix); } return true; } // Handle convex paths. Make sure to check 'path' for convexity since it may have been // pre-chopped, not 'fShape'. if (path.isConvex() && !path.isInverseFillType()) { auto op = GrOp::Make(args.fContext, args.fSurfaceDrawContext->arenaAlloc(), args.fAAType, args.fUserStencilSettings, *args.fViewMatrix, path, std::move(args.fPaint), pathDevBounds); sdc->addDrawOp(args.fClip, std::move(op)); return true; } SkASSERT(args.fUserStencilSettings->isUnused()); // See onGetStencilSupport(). const SkRect& drawBounds = path.isInverseFillType() ? args.fSurfaceDrawContext->asSurfaceProxy()->backingStoreBoundsRect() : pathDevBounds; auto op = make_non_convex_fill_op(args.fContext, args.fSurfaceDrawContext->arenaAlloc(), FillPathFlags::kNone, args.fAAType, drawBounds, *args.fClipConservativeBounds, *args.fViewMatrix, path, std::move(args.fPaint)); sdc->addDrawOp(args.fClip, std::move(op)); return true; } void TessellationPathRenderer::onStencilPath(const StencilPathArgs& args) { SkASSERT(args.fShape->style().isSimpleFill()); // See onGetStencilSupport(). SkASSERT(!args.fShape->inverseFilled()); // See onGetStencilSupport(). auto sdc = args.fSurfaceDrawContext; GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone; SkRect pathDevBounds; args.fViewMatrix->mapRect(&pathDevBounds, args.fShape->bounds()); SkPath path; args.fShape->asPath(&path); float n4 = wangs_formula::worst_case_cubic_p4(tess::kPrecision, pathDevBounds.width(), pathDevBounds.height()); if (n4 > tess::kMaxSegmentsPerCurve_p4) { SkRect viewport = SkRect::Make(*args.fClipConservativeBounds); path = PreChopPathCurves(tess::kPrecision, path, *args.fViewMatrix, viewport); } // Make sure to check 'path' for convexity since it may have been pre-chopped, not 'fShape'. if (path.isConvex()) { constexpr static GrUserStencilSettings kMarkStencil( GrUserStencilSettings::StaticInit< 0x0001, GrUserStencilTest::kAlways, 0xffff, GrUserStencilOp::kReplace, GrUserStencilOp::kKeep, 0xffff>()); GrPaint stencilPaint; stencilPaint.setXPFactory(GrDisableColorXPFactory::Get()); auto op = GrOp::Make(args.fContext, args.fSurfaceDrawContext->arenaAlloc(), aaType, &kMarkStencil, *args.fViewMatrix, path, std::move(stencilPaint), pathDevBounds); sdc->addDrawOp(args.fClip, std::move(op)); return; } auto op = make_non_convex_fill_op(args.fContext, args.fSurfaceDrawContext->arenaAlloc(), FillPathFlags::kStencilOnly, aaType, pathDevBounds, *args.fClipConservativeBounds, *args.fViewMatrix, path, GrPaint()); sdc->addDrawOp(args.fClip, std::move(op)); } } // namespace skgpu::ganesh