blob: 137c83e411ec1af46779587099b9a5f6d4ecf820 [file] [log] [blame]
bsalomon@google.com170bd792012-12-05 22:26:11 +00001/*
2 * Copyright 2012 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 "GrReducedClip.h"
9
10typedef SkClipStack::Element Element;
bsalomon@google.com170bd792012-12-05 22:26:11 +000011
tfarinabf54e492014-10-23 17:47:18 -070012static void reduced_stack_walker(const SkClipStack& stack,
13 const SkRect& queryBounds,
14 GrReducedClip::ElementList* result,
15 int32_t* resultGenID,
16 GrReducedClip::InitialState* initialState,
17 bool* requiresAA) {
bsalomon@google.com170bd792012-12-05 22:26:11 +000018
tfarinabf54e492014-10-23 17:47:18 -070019 // walk backwards until we get to:
20 // a) the beginning
21 // b) an operation that is known to make the bounds all inside/outside
22 // c) a replace operation
23
24 static const GrReducedClip::InitialState kUnknown_InitialState =
25 static_cast<GrReducedClip::InitialState>(-1);
26 *initialState = kUnknown_InitialState;
27
28 // During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
29 // TODO: track these per saved clip so that we can consider them on the forward pass.
30 bool embiggens = false;
31 bool emsmallens = false;
32
33 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
34 int numAAElements = 0;
35 while ((kUnknown_InitialState == *initialState)) {
36 const Element* element = iter.prev();
halcanary96fcdcc2015-08-27 07:41:13 -070037 if (nullptr == element) {
tfarinabf54e492014-10-23 17:47:18 -070038 *initialState = GrReducedClip::kAllIn_InitialState;
39 break;
40 }
41 if (SkClipStack::kEmptyGenID == element->getGenID()) {
42 *initialState = GrReducedClip::kAllOut_InitialState;
43 break;
44 }
45 if (SkClipStack::kWideOpenGenID == element->getGenID()) {
46 *initialState = GrReducedClip::kAllIn_InitialState;
47 break;
48 }
49
50 bool skippable = false;
51 bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
52
53 switch (element->getOp()) {
54 case SkRegion::kDifference_Op:
55 // check if the shape subtracted either contains the entire bounds (and makes
56 // the clip empty) or is outside the bounds and therefore can be skipped.
57 if (element->isInverseFilled()) {
58 if (element->contains(queryBounds)) {
59 skippable = true;
60 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
61 *initialState = GrReducedClip::kAllOut_InitialState;
62 skippable = true;
63 }
64 } else {
65 if (element->contains(queryBounds)) {
66 *initialState = GrReducedClip::kAllOut_InitialState;
67 skippable = true;
68 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
69 skippable = true;
70 }
71 }
72 if (!skippable) {
73 emsmallens = true;
74 }
75 break;
76 case SkRegion::kIntersect_Op:
77 // check if the shape intersected contains the entire bounds and therefore can
78 // be skipped or it is outside the entire bounds and therefore makes the clip
79 // empty.
80 if (element->isInverseFilled()) {
81 if (element->contains(queryBounds)) {
82 *initialState = GrReducedClip::kAllOut_InitialState;
83 skippable = true;
84 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
85 skippable = true;
86 }
87 } else {
88 if (element->contains(queryBounds)) {
89 skippable = true;
90 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
91 *initialState = GrReducedClip::kAllOut_InitialState;
92 skippable = true;
93 }
94 }
95 if (!skippable) {
96 emsmallens = true;
97 }
98 break;
99 case SkRegion::kUnion_Op:
100 // If the union-ed shape contains the entire bounds then after this element
101 // the bounds is entirely inside the clip. If the union-ed shape is outside the
102 // bounds then this op can be skipped.
103 if (element->isInverseFilled()) {
104 if (element->contains(queryBounds)) {
105 skippable = true;
106 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
107 *initialState = GrReducedClip::kAllIn_InitialState;
108 skippable = true;
109 }
110 } else {
111 if (element->contains(queryBounds)) {
112 *initialState = GrReducedClip::kAllIn_InitialState;
113 skippable = true;
114 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
115 skippable = true;
116 }
117 }
118 if (!skippable) {
119 embiggens = true;
120 }
121 break;
122 case SkRegion::kXOR_Op:
123 // If the bounds is entirely inside the shape being xor-ed then the effect is
124 // to flip the inside/outside state of every point in the bounds. We may be
125 // able to take advantage of this in the forward pass. If the xor-ed shape
126 // doesn't intersect the bounds then it can be skipped.
127 if (element->isInverseFilled()) {
128 if (element->contains(queryBounds)) {
129 skippable = true;
130 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
131 isFlip = true;
132 }
133 } else {
134 if (element->contains(queryBounds)) {
135 isFlip = true;
136 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
137 skippable = true;
138 }
139 }
140 if (!skippable) {
141 emsmallens = embiggens = true;
142 }
143 break;
144 case SkRegion::kReverseDifference_Op:
145 // When the bounds is entirely within the rev-diff shape then this behaves like xor
146 // and reverses every point inside the bounds. If the shape is completely outside
147 // the bounds then we know after this element is applied that the bounds will be
148 // all outside the current clip.B
149 if (element->isInverseFilled()) {
150 if (element->contains(queryBounds)) {
151 *initialState = GrReducedClip::kAllOut_InitialState;
152 skippable = true;
153 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
154 isFlip = true;
155 }
156 } else {
157 if (element->contains(queryBounds)) {
158 isFlip = true;
159 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
160 *initialState = GrReducedClip::kAllOut_InitialState;
161 skippable = true;
162 }
163 }
164 if (!skippable) {
165 emsmallens = embiggens = true;
166 }
167 break;
168 case SkRegion::kReplace_Op:
169 // Replace will always terminate our walk. We will either begin the forward walk
170 // at the replace op or detect here than the shape is either completely inside
171 // or completely outside the bounds. In this latter case it can be skipped by
172 // setting the correct value for initialState.
173 if (element->isInverseFilled()) {
174 if (element->contains(queryBounds)) {
175 *initialState = GrReducedClip::kAllOut_InitialState;
176 skippable = true;
177 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
178 *initialState = GrReducedClip::kAllIn_InitialState;
179 skippable = true;
180 }
181 } else {
182 if (element->contains(queryBounds)) {
183 *initialState = GrReducedClip::kAllIn_InitialState;
184 skippable = true;
185 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
186 *initialState = GrReducedClip::kAllOut_InitialState;
187 skippable = true;
188 }
189 }
190 if (!skippable) {
191 *initialState = GrReducedClip::kAllOut_InitialState;
192 embiggens = emsmallens = true;
193 }
194 break;
195 default:
196 SkDEBUGFAIL("Unexpected op.");
197 break;
198 }
199 if (!skippable) {
200 if (0 == result->count()) {
201 // This will be the last element. Record the stricter genID.
202 *resultGenID = element->getGenID();
203 }
204
205 // if it is a flip, change it to a bounds-filling rect
206 if (isFlip) {
207 SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
208 SkRegion::kReverseDifference_Op == element->getOp());
209 SkNEW_INSERT_AT_LLIST_HEAD(result,
210 Element,
211 (queryBounds, SkRegion::kReverseDifference_Op, false));
212 } else {
213 Element* newElement = result->addToHead(*element);
214 if (newElement->isAA()) {
215 ++numAAElements;
216 }
217 // Intersecting an inverse shape is the same as differencing the non-inverse shape.
218 // Replacing with an inverse shape is the same as setting initialState=kAllIn and
219 // differencing the non-inverse shape.
220 bool isReplace = SkRegion::kReplace_Op == newElement->getOp();
221 if (newElement->isInverseFilled() &&
222 (SkRegion::kIntersect_Op == newElement->getOp() || isReplace)) {
223 newElement->invertShapeFillType();
224 newElement->setOp(SkRegion::kDifference_Op);
225 if (isReplace) {
226 SkASSERT(GrReducedClip::kAllOut_InitialState == *initialState);
227 *initialState = GrReducedClip::kAllIn_InitialState;
228 }
229 }
230 }
231 }
232 }
233
234 if ((GrReducedClip::kAllOut_InitialState == *initialState && !embiggens) ||
235 (GrReducedClip::kAllIn_InitialState == *initialState && !emsmallens)) {
236 result->reset();
237 } else {
238 Element* element = result->headIter().get();
239 while (element) {
240 bool skippable = false;
241 switch (element->getOp()) {
242 case SkRegion::kDifference_Op:
243 // subtracting from the empty set yields the empty set.
244 skippable = GrReducedClip::kAllOut_InitialState == *initialState;
245 break;
246 case SkRegion::kIntersect_Op:
247 // intersecting with the empty set yields the empty set
248 if (GrReducedClip::kAllOut_InitialState == *initialState) {
249 skippable = true;
250 } else {
251 // We can clear to zero and then simply draw the clip element.
252 *initialState = GrReducedClip::kAllOut_InitialState;
253 element->setOp(SkRegion::kReplace_Op);
254 }
255 break;
256 case SkRegion::kUnion_Op:
257 if (GrReducedClip::kAllIn_InitialState == *initialState) {
258 // unioning the infinite plane with anything is a no-op.
259 skippable = true;
260 } else {
261 // unioning the empty set with a shape is the shape.
262 element->setOp(SkRegion::kReplace_Op);
263 }
264 break;
265 case SkRegion::kXOR_Op:
266 if (GrReducedClip::kAllOut_InitialState == *initialState) {
267 // xor could be changed to diff in the kAllIn case, not sure it's a win.
268 element->setOp(SkRegion::kReplace_Op);
269 }
270 break;
271 case SkRegion::kReverseDifference_Op:
272 if (GrReducedClip::kAllIn_InitialState == *initialState) {
273 // subtracting the whole plane will yield the empty set.
274 skippable = true;
275 *initialState = GrReducedClip::kAllOut_InitialState;
276 } else {
277 // this picks up flips inserted in the backwards pass.
278 skippable = element->isInverseFilled() ?
279 !SkRect::Intersects(element->getBounds(), queryBounds) :
280 element->contains(queryBounds);
281 if (skippable) {
282 *initialState = GrReducedClip::kAllIn_InitialState;
283 } else {
284 element->setOp(SkRegion::kReplace_Op);
285 }
286 }
287 break;
288 case SkRegion::kReplace_Op:
289 skippable = false; // we would have skipped it in the backwards walk if we
290 // could've.
291 break;
292 default:
293 SkDEBUGFAIL("Unexpected op.");
294 break;
295 }
296 if (!skippable) {
297 break;
298 } else {
299 if (element->isAA()) {
300 --numAAElements;
301 }
302 result->popHead();
303 element = result->headIter().get();
304 }
305 }
306 }
307 if (requiresAA) {
308 *requiresAA = numAAElements > 0;
309 }
310
311 if (0 == result->count()) {
312 if (*initialState == GrReducedClip::kAllIn_InitialState) {
313 *resultGenID = SkClipStack::kWideOpenGenID;
314 } else {
315 *resultGenID = SkClipStack::kEmptyGenID;
316 }
317 }
318}
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000319
bsalomon@google.com170bd792012-12-05 22:26:11 +0000320/*
bsalomon@google.comc6b3e482012-12-07 20:43:52 +0000321There are plenty of optimizations that could be added here. Maybe flips could be folded into
bsalomon@google.com170bd792012-12-05 22:26:11 +0000322earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
323for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
324based on later intersect operations, and perhaps remove intersect-rects. We could optionally
325take a rect in case the caller knows a bound on what is to be drawn through this clip.
326*/
tfarinabf54e492014-10-23 17:47:18 -0700327void GrReducedClip::ReduceClipStack(const SkClipStack& stack,
328 const SkIRect& queryBounds,
329 ElementList* result,
330 int32_t* resultGenID,
331 InitialState* initialState,
332 SkIRect* tighterBounds,
333 bool* requiresAA) {
bsalomon@google.com170bd792012-12-05 22:26:11 +0000334 result->reset();
335
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +0000336 // The clip established by the element list might be cached based on the last
337 // generation id. When we make early returns, we do not know what was the generation
338 // id that lead to the state. Make a conservative guess.
339 *resultGenID = stack.getTopmostGenID();
340
bsalomon@google.com170bd792012-12-05 22:26:11 +0000341 if (stack.isWideOpen()) {
342 *initialState = kAllIn_InitialState;
343 return;
344 }
345
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000346
347 // We initially look at whether the bounds alone is sufficient. We also use the stack bounds to
348 // attempt to compute the tighterBounds.
349
bsalomon@google.com170bd792012-12-05 22:26:11 +0000350 SkClipStack::BoundsType stackBoundsType;
351 SkRect stackBounds;
352 bool iior;
353 stack.getBounds(&stackBounds, &stackBoundsType, &iior);
354
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000355 const SkIRect* bounds = &queryBounds;
356
reed@google.com44699382013-10-31 17:28:30 +0000357 SkRect scalarQueryBounds = SkRect::Make(queryBounds);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000358
bsalomon@google.com170bd792012-12-05 22:26:11 +0000359 if (iior) {
360 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
361 SkRect isectRect;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000362 if (stackBounds.contains(scalarQueryBounds)) {
tfarinabf54e492014-10-23 17:47:18 -0700363 *initialState = GrReducedClip::kAllIn_InitialState;
bsalomon49f085d2014-09-05 13:34:00 -0700364 if (tighterBounds) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000365 *tighterBounds = queryBounds;
366 }
bsalomon49f085d2014-09-05 13:34:00 -0700367 if (requiresAA) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000368 *requiresAA = false;
369 }
370 } else if (isectRect.intersect(stackBounds, scalarQueryBounds)) {
commit-bot@chromium.orge5b2af92014-02-16 13:25:24 +0000371 // If the caller asked for tighter integer bounds we may be able to
372 // return kAllIn and give the bounds with no elements
bsalomon49f085d2014-09-05 13:34:00 -0700373 if (tighterBounds) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000374 isectRect.roundOut(tighterBounds);
reed@google.com44699382013-10-31 17:28:30 +0000375 SkRect scalarTighterBounds = SkRect::Make(*tighterBounds);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000376 if (scalarTighterBounds == isectRect) {
377 // the round-out didn't add any area outside the clip rect.
bsalomon49f085d2014-09-05 13:34:00 -0700378 if (requiresAA) {
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +0000379 *requiresAA = false;
380 }
tfarinabf54e492014-10-23 17:47:18 -0700381 *initialState = GrReducedClip::kAllIn_InitialState;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000382 return;
383 }
commit-bot@chromium.orge5b2af92014-02-16 13:25:24 +0000384 }
385 *initialState = kAllOut_InitialState;
386 // iior should only be true if aa/non-aa status matches among all elements.
387 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
388 bool doAA = iter.prev()->isAA();
389 SkNEW_INSERT_AT_LLIST_HEAD(result, Element, (isectRect, SkRegion::kReplace_Op, doAA));
bsalomon49f085d2014-09-05 13:34:00 -0700390 if (requiresAA) {
commit-bot@chromium.orge5b2af92014-02-16 13:25:24 +0000391 *requiresAA = doAA;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000392 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000393 } else {
394 *initialState = kAllOut_InitialState;
bsalomon49f085d2014-09-05 13:34:00 -0700395 if (requiresAA) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000396 *requiresAA = false;
397 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000398 }
399 return;
400 } else {
401 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000402 if (!SkRect::Intersects(stackBounds, scalarQueryBounds)) {
bsalomon@google.com170bd792012-12-05 22:26:11 +0000403 *initialState = kAllOut_InitialState;
bsalomon49f085d2014-09-05 13:34:00 -0700404 if (requiresAA) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000405 *requiresAA = false;
406 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000407 return;
408 }
bsalomon49f085d2014-09-05 13:34:00 -0700409 if (tighterBounds) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000410 SkIRect stackIBounds;
411 stackBounds.roundOut(&stackIBounds);
robertphillipsf4e59952015-01-07 12:16:10 -0800412 if (!tighterBounds->intersect(queryBounds, stackIBounds)) {
413 SkASSERT(0);
414 tighterBounds->setEmpty();
415 }
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000416 bounds = tighterBounds;
417 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000418 } else {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000419 if (stackBounds.contains(scalarQueryBounds)) {
bsalomon@google.com170bd792012-12-05 22:26:11 +0000420 *initialState = kAllOut_InitialState;
bsalomon49f085d2014-09-05 13:34:00 -0700421 if (requiresAA) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000422 *requiresAA = false;
423 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000424 return;
425 }
bsalomon49f085d2014-09-05 13:34:00 -0700426 if (tighterBounds) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000427 *tighterBounds = queryBounds;
428 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000429 }
430 }
431
reed@google.com44699382013-10-31 17:28:30 +0000432 SkRect scalarBounds = SkRect::Make(*bounds);
skia.committer@gmail.comd21444a2012-12-07 02:01:25 +0000433
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000434 // Now that we have determined the bounds to use and filtered out the trivial cases, call the
435 // helper that actually walks the stack.
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +0000436 reduced_stack_walker(stack, scalarBounds, result, resultGenID, initialState, requiresAA);
437
438 // The list that was computed in this function may be cached based on the gen id of the last
439 // element.
440 SkASSERT(SkClipStack::kInvalidGenID != *resultGenID);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000441}