1 /* 2 * Copyright 2011 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 #ifndef SkClipStack_DEFINED 9 #define SkClipStack_DEFINED 10 11 #include "include/core/SkCanvas.h" 12 #include "include/core/SkPath.h" 13 #include "include/core/SkRRect.h" 14 #include "include/core/SkRect.h" 15 #include "include/core/SkRegion.h" 16 #include "include/core/SkShader.h" 17 #include "include/private/SkDeque.h" 18 #include "src/core/SkMessageBus.h" 19 #include "src/core/SkTLazy.h" 20 21 #if SK_SUPPORT_GPU 22 class GrProxyProvider; 23 24 #include "include/private/GrResourceKey.h" 25 #endif 26 27 // Because a single save/restore state can have multiple clips, this class 28 // stores the stack depth (fSaveCount) and clips (fDeque) separately. 29 // Each clip in fDeque stores the stack state to which it belongs 30 // (i.e., the fSaveCount in force when it was added). Restores are thus 31 // implemented by removing clips from fDeque that have an fSaveCount larger 32 // then the freshly decremented count. 33 class SkClipStack { 34 public: 35 enum BoundsType { 36 // The bounding box contains all the pixels that can be written to 37 kNormal_BoundsType, 38 // The bounding box contains all the pixels that cannot be written to. 39 // The real bound extends out to infinity and all the pixels outside 40 // of the bound can be written to. Note that some of the pixels inside 41 // the bound may also be writeable but all pixels that cannot be 42 // written to are guaranteed to be inside. 43 kInsideOut_BoundsType 44 }; 45 46 /** 47 * An element of the clip stack. It represents a shape combined with the prevoius clip using a 48 * set operator. Each element can be antialiased or not. 49 */ 50 class Element { 51 public: 52 /** This indicates the shape type of the clip element in device space. */ 53 enum class DeviceSpaceType { 54 //!< This element makes the clip empty (regardless of previous elements). 55 kEmpty, 56 //!< This element combines a device space rect with the current clip. 57 kRect, 58 //!< This element combines a device space round-rect with the current clip. 59 kRRect, 60 //!< This element combines a device space path with the current clip. 61 kPath, 62 //!< This element does not have geometry, but applies a shader to the clip 63 kShader, 64 65 kLastType = kShader 66 }; 67 static const int kTypeCnt = (int)DeviceSpaceType::kLastType + 1; 68 Element()69 Element() { 70 this->initCommon(0, SkClipOp::kIntersect, false); 71 this->setEmpty(); 72 } 73 74 Element(const Element&); 75 Element(const SkRect & rect,const SkMatrix & m,SkClipOp op,bool doAA)76 Element(const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) { 77 this->initRect(0, rect, m, op, doAA); 78 } 79 Element(const SkRRect & rrect,const SkMatrix & m,SkClipOp op,bool doAA)80 Element(const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) { 81 this->initRRect(0, rrect, m, op, doAA); 82 } 83 Element(const SkPath & path,const SkMatrix & m,SkClipOp op,bool doAA)84 Element(const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) { 85 this->initPath(0, path, m, op, doAA); 86 } 87 Element(sk_sp<SkShader> shader)88 Element(sk_sp<SkShader> shader) { 89 this->initShader(0, std::move(shader)); 90 } 91 Element(const SkRect & rect,bool doAA)92 Element(const SkRect& rect, bool doAA) { 93 this->initReplaceRect(0, rect, doAA); 94 } 95 96 ~Element(); 97 98 bool operator== (const Element& element) const; 99 bool operator!= (const Element& element) const { return !(*this == element); } 100 101 //!< Call to get the type of the clip element. getDeviceSpaceType()102 DeviceSpaceType getDeviceSpaceType() const { return fDeviceSpaceType; } 103 104 //!< Call to get the save count associated with this clip element. getSaveCount()105 int getSaveCount() const { return fSaveCount; } 106 107 //!< Call if getDeviceSpaceType() is kPath to get the path. getDeviceSpacePath()108 const SkPath& getDeviceSpacePath() const { 109 SkASSERT(DeviceSpaceType::kPath == fDeviceSpaceType); 110 return *fDeviceSpacePath; 111 } 112 113 //!< Call if getDeviceSpaceType() is kRRect to get the round-rect. getDeviceSpaceRRect()114 const SkRRect& getDeviceSpaceRRect() const { 115 SkASSERT(DeviceSpaceType::kRRect == fDeviceSpaceType); 116 return fDeviceSpaceRRect; 117 } 118 119 //!< Call if getDeviceSpaceType() is kRect to get the rect. getDeviceSpaceRect()120 const SkRect& getDeviceSpaceRect() const { 121 SkASSERT(DeviceSpaceType::kRect == fDeviceSpaceType && 122 (fDeviceSpaceRRect.isRect() || fDeviceSpaceRRect.isEmpty())); 123 return fDeviceSpaceRRect.getBounds(); 124 } 125 126 //!<Call if getDeviceSpaceType() is kShader to get a reference to the clip shader. refShader()127 sk_sp<SkShader> refShader() const { 128 return fShader; 129 } getShader()130 const SkShader* getShader() const { 131 return fShader.get(); 132 } 133 134 //!< Call if getDeviceSpaceType() is not kEmpty to get the set operation used to combine 135 //!< this element. getOp()136 SkClipOp getOp() const { return fOp; } 137 // Augments getOps()'s behavior by requiring a clip reset before the op is applied. isReplaceOp()138 bool isReplaceOp() const { return fIsReplace; } 139 140 //!< Call to get the element as a path, regardless of its type. 141 void asDeviceSpacePath(SkPath* path) const; 142 143 //!< Call if getType() is not kPath to get the element as a round rect. asDeviceSpaceRRect()144 const SkRRect& asDeviceSpaceRRect() const { 145 SkASSERT(DeviceSpaceType::kPath != fDeviceSpaceType); 146 return fDeviceSpaceRRect; 147 } 148 149 /** If getType() is not kEmpty this indicates whether the clip shape should be anti-aliased 150 when it is rasterized. */ isAA()151 bool isAA() const { return fDoAA; } 152 153 //!< Inverts the fill of the clip shape. Note that a kEmpty element remains kEmpty. 154 void invertShapeFillType(); 155 156 /** The GenID can be used by clip stack clients to cache representations of the clip. The 157 ID corresponds to the set of clip elements up to and including this element within the 158 stack not to the element itself. That is the same clip path in different stacks will 159 have a different ID since the elements produce different clip result in the context of 160 their stacks. */ getGenID()161 uint32_t getGenID() const { SkASSERT(kInvalidGenID != fGenID); return fGenID; } 162 163 /** 164 * Gets the bounds of the clip element, either the rect or path bounds. (Whether the shape 165 * is inverse filled is not considered.) 166 */ 167 const SkRect& getBounds() const; 168 169 /** 170 * Conservatively checks whether the clip shape contains the rect/rrect. (Whether the shape 171 * is inverse filled is not considered.) 172 */ 173 bool contains(const SkRect& rect) const; 174 bool contains(const SkRRect& rrect) const; 175 176 /** 177 * Is the clip shape inverse filled. 178 */ isInverseFilled()179 bool isInverseFilled() const { 180 return DeviceSpaceType::kPath == fDeviceSpaceType && 181 fDeviceSpacePath->isInverseFillType(); 182 } 183 184 #ifdef SK_DEBUG 185 /** 186 * Dumps the element to SkDebugf. This is intended for Skia development debugging 187 * Don't rely on the existence of this function or the formatting of its output. 188 */ 189 void dump() const; 190 #endif 191 192 #if SK_SUPPORT_GPU 193 /** 194 * This is used to purge any GPU resource cache items that become unreachable when 195 * the element is destroyed because their key is based on this element's gen ID. 196 */ addResourceInvalidationMessage(GrProxyProvider * proxyProvider,const GrUniqueKey & key)197 void addResourceInvalidationMessage(GrProxyProvider* proxyProvider, 198 const GrUniqueKey& key) const { 199 SkASSERT(proxyProvider); 200 201 if (!fProxyProvider) { 202 fProxyProvider = proxyProvider; 203 } 204 SkASSERT(fProxyProvider == proxyProvider); 205 206 fKeysToInvalidate.push_back(key); 207 } 208 #endif 209 210 private: 211 friend class SkClipStack; 212 213 SkTLazy<SkPath> fDeviceSpacePath; 214 SkRRect fDeviceSpaceRRect; 215 sk_sp<SkShader> fShader; 216 int fSaveCount; // save count of stack when this element was added. 217 SkClipOp fOp; 218 DeviceSpaceType fDeviceSpaceType; 219 bool fDoAA; 220 bool fIsReplace; 221 222 /* fFiniteBoundType and fFiniteBound are used to incrementally update the clip stack's 223 bound. When fFiniteBoundType is kNormal_BoundsType, fFiniteBound represents the 224 conservative bounding box of the pixels that aren't clipped (i.e., any pixels that can be 225 drawn to are inside the bound). When fFiniteBoundType is kInsideOut_BoundsType (which 226 occurs when a clip is inverse filled), fFiniteBound represents the conservative bounding 227 box of the pixels that _are_ clipped (i.e., any pixels that cannot be drawn to are inside 228 the bound). When fFiniteBoundType is kInsideOut_BoundsType the actual bound is the 229 infinite plane. This behavior of fFiniteBoundType and fFiniteBound is required so that we 230 can capture the cancelling out of the extensions to infinity when two inverse filled 231 clips are Booleaned together. */ 232 SkClipStack::BoundsType fFiniteBoundType; 233 SkRect fFiniteBound; 234 235 // When element is applied to the previous elements in the stack is the result known to be 236 // equivalent to a single rect intersection? IIOW, is the clip effectively a rectangle. 237 bool fIsIntersectionOfRects; 238 239 uint32_t fGenID; 240 #if SK_SUPPORT_GPU 241 mutable GrProxyProvider* fProxyProvider = nullptr; 242 mutable SkTArray<GrUniqueKey> fKeysToInvalidate; 243 #endif Element(int saveCount)244 Element(int saveCount) { 245 this->initCommon(saveCount, SkClipOp::kIntersect, false); 246 this->setEmpty(); 247 } 248 Element(int saveCount,const SkRRect & rrect,const SkMatrix & m,SkClipOp op,bool doAA)249 Element(int saveCount, const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) { 250 this->initRRect(saveCount, rrect, m, op, doAA); 251 } 252 Element(int saveCount,const SkRect & rect,const SkMatrix & m,SkClipOp op,bool doAA)253 Element(int saveCount, const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) { 254 this->initRect(saveCount, rect, m, op, doAA); 255 } 256 Element(int saveCount,const SkPath & path,const SkMatrix & m,SkClipOp op,bool doAA)257 Element(int saveCount, const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) { 258 this->initPath(saveCount, path, m, op, doAA); 259 } 260 Element(int saveCount,sk_sp<SkShader> shader)261 Element(int saveCount, sk_sp<SkShader> shader) { 262 this->initShader(saveCount, std::move(shader)); 263 } 264 Element(int saveCount,const SkRect & rect,bool doAA)265 Element(int saveCount, const SkRect& rect, bool doAA) { 266 this->initReplaceRect(saveCount, rect, doAA); 267 } 268 269 void initCommon(int saveCount, SkClipOp op, bool doAA); 270 void initRect(int saveCount, const SkRect&, const SkMatrix&, SkClipOp, bool doAA); 271 void initRRect(int saveCount, const SkRRect&, const SkMatrix&, SkClipOp, bool doAA); 272 void initPath(int saveCount, const SkPath&, const SkMatrix&, SkClipOp, bool doAA); 273 void initAsPath(int saveCount, const SkPath&, const SkMatrix&, SkClipOp, bool doAA); 274 void initShader(int saveCount, sk_sp<SkShader>); 275 void initReplaceRect(int saveCount, const SkRect&, bool doAA); 276 277 void setEmpty(); 278 279 // All Element methods below are only used within SkClipStack.cpp 280 inline void checkEmpty() const; 281 inline bool canBeIntersectedInPlace(int saveCount, SkClipOp op) const; 282 /* This method checks to see if two rect clips can be safely merged into one. The issue here 283 is that to be strictly correct all the edges of the resulting rect must have the same 284 anti-aliasing. */ 285 bool rectRectIntersectAllowed(const SkRect& newR, bool newAA) const; 286 /** Determines possible finite bounds for the Element given the previous element of the 287 stack */ 288 void updateBoundAndGenID(const Element* prior); 289 // The different combination of fill & inverse fill when combining bounding boxes 290 enum FillCombo { 291 kPrev_Cur_FillCombo, 292 kPrev_InvCur_FillCombo, 293 kInvPrev_Cur_FillCombo, 294 kInvPrev_InvCur_FillCombo 295 }; 296 // per-set operation functions used by updateBoundAndGenID(). 297 inline void combineBoundsDiff(FillCombo combination, const SkRect& prevFinite); 298 inline void combineBoundsIntersection(int combination, const SkRect& prevFinite); 299 }; 300 301 SkClipStack(); 302 SkClipStack(void* storage, size_t size); 303 SkClipStack(const SkClipStack& b); 304 ~SkClipStack(); 305 306 SkClipStack& operator=(const SkClipStack& b); 307 bool operator==(const SkClipStack& b) const; 308 bool operator!=(const SkClipStack& b) const { return !(*this == b); } 309 310 void reset(); 311 getSaveCount()312 int getSaveCount() const { return fSaveCount; } 313 void save(); 314 void restore(); 315 316 class AutoRestore { 317 public: AutoRestore(SkClipStack * cs,bool doSave)318 AutoRestore(SkClipStack* cs, bool doSave) 319 : fCS(cs), fSaveCount(cs->getSaveCount()) 320 { 321 if (doSave) { 322 fCS->save(); 323 } 324 } ~AutoRestore()325 ~AutoRestore() { 326 SkASSERT(fCS->getSaveCount() >= fSaveCount); // no underflow 327 while (fCS->getSaveCount() > fSaveCount) { 328 fCS->restore(); 329 } 330 } 331 332 private: 333 SkClipStack* fCS; 334 const int fSaveCount; 335 }; 336 337 /** 338 * getBounds places the current finite bound in its first parameter. In its 339 * second, it indicates which kind of bound is being returned. If 340 * 'canvFiniteBound' is a normal bounding box then it encloses all writeable 341 * pixels. If 'canvFiniteBound' is an inside out bounding box then it 342 * encloses all the un-writeable pixels and the true/normal bound is the 343 * infinite plane. isIntersectionOfRects is an optional parameter 344 * that is true if 'canvFiniteBound' resulted from an intersection of rects. 345 */ 346 void getBounds(SkRect* canvFiniteBound, 347 BoundsType* boundType, 348 bool* isIntersectionOfRects = nullptr) const; 349 350 SkRect bounds(const SkIRect& deviceBounds) const; 351 bool isEmpty(const SkIRect& deviceBounds) const; 352 353 /** 354 * Returns true if the input (r)rect in device space is entirely contained 355 * by the clip. A return value of false does not guarantee that the (r)rect 356 * is not contained by the clip. 357 */ quickContains(const SkRect & devRect)358 bool quickContains(const SkRect& devRect) const { 359 return this->isWideOpen() || this->internalQuickContains(devRect); 360 } 361 quickContains(const SkRRect & devRRect)362 bool quickContains(const SkRRect& devRRect) const { 363 return this->isWideOpen() || this->internalQuickContains(devRRect); 364 } 365 clipDevRect(const SkIRect & ir,SkClipOp op)366 void clipDevRect(const SkIRect& ir, SkClipOp op) { 367 SkRect r; 368 r.set(ir); 369 this->clipRect(r, SkMatrix::I(), op, false); 370 } 371 void clipRect(const SkRect&, const SkMatrix& matrix, SkClipOp, bool doAA); 372 void clipRRect(const SkRRect&, const SkMatrix& matrix, SkClipOp, bool doAA); 373 void clipPath(const SkPath&, const SkMatrix& matrix, SkClipOp, bool doAA); 374 void clipShader(sk_sp<SkShader>); 375 // An optimized version of clipDevRect(emptyRect, kIntersect, ...) 376 void clipEmpty(); 377 378 void replaceClip(const SkRect& devRect, bool doAA); 379 380 /** 381 * isWideOpen returns true if the clip state corresponds to the infinite 382 * plane (i.e., draws are not limited at all) 383 */ isWideOpen()384 bool isWideOpen() const { return this->getTopmostGenID() == kWideOpenGenID; } 385 386 /** 387 * This method quickly and conservatively determines whether the entire stack is equivalent to 388 * intersection with a rrect given a bounds, where the rrect must not contain the entire bounds. 389 * 390 * @param bounds A bounds on what will be drawn through the clip. The clip only need be 391 * equivalent to a intersection with a rrect for draws within the bounds. The 392 * returned rrect must intersect the bounds but need not be contained by the 393 * bounds. 394 * @param rrect If return is true rrect will contain the rrect equivalent to the stack. 395 * @param aa If return is true aa will indicate whether the equivalent rrect clip is 396 * antialiased. 397 * @return true if the stack is equivalent to a single rrect intersect clip, false otherwise. 398 */ 399 bool isRRect(const SkRect& bounds, SkRRect* rrect, bool* aa) const; 400 401 /** 402 * The generation ID has three reserved values to indicate special 403 * (potentially ignorable) cases 404 */ 405 static const uint32_t kInvalidGenID = 0; //!< Invalid id that is never returned by 406 //!< SkClipStack. Useful when caching clips 407 //!< based on GenID. 408 static const uint32_t kEmptyGenID = 1; // no pixels writeable 409 static const uint32_t kWideOpenGenID = 2; // all pixels writeable 410 411 uint32_t getTopmostGenID() const; 412 413 #ifdef SK_DEBUG 414 /** 415 * Dumps the contents of the clip stack to SkDebugf. This is intended for Skia development 416 * debugging. Don't rely on the existence of this function or the formatting of its output. 417 */ 418 void dump() const; 419 #endif 420 421 public: 422 class Iter { 423 public: 424 enum IterStart { 425 kBottom_IterStart = SkDeque::Iter::kFront_IterStart, 426 kTop_IterStart = SkDeque::Iter::kBack_IterStart 427 }; 428 429 /** 430 * Creates an uninitialized iterator. Must be reset() 431 */ 432 Iter(); 433 434 Iter(const SkClipStack& stack, IterStart startLoc); 435 436 /** 437 * Return the clip element for this iterator. If next()/prev() returns NULL, then the 438 * iterator is done. 439 */ 440 const Element* next(); 441 const Element* prev(); 442 443 /** 444 * Moves the iterator to the topmost element with the specified RegionOp and returns that 445 * element. If no clip element with that op is found, the first element is returned. 446 */ 447 const Element* skipToTopmost(SkClipOp op); 448 449 /** 450 * Restarts the iterator on a clip stack. 451 */ 452 void reset(const SkClipStack& stack, IterStart startLoc); 453 454 private: 455 const SkClipStack* fStack; 456 SkDeque::Iter fIter; 457 }; 458 459 /** 460 * The B2TIter iterates from the bottom of the stack to the top. 461 * It inherits privately from Iter to prevent access to reverse iteration. 462 */ 463 class B2TIter : private Iter { 464 public: B2TIter()465 B2TIter() {} 466 467 /** 468 * Wrap Iter's 2 parameter ctor to force initialization to the 469 * beginning of the deque/bottom of the stack 470 */ B2TIter(const SkClipStack & stack)471 B2TIter(const SkClipStack& stack) 472 : INHERITED(stack, kBottom_IterStart) { 473 } 474 475 using Iter::next; 476 477 /** 478 * Wrap Iter::reset to force initialization to the 479 * beginning of the deque/bottom of the stack 480 */ reset(const SkClipStack & stack)481 void reset(const SkClipStack& stack) { 482 this->INHERITED::reset(stack, kBottom_IterStart); 483 } 484 485 private: 486 487 using INHERITED = Iter; 488 }; 489 490 /** 491 * GetConservativeBounds returns a conservative bound of the current clip. 492 * Since this could be the infinite plane (if inverse fills were involved) the 493 * maxWidth and maxHeight parameters can be used to limit the returned bound 494 * to the expected drawing area. Similarly, the offsetX and offsetY parameters 495 * allow the caller to offset the returned bound to account for translated 496 * drawing areas (i.e., those resulting from a saveLayer). For finite bounds, 497 * the translation (+offsetX, +offsetY) is applied before the clamp to the 498 * maximum rectangle: [0,maxWidth) x [0,maxHeight). 499 * isIntersectionOfRects is an optional parameter that is true when 500 * 'devBounds' is the result of an intersection of rects. In this case 501 * 'devBounds' is the exact answer/clip. 502 */ 503 void getConservativeBounds(int offsetX, 504 int offsetY, 505 int maxWidth, 506 int maxHeight, 507 SkRect* devBounds, 508 bool* isIntersectionOfRects = nullptr) const; 509 510 private: 511 friend class Iter; 512 513 SkDeque fDeque; 514 int fSaveCount; 515 516 bool internalQuickContains(const SkRect& devRect) const; 517 bool internalQuickContains(const SkRRect& devRRect) const; 518 519 /** 520 * Helper for clipDevPath, etc. 521 */ 522 void pushElement(const Element& element); 523 524 /** 525 * Restore the stack back to the specified save count. 526 */ 527 void restoreTo(int saveCount); 528 529 /** 530 * Return the next unique generation ID. 531 */ 532 static uint32_t GetNextGenID(); 533 }; 534 535 #endif 536