1 /*
2 * Copyright 2020 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/StencilMaskHelper.h"
9
10 #include "include/core/SkMatrix.h"
11 #include "include/core/SkPath.h"
12 #include "include/core/SkRegion.h"
13 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
14 #include "src/gpu/ganesh/GrStencilSettings.h"
15 #include "src/gpu/ganesh/SurfaceDrawContext.h"
16 #include "src/gpu/ganesh/effects/GrDisableColorXP.h"
17 #include "src/gpu/ganesh/geometry/GrShape.h"
18 #include "src/gpu/ganesh/geometry/GrStyledShape.h"
19
20 namespace {
21
22 ////////////////////////////////////////////////////////////////////////////////
23 // Stencil Rules for Merging user stencil space into clip
24 //
25
26 ///////
27 // Replace
28 static constexpr GrUserStencilSettings gUserToClipReplace(
29 GrUserStencilSettings::StaticInit<
30 0x0000,
31 GrUserStencilTest::kNotEqual,
32 0xffff,
33 GrUserStencilOp::kSetClipAndReplaceUserBits,
34 GrUserStencilOp::kZeroClipAndUserBits,
35 0xffff>()
36 );
37
38 static constexpr GrUserStencilSettings gInvUserToClipReplace(
39 GrUserStencilSettings::StaticInit<
40 0x0000,
41 GrUserStencilTest::kEqual,
42 0xffff,
43 GrUserStencilOp::kSetClipAndReplaceUserBits,
44 GrUserStencilOp::kZeroClipAndUserBits,
45 0xffff>()
46 );
47
48 ///////
49 // Intersect
50 static constexpr GrUserStencilSettings gUserToClipIsect(
51 GrUserStencilSettings::StaticInit<
52 0x0000,
53 GrUserStencilTest::kLessIfInClip, // "0 < userBits" is equivalent to "0 != userBits".
54 0xffff,
55 GrUserStencilOp::kSetClipAndReplaceUserBits,
56 GrUserStencilOp::kZeroClipAndUserBits,
57 0xffff>()
58 );
59
60 ///////
61 // Difference
62 static constexpr GrUserStencilSettings gUserToClipDiff(
63 GrUserStencilSettings::StaticInit<
64 0x0000,
65 GrUserStencilTest::kEqualIfInClip,
66 0xffff,
67 GrUserStencilOp::kSetClipAndReplaceUserBits,
68 GrUserStencilOp::kZeroClipAndUserBits,
69 0xffff>()
70 );
71
72 ///////
73 // Union
74 static constexpr GrUserStencilSettings gUserToClipUnion(
75 GrUserStencilSettings::StaticInit<
76 0x0000,
77 GrUserStencilTest::kNotEqual,
78 0xffff,
79 GrUserStencilOp::kSetClipAndReplaceUserBits,
80 GrUserStencilOp::kKeep,
81 0xffff>()
82 );
83
84 static constexpr GrUserStencilSettings gInvUserToClipUnionPass0( // Does not zero user bits.
85 GrUserStencilSettings::StaticInit<
86 0x0000,
87 GrUserStencilTest::kEqual,
88 0xffff,
89 GrUserStencilOp::kSetClipBit,
90 GrUserStencilOp::kKeep,
91 0x0000>()
92 );
93
94 ///////
95 // Xor
96 static constexpr GrUserStencilSettings gUserToClipXorPass0( // Does not zero user bits.
97 GrUserStencilSettings::StaticInit<
98 0x0000,
99 GrUserStencilTest::kNotEqual,
100 0xffff,
101 GrUserStencilOp::kInvertClipBit,
102 GrUserStencilOp::kKeep,
103 0x0000>()
104 );
105
106 static constexpr GrUserStencilSettings gInvUserToClipXorPass0( // Does not zero user bits.
107 GrUserStencilSettings::StaticInit<
108 0x0000,
109 GrUserStencilTest::kEqual,
110 0xffff,
111 GrUserStencilOp::kInvertClipBit,
112 GrUserStencilOp::kKeep,
113 0x0000>()
114 );
115
116 ///////
117 // Reverse Diff
118 static constexpr GrUserStencilSettings gUserToClipRDiffPass0( // Does not zero user bits.
119 GrUserStencilSettings::StaticInit<
120 0x0000,
121 GrUserStencilTest::kNotEqual,
122 0xffff,
123 GrUserStencilOp::kInvertClipBit,
124 GrUserStencilOp::kZeroClipBit,
125 0x0000>()
126 );
127
128 static constexpr GrUserStencilSettings gInvUserToClipRDiffPass0( // Does not zero user bits.
129 GrUserStencilSettings::StaticInit<
130 0x0000,
131 GrUserStencilTest::kEqual,
132 0xffff,
133 GrUserStencilOp::kInvertClipBit,
134 GrUserStencilOp::kZeroClipBit,
135 0x0000>()
136 );
137
138 ///////
139 // Second pass to clear user bits (only needed sometimes)
140 static constexpr GrUserStencilSettings gZeroUserBits(
141 GrUserStencilSettings::StaticInit<
142 0x0000,
143 GrUserStencilTest::kNotEqual,
144 0xffff,
145 GrUserStencilOp::kZero,
146 GrUserStencilOp::kKeep,
147 0xffff>()
148 );
149
150 static constexpr const GrUserStencilSettings* gUserToClipTable[2][1 + SkRegion::kLastOp][3] = {
151 { /* Normal fill. */
152 {&gUserToClipDiff, nullptr, nullptr}, // kDifference_Op.
153 {&gUserToClipIsect, nullptr, nullptr}, // kIntersect_Op.
154 {&gUserToClipUnion, nullptr, nullptr}, // kUnion_Op.
155 {&gUserToClipXorPass0, &gZeroUserBits, nullptr}, // kXOR_Op.
156 {&gUserToClipRDiffPass0, &gZeroUserBits, nullptr}, // kReverseDifference_Op.
157 {&gUserToClipReplace, nullptr, nullptr} // kReplace_Op.
158
159 }, /* Inverse fill. */ {
160 {&gUserToClipIsect, nullptr, nullptr}, // ~diff (aka isect).
161 {&gUserToClipDiff, nullptr, nullptr}, // ~isect (aka diff).
162 {&gInvUserToClipUnionPass0, &gZeroUserBits, nullptr}, // ~union.
163 {&gInvUserToClipXorPass0, &gZeroUserBits, nullptr}, // ~xor.
164 {&gInvUserToClipRDiffPass0, &gZeroUserBits, nullptr}, // ~reverse diff.
165 {&gInvUserToClipReplace, nullptr, nullptr} // ~replace.
166 }
167 };
168
169 ///////
170 // Direct to Stencil
171
172 // We can render a clip element directly without first writing to the client
173 // portion of the clip when the fill is not inverse and the set operation will
174 // only modify the in/out status of samples covered by the clip element.
175
176 // this one only works if used right after stencil clip was cleared.
177 // Our clip mask creation code doesn't allow midstream replace ops.
178 static constexpr GrUserStencilSettings gReplaceClip(
179 GrUserStencilSettings::StaticInit<
180 0x0000,
181 GrUserStencilTest::kAlways,
182 0xffff,
183 GrUserStencilOp::kSetClipBit,
184 GrUserStencilOp::kSetClipBit,
185 0x0000>()
186 );
187
188 static constexpr GrUserStencilSettings gUnionClip(
189 GrUserStencilSettings::StaticInit<
190 0x0000,
191 GrUserStencilTest::kAlwaysIfInClip,
192 0xffff,
193 GrUserStencilOp::kKeep,
194 GrUserStencilOp::kSetClipBit,
195 0x0000>()
196 );
197
198 static constexpr GrUserStencilSettings gXorClip(
199 GrUserStencilSettings::StaticInit<
200 0x0000,
201 GrUserStencilTest::kAlways,
202 0xffff,
203 GrUserStencilOp::kInvertClipBit,
204 GrUserStencilOp::kInvertClipBit,
205 0x0000>()
206 );
207
208 static constexpr GrUserStencilSettings gDiffClip(
209 GrUserStencilSettings::StaticInit<
210 0x0000,
211 GrUserStencilTest::kAlwaysIfInClip,
212 0xffff,
213 GrUserStencilOp::kZeroClipBit,
214 GrUserStencilOp::kKeep,
215 0x0000>()
216 );
217
218 static constexpr const GrUserStencilSettings* gDirectDrawTable[1 + SkRegion::kLastOp][2] = {
219 {&gDiffClip, nullptr}, // kDifference_Op.
220 {nullptr, nullptr}, // kIntersect_Op.
221 {&gUnionClip, nullptr}, // kUnion_Op.
222 {&gXorClip, nullptr}, // kXOR_Op.
223 {nullptr, nullptr}, // kReverseDifference_Op.
224 {&gReplaceClip, nullptr} // kReplace_Op.
225 };
226
227 static_assert(0 == SkRegion::kDifference_Op);
228 static_assert(1 == SkRegion::kIntersect_Op);
229 static_assert(2 == SkRegion::kUnion_Op);
230 static_assert(3 == SkRegion::kXOR_Op);
231 static_assert(4 == SkRegion::kReverseDifference_Op);
232 static_assert(5 == SkRegion::kReplace_Op);
233
234 // Settings used to when not allowed to draw directly to the clip to fill the user stencil bits
235 // before applying the covering clip stencil passes.
236 static constexpr GrUserStencilSettings gDrawToStencil(
237 GrUserStencilSettings::StaticInit<
238 0x0000,
239 GrUserStencilTest::kAlways,
240 0xffff,
241 GrUserStencilOp::kIncMaybeClamp,
242 GrUserStencilOp::kIncMaybeClamp,
243 0xffff>()
244 );
245
246 // Get the stencil settings per-pass to achieve the given fill+region op effect on the
247 // stencil buffer.
248 //
249 // If drawDirectToClip comes back false, the caller must first draw the element into the user
250 // stencil bits, and then cover the clip area with multiple passes using the returned
251 // stencil settings.
252
253 // If drawDirectToClip is true, the returned array will only have one pass and the
254 // caller should use those stencil settings while drawing the element directly.
255 //
256 // This returns a null-terminated list of const GrUserStencilSettings*
get_stencil_passes(SkRegion::Op op,skgpu::ganesh::PathRenderer::StencilSupport stencilSupport,bool fillInverted,bool * drawDirectToClip)257 GrUserStencilSettings const* const* get_stencil_passes(
258 SkRegion::Op op,
259 skgpu::ganesh::PathRenderer::StencilSupport stencilSupport,
260 bool fillInverted,
261 bool* drawDirectToClip) {
262 bool canRenderDirectToStencil =
263 skgpu::ganesh::PathRenderer::kNoRestriction_StencilSupport == stencilSupport;
264
265 // TODO: inverse fill + intersect op can be direct.
266 // TODO: this can be greatly simplified when we only need intersect and difference ops and
267 // none of the paths will be inverse-filled (just toggle the op instead).
268 SkASSERT((unsigned)op <= SkRegion::kLastOp);
269 if (canRenderDirectToStencil && !fillInverted) {
270 GrUserStencilSettings const* const* directPass = gDirectDrawTable[op];
271 if (directPass[0]) {
272 *drawDirectToClip = true;
273 return directPass;
274 }
275 }
276 *drawDirectToClip = false;
277 return gUserToClipTable[fillInverted][op];
278 }
279
draw_stencil_rect(skgpu::ganesh::SurfaceDrawContext * sdc,const GrHardClip & clip,const GrUserStencilSettings * ss,const SkMatrix & matrix,const SkRect & rect,GrAA aa)280 void draw_stencil_rect(skgpu::ganesh::SurfaceDrawContext* sdc,
281 const GrHardClip& clip,
282 const GrUserStencilSettings* ss,
283 const SkMatrix& matrix,
284 const SkRect& rect,
285 GrAA aa) {
286 GrPaint paint;
287 paint.setXPFactory(GrDisableColorXPFactory::Get());
288 sdc->stencilRect(&clip, ss, std::move(paint), aa, matrix, rect);
289 }
290
draw_path(GrRecordingContext * rContext,skgpu::ganesh::SurfaceDrawContext * sdc,skgpu::ganesh::PathRenderer * pr,const GrHardClip & clip,const SkIRect & bounds,const GrUserStencilSettings * ss,const SkMatrix & matrix,const GrStyledShape & shape,GrAA aa)291 void draw_path(GrRecordingContext* rContext,
292 skgpu::ganesh::SurfaceDrawContext* sdc,
293 skgpu::ganesh::PathRenderer* pr,
294 const GrHardClip& clip,
295 const SkIRect& bounds,
296 const GrUserStencilSettings* ss,
297 const SkMatrix& matrix,
298 const GrStyledShape& shape,
299 GrAA aa) {
300 GrPaint paint;
301 paint.setXPFactory(GrDisableColorXPFactory::Get());
302
303 // kMSAA is the only type of AA that's possible on a stencil buffer.
304 GrAAType pathAAType = aa == GrAA::kYes ? GrAAType::kMSAA : GrAAType::kNone;
305
306 skgpu::ganesh::PathRenderer::DrawPathArgs args{rContext,
307 std::move(paint),
308 ss,
309 sdc,
310 &clip,
311 &bounds,
312 &matrix,
313 &shape,
314 pathAAType,
315 false};
316 pr->drawPath(args);
317 }
318
stencil_path(GrRecordingContext * rContext,skgpu::ganesh::SurfaceDrawContext * sdc,skgpu::ganesh::PathRenderer * pr,const GrFixedClip & clip,const SkMatrix & matrix,const GrStyledShape & shape,GrAA aa)319 void stencil_path(GrRecordingContext* rContext,
320 skgpu::ganesh::SurfaceDrawContext* sdc,
321 skgpu::ganesh::PathRenderer* pr,
322 const GrFixedClip& clip,
323 const SkMatrix& matrix,
324 const GrStyledShape& shape,
325 GrAA aa) {
326 skgpu::ganesh::PathRenderer::StencilPathArgs args;
327 args.fContext = rContext;
328 args.fSurfaceDrawContext = sdc;
329 args.fClip = &clip;
330 args.fClipConservativeBounds = &clip.scissorRect();
331 args.fViewMatrix = &matrix;
332 args.fShape = &shape;
333 args.fDoStencilMSAA = aa;
334
335 pr->stencilPath(args);
336 }
337
supported_aa(skgpu::ganesh::SurfaceDrawContext * sdc,GrAA aa)338 GrAA supported_aa(skgpu::ganesh::SurfaceDrawContext* sdc, GrAA aa) {
339 return GrAA(sdc->numSamples() > 1 || sdc->canUseDynamicMSAA());
340 }
341
342 } // namespace
343
344 namespace skgpu::ganesh {
345
StencilMaskHelper(GrRecordingContext * rContext,SurfaceDrawContext * sdc)346 StencilMaskHelper::StencilMaskHelper(GrRecordingContext* rContext,
347 SurfaceDrawContext* sdc)
348 : fContext(rContext)
349 , fSDC(sdc)
350 , fClip(sdc->dimensions()) {
351 }
352
init(const SkIRect & bounds,uint32_t genID,const GrWindowRectangles & windowRects,int numFPs)353 bool StencilMaskHelper::init(const SkIRect& bounds, uint32_t genID,
354 const GrWindowRectangles& windowRects, int numFPs) {
355 if (!fSDC->mustRenderClip(genID, bounds, numFPs)) {
356 return false;
357 }
358
359 fClip.setStencilClip(genID);
360 // Should have caught bounds not intersecting the render target much earlier in clip application
361 SkAssertResult(fClip.fixedClip().setScissor(bounds));
362 if (!windowRects.empty()) {
363 fClip.fixedClip().setWindowRectangles(
364 windowRects, GrWindowRectsState::Mode::kExclusive);
365 }
366 fNumFPs = numFPs;
367 return true;
368 }
369
drawRect(const SkRect & rect,const SkMatrix & matrix,SkRegion::Op op,GrAA aa)370 void StencilMaskHelper::drawRect(const SkRect& rect,
371 const SkMatrix& matrix,
372 SkRegion::Op op,
373 GrAA aa) {
374 if (rect.isEmpty()) {
375 return;
376 }
377
378 bool drawDirectToClip;
379 auto passes = get_stencil_passes(op, PathRenderer::kNoRestriction_StencilSupport,
380 false, &drawDirectToClip);
381 aa = supported_aa(fSDC, aa);
382
383 if (!drawDirectToClip) {
384 // Draw to client stencil bits first
385 draw_stencil_rect(fSDC, fClip.fixedClip(), &gDrawToStencil, matrix, rect, aa);
386 }
387
388 // Now modify the clip bit (either by rendering directly), or by covering the bounding box
389 // of the clip
390 for (GrUserStencilSettings const* const* pass = passes; *pass; ++pass) {
391 if (drawDirectToClip) {
392 draw_stencil_rect(fSDC, fClip, *pass, matrix, rect, aa);
393 } else {
394 draw_stencil_rect(fSDC, fClip, *pass, SkMatrix::I(),
395 SkRect::Make(fClip.fixedClip().scissorRect()), aa);
396 }
397 }
398 }
399
drawPath(const SkPath & path,const SkMatrix & matrix,SkRegion::Op op,GrAA aa)400 bool StencilMaskHelper::drawPath(const SkPath& path,
401 const SkMatrix& matrix,
402 SkRegion::Op op,
403 GrAA aa) {
404 if (path.isEmpty()) {
405 return true;
406 }
407
408 // drawPath follows a similar approach to drawRect(), where we either draw directly to the clip
409 // bit or first draw to client bits and then apply a cover pass. The complicating factor is that
410 // we rely on path rendering and how the chosen path renderer uses the stencil buffer.
411 aa = supported_aa(fSDC, aa);
412
413 GrAAType pathAAType = aa == GrAA::kYes ? GrAAType::kMSAA : GrAAType::kNone;
414
415 // This will be used to determine whether the clip shape can be rendered into the
416 // stencil with arbitrary stencil settings.
417 PathRenderer::StencilSupport stencilSupport;
418
419 // Make path canonical with regards to fill type (inverse handled by stencil settings).
420 bool fillInverted = path.isInverseFillType();
421 SkTCopyOnFirstWrite<SkPath> clipPath(path);
422 if (fillInverted) {
423 clipPath.writable()->toggleInverseFillType();
424 }
425
426 GrStyledShape shape(*clipPath, GrStyle::SimpleFill());
427 SkASSERT(!shape.inverseFilled());
428
429 PathRenderer::CanDrawPathArgs canDrawArgs;
430 canDrawArgs.fCaps = fContext->priv().caps();
431 canDrawArgs.fProxy = fSDC->asRenderTargetProxy();
432 canDrawArgs.fClipConservativeBounds = &fClip.fixedClip().scissorRect();
433 canDrawArgs.fViewMatrix = &matrix;
434 canDrawArgs.fShape = &shape;
435 canDrawArgs.fPaint = nullptr;
436 canDrawArgs.fSurfaceProps = &fSDC->surfaceProps();
437 canDrawArgs.fAAType = pathAAType;
438 canDrawArgs.fHasUserStencilSettings = false;
439
440 auto pr = fContext->priv().drawingManager()->getPathRenderer(
441 canDrawArgs, false, PathRendererChain::DrawType::kStencil, &stencilSupport);
442 if (!pr) {
443 return false;
444 }
445
446 bool drawDirectToClip;
447 auto passes = get_stencil_passes(op, stencilSupport, fillInverted, &drawDirectToClip);
448
449 // Write to client bits if necessary
450 if (!drawDirectToClip) {
451 if (stencilSupport == PathRenderer::kNoRestriction_StencilSupport) {
452 draw_path(fContext, fSDC, pr, fClip.fixedClip(), fClip.fixedClip().scissorRect(),
453 &gDrawToStencil, matrix, shape, aa);
454 } else {
455 stencil_path(fContext, fSDC, pr, fClip.fixedClip(), matrix, shape, aa);
456 }
457 }
458
459 // Now modify the clip bit (either by rendering directly), or by covering the bounding box
460 // of the clip
461 for (GrUserStencilSettings const* const* pass = passes; *pass; ++pass) {
462 if (drawDirectToClip) {
463 draw_path(fContext, fSDC, pr, fClip, fClip.fixedClip().scissorRect(),
464 *pass, matrix, shape, aa);
465 } else {
466 draw_stencil_rect(fSDC, fClip, *pass, SkMatrix::I(),
467 SkRect::Make(fClip.fixedClip().scissorRect()), aa);
468 }
469 }
470
471 return true;
472 }
473
drawShape(const GrShape & shape,const SkMatrix & matrix,SkRegion::Op op,GrAA aa)474 bool StencilMaskHelper::drawShape(const GrShape& shape,
475 const SkMatrix& matrix,
476 SkRegion::Op op,
477 GrAA aa) {
478 if (shape.isRect() && !shape.inverted()) {
479 this->drawRect(shape.rect(), matrix, op, aa);
480 return true;
481 } else {
482 SkPath p;
483 shape.asPath(&p);
484 return this->drawPath(p, matrix, op, aa);
485 }
486 }
487
clear(bool insideStencil)488 void StencilMaskHelper::clear(bool insideStencil) {
489 if (fClip.fixedClip().hasWindowRectangles()) {
490 // Use a draw to benefit from window rectangles when resetting the stencil buffer; for
491 // large buffers with MSAA this can be significant.
492 draw_stencil_rect(fSDC, fClip.fixedClip(),
493 GrStencilSettings::SetClipBitSettings(insideStencil), SkMatrix::I(),
494 SkRect::Make(fClip.fixedClip().scissorRect()), GrAA::kNo);
495 } else {
496 fSDC->clearStencilClip(fClip.fixedClip().scissorRect(), insideStencil);
497 }
498 }
499
finish()500 void StencilMaskHelper::finish() {
501 fSDC->setLastClip(fClip.stencilStackID(), fClip.fixedClip().scissorRect(), fNumFPs);
502 }
503
504 } // namespace skgpu::ganesh
505