Home | History | Annotate | Download | only in gpu
      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 */
     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 
    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