xref: /aosp_15_r20/external/skia/src/gpu/ganesh/ops/TessellationPathRenderer.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2019 Google LLC.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 #include "src/gpu/ganesh/ops/TessellationPathRenderer.h"
8 
9 #include "include/core/SkMatrix.h"
10 #include "include/core/SkPath.h"
11 #include "include/core/SkRect.h"
12 #include "include/core/SkStrokeRec.h"
13 #include "include/private/base/SkAssert.h"
14 #include "include/private/gpu/ganesh/GrTypesPriv.h"
15 #include "src/base/SkMathPriv.h"
16 #include "src/gpu/ganesh/GrCaps.h"
17 #include "src/gpu/ganesh/GrClip.h"
18 #include "src/gpu/ganesh/GrPaint.h"
19 #include "src/gpu/ganesh/GrRenderTargetProxy.h"
20 #include "src/gpu/ganesh/GrStyle.h"
21 #include "src/gpu/ganesh/GrSurfaceProxy.h"
22 #include "src/gpu/ganesh/GrUserStencilSettings.h"
23 #include "src/gpu/ganesh/SurfaceDrawContext.h"
24 #include "src/gpu/ganesh/effects/GrDisableColorXP.h"
25 #include "src/gpu/ganesh/geometry/GrStyledShape.h"
26 #include "src/gpu/ganesh/ops/FillPathFlags.h"
27 #include "src/gpu/ganesh/ops/GrOp.h"
28 #include "src/gpu/ganesh/ops/PathInnerTriangulateOp.h"
29 #include "src/gpu/ganesh/ops/PathStencilCoverOp.h"
30 #include "src/gpu/ganesh/ops/PathTessellateOp.h"
31 #include "src/gpu/ganesh/ops/StrokeTessellateOp.h"
32 #include "src/gpu/tessellate/Tessellation.h"
33 #include "src/gpu/tessellate/WangsFormula.h"
34 
35 #include <utility>
36 
37 class GrRecordingContext;
38 class SkArenaAlloc;
39 
40 namespace {
41 
42 using namespace skgpu::tess;
43 
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)44 GrOp::Owner make_non_convex_fill_op(GrRecordingContext* rContext,
45                                     SkArenaAlloc* arena,
46                                     skgpu::ganesh::FillPathFlags fillPathFlags,
47                                     GrAAType aaType,
48                                     const SkRect& drawBounds,
49                                     const SkIRect& clipBounds,
50                                     const SkMatrix& viewMatrix,
51                                     const SkPath& path,
52                                     GrPaint&& paint) {
53     SkASSERT(!path.isConvex() || path.isInverseFillType());
54 #if !defined(SK_ENABLE_OPTIMIZE_SIZE)
55     int numVerbs = path.countVerbs();
56     if (numVerbs > 0 && !path.isInverseFillType()) {
57         // Check if the path is large and/or simple enough that we can triangulate the inner fan
58         // on the CPU. This is our fastest approach. It allows us to stencil only the curves,
59         // and then fill the inner fan directly to the final render target, thus drawing the
60         // majority of pixels in a single render pass.
61         SkRect clippedDrawBounds = SkRect::Make(clipBounds);
62         if (clippedDrawBounds.intersect(drawBounds)) {
63             float gpuFragmentWork = clippedDrawBounds.height() * clippedDrawBounds.width();
64             float cpuTessellationWork = numVerbs * SkNextLog2(numVerbs);  // N log N.
65             constexpr static float kCpuWeight = 512;
66             constexpr static float kMinNumPixelsToTriangulate = 256 * 256;
67             if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) {
68                 return GrOp::Make<skgpu::ganesh::PathInnerTriangulateOp>(rContext,
69                                                                          viewMatrix,
70                                                                          path,
71                                                                          std::move(paint),
72                                                                          aaType,
73                                                                          fillPathFlags,
74                                                                          drawBounds);
75             }
76         } // we should be clipped out when the GrClip is analyzed, so just return the default op
77     }
78 #endif
79 
80     return GrOp::Make<skgpu::ganesh::PathStencilCoverOp>(
81             rContext, arena, viewMatrix, path, std::move(paint), aaType, fillPathFlags, drawBounds);
82 }
83 
84 } // anonymous namespace
85 
86 namespace skgpu::ganesh {
87 
88 namespace {
89 
90 // `chopped_path` may be null, in which case no chopping actually happens. Returns true on success,
91 // false on failure (chopping not allowed).
ChopPathIfNecessary(const SkMatrix & viewMatrix,const GrStyledShape & shape,const SkIRect & clipConservativeBounds,const SkStrokeRec & stroke,SkPath * chopped_path)92 bool ChopPathIfNecessary(const SkMatrix& viewMatrix,
93                          const GrStyledShape& shape,
94                          const SkIRect& clipConservativeBounds,
95                          const SkStrokeRec& stroke,
96                          SkPath* chopped_path) {
97     const SkRect pathDevBounds = viewMatrix.mapRect(shape.bounds());
98     float n4 = wangs_formula::worst_case_cubic_p4(tess::kPrecision,
99                                                   pathDevBounds.width(),
100                                                   pathDevBounds.height());
101     if (n4 > tess::kMaxSegmentsPerCurve_p4 && shape.segmentMask() != SkPath::kLine_SegmentMask) {
102         // The path is extremely large. Pre-chop its curves to keep the number of tessellation
103         // segments tractable. This will also flatten curves that fall completely outside the
104         // viewport.
105         SkRect viewport = SkRect::Make(clipConservativeBounds);
106         if (!shape.style().isSimpleFill()) {
107             // Outset the viewport to pad for the stroke width.
108             float inflationRadius;
109             if (stroke.isHairlineStyle()) {
110                 // SkStrokeRec::getInflationRadius() doesn't handle hairlines robustly. Instead
111                 // find the inflation of an equivalent stroke in device space with a width of 1.
112                 inflationRadius = SkStrokeRec::GetInflationRadius(stroke.getJoin(),
113                                                                   stroke.getMiter(),
114                                                                   stroke.getCap(), 1);
115             } else {
116                 inflationRadius = stroke.getInflationRadius() * viewMatrix.getMaxScale();
117             }
118             viewport.outset(inflationRadius, inflationRadius);
119         }
120         if (wangs_formula::worst_case_cubic(
121                      tess::kPrecision,
122                      viewport.width(),
123                      viewport.height()) > kMaxSegmentsPerCurve) {
124             return false;
125         }
126         if (chopped_path) {
127             *chopped_path = PreChopPathCurves(tess::kPrecision, *chopped_path, viewMatrix,
128                                               viewport);
129         }
130     }
131     return true;
132 }
133 
134 } // anonymous namespace
135 
IsSupported(const GrCaps & caps)136 bool TessellationPathRenderer::IsSupported(const GrCaps& caps) {
137     return !caps.avoidStencilBuffers() &&
138            caps.drawInstancedSupport() &&
139            !caps.disableTessellationPathRenderer();
140 }
141 
onGetStencilSupport(const GrStyledShape & shape) const142 PathRenderer::StencilSupport TessellationPathRenderer::onGetStencilSupport(
143         const GrStyledShape& shape) const {
144     if (!shape.style().isSimpleFill() || shape.inverseFilled()) {
145         // Don't bother with stroke stencilling or inverse fills yet. The Skia API doesn't support
146         // clipping by a stroke, and the stencilling code already knows how to invert a fill.
147         return kNoSupport_StencilSupport;
148     }
149     return shape.knownToBeConvex() ? kNoRestriction_StencilSupport : kStencilOnly_StencilSupport;
150 }
151 
onCanDrawPath(const CanDrawPathArgs & args) const152 PathRenderer::CanDrawPath TessellationPathRenderer::onCanDrawPath(
153         const CanDrawPathArgs& args) const {
154     const GrStyledShape& shape = *args.fShape;
155     if (args.fAAType == GrAAType::kCoverage ||
156         shape.style().hasPathEffect() ||
157         args.fViewMatrix->hasPerspective() ||
158         shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style ||
159         !args.fProxy->canUseStencil(*args.fCaps)) {
160         return CanDrawPath::kNo;
161     }
162     if (!shape.style().isSimpleFill()) {
163         if (shape.inverseFilled()) {
164             return CanDrawPath::kNo;
165         }
166         if (shape.style().strokeRec().getWidth() * args.fViewMatrix->getMaxScale() > 10000) {
167             // crbug.com/1266446 -- Don't draw massively wide strokes with the tessellator. Since we
168             // outset the viewport by stroke width for pre-chopping, astronomically wide strokes can
169             // result in an astronomical viewport size, and therefore an exponential explosion chops
170             // and memory usage. It is also simply inefficient to tessellate these strokes due to
171             // the number of radial edges required. We're better off just converting them to a path
172             // after a certain point.
173             return CanDrawPath::kNo;
174         }
175     }
176     if (args.fHasUserStencilSettings) {
177         // Non-convex paths and strokes use the stencil buffer internally, so they can't support
178         // draws with stencil settings.
179         if (!shape.style().isSimpleFill() || !shape.knownToBeConvex() || shape.inverseFilled()) {
180             return CanDrawPath::kNo;
181         }
182     }
183 
184     // By passing in null for the chopped-path no chopping happens. Rather this returns whether
185     // chopping is possible.
186     if (!ChopPathIfNecessary(*args.fViewMatrix, shape, *args.fClipConservativeBounds,
187                              shape.style().strokeRec(), nullptr)) {
188         return CanDrawPath::kNo;
189     }
190 
191     return CanDrawPath::kYes;
192 }
193 
onDrawPath(const DrawPathArgs & args)194 bool TessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
195     auto sdc = args.fSurfaceDrawContext;
196 
197     SkPath path;
198     args.fShape->asPath(&path);
199 
200     // onDrawPath() should only be called if ChopPathIfNecessary() succeeded.
201     SkAssertResult(ChopPathIfNecessary(*args.fViewMatrix, *args.fShape,
202                                        *args.fClipConservativeBounds,
203                                        args.fShape->style().strokeRec(), &path));
204 
205     // Handle strokes first.
206     if (!args.fShape->style().isSimpleFill()) {
207         SkASSERT(!path.isInverseFillType());  // See onGetStencilSupport().
208         SkASSERT(args.fUserStencilSettings->isUnused());
209         const SkStrokeRec& stroke = args.fShape->style().strokeRec();
210         SkASSERT(stroke.getStyle() != SkStrokeRec::kStrokeAndFill_Style);
211         auto op = GrOp::Make<StrokeTessellateOp>(args.fContext, args.fAAType, *args.fViewMatrix,
212                                                  path, stroke, std::move(args.fPaint));
213         sdc->addDrawOp(args.fClip, std::move(op));
214         return true;
215     }
216 
217     // Handle empty paths.
218     const SkRect pathDevBounds = args.fViewMatrix->mapRect(args.fShape->bounds());
219     if (pathDevBounds.isEmpty()) {
220         if (path.isInverseFillType()) {
221             args.fSurfaceDrawContext->drawPaint(args.fClip, std::move(args.fPaint),
222                                                 *args.fViewMatrix);
223         }
224         return true;
225     }
226 
227     // Handle convex paths. Make sure to check 'path' for convexity since it may have been
228     // pre-chopped, not 'fShape'.
229     if (path.isConvex() && !path.isInverseFillType()) {
230         auto op = GrOp::Make<PathTessellateOp>(args.fContext,
231                                                args.fSurfaceDrawContext->arenaAlloc(),
232                                                args.fAAType,
233                                                args.fUserStencilSettings,
234                                                *args.fViewMatrix,
235                                                path,
236                                                std::move(args.fPaint),
237                                                pathDevBounds);
238         sdc->addDrawOp(args.fClip, std::move(op));
239         return true;
240     }
241 
242     SkASSERT(args.fUserStencilSettings->isUnused());  // See onGetStencilSupport().
243     const SkRect& drawBounds = path.isInverseFillType()
244             ? args.fSurfaceDrawContext->asSurfaceProxy()->backingStoreBoundsRect()
245             : pathDevBounds;
246     auto op = make_non_convex_fill_op(args.fContext,
247                                       args.fSurfaceDrawContext->arenaAlloc(),
248                                       FillPathFlags::kNone,
249                                       args.fAAType,
250                                       drawBounds,
251                                       *args.fClipConservativeBounds,
252                                       *args.fViewMatrix,
253                                       path,
254                                       std::move(args.fPaint));
255     sdc->addDrawOp(args.fClip, std::move(op));
256     return true;
257 }
258 
onStencilPath(const StencilPathArgs & args)259 void TessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
260     SkASSERT(args.fShape->style().isSimpleFill());  // See onGetStencilSupport().
261     SkASSERT(!args.fShape->inverseFilled());  // See onGetStencilSupport().
262 
263     auto sdc = args.fSurfaceDrawContext;
264     GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
265 
266     SkRect pathDevBounds;
267     args.fViewMatrix->mapRect(&pathDevBounds, args.fShape->bounds());
268 
269     SkPath path;
270     args.fShape->asPath(&path);
271 
272     float n4 = wangs_formula::worst_case_cubic_p4(tess::kPrecision,
273                                                   pathDevBounds.width(),
274                                                   pathDevBounds.height());
275     if (n4 > tess::kMaxSegmentsPerCurve_p4) {
276         SkRect viewport = SkRect::Make(*args.fClipConservativeBounds);
277         path = PreChopPathCurves(tess::kPrecision, path, *args.fViewMatrix, viewport);
278     }
279 
280     // Make sure to check 'path' for convexity since it may have been pre-chopped, not 'fShape'.
281     if (path.isConvex()) {
282         constexpr static GrUserStencilSettings kMarkStencil(
283             GrUserStencilSettings::StaticInit<
284                 0x0001,
285                 GrUserStencilTest::kAlways,
286                 0xffff,
287                 GrUserStencilOp::kReplace,
288                 GrUserStencilOp::kKeep,
289                 0xffff>());
290 
291         GrPaint stencilPaint;
292         stencilPaint.setXPFactory(GrDisableColorXPFactory::Get());
293         auto op = GrOp::Make<PathTessellateOp>(args.fContext,
294                                                args.fSurfaceDrawContext->arenaAlloc(),
295                                                aaType,
296                                                &kMarkStencil,
297                                                *args.fViewMatrix,
298                                                path,
299                                                std::move(stencilPaint),
300                                                pathDevBounds);
301         sdc->addDrawOp(args.fClip, std::move(op));
302         return;
303     }
304 
305     auto op = make_non_convex_fill_op(args.fContext,
306                                       args.fSurfaceDrawContext->arenaAlloc(),
307                                       FillPathFlags::kStencilOnly,
308                                       aaType,
309                                       pathDevBounds,
310                                       *args.fClipConservativeBounds,
311                                       *args.fViewMatrix,
312                                       path,
313                                       GrPaint());
314     sdc->addDrawOp(args.fClip, std::move(op));
315 }
316 
317 }  // namespace skgpu::ganesh
318