• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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