xref: /third_party/skia/src/core/SkClipStack.h (revision cb93a386)
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
22class 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.
33class SkClipStack {
34public:
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
69        Element() {
70            this->initCommon(0, SkClipOp::kIntersect, false);
71            this->setEmpty();
72        }
73
74        Element(const Element&);
75
76        Element(const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) {
77            this->initRect(0, rect, m, op, doAA);
78        }
79
80        Element(const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) {
81            this->initRRect(0, rrect, m, op, doAA);
82        }
83
84        Element(const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) {
85            this->initPath(0, path, m, op, doAA);
86        }
87
88        Element(sk_sp<SkShader> shader) {
89            this->initShader(0, std::move(shader));
90        }
91
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.
102        DeviceSpaceType getDeviceSpaceType() const { return fDeviceSpaceType; }
103
104        //!< Call to get the save count associated with this clip element.
105        int getSaveCount() const { return fSaveCount; }
106
107        //!< Call if getDeviceSpaceType() is kPath to get the path.
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.
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.
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.
127        sk_sp<SkShader> refShader() const {
128            return fShader;
129        }
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.
136        SkClipOp getOp() const { return fOp; }
137        // Augments getOps()'s behavior by requiring a clip reset before the op is applied.
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.
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. */
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. */
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         */
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         */
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
244        Element(int saveCount) {
245            this->initCommon(saveCount, SkClipOp::kIntersect, false);
246            this->setEmpty();
247        }
248
249        Element(int saveCount, const SkRRect& rrect, const SkMatrix& m, SkClipOp op, bool doAA) {
250            this->initRRect(saveCount, rrect, m, op, doAA);
251        }
252
253        Element(int saveCount, const SkRect& rect, const SkMatrix& m, SkClipOp op, bool doAA) {
254            this->initRect(saveCount, rect, m, op, doAA);
255        }
256
257        Element(int saveCount, const SkPath& path, const SkMatrix& m, SkClipOp op, bool doAA) {
258            this->initPath(saveCount, path, m, op, doAA);
259        }
260
261        Element(int saveCount, sk_sp<SkShader> shader) {
262            this->initShader(saveCount, std::move(shader));
263        }
264
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
312    int getSaveCount() const { return fSaveCount; }
313    void save();
314    void restore();
315
316    class AutoRestore {
317    public:
318        AutoRestore(SkClipStack* cs, bool doSave)
319            : fCS(cs), fSaveCount(cs->getSaveCount())
320        {
321            if (doSave) {
322                fCS->save();
323            }
324        }
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     */
358    bool quickContains(const SkRect& devRect) const {
359        return this->isWideOpen() || this->internalQuickContains(devRect);
360    }
361
362    bool quickContains(const SkRRect& devRRect) const {
363        return this->isWideOpen() || this->internalQuickContains(devRRect);
364    }
365
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     */
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
421public:
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:
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         */
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         */
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
510private:
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