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