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