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