1/*
2 * Copyright 2016 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 GrShape_DEFINED
9#define GrShape_DEFINED
10
11#include "GrStyle.h"
12#include "SkPath.h"
13#include "SkPathPriv.h"
14#include "SkRRect.h"
15#include "SkTemplates.h"
16#include "SkTLazy.h"
17
18/**
19 * Represents a geometric shape (rrect or path) and the GrStyle that it should be rendered with.
20 * It is possible to apply the style to the GrShape to produce a new GrShape where the geometry
21 * reflects the styling information (e.g. is stroked). It is also possible to apply just the
22 * path effect from the style. In this case the resulting shape will include any remaining
23 * stroking information that is to be applied after the path effect.
24 *
25 * Shapes can produce keys that represent only the geometry information, not the style. Note that
26 * when styling information is applied to produce a new shape then the style has been converted
27 * to geometric information and is included in the new shape's key. When the same style is applied
28 * to two shapes that reflect the same underlying geometry the computed keys of the stylized shapes
29 * will be the same.
30 *
31 * Currently this can only be constructed from a path, rect, or rrect though it can become a path
32 * applying style to the geometry. The idea is to expand this to cover most or all of the geometries
33 * that have fast paths in the GPU backend.
34 */
35class GrShape {
36public:
37    // Keys for paths may be extracted from the path data for small paths. Clients aren't supposed
38    // to have to worry about this. This value is exposed for unit tests.
39    static constexpr int kMaxKeyFromDataVerbCnt = 10;
40
41    GrShape() { this->initType(Type::kEmpty); }
42
43    explicit GrShape(const SkPath& path) : GrShape(path, GrStyle::SimpleFill()) {}
44
45    explicit GrShape(const SkRRect& rrect) : GrShape(rrect, GrStyle::SimpleFill()) {}
46
47    explicit GrShape(const SkRect& rect) : GrShape(rect, GrStyle::SimpleFill()) {}
48
49    GrShape(const SkPath& path, const GrStyle& style) : fStyle(style) {
50        this->initType(Type::kPath, &path);
51        this->attemptToSimplifyPath();
52    }
53
54    GrShape(const SkRRect& rrect, const GrStyle& style)
55        : fStyle(style) {
56        this->initType(Type::kRRect);
57        fRRectData.fRRect = rrect;
58        fRRectData.fInverted = false;
59        fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(),
60                                                         &fRRectData.fDir);
61        this->attemptToSimplifyRRect();
62    }
63
64    GrShape(const SkRRect& rrect, SkPath::Direction dir, unsigned start, bool inverted,
65            const GrStyle& style)
66        : fStyle(style) {
67        this->initType(Type::kRRect);
68        fRRectData.fRRect = rrect;
69        fRRectData.fInverted = inverted;
70        if (style.pathEffect()) {
71            fRRectData.fDir = dir;
72            fRRectData.fStart = start;
73            if (fRRectData.fRRect.getType() == SkRRect::kRect_Type) {
74                fRRectData.fStart = (fRRectData.fStart + 1) & 0b110;
75            } else if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
76                fRRectData.fStart &= 0b110;
77            }
78        } else {
79            fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectData.fDir);
80        }
81        this->attemptToSimplifyRRect();
82    }
83
84    GrShape(const SkRect& rect, const GrStyle& style)
85        : fStyle(style) {
86        this->initType(Type::kRRect);
87        fRRectData.fRRect = SkRRect::MakeRect(rect);
88        fRRectData.fInverted = false;
89        fRRectData.fStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(),
90                                                        &fRRectData.fDir);
91        this->attemptToSimplifyRRect();
92    }
93
94    GrShape(const SkPath& path, const SkPaint& paint) : fStyle(paint) {
95        this->initType(Type::kPath, &path);
96        this->attemptToSimplifyPath();
97    }
98
99    GrShape(const SkRRect& rrect, const SkPaint& paint)
100        : fStyle(paint) {
101        this->initType(Type::kRRect);
102        fRRectData.fRRect = rrect;
103        fRRectData.fInverted = false;
104        fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(),
105                                                         &fRRectData.fDir);
106        this->attemptToSimplifyRRect();
107    }
108
109    GrShape(const SkRect& rect, const SkPaint& paint)
110        : fStyle(paint) {
111        this->initType(Type::kRRect);
112        fRRectData.fRRect = SkRRect::MakeRect(rect);
113        fRRectData.fInverted = false;
114        fRRectData.fStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(),
115                                                        &fRRectData.fDir);
116        this->attemptToSimplifyRRect();
117    }
118
119    GrShape(const GrShape&);
120    GrShape& operator=(const GrShape& that);
121
122    ~GrShape() { this->changeType(Type::kEmpty); }
123
124    /**
125     * Informs MakeFilled on how to modify that shape's fill rule when making a simple filled
126     * version of the shape.
127     */
128    enum class FillInversion {
129        kPreserve,
130        kFlip,
131        kForceNoninverted,
132        kForceInverted
133    };
134    /**
135     * Makes a filled shape from the pre-styled original shape and optionally modifies whether
136     * the fill is inverted or not. It's important to note that the original shape's geometry
137     * may already have been modified if doing so was neutral with respect to its style
138     * (e.g. filled paths are always closed when stored in a shape and dashed paths are always
139     * made non-inverted since dashing ignores inverseness).
140     */
141    static GrShape MakeFilled(const GrShape& original, FillInversion = FillInversion::kPreserve);
142
143    const GrStyle& style() const { return fStyle; }
144
145    /**
146     * Returns a shape that has either applied the path effect or path effect and stroking
147     * information from this shape's style to its geometry. Scale is used when approximating the
148     * output geometry and typically is computed from the view matrix
149     */
150    GrShape applyStyle(GrStyle::Apply apply, SkScalar scale) const {
151        return GrShape(*this, apply, scale);
152    }
153
154    /** Returns the unstyled geometry as a rrect if possible. */
155    bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start, bool* inverted) const {
156        if (Type::kRRect != fType) {
157            return false;
158        }
159        if (rrect) {
160            *rrect = fRRectData.fRRect;
161        }
162        if (dir) {
163            *dir = fRRectData.fDir;
164        }
165        if (start) {
166            *start = fRRectData.fStart;
167        }
168        if (inverted) {
169            *inverted = fRRectData.fInverted;
170        }
171        return true;
172    }
173
174    /**
175     * If the unstyled shape is a straight line segment, returns true and sets pts to the endpoints.
176     * An inverse filled line path is still considered a line.
177     */
178    bool asLine(SkPoint pts[2], bool* inverted) const {
179        if (fType != Type::kLine) {
180            return false;
181        }
182        if (pts) {
183            pts[0] = fLineData.fPts[0];
184            pts[1] = fLineData.fPts[1];
185        }
186        if (inverted) {
187            *inverted = fLineData.fInverted;
188        }
189        return true;
190    }
191
192    /** Returns the unstyled geometry as a path. */
193    void asPath(SkPath* out) const {
194        switch (fType) {
195            case Type::kEmpty:
196                out->reset();
197                break;
198            case Type::kInvertedEmpty:
199                out->reset();
200                out->setFillType(kDefaultPathInverseFillType);
201                break;
202            case Type::kRRect:
203                out->reset();
204                out->addRRect(fRRectData.fRRect, fRRectData.fDir, fRRectData.fStart);
205                // Below matches the fill type that attemptToSimplifyPath uses.
206                if (fRRectData.fInverted) {
207                    out->setFillType(kDefaultPathInverseFillType);
208                } else {
209                    out->setFillType(kDefaultPathFillType);
210                }
211                break;
212            case Type::kLine:
213                out->reset();
214                out->moveTo(fLineData.fPts[0]);
215                out->lineTo(fLineData.fPts[1]);
216                if (fLineData.fInverted) {
217                    out->setFillType(kDefaultPathInverseFillType);
218                } else {
219                    out->setFillType(kDefaultPathFillType);
220                }
221                break;
222            case Type::kPath:
223                *out = this->path();
224                break;
225        }
226    }
227
228    /**
229     * Returns whether the geometry is empty. Note that applying the style could produce a
230     * non-empty shape. It also may have an inverse fill.
231     */
232    bool isEmpty() const { return Type::kEmpty == fType || Type::kInvertedEmpty == fType; }
233
234    /**
235     * Gets the bounds of the geometry without reflecting the shape's styling. This ignores
236     * the inverse fill nature of the geometry.
237     */
238    SkRect bounds() const;
239
240    /**
241     * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill
242     * status).
243     */
244    SkRect styledBounds() const;
245
246    /**
247     * Is this shape known to be convex, before styling is applied. An unclosed but otherwise
248     * convex path is considered to be closed if they styling reflects a fill and not otherwise.
249     * This is because filling closes all contours in the path.
250     */
251    bool knownToBeConvex() const {
252        switch (fType) {
253            case Type::kEmpty:
254                return true;
255            case Type::kInvertedEmpty:
256                return true;
257            case Type::kRRect:
258                return true;
259            case Type::kLine:
260                return true;
261            case Type::kPath:
262                // SkPath.isConvex() really means "is this path convex were it to be closed" and
263                // thus doesn't give the correct answer for stroked paths, hence we also check
264                // whether the path is either filled or closed. Convex paths may only have one
265                // contour hence isLastContourClosed() is a sufficient for a convex path.
266                return (this->style().isSimpleFill() || this->path().isLastContourClosed()) &&
267                        this->path().isConvex();
268        }
269        return false;
270    }
271
272    /** Is the pre-styled geometry inverse filled? */
273    bool inverseFilled() const {
274        bool ret = false;
275        switch (fType) {
276            case Type::kEmpty:
277                ret = false;
278                break;
279            case Type::kInvertedEmpty:
280                ret = true;
281                break;
282            case Type::kRRect:
283                ret = fRRectData.fInverted;
284                break;
285            case Type::kLine:
286                ret = fLineData.fInverted;
287                break;
288            case Type::kPath:
289                ret = this->path().isInverseFillType();
290                break;
291        }
292        // Dashing ignores inverseness. We should have caught this earlier. skbug.com/5421
293        SkASSERT(!(ret && this->style().isDashed()));
294        return ret;
295    }
296
297    /**
298     * Might applying the styling to the geometry produce an inverse fill. The "may" part comes in
299     * because an arbitrary path effect could produce an inverse filled path. In other cases this
300     * can be thought of as "inverseFilledAfterStyling()".
301     */
302    bool mayBeInverseFilledAfterStyling() const {
303         // An arbitrary path effect can produce an arbitrary output path, which may be inverse
304         // filled.
305        if (this->style().hasNonDashPathEffect()) {
306            return true;
307        }
308        return this->inverseFilled();
309    }
310
311    /**
312     * Is it known that the unstyled geometry has no unclosed contours. This means that it will
313     * not have any caps if stroked (modulo the effect of any path effect).
314     */
315    bool knownToBeClosed() const {
316        switch (fType) {
317            case Type::kEmpty:
318                return true;
319            case Type::kInvertedEmpty:
320                return true;
321            case Type::kRRect:
322                return true;
323            case Type::kLine:
324                return false;
325            case Type::kPath:
326                // SkPath doesn't keep track of the closed status of each contour.
327                return SkPathPriv::IsClosedSingleContour(this->path());
328        }
329        return false;
330    }
331
332    uint32_t segmentMask() const {
333        switch (fType) {
334            case Type::kEmpty:
335                return 0;
336            case Type::kInvertedEmpty:
337                return 0;
338            case Type::kRRect:
339                if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
340                    return SkPath::kConic_SegmentMask;
341                } else if (fRRectData.fRRect.getType() == SkRRect::kRect_Type ||
342                           fRRectData.fRRect.getType() == SkRRect::kEmpty_Type) {
343                    return SkPath::kLine_SegmentMask;
344                }
345                return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask;
346            case Type::kLine:
347                return SkPath::kLine_SegmentMask;
348            case Type::kPath:
349                return this->path().getSegmentMasks();
350        }
351        return 0;
352    }
353
354    /**
355     * Gets the size of the key for the shape represented by this GrShape (ignoring its styling).
356     * A negative value is returned if the shape has no key (shouldn't be cached).
357     */
358    int unstyledKeySize() const;
359
360    bool hasUnstyledKey() const { return this->unstyledKeySize() >= 0; }
361
362    /**
363     * Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough
364     * space allocated for the key and that unstyledKeySize() does not return a negative value
365     * for this shape.
366     */
367    void writeUnstyledKey(uint32_t* key) const;
368
369    /**
370     * Adds a listener to the *original* path. Typically used to invalidate cached resources when
371     * a path is no longer in-use. If the shape started out as something other than a path, this
372     * does nothing (but will delete the listener).
373     */
374    void addGenIDChangeListener(SkPathRef::GenIDChangeListener* listener) const;
375
376    /**
377     * Helpers that are only exposed for unit tests, to determine if the shape is a path, and get
378     * the generation ID of the *original* path. This is the path that will receive
379     * GenIDChangeListeners added to this shape.
380     */
381    uint32_t testingOnly_getOriginalGenerationID() const;
382    bool testingOnly_isPath() const;
383    bool testingOnly_isNonVolatilePath() const;
384
385private:
386    enum class Type {
387        kEmpty,
388        kInvertedEmpty,
389        kRRect,
390        kLine,
391        kPath,
392    };
393
394    void initType(Type type, const SkPath* path = nullptr) {
395        fType = Type::kEmpty;
396        this->changeType(type, path);
397    }
398
399    void changeType(Type type, const SkPath* path = nullptr) {
400        bool wasPath = Type::kPath == fType;
401        fType = type;
402        bool isPath = Type::kPath == type;
403        SkASSERT(!path || isPath);
404        if (wasPath && !isPath) {
405            fPathData.fPath.~SkPath();
406        } else if (!wasPath && isPath) {
407            if (path) {
408                new (&fPathData.fPath) SkPath(*path);
409            } else {
410                new (&fPathData.fPath) SkPath();
411            }
412        } else if (isPath && path) {
413            fPathData.fPath = *path;
414        }
415        // Whether or not we use the path's gen ID is decided in attemptToSimplifyPath.
416        fPathData.fGenID = 0;
417    }
418
419    SkPath& path() {
420        SkASSERT(Type::kPath == fType);
421        return fPathData.fPath;
422    }
423
424    const SkPath& path() const {
425        SkASSERT(Type::kPath == fType);
426        return fPathData.fPath;
427    }
428
429    /** Constructor used by the applyStyle() function */
430    GrShape(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
431
432    /**
433     * Determines the key we should inherit from the input shape's geometry and style when
434     * we are applying the style to create a new shape.
435     */
436    void setInheritedKey(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
437
438    void attemptToSimplifyPath();
439    void attemptToSimplifyRRect();
440    void attemptToSimplifyLine();
441
442    bool attemptToSimplifyStrokedLineToRRect();
443
444    /** Gets the path that gen id listeners should be added to. */
445    const SkPath* originalPathForListeners() const;
446
447    // Defaults to use when there is no distinction between even/odd and winding fills.
448    static constexpr SkPath::FillType kDefaultPathFillType = SkPath::kEvenOdd_FillType;
449    static constexpr SkPath::FillType kDefaultPathInverseFillType =
450            SkPath::kInverseEvenOdd_FillType;
451
452    static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction;
453    static constexpr unsigned kDefaultRRectStart = 0;
454
455    static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect,
456                                                SkPath::Direction* dir) {
457        *dir = kDefaultRRectDir;
458        // This comes from SkPath's interface. The default for adding a SkRect is counter clockwise
459        // beginning at index 0 (which happens to correspond to rrect index 0 or 7).
460        if (!hasPathEffect) {
461            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
462            return kDefaultRRectStart;
463        }
464        // In SkPath a rect starts at index 0 by default. This is the top left corner. However,
465        // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the
466        // rect edges. Thus, we may need to modify the rrect's start index to account for the sort.
467        bool swapX = rect.fLeft > rect.fRight;
468        bool swapY = rect.fTop > rect.fBottom;
469        if (swapX && swapY) {
470            // 0 becomes start index 2 and times 2 to convert from rect the rrect indices.
471            return 2 * 2;
472        } else if (swapX) {
473            *dir = SkPath::kCCW_Direction;
474            // 0 becomes start index 1 and times 2 to convert from rect the rrect indices.
475            return 2 * 1;
476        } else if (swapY) {
477            *dir = SkPath::kCCW_Direction;
478            // 0 becomes start index 3 and times 2 to convert from rect the rrect indices.
479            return 2 * 3;
480        }
481        return 0;
482    }
483
484    static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect,
485                                                 SkPath::Direction* dir) {
486        // This comes from SkPath's interface. The default for adding a SkRRect to a path is
487        // clockwise beginning at starting index 6.
488        static constexpr unsigned kPathRRectStartIdx = 6;
489        *dir = kDefaultRRectDir;
490        if (!hasPathEffect) {
491            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
492            return kDefaultRRectStart;
493        }
494        return kPathRRectStartIdx;
495    }
496
497    Type                        fType;
498    union {
499        struct {
500            SkRRect                     fRRect;
501            SkPath::Direction           fDir;
502            unsigned                    fStart;
503            bool                        fInverted;
504        } fRRectData;
505        struct {
506            SkPath                      fPath;
507            // Gen ID of the original path (fPath may be modified)
508            int32_t                     fGenID;
509        } fPathData;
510        struct {
511            SkPoint                     fPts[2];
512            bool                        fInverted;
513        } fLineData;
514    };
515    GrStyle                     fStyle;
516    SkTLazy<SkPath>             fInheritedPathForListeners;
517    SkAutoSTArray<8, uint32_t>  fInheritedKey;
518};
519#endif
520