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