blob: baa8533235cf234992b7f848095cc4d988066e58 [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,
20 InitialState* initialState,
21 bool* requiresAA);
22
bsalomon@google.com170bd792012-12-05 22:26:11 +000023/*
24There are plenty of optimizations that could be added here. For example we could consider
25checking for cases where an inverse path can be changed to a regular fill with a different op.
26(e.g. [kIntersect, inverse path] -> [kDifference, path]). Maybe flips could be folded into
27earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
28for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
29based on later intersect operations, and perhaps remove intersect-rects. We could optionally
30take a rect in case the caller knows a bound on what is to be drawn through this clip.
31*/
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000032void ReduceClipStack(const SkClipStack& stack,
33 const SkIRect& queryBounds,
34 ElementList* result,
35 InitialState* initialState,
36 SkIRect* tighterBounds,
37 bool* requiresAA) {
bsalomon@google.com170bd792012-12-05 22:26:11 +000038 result->reset();
39
40 if (stack.isWideOpen()) {
41 *initialState = kAllIn_InitialState;
42 return;
43 }
44
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000045
46 // We initially look at whether the bounds alone is sufficient. We also use the stack bounds to
47 // attempt to compute the tighterBounds.
48
bsalomon@google.com170bd792012-12-05 22:26:11 +000049 SkClipStack::BoundsType stackBoundsType;
50 SkRect stackBounds;
51 bool iior;
52 stack.getBounds(&stackBounds, &stackBoundsType, &iior);
53
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000054 const SkIRect* bounds = &queryBounds;
55
56 SkRect scalarQueryBounds = SkRect::MakeFromIRect(queryBounds);
57
bsalomon@google.com170bd792012-12-05 22:26:11 +000058 if (iior) {
59 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
60 SkRect isectRect;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000061 if (stackBounds.contains(scalarQueryBounds)) {
bsalomon@google.com170bd792012-12-05 22:26:11 +000062 *initialState = kAllIn_InitialState;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000063 if (NULL != tighterBounds) {
64 *tighterBounds = queryBounds;
65 }
66 if (NULL != requiresAA) {
67 *requiresAA = false;
68 }
69 } else if (isectRect.intersect(stackBounds, scalarQueryBounds)) {
70 if (NULL != tighterBounds) {
71 isectRect.roundOut(tighterBounds);
72 SkRect scalarTighterBounds = SkRect::MakeFromIRect(*tighterBounds);
73 if (scalarTighterBounds == isectRect) {
74 // the round-out didn't add any area outside the clip rect.
75 *requiresAA = false;
76 *initialState = kAllIn_InitialState;
77 return;
78 }
79 *initialState = kAllOut_InitialState;
80 // iior should only be true if aa/non-aa status matches among all elements.
81 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
82 bool doAA = iter.prev()->isAA();
83 SkNEW_INSERT_AT_LLIST_HEAD(result, Element, (isectRect, SkRegion::kReplace_Op, doAA));
84 if (NULL != requiresAA) {
85 *requiresAA = doAA;
86 }
87 }
bsalomon@google.com170bd792012-12-05 22:26:11 +000088 } else {
89 *initialState = kAllOut_InitialState;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000090 if (NULL != requiresAA) {
91 *requiresAA = false;
92 }
bsalomon@google.com170bd792012-12-05 22:26:11 +000093 }
94 return;
95 } else {
96 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +000097 if (!SkRect::Intersects(stackBounds, scalarQueryBounds)) {
bsalomon@google.com170bd792012-12-05 22:26:11 +000098 *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 return;
103 }
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000104 if (NULL != tighterBounds) {
105 SkIRect stackIBounds;
106 stackBounds.roundOut(&stackIBounds);
107 tighterBounds->intersect(queryBounds, stackIBounds);
108 bounds = tighterBounds;
109 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000110 } else {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000111 if (stackBounds.contains(scalarQueryBounds)) {
bsalomon@google.com170bd792012-12-05 22:26:11 +0000112 *initialState = kAllOut_InitialState;
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000113 if (NULL != requiresAA) {
114 *requiresAA = false;
115 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000116 return;
117 }
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000118 if (NULL != tighterBounds) {
119 *tighterBounds = queryBounds;
120 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000121 }
122 }
123
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000124 SkRect scalarBounds = SkRect::MakeFromIRect(*bounds);
skia.committer@gmail.comd21444a2012-12-07 02:01:25 +0000125
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000126 // Now that we have determined the bounds to use and filtered out the trivial cases, call the
127 // helper that actually walks the stack.
128 reduced_stack_walker(stack, scalarBounds, result, initialState, requiresAA);
129}
130
131void reduced_stack_walker(const SkClipStack& stack,
132 const SkRect& queryBounds,
133 ElementList* result,
134 InitialState* initialState,
135 bool* requiresAA) {
136
bsalomon@google.com170bd792012-12-05 22:26:11 +0000137 // walk backwards until we get to:
138 // a) the beginning
139 // b) an operation that is known to make the bounds all inside/outside
140 // c) a replace operation
141
142 static const InitialState kUnknown_InitialState = static_cast<InitialState>(-1);
143 *initialState = kUnknown_InitialState;
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 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000151 int numAAElements = 0;
bsalomon@google.com170bd792012-12-05 22:26:11 +0000152 while ((kUnknown_InitialState == *initialState)) {
153 const Element* element = iter.prev();
154 if (NULL == element) {
155 *initialState = kAllIn_InitialState;
156 break;
157 }
158 if (SkClipStack::kEmptyGenID == element->getGenID()) {
159 *initialState = kAllOut_InitialState;
160 break;
161 }
162 if (SkClipStack::kWideOpenGenID == element->getGenID()) {
163 *initialState = kAllIn_InitialState;
164 break;
165 }
166
167 bool skippable = false;
168 bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
169
170 switch (element->getOp()) {
171 case SkRegion::kDifference_Op:
172 // check if the shape subtracted either contains the entire bounds (and makes
173 // the clip empty) or is outside the bounds and therefore can be skipped.
174 if (element->isInverseFilled()) {
175 if (element->contains(queryBounds)) {
176 skippable = true;
177 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
178 *initialState = kAllOut_InitialState;
179 skippable = true;
180 }
181 } else {
182 if (element->contains(queryBounds)) {
183 *initialState = kAllOut_InitialState;
184 skippable = true;
185 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
186 skippable = true;
187 }
188 }
189 if (!skippable) {
190 emsmallens = true;
191 }
192 break;
193 case SkRegion::kIntersect_Op:
194 // check if the shape intersected contains the entire bounds and therefore can
195 // be skipped or it is outside the entire bounds and therefore makes the clip
196 // empty.
197 if (element->isInverseFilled()) {
198 if (element->contains(queryBounds)) {
199 *initialState = kAllOut_InitialState;
200 skippable = true;
201 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
202 skippable = true;
203 }
204 } else {
205 if (element->contains(queryBounds)) {
206 skippable = true;
207 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
208 *initialState = kAllOut_InitialState;
209 skippable = true;
210 }
211 }
212 if (!skippable) {
213 emsmallens = true;
214 }
215 break;
216 case SkRegion::kUnion_Op:
217 // If the union-ed shape contains the entire bounds then after this element
218 // the bounds is entirely inside the clip. If the union-ed shape is outside the
219 // bounds then this op can be skipped.
220 if (element->isInverseFilled()) {
221 if (element->contains(queryBounds)) {
222 skippable = true;
223 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
224 *initialState = kAllIn_InitialState;
225 skippable = true;
226 }
227 } else {
228 if (element->contains(queryBounds)) {
229 *initialState = kAllIn_InitialState;
230 skippable = true;
231 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
232 skippable = true;
233 }
234 }
235 if (!skippable) {
236 embiggens = true;
237 }
238 break;
239 case SkRegion::kXOR_Op:
240 // If the bounds is entirely inside the shape being xor-ed then the effect is
241 // to flip the inside/outside state of every point in the bounds. We may be
242 // able to take advantage of this in the forward pass. If the xor-ed shape
243 // doesn't intersect the bounds then it can be skipped.
244 if (element->isInverseFilled()) {
245 if (element->contains(queryBounds)) {
246 skippable = true;
247 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
248 isFlip = true;
249 }
250 } else {
251 if (element->contains(queryBounds)) {
252 isFlip = true;
253 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
254 skippable = true;
255 }
256 }
257 if (!skippable) {
258 emsmallens = embiggens = true;
259 }
260 break;
261 case SkRegion::kReverseDifference_Op:
262 // When the bounds is entirely within the rev-diff shape then this behaves like xor
263 // and reverses every point inside the bounds. If the shape is completely outside
264 // the bounds then we know after this element is applied that the bounds will be
265 // all outside the current clip.B
266 if (element->isInverseFilled()) {
267 if (element->contains(queryBounds)) {
268 *initialState = kAllOut_InitialState;
269 skippable = true;
270 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
271 isFlip = true;
272 }
273 } else {
274 if (element->contains(queryBounds)) {
275 isFlip = true;
276 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
277 *initialState = kAllOut_InitialState;
278 skippable = true;
279 }
280 }
281 if (!skippable) {
282 emsmallens = embiggens = true;
283 }
284 break;
285 case SkRegion::kReplace_Op:
286 // Replace will always terminate our walk. We will either begin the forward walk
287 // at the replace op or detect here than the shape is either completely inside
288 // or completely outside the bounds. In this latter case it can be skipped by
289 // setting the correct value for initialState.
290 if (element->isInverseFilled()) {
291 if (element->contains(queryBounds)) {
292 *initialState = kAllOut_InitialState;
293 skippable = true;
294 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
295 *initialState = kAllIn_InitialState;
296 skippable = true;
297 }
298 } else {
299 if (element->contains(queryBounds)) {
300 *initialState = kAllIn_InitialState;
301 skippable = true;
302 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
303 *initialState = kAllOut_InitialState;
304 skippable = true;
305 }
306 }
307 if (!skippable) {
308 *initialState = kAllOut_InitialState;
309 embiggens = emsmallens = true;
310 }
311 break;
312 default:
313 SkDEBUGFAIL("Unexpected op.");
314 break;
315 }
316 if (!skippable) {
317 // if it is a flip, change it to a bounds-filling rect
318 if (isFlip) {
319 SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
320 SkRegion::kReverseDifference_Op == element->getOp());
321 SkNEW_INSERT_AT_LLIST_HEAD(result,
322 Element,
323 (queryBounds, SkRegion::kReverseDifference_Op, false));
324 } else {
325 result->addToHead(*element);
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000326 if (element->isAA()) {
327 ++numAAElements;
328 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000329 }
330 }
331 }
332
333 if ((kAllOut_InitialState == *initialState && !embiggens) ||
334 (kAllIn_InitialState == *initialState && !emsmallens)) {
335 result->reset();
336 } else {
bsalomon@google.com170bd792012-12-05 22:26:11 +0000337 Element* element = result->headIter().get();
338 while (NULL != element) {
339 bool skippable = false;
340 switch (element->getOp()) {
341 case SkRegion::kDifference_Op:
342 // subtracting from the empty set yields the empty set.
343 skippable = kAllOut_InitialState == *initialState;
344 break;
345 case SkRegion::kIntersect_Op:
346 // intersecting with the empty set yields the empty set
347 skippable = kAllOut_InitialState == *initialState;
348 break;
349 case SkRegion::kUnion_Op:
350 if (kAllIn_InitialState == *initialState) {
351 // unioning the infinite plane with anything is a no-op.
352 skippable = true;
353 } else {
354 // unioning the empty set with a shape is the shape.
355 element->setOp(SkRegion::kReplace_Op);
356 }
357 break;
358 case SkRegion::kXOR_Op:
359 if (kAllOut_InitialState == *initialState) {
360 // xor could be changed to diff in the kAllIn case, not sure it's a win.
361 element->setOp(SkRegion::kReplace_Op);
362 }
363 break;
364 case SkRegion::kReverseDifference_Op:
365 if (kAllIn_InitialState == *initialState) {
366 // subtracting the whole plane will yield the empty set.
367 skippable = true;
368 *initialState = kAllOut_InitialState;
369 } else {
370 // this picks up flips inserted in the backwards pass.
371 skippable = element->isInverseFilled() ?
372 !SkRect::Intersects(element->getBounds(), queryBounds) :
373 element->contains(queryBounds);
374 if (skippable) {
375 *initialState = kAllIn_InitialState;
376 } else {
377 element->setOp(SkRegion::kReplace_Op);
378 }
379 }
380 break;
381 case SkRegion::kReplace_Op:
bsalomon@google.com170bd792012-12-05 22:26:11 +0000382 skippable = false; // we would have skipped it in the backwards walk if we
383 // could've.
384 break;
385 default:
386 SkDEBUGFAIL("Unexpected op.");
387 break;
388 }
389 if (!skippable) {
390 break;
391 } else {
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000392 if (element->isAA()) {
393 --numAAElements;
394 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000395 result->popHead();
396 element = result->headIter().get();
397 }
398 }
399 }
bsalomon@google.com34cd70a2012-12-06 14:23:20 +0000400 if (NULL != requiresAA) {
401 *requiresAA = numAAElements > 0;
402 }
bsalomon@google.com170bd792012-12-05 22:26:11 +0000403}
404} // namespace GrReducedClip