blob: 8ee28ddfb1fdb5b93c105a841424eac6f5f97111 [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
16/*
17There are plenty of optimizations that could be added here. For example we could consider
18checking for cases where an inverse path can be changed to a regular fill with a different op.
19(e.g. [kIntersect, inverse path] -> [kDifference, path]). Maybe flips could be folded into
20earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
21for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
22based on later intersect operations, and perhaps remove intersect-rects. We could optionally
23take a rect in case the caller knows a bound on what is to be drawn through this clip.
24*/
25void GrReduceClipStack(const SkClipStack& stack,
26 const SkRect& queryBounds,
27 ElementList* result,
28 InitialState* initialState) {
29 result->reset();
30
31 if (stack.isWideOpen()) {
32 *initialState = kAllIn_InitialState;
33 return;
34 }
35
36 SkClipStack::BoundsType stackBoundsType;
37 SkRect stackBounds;
38 bool iior;
39 stack.getBounds(&stackBounds, &stackBoundsType, &iior);
40
41 if (iior) {
42 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
43 SkRect isectRect;
44 if (stackBounds.contains(queryBounds)) {
45 *initialState = kAllIn_InitialState;
46 } else if (isectRect.intersect(stackBounds, queryBounds)) {
47 // iior should only be true if aa/non-aa status matches among all elements.
48 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
49 bool doAA = iter.prev()->isAA();
50 SkNEW_INSERT_AT_LLIST_HEAD(result, Element, (isectRect, SkRegion::kReplace_Op, doAA));
51 } else {
52 *initialState = kAllOut_InitialState;
53 }
54 return;
55 } else {
56 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
57 if (!SkRect::Intersects(stackBounds, queryBounds)) {
58 *initialState = kAllOut_InitialState;
59 return;
60 }
61 } else {
62 if (stackBounds.contains(queryBounds)) {
63 *initialState = kAllOut_InitialState;
64 return;
65 }
66 }
67 }
68
69 // walk backwards until we get to:
70 // a) the beginning
71 // b) an operation that is known to make the bounds all inside/outside
72 // c) a replace operation
73
74 static const InitialState kUnknown_InitialState = static_cast<InitialState>(-1);
75 *initialState = kUnknown_InitialState;
76
77 // During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
78 // TODO: track these per saved clip so that we can consider them on the forward pass.
79 bool embiggens = false;
80 bool emsmallens = false;
81
82 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
83 while ((kUnknown_InitialState == *initialState)) {
84 const Element* element = iter.prev();
85 if (NULL == element) {
86 *initialState = kAllIn_InitialState;
87 break;
88 }
89 if (SkClipStack::kEmptyGenID == element->getGenID()) {
90 *initialState = kAllOut_InitialState;
91 break;
92 }
93 if (SkClipStack::kWideOpenGenID == element->getGenID()) {
94 *initialState = kAllIn_InitialState;
95 break;
96 }
97
98 bool skippable = false;
99 bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
100
101 switch (element->getOp()) {
102 case SkRegion::kDifference_Op:
103 // check if the shape subtracted either contains the entire bounds (and makes
104 // the clip empty) or is outside the bounds and therefore can be skipped.
105 if (element->isInverseFilled()) {
106 if (element->contains(queryBounds)) {
107 skippable = true;
108 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
109 *initialState = kAllOut_InitialState;
110 skippable = true;
111 }
112 } else {
113 if (element->contains(queryBounds)) {
114 *initialState = kAllOut_InitialState;
115 skippable = true;
116 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
117 skippable = true;
118 }
119 }
120 if (!skippable) {
121 emsmallens = true;
122 }
123 break;
124 case SkRegion::kIntersect_Op:
125 // check if the shape intersected contains the entire bounds and therefore can
126 // be skipped or it is outside the entire bounds and therefore makes the clip
127 // empty.
128 if (element->isInverseFilled()) {
129 if (element->contains(queryBounds)) {
130 *initialState = kAllOut_InitialState;
131 skippable = true;
132 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
133 skippable = true;
134 }
135 } else {
136 if (element->contains(queryBounds)) {
137 skippable = true;
138 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
139 *initialState = kAllOut_InitialState;
140 skippable = true;
141 }
142 }
143 if (!skippable) {
144 emsmallens = true;
145 }
146 break;
147 case SkRegion::kUnion_Op:
148 // If the union-ed shape contains the entire bounds then after this element
149 // the bounds is entirely inside the clip. If the union-ed shape is outside the
150 // bounds then this op can be skipped.
151 if (element->isInverseFilled()) {
152 if (element->contains(queryBounds)) {
153 skippable = true;
154 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
155 *initialState = kAllIn_InitialState;
156 skippable = true;
157 }
158 } else {
159 if (element->contains(queryBounds)) {
160 *initialState = kAllIn_InitialState;
161 skippable = true;
162 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
163 skippable = true;
164 }
165 }
166 if (!skippable) {
167 embiggens = true;
168 }
169 break;
170 case SkRegion::kXOR_Op:
171 // If the bounds is entirely inside the shape being xor-ed then the effect is
172 // to flip the inside/outside state of every point in the bounds. We may be
173 // able to take advantage of this in the forward pass. If the xor-ed shape
174 // doesn't intersect the bounds then it can be skipped.
175 if (element->isInverseFilled()) {
176 if (element->contains(queryBounds)) {
177 skippable = true;
178 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
179 isFlip = true;
180 }
181 } else {
182 if (element->contains(queryBounds)) {
183 isFlip = true;
184 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
185 skippable = true;
186 }
187 }
188 if (!skippable) {
189 emsmallens = embiggens = true;
190 }
191 break;
192 case SkRegion::kReverseDifference_Op:
193 // When the bounds is entirely within the rev-diff shape then this behaves like xor
194 // and reverses every point inside the bounds. If the shape is completely outside
195 // the bounds then we know after this element is applied that the bounds will be
196 // all outside the current clip.B
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 isFlip = true;
203 }
204 } else {
205 if (element->contains(queryBounds)) {
206 isFlip = true;
207 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
208 *initialState = kAllOut_InitialState;
209 skippable = true;
210 }
211 }
212 if (!skippable) {
213 emsmallens = embiggens = true;
214 }
215 break;
216 case SkRegion::kReplace_Op:
217 // Replace will always terminate our walk. We will either begin the forward walk
218 // at the replace op or detect here than the shape is either completely inside
219 // or completely outside the bounds. In this latter case it can be skipped by
220 // setting the correct value for initialState.
221 if (element->isInverseFilled()) {
222 if (element->contains(queryBounds)) {
223 *initialState = kAllOut_InitialState;
224 skippable = true;
225 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
226 *initialState = kAllIn_InitialState;
227 skippable = true;
228 }
229 } else {
230 if (element->contains(queryBounds)) {
231 *initialState = kAllIn_InitialState;
232 skippable = true;
233 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
234 *initialState = kAllOut_InitialState;
235 skippable = true;
236 }
237 }
238 if (!skippable) {
239 *initialState = kAllOut_InitialState;
240 embiggens = emsmallens = true;
241 }
242 break;
243 default:
244 SkDEBUGFAIL("Unexpected op.");
245 break;
246 }
247 if (!skippable) {
248 // if it is a flip, change it to a bounds-filling rect
249 if (isFlip) {
250 SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
251 SkRegion::kReverseDifference_Op == element->getOp());
252 SkNEW_INSERT_AT_LLIST_HEAD(result,
253 Element,
254 (queryBounds, SkRegion::kReverseDifference_Op, false));
255 } else {
256 result->addToHead(*element);
257 }
258 }
259 }
260
261 if ((kAllOut_InitialState == *initialState && !embiggens) ||
262 (kAllIn_InitialState == *initialState && !emsmallens)) {
263 result->reset();
264 } else {
265 int clipsToSkip = 0;
266 Element* element = result->headIter().get();
267 while (NULL != element) {
268 bool skippable = false;
269 switch (element->getOp()) {
270 case SkRegion::kDifference_Op:
271 // subtracting from the empty set yields the empty set.
272 skippable = kAllOut_InitialState == *initialState;
273 break;
274 case SkRegion::kIntersect_Op:
275 // intersecting with the empty set yields the empty set
276 skippable = kAllOut_InitialState == *initialState;
277 break;
278 case SkRegion::kUnion_Op:
279 if (kAllIn_InitialState == *initialState) {
280 // unioning the infinite plane with anything is a no-op.
281 skippable = true;
282 } else {
283 // unioning the empty set with a shape is the shape.
284 element->setOp(SkRegion::kReplace_Op);
285 }
286 break;
287 case SkRegion::kXOR_Op:
288 if (kAllOut_InitialState == *initialState) {
289 // xor could be changed to diff in the kAllIn case, not sure it's a win.
290 element->setOp(SkRegion::kReplace_Op);
291 }
292 break;
293 case SkRegion::kReverseDifference_Op:
294 if (kAllIn_InitialState == *initialState) {
295 // subtracting the whole plane will yield the empty set.
296 skippable = true;
297 *initialState = kAllOut_InitialState;
298 } else {
299 // this picks up flips inserted in the backwards pass.
300 skippable = element->isInverseFilled() ?
301 !SkRect::Intersects(element->getBounds(), queryBounds) :
302 element->contains(queryBounds);
303 if (skippable) {
304 *initialState = kAllIn_InitialState;
305 } else {
306 element->setOp(SkRegion::kReplace_Op);
307 }
308 }
309 break;
310 case SkRegion::kReplace_Op:
311 SkASSERT(!clipsToSkip); // replace should always be the first op
312 skippable = false; // we would have skipped it in the backwards walk if we
313 // could've.
314 break;
315 default:
316 SkDEBUGFAIL("Unexpected op.");
317 break;
318 }
319 if (!skippable) {
320 break;
321 } else {
322 result->popHead();
323 element = result->headIter().get();
324 }
325 }
326 }
327}
328} // namespace GrReducedClip