blob: 1b9d36a005f1ea289e36e5e6bb3c9282603a8ba1 [file] [log] [blame]
/*
* 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/SkRRect.h"
#include "include/private/base/SkTemplates.h"
#include "src/base/SkTLazy.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/ganesh/GrStyle.h"
#include "src/gpu/ganesh/geometry/GrShape.h"
#include <new>
class SkIDChangeListener;
/**
* 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)
: fShape(rrect), fStyle(style) {
if (doSimplify == DoSimplify::kYes) {
this->simplify();
}
}
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 SkRect& oval, SkScalar startAngleDegrees,
SkScalar sweepAngleDegrees, bool useCenter, 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, SkPathDirection* dir, unsigned* start, 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. */
void asPath(SkPath* out) const {
fShape.asPath(out, 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/5421
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 negative value is returned if the shape has no key (shouldn't be cached).
*/
int 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.
*/
uint32_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).
int32_t fGenID = 0;
bool fClosed = false;
bool fSimplified = false;
SkTLazy<SkPath> fInheritedPathForListeners;
skia_private::AutoSTArray<8, uint32_t> fInheritedKey;
};
#endif