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 (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
142void 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