| /* |
| * 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 "src/core/SkPathPriv.h" |
| #include "src/gpu/ganesh/GrCaps.h" |
| #include "src/gpu/ganesh/GrClip.h" |
| #include "src/gpu/ganesh/GrMemoryPool.h" |
| #include "src/gpu/ganesh/GrRecordingContextPriv.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/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" |
| |
| 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<skgpu::ganesh::PathInnerTriangulateOp>(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<skgpu::ganesh::PathStencilCoverOp>( |
| 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<StrokeTessellateOp>(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<PathTessellateOp>(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<PathTessellateOp>(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 |