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