1/*
2 * Copyright 2016 Google Inc.
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/core/SkClipOpPriv.h"
9#include "src/gpu/GrAppliedClip.h"
10#include "src/gpu/GrClip.h"
11#include "src/gpu/GrColor.h"
12#include "src/gpu/GrDrawingManager.h"
13#include "src/gpu/GrFixedClip.h"
14#include "src/gpu/GrPathRenderer.h"
15#include "src/gpu/GrRecordingContextPriv.h"
16#include "src/gpu/GrReducedClip.h"
17#include "src/gpu/GrRenderTargetContext.h"
18#include "src/gpu/GrRenderTargetContextPriv.h"
19#include "src/gpu/GrStencilClip.h"
20#include "src/gpu/GrStencilMaskHelper.h"
21#include "src/gpu/GrStencilSettings.h"
22#include "src/gpu/GrStyle.h"
23#include "src/gpu/GrUserStencilSettings.h"
24#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h"
25#include "src/gpu/effects/GrConvexPolyEffect.h"
26#include "src/gpu/effects/GrRRectEffect.h"
27#include "src/gpu/effects/generated/GrAARectEffect.h"
28#include "src/gpu/effects/generated/GrDeviceSpaceEffect.h"
29#include "src/gpu/geometry/GrStyledShape.h"
30#include "src/shaders/SkShaderBase.h"
31
32/**
33 * There are plenty of optimizations that could be added here. Maybe flips could be folded into
34 * earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
35 * for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
36 * based on later intersect operations, and perhaps remove intersect-rects. We could optionally
37 * take a rect in case the caller knows a bound on what is to be drawn through this clip.
38 */
39GrReducedClip::GrReducedClip(const SkClipStack& stack, const SkRect& queryBounds,
40 const GrCaps* caps, int maxWindowRectangles, int maxAnalyticElements,
41 int maxCCPRClipPaths)
42 : fCaps(caps)
43 , fMaxWindowRectangles(maxWindowRectangles)
44 , fMaxAnalyticElements(maxAnalyticElements)
45 , fMaxCCPRClipPaths(maxCCPRClipPaths) {
46 SkASSERT(!queryBounds.isEmpty());
47 SkASSERT(fMaxWindowRectangles <= GrWindowRectangles::kMaxWindows);
48 SkASSERT(fMaxCCPRClipPaths <= fMaxAnalyticElements);
49
50 if (stack.isWideOpen()) {
51 fInitialState = InitialState::kAllIn;
52 return;
53 }
54
55 SkClipStack::BoundsType stackBoundsType;
56 SkRect stackBounds;
57 bool iior;
58 stack.getBounds(&stackBounds, &stackBoundsType, &iior);
59
60 if (GrClip::IsOutsideClip(stackBounds, queryBounds)) {
61 bool insideOut = SkClipStack::kInsideOut_BoundsType == stackBoundsType;
62 fInitialState = insideOut ? InitialState::kAllIn : InitialState::kAllOut;
63 return;
64 }
65
66 if (iior) {
67 // "Is intersection of rects" means the clip is a single rect indicated by the stack bounds.
68 // This should only be true if aa/non-aa status matches among all elements.
69 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
70
71 if (GrClip::IsInsideClip(stackBounds, queryBounds)) {
72 fInitialState = InitialState::kAllIn;
73 return;
74 }
75
76 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
77
78 if (!iter.prev()->isAA() || GrClip::IsPixelAligned(stackBounds)) {
79 // The clip is a non-aa rect. Here we just implement the entire thing using fScissor.
80 stackBounds.round(&fScissor);
81 fHasScissor = true;
82 fInitialState = fScissor.isEmpty() ? InitialState::kAllOut : InitialState::kAllIn;
83 return;
84 }
85
86 SkRect tightBounds;
87 SkAssertResult(tightBounds.intersect(stackBounds, queryBounds));
88 fScissor = GrClip::GetPixelIBounds(tightBounds);
89 if (fScissor.isEmpty()) {
90 fInitialState = InitialState::kAllOut;
91 return;
92 }
93 fHasScissor = true;
94
95 fAAClipRect = stackBounds;
96 fAAClipRectGenID = stack.getTopmostGenID();
97 SkASSERT(SK_InvalidGenID != fAAClipRectGenID);
98
99 fInitialState = InitialState::kAllIn;
100 } else {
101 SkRect tighterQuery = queryBounds;
102 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
103 // Tighten the query by introducing a new clip at the stack's pixel boundaries. (This
104 // new clip will be enforced by the scissor.)
105 SkAssertResult(tighterQuery.intersect(GrClip::GetPixelBounds(stackBounds)));
106 }
107
108 fScissor = GrClip::GetPixelIBounds(tighterQuery);
109 if (fScissor.isEmpty()) {
110 fInitialState = InitialState::kAllOut;
111 return;
112 }
113 fHasScissor = true;
114
115 // Now that we have determined the bounds to use and filtered out the trivial cases, call
116 // the helper that actually walks the stack.
117 this->walkStack(stack, tighterQuery);
118
119 if (fInitialState == InitialState::kAllOut && fMaskElements.isEmpty()) {
120 // The clip starts with no coverage and there are no elements to add coverage with
121 // expanding ops. We ignore the AAClipRectGenID since it is an implied intersection.
122 this->makeEmpty();
123 return;
124 }
125 }
126
127 if (SK_InvalidGenID != fAAClipRectGenID && // Is there an AA clip rect?
128 ClipResult::kNotClipped == this->addAnalyticRect(fAAClipRect, Invert::kNo, GrAA::kYes)) {
129 if (fMaskElements.isEmpty()) {
130 // Use a replace since it is faster than intersect.
131 fMaskElements.addToHead(fAAClipRect, SkMatrix::I(), kReplace_SkClipOp, true /*doAA*/);
132 fInitialState = InitialState::kAllOut;
133 } else {
134 fMaskElements.addToTail(fAAClipRect, SkMatrix::I(), kIntersect_SkClipOp, true /*doAA*/);
135 }
136 fMaskRequiresAA = true;
137 fMaskGenID = fAAClipRectGenID;
138 }
139}
140
141void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBounds) {
142 // walk backwards until we get to:
143 // a) the beginning
144 // b) an operation that is known to make the bounds all inside/outside
145 // c) a replace operation
146
147 enum class InitialTriState {
148 kUnknown = -1,
149 kAllIn = (int)GrReducedClip::InitialState::kAllIn,
150 kAllOut = (int)GrReducedClip::InitialState::kAllOut
151 } initialTriState = InitialTriState::kUnknown;
152
153 // During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
154 // TODO: track these per saved clip so that we can consider them on the forward pass.
155 bool embiggens = false;
156 bool emsmallens = false;
157
158 // We use a slightly relaxed set of query bounds for element containment tests. This is to
159 // account for floating point rounding error that may have occurred during coord transforms.
160 SkRect relaxedQueryBounds = queryBounds.makeInset(GrClip::kBoundsTolerance,
161 GrClip::kBoundsTolerance);
162 if (relaxedQueryBounds.isEmpty()) {
163 relaxedQueryBounds = queryBounds;
164 }
165
166 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
167 int numAAElements = 0;
168 while (InitialTriState::kUnknown == initialTriState) {
169 const Element* element = iter.prev();
170 if (nullptr == element) {
171 initialTriState = InitialTriState::kAllIn;
172 break;
173 }
174 if (SkClipStack::kEmptyGenID == element->getGenID()) {
175 initialTriState = InitialTriState::kAllOut;
176 break;
177 }
178 if (SkClipStack::kWideOpenGenID == element->getGenID()) {
179 initialTriState = InitialTriState::kAllIn;
180 break;
181 }
182
183 if (element->getDeviceSpaceType() == Element::DeviceSpaceType::kShader) {
184 if (fShader) {
185 // Combine multiple shaders together with src-in blending. This works because all
186 // shaders are effectively intersections (difference ops have been modified to be
187 // 1 - alpha already).
188 fShader = SkShaders::Blend(SkBlendMode::kSrcIn, element->refShader(), fShader);
189 } else {
190 fShader = element->refShader();
191 }
192 continue;
193 }
194
195 bool skippable = false;
196 bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
197
198 switch (element->getOp()) {
199 case kDifference_SkClipOp:
200 // check if the shape subtracted either contains the entire bounds (and makes
201 // the clip empty) or is outside the bounds and therefore 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::kAllOut;
207 skippable = true;
208 } else if (!embiggens) {
209 ClipResult result = this->clipInsideElement(element);
210 if (ClipResult::kMadeEmpty == result) {
211 return;
212 }
213 skippable = (ClipResult::kClipped == result);
214 }
215 } else {
216 if (element->contains(relaxedQueryBounds)) {
217 initialTriState = InitialTriState::kAllOut;
218 skippable = true;
219 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
220 skippable = true;
221 } else if (!embiggens) {
222 ClipResult result = this->clipOutsideElement(element);
223 if (ClipResult::kMadeEmpty == result) {
224 return;
225 }
226 skippable = (ClipResult::kClipped == result);
227 }
228 }
229 if (!skippable) {
230 emsmallens = true;
231 }
232 break;
233 case kIntersect_SkClipOp:
234 // check if the shape intersected contains the entire bounds and therefore can
235 // be skipped or it is outside the entire bounds and therefore makes the clip
236 // empty.
237 if (element->isInverseFilled()) {
238 if (element->contains(relaxedQueryBounds)) {
239 initialTriState = InitialTriState::kAllOut;
240 skippable = true;
241 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
242 skippable = true;
243 } else if (!embiggens) {
244 ClipResult result = this->clipOutsideElement(element);
245 if (ClipResult::kMadeEmpty == result) {
246 return;
247 }
248 skippable = (ClipResult::kClipped == result);
249 }
250 } else {
251 if (element->contains(relaxedQueryBounds)) {
252 skippable = true;
253 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
254 initialTriState = InitialTriState::kAllOut;
255 skippable = true;
256 } else if (!embiggens) {
257 ClipResult result = this->clipInsideElement(element);
258 if (ClipResult::kMadeEmpty == result) {
259 return;
260 }
261 skippable = (ClipResult::kClipped == result);
262 }
263 }
264 if (!skippable) {
265 emsmallens = true;
266 }
267 break;
268 case kUnion_SkClipOp:
269 // If the union-ed shape contains the entire bounds then after this element
270 // the bounds is entirely inside the clip. If the union-ed shape is outside the
271 // bounds then this op can be skipped.
272 if (element->isInverseFilled()) {
273 if (element->contains(relaxedQueryBounds)) {
274 skippable = true;
275 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
276 initialTriState = InitialTriState::kAllIn;
277 skippable = true;
278 }
279 } else {
280 if (element->contains(relaxedQueryBounds)) {
281 initialTriState = InitialTriState::kAllIn;
282 skippable = true;
283 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
284 skippable = true;
285 }
286 }
287 if (!skippable) {
288 embiggens = true;
289 }
290 break;
291 case kXOR_SkClipOp:
292 // If the bounds is entirely inside the shape being xor-ed then the effect is
293 // to flip the inside/outside state of every point in the bounds. We may be
294 // able to take advantage of this in the forward pass. If the xor-ed shape
295 // doesn't intersect the bounds then it can be skipped.
296 if (element->isInverseFilled()) {
297 if (element->contains(relaxedQueryBounds)) {
298 skippable = true;
299 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
300 isFlip = true;
301 }
302 } else {
303 if (element->contains(relaxedQueryBounds)) {
304 isFlip = true;
305 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
306 skippable = true;
307 }
308 }
309 if (!skippable) {
310 emsmallens = embiggens = true;
311 }
312 break;
313 case kReverseDifference_SkClipOp:
314 // When the bounds is entirely within the rev-diff shape then this behaves like xor
315 // and reverses every point inside the bounds. If the shape is completely outside
316 // the bounds then we know after this element is applied that the bounds will be
317 // all outside the current clip.B
318 if (element->isInverseFilled()) {
319 if (element->contains(relaxedQueryBounds)) {
320 initialTriState = InitialTriState::kAllOut;
321 skippable = true;
322 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
323 isFlip = true;
324 }
325 } else {
326 if (element->contains(relaxedQueryBounds)) {
327 isFlip = true;
328 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
329 initialTriState = InitialTriState::kAllOut;
330 skippable = true;
331 }
332 }
333 if (!skippable) {
334 emsmallens = embiggens = true;
335 }
336 break;
337
338 case kReplace_SkClipOp:
339 // Replace will always terminate our walk. We will either begin the forward walk
340 // at the replace op or detect here than the shape is either completely inside
341 // or completely outside the bounds. In this latter case it can be skipped by
342 // setting the correct value for initialTriState.
343 if (element->isInverseFilled()) {
344 if (element->contains(relaxedQueryBounds)) {
345 initialTriState = InitialTriState::kAllOut;
346 skippable = true;
347 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
348 initialTriState = InitialTriState::kAllIn;
349 skippable = true;
350 } else if (!embiggens) {
351 ClipResult result = this->clipOutsideElement(element);
352 if (ClipResult::kMadeEmpty == result) {
353 return;
354 }
355 if (ClipResult::kClipped == result) {
356 initialTriState = InitialTriState::kAllIn;
357 skippable = true;
358 }
359 }
360 } else {
361 if (element->contains(relaxedQueryBounds)) {
362 initialTriState = InitialTriState::kAllIn;
363 skippable = true;
364 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
365 initialTriState = InitialTriState::kAllOut;
366 skippable = true;
367 } else if (!embiggens) {
368 ClipResult result = this->clipInsideElement(element);
369 if (ClipResult::kMadeEmpty == result) {
370 return;
371 }
372 if (ClipResult::kClipped == result) {
373 initialTriState = InitialTriState::kAllIn;
374 skippable = true;
375 }
376 }
377 }
378 if (!skippable) {
379 initialTriState = InitialTriState::kAllOut;
380 embiggens = emsmallens = true;
381 }
382 break;
383 default:
384 SkDEBUGFAIL("Unexpected op.");
385 break;
386 }
387 if (!skippable) {
388 if (fMaskElements.isEmpty()) {
389 // This will be the last element. Record the stricter genID.
390 fMaskGenID = element->getGenID();
391 }
392
393 // if it is a flip, change it to a bounds-filling rect
394 if (isFlip) {
395 SkASSERT(kXOR_SkClipOp == element->getOp() ||
396 kReverseDifference_SkClipOp == element->getOp());
397 fMaskElements.addToHead(SkRect::Make(fScissor), SkMatrix::I(),
398 kReverseDifference_SkClipOp, false);
399 } else {
400 Element* newElement = fMaskElements.addToHead(*element);
401 if (newElement->isAA()) {
402 ++numAAElements;
403 }
404 // Intersecting an inverse shape is the same as differencing the non-inverse shape.
405 // Replacing with an inverse shape is the same as setting initialState=kAllIn and
406 // differencing the non-inverse shape.
407 bool isReplace = kReplace_SkClipOp == newElement->getOp();
408 if (newElement->isInverseFilled() &&
409 (kIntersect_SkClipOp == newElement->getOp() || isReplace)) {
410 newElement->invertShapeFillType();
411 newElement->setOp(kDifference_SkClipOp);
412 if (isReplace) {
413 SkASSERT(InitialTriState::kAllOut == initialTriState);
414 initialTriState = InitialTriState::kAllIn;
415 }
416 }
417 }
418 }
419 }
420
421 if ((InitialTriState::kAllOut == initialTriState && !embiggens) ||
422 (InitialTriState::kAllIn == initialTriState && !emsmallens)) {
423 fMaskElements.reset();
424 numAAElements = 0;
425 } else {
426 Element* element = fMaskElements.headIter().get();
427 while (element) {
428 bool skippable = false;
429 switch (element->getOp()) {
430 case kDifference_SkClipOp:
431 // subtracting from the empty set yields the empty set.
432 skippable = InitialTriState::kAllOut == initialTriState;
433 break;
434 case kIntersect_SkClipOp:
435 // intersecting with the empty set yields the empty set
436 if (InitialTriState::kAllOut == initialTriState) {
437 skippable = true;
438 } else {
439 // We can clear to zero and then simply draw the clip element.
440 initialTriState = InitialTriState::kAllOut;
441 element->setOp(kReplace_SkClipOp);
442 }
443 break;
444 case kUnion_SkClipOp:
445 if (InitialTriState::kAllIn == initialTriState) {
446 // unioning the infinite plane with anything is a no-op.
447 skippable = true;
448 } else {
449 // unioning the empty set with a shape is the shape.
450 element->setOp(kReplace_SkClipOp);
451 }
452 break;
453 case kXOR_SkClipOp:
454 if (InitialTriState::kAllOut == initialTriState) {
455 // xor could be changed to diff in the kAllIn case, not sure it's a win.
456 element->setOp(kReplace_SkClipOp);
457 }
458 break;
459 case kReverseDifference_SkClipOp:
460 if (InitialTriState::kAllIn == initialTriState) {
461 // subtracting the whole plane will yield the empty set.
462 skippable = true;
463 initialTriState = InitialTriState::kAllOut;
464 } else {
465 // this picks up flips inserted in the backwards pass.
466 skippable = element->isInverseFilled() ?
467 GrClip::IsOutsideClip(element->getBounds(), queryBounds) :
468 element->contains(relaxedQueryBounds);
469 if (skippable) {
470 initialTriState = InitialTriState::kAllIn;
471 } else {
472 element->setOp(kReplace_SkClipOp);
473 }
474 }
475 break;
476 case kReplace_SkClipOp:
477 skippable = false; // we would have skipped it in the backwards walk if we
478 // could've.
479 break;
480 default:
481 SkDEBUGFAIL("Unexpected op.");
482 break;
483 }
484 if (!skippable) {
485 break;
486 } else {
487 if (element->isAA()) {
488 --numAAElements;
489 }
490 fMaskElements.popHead();
491 element = fMaskElements.headIter().get();
492 }
493 }
494 }
495 fMaskRequiresAA = numAAElements > 0;
496
497 SkASSERT(InitialTriState::kUnknown != initialTriState);
498 fInitialState = static_cast<GrReducedClip::InitialState>(initialTriState);
499}
500
501GrReducedClip::ClipResult GrReducedClip::clipInsideElement(const Element* element) {
502 SkASSERT(element->getDeviceSpaceType() != Element::DeviceSpaceType::kShader);
503
504 SkIRect elementIBounds;
505 if (!element->isAA()) {
506 element->getBounds().round(&elementIBounds);
507 } else {
508 elementIBounds = GrClip::GetPixelIBounds(element->getBounds());
509 }
510 SkASSERT(fHasScissor);
511 if (!fScissor.intersect(elementIBounds)) {
512 this->makeEmpty();
513 return ClipResult::kMadeEmpty;
514 }
515
516 switch (element->getDeviceSpaceType()) {
517 case Element::DeviceSpaceType::kEmpty:
518 return ClipResult::kMadeEmpty;
519
520 case Element::DeviceSpaceType::kRect:
521 SkASSERT(element->getBounds() == element->getDeviceSpaceRect());
522 SkASSERT(!element->isInverseFilled());
523 if (element->isAA()) {
524 if (SK_InvalidGenID == fAAClipRectGenID) { // No AA clip rect yet?
525 fAAClipRect = element->getDeviceSpaceRect();
526 // fAAClipRectGenID is the value we should use for fMaskGenID if we end up
527 // moving the AA clip rect into the mask. The mask GenID is simply the topmost
528 // element's GenID. And since we walk the stack backwards, this means it's just
529 // the first element we don't skip during our walk.
530 fAAClipRectGenID = fMaskElements.isEmpty() ? element->getGenID() : fMaskGenID;
531 SkASSERT(SK_InvalidGenID != fAAClipRectGenID);
532 } else if (!fAAClipRect.intersect(element->getDeviceSpaceRect())) {
533 this->makeEmpty();
534 return ClipResult::kMadeEmpty;
535 }
536 }
537 return ClipResult::kClipped;
538
539 case Element::DeviceSpaceType::kRRect:
540 SkASSERT(!element->isInverseFilled());
541 return this->addAnalyticRRect(element->getDeviceSpaceRRect(), Invert::kNo,
542 GrAA(element->isAA()));
543
544 case Element::DeviceSpaceType::kPath:
545 return this->addAnalyticPath(element->getDeviceSpacePath(),
546 Invert(element->isInverseFilled()), GrAA(element->isAA()));
547
548 case Element::DeviceSpaceType::kShader:
549 SkUNREACHABLE;
550 }
551
552 SK_ABORT("Unexpected DeviceSpaceType");
553}
554
555GrReducedClip::ClipResult GrReducedClip::clipOutsideElement(const Element* element) {
556 SkASSERT(element->getDeviceSpaceType() != Element::DeviceSpaceType::kShader);
557
558 switch (element->getDeviceSpaceType()) {
559 case Element::DeviceSpaceType::kEmpty:
560 return ClipResult::kMadeEmpty;
561
562 case Element::DeviceSpaceType::kRect:
563 SkASSERT(!element->isInverseFilled());
564 if (fWindowRects.count() < fMaxWindowRectangles) {
565 // Clip out the inside of every rect. We won't be able to entirely skip the AA ones,
566 // but it saves processing time.
567 this->addWindowRectangle(element->getDeviceSpaceRect(), element->isAA());
568 if (!element->isAA()) {
569 return ClipResult::kClipped;
570 }
571 }
572 return this->addAnalyticRect(element->getDeviceSpaceRect(), Invert::kYes,
573 GrAA(element->isAA()));
574
575 case Element::DeviceSpaceType::kRRect: {
576 SkASSERT(!element->isInverseFilled());
577 const SkRRect& clipRRect = element->getDeviceSpaceRRect();
578 ClipResult clipResult = this->addAnalyticRRect(clipRRect, Invert::kYes,
579 GrAA(element->isAA()));
580 if (fWindowRects.count() >= fMaxWindowRectangles) {
581 return clipResult;
582 }
583
584 // Clip out the interiors of round rects with two window rectangles in the shape of a
585 // "plus". This doesn't let us skip the clip element, but still saves processing time.
586 SkVector insetTL = clipRRect.radii(SkRRect::kUpperLeft_Corner);
587 SkVector insetBR = clipRRect.radii(SkRRect::kLowerRight_Corner);
588 if (SkRRect::kComplex_Type == clipRRect.getType()) {
589 const SkVector& insetTR = clipRRect.radii(SkRRect::kUpperRight_Corner);
590 const SkVector& insetBL = clipRRect.radii(SkRRect::kLowerLeft_Corner);
591 insetTL.fX = std::max(insetTL.x(), insetBL.x());
592 insetTL.fY = std::max(insetTL.y(), insetTR.y());
593 insetBR.fX = std::max(insetBR.x(), insetTR.x());
594 insetBR.fY = std::max(insetBR.y(), insetBL.y());
595 }
596 const SkRect& bounds = clipRRect.getBounds();
597 if (insetTL.x() + insetBR.x() >= bounds.width() ||
598 insetTL.y() + insetBR.y() >= bounds.height()) {
599 return clipResult; // The interior "plus" is empty.
600 }
601
602 SkRect horzRect = SkRect::MakeLTRB(bounds.left(), bounds.top() + insetTL.y(),
603 bounds.right(), bounds.bottom() - insetBR.y());
604 this->addWindowRectangle(horzRect, element->isAA());
605
606 if (fWindowRects.count() < fMaxWindowRectangles) {
607 SkRect vertRect = SkRect::MakeLTRB(bounds.left() + insetTL.x(), bounds.top(),
608 bounds.right() - insetBR.x(), bounds.bottom());
609 this->addWindowRectangle(vertRect, element->isAA());
610 }
611
612 return clipResult;
613 }
614
615 case Element::DeviceSpaceType::kPath:
616 return this->addAnalyticPath(element->getDeviceSpacePath(),
617 Invert(!element->isInverseFilled()),
618 GrAA(element->isAA()));
619
620 case Element::DeviceSpaceType::kShader:
621 SkUNREACHABLE;
622 }
623
624 SK_ABORT("Unexpected DeviceSpaceType");
625}
626
627inline void GrReducedClip::addWindowRectangle(const SkRect& elementInteriorRect, bool elementIsAA) {
628 SkIRect window;
629 if (!elementIsAA) {
630 elementInteriorRect.round(&window);
631 } else {
632 elementInteriorRect.roundIn(&window);
633 }
634 if (!window.isEmpty()) { // Skip very thin windows that round to zero or negative dimensions.
635 fWindowRects.addWindow(window);
636 }
637}
638
639GrClipEdgeType GrReducedClip::GetClipEdgeType(Invert invert, GrAA aa) {
640 if (Invert::kNo == invert) {
641 return (GrAA::kYes == aa) ? GrClipEdgeType::kFillAA : GrClipEdgeType::kFillBW;
642 } else {
643 return (GrAA::kYes == aa) ? GrClipEdgeType::kInverseFillAA : GrClipEdgeType::kInverseFillBW;
644 }
645}
646
647GrReducedClip::ClipResult GrReducedClip::addAnalyticRect(const SkRect& deviceSpaceRect,
648 Invert invert, GrAA aa) {
649 if (this->numAnalyticElements() >= fMaxAnalyticElements) {
650 return ClipResult::kNotClipped;
651 }
652
653 fAnalyticFP = GrAARectEffect::Make(std::move(fAnalyticFP), GetClipEdgeType(invert, aa),
654 deviceSpaceRect);
655
656 SkASSERT(fAnalyticFP != nullptr);
657 ++fNumAnalyticElements;
658
659 return ClipResult::kClipped;
660}
661
662GrReducedClip::ClipResult GrReducedClip::addAnalyticRRect(const SkRRect& deviceSpaceRRect,
663 Invert invert, GrAA aa) {
664 if (this->numAnalyticElements() >= fMaxAnalyticElements) {
665 return ClipResult::kNotClipped;
666 }
667
668 // Combine this analytic effect with the previous effect in the stack.
669 bool success;
670 std::tie(success, fAnalyticFP) = GrRRectEffect::Make(std::move(fAnalyticFP),
671 GetClipEdgeType(invert, aa),
672 deviceSpaceRRect, *fCaps->shaderCaps());
673 if (success) {
674 ++fNumAnalyticElements;
675 return ClipResult::kClipped;
676 }
677
678 SkPath deviceSpacePath;
679 deviceSpacePath.setIsVolatile(true);
680 deviceSpacePath.addRRect(deviceSpaceRRect);
681 return this->addAnalyticPath(deviceSpacePath, invert, aa);
682}
683
684GrReducedClip::ClipResult GrReducedClip::addAnalyticPath(const SkPath& deviceSpacePath,
685 Invert invert, GrAA aa) {
686 if (this->numAnalyticElements() >= fMaxAnalyticElements) {
687 return ClipResult::kNotClipped;
688 }
689
690 // Combine this analytic effect with the previous effect in the stack.
691 bool success;
692 std::tie(success, fAnalyticFP) = GrConvexPolyEffect::Make(std::move(fAnalyticFP),
693 GetClipEdgeType(invert, aa),
694 deviceSpacePath);
695 if (success) {
696 ++fNumAnalyticElements;
697 return ClipResult::kClipped;
698 }
699
700 if (fCCPRClipPaths.count() < fMaxCCPRClipPaths && GrAA::kYes == aa) {
701 // Set aside CCPR paths for later. We will create their clip FPs once we know the ID of the
702 // opsTask they will operate in.
703 SkPath& ccprClipPath = fCCPRClipPaths.push_back(deviceSpacePath);
704 if (Invert::kYes == invert) {
705 ccprClipPath.toggleInverseFillType();
706 }
707 return ClipResult::kClipped;
708 }
709
710 return ClipResult::kNotClipped;
711}
712
713void GrReducedClip::makeEmpty() {
714 fHasScissor = false;
715 fAAClipRectGenID = SK_InvalidGenID;
716 fWindowRects.reset();
717 fMaskElements.reset();
718 fShader.reset();
719 fInitialState = InitialState::kAllOut;
720 fAnalyticFP = nullptr;
721 fNumAnalyticElements = 0;
722 fCCPRClipPaths.reset();
723}
724
725////////////////////////////////////////////////////////////////////////////////
726// Create a 8-bit clip mask in alpha
727
728static bool stencil_element(GrRenderTargetContext* rtc,
729 const GrFixedClip& clip,
730 const GrUserStencilSettings* ss,
731 const SkMatrix& viewMatrix,
732 const SkClipStack::Element* element) {
733 GrAA aa = GrAA(element->isAA());
734 switch (element->getDeviceSpaceType()) {
735 case SkClipStack::Element::DeviceSpaceType::kEmpty:
736 SkDEBUGFAIL("Should never get here with an empty element.");
737 break;
738 case SkClipStack::Element::DeviceSpaceType::kRect: {
739 GrPaint paint;
740 paint.setCoverageSetOpXPFactory((SkRegion::Op)element->getOp(),
741 element->isInverseFilled());
742 rtc->priv().stencilRect(&clip, ss, std::move(paint), aa, viewMatrix,
743 element->getDeviceSpaceRect());
744 return true;
745 }
746 default: {
747 SkPath path;
748 element->asDeviceSpacePath(&path);
749 if (path.isInverseFillType()) {
750 path.toggleInverseFillType();
751 }
752
753 return rtc->priv().drawAndStencilPath(&clip, ss, (SkRegion::Op)element->getOp(),
754 element->isInverseFilled(), aa, viewMatrix, path);
755 }
756 }
757
758 return false;
759}
760
761static void draw_element(GrRenderTargetContext* rtc,
762 const GrClip& clip, // TODO: can this just always be WideOpen?
763 GrPaint&& paint,
764 GrAA aa,
765 const SkMatrix& viewMatrix,
766 const SkClipStack::Element* element) {
767 // TODO: Draw rrects directly here.
768 switch (element->getDeviceSpaceType()) {
769 case SkClipStack::Element::DeviceSpaceType::kEmpty:
770 SkDEBUGFAIL("Should never get here with an empty element.");
771 break;
772 case SkClipStack::Element::DeviceSpaceType::kRect:
773 rtc->drawRect(&clip, std::move(paint), aa, viewMatrix, element->getDeviceSpaceRect());
774 break;
775 default: {
776 SkPath path;
777 element->asDeviceSpacePath(&path);
778 if (path.isInverseFillType()) {
779 path.toggleInverseFillType();
780 }
781
782 rtc->drawPath(&clip, std::move(paint), aa, viewMatrix, path, GrStyle::SimpleFill());
783 break;
784 }
785 }
786}
787
788bool GrReducedClip::drawAlphaClipMask(GrRenderTargetContext* rtc) const {
789 // The texture may be larger than necessary, this rect represents the part of the texture
790 // we populate with a rasterization of the clip.
791 GrFixedClip clip(rtc->dimensions(), SkIRect::MakeWH(fScissor.width(), fScissor.height()));
792
793 if (!fWindowRects.empty()) {
794 clip.setWindowRectangles(fWindowRects.makeOffset(-fScissor.left(), -fScissor.top()),
795 GrWindowRectsState::Mode::kExclusive);
796 }
797
798 // The scratch texture that we are drawing into can be substantially larger than the mask. Only
799 // clear the part that we care about.
800 SkPMColor4f initialCoverage =
801 InitialState::kAllIn == this->initialState() ? SK_PMColor4fWHITE : SK_PMColor4fTRANSPARENT;
802 if (clip.hasWindowRectangles()) {
803 GrPaint paint;
804 paint.setColor4f(initialCoverage);
805 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
806 rtc->drawRect(&clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
807 SkRect::Make(clip.scissorRect()));
808 } else {
809 rtc->priv().clearAtLeast(clip.scissorRect(), initialCoverage);
810 }
811
812 // Set the matrix so that rendered clip elements are transformed to mask space from clip space.
813 SkMatrix translate;
814 translate.setTranslate(SkIntToScalar(-fScissor.left()), SkIntToScalar(-fScissor.top()));
815
816 // walk through each clip element and perform its set op
817 for (ElementList::Iter iter(fMaskElements); iter.get(); iter.next()) {
818 const Element* element = iter.get();
819 SkRegion::Op op = (SkRegion::Op)element->getOp();
820 GrAA aa = GrAA(element->isAA());
821 bool invert = element->isInverseFilled();
822 if (invert || SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) {
823 // draw directly into the result with the stencil set to make the pixels affected
824 // by the clip shape be non-zero.
825 static constexpr GrUserStencilSettings kStencilInElement(
826 GrUserStencilSettings::StaticInit<
827 0xffff,
828 GrUserStencilTest::kAlways,
829 0xffff,
830 GrUserStencilOp::kReplace,
831 GrUserStencilOp::kReplace,
832 0xffff>()
833 );
834 if (!stencil_element(rtc, clip, &kStencilInElement, translate, element)) {
835 return false;
836 }
837
838 // Draw to the exterior pixels (those with a zero stencil value).
839 static constexpr GrUserStencilSettings kDrawOutsideElement(
840 GrUserStencilSettings::StaticInit<
841 0x0000,
842 GrUserStencilTest::kEqual,
843 0xffff,
844 GrUserStencilOp::kZero,
845 GrUserStencilOp::kZero,
846 0xffff>()
847 );
848
849 GrPaint paint;
850 paint.setCoverageSetOpXPFactory(op, !invert);
851 rtc->priv().stencilRect(&clip, &kDrawOutsideElement, std::move(paint), GrAA::kNo,
852 translate, SkRect::Make(fScissor));
853 } else {
854 // all the remaining ops can just be directly draw into the accumulation buffer
855 GrPaint paint;
856 paint.setCoverageSetOpXPFactory(op, false);
857
858 draw_element(rtc, clip, std::move(paint), aa, translate, element);
859 }
860 }
861
862 return true;
863}
864
865////////////////////////////////////////////////////////////////////////////////
866// Create a 1-bit clip mask in the stencil buffer.
867
868bool GrReducedClip::drawStencilClipMask(GrRecordingContext* context,
869 GrRenderTargetContext* renderTargetContext) const {
870 GrStencilMaskHelper helper(context, renderTargetContext);
871 if (!helper.init(fScissor, this->maskGenID(), fWindowRects, this->numAnalyticElements())) {
872 // The stencil mask doesn't need updating
873 return true;
874 }
875
876 helper.clear(InitialState::kAllIn == this->initialState());
877
878 // walk through each clip element and perform its set op with the existing clip.
879 for (ElementList::Iter iter(fMaskElements); iter.get(); iter.next()) {
880 const Element* element = iter.get();
881 SkRegion::Op op = (SkRegion::Op)element->getOp();
882 GrAA aa = element->isAA() ? GrAA::kYes : GrAA::kNo;
883
884 if (Element::DeviceSpaceType::kRect == element->getDeviceSpaceType()) {
885 helper.drawRect(element->getDeviceSpaceRect(), SkMatrix::I(), op, aa);
886 } else {
887 SkPath path;
888 element->asDeviceSpacePath(&path);
889 if (!helper.drawPath(path, SkMatrix::I(), op, aa)) {
890 return false;
891 }
892 }
893 }
894
895 helper.finish();
896 return true;
897}
898
899int GrReducedClip::numAnalyticElements() const {
900 return fCCPRClipPaths.size() + fNumAnalyticElements;
901}
902
903std::unique_ptr<GrFragmentProcessor> GrReducedClip::finishAndDetachAnalyticElements(
904 GrRecordingContext* context, const SkMatrixProvider& matrixProvider,
905 GrCoverageCountingPathRenderer* ccpr, uint32_t opsTaskID) {
906 // Combine the analytic FP with any CCPR clip processors.
907 std::unique_ptr<GrFragmentProcessor> clipFP = std::move(fAnalyticFP);
908 fNumAnalyticElements = 0;
909
910 for (const SkPath& ccprClipPath : fCCPRClipPaths) {
911 SkASSERT(ccpr);
912 SkASSERT(fHasScissor);
913 clipFP = ccpr->makeClipProcessor(std::move(clipFP), opsTaskID, ccprClipPath,
914 fScissor, *fCaps);
915 }
916 fCCPRClipPaths.reset();
917
918 // Create the shader.
919 std::unique_ptr<GrFragmentProcessor> shaderFP;
920 if (fShader != nullptr) {
921 static const GrColorInfo kCoverageColorInfo{GrColorType::kUnknown, kPremul_SkAlphaType,
922 nullptr};
923 GrFPArgs args(context, matrixProvider, kNone_SkFilterQuality, &kCoverageColorInfo);
924 shaderFP = as_SB(fShader)->asFragmentProcessor(args);
925 if (shaderFP != nullptr) {
926 shaderFP = GrFragmentProcessor::SwizzleOutput(std::move(shaderFP), GrSwizzle::AAAA());
927 }
928 }
929
930 // Compose the clip and shader FPs.
931 return GrFragmentProcessor::Compose(std::move(clipFP), std::move(shaderFP));
932}
933
934