Home | History | Annotate | Download | only in gpu
      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 
     10 typedef SkClipStack::Element Element;
     11 
     12 static 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 (nullptr == 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                 result->addToHead(queryBounds, SkRegion::kReverseDifference_Op, false);
    210             } else {
    211                 Element* newElement = result->addToHead(*element);
    212                 if (newElement->isAA()) {
    213                     ++numAAElements;
    214                 }
    215                 // Intersecting an inverse shape is the same as differencing the non-inverse shape.
    216                 // Replacing with an inverse shape is the same as setting initialState=kAllIn and
    217                 // differencing the non-inverse shape.
    218                 bool isReplace = SkRegion::kReplace_Op == newElement->getOp();
    219                 if (newElement->isInverseFilled() &&
    220                     (SkRegion::kIntersect_Op == newElement->getOp() || isReplace)) {
    221                     newElement->invertShapeFillType();
    222                     newElement->setOp(SkRegion::kDifference_Op);
    223                     if (isReplace) {
    224                         SkASSERT(GrReducedClip::kAllOut_InitialState == *initialState);
    225                         *initialState = GrReducedClip::kAllIn_InitialState;
    226                     }
    227                 }
    228             }
    229         }
    230     }
    231 
    232     if ((GrReducedClip::kAllOut_InitialState == *initialState && !embiggens) ||
    233         (GrReducedClip::kAllIn_InitialState == *initialState && !emsmallens)) {
    234         result->reset();
    235     } else {
    236         Element* element = result->headIter().get();
    237         while (element) {
    238             bool skippable = false;
    239             switch (element->getOp()) {
    240                 case SkRegion::kDifference_Op:
    241                     // subtracting from the empty set yields the empty set.
    242                     skippable = GrReducedClip::kAllOut_InitialState == *initialState;
    243                     break;
    244                 case SkRegion::kIntersect_Op:
    245                     // intersecting with the empty set yields the empty set
    246                     if (GrReducedClip::kAllOut_InitialState == *initialState) {
    247                         skippable = true;
    248                     } else {
    249                         // We can clear to zero and then simply draw the clip element.
    250                         *initialState = GrReducedClip::kAllOut_InitialState;
    251                         element->setOp(SkRegion::kReplace_Op);
    252                     }
    253                     break;
    254                 case SkRegion::kUnion_Op:
    255                     if (GrReducedClip::kAllIn_InitialState == *initialState) {
    256                         // unioning the infinite plane with anything is a no-op.
    257                         skippable = true;
    258                     } else {
    259                         // unioning the empty set with a shape is the shape.
    260                         element->setOp(SkRegion::kReplace_Op);
    261                     }
    262                     break;
    263                 case SkRegion::kXOR_Op:
    264                     if (GrReducedClip::kAllOut_InitialState == *initialState) {
    265                         // xor could be changed to diff in the kAllIn case, not sure it's a win.
    266                         element->setOp(SkRegion::kReplace_Op);
    267                     }
    268                     break;
    269                 case SkRegion::kReverseDifference_Op:
    270                     if (GrReducedClip::kAllIn_InitialState == *initialState) {
    271                         // subtracting the whole plane will yield the empty set.
    272                         skippable = true;
    273                         *initialState = GrReducedClip::kAllOut_InitialState;
    274                     } else {
    275                         // this picks up flips inserted in the backwards pass.
    276                         skippable = element->isInverseFilled() ?
    277                             !SkRect::Intersects(element->getBounds(), queryBounds) :
    278                             element->contains(queryBounds);
    279                         if (skippable) {
    280                             *initialState = GrReducedClip::kAllIn_InitialState;
    281                         } else {
    282                             element->setOp(SkRegion::kReplace_Op);
    283                         }
    284                     }
    285                     break;
    286                 case SkRegion::kReplace_Op:
    287                     skippable = false; // we would have skipped it in the backwards walk if we
    288                                        // could've.
    289                     break;
    290                 default:
    291                     SkDEBUGFAIL("Unexpected op.");
    292                     break;
    293             }
    294             if (!skippable) {
    295                 break;
    296             } else {
    297                 if (element->isAA()) {
    298                     --numAAElements;
    299                 }
    300                 result->popHead();
    301                 element = result->headIter().get();
    302             }
    303         }
    304     }
    305     if (requiresAA) {
    306         *requiresAA = numAAElements > 0;
    307     }
    308 
    309     if (0 == result->count()) {
    310         if (*initialState == GrReducedClip::kAllIn_InitialState) {
    311             *resultGenID = SkClipStack::kWideOpenGenID;
    312         } else {
    313             *resultGenID = SkClipStack::kEmptyGenID;
    314         }
    315     }
    316 }
    317 
    318 /*
    319 There are plenty of optimizations that could be added here. Maybe flips could be folded into
    320 earlier operations. Or would inserting flips and reversing earlier ops ever be a win? Perhaps
    321 for the case where the bounds are kInsideOut_BoundsType. We could restrict earlier operations
    322 based on later intersect operations, and perhaps remove intersect-rects. We could optionally
    323 take a rect in case the caller knows a bound on what is to be drawn through this clip.
    324 */
    325 void GrReducedClip::ReduceClipStack(const SkClipStack& stack,
    326                                     const SkIRect& queryBounds,
    327                                     ElementList* result,
    328                                     int32_t* resultGenID,
    329                                     InitialState* initialState,
    330                                     SkIRect* tighterBounds,
    331                                     bool* requiresAA) {
    332     result->reset();
    333 
    334     // The clip established by the element list might be cached based on the last
    335     // generation id. When we make early returns, we do not know what was the generation
    336     // id that lead to the state. Make a conservative guess.
    337     *resultGenID = stack.getTopmostGenID();
    338 
    339     if (stack.isWideOpen()) {
    340         *initialState = kAllIn_InitialState;
    341         return;
    342     }
    343 
    344 
    345     // We initially look at whether the bounds alone is sufficient. We also use the stack bounds to
    346     // attempt to compute the tighterBounds.
    347 
    348     SkClipStack::BoundsType stackBoundsType;
    349     SkRect stackBounds;
    350     bool iior;
    351     stack.getBounds(&stackBounds, &stackBoundsType, &iior);
    352 
    353     const SkIRect* bounds = &queryBounds;
    354 
    355     SkRect scalarQueryBounds = SkRect::Make(queryBounds);
    356 
    357     if (iior) {
    358         SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType);
    359         SkRect isectRect;
    360         if (stackBounds.contains(scalarQueryBounds)) {
    361             *initialState = GrReducedClip::kAllIn_InitialState;
    362             if (tighterBounds) {
    363                 *tighterBounds = queryBounds;
    364             }
    365             if (requiresAA) {
    366                *requiresAA = false;
    367             }
    368         } else if (isectRect.intersect(stackBounds, scalarQueryBounds)) {
    369             // If the caller asked for tighter integer bounds we may be able to
    370             // return kAllIn and give the bounds with no elements
    371             if (tighterBounds) {
    372                 isectRect.roundOut(tighterBounds);
    373                 SkRect scalarTighterBounds = SkRect::Make(*tighterBounds);
    374                 if (scalarTighterBounds == isectRect) {
    375                     // the round-out didn't add any area outside the clip rect.
    376                     if (requiresAA) {
    377                         *requiresAA = false;
    378                     }
    379                     *initialState = GrReducedClip::kAllIn_InitialState;
    380                     return;
    381                 }
    382             }
    383             *initialState = kAllOut_InitialState;
    384             // iior should only be true if aa/non-aa status matches among all elements.
    385             SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
    386             bool doAA = iter.prev()->isAA();
    387             result->addToHead(isectRect, SkRegion::kReplace_Op, doAA);
    388             if (requiresAA) {
    389                 *requiresAA = doAA;
    390             }
    391         } else {
    392             *initialState = kAllOut_InitialState;
    393              if (requiresAA) {
    394                 *requiresAA = false;
    395              }
    396         }
    397         return;
    398     } else {
    399         if (SkClipStack::kNormal_BoundsType == stackBoundsType) {
    400             if (!SkRect::Intersects(stackBounds, scalarQueryBounds)) {
    401                 *initialState = kAllOut_InitialState;
    402                 if (requiresAA) {
    403                    *requiresAA = false;
    404                 }
    405                 return;
    406             }
    407             if (tighterBounds) {
    408                 SkIRect stackIBounds;
    409                 stackBounds.roundOut(&stackIBounds);
    410                 if (!tighterBounds->intersect(queryBounds, stackIBounds)) {
    411                     SkASSERT(0);
    412                     tighterBounds->setEmpty();
    413                 }
    414                 bounds = tighterBounds;
    415             }
    416         } else {
    417             if (stackBounds.contains(scalarQueryBounds)) {
    418                 *initialState = kAllOut_InitialState;
    419                 // We know that the bounding box contains all the pixels that are outside the clip,
    420                 // but we don't know that *all* the pixels in the box are outside the clip. So
    421                 // proceed to walking the stack.
    422             }
    423             if (tighterBounds) {
    424                 *tighterBounds = queryBounds;
    425             }
    426         }
    427     }
    428 
    429     SkRect scalarBounds = SkRect::Make(*bounds);
    430 
    431     // Now that we have determined the bounds to use and filtered out the trivial cases, call the
    432     // helper that actually walks the stack.
    433     reduced_stack_walker(stack, scalarBounds, result, resultGenID, initialState, requiresAA);
    434 
    435     // The list that was computed in this function may be cached based on the gen id of the last
    436     // element.
    437     SkASSERT(SkClipStack::kInvalidGenID != *resultGenID);
    438 }
    439