/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef GrGrCCStrokeGeometry_DEFINED #define GrGrCCStrokeGeometry_DEFINED #include "include/core/SkPaint.h" #include "include/core/SkPoint.h" #include "include/private/SkTArray.h" class SkStrokeRec; /** * This class converts device-space stroked paths into a set of independent strokes, joins, and caps * that map directly to coverage-counted GPU instances. Non-hairline strokes can only be drawn with * rigid body transforms; we don't yet support skewing the stroke lines themselves. */ class GrCCStrokeGeometry { public: static constexpr int kMaxNumLinearSegmentsLog2 = 15; GrCCStrokeGeometry(int numSkPoints = 0, int numSkVerbs = 0) : fVerbs(numSkVerbs * 5/2) // Reserve for a 2.5x expansion in verbs. (Joins get their // own separate verb in our representation.) , fParams(numSkVerbs * 3) // Somewhere around 1-2 params per verb. , fPoints(numSkPoints * 5/4) // Reserve for a 1.25x expansion in points and normals. , fNormals(numSkPoints * 5/4) {} // A string of verbs and their corresponding, params, points, and normals are a compact // representation of what will eventually be independent instances in GPU buffers. When added // up, the combined coverage of all these instances will make complete stroked paths. enum class Verb : uint8_t { kBeginPath, // Instructs the iterator to advance its stroke width, atlas offset, etc. // Independent strokes of a single line or curve, with (antialiased) butt caps on the ends. kLinearStroke, kQuadraticStroke, kCubicStroke, // Joins are a triangles that connect the outer corners of two adjoining strokes. Miters // have an additional triangle cap on top of the bevel, and round joins have an arc on top. kBevelJoin, kMiterJoin, kRoundJoin, // We use internal joins when we have to internally break up a stroke because its curvature // is too strong for a triangle strip. They are coverage-counted, self-intersecting // quadrilaterals that tie the four corners of two adjoining strokes together a like a // shoelace. (Coverage is negative on the inside half.) We place an arc on both ends of an // internal round join. kInternalBevelJoin, kInternalRoundJoin, kSquareCap, kRoundCap, kEndContour // Instructs the iterator to advance its internal point and normal ptrs. }; static bool IsInternalJoinVerb(Verb verb); // Some verbs require additional parameters(s). union Parameter { // For cubic and quadratic strokes: How many flat line segments to chop the curve into? int fNumLinearSegmentsLog2; // For miter and round joins: How tall should the triangle cap be on top of the join? // (This triangle is the conic control points for a round join.) float fMiterCapHeightOverWidth; float fConicWeight; // Round joins only. }; const SkTArray& verbs() const { SkASSERT(!fInsideContour); return fVerbs; } const SkTArray& params() const { SkASSERT(!fInsideContour); return fParams; } const SkTArray& points() const { SkASSERT(!fInsideContour); return fPoints; } const SkTArray& normals() const { SkASSERT(!fInsideContour); return fNormals; } // These track the numbers of instances required to draw all the recorded strokes. struct InstanceTallies { int fStrokes[kMaxNumLinearSegmentsLog2 + 1]; int fTriangles; int fConics; InstanceTallies operator+(const InstanceTallies&) const; }; void beginPath(const SkStrokeRec&, float strokeDevWidth, InstanceTallies*); void moveTo(SkPoint); void lineTo(SkPoint); void quadraticTo(const SkPoint[3]); void cubicTo(const SkPoint[4]); void closeContour(); // Connect back to the first point in the contour and exit. void capContourAndExit(); // Add endcaps (if any) and exit the contour. private: void lineTo(Verb leftJoinVerb, SkPoint); void quadraticTo(Verb leftJoinVerb, const SkPoint[3], float maxCurvatureT); static constexpr float kLeftMaxCurvatureNone = 1; static constexpr float kRightMaxCurvatureNone = 0; void cubicTo(Verb leftJoinVerb, const SkPoint[4], float maxCurvatureT, float leftMaxCurvatureT, float rightMaxCurvatureT); // Pushes a new normal to fNormals and records a join, without changing the current position. void rotateTo(Verb leftJoinVerb, SkVector normal); // Records a stroke in fElememts. void recordStroke(Verb, int numSegmentsLog2); // Records a join in fElememts with the previous stroke, if the cuurent contour is not empty. void recordLeftJoinIfNotEmpty(Verb joinType, SkVector nextNormal); void recordBevelJoin(Verb originalJoinVerb); void recordMiterJoin(float miterCapHeightOverWidth); void recordRoundJoin(Verb roundJoinVerb, float miterCapHeightOverWidth, float conicWeight); void recordCapsIfAny(); float fCurrStrokeRadius; Verb fCurrStrokeJoinVerb; SkPaint::Cap fCurrStrokeCapType; InstanceTallies* fCurrStrokeTallies = nullptr; // We implement miters by placing a triangle-shaped cap on top of a bevel join. This field tells // us what the miter limit is, restated in terms of how tall that triangle cap can be. float fMiterMaxCapHeightOverWidth; // Any curvature on the original curve gets magnified on the outer edge of the stroke, // proportional to how thick the stroke radius is. This field tells us the maximum curvature we // can tolerate using the current stroke radius, before linearization artifacts begin to appear // on the outer edge. // // (Curvature this strong is quite rare in practice, but when it does happen, we decompose the // section with strong curvature into lineTo's with round joins in between.) float fMaxCurvatureCosTheta; int fCurrContourFirstPtIdx; int fCurrContourFirstNormalIdx; SkDEBUGCODE(bool fInsideContour = false); SkSTArray<128, Verb, true> fVerbs; SkSTArray<128, Parameter, true> fParams; SkSTArray<128, SkPoint, true> fPoints; SkSTArray<128, SkVector, true> fNormals; }; inline GrCCStrokeGeometry::InstanceTallies GrCCStrokeGeometry::InstanceTallies::operator+( const InstanceTallies& t) const { InstanceTallies ret; for (int i = 0; i <= kMaxNumLinearSegmentsLog2; ++i) { ret.fStrokes[i] = fStrokes[i] + t.fStrokes[i]; } ret.fTriangles = fTriangles + t.fTriangles; ret.fConics = fConics + t.fConics; return ret; } inline bool GrCCStrokeGeometry::IsInternalJoinVerb(Verb verb) { switch (verb) { case Verb::kInternalBevelJoin: case Verb::kInternalRoundJoin: return true; case Verb::kBeginPath: case Verb::kLinearStroke: case Verb::kQuadraticStroke: case Verb::kCubicStroke: case Verb::kBevelJoin: case Verb::kMiterJoin: case Verb::kRoundJoin: case Verb::kSquareCap: case Verb::kRoundCap: case Verb::kEndContour: return false; } SK_ABORT("Invalid GrCCStrokeGeometry::Verb."); return false; } #endif