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 */
ReduceClipStack(const SkClipStack & stack,const SkIRect & queryBounds,ElementList * result,int32_t * resultGenID,InitialState * initialState,SkIRect * tighterBounds,bool * requiresAA)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 (NULL != tighterBounds) {
76 isectRect.roundOut(tighterBounds);
77 SkRect scalarTighterBounds = SkRect::Make(*tighterBounds);
78 if (scalarTighterBounds == isectRect) {
79 // the round-out didn't add any area outside the clip rect.
80 if (NULL != requiresAA) {
81 *requiresAA = false;
82 }
83 *initialState = kAllIn_InitialState;
84 return;
85 }
86 *initialState = kAllOut_InitialState;
87 // iior should only be true if aa/non-aa status matches among all elements.
88 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
89 bool doAA = iter.prev()->isAA();
90 SkNEW_INSERT_AT_LLIST_HEAD(result, Element, (isectRect, SkRegion::kReplace_Op, doAA));
91 if (NULL != requiresAA) {
92 *requiresAA = doAA;
93 }
94 }
95 } else {
96 *initialState = kAllOut_InitialState;
97 if (NULL != requiresAA) {
98 *requiresAA = false;
99 }
100 }
101 return;
102 } else {
103 if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
104 if (!SkRect::Intersects(stackBounds, scalarQueryBounds)) {
105 *initialState = kAllOut_InitialState;
106 if (NULL != requiresAA) {
107 *requiresAA = false;
108 }
109 return;
110 }
111 if (NULL != tighterBounds) {
112 SkIRect stackIBounds;
113 stackBounds.roundOut(&stackIBounds);
114 tighterBounds->intersect(queryBounds, stackIBounds);
115 bounds = tighterBounds;
116 }
117 } else {
118 if (stackBounds.contains(scalarQueryBounds)) {
119 *initialState = kAllOut_InitialState;
120 if (NULL != requiresAA) {
121 *requiresAA = false;
122 }
123 return;
124 }
125 if (NULL != tighterBounds) {
126 *tighterBounds = queryBounds;
127 }
128 }
129 }
130
131 SkRect scalarBounds = SkRect::Make(*bounds);
132
133 // Now that we have determined the bounds to use and filtered out the trivial cases, call the
134 // helper that actually walks the stack.
135 reduced_stack_walker(stack, scalarBounds, result, resultGenID, initialState, requiresAA);
136
137 // The list that was computed in this function may be cached based on the gen id of the last
138 // element.
139 SkASSERT(SkClipStack::kInvalidGenID != *resultGenID);
140 }
141
reduced_stack_walker(const SkClipStack & stack,const SkRect & queryBounds,ElementList * result,int32_t * resultGenID,InitialState * initialState,bool * requiresAA)142 void reduced_stack_walker(const SkClipStack& stack,
143 const SkRect& queryBounds,
144 ElementList* result,
145 int32_t* resultGenID,
146 InitialState* initialState,
147 bool* requiresAA) {
148
149 // walk backwards until we get to:
150 // a) the beginning
151 // b) an operation that is known to make the bounds all inside/outside
152 // c) a replace operation
153
154 static const InitialState kUnknown_InitialState = static_cast<InitialState>(-1);
155 *initialState = kUnknown_InitialState;
156
157 // During our backwards walk, track whether we've seen ops that either grow or shrink the clip.
158 // TODO: track these per saved clip so that we can consider them on the forward pass.
159 bool embiggens = false;
160 bool emsmallens = false;
161
162 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
163 int numAAElements = 0;
164 while ((kUnknown_InitialState == *initialState)) {
165 const Element* element = iter.prev();
166 if (NULL == element) {
167 *initialState = kAllIn_InitialState;
168 break;
169 }
170 if (SkClipStack::kEmptyGenID == element->getGenID()) {
171 *initialState = kAllOut_InitialState;
172 break;
173 }
174 if (SkClipStack::kWideOpenGenID == element->getGenID()) {
175 *initialState = kAllIn_InitialState;
176 break;
177 }
178
179 bool skippable = false;
180 bool isFlip = false; // does this op just flip the in/out state of every point in the bounds
181
182 switch (element->getOp()) {
183 case SkRegion::kDifference_Op:
184 // check if the shape subtracted either contains the entire bounds (and makes
185 // the clip empty) or is outside the bounds and therefore can be skipped.
186 if (element->isInverseFilled()) {
187 if (element->contains(queryBounds)) {
188 skippable = true;
189 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
190 *initialState = kAllOut_InitialState;
191 skippable = true;
192 }
193 } else {
194 if (element->contains(queryBounds)) {
195 *initialState = kAllOut_InitialState;
196 skippable = true;
197 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
198 skippable = true;
199 }
200 }
201 if (!skippable) {
202 emsmallens = true;
203 }
204 break;
205 case SkRegion::kIntersect_Op:
206 // check if the shape intersected contains the entire bounds and therefore can
207 // be skipped or it is outside the entire bounds and therefore makes the clip
208 // empty.
209 if (element->isInverseFilled()) {
210 if (element->contains(queryBounds)) {
211 *initialState = kAllOut_InitialState;
212 skippable = true;
213 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
214 skippable = true;
215 }
216 } else {
217 if (element->contains(queryBounds)) {
218 skippable = true;
219 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
220 *initialState = kAllOut_InitialState;
221 skippable = true;
222 }
223 }
224 if (!skippable) {
225 emsmallens = true;
226 }
227 break;
228 case SkRegion::kUnion_Op:
229 // If the union-ed shape contains the entire bounds then after this element
230 // the bounds is entirely inside the clip. If the union-ed shape is outside the
231 // bounds then this op can be skipped.
232 if (element->isInverseFilled()) {
233 if (element->contains(queryBounds)) {
234 skippable = true;
235 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
236 *initialState = kAllIn_InitialState;
237 skippable = true;
238 }
239 } else {
240 if (element->contains(queryBounds)) {
241 *initialState = kAllIn_InitialState;
242 skippable = true;
243 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
244 skippable = true;
245 }
246 }
247 if (!skippable) {
248 embiggens = true;
249 }
250 break;
251 case SkRegion::kXOR_Op:
252 // If the bounds is entirely inside the shape being xor-ed then the effect is
253 // to flip the inside/outside state of every point in the bounds. We may be
254 // able to take advantage of this in the forward pass. If the xor-ed shape
255 // doesn't intersect the bounds then it can be skipped.
256 if (element->isInverseFilled()) {
257 if (element->contains(queryBounds)) {
258 skippable = true;
259 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
260 isFlip = true;
261 }
262 } else {
263 if (element->contains(queryBounds)) {
264 isFlip = true;
265 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
266 skippable = true;
267 }
268 }
269 if (!skippable) {
270 emsmallens = embiggens = true;
271 }
272 break;
273 case SkRegion::kReverseDifference_Op:
274 // When the bounds is entirely within the rev-diff shape then this behaves like xor
275 // and reverses every point inside the bounds. If the shape is completely outside
276 // the bounds then we know after this element is applied that the bounds will be
277 // all outside the current clip.B
278 if (element->isInverseFilled()) {
279 if (element->contains(queryBounds)) {
280 *initialState = kAllOut_InitialState;
281 skippable = true;
282 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
283 isFlip = true;
284 }
285 } else {
286 if (element->contains(queryBounds)) {
287 isFlip = true;
288 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
289 *initialState = kAllOut_InitialState;
290 skippable = true;
291 }
292 }
293 if (!skippable) {
294 emsmallens = embiggens = true;
295 }
296 break;
297 case SkRegion::kReplace_Op:
298 // Replace will always terminate our walk. We will either begin the forward walk
299 // at the replace op or detect here than the shape is either completely inside
300 // or completely outside the bounds. In this latter case it can be skipped by
301 // setting the correct value for initialState.
302 if (element->isInverseFilled()) {
303 if (element->contains(queryBounds)) {
304 *initialState = kAllOut_InitialState;
305 skippable = true;
306 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
307 *initialState = kAllIn_InitialState;
308 skippable = true;
309 }
310 } else {
311 if (element->contains(queryBounds)) {
312 *initialState = kAllIn_InitialState;
313 skippable = true;
314 } else if (!SkRect::Intersects(element->getBounds(), queryBounds)) {
315 *initialState = kAllOut_InitialState;
316 skippable = true;
317 }
318 }
319 if (!skippable) {
320 *initialState = kAllOut_InitialState;
321 embiggens = emsmallens = true;
322 }
323 break;
324 default:
325 SkDEBUGFAIL("Unexpected op.");
326 break;
327 }
328 if (!skippable) {
329 if (0 == result->count()) {
330 // This will be the last element. Record the stricter genID.
331 *resultGenID = element->getGenID();
332 }
333
334 // if it is a flip, change it to a bounds-filling rect
335 if (isFlip) {
336 SkASSERT(SkRegion::kXOR_Op == element->getOp() ||
337 SkRegion::kReverseDifference_Op == element->getOp());
338 SkNEW_INSERT_AT_LLIST_HEAD(result,
339 Element,
340 (queryBounds, SkRegion::kReverseDifference_Op, false));
341 } else {
342 Element* newElement = result->addToHead(*element);
343 if (newElement->isAA()) {
344 ++numAAElements;
345 }
346 // Intersecting an inverse shape is the same as differencing the non-inverse shape.
347 // Replacing with an inverse shape is the same as setting initialState=kAllIn and
348 // differencing the non-inverse shape.
349 bool isReplace = SkRegion::kReplace_Op == newElement->getOp();
350 if (newElement->isInverseFilled() &&
351 (SkRegion::kIntersect_Op == newElement->getOp() || isReplace)) {
352 newElement->invertShapeFillType();
353 newElement->setOp(SkRegion::kDifference_Op);
354 if (isReplace) {
355 SkASSERT(kAllOut_InitialState == *initialState);
356 *initialState = kAllIn_InitialState;
357 }
358 }
359 }
360 }
361 }
362
363 if ((kAllOut_InitialState == *initialState && !embiggens) ||
364 (kAllIn_InitialState == *initialState && !emsmallens)) {
365 result->reset();
366 } else {
367 Element* element = result->headIter().get();
368 while (NULL != element) {
369 bool skippable = false;
370 switch (element->getOp()) {
371 case SkRegion::kDifference_Op:
372 // subtracting from the empty set yields the empty set.
373 skippable = kAllOut_InitialState == *initialState;
374 break;
375 case SkRegion::kIntersect_Op:
376 // intersecting with the empty set yields the empty set
377 if (kAllOut_InitialState == *initialState) {
378 skippable = true;
379 } else {
380 // We can clear to zero and then simply draw the clip element.
381 *initialState = kAllOut_InitialState;
382 element->setOp(SkRegion::kReplace_Op);
383 }
384 break;
385 case SkRegion::kUnion_Op:
386 if (kAllIn_InitialState == *initialState) {
387 // unioning the infinite plane with anything is a no-op.
388 skippable = true;
389 } else {
390 // unioning the empty set with a shape is the shape.
391 element->setOp(SkRegion::kReplace_Op);
392 }
393 break;
394 case SkRegion::kXOR_Op:
395 if (kAllOut_InitialState == *initialState) {
396 // xor could be changed to diff in the kAllIn case, not sure it's a win.
397 element->setOp(SkRegion::kReplace_Op);
398 }
399 break;
400 case SkRegion::kReverseDifference_Op:
401 if (kAllIn_InitialState == *initialState) {
402 // subtracting the whole plane will yield the empty set.
403 skippable = true;
404 *initialState = kAllOut_InitialState;
405 } else {
406 // this picks up flips inserted in the backwards pass.
407 skippable = element->isInverseFilled() ?
408 !SkRect::Intersects(element->getBounds(), queryBounds) :
409 element->contains(queryBounds);
410 if (skippable) {
411 *initialState = kAllIn_InitialState;
412 } else {
413 element->setOp(SkRegion::kReplace_Op);
414 }
415 }
416 break;
417 case SkRegion::kReplace_Op:
418 skippable = false; // we would have skipped it in the backwards walk if we
419 // could've.
420 break;
421 default:
422 SkDEBUGFAIL("Unexpected op.");
423 break;
424 }
425 if (!skippable) {
426 break;
427 } else {
428 if (element->isAA()) {
429 --numAAElements;
430 }
431 result->popHead();
432 element = result->headIter().get();
433 }
434 }
435 }
436 if (NULL != requiresAA) {
437 *requiresAA = numAAElements > 0;
438 }
439
440 if (0 == result->count()) {
441 if (*initialState == kAllIn_InitialState) {
442 *resultGenID = SkClipStack::kWideOpenGenID;
443 } else {
444 *resultGenID = SkClipStack::kEmptyGenID;
445 }
446 }
447 }
448 } // namespace GrReducedClip
449