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 | |
18 | namespace { |
19 | |
20 | //////////////////////////////////////////////////////////////////////////////// |
21 | // Stencil Rules for Merging user stencil space into clip |
22 | // |
23 | |
24 | /////// |
25 | // Replace |
26 | static constexpr GrUserStencilSettings gUserToClipReplace( |
27 | GrUserStencilSettings::StaticInit< |
28 | 0x0000, |
29 | GrUserStencilTest::kNotEqual, |
30 | 0xffff, |
31 | GrUserStencilOp::kSetClipAndReplaceUserBits, |
32 | GrUserStencilOp::kZeroClipAndUserBits, |
33 | 0xffff>() |
34 | ); |
35 | |
36 | static 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 |
48 | static 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 |
60 | static 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 |
72 | static constexpr GrUserStencilSettings gUserToClipUnion( |
73 | GrUserStencilSettings::StaticInit< |
74 | 0x0000, |
75 | GrUserStencilTest::kNotEqual, |
76 | 0xffff, |
77 | GrUserStencilOp::kSetClipAndReplaceUserBits, |
78 | GrUserStencilOp::kKeep, |
79 | 0xffff>() |
80 | ); |
81 | |
82 | static 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 |
94 | static 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 | |
104 | static 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 |
116 | static 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 | |
126 | static 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) |
138 | static constexpr GrUserStencilSettings gZeroUserBits( |
139 | GrUserStencilSettings::StaticInit< |
140 | 0x0000, |
141 | GrUserStencilTest::kNotEqual, |
142 | 0xffff, |
143 | GrUserStencilOp::kZero, |
144 | GrUserStencilOp::kKeep, |
145 | 0xffff>() |
146 | ); |
147 | |
148 | static 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. |
176 | static constexpr GrUserStencilSettings gReplaceClip( |
177 | GrUserStencilSettings::StaticInit< |
178 | 0x0000, |
179 | GrUserStencilTest::kAlways, |
180 | 0xffff, |
181 | GrUserStencilOp::kSetClipBit, |
182 | GrUserStencilOp::kSetClipBit, |
183 | 0x0000>() |
184 | ); |
185 | |
186 | static constexpr GrUserStencilSettings gUnionClip( |
187 | GrUserStencilSettings::StaticInit< |
188 | 0x0000, |
189 | GrUserStencilTest::kAlwaysIfInClip, |
190 | 0xffff, |
191 | GrUserStencilOp::kKeep, |
192 | GrUserStencilOp::kSetClipBit, |
193 | 0x0000>() |
194 | ); |
195 | |
196 | static constexpr GrUserStencilSettings gXorClip( |
197 | GrUserStencilSettings::StaticInit< |
198 | 0x0000, |
199 | GrUserStencilTest::kAlways, |
200 | 0xffff, |
201 | GrUserStencilOp::kInvertClipBit, |
202 | GrUserStencilOp::kInvertClipBit, |
203 | 0x0000>() |
204 | ); |
205 | |
206 | static constexpr GrUserStencilSettings gDiffClip( |
207 | GrUserStencilSettings::StaticInit< |
208 | 0x0000, |
209 | GrUserStencilTest::kAlwaysIfInClip, |
210 | 0xffff, |
211 | GrUserStencilOp::kZeroClipBit, |
212 | GrUserStencilOp::kKeep, |
213 | 0x0000>() |
214 | ); |
215 | |
216 | static 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 | |
225 | static_assert(0 == SkRegion::kDifference_Op); |
226 | static_assert(1 == SkRegion::kIntersect_Op); |
227 | static_assert(2 == SkRegion::kUnion_Op); |
228 | static_assert(3 == SkRegion::kXOR_Op); |
229 | static_assert(4 == SkRegion::kReverseDifference_Op); |
230 | static_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. |
234 | static 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* |
255 | static 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 | |
276 | static 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 | |
284 | static 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 | |
308 | static 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 | |
323 | static 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 | |
338 | bool 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 | |
355 | void 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 | |
385 | bool 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 | |
459 | bool 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 | |
473 | void 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 | |
485 | void GrStencilMaskHelper::finish() { |
486 | fRTC->priv().setLastClip(fClip.stencilStackID(), fClip.fixedClip().scissorRect(), fNumFPs); |
487 | } |
488 | |