• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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