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
11typedef SkClipStack::Element Element;
12////////////////////////////////////////////////////////////////////////////////
13
14namespace GrReducedClip {
15
16// helper function
17void 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/*
25There are plenty of optimizations that could be added here. Maybe flips could be folded into
26earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
27for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
28based on later intersect operations, and perhaps remove intersect-rects. We could optionally
29take a rect in case the caller knows a bound on what is to be drawn through this clip.
30*/
31void 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 (tighterBounds) {
69                *tighterBounds = queryBounds;
70            }
71            if (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 (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 (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 (requiresAA) {
95                *requiresAA = doAA;
96            }
97        } else {
98            *initialState = kAllOut_InitialState;
99             if (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 (requiresAA) {
109                   *requiresAA = false;
110                }
111                return;
112            }
113            if (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 (requiresAA) {
123                   *requiresAA = false;
124                }
125                return;
126            }
127            if (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
144void 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 (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 (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
451