| /* | |
| * Copyright 2016 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #ifndef GrStyledShape_DEFINED | |
| #define GrStyledShape_DEFINED | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathTypes.h" | |
| #include "include/core/SkRRect.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/private/SkAssert.h" | |
| #include "include/private/SkTemplates.h" | |
| #include "src/core/SkPathEnums.h" | |
| #include "src/core/SkPathPriv.h" | |
| #include "src/gpu/ganesh/GrStyle.h" | |
| #include "src/gpu/ganesh/geometry/GrShape.h" | |
| #include <cstdint> | |
| #include <optional> | |
| struct SkArc; | |
| class SkIDChangeListener; | |
| class SkPaint; | |
| struct SkPoint; | |
| /** | |
| * Represents a geometric shape (rrect or path) and the GrStyle that it should be rendered with. | |
| * It is possible to apply the style to the GrStyledShape to produce a new GrStyledShape where the | |
| * geometry reflects the styling information (e.g. is stroked). It is also possible to apply just | |
| * the path effect from the style. In this case the resulting shape will include any remaining | |
| * stroking information that is to be applied after the path effect. | |
| * | |
| * Shapes can produce keys that represent only the geometry information, not the style. Note that | |
| * when styling information is applied to produce a new shape then the style has been converted | |
| * to geometric information and is included in the new shape's key. When the same style is applied | |
| * to two shapes that reflect the same underlying geometry the computed keys of the stylized shapes | |
| * will be the same. | |
| * | |
| * Currently this can only be constructed from a path, rect, or rrect though it can become a path | |
| * applying style to the geometry. The idea is to expand this to cover most or all of the geometries | |
| * that have fast paths in the GPU backend. | |
| */ | |
| class GrStyledShape { | |
| public: | |
| // Keys for paths may be extracted from the path data for small paths. Clients aren't supposed | |
| // to have to worry about this. This value is exposed for unit tests. | |
| inline static constexpr int kMaxKeyFromDataVerbCnt = 10; | |
| GrStyledShape() {} | |
| enum class DoSimplify : bool { kNo = false, kYes }; | |
| explicit GrStyledShape(const SkPath& path, DoSimplify doSimplify = DoSimplify::kYes) | |
| : GrStyledShape(path, GrStyle::SimpleFill(), doSimplify) {} | |
| explicit GrStyledShape(const SkRRect& rrect, DoSimplify doSimplify = DoSimplify::kYes) | |
| : GrStyledShape(rrect, GrStyle::SimpleFill(), doSimplify) {} | |
| explicit GrStyledShape(const SkRect& rect, DoSimplify doSimplify = DoSimplify::kYes) | |
| : GrStyledShape(rect, GrStyle::SimpleFill(), doSimplify) {} | |
| GrStyledShape(const SkPath& path, const SkPaint& paint, | |
| DoSimplify doSimplify = DoSimplify::kYes) | |
| : GrStyledShape(path, GrStyle(paint), doSimplify) {} | |
| GrStyledShape(const SkRRect& rrect, const SkPaint& paint, | |
| DoSimplify doSimplify = DoSimplify::kYes) | |
| : GrStyledShape(rrect, GrStyle(paint), doSimplify) {} | |
| GrStyledShape(const SkRect& rect, const SkPaint& paint, | |
| DoSimplify doSimplify = DoSimplify::kYes) | |
| : GrStyledShape(rect, GrStyle(paint), doSimplify) {} | |
| GrStyledShape(const SkPath& path, const GrStyle& style, | |
| DoSimplify doSimplify = DoSimplify::kYes) | |
| : fShape(path), fStyle(style) { | |
| if (doSimplify == DoSimplify::kYes) { | |
| this->simplify(); | |
| } | |
| } | |
| GrStyledShape(const SkRRect& rrect, const GrStyle& style, | |
| DoSimplify doSimplify = DoSimplify::kYes) | |
| // Preserve legacy indices (6 for CW), see SkPathBuilder::addRRect(). | |
| : GrStyledShape(rrect, SkPathDirection::kCW, 6, false, style, doSimplify) {} | |
| GrStyledShape(const SkRRect& rrect, SkPathDirection dir, unsigned start, bool inverted, | |
| const GrStyle& style, DoSimplify doSimplify = DoSimplify::kYes) | |
| : fShape(rrect) | |
| , fStyle(style) { | |
| fShape.setPathWindingParams(dir, start); | |
| fShape.setInverted(inverted); | |
| if (doSimplify == DoSimplify::kYes) { | |
| this->simplify(); | |
| } | |
| } | |
| GrStyledShape(const SkRect& rect, const GrStyle& style, | |
| DoSimplify doSimplify = DoSimplify::kYes) | |
| : fShape(rect), fStyle(style) { | |
| if (doSimplify == DoSimplify::kYes) { | |
| this->simplify(); | |
| } | |
| } | |
| GrStyledShape(const GrStyledShape&); | |
| static GrStyledShape MakeArc(const SkArc& arc, | |
| const GrStyle& style, | |
| DoSimplify = DoSimplify::kYes); | |
| GrStyledShape& operator=(const GrStyledShape& that); | |
| /** | |
| * Informs MakeFilled on how to modify that shape's fill rule when making a simple filled | |
| * version of the shape. | |
| */ | |
| enum class FillInversion { | |
| kPreserve, | |
| kFlip, | |
| kForceNoninverted, | |
| kForceInverted | |
| }; | |
| /** | |
| * Makes a filled shape from the pre-styled original shape and optionally modifies whether | |
| * the fill is inverted or not. It's important to note that the original shape's geometry | |
| * may already have been modified if doing so was neutral with respect to its style | |
| * (e.g. filled paths are always closed when stored in a shape and dashed paths are always | |
| * made non-inverted since dashing ignores inverseness). | |
| */ | |
| static GrStyledShape MakeFilled(const GrStyledShape& original, | |
| FillInversion = FillInversion::kPreserve); | |
| const GrStyle& style() const { return fStyle; } | |
| // True if the shape and/or style were modified into a simpler, equivalent pairing | |
| bool simplified() const { return fSimplified; } | |
| /** | |
| * Returns a shape that has either applied the path effect or path effect and stroking | |
| * information from this shape's style to its geometry. Scale is used when approximating the | |
| * output geometry and typically is computed from the view matrix | |
| */ | |
| GrStyledShape applyStyle(GrStyle::Apply apply, SkScalar scale) const { | |
| return GrStyledShape(*this, apply, scale); | |
| } | |
| bool isRect() const { | |
| // Should have simplified a rrect to a rect if possible already. | |
| SkASSERT(!fShape.isRRect() || !fShape.rrect().isRect()); | |
| return fShape.isRect(); | |
| } | |
| /** Returns the unstyled geometry as a rrect if possible. */ | |
| bool asRRect(SkRRect* rrect, bool* inverted) const; | |
| /** | |
| * If the unstyled shape is a straight line segment, returns true and sets pts to the endpoints. | |
| * An inverse filled line path is still considered a line. | |
| */ | |
| bool asLine(SkPoint pts[2], bool* inverted) const; | |
| // Can this shape be drawn as a pair of filled nested rectangles? | |
| bool asNestedRects(SkRect rects[2]) const; | |
| /** Returns the unstyled geometry as a path. */ | |
| SkPath asPath() const { | |
| return fShape.asPath(fStyle.isSimpleFill()); | |
| } | |
| /** | |
| * Returns whether the geometry is empty. Note that applying the style could produce a | |
| * non-empty shape. It also may have an inverse fill. | |
| */ | |
| bool isEmpty() const { return fShape.isEmpty(); } | |
| /** | |
| * Gets the bounds of the geometry without reflecting the shape's styling. This ignores | |
| * the inverse fill nature of the geometry. | |
| */ | |
| SkRect bounds() const { return fShape.bounds(); } | |
| /** | |
| * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill | |
| * status). | |
| */ | |
| SkRect styledBounds() const; | |
| /** | |
| * Is this shape known to be convex, before styling is applied. An unclosed but otherwise | |
| * convex path is considered to be closed if they styling reflects a fill and not otherwise. | |
| * This is because filling closes all contours in the path. | |
| */ | |
| bool knownToBeConvex() const { | |
| return fShape.convex(fStyle.isSimpleFill()); | |
| } | |
| /** | |
| * Does the shape have a known winding direction. Some degenerate convex shapes may not have | |
| * a computable direction, but this is not always a requirement for path renderers so it is | |
| * kept separate from knownToBeConvex(). | |
| */ | |
| bool knownDirection() const { | |
| // Assuming this is called after knownToBeConvex(), this should just be relying on | |
| // cached convexity and direction and will be cheap. | |
| return !fShape.isPath() || | |
| SkPathPriv::ComputeFirstDirection(fShape.path()) != SkPathFirstDirection::kUnknown; | |
| } | |
| /** Is the pre-styled geometry inverse filled? */ | |
| bool inverseFilled() const { | |
| // Since the path tracks inverted-fillness itself, it should match what was recorded. | |
| SkASSERT(!fShape.isPath() || fShape.inverted() == fShape.path().isInverseFillType()); | |
| // Dashing ignores inverseness. We should have caught this earlier. skbug.com/40036591 | |
| SkASSERT(!(fShape.inverted() && this->style().isDashed())); | |
| return fShape.inverted(); | |
| } | |
| /** | |
| * Might applying the styling to the geometry produce an inverse fill. The "may" part comes in | |
| * because an arbitrary path effect could produce an inverse filled path. In other cases this | |
| * can be thought of as "inverseFilledAfterStyling()". | |
| */ | |
| bool mayBeInverseFilledAfterStyling() const { | |
| // An arbitrary path effect can produce an arbitrary output path, which may be inverse | |
| // filled. | |
| if (this->style().hasNonDashPathEffect()) { | |
| return true; | |
| } | |
| return this->inverseFilled(); | |
| } | |
| /** | |
| * Is it known that the unstyled geometry has no unclosed contours. This means that it will | |
| * not have any caps if stroked (modulo the effect of any path effect). | |
| */ | |
| bool knownToBeClosed() const { | |
| // This refers to the base shape and does not depend on invertedness. | |
| return fShape.closed(); | |
| } | |
| uint32_t segmentMask() const { | |
| // This refers to the base shape and does not depend on invertedness. | |
| return fShape.segmentMask(); | |
| } | |
| /** | |
| * Gets the size of the key for the shape represented by this GrStyledShape (ignoring its | |
| * styling). A zero value is returned if the shape has no key (shouldn't be cached). | |
| */ | |
| uint16_t unstyledKeySize() const; | |
| bool hasUnstyledKey() const { return this->unstyledKeySize() > 0; } | |
| /** | |
| * Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough | |
| * space allocated for the key and that unstyledKeySize() does not return a negative value | |
| * for this shape. | |
| */ | |
| void writeUnstyledKey(uint32_t* key) const; | |
| /** | |
| * Adds a listener to the *original* path. Typically used to invalidate cached resources when | |
| * a path is no longer in-use. If the shape started out as something other than a path, this | |
| * does nothing. | |
| */ | |
| void addGenIDChangeListener(sk_sp<SkIDChangeListener>) const; | |
| /** | |
| * Helpers that are only exposed for unit tests, to determine if the shape is a path, and get | |
| * the generation ID of the *original* path. This is the path that will receive | |
| * GenIDChangeListeners added to this shape. | |
| */ | |
| uint64_t testingOnly_getOriginalGenerationID() const; | |
| bool testingOnly_isPath() const; | |
| bool testingOnly_isNonVolatilePath() const; | |
| /** | |
| * Similar to GrShape::simplify but also takes into account style and stroking, possibly | |
| * applying the style explicitly to produce a new analytic shape with a simpler style. | |
| * Unless "doSimplify" is kNo, this method gets called automatically during construction. | |
| */ | |
| void simplify(); | |
| private: | |
| /** Constructor used by the applyStyle() function */ | |
| GrStyledShape(const GrStyledShape& parentShape, GrStyle::Apply, SkScalar scale); | |
| /** | |
| * Determines the key we should inherit from the input shape's geometry and style when | |
| * we are applying the style to create a new shape. | |
| */ | |
| void setInheritedKey(const GrStyledShape& parentShape, GrStyle::Apply, SkScalar scale); | |
| /** | |
| * As part of the simplification process, some shapes can have stroking trivially evaluated | |
| * and form a new geometry with just a fill. | |
| */ | |
| void simplifyStroke(); | |
| /** Gets the path that gen id listeners should be added to. */ | |
| const SkPath* originalPathForListeners() const; | |
| GrShape fShape; | |
| GrStyle fStyle; | |
| // Gen ID of the original path (path may be modified or simplified away). | |
| uint64_t fGenID = 0; | |
| bool fClosed = false; | |
| bool fSimplified = false; | |
| std::optional<SkPath> fInheritedPathForListeners; | |
| skia_private::AutoSTArray<8, uint32_t> fInheritedKey; | |
| }; | |
| #endif |