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