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::v1::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::v1::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::v1::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::v1::PathStencilCoverOp>(rContext,
66 arena,
67 viewMatrix,
68 path,
69 std::move(paint),
70 aaType,
71 fillPathFlags,
72 drawBounds);
73 }
74
75 } // anonymous namespace
76
77 namespace skgpu::v1 {
78
IsSupported(const GrCaps & caps)79 bool TessellationPathRenderer::IsSupported(const GrCaps& caps) {
80 return !caps.avoidStencilBuffers() &&
81 caps.drawInstancedSupport() &&
82 !caps.disableTessellationPathRenderer();
83 }
84
onGetStencilSupport(const GrStyledShape & shape) const85 PathRenderer::StencilSupport TessellationPathRenderer::onGetStencilSupport(
86 const GrStyledShape& shape) const {
87 if (!shape.style().isSimpleFill() || shape.inverseFilled()) {
88 // Don't bother with stroke stencilling or inverse fills yet. The Skia API doesn't support
89 // clipping by a stroke, and the stencilling code already knows how to invert a fill.
90 return kNoSupport_StencilSupport;
91 }
92 return shape.knownToBeConvex() ? kNoRestriction_StencilSupport : kStencilOnly_StencilSupport;
93 }
94
onCanDrawPath(const CanDrawPathArgs & args) const95 PathRenderer::CanDrawPath TessellationPathRenderer::onCanDrawPath(
96 const CanDrawPathArgs& args) const {
97 const GrStyledShape& shape = *args.fShape;
98 if (args.fAAType == GrAAType::kCoverage ||
99 shape.style().hasPathEffect() ||
100 args.fViewMatrix->hasPerspective() ||
101 shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style ||
102 !args.fProxy->canUseStencil(*args.fCaps)) {
103 return CanDrawPath::kNo;
104 }
105 if (!shape.style().isSimpleFill()) {
106 if (shape.inverseFilled()) {
107 return CanDrawPath::kNo;
108 }
109 if (shape.style().strokeRec().getWidth() * args.fViewMatrix->getMaxScale() > 10000) {
110 // crbug.com/1266446 -- Don't draw massively wide strokes with the tessellator. Since we
111 // outset the viewport by stroke width for pre-chopping, astronomically wide strokes can
112 // result in an astronomical viewport size, and therefore an exponential explosion chops
113 // and memory usage. It is also simply inefficient to tessellate these strokes due to
114 // the number of radial edges required. We're better off just converting them to a path
115 // after a certain point.
116 return CanDrawPath::kNo;
117 }
118 }
119 if (args.fHasUserStencilSettings) {
120 // Non-convex paths and strokes use the stencil buffer internally, so they can't support
121 // draws with stencil settings.
122 if (!shape.style().isSimpleFill() || !shape.knownToBeConvex() || shape.inverseFilled()) {
123 return CanDrawPath::kNo;
124 }
125 }
126 return CanDrawPath::kYes;
127 }
128
onDrawPath(const DrawPathArgs & args)129 bool TessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
130 auto sdc = args.fSurfaceDrawContext;
131
132 SkPath path;
133 args.fShape->asPath(&path);
134
135 const SkRect pathDevBounds = args.fViewMatrix->mapRect(args.fShape->bounds());
136 float n4 = wangs_formula::worst_case_cubic_p4(tess::kPrecision,
137 pathDevBounds.width(),
138 pathDevBounds.height());
139 if (n4 > tess::kMaxSegmentsPerCurve_p4) {
140 // The path is extremely large. Pre-chop its curves to keep the number of tessellation
141 // segments tractable. This will also flatten curves that fall completely outside the
142 // viewport.
143 SkRect viewport = SkRect::Make(*args.fClipConservativeBounds);
144 if (!args.fShape->style().isSimpleFill()) {
145 // Outset the viewport to pad for the stroke width.
146 const SkStrokeRec& stroke = args.fShape->style().strokeRec();
147 float inflationRadius;
148 if (stroke.isHairlineStyle()) {
149 // SkStrokeRec::getInflationRadius() doesn't handle hairlines robustly. Instead
150 // find the inflation of an equivalent stroke in device space with a width of 1.
151 inflationRadius = SkStrokeRec::GetInflationRadius(stroke.getJoin(),
152 stroke.getMiter(),
153 stroke.getCap(), 1);
154 } else {
155 inflationRadius = stroke.getInflationRadius() * args.fViewMatrix->getMaxScale();
156 }
157 viewport.outset(inflationRadius, inflationRadius);
158 }
159 path = PreChopPathCurves(tess::kPrecision, path, *args.fViewMatrix, viewport);
160 }
161
162 // Handle strokes first.
163 if (!args.fShape->style().isSimpleFill()) {
164 SkASSERT(!path.isInverseFillType()); // See onGetStencilSupport().
165 SkASSERT(args.fUserStencilSettings->isUnused());
166 const SkStrokeRec& stroke = args.fShape->style().strokeRec();
167 SkASSERT(stroke.getStyle() != SkStrokeRec::kStrokeAndFill_Style);
168 auto op = GrOp::Make<StrokeTessellateOp>(args.fContext, args.fAAType, *args.fViewMatrix,
169 path, stroke, std::move(args.fPaint));
170 sdc->addDrawOp(args.fClip, std::move(op));
171 return true;
172 }
173
174 // Handle empty paths.
175 if (pathDevBounds.isEmpty()) {
176 if (path.isInverseFillType()) {
177 args.fSurfaceDrawContext->drawPaint(args.fClip, std::move(args.fPaint),
178 *args.fViewMatrix);
179 }
180 return true;
181 }
182
183 // Handle convex paths. Make sure to check 'path' for convexity since it may have been
184 // pre-chopped, not 'fShape'.
185 if (path.isConvex() && !path.isInverseFillType()) {
186 auto op = GrOp::Make<PathTessellateOp>(args.fContext,
187 args.fSurfaceDrawContext->arenaAlloc(),
188 args.fAAType,
189 args.fUserStencilSettings,
190 *args.fViewMatrix,
191 path,
192 std::move(args.fPaint),
193 pathDevBounds);
194 sdc->addDrawOp(args.fClip, std::move(op));
195 return true;
196 }
197
198 SkASSERT(args.fUserStencilSettings->isUnused()); // See onGetStencilSupport().
199 const SkRect& drawBounds = path.isInverseFillType()
200 ? args.fSurfaceDrawContext->asSurfaceProxy()->backingStoreBoundsRect()
201 : pathDevBounds;
202 auto op = make_non_convex_fill_op(args.fContext,
203 args.fSurfaceDrawContext->arenaAlloc(),
204 FillPathFlags::kNone,
205 args.fAAType,
206 drawBounds,
207 *args.fClipConservativeBounds,
208 *args.fViewMatrix,
209 path,
210 std::move(args.fPaint));
211 sdc->addDrawOp(args.fClip, std::move(op));
212 return true;
213 }
214
onStencilPath(const StencilPathArgs & args)215 void TessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
216 SkASSERT(args.fShape->style().isSimpleFill()); // See onGetStencilSupport().
217 SkASSERT(!args.fShape->inverseFilled()); // See onGetStencilSupport().
218
219 auto sdc = args.fSurfaceDrawContext;
220 GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
221
222 SkRect pathDevBounds;
223 args.fViewMatrix->mapRect(&pathDevBounds, args.fShape->bounds());
224
225 SkPath path;
226 args.fShape->asPath(&path);
227
228 float n4 = wangs_formula::worst_case_cubic_p4(tess::kPrecision,
229 pathDevBounds.width(),
230 pathDevBounds.height());
231 if (n4 > tess::kMaxSegmentsPerCurve_p4) {
232 SkRect viewport = SkRect::Make(*args.fClipConservativeBounds);
233 path = PreChopPathCurves(tess::kPrecision, path, *args.fViewMatrix, viewport);
234 }
235
236 // Make sure to check 'path' for convexity since it may have been pre-chopped, not 'fShape'.
237 if (path.isConvex()) {
238 constexpr static GrUserStencilSettings kMarkStencil(
239 GrUserStencilSettings::StaticInit<
240 0x0001,
241 GrUserStencilTest::kAlways,
242 0xffff,
243 GrUserStencilOp::kReplace,
244 GrUserStencilOp::kKeep,
245 0xffff>());
246
247 GrPaint stencilPaint;
248 stencilPaint.setXPFactory(GrDisableColorXPFactory::Get());
249 auto op = GrOp::Make<PathTessellateOp>(args.fContext,
250 args.fSurfaceDrawContext->arenaAlloc(),
251 aaType,
252 &kMarkStencil,
253 *args.fViewMatrix,
254 path,
255 std::move(stencilPaint),
256 pathDevBounds);
257 sdc->addDrawOp(args.fClip, std::move(op));
258 return;
259 }
260
261 auto op = make_non_convex_fill_op(args.fContext,
262 args.fSurfaceDrawContext->arenaAlloc(),
263 FillPathFlags::kStencilOnly,
264 aaType,
265 pathDevBounds,
266 *args.fClipConservativeBounds,
267 *args.fViewMatrix,
268 path,
269 GrPaint());
270 sdc->addDrawOp(args.fClip, std::move(op));
271 }
272
273 } // namespace skgpu::v1
274