blob: 6ad9cc6d8b84d1e3632e4bd262709b3b759e3437 [file] [log] [blame]
bsalomon@google.com170bd792012-12-05 22:26:11 +00001
2/*
3 * Copyright 2012 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9#include "GrReducedClip.h"
10
11typedef SkClipStack::Element Element;
12////////////////////////////////////////////////////////////////////////////////
13
14namespace GrReducedClip {
15
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000016// helper function
17void reduced_stack_walker(const SkClipStack& stack,
18 const SkRect& queryBounds,
19 ElementList* result,
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +000020 int32_t* resultGenID,
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000021 InitialState* initialState,
22 bool* requiresAA);
23
bsalomon@google.com170bd792012-12-05 22:26:11 +000024/*
bsalomon@google.comc6b3e482012-12-07 20:43:52 +000025There are plenty of optimizations that could be added here. Maybe flips could be folded into
bsalomon@google.com170bd792012-12-05 22:26:11 +000026earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
27for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
28based on later intersect operations, and perhaps remove intersect-rects. We could optionally
29take a rect in case the caller knows a bound on what is to be drawn through this clip.
30*/
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000031void ReduceClipStack(const SkClipStack& stack,
32 const SkIRect& queryBounds,
33 ElementList* result,
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +000034 int32_t* resultGenID,
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000035 InitialState* initialState,
36 SkIRect* tighterBounds,
37 bool* requiresAA) {
bsalomon@google.com170bd792012-12-05 22:26:11 +000038 result->reset();
39
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +000040 // The clip established by the element list might be cached based on the last
41 // generation id. When we make early returns, we do not know what was the generation
42 // id that lead to the state. Make a conservative guess.
43 *resultGenID = stack.getTopmostGenID();
44
bsalomon@google.com170bd792012-12-05 22:26:11 +000045 if (stack.isWideOpen()) {
46 *initialState = kAllIn_InitialState;
47 return;
48 }
49
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000050
51 // We initially look at whether the bounds alone is sufficient. We also use the stack bounds to
52 // attempt to compute the tighterBounds.
53
bsalomon@google.com170bd792012-12-05 22:26:11 +000054 SkClipStack::BoundsType stackBoundsType;
55 SkRect stackBounds;
56 bool iior;
57 stack.getBounds(&stackBounds, &stackBoundsType, &iior);
58
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000059 const SkIRect* bounds = &queryBounds;
60
reed@google.com44699382013-10-31 17:28:30 +000061 SkRect scalarQueryBounds = SkRect::Make(queryBounds);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000062
bsalomon@google.com170bd792012-12-05 22:26:11 +000063 if (iior) {
64 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
65 SkRect isectRect;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000066 if (stackBounds.contains(scalarQueryBounds)) {
bsalomon@google.com170bd792012-12-05 22:26:11 +000067 *initialState = kAllIn_InitialState;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000068 if (NULL != tighterBounds) {
69 *tighterBounds = queryBounds;
70 }
71 if (NULL != requiresAA) {
72 *requiresAA = false;
73 }
74 } else if (isectRect.intersect(stackBounds, scalarQueryBounds)) {
commit-bot@chromium.orge5b2af92014-02-16 13:25:24 +000075 // If the caller asked for tighter integer bounds we may be able to
76 // return kAllIn and give the bounds with no elements
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000077 if (NULL != tighterBounds) {
78 isectRect.roundOut(tighterBounds);
reed@google.com44699382013-10-31 17:28:30 +000079 SkRect scalarTighterBounds = SkRect::Make(*tighterBounds);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000080 if (scalarTighterBounds == isectRect) {
81 // the round-out didn't add any area outside the clip rect.
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +000082 if (NULL != requiresAA) {
83 *requiresAA = false;
84 }
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000085 *initialState = kAllIn_InitialState;
86 return;
87 }
commit-bot@chromium.orge5b2af92014-02-16 13:25:24 +000088 }
89 *initialState = kAllOut_InitialState;
90 // iior should only be true if aa/non-aa status matches among all elements.
91 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
92 bool doAA = iter.prev()->isAA();
93 SkNEW_INSERT_AT_LLIST_HEAD(result, Element, (isectRect, SkRegion::kReplace_Op, doAA));
94 if (NULL != requiresAA) {
95 *requiresAA = doAA;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000096 }
bsalomon@google.com170bd792012-12-05 22:26:11 +000097 } else {
98 *initialState = kAllOut_InitialState;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000099 if (NULL != requiresAA) {
100 *requiresAA = false;
101 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000102 }
103 return;
104 } else {
105 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000106 if (!SkRect::Intersects(stackBounds, scalarQueryBounds)) {
bsalomon@google.com170bd792012-12-05 22:26:11 +0000107 *initialState = kAllOut_InitialState;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000108 if (NULL != requiresAA) {
109 *requiresAA = false;
110 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000111 return;
112 }
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000113 if (NULL != tighterBounds) {
114 SkIRect stackIBounds;
115 stackBounds.roundOut(&stackIBounds);
116 tighterBounds->intersect(queryBounds, stackIBounds);
117 bounds = tighterBounds;
118 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000119 } else {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000120 if (stackBounds.contains(scalarQueryBounds)) {
bsalomon@google.com170bd792012-12-05 22:26:11 +0000121 *initialState = kAllOut_InitialState;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000122 if (NULL != requiresAA) {
123 *requiresAA = false;
124 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000125 return;
126 }
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000127 if (NULL != tighterBounds) {
128 *tighterBounds = queryBounds;
129 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000130 }
131 }
132
reed@google.com44699382013-10-31 17:28:30 +0000133 SkRect scalarBounds = SkRect::Make(*bounds);
skia.committer@gmail.comd21444a2012-12-07 02:01:25 +0000134
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000135 // Now that we have determined the bounds to use and filtered out the trivial cases, call the
136 // helper that actually walks the stack.
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +0000137 reduced_stack_walker(stack, scalarBounds, result, resultGenID, initialState, requiresAA);
138
139 // The list that was computed in this function may be cached based on the gen id of the last
140 // element.
141 SkASSERT(SkClipStack::kInvalidGenID != *resultGenID);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000142}
143
144void reduced_stack_walker(const SkClipStack& stack,
145 const SkRect& queryBounds,
146 ElementList* result,
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +0000147 int32_t* resultGenID,
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000148 InitialState* initialState,
149 bool* requiresAA) {
150
bsalomon@google.com170bd792012-12-05 22:26:11 +0000151 // walk backwards until we get to:
152 // a) the beginning
153 // b) an operation that is known to make the bounds all inside/outside
154 // c) a replace operation
155
156 static const InitialState kUnknown_InitialState = static_cast<InitialState>(-1);
157 *initialState = kUnknown_InitialState;
158
159 // During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
160 // TODO: track these per saved clip so that we can consider them on the forward pass.
161 bool embiggens = false;
162 bool emsmallens = false;
163
164 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000165 int numAAElements = 0;
bsalomon@google.com170bd792012-12-05 22:26:11 +0000166 while ((kUnknown_InitialState == *initialState)) {
167 const Element* element = iter.prev();
168 if (NULL == element) {
169 *initialState = kAllIn_InitialState;
170 break;
171 }
172 if (SkClipStack::kEmptyGenID == element->getGenID()) {
173 *initialState = kAllOut_InitialState;
174 break;
175 }
176 if (SkClipStack::kWideOpenGenID == element->getGenID()) {
177 *initialState = kAllIn_InitialState;
178 break;
179 }
180
181 bool skippable = false;
182 bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
183
184 switch (element->getOp()) {
185 case SkRegion::kDifference_Op:
186 // check if the shape subtracted either contains the entire bounds (and makes
187 // the clip empty) or is outside the bounds and therefore can be skipped.
188 if (element->isInverseFilled()) {
189 if (element->contains(queryBounds)) {
190 skippable = true;
191 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
192 *initialState = kAllOut_InitialState;
193 skippable = true;
194 }
195 } else {
196 if (element->contains(queryBounds)) {
197 *initialState = kAllOut_InitialState;
198 skippable = true;
199 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
200 skippable = true;
201 }
202 }
203 if (!skippable) {
204 emsmallens = true;
205 }
206 break;
207 case SkRegion::kIntersect_Op:
208 // check if the shape intersected contains the entire bounds and therefore can
209 // be skipped or it is outside the entire bounds and therefore makes the clip
210 // empty.
211 if (element->isInverseFilled()) {
212 if (element->contains(queryBounds)) {
213 *initialState = kAllOut_InitialState;
214 skippable = true;
215 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
216 skippable = true;
217 }
218 } else {
219 if (element->contains(queryBounds)) {
220 skippable = true;
221 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
222 *initialState = kAllOut_InitialState;
223 skippable = true;
224 }
225 }
226 if (!skippable) {
227 emsmallens = true;
228 }
229 break;
230 case SkRegion::kUnion_Op:
231 // If the union-ed shape contains the entire bounds then after this element
232 // the bounds is entirely inside the clip. If the union-ed shape is outside the
233 // bounds then this op can be skipped.
234 if (element->isInverseFilled()) {
235 if (element->contains(queryBounds)) {
236 skippable = true;
237 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
238 *initialState = kAllIn_InitialState;
239 skippable = true;
240 }
241 } else {
242 if (element->contains(queryBounds)) {
243 *initialState = kAllIn_InitialState;
244 skippable = true;
245 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
246 skippable = true;
247 }
248 }
249 if (!skippable) {
250 embiggens = true;
251 }
252 break;
253 case SkRegion::kXOR_Op:
254 // If the bounds is entirely inside the shape being xor-ed then the effect is
255 // to flip the inside/outside state of every point in the bounds. We may be
256 // able to take advantage of this in the forward pass. If the xor-ed shape
257 // doesn't intersect the bounds then it can be skipped.
258 if (element->isInverseFilled()) {
259 if (element->contains(queryBounds)) {
260 skippable = true;
261 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
262 isFlip = true;
263 }
264 } else {
265 if (element->contains(queryBounds)) {
266 isFlip = true;
267 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
268 skippable = true;
269 }
270 }
271 if (!skippable) {
272 emsmallens = embiggens = true;
273 }
274 break;
275 case SkRegion::kReverseDifference_Op:
276 // When the bounds is entirely within the rev-diff shape then this behaves like xor
277 // and reverses every point inside the bounds. If the shape is completely outside
278 // the bounds then we know after this element is applied that the bounds will be
279 // all outside the current clip.B
280 if (element->isInverseFilled()) {
281 if (element->contains(queryBounds)) {
282 *initialState = kAllOut_InitialState;
283 skippable = true;
284 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
285 isFlip = true;
286 }
287 } else {
288 if (element->contains(queryBounds)) {
289 isFlip = true;
290 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
291 *initialState = kAllOut_InitialState;
292 skippable = true;
293 }
294 }
295 if (!skippable) {
296 emsmallens = embiggens = true;
297 }
298 break;
299 case SkRegion::kReplace_Op:
300 // Replace will always terminate our walk. We will either begin the forward walk
301 // at the replace op or detect here than the shape is either completely inside
302 // or completely outside the bounds. In this latter case it can be skipped by
303 // setting the correct value for initialState.
304 if (element->isInverseFilled()) {
305 if (element->contains(queryBounds)) {
306 *initialState = kAllOut_InitialState;
307 skippable = true;
308 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
309 *initialState = kAllIn_InitialState;
310 skippable = true;
311 }
312 } else {
313 if (element->contains(queryBounds)) {
314 *initialState = kAllIn_InitialState;
315 skippable = true;
316 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
317 *initialState = kAllOut_InitialState;
318 skippable = true;
319 }
320 }
321 if (!skippable) {
322 *initialState = kAllOut_InitialState;
323 embiggens = emsmallens = true;
324 }
325 break;
326 default:
327 SkDEBUGFAIL("Unexpected op.");
328 break;
329 }
330 if (!skippable) {
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +0000331 if (0 == result->count()) {
332 // This will be the last element. Record the stricter genID.
333 *resultGenID = element->getGenID();
334 }
335
bsalomon@google.com170bd792012-12-05 22:26:11 +0000336 // if it is a flip, change it to a bounds-filling rect
337 if (isFlip) {
338 SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
339 SkRegion::kReverseDifference_Op == element->getOp());
340 SkNEW_INSERT_AT_LLIST_HEAD(result,
341 Element,
342 (queryBounds, SkRegion::kReverseDifference_Op, false));
343 } else {
bsalomon@google.comc6b3e482012-12-07 20:43:52 +0000344 Element* newElement = result->addToHead(*element);
345 if (newElement->isAA()) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000346 ++numAAElements;
347 }
bsalomon@google.comc6b3e482012-12-07 20:43:52 +0000348 // Intersecting an inverse shape is the same as differencing the non-inverse shape.
bsalomon@google.com45a15f52012-12-10 19:10:17 +0000349 // Replacing with an inverse shape is the same as setting initialState=kAllIn and
bsalomon@google.comc6b3e482012-12-07 20:43:52 +0000350 // differencing the non-inverse shape.
351 bool isReplace = SkRegion::kReplace_Op == newElement->getOp();
352 if (newElement->isInverseFilled() &&
353 (SkRegion::kIntersect_Op == newElement->getOp() || isReplace)) {
354 newElement->invertShapeFillType();
355 newElement->setOp(SkRegion::kDifference_Op);
356 if (isReplace) {
357 SkASSERT(kAllOut_InitialState == *initialState);
358 *initialState = kAllIn_InitialState;
359 }
360 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000361 }
362 }
363 }
364
365 if ((kAllOut_InitialState == *initialState && !embiggens) ||
366 (kAllIn_InitialState == *initialState && !emsmallens)) {
367 result->reset();
368 } else {
bsalomon@google.com170bd792012-12-05 22:26:11 +0000369 Element* element = result->headIter().get();
370 while (NULL != element) {
371 bool skippable = false;
372 switch (element->getOp()) {
373 case SkRegion::kDifference_Op:
374 // subtracting from the empty set yields the empty set.
375 skippable = kAllOut_InitialState == *initialState;
376 break;
377 case SkRegion::kIntersect_Op:
378 // intersecting with the empty set yields the empty set
commit-bot@chromium.orgdb627092013-10-15 20:04:07 +0000379 if (kAllOut_InitialState == *initialState) {
380 skippable = true;
381 } else {
382 // We can clear to zero and then simply draw the clip element.
383 *initialState = kAllOut_InitialState;
384 element->setOp(SkRegion::kReplace_Op);
385 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000386 break;
387 case SkRegion::kUnion_Op:
388 if (kAllIn_InitialState == *initialState) {
389 // unioning the infinite plane with anything is a no-op.
390 skippable = true;
391 } else {
392 // unioning the empty set with a shape is the shape.
393 element->setOp(SkRegion::kReplace_Op);
394 }
395 break;
396 case SkRegion::kXOR_Op:
397 if (kAllOut_InitialState == *initialState) {
398 // xor could be changed to diff in the kAllIn case, not sure it's a win.
399 element->setOp(SkRegion::kReplace_Op);
400 }
401 break;
402 case SkRegion::kReverseDifference_Op:
403 if (kAllIn_InitialState == *initialState) {
404 // subtracting the whole plane will yield the empty set.
405 skippable = true;
406 *initialState = kAllOut_InitialState;
407 } else {
408 // this picks up flips inserted in the backwards pass.
409 skippable = element->isInverseFilled() ?
410 !SkRect::Intersects(element->getBounds(), queryBounds) :
411 element->contains(queryBounds);
412 if (skippable) {
413 *initialState = kAllIn_InitialState;
414 } else {
415 element->setOp(SkRegion::kReplace_Op);
416 }
417 }
418 break;
419 case SkRegion::kReplace_Op:
bsalomon@google.com170bd792012-12-05 22:26:11 +0000420 skippable = false; // we would have skipped it in the backwards walk if we
421 // could've.
422 break;
423 default:
424 SkDEBUGFAIL("Unexpected op.");
425 break;
426 }
427 if (!skippable) {
428 break;
429 } else {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000430 if (element->isAA()) {
431 --numAAElements;
432 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000433 result->popHead();
434 element = result->headIter().get();
435 }
436 }
437 }
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000438 if (NULL != requiresAA) {
439 *requiresAA = numAAElements > 0;
440 }
commit-bot@chromium.orgd3e58422013-11-05 15:03:08 +0000441
442 if (0 == result->count()) {
443 if (*initialState == kAllIn_InitialState) {
444 *resultGenID = SkClipStack::kWideOpenGenID;
445 } else {
446 *resultGenID = SkClipStack::kEmptyGenID;
447 }
448 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000449}
450} // namespace GrReducedClip