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
15class 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 */
22class GrCCStrokeGeometry {
23public:
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
97private:
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
149inline 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
160inline 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