blob: 237ea221e1a6c84cae087c503011a05134eb12a9 [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
csmartdaltoncbecb082016-07-22 08:59:08 -070010#include "GrClip.h"
11
bsalomon@google.com170bd792012-12-05 22:26:11 +000012typedef SkClipStack::Element Element;
bsalomon@google.com170bd792012-12-05 22:26:11 +000013
csmartdalton5ecbbbe2016-08-23 13:26:40 -070014/**
15 * There are plenty of optimizations that could be added here. Maybe flips could be folded into
16 * earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
17 * for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
18 * based on later intersect operations, and perhaps remove intersect-rects. We could optionally
19 * take a rect in case the caller knows a bound on what is to be drawn through this clip.
20 */
csmartdalton77f2fae2016-08-08 09:55:06 -070021GrReducedClip::GrReducedClip(const SkClipStack& stack, const SkRect& queryBounds) {
csmartdaltoncbecb082016-07-22 08:59:08 -070022 SkASSERT(!queryBounds.isEmpty());
csmartdaltond211e782016-08-15 11:17:19 -070023 fHasIBounds = false;
csmartdaltoncbecb082016-07-22 08:59:08 -070024
bsalomon@google.com170bd792012-12-05 22:26:11 +000025 if (stack.isWideOpen()) {
csmartdalton77f2fae2016-08-08 09:55:06 -070026 fInitialState = InitialState::kAllIn;
27 return;
bsalomon@google.com170bd792012-12-05 22:26:11 +000028 }
29
30 SkClipStack::BoundsType stackBoundsType;
31 SkRect stackBounds;
32 bool iior;
33 stack.getBounds(&stackBounds, &stackBoundsType, &iior);
34
csmartdaltoncbecb082016-07-22 08:59:08 -070035 if (stackBounds.isEmpty() || GrClip::IsOutsideClip(stackBounds, queryBounds)) {
36 bool insideOut = SkClipStack::kInsideOut_BoundsType == stackBoundsType;
csmartdalton77f2fae2016-08-08 09:55:06 -070037 fInitialState = insideOut ? InitialState::kAllIn : InitialState::kAllOut;
38 return;
bsalomon@google.com170bd792012-12-05 22:26:11 +000039 }
40
csmartdaltoncbecb082016-07-22 08:59:08 -070041 if (iior) {
42 // "Is intersection of rects" means the clip is a single rect indicated by the stack bounds.
43 // This should only be true if aa/non-aa status matches among all elements.
44 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
45 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
46 if (!iter.prev()->isAA() || GrClip::IsPixelAligned(stackBounds)) {
47 // The clip is a non-aa rect. This is the one spot where we can actually implement the
csmartdalton77f2fae2016-08-08 09:55:06 -070048 // clip (using fIBounds) rather than just telling the caller what it should be.
49 stackBounds.round(&fIBounds);
csmartdaltond211e782016-08-15 11:17:19 -070050 fHasIBounds = true;
csmartdalton77f2fae2016-08-08 09:55:06 -070051 fInitialState = fIBounds.isEmpty() ? InitialState::kAllOut : InitialState::kAllIn;
52 return;
csmartdaltoncbecb082016-07-22 08:59:08 -070053 }
54 if (GrClip::IsInsideClip(stackBounds, queryBounds)) {
csmartdalton77f2fae2016-08-08 09:55:06 -070055 fInitialState = InitialState::kAllIn;
56 return;
csmartdaltoncbecb082016-07-22 08:59:08 -070057 }
58
csmartdaltond211e782016-08-15 11:17:19 -070059 SkRect tightBounds;
60 SkAssertResult(tightBounds.intersect(stackBounds, queryBounds));
61 fIBounds = GrClip::GetPixelIBounds(tightBounds);
csmartdalton77f2fae2016-08-08 09:55:06 -070062 SkASSERT(!fIBounds.isEmpty()); // Empty should have been blocked by IsOutsideClip above.
csmartdaltond211e782016-08-15 11:17:19 -070063 fHasIBounds = true;
csmartdaltoncbecb082016-07-22 08:59:08 -070064
csmartdalton77f2fae2016-08-08 09:55:06 -070065 // Implement the clip with an AA rect element.
66 fElements.addToHead(stackBounds, SkRegion::kReplace_Op, true/*doAA*/);
csmartdalton8d3f92a2016-08-17 09:39:38 -070067 fElementsGenID = stack.getTopmostGenID();
csmartdalton77f2fae2016-08-08 09:55:06 -070068 fRequiresAA = true;
69
70 fInitialState = InitialState::kAllOut;
71 return;
csmartdaltoncbecb082016-07-22 08:59:08 -070072 }
73
74 SkRect tighterQuery = queryBounds;
75 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
76 // Tighten the query by introducing a new clip at the stack's pixel boundaries. (This new
csmartdalton77f2fae2016-08-08 09:55:06 -070077 // clip will be enforced by the scissor through fIBounds.)
csmartdaltoncbecb082016-07-22 08:59:08 -070078 SkAssertResult(tighterQuery.intersect(GrClip::GetPixelBounds(stackBounds)));
csmartdaltoncbecb082016-07-22 08:59:08 -070079 }
skia.committer@gmail.comd21444a2012-12-07 02:01:25 +000080
csmartdaltond211e782016-08-15 11:17:19 -070081 fIBounds = GrClip::GetPixelIBounds(tighterQuery);
csmartdalton77f2fae2016-08-08 09:55:06 -070082 SkASSERT(!fIBounds.isEmpty()); // Empty should have been blocked by IsOutsideClip above.
csmartdaltond211e782016-08-15 11:17:19 -070083 fHasIBounds = true;
csmartdalton77f2fae2016-08-08 09:55:06 -070084
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000085 // Now that we have determined the bounds to use and filtered out the trivial cases, call the
86 // helper that actually walks the stack.
csmartdalton5ecbbbe2016-08-23 13:26:40 -070087 this->walkStack(stack, tighterQuery);
88}
89
90void GrReducedClip::walkStack(const SkClipStack& stack, const SkRect& queryBounds) {
91 // walk backwards until we get to:
92 // a) the beginning
93 // b) an operation that is known to make the bounds all inside/outside
94 // c) a replace operation
95
96 enum class InitialTriState {
97 kUnknown = -1,
98 kAllIn = (int)GrReducedClip::InitialState::kAllIn,
99 kAllOut = (int)GrReducedClip::InitialState::kAllOut
100 } initialTriState = InitialTriState::kUnknown;
101
102 // During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
103 // TODO: track these per saved clip so that we can consider them on the forward pass.
104 bool embiggens = false;
105 bool emsmallens = false;
106
107 // We use a slightly relaxed set of query bounds for element containment tests. This is to
108 // account for floating point rounding error that may have occurred during coord transforms.
109 SkRect relaxedQueryBounds = queryBounds.makeInset(GrClip::kBoundsTolerance,
110 GrClip::kBoundsTolerance);
111
112 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
113 int numAAElements = 0;
114 while (InitialTriState::kUnknown == initialTriState) {
115 const Element* element = iter.prev();
116 if (nullptr == element) {
117 initialTriState = InitialTriState::kAllIn;
118 break;
119 }
120 if (SkClipStack::kEmptyGenID == element->getGenID()) {
121 initialTriState = InitialTriState::kAllOut;
122 break;
123 }
124 if (SkClipStack::kWideOpenGenID == element->getGenID()) {
125 initialTriState = InitialTriState::kAllIn;
126 break;
127 }
128
129 bool skippable = false;
130 bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
131
132 switch (element->getOp()) {
133 case SkRegion::kDifference_Op:
134 // check if the shape subtracted either contains the entire bounds (and makes
135 // the clip empty) or is outside the bounds and therefore can be skipped.
136 if (element->isInverseFilled()) {
137 if (element->contains(relaxedQueryBounds)) {
138 skippable = true;
139 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
140 initialTriState = InitialTriState::kAllOut;
141 skippable = true;
142 }
143 } else {
144 if (element->contains(relaxedQueryBounds)) {
145 initialTriState = InitialTriState::kAllOut;
146 skippable = true;
147 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
148 skippable = true;
149 }
150 }
151 if (!skippable) {
152 emsmallens = true;
153 }
154 break;
155 case SkRegion::kIntersect_Op:
156 // check if the shape intersected contains the entire bounds and therefore can
157 // be skipped or it is outside the entire bounds and therefore makes the clip
158 // empty.
159 if (element->isInverseFilled()) {
160 if (element->contains(relaxedQueryBounds)) {
161 initialTriState = InitialTriState::kAllOut;
162 skippable = true;
163 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
164 skippable = true;
165 }
166 } else {
167 if (element->contains(relaxedQueryBounds)) {
168 skippable = true;
169 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
170 initialTriState = InitialTriState::kAllOut;
171 skippable = true;
172 } else if (!embiggens && !element->isAA() &&
173 Element::kRect_Type == element->getType()) {
174 // fIBounds and queryBounds have already acccounted for this element via
175 // clip stack bounds; here we just apply the non-aa rounding effect.
176 SkIRect nonaaRect;
177 element->getRect().round(&nonaaRect);
178 if (!this->intersectIBounds(nonaaRect)) {
179 return;
180 }
181 skippable = true;
182 }
183 }
184 if (!skippable) {
185 emsmallens = true;
186 }
187 break;
188 case SkRegion::kUnion_Op:
189 // If the union-ed shape contains the entire bounds then after this element
190 // the bounds is entirely inside the clip. If the union-ed shape is outside the
191 // bounds then this op can be skipped.
192 if (element->isInverseFilled()) {
193 if (element->contains(relaxedQueryBounds)) {
194 skippable = true;
195 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
196 initialTriState = InitialTriState::kAllIn;
197 skippable = true;
198 }
199 } else {
200 if (element->contains(relaxedQueryBounds)) {
201 initialTriState = InitialTriState::kAllIn;
202 skippable = true;
203 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
204 skippable = true;
205 }
206 }
207 if (!skippable) {
208 embiggens = true;
209 }
210 break;
211 case SkRegion::kXOR_Op:
212 // If the bounds is entirely inside the shape being xor-ed then the effect is
213 // to flip the inside/outside state of every point in the bounds. We may be
214 // able to take advantage of this in the forward pass. If the xor-ed shape
215 // doesn't intersect the bounds then it can be skipped.
216 if (element->isInverseFilled()) {
217 if (element->contains(relaxedQueryBounds)) {
218 skippable = true;
219 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
220 isFlip = true;
221 }
222 } else {
223 if (element->contains(relaxedQueryBounds)) {
224 isFlip = true;
225 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
226 skippable = true;
227 }
228 }
229 if (!skippable) {
230 emsmallens = embiggens = true;
231 }
232 break;
233 case SkRegion::kReverseDifference_Op:
234 // When the bounds is entirely within the rev-diff shape then this behaves like xor
235 // and reverses every point inside the bounds. If the shape is completely outside
236 // the bounds then we know after this element is applied that the bounds will be
237 // all outside the current clip.B
238 if (element->isInverseFilled()) {
239 if (element->contains(relaxedQueryBounds)) {
240 initialTriState = InitialTriState::kAllOut;
241 skippable = true;
242 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
243 isFlip = true;
244 }
245 } else {
246 if (element->contains(relaxedQueryBounds)) {
247 isFlip = true;
248 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
249 initialTriState = InitialTriState::kAllOut;
250 skippable = true;
251 }
252 }
253 if (!skippable) {
254 emsmallens = embiggens = true;
255 }
256 break;
257
258 case SkRegion::kReplace_Op:
259 // Replace will always terminate our walk. We will either begin the forward walk
260 // at the replace op or detect here than the shape is either completely inside
261 // or completely outside the bounds. In this latter case it can be skipped by
262 // setting the correct value for initialTriState.
263 if (element->isInverseFilled()) {
264 if (element->contains(relaxedQueryBounds)) {
265 initialTriState = InitialTriState::kAllOut;
266 skippable = true;
267 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
268 initialTriState = InitialTriState::kAllIn;
269 skippable = true;
270 }
271 } else {
272 if (element->contains(relaxedQueryBounds)) {
273 initialTriState = InitialTriState::kAllIn;
274 skippable = true;
275 } else if (GrClip::IsOutsideClip(element->getBounds(), queryBounds)) {
276 initialTriState = InitialTriState::kAllOut;
277 skippable = true;
278 } else if (!embiggens && !element->isAA() &&
279 Element::kRect_Type == element->getType()) {
280 // fIBounds and queryBounds have already acccounted for this element via
281 // clip stack bounds; here we just apply the non-aa rounding effect.
282 SkIRect nonaaRect;
283 element->getRect().round(&nonaaRect);
284 if (!this->intersectIBounds(nonaaRect)) {
285 return;
286 }
287 initialTriState = InitialTriState::kAllIn;
288 skippable = true;
289 }
290 }
291 if (!skippable) {
292 initialTriState = InitialTriState::kAllOut;
293 embiggens = emsmallens = true;
294 }
295 break;
296 default:
297 SkDEBUGFAIL("Unexpected op.");
298 break;
299 }
300 if (!skippable) {
301 if (0 == fElements.count()) {
302 // This will be the last element. Record the stricter genID.
303 fElementsGenID = element->getGenID();
304 }
305
306 // if it is a flip, change it to a bounds-filling rect
307 if (isFlip) {
308 SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
309 SkRegion::kReverseDifference_Op == element->getOp());
310 fElements.addToHead(SkRect::Make(fIBounds), SkRegion::kReverseDifference_Op, false);
311 } else {
312 Element* newElement = fElements.addToHead(*element);
313 if (newElement->isAA()) {
314 ++numAAElements;
315 }
316 // Intersecting an inverse shape is the same as differencing the non-inverse shape.
317 // Replacing with an inverse shape is the same as setting initialState=kAllIn and
318 // differencing the non-inverse shape.
319 bool isReplace = SkRegion::kReplace_Op == newElement->getOp();
320 if (newElement->isInverseFilled() &&
321 (SkRegion::kIntersect_Op == newElement->getOp() || isReplace)) {
322 newElement->invertShapeFillType();
323 newElement->setOp(SkRegion::kDifference_Op);
324 if (isReplace) {
325 SkASSERT(InitialTriState::kAllOut == initialTriState);
326 initialTriState = InitialTriState::kAllIn;
327 }
328 }
329 }
330 }
331 }
332
333 if ((InitialTriState::kAllOut == initialTriState && !embiggens) ||
334 (InitialTriState::kAllIn == initialTriState && !emsmallens)) {
335 fElements.reset();
336 numAAElements = 0;
337 } else {
338 Element* element = fElements.headIter().get();
339 while (element) {
340 bool skippable = false;
341 switch (element->getOp()) {
342 case SkRegion::kDifference_Op:
343 // subtracting from the empty set yields the empty set.
344 skippable = InitialTriState::kAllOut == initialTriState;
345 break;
346 case SkRegion::kIntersect_Op:
347 // intersecting with the empty set yields the empty set
348 if (InitialTriState::kAllOut == initialTriState) {
349 skippable = true;
350 } else {
351 // We can clear to zero and then simply draw the clip element.
352 initialTriState = InitialTriState::kAllOut;
353 element->setOp(SkRegion::kReplace_Op);
354 }
355 break;
356 case SkRegion::kUnion_Op:
357 if (InitialTriState::kAllIn == initialTriState) {
358 // unioning the infinite plane with anything is a no-op.
359 skippable = true;
360 } else {
361 // unioning the empty set with a shape is the shape.
362 element->setOp(SkRegion::kReplace_Op);
363 }
364 break;
365 case SkRegion::kXOR_Op:
366 if (InitialTriState::kAllOut == initialTriState) {
367 // xor could be changed to diff in the kAllIn case, not sure it's a win.
368 element->setOp(SkRegion::kReplace_Op);
369 }
370 break;
371 case SkRegion::kReverseDifference_Op:
372 if (InitialTriState::kAllIn == initialTriState) {
373 // subtracting the whole plane will yield the empty set.
374 skippable = true;
375 initialTriState = InitialTriState::kAllOut;
376 } else {
377 // this picks up flips inserted in the backwards pass.
378 skippable = element->isInverseFilled() ?
379 GrClip::IsOutsideClip(element->getBounds(), queryBounds) :
380 element->contains(relaxedQueryBounds);
381 if (skippable) {
382 initialTriState = InitialTriState::kAllIn;
383 } else {
384 element->setOp(SkRegion::kReplace_Op);
385 }
386 }
387 break;
388 case SkRegion::kReplace_Op:
389 skippable = false; // we would have skipped it in the backwards walk if we
390 // could've.
391 break;
392 default:
393 SkDEBUGFAIL("Unexpected op.");
394 break;
395 }
396 if (!skippable) {
397 break;
398 } else {
399 if (element->isAA()) {
400 --numAAElements;
401 }
402 fElements.popHead();
403 element = fElements.headIter().get();
404 }
405 }
406 }
407 fRequiresAA = numAAElements > 0;
408
409 SkASSERT(InitialTriState::kUnknown != initialTriState);
410 fInitialState = static_cast<GrReducedClip::InitialState>(initialTriState);
411}
412
413inline bool GrReducedClip::intersectIBounds(const SkIRect& irect) {
414 SkASSERT(fHasIBounds);
415 if (!fIBounds.intersect(irect)) {
416 fHasIBounds = false;
417 fElements.reset();
418 fRequiresAA = false;
419 fInitialState = InitialState::kAllOut;
420 return false;
421 }
422 return true;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000423}