• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include <new>
18 
19 /**
20  * Represents a geometric shape (rrect or path) and the GrStyle that it should be rendered with.
21  * It is possible to apply the style to the GrShape to produce a new GrShape where the geometry
22  * reflects the styling information (e.g. is stroked). It is also possible to apply just the
23  * path effect from the style. In this case the resulting shape will include any remaining
24  * stroking information that is to be applied after the path effect.
25  *
26  * Shapes can produce keys that represent only the geometry information, not the style. Note that
27  * when styling information is applied to produce a new shape then the style has been converted
28  * to geometric information and is included in the new shape's key. When the same style is applied
29  * to two shapes that reflect the same underlying geometry the computed keys of the stylized shapes
30  * will be the same.
31  *
32  * Currently this can only be constructed from a path, rect, or rrect though it can become a path
33  * applying style to the geometry. The idea is to expand this to cover most or all of the geometries
34  * that have fast paths in the GPU backend.
35  */
36 class GrShape {
37 public:
38     // Keys for paths may be extracted from the path data for small paths. Clients aren't supposed
39     // to have to worry about this. This value is exposed for unit tests.
40     static constexpr int kMaxKeyFromDataVerbCnt = 10;
41 
GrShape()42     GrShape() { this->initType(Type::kEmpty); }
43 
GrShape(const SkPath & path)44     explicit GrShape(const SkPath& path) : GrShape(path, GrStyle::SimpleFill()) {}
45 
GrShape(const SkRRect & rrect)46     explicit GrShape(const SkRRect& rrect) : GrShape(rrect, GrStyle::SimpleFill()) {}
47 
GrShape(const SkRect & rect)48     explicit GrShape(const SkRect& rect) : GrShape(rect, GrStyle::SimpleFill()) {}
49 
GrShape(const SkPath & path,const GrStyle & style)50     GrShape(const SkPath& path, const GrStyle& style) : fStyle(style) {
51         this->initType(Type::kPath, &path);
52         this->attemptToSimplifyPath();
53     }
54 
GrShape(const SkRRect & rrect,const GrStyle & style)55     GrShape(const SkRRect& rrect, const GrStyle& style) : 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 
GrShape(const SkRRect & rrect,SkPath::Direction dir,unsigned start,bool inverted,const GrStyle & style)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 
GrShape(const SkRect & rect,const GrStyle & style)84     GrShape(const SkRect& rect, const GrStyle& style) : fStyle(style) {
85         this->initType(Type::kRRect);
86         fRRectData.fRRect = SkRRect::MakeRect(rect);
87         fRRectData.fInverted = false;
88         fRRectData.fStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(),
89                                                         &fRRectData.fDir);
90         this->attemptToSimplifyRRect();
91     }
92 
GrShape(const SkPath & path,const SkPaint & paint)93     GrShape(const SkPath& path, const SkPaint& paint) : fStyle(paint) {
94         this->initType(Type::kPath, &path);
95         this->attemptToSimplifyPath();
96     }
97 
GrShape(const SkRRect & rrect,const SkPaint & paint)98     GrShape(const SkRRect& rrect, const SkPaint& paint) : fStyle(paint) {
99         this->initType(Type::kRRect);
100         fRRectData.fRRect = rrect;
101         fRRectData.fInverted = false;
102         fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(),
103                                                          &fRRectData.fDir);
104         this->attemptToSimplifyRRect();
105     }
106 
GrShape(const SkRect & rect,const SkPaint & paint)107     GrShape(const SkRect& rect, const SkPaint& paint) : fStyle(paint) {
108         this->initType(Type::kRRect);
109         fRRectData.fRRect = SkRRect::MakeRect(rect);
110         fRRectData.fInverted = false;
111         fRRectData.fStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(),
112                                                         &fRRectData.fDir);
113         this->attemptToSimplifyRRect();
114     }
115 
116     static GrShape MakeArc(const SkRect& oval, SkScalar startAngleDegrees,
117                            SkScalar sweepAngleDegrees, bool useCenter, const GrStyle& style);
118 
119     GrShape(const GrShape&);
120     GrShape& operator=(const GrShape& that);
121 
~GrShape()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 
style()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      */
applyStyle(GrStyle::Apply apply,SkScalar scale)150     GrShape applyStyle(GrStyle::Apply apply, SkScalar scale) const {
151         return GrShape(*this, apply, scale);
152     }
153 
isRect()154     bool isRect() const {
155         if (Type::kRRect != fType) {
156             return false;
157         }
158 
159         return fRRectData.fRRect.isRect();
160     }
161 
162     /** Returns the unstyled geometry as a rrect if possible. */
asRRect(SkRRect * rrect,SkPath::Direction * dir,unsigned * start,bool * inverted)163     bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start, bool* inverted) const {
164         if (Type::kRRect != fType) {
165             return false;
166         }
167         if (rrect) {
168             *rrect = fRRectData.fRRect;
169         }
170         if (dir) {
171             *dir = fRRectData.fDir;
172         }
173         if (start) {
174             *start = fRRectData.fStart;
175         }
176         if (inverted) {
177             *inverted = fRRectData.fInverted;
178         }
179         return true;
180     }
181 
182     /**
183      * If the unstyled shape is a straight line segment, returns true and sets pts to the endpoints.
184      * An inverse filled line path is still considered a line.
185      */
asLine(SkPoint pts[2],bool * inverted)186     bool asLine(SkPoint pts[2], bool* inverted) const {
187         if (fType != Type::kLine) {
188             return false;
189         }
190         if (pts) {
191             pts[0] = fLineData.fPts[0];
192             pts[1] = fLineData.fPts[1];
193         }
194         if (inverted) {
195             *inverted = fLineData.fInverted;
196         }
197         return true;
198     }
199 
200     /** Returns the unstyled geometry as a path. */
asPath(SkPath * out)201     void asPath(SkPath* out) const {
202         switch (fType) {
203             case Type::kEmpty:
204                 out->reset();
205                 break;
206             case Type::kInvertedEmpty:
207                 out->reset();
208                 out->setFillType(kDefaultPathInverseFillType);
209                 break;
210             case Type::kRRect:
211                 out->reset();
212                 out->addRRect(fRRectData.fRRect, fRRectData.fDir, fRRectData.fStart);
213                 // Below matches the fill type that attemptToSimplifyPath uses.
214                 if (fRRectData.fInverted) {
215                     out->setFillType(kDefaultPathInverseFillType);
216                 } else {
217                     out->setFillType(kDefaultPathFillType);
218                 }
219                 break;
220             case Type::kArc:
221                 SkPathPriv::CreateDrawArcPath(out, fArcData.fOval, fArcData.fStartAngleDegrees,
222                                               fArcData.fSweepAngleDegrees, fArcData.fUseCenter,
223                                               fStyle.isSimpleFill());
224                 if (fArcData.fInverted) {
225                     out->setFillType(kDefaultPathInverseFillType);
226                 } else {
227                     out->setFillType(kDefaultPathFillType);
228                 }
229                 break;
230             case Type::kLine:
231                 out->reset();
232                 out->moveTo(fLineData.fPts[0]);
233                 out->lineTo(fLineData.fPts[1]);
234                 if (fLineData.fInverted) {
235                     out->setFillType(kDefaultPathInverseFillType);
236                 } else {
237                     out->setFillType(kDefaultPathFillType);
238                 }
239                 break;
240             case Type::kPath:
241                 *out = this->path();
242                 break;
243         }
244     }
245 
246     // Can this shape be drawn as a pair of filled nested rectangles?
asNestedRects(SkRect rects[2])247     bool asNestedRects(SkRect rects[2]) const {
248         if (Type::kPath != fType) {
249             return false;
250         }
251 
252         // TODO: it would be better two store DRRects natively in the shape rather than converting
253         // them to a path and then reextracting the nested rects
254         if (this->path().isInverseFillType()) {
255             return false;
256         }
257 
258         SkPath::Direction dirs[2];
259         if (!this->path().isNestedFillRects(rects, dirs)) {
260             return false;
261         }
262 
263         if (SkPath::kWinding_FillType == this->path().getFillType() && dirs[0] == dirs[1]) {
264             // The two rects need to be wound opposite to each other
265             return false;
266         }
267 
268         // Right now, nested rects where the margin is not the same width
269         // all around do not render correctly
270         const SkScalar* outer = rects[0].asScalars();
271         const SkScalar* inner = rects[1].asScalars();
272 
273         bool allEq = true;
274 
275         SkScalar margin = SkScalarAbs(outer[0] - inner[0]);
276         bool allGoE1 = margin >= SK_Scalar1;
277 
278         for (int i = 1; i < 4; ++i) {
279             SkScalar temp = SkScalarAbs(outer[i] - inner[i]);
280             if (temp < SK_Scalar1) {
281                 allGoE1 = false;
282             }
283             if (!SkScalarNearlyEqual(margin, temp)) {
284                 allEq = false;
285             }
286         }
287 
288         return allEq || allGoE1;
289     }
290 
291     /**
292      * Returns whether the geometry is empty. Note that applying the style could produce a
293      * non-empty shape. It also may have an inverse fill.
294      */
isEmpty()295     bool isEmpty() const { return Type::kEmpty == fType || Type::kInvertedEmpty == fType; }
296 
297     /**
298      * Gets the bounds of the geometry without reflecting the shape's styling. This ignores
299      * the inverse fill nature of the geometry.
300      */
301     SkRect bounds() const;
302 
303     /**
304      * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill
305      * status).
306      */
307     SkRect styledBounds() const;
308 
309     /**
310      * Is this shape known to be convex, before styling is applied. An unclosed but otherwise
311      * convex path is considered to be closed if they styling reflects a fill and not otherwise.
312      * This is because filling closes all contours in the path.
313      */
knownToBeConvex()314     bool knownToBeConvex() const {
315         switch (fType) {
316             case Type::kEmpty:
317                 return true;
318             case Type::kInvertedEmpty:
319                 return true;
320             case Type::kRRect:
321                 return true;
322             case Type::kArc:
323                 return SkPathPriv::DrawArcIsConvex(fArcData.fSweepAngleDegrees,
324                                                    SkToBool(fArcData.fUseCenter),
325                                                    fStyle.isSimpleFill());
326             case Type::kLine:
327                 return true;
328             case Type::kPath:
329                 // SkPath.isConvex() really means "is this path convex were it to be closed" and
330                 // thus doesn't give the correct answer for stroked paths, hence we also check
331                 // whether the path is either filled or closed. Convex paths may only have one
332                 // contour hence isLastContourClosed() is a sufficient for a convex path.
333                 return (this->style().isSimpleFill() || this->path().isLastContourClosed()) &&
334                         this->path().isConvex();
335         }
336         return false;
337     }
338 
339     /** Is the pre-styled geometry inverse filled? */
inverseFilled()340     bool inverseFilled() const {
341         bool ret = false;
342         switch (fType) {
343             case Type::kEmpty:
344                 ret = false;
345                 break;
346             case Type::kInvertedEmpty:
347                 ret = true;
348                 break;
349             case Type::kRRect:
350                 ret = fRRectData.fInverted;
351                 break;
352             case Type::kArc:
353                 ret = fArcData.fInverted;
354                 break;
355             case Type::kLine:
356                 ret = fLineData.fInverted;
357                 break;
358             case Type::kPath:
359                 ret = this->path().isInverseFillType();
360                 break;
361         }
362         // Dashing ignores inverseness. We should have caught this earlier. skbug.com/5421
363         SkASSERT(!(ret && this->style().isDashed()));
364         return ret;
365     }
366 
367     /**
368      * Might applying the styling to the geometry produce an inverse fill. The "may" part comes in
369      * because an arbitrary path effect could produce an inverse filled path. In other cases this
370      * can be thought of as "inverseFilledAfterStyling()".
371      */
mayBeInverseFilledAfterStyling()372     bool mayBeInverseFilledAfterStyling() const {
373          // An arbitrary path effect can produce an arbitrary output path, which may be inverse
374          // filled.
375         if (this->style().hasNonDashPathEffect()) {
376             return true;
377         }
378         return this->inverseFilled();
379     }
380 
381     /**
382      * Is it known that the unstyled geometry has no unclosed contours. This means that it will
383      * not have any caps if stroked (modulo the effect of any path effect).
384      */
knownToBeClosed()385     bool knownToBeClosed() const {
386         switch (fType) {
387             case Type::kEmpty:
388                 return true;
389             case Type::kInvertedEmpty:
390                 return true;
391             case Type::kRRect:
392                 return true;
393             case Type::kArc:
394                 return fArcData.fUseCenter;
395             case Type::kLine:
396                 return false;
397             case Type::kPath:
398                 // SkPath doesn't keep track of the closed status of each contour.
399                 return SkPathPriv::IsClosedSingleContour(this->path());
400         }
401         return false;
402     }
403 
segmentMask()404     uint32_t segmentMask() const {
405         switch (fType) {
406             case Type::kEmpty:
407                 return 0;
408             case Type::kInvertedEmpty:
409                 return 0;
410             case Type::kRRect:
411                 if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
412                     return SkPath::kConic_SegmentMask;
413                 } else if (fRRectData.fRRect.getType() == SkRRect::kRect_Type ||
414                            fRRectData.fRRect.getType() == SkRRect::kEmpty_Type) {
415                     return SkPath::kLine_SegmentMask;
416                 }
417                 return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask;
418             case Type::kArc:
419                 if (fArcData.fUseCenter) {
420                     return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
421                 }
422                 return SkPath::kConic_SegmentMask;
423             case Type::kLine:
424                 return SkPath::kLine_SegmentMask;
425             case Type::kPath:
426                 return this->path().getSegmentMasks();
427         }
428         return 0;
429     }
430 
431     /**
432      * Gets the size of the key for the shape represented by this GrShape (ignoring its styling).
433      * A negative value is returned if the shape has no key (shouldn't be cached).
434      */
435     int unstyledKeySize() const;
436 
hasUnstyledKey()437     bool hasUnstyledKey() const { return this->unstyledKeySize() >= 0; }
438 
439     /**
440      * Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough
441      * space allocated for the key and that unstyledKeySize() does not return a negative value
442      * for this shape.
443      */
444     void writeUnstyledKey(uint32_t* key) const;
445 
446     /**
447      * Adds a listener to the *original* path. Typically used to invalidate cached resources when
448      * a path is no longer in-use. If the shape started out as something other than a path, this
449      * does nothing.
450      */
451     void addGenIDChangeListener(sk_sp<SkPathRef::GenIDChangeListener>) const;
452 
453     /**
454      * Helpers that are only exposed for unit tests, to determine if the shape is a path, and get
455      * the generation ID of the *original* path. This is the path that will receive
456      * GenIDChangeListeners added to this shape.
457      */
458     uint32_t testingOnly_getOriginalGenerationID() const;
459     bool testingOnly_isPath() const;
460     bool testingOnly_isNonVolatilePath() const;
461 
462 private:
463     enum class Type {
464         kEmpty,
465         kInvertedEmpty,
466         kRRect,
467         kArc,
468         kLine,
469         kPath,
470     };
471 
472     void initType(Type type, const SkPath* path = nullptr) {
473         fType = Type::kEmpty;
474         this->changeType(type, path);
475     }
476 
477     void changeType(Type type, const SkPath* path = nullptr) {
478         bool wasPath = Type::kPath == fType;
479         fType = type;
480         bool isPath = Type::kPath == type;
481         SkASSERT(!path || isPath);
482         if (wasPath && !isPath) {
483             fPathData.fPath.~SkPath();
484         } else if (!wasPath && isPath) {
485             if (path) {
486                 new (&fPathData.fPath) SkPath(*path);
487             } else {
488                 new (&fPathData.fPath) SkPath();
489             }
490         } else if (isPath && path) {
491             fPathData.fPath = *path;
492         }
493         // Whether or not we use the path's gen ID is decided in attemptToSimplifyPath.
494         fPathData.fGenID = 0;
495     }
496 
path()497     SkPath& path() {
498         SkASSERT(Type::kPath == fType);
499         return fPathData.fPath;
500     }
501 
path()502     const SkPath& path() const {
503         SkASSERT(Type::kPath == fType);
504         return fPathData.fPath;
505     }
506 
507     /** Constructor used by the applyStyle() function */
508     GrShape(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
509 
510     /**
511      * Determines the key we should inherit from the input shape's geometry and style when
512      * we are applying the style to create a new shape.
513      */
514     void setInheritedKey(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
515 
516     void attemptToSimplifyPath();
517     void attemptToSimplifyRRect();
518     void attemptToSimplifyLine();
519     void attemptToSimplifyArc();
520 
521     bool attemptToSimplifyStrokedLineToRRect();
522 
523     /** Gets the path that gen id listeners should be added to. */
524     const SkPath* originalPathForListeners() const;
525 
526     // Defaults to use when there is no distinction between even/odd and winding fills.
527     static constexpr SkPath::FillType kDefaultPathFillType = SkPath::kEvenOdd_FillType;
528     static constexpr SkPath::FillType kDefaultPathInverseFillType =
529             SkPath::kInverseEvenOdd_FillType;
530 
531     static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction;
532     static constexpr unsigned kDefaultRRectStart = 0;
533 
DefaultRectDirAndStartIndex(const SkRect & rect,bool hasPathEffect,SkPath::Direction * dir)534     static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect,
535                                                 SkPath::Direction* dir) {
536         *dir = kDefaultRRectDir;
537         // This comes from SkPath's interface. The default for adding a SkRect is counter clockwise
538         // beginning at index 0 (which happens to correspond to rrect index 0 or 7).
539         if (!hasPathEffect) {
540             // It doesn't matter what start we use, just be consistent to avoid redundant keys.
541             return kDefaultRRectStart;
542         }
543         // In SkPath a rect starts at index 0 by default. This is the top left corner. However,
544         // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the
545         // rect edges. Thus, we may need to modify the rrect's start index to account for the sort.
546         bool swapX = rect.fLeft > rect.fRight;
547         bool swapY = rect.fTop > rect.fBottom;
548         if (swapX && swapY) {
549             // 0 becomes start index 2 and times 2 to convert from rect the rrect indices.
550             return 2 * 2;
551         } else if (swapX) {
552             *dir = SkPath::kCCW_Direction;
553             // 0 becomes start index 1 and times 2 to convert from rect the rrect indices.
554             return 2 * 1;
555         } else if (swapY) {
556             *dir = SkPath::kCCW_Direction;
557             // 0 becomes start index 3 and times 2 to convert from rect the rrect indices.
558             return 2 * 3;
559         }
560         return 0;
561     }
562 
DefaultRRectDirAndStartIndex(const SkRRect & rrect,bool hasPathEffect,SkPath::Direction * dir)563     static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect,
564                                                  SkPath::Direction* dir) {
565         // This comes from SkPath's interface. The default for adding a SkRRect to a path is
566         // clockwise beginning at starting index 6.
567         static constexpr unsigned kPathRRectStartIdx = 6;
568         *dir = kDefaultRRectDir;
569         if (!hasPathEffect) {
570             // It doesn't matter what start we use, just be consistent to avoid redundant keys.
571             return kDefaultRRectStart;
572         }
573         return kPathRRectStartIdx;
574     }
575 
576     union {
577         struct {
578             SkRRect fRRect;
579             SkPath::Direction fDir;
580             unsigned fStart;
581             bool fInverted;
582         } fRRectData;
583         struct {
584             SkRect fOval;
585             SkScalar fStartAngleDegrees;
586             SkScalar fSweepAngleDegrees;
587             int16_t fUseCenter;
588             int16_t fInverted;
589         } fArcData;
590         struct {
591             SkPath fPath;
592             // Gen ID of the original path (fPath may be modified)
593             int32_t fGenID;
594         } fPathData;
595         struct {
596             SkPoint fPts[2];
597             bool fInverted;
598         } fLineData;
599     };
600     GrStyle fStyle;
601     SkTLazy<SkPath> fInheritedPathForListeners;
602     SkAutoSTArray<8, uint32_t>  fInheritedKey;
603     Type fType;
604 };
605 #endif
606