1 | /* |
2 | * Copyright 2018 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 GrGrCCStrokeGeometry_DEFINED |
9 | #define GrGrCCStrokeGeometry_DEFINED |
10 | |
11 | #include "include/core/SkPaint.h" |
12 | #include "include/core/SkPoint.h" |
13 | #include "include/private/SkTArray.h" |
14 | |
15 | class SkStrokeRec; |
16 | |
17 | /** |
18 | * This class converts device-space stroked paths into a set of independent strokes, joins, and caps |
19 | * that map directly to coverage-counted GPU instances. Non-hairline strokes can only be drawn with |
20 | * rigid body transforms; we don't yet support skewing the stroke lines themselves. |
21 | */ |
22 | class GrCCStrokeGeometry { |
23 | public: |
24 | static constexpr int kMaxNumLinearSegmentsLog2 = 15; |
25 | |
26 | GrCCStrokeGeometry(int numSkPoints = 0, int numSkVerbs = 0) |
27 | : fVerbs(numSkVerbs * 5/2) // Reserve for a 2.5x expansion in verbs. (Joins get their |
28 | // own separate verb in our representation.) |
29 | , fParams(numSkVerbs * 3) // Somewhere around 1-2 params per verb. |
30 | , fPoints(numSkPoints * 5/4) // Reserve for a 1.25x expansion in points and normals. |
31 | , fNormals(numSkPoints * 5/4) {} |
32 | |
33 | // A string of verbs and their corresponding, params, points, and normals are a compact |
34 | // representation of what will eventually be independent instances in GPU buffers. When added |
35 | // up, the combined coverage of all these instances will make complete stroked paths. |
36 | enum class Verb : uint8_t { |
37 | kBeginPath, // Instructs the iterator to advance its stroke width, atlas offset, etc. |
38 | |
39 | // Independent strokes of a single line or curve, with (antialiased) butt caps on the ends. |
40 | kLinearStroke, |
41 | kQuadraticStroke, |
42 | kCubicStroke, |
43 | |
44 | // Joins are a triangles that connect the outer corners of two adjoining strokes. Miters |
45 | // have an additional triangle cap on top of the bevel, and round joins have an arc on top. |
46 | kBevelJoin, |
47 | kMiterJoin, |
48 | kRoundJoin, |
49 | |
50 | // We use internal joins when we have to internally break up a stroke because its curvature |
51 | // is too strong for a triangle strip. They are coverage-counted, self-intersecting |
52 | // quadrilaterals that tie the four corners of two adjoining strokes together a like a |
53 | // shoelace. (Coverage is negative on the inside half.) We place an arc on both ends of an |
54 | // internal round join. |
55 | kInternalBevelJoin, |
56 | kInternalRoundJoin, |
57 | |
58 | kSquareCap, |
59 | kRoundCap, |
60 | |
61 | kEndContour // Instructs the iterator to advance its internal point and normal ptrs. |
62 | }; |
63 | static bool IsInternalJoinVerb(Verb verb); |
64 | |
65 | // Some verbs require additional parameters(s). |
66 | union Parameter { |
67 | // For cubic and quadratic strokes: How many flat line segments to chop the curve into? |
68 | int fNumLinearSegmentsLog2; |
69 | // For miter and round joins: How tall should the triangle cap be on top of the join? |
70 | // (This triangle is the conic control points for a round join.) |
71 | float fMiterCapHeightOverWidth; |
72 | float fConicWeight; // Round joins only. |
73 | }; |
74 | |
75 | const SkTArray<Verb, true>& verbs() const { SkASSERT(!fInsideContour); return fVerbs; } |
76 | const SkTArray<Parameter, true>& params() const { SkASSERT(!fInsideContour); return fParams; } |
77 | const SkTArray<SkPoint, true>& points() const { SkASSERT(!fInsideContour); return fPoints; } |
78 | const SkTArray<SkVector, true>& normals() const { SkASSERT(!fInsideContour); return fNormals; } |
79 | |
80 | // These track the numbers of instances required to draw all the recorded strokes. |
81 | struct InstanceTallies { |
82 | int fStrokes[kMaxNumLinearSegmentsLog2 + 1]; |
83 | int fTriangles; |
84 | int fConics; |
85 | |
86 | InstanceTallies operator+(const InstanceTallies&) const; |
87 | }; |
88 | |
89 | void beginPath(const SkStrokeRec&, float strokeDevWidth, InstanceTallies*); |
90 | void moveTo(SkPoint); |
91 | void lineTo(SkPoint); |
92 | void quadraticTo(const SkPoint[3]); |
93 | void cubicTo(const SkPoint[4]); |
94 | void closeContour(); // Connect back to the first point in the contour and exit. |
95 | void capContourAndExit(); // Add endcaps (if any) and exit the contour. |
96 | |
97 | private: |
98 | void lineTo(Verb leftJoinVerb, SkPoint); |
99 | void quadraticTo(Verb leftJoinVerb, const SkPoint[3], float maxCurvatureT); |
100 | |
101 | static constexpr float kLeftMaxCurvatureNone = 1; |
102 | static constexpr float kRightMaxCurvatureNone = 0; |
103 | void cubicTo(Verb leftJoinVerb, const SkPoint[4], float maxCurvatureT, float leftMaxCurvatureT, |
104 | float rightMaxCurvatureT); |
105 | |
106 | // Pushes a new normal to fNormals and records a join, without changing the current position. |
107 | void rotateTo(Verb leftJoinVerb, SkVector normal); |
108 | |
109 | // Records a stroke in fElememts. |
110 | void recordStroke(Verb, int numSegmentsLog2); |
111 | |
112 | // Records a join in fElememts with the previous stroke, if the cuurent contour is not empty. |
113 | void recordLeftJoinIfNotEmpty(Verb joinType, SkVector nextNormal); |
114 | void recordBevelJoin(Verb originalJoinVerb); |
115 | void recordMiterJoin(float miterCapHeightOverWidth); |
116 | void recordRoundJoin(Verb roundJoinVerb, float miterCapHeightOverWidth, float conicWeight); |
117 | |
118 | void recordCapsIfAny(); |
119 | |
120 | float fCurrStrokeRadius; |
121 | Verb fCurrStrokeJoinVerb; |
122 | SkPaint::Cap fCurrStrokeCapType; |
123 | InstanceTallies* fCurrStrokeTallies = nullptr; |
124 | |
125 | // We implement miters by placing a triangle-shaped cap on top of a bevel join. This field tells |
126 | // us what the miter limit is, restated in terms of how tall that triangle cap can be. |
127 | float fMiterMaxCapHeightOverWidth; |
128 | |
129 | // Any curvature on the original curve gets magnified on the outer edge of the stroke, |
130 | // proportional to how thick the stroke radius is. This field tells us the maximum curvature we |
131 | // can tolerate using the current stroke radius, before linearization artifacts begin to appear |
132 | // on the outer edge. |
133 | // |
134 | // (Curvature this strong is quite rare in practice, but when it does happen, we decompose the |
135 | // section with strong curvature into lineTo's with round joins in between.) |
136 | float fMaxCurvatureCosTheta; |
137 | |
138 | int fCurrContourFirstPtIdx; |
139 | int fCurrContourFirstNormalIdx; |
140 | |
141 | SkDEBUGCODE(bool fInsideContour = false); |
142 | |
143 | SkSTArray<128, Verb, true> fVerbs; |
144 | SkSTArray<128, Parameter, true> fParams; |
145 | SkSTArray<128, SkPoint, true> fPoints; |
146 | SkSTArray<128, SkVector, true> fNormals; |
147 | }; |
148 | |
149 | inline GrCCStrokeGeometry::InstanceTallies GrCCStrokeGeometry::InstanceTallies::operator+( |
150 | const InstanceTallies& t) const { |
151 | InstanceTallies ret; |
152 | for (int i = 0; i <= kMaxNumLinearSegmentsLog2; ++i) { |
153 | ret.fStrokes[i] = fStrokes[i] + t.fStrokes[i]; |
154 | } |
155 | ret.fTriangles = fTriangles + t.fTriangles; |
156 | ret.fConics = fConics + t.fConics; |
157 | return ret; |
158 | } |
159 | |
160 | inline bool GrCCStrokeGeometry::IsInternalJoinVerb(Verb verb) { |
161 | switch (verb) { |
162 | case Verb::kInternalBevelJoin: |
163 | case Verb::kInternalRoundJoin: |
164 | return true; |
165 | case Verb::kBeginPath: |
166 | case Verb::kLinearStroke: |
167 | case Verb::kQuadraticStroke: |
168 | case Verb::kCubicStroke: |
169 | case Verb::kBevelJoin: |
170 | case Verb::kMiterJoin: |
171 | case Verb::kRoundJoin: |
172 | case Verb::kSquareCap: |
173 | case Verb::kRoundCap: |
174 | case Verb::kEndContour: |
175 | return false; |
176 | } |
177 | SK_ABORT("Invalid GrCCStrokeGeometry::Verb." ); |
178 | } |
179 | #endif |
180 | |