|
1 |
|
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 |
|
11 typedef SkClipStack::Element Element; |
|
12 //////////////////////////////////////////////////////////////////////////////// |
|
13 |
|
14 namespace GrReducedClip { |
|
15 |
|
16 // helper function |
|
17 void reduced_stack_walker(const SkClipStack& stack, |
|
18 const SkRect& queryBounds, |
|
19 ElementList* result, |
|
20 int32_t* resultGenID, |
|
21 InitialState* initialState, |
|
22 bool* requiresAA); |
|
23 |
|
24 /* |
|
25 There are plenty of optimizations that could be added here. Maybe flips could be folded into |
|
26 earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps |
|
27 for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations |
|
28 based on later intersect operations, and perhaps remove intersect-rects. We could optionally |
|
29 take a rect in case the caller knows a bound on what is to be drawn through this clip. |
|
30 */ |
|
31 void ReduceClipStack(const SkClipStack& stack, |
|
32 const SkIRect& queryBounds, |
|
33 ElementList* result, |
|
34 int32_t* resultGenID, |
|
35 InitialState* initialState, |
|
36 SkIRect* tighterBounds, |
|
37 bool* requiresAA) { |
|
38 result->reset(); |
|
39 |
|
40 // 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 |
|
45 if (stack.isWideOpen()) { |
|
46 *initialState = kAllIn_InitialState; |
|
47 return; |
|
48 } |
|
49 |
|
50 |
|
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 |
|
54 SkClipStack::BoundsType stackBoundsType; |
|
55 SkRect stackBounds; |
|
56 bool iior; |
|
57 stack.getBounds(&stackBounds, &stackBoundsType, &iior); |
|
58 |
|
59 const SkIRect* bounds = &queryBounds; |
|
60 |
|
61 SkRect scalarQueryBounds = SkRect::Make(queryBounds); |
|
62 |
|
63 if (iior) { |
|
64 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType); |
|
65 SkRect isectRect; |
|
66 if (stackBounds.contains(scalarQueryBounds)) { |
|
67 *initialState = kAllIn_InitialState; |
|
68 if (NULL != tighterBounds) { |
|
69 *tighterBounds = queryBounds; |
|
70 } |
|
71 if (NULL != requiresAA) { |
|
72 *requiresAA = false; |
|
73 } |
|
74 } else if (isectRect.intersect(stackBounds, scalarQueryBounds)) { |
|
75 // If the caller asked for tighter integer bounds we may be able to |
|
76 // return kAllIn and give the bounds with no elements |
|
77 if (NULL != tighterBounds) { |
|
78 isectRect.roundOut(tighterBounds); |
|
79 SkRect scalarTighterBounds = SkRect::Make(*tighterBounds); |
|
80 if (scalarTighterBounds == isectRect) { |
|
81 // the round-out didn't add any area outside the clip rect. |
|
82 if (NULL != requiresAA) { |
|
83 *requiresAA = false; |
|
84 } |
|
85 *initialState = kAllIn_InitialState; |
|
86 return; |
|
87 } |
|
88 } |
|
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; |
|
96 } |
|
97 } else { |
|
98 *initialState = kAllOut_InitialState; |
|
99 if (NULL != requiresAA) { |
|
100 *requiresAA = false; |
|
101 } |
|
102 } |
|
103 return; |
|
104 } else { |
|
105 if (SkClipStack::kNormal_BoundsType == stackBoundsType) { |
|
106 if (!SkRect::Intersects(stackBounds, scalarQueryBounds)) { |
|
107 *initialState = kAllOut_InitialState; |
|
108 if (NULL != requiresAA) { |
|
109 *requiresAA = false; |
|
110 } |
|
111 return; |
|
112 } |
|
113 if (NULL != tighterBounds) { |
|
114 SkIRect stackIBounds; |
|
115 stackBounds.roundOut(&stackIBounds); |
|
116 tighterBounds->intersect(queryBounds, stackIBounds); |
|
117 bounds = tighterBounds; |
|
118 } |
|
119 } else { |
|
120 if (stackBounds.contains(scalarQueryBounds)) { |
|
121 *initialState = kAllOut_InitialState; |
|
122 if (NULL != requiresAA) { |
|
123 *requiresAA = false; |
|
124 } |
|
125 return; |
|
126 } |
|
127 if (NULL != tighterBounds) { |
|
128 *tighterBounds = queryBounds; |
|
129 } |
|
130 } |
|
131 } |
|
132 |
|
133 SkRect scalarBounds = SkRect::Make(*bounds); |
|
134 |
|
135 // Now that we have determined the bounds to use and filtered out the trivial cases, call the |
|
136 // helper that actually walks the stack. |
|
137 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); |
|
142 } |
|
143 |
|
144 void reduced_stack_walker(const SkClipStack& stack, |
|
145 const SkRect& queryBounds, |
|
146 ElementList* result, |
|
147 int32_t* resultGenID, |
|
148 InitialState* initialState, |
|
149 bool* requiresAA) { |
|
150 |
|
151 // 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); |
|
165 int numAAElements = 0; |
|
166 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) { |
|
331 if (0 == result->count()) { |
|
332 // This will be the last element. Record the stricter genID. |
|
333 *resultGenID = element->getGenID(); |
|
334 } |
|
335 |
|
336 // 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 { |
|
344 Element* newElement = result->addToHead(*element); |
|
345 if (newElement->isAA()) { |
|
346 ++numAAElements; |
|
347 } |
|
348 // Intersecting an inverse shape is the same as differencing the non-inverse shape. |
|
349 // Replacing with an inverse shape is the same as setting initialState=kAllIn and |
|
350 // 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 } |
|
361 } |
|
362 } |
|
363 } |
|
364 |
|
365 if ((kAllOut_InitialState == *initialState && !embiggens) || |
|
366 (kAllIn_InitialState == *initialState && !emsmallens)) { |
|
367 result->reset(); |
|
368 } else { |
|
369 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 |
|
379 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 } |
|
386 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: |
|
420 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 { |
|
430 if (element->isAA()) { |
|
431 --numAAElements; |
|
432 } |
|
433 result->popHead(); |
|
434 element = result->headIter().get(); |
|
435 } |
|
436 } |
|
437 } |
|
438 if (NULL != requiresAA) { |
|
439 *requiresAA = numAAElements > 0; |
|
440 } |
|
441 |
|
442 if (0 == result->count()) { |
|
443 if (*initialState == kAllIn_InitialState) { |
|
444 *resultGenID = SkClipStack::kWideOpenGenID; |
|
445 } else { |
|
446 *resultGenID = SkClipStack::kEmptyGenID; |
|
447 } |
|
448 } |
|
449 } |
|
450 } // namespace GrReducedClip |