blob: dfc0a2651bd45be8ff51a637f25523f6dd1633f2 [file] [log] [blame]
bsalomon@google.com170bd792012-12-05 22:26:11 +00001/*
csmartdalton77f2fae2016-08-08 09:55:06 -07002 * Copyright 2016 Google Inc.
bsalomon@google.com170bd792012-12-05 22:26:11 +00003 *
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 "GrReducedClip.h"
9
csmartdaltonbde96c62016-08-31 12:54:46 -070010#include "GrAppliedClip.h"
csmartdaltoncbecb082016-07-22 08:59:08 -070011#include "GrClip.h"
csmartdaltonbde96c62016-08-31 12:54:46 -070012#include "GrColor.h"
13#include "GrContextPriv.h"
14#include "GrDrawContext.h"
15#include "GrDrawContextPriv.h"
16#include "GrDrawingManager.h"
17#include "GrFixedClip.h"
18#include "GrPathRenderer.h"
19#include "GrStyle.h"
20#include "GrUserStencilSettings.h"
csmartdaltoncbecb082016-07-22 08:59:08 -070021
bsalomon@google.com170bd792012-12-05 22:26:11 +000022typedef SkClipStack::Element Element;
bsalomon@google.com170bd792012-12-05 22:26:11 +000023
csmartdalton5ecbbbe2016-08-23 13:26:40 -070024/**
25 * There are plenty of optimizations that could be added here. Maybe flips could be folded into
26 * earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
27 * for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
28 * based on later intersect operations, and perhaps remove intersect-rects. We could optionally
29 * take a rect in case the caller knows a bound on what is to be drawn through this clip.
30 */
csmartdalton77f2fae2016-08-08 09:55:06 -070031GrReducedClip::GrReducedClip(const SkClipStack& stack, const SkRect& queryBounds) {
csmartdaltoncbecb082016-07-22 08:59:08 -070032 SkASSERT(!queryBounds.isEmpty());
csmartdaltond211e782016-08-15 11:17:19 -070033 fHasIBounds = false;
csmartdaltoncbecb082016-07-22 08:59:08 -070034
bsalomon@google.com170bd792012-12-05 22:26:11 +000035 if (stack.isWideOpen()) {
csmartdalton77f2fae2016-08-08 09:55:06 -070036 fInitialState = InitialState::kAllIn;
37 return;
bsalomon@google.com170bd792012-12-05 22:26:11 +000038 }
39
40 SkClipStack::BoundsType stackBoundsType;
41 SkRect stackBounds;
42 bool iior;
43 stack.getBounds(&stackBounds, &stackBoundsType, &iior);
44
csmartdaltoncbecb082016-07-22 08:59:08 -070045 if (stackBounds.isEmpty() || GrClip::IsOutsideClip(stackBounds, queryBounds)) {
46 bool insideOut = SkClipStack::kInsideOut_BoundsType == stackBoundsType;
csmartdalton77f2fae2016-08-08 09:55:06 -070047 fInitialState = insideOut ? InitialState::kAllIn : InitialState::kAllOut;
48 return;
bsalomon@google.com170bd792012-12-05 22:26:11 +000049 }
50
csmartdaltoncbecb082016-07-22 08:59:08 -070051 if (iior) {
52 // "Is intersection of rects" means the clip is a single rect indicated by the stack bounds.
53 // This should only be true if aa/non-aa status matches among all elements.
54 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
55 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
56 if (!iter.prev()->isAA() || GrClip::IsPixelAligned(stackBounds)) {
57 // The clip is a non-aa rect. This is the one spot where we can actually implement the
csmartdalton77f2fae2016-08-08 09:55:06 -070058 // clip (using fIBounds) rather than just telling the caller what it should be.
59 stackBounds.round(&fIBounds);
csmartdaltond211e782016-08-15 11:17:19 -070060 fHasIBounds = true;
csmartdalton77f2fae2016-08-08 09:55:06 -070061 fInitialState = fIBounds.isEmpty() ? InitialState::kAllOut : InitialState::kAllIn;
62 return;
csmartdaltoncbecb082016-07-22 08:59:08 -070063 }
64 if (GrClip::IsInsideClip(stackBounds, queryBounds)) {
csmartdalton77f2fae2016-08-08 09:55:06 -070065 fInitialState = InitialState::kAllIn;
66 return;
csmartdaltoncbecb082016-07-22 08:59:08 -070067 }
68
csmartdaltond211e782016-08-15 11:17:19 -070069 SkRect tightBounds;
70 SkAssertResult(tightBounds.intersect(stackBounds, queryBounds));
71 fIBounds = GrClip::GetPixelIBounds(tightBounds);
csmartdalton77f2fae2016-08-08 09:55:06 -070072 SkASSERT(!fIBounds.isEmpty()); // Empty should have been blocked by IsOutsideClip above.
csmartdaltond211e782016-08-15 11:17:19 -070073 fHasIBounds = true;
csmartdaltoncbecb082016-07-22 08:59:08 -070074
csmartdalton77f2fae2016-08-08 09:55:06 -070075 // Implement the clip with an AA rect element.
76 fElements.addToHead(stackBounds, SkRegion::kReplace_Op, true/*doAA*/);
csmartdalton8d3f92a2016-08-17 09:39:38 -070077 fElementsGenID = stack.getTopmostGenID();
csmartdalton77f2fae2016-08-08 09:55:06 -070078 fRequiresAA = true;
79
80 fInitialState = InitialState::kAllOut;
81 return;
csmartdaltoncbecb082016-07-22 08:59:08 -070082 }
83
84 SkRect tighterQuery = queryBounds;
85 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
86 // Tighten the query by introducing a new clip at the stack's pixel boundaries. (This new
csmartdalton77f2fae2016-08-08 09:55:06 -070087 // clip will be enforced by the scissor through fIBounds.)
csmartdaltoncbecb082016-07-22 08:59:08 -070088 SkAssertResult(tighterQuery.intersect(GrClip::GetPixelBounds(stackBounds)));
csmartdaltoncbecb082016-07-22 08:59:08 -070089 }
skia.committer@gmail.comd21444a2012-12-07 02:01:25 +000090
csmartdaltond211e782016-08-15 11:17:19 -070091 fIBounds = GrClip::GetPixelIBounds(tighterQuery);
csmartdalton77f2fae2016-08-08 09:55:06 -070092 SkASSERT(!fIBounds.isEmpty()); // Empty should have been blocked by IsOutsideClip above.
csmartdaltond211e782016-08-15 11:17:19 -070093 fHasIBounds = true;
csmartdalton77f2fae2016-08-08 09:55:06 -070094
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000095 // Now that we have determined the bounds to use and filtered out the trivial cases, call the
96 // helper that actually walks the stack.
csmartdalton5ecbbbe2016-08-23 13:26:40 -070097 this->walkStack(stack, tighterQuery);
98}
99
100void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBounds) {
101 // walk backwards until we get to:
102 // a) the beginning
103 // b) an operation that is known to make the bounds all inside/outside
104 // c) a replace operation
105
106 enum class InitialTriState {
107 kUnknown = -1,
108 kAllIn = (int)GrReducedClip::InitialState::kAllIn,
109 kAllOut = (int)GrReducedClip::InitialState::kAllOut
110 } initialTriState = InitialTriState::kUnknown;
111
112 // During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
113 // TODO: track these per saved clip so that we can consider them on the forward pass.
114 bool embiggens = false;
115 bool emsmallens = false;
116
117 // We use a slightly relaxed set of query bounds for element containment tests. This is to
118 // account for floating point rounding error that may have occurred during coord transforms.
119 SkRect relaxedQueryBounds = queryBounds.makeInset(GrClip::kBoundsTolerance,
120 GrClip::kBoundsTolerance);
121
122 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
123 int numAAElements = 0;
124 while (InitialTriState::kUnknown == initialTriState) {
125 const Element* element = iter.prev();
126 if (nullptr == element) {
127 initialTriState = InitialTriState::kAllIn;
128 break;
129 }
130 if (SkClipStack::kEmptyGenID == element->getGenID()) {
131 initialTriState = InitialTriState::kAllOut;
132 break;
133 }
134 if (SkClipStack::kWideOpenGenID == element->getGenID()) {
135 initialTriState = InitialTriState::kAllIn;
136 break;
137 }
138
139 bool skippable = false;
140 bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
141
142 switch (element->getOp()) {
143 case SkRegion::kDifference_Op:
144 // check if the shape subtracted either contains the entire bounds (and makes
145 // the clip empty) or is outside the bounds and therefore can be skipped.
146 if (element->isInverseFilled()) {
147 if (element->contains(relaxedQueryBounds)) {
148 skippable = true;
149 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
150 initialTriState = InitialTriState::kAllOut;
151 skippable = true;
152 }
153 } else {
154 if (element->contains(relaxedQueryBounds)) {
155 initialTriState = InitialTriState::kAllOut;
156 skippable = true;
157 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
158 skippable = true;
159 }
160 }
161 if (!skippable) {
162 emsmallens = true;
163 }
164 break;
165 case SkRegion::kIntersect_Op:
166 // check if the shape intersected contains the entire bounds and therefore can
167 // be skipped or it is outside the entire bounds and therefore makes the clip
168 // empty.
169 if (element->isInverseFilled()) {
170 if (element->contains(relaxedQueryBounds)) {
171 initialTriState = InitialTriState::kAllOut;
172 skippable = true;
173 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
174 skippable = true;
175 }
176 } else {
177 if (element->contains(relaxedQueryBounds)) {
178 skippable = true;
179 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
180 initialTriState = InitialTriState::kAllOut;
181 skippable = true;
182 } else if (!embiggens && !element->isAA() &&
183 Element::kRect_Type == element->getType()) {
184 // fIBounds and queryBounds have already acccounted for this element via
185 // clip stack bounds; here we just apply the non-aa rounding effect.
186 SkIRect nonaaRect;
187 element->getRect().round(&nonaaRect);
188 if (!this->intersectIBounds(nonaaRect)) {
189 return;
190 }
191 skippable = true;
192 }
193 }
194 if (!skippable) {
195 emsmallens = true;
196 }
197 break;
198 case SkRegion::kUnion_Op:
199 // If the union-ed shape contains the entire bounds then after this element
200 // the bounds is entirely inside the clip. If the union-ed shape is outside the
201 // bounds then this op can be skipped.
202 if (element->isInverseFilled()) {
203 if (element->contains(relaxedQueryBounds)) {
204 skippable = true;
205 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
206 initialTriState = InitialTriState::kAllIn;
207 skippable = true;
208 }
209 } else {
210 if (element->contains(relaxedQueryBounds)) {
211 initialTriState = InitialTriState::kAllIn;
212 skippable = true;
213 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
214 skippable = true;
215 }
216 }
217 if (!skippable) {
218 embiggens = true;
219 }
220 break;
221 case SkRegion::kXOR_Op:
222 // If the bounds is entirely inside the shape being xor-ed then the effect is
223 // to flip the inside/outside state of every point in the bounds. We may be
224 // able to take advantage of this in the forward pass. If the xor-ed shape
225 // doesn't intersect the bounds then it can be skipped.
226 if (element->isInverseFilled()) {
227 if (element->contains(relaxedQueryBounds)) {
228 skippable = true;
229 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
230 isFlip = true;
231 }
232 } else {
233 if (element->contains(relaxedQueryBounds)) {
234 isFlip = true;
235 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
236 skippable = true;
237 }
238 }
239 if (!skippable) {
240 emsmallens = embiggens = true;
241 }
242 break;
243 case SkRegion::kReverseDifference_Op:
244 // When the bounds is entirely within the rev-diff shape then this behaves like xor
245 // and reverses every point inside the bounds. If the shape is completely outside
246 // the bounds then we know after this element is applied that the bounds will be
247 // all outside the current clip.B
248 if (element->isInverseFilled()) {
249 if (element->contains(relaxedQueryBounds)) {
250 initialTriState = InitialTriState::kAllOut;
251 skippable = true;
252 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
253 isFlip = true;
254 }
255 } else {
256 if (element->contains(relaxedQueryBounds)) {
257 isFlip = true;
258 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
259 initialTriState = InitialTriState::kAllOut;
260 skippable = true;
261 }
262 }
263 if (!skippable) {
264 emsmallens = embiggens = true;
265 }
266 break;
267
268 case SkRegion::kReplace_Op:
269 // Replace will always terminate our walk. We will either begin the forward walk
270 // at the replace op or detect here than the shape is either completely inside
271 // or completely outside the bounds. In this latter case it can be skipped by
272 // setting the correct value for initialTriState.
273 if (element->isInverseFilled()) {
274 if (element->contains(relaxedQueryBounds)) {
275 initialTriState = InitialTriState::kAllOut;
276 skippable = true;
277 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
278 initialTriState = InitialTriState::kAllIn;
279 skippable = true;
280 }
281 } else {
282 if (element->contains(relaxedQueryBounds)) {
283 initialTriState = InitialTriState::kAllIn;
284 skippable = true;
285 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
286 initialTriState = InitialTriState::kAllOut;
287 skippable = true;
288 } else if (!embiggens && !element->isAA() &&
289 Element::kRect_Type == element->getType()) {
290 // fIBounds and queryBounds have already acccounted for this element via
291 // clip stack bounds; here we just apply the non-aa rounding effect.
292 SkIRect nonaaRect;
293 element->getRect().round(&nonaaRect);
294 if (!this->intersectIBounds(nonaaRect)) {
295 return;
296 }
297 initialTriState = InitialTriState::kAllIn;
298 skippable = true;
299 }
300 }
301 if (!skippable) {
302 initialTriState = InitialTriState::kAllOut;
303 embiggens = emsmallens = true;
304 }
305 break;
306 default:
307 SkDEBUGFAIL("Unexpected op.");
308 break;
309 }
310 if (!skippable) {
311 if (0 == fElements.count()) {
312 // This will be the last element. Record the stricter genID.
313 fElementsGenID = element->getGenID();
314 }
315
316 // if it is a flip, change it to a bounds-filling rect
317 if (isFlip) {
318 SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
319 SkRegion::kReverseDifference_Op == element->getOp());
320 fElements.addToHead(SkRect::Make(fIBounds), SkRegion::kReverseDifference_Op, false);
321 } else {
322 Element* newElement = fElements.addToHead(*element);
323 if (newElement->isAA()) {
324 ++numAAElements;
325 }
326 // Intersecting an inverse shape is the same as differencing the non-inverse shape.
327 // Replacing with an inverse shape is the same as setting initialState=kAllIn and
328 // differencing the non-inverse shape.
329 bool isReplace = SkRegion::kReplace_Op == newElement->getOp();
330 if (newElement->isInverseFilled() &&
331 (SkRegion::kIntersect_Op == newElement->getOp() || isReplace)) {
332 newElement->invertShapeFillType();
333 newElement->setOp(SkRegion::kDifference_Op);
334 if (isReplace) {
335 SkASSERT(InitialTriState::kAllOut == initialTriState);
336 initialTriState = InitialTriState::kAllIn;
337 }
338 }
339 }
340 }
341 }
342
343 if ((InitialTriState::kAllOut == initialTriState && !embiggens) ||
344 (InitialTriState::kAllIn == initialTriState && !emsmallens)) {
345 fElements.reset();
346 numAAElements = 0;
347 } else {
348 Element* element = fElements.headIter().get();
349 while (element) {
350 bool skippable = false;
351 switch (element->getOp()) {
352 case SkRegion::kDifference_Op:
353 // subtracting from the empty set yields the empty set.
354 skippable = InitialTriState::kAllOut == initialTriState;
355 break;
356 case SkRegion::kIntersect_Op:
357 // intersecting with the empty set yields the empty set
358 if (InitialTriState::kAllOut == initialTriState) {
359 skippable = true;
360 } else {
361 // We can clear to zero and then simply draw the clip element.
362 initialTriState = InitialTriState::kAllOut;
363 element->setOp(SkRegion::kReplace_Op);
364 }
365 break;
366 case SkRegion::kUnion_Op:
367 if (InitialTriState::kAllIn == initialTriState) {
368 // unioning the infinite plane with anything is a no-op.
369 skippable = true;
370 } else {
371 // unioning the empty set with a shape is the shape.
372 element->setOp(SkRegion::kReplace_Op);
373 }
374 break;
375 case SkRegion::kXOR_Op:
376 if (InitialTriState::kAllOut == initialTriState) {
377 // xor could be changed to diff in the kAllIn case, not sure it's a win.
378 element->setOp(SkRegion::kReplace_Op);
379 }
380 break;
381 case SkRegion::kReverseDifference_Op:
382 if (InitialTriState::kAllIn == initialTriState) {
383 // subtracting the whole plane will yield the empty set.
384 skippable = true;
385 initialTriState = InitialTriState::kAllOut;
386 } else {
387 // this picks up flips inserted in the backwards pass.
388 skippable = element->isInverseFilled() ?
389 GrClip::IsOutsideClip(element->getBounds(), queryBounds) :
390 element->contains(relaxedQueryBounds);
391 if (skippable) {
392 initialTriState = InitialTriState::kAllIn;
393 } else {
394 element->setOp(SkRegion::kReplace_Op);
395 }
396 }
397 break;
398 case SkRegion::kReplace_Op:
399 skippable = false; // we would have skipped it in the backwards walk if we
400 // could've.
401 break;
402 default:
403 SkDEBUGFAIL("Unexpected op.");
404 break;
405 }
406 if (!skippable) {
407 break;
408 } else {
409 if (element->isAA()) {
410 --numAAElements;
411 }
412 fElements.popHead();
413 element = fElements.headIter().get();
414 }
415 }
416 }
417 fRequiresAA = numAAElements > 0;
418
419 SkASSERT(InitialTriState::kUnknown != initialTriState);
420 fInitialState = static_cast<GrReducedClip::InitialState>(initialTriState);
421}
422
423inline bool GrReducedClip::intersectIBounds(const SkIRect& irect) {
424 SkASSERT(fHasIBounds);
425 if (!fIBounds.intersect(irect)) {
426 fHasIBounds = false;
427 fElements.reset();
428 fRequiresAA = false;
429 fInitialState = InitialState::kAllOut;
430 return false;
431 }
432 return true;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000433}
csmartdaltonbde96c62016-08-31 12:54:46 -0700434
435////////////////////////////////////////////////////////////////////////////////
436// Create a 8-bit clip mask in alpha
437
438static bool stencil_element(GrDrawContext* dc,
439 const GrFixedClip& clip,
440 const GrUserStencilSettings* ss,
441 const SkMatrix& viewMatrix,
442 const SkClipStack::Element* element) {
443
444 // TODO: Draw rrects directly here.
445 switch (element->getType()) {
446 case Element::kEmpty_Type:
447 SkDEBUGFAIL("Should never get here with an empty element.");
448 break;
449 case Element::kRect_Type:
450 return dc->drawContextPriv().drawAndStencilRect(clip, ss,
451 element->getOp(),
452 element->isInverseFilled(),
453 element->isAA(),
454 viewMatrix, element->getRect());
455 break;
456 default: {
457 SkPath path;
458 element->asPath(&path);
459 if (path.isInverseFillType()) {
460 path.toggleInverseFillType();
461 }
462
463 return dc->drawContextPriv().drawAndStencilPath(clip, ss,
464 element->getOp(),
465 element->isInverseFilled(),
466 element->isAA(), viewMatrix, path);
467 break;
468 }
469 }
470
471 return false;
472}
473
474static void draw_element(GrDrawContext* dc,
475 const GrClip& clip, // TODO: can this just always be WideOpen?
476 const GrPaint &paint,
477 const SkMatrix& viewMatrix,
478 const SkClipStack::Element* element) {
479
480 // TODO: Draw rrects directly here.
481 switch (element->getType()) {
482 case Element::kEmpty_Type:
483 SkDEBUGFAIL("Should never get here with an empty element.");
484 break;
485 case Element::kRect_Type:
486 dc->drawRect(clip, paint, viewMatrix, element->getRect());
487 break;
488 default: {
489 SkPath path;
490 element->asPath(&path);
491 if (path.isInverseFillType()) {
492 path.toggleInverseFillType();
493 }
494
495 dc->drawPath(clip, paint, viewMatrix, path, GrStyle::SimpleFill());
496 break;
497 }
498 }
499}
500
501bool GrReducedClip::drawAlphaClipMask(GrDrawContext* dc) const {
502 // The texture may be larger than necessary, this rect represents the part of the texture
503 // we populate with a rasterization of the clip.
504 GrFixedClip clip(SkIRect::MakeWH(fIBounds.width(), fIBounds.height()));
505
506 // The scratch texture that we are drawing into can be substantially larger than the mask. Only
507 // clear the part that we care about.
508 GrColor initialCoverage = InitialState::kAllIn == this->initialState() ? -1 : 0;
509 dc->drawContextPriv().clear(clip, initialCoverage, true);
510
511 // Set the matrix so that rendered clip elements are transformed to mask space from clip space.
512 SkMatrix translate;
513 translate.setTranslate(SkIntToScalar(-fIBounds.left()), SkIntToScalar(-fIBounds.top()));
514
515 // walk through each clip element and perform its set op
516 for (ElementList::Iter iter(fElements); iter.get(); iter.next()) {
517 const Element* element = iter.get();
518 SkRegion::Op op = element->getOp();
519 bool invert = element->isInverseFilled();
520 if (invert || SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) {
521 // draw directly into the result with the stencil set to make the pixels affected
522 // by the clip shape be non-zero.
523 static constexpr GrUserStencilSettings kStencilInElement(
524 GrUserStencilSettings::StaticInit<
525 0xffff,
526 GrUserStencilTest::kAlways,
527 0xffff,
528 GrUserStencilOp::kReplace,
529 GrUserStencilOp::kReplace,
530 0xffff>()
531 );
532 if (!stencil_element(dc, clip, &kStencilInElement, translate, element)) {
533 return false;
534 }
535
536 // Draw to the exterior pixels (those with a zero stencil value).
537 static constexpr GrUserStencilSettings kDrawOutsideElement(
538 GrUserStencilSettings::StaticInit<
539 0x0000,
540 GrUserStencilTest::kEqual,
541 0xffff,
542 GrUserStencilOp::kZero,
543 GrUserStencilOp::kZero,
544 0xffff>()
545 );
546 if (!dc->drawContextPriv().drawAndStencilRect(clip, &kDrawOutsideElement,
547 op, !invert, false,
548 translate,
549 SkRect::Make(fIBounds))) {
550 return false;
551 }
552 } else {
553 // all the remaining ops can just be directly draw into the accumulation buffer
554 GrPaint paint;
555 paint.setAntiAlias(element->isAA());
556 paint.setCoverageSetOpXPFactory(op, false);
557
558 draw_element(dc, clip, paint, translate, element);
559 }
560 }
561
562 return true;
563}
564
565////////////////////////////////////////////////////////////////////////////////
566// Create a 1-bit clip mask in the stencil buffer.
567
568class StencilClip final : public GrClip {
569public:
570 StencilClip(const SkIRect& scissorRect) : fFixedClip(scissorRect) {}
571 const GrFixedClip& fixedClip() const { return fFixedClip; }
572
573private:
574 bool quickContains(const SkRect&) const final {
575 return false;
576 }
577 void getConservativeBounds(int width, int height, SkIRect* devResult, bool* iior) const final {
578 fFixedClip.getConservativeBounds(width, height, devResult, iior);
579 }
580 bool isRRect(const SkRect& rtBounds, SkRRect* rr, bool* aa) const final {
581 return false;
582 }
583 bool apply(GrContext* context, GrDrawContext* drawContext, bool useHWAA,
584 bool hasUserStencilSettings, GrAppliedClip* out) const final {
585 if (!fFixedClip.apply(context, drawContext, useHWAA, hasUserStencilSettings, out)) {
586 return false;
587 }
588 out->addStencilClip();
589 return true;
590 }
591
592 GrFixedClip fFixedClip;
593
594 typedef GrClip INHERITED;
595};
596
597bool GrReducedClip::drawStencilClipMask(GrContext* context,
598 GrDrawContext* drawContext,
599 const SkIPoint& clipOrigin) const {
600 // We set the current clip to the bounds so that our recursive draws are scissored to them.
601 StencilClip stencilClip(fIBounds.makeOffset(-clipOrigin.x(), -clipOrigin.y()));
602
603 bool initialState = InitialState::kAllIn == this->initialState();
604 drawContext->drawContextPriv().clearStencilClip(stencilClip.fixedClip(), initialState);
605
606 // Set the matrix so that rendered clip elements are transformed from clip to stencil space.
607 SkMatrix viewMatrix;
608 viewMatrix.setTranslate(SkIntToScalar(-clipOrigin.x()), SkIntToScalar(-clipOrigin.y()));
609
610 // walk through each clip element and perform its set op
611 // with the existing clip.
612 for (ElementList::Iter iter(fElements); iter.get(); iter.next()) {
613 const Element* element = iter.get();
614 bool useHWAA = element->isAA() && drawContext->isStencilBufferMultisampled();
615
616 bool fillInverted = false;
617
618 // This will be used to determine whether the clip shape can be rendered into the
619 // stencil with arbitrary stencil settings.
620 GrPathRenderer::StencilSupport stencilSupport;
621
622 SkRegion::Op op = element->getOp();
623
624 GrPathRenderer* pr = nullptr;
625 SkPath clipPath;
626 if (Element::kRect_Type == element->getType()) {
627 stencilSupport = GrPathRenderer::kNoRestriction_StencilSupport;
628 fillInverted = false;
629 } else {
630 element->asPath(&clipPath);
631 fillInverted = clipPath.isInverseFillType();
632 if (fillInverted) {
633 clipPath.toggleInverseFillType();
634 }
635
636 GrShape shape(clipPath, GrStyle::SimpleFill());
637 GrPathRenderer::CanDrawPathArgs canDrawArgs;
638 canDrawArgs.fShaderCaps = context->caps()->shaderCaps();
639 canDrawArgs.fViewMatrix = &viewMatrix;
640 canDrawArgs.fShape = &shape;
641 canDrawArgs.fAntiAlias = false;
642 canDrawArgs.fHasUserStencilSettings = false;
643 canDrawArgs.fIsStencilBufferMSAA = drawContext->isStencilBufferMultisampled();
644
645 GrDrawingManager* dm = context->contextPriv().drawingManager();
646 pr = dm->getPathRenderer(canDrawArgs, false,
647 GrPathRendererChain::kStencilOnly_DrawType,
648 &stencilSupport);
649 if (!pr) {
650 return false;
651 }
652 }
653
654 bool canRenderDirectToStencil =
655 GrPathRenderer::kNoRestriction_StencilSupport == stencilSupport;
656 bool drawDirectToClip; // Given the renderer, the element,
657 // fill rule, and set operation should
658 // we render the element directly to
659 // stencil bit used for clipping.
660 GrUserStencilSettings const* const* stencilPasses =
661 GrStencilSettings::GetClipPasses(op, canRenderDirectToStencil, fillInverted,
662 &drawDirectToClip);
663
664 // draw the element to the client stencil bits if necessary
665 if (!drawDirectToClip) {
666 static constexpr GrUserStencilSettings kDrawToStencil(
667 GrUserStencilSettings::StaticInit<
668 0x0000,
669 GrUserStencilTest::kAlways,
670 0xffff,
671 GrUserStencilOp::kIncMaybeClamp,
672 GrUserStencilOp::kIncMaybeClamp,
673 0xffff>()
674 );
675 if (Element::kRect_Type == element->getType()) {
676 drawContext->drawContextPriv().stencilRect(stencilClip.fixedClip(),
677 &kDrawToStencil, useHWAA,
678 viewMatrix, element->getRect());
679 } else {
680 if (!clipPath.isEmpty()) {
681 GrShape shape(clipPath, GrStyle::SimpleFill());
682 if (canRenderDirectToStencil) {
683 GrPaint paint;
684 paint.setXPFactory(GrDisableColorXPFactory::Make());
685 paint.setAntiAlias(element->isAA());
686
687 GrPathRenderer::DrawPathArgs args;
688 args.fResourceProvider = context->resourceProvider();
689 args.fPaint = &paint;
690 args.fUserStencilSettings = &kDrawToStencil;
691 args.fDrawContext = drawContext;
692 args.fClip = &stencilClip.fixedClip();
693 args.fViewMatrix = &viewMatrix;
694 args.fShape = &shape;
695 args.fAntiAlias = false;
696 args.fGammaCorrect = false;
697 pr->drawPath(args);
698 } else {
699 GrPathRenderer::StencilPathArgs args;
700 args.fResourceProvider = context->resourceProvider();
701 args.fDrawContext = drawContext;
702 args.fClip = &stencilClip.fixedClip();
703 args.fViewMatrix = &viewMatrix;
704 args.fIsAA = element->isAA();
705 args.fShape = &shape;
706 pr->stencilPath(args);
707 }
708 }
709 }
710 }
711
712 // now we modify the clip bit by rendering either the clip
713 // element directly or a bounding rect of the entire clip.
714 for (GrUserStencilSettings const* const* pass = stencilPasses; *pass; ++pass) {
715 if (drawDirectToClip) {
716 if (Element::kRect_Type == element->getType()) {
717 drawContext->drawContextPriv().stencilRect(stencilClip, *pass, useHWAA,
718 viewMatrix, element->getRect());
719 } else {
720 GrShape shape(clipPath, GrStyle::SimpleFill());
721 GrPaint paint;
722 paint.setXPFactory(GrDisableColorXPFactory::Make());
723 paint.setAntiAlias(element->isAA());
724 GrPathRenderer::DrawPathArgs args;
725 args.fResourceProvider = context->resourceProvider();
726 args.fPaint = &paint;
727 args.fUserStencilSettings = *pass;
728 args.fDrawContext = drawContext;
729 args.fClip = &stencilClip;
730 args.fViewMatrix = &viewMatrix;
731 args.fShape = &shape;
732 args.fAntiAlias = false;
733 args.fGammaCorrect = false;
734 pr->drawPath(args);
735 }
736 } else {
737 // The view matrix is setup to do clip space -> stencil space translation, so
738 // draw rect in clip space.
739 drawContext->drawContextPriv().stencilRect(stencilClip, *pass,
740 false, viewMatrix,
741 SkRect::Make(fIBounds));
742 }
743 }
744 }
745 return true;
746}