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