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