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