• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 Google LLC.
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 GrStrokeTessellateShader_DEFINED
9 #define GrStrokeTessellateShader_DEFINED
10 
11 #include "src/gpu/tessellate/GrPathShader.h"
12 
13 #include "include/core/SkStrokeRec.h"
14 #include "src/gpu/GrVx.h"
15 #include "src/gpu/tessellate/GrTessellationPathRenderer.h"
16 #include <array>
17 
18 class GrGLSLUniformHandler;
19 
20 // Tessellates a batch of stroke patches directly to the canvas. Tessellated stroking works by
21 // creating stroke-width, orthogonal edges at set locations along the curve and then connecting them
22 // with a quad strip. These orthogonal edges come from two different sets: "parametric edges" and
23 // "radial edges". Parametric edges are spaced evenly in the parametric sense, and radial edges
24 // divide the curve's _rotation_ into even steps. The tessellation shader evaluates both sets of
25 // edges and sorts them into a single quad strip. With this combined set of edges we can stroke any
26 // curve, regardless of curvature.
27 class GrStrokeTessellateShader : public GrPathShader {
28 public:
29     // Are we using hardware tessellation or indirect draws?
30     enum class Mode {
31         kHardwareTessellation,
32         kLog2Indirect,
33         kFixedCount
34     };
35 
36     enum class ShaderFlags {
37         kNone          = 0,
38         kHasConics     = 1 << 0,
39         kWideColor     = 1 << 1,
40         kDynamicStroke = 1 << 2,  // Each patch or instance has its own stroke width and join type.
41         kDynamicColor  = 1 << 3,  // Each patch or instance has its own color.
42     };
43 
44     GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(ShaderFlags);
45 
46     // Returns the fixed number of edges that are always emitted with the given join type. If the
47     // join is round, the caller needs to account for the additional radial edges on their own.
48     // Specifically, each join always emits:
49     //
50     //   * Two colocated edges at the beginning (a full-width edge to seam with the preceding stroke
51     //     and a half-width edge to begin the join).
52     //
53     //   * An extra edge in the middle for miter joins, or else a variable number of radial edges
54     //     for round joins (the caller is responsible for counting radial edges from round joins).
55     //
56     //   * A half-width edge at the end of the join that will be colocated with the first
57     //     (full-width) edge of the stroke.
58     //
NumFixedEdgesInJoin(SkPaint::Join joinType)59     constexpr static int NumFixedEdgesInJoin(SkPaint::Join joinType) {
60         switch (joinType) {
61             case SkPaint::kMiter_Join:
62                 return 4;
63             case SkPaint::kRound_Join:
64                 // The caller is responsible for counting the variable number of middle, radial
65                 // segments on round joins.
66                 [[fallthrough]];
67             case SkPaint::kBevel_Join:
68                 return 3;
69         }
70         SkUNREACHABLE;
71     }
72 
73     // We encode all of a join's information in a single float value:
74     //
75     //     Negative => Round Join
76     //     Zero     => Bevel Join
77     //     Positive => Miter join, and the value is also the miter limit
78     //
GetJoinType(const SkStrokeRec & stroke)79     static float GetJoinType(const SkStrokeRec& stroke) {
80         switch (stroke.getJoin()) {
81             case SkPaint::kRound_Join: return -1;
82             case SkPaint::kBevel_Join: return 0;
83             case SkPaint::kMiter_Join: SkASSERT(stroke.getMiter() >= 0); return stroke.getMiter();
84         }
85         SkUNREACHABLE;
86     }
87 
88     // This struct gets written out to each patch or instance if kDynamicStroke is enabled.
89     struct DynamicStroke {
StrokesHaveEqualDynamicStateDynamicStroke90         static bool StrokesHaveEqualDynamicState(const SkStrokeRec& a, const SkStrokeRec& b) {
91             return a.getWidth() == b.getWidth() && a.getJoin() == b.getJoin() &&
92                    (a.getJoin() != SkPaint::kMiter_Join || a.getMiter() == b.getMiter());
93         }
setDynamicStroke94         void set(const SkStrokeRec& stroke) {
95             fRadius = stroke.getWidth() * .5f;
96             fJoinType = GetJoinType(stroke);
97         }
98         float fRadius;
99         float fJoinType;  // See GetJoinType().
100     };
101 
102     // 'viewMatrix' is applied to the geometry post tessellation. It cannot have perspective.
GrStrokeTessellateShader(Mode mode,ShaderFlags shaderFlags,const SkMatrix & viewMatrix,const SkStrokeRec & stroke,SkPMColor4f color)103     GrStrokeTessellateShader(Mode mode, ShaderFlags shaderFlags, const SkMatrix& viewMatrix,
104                              const SkStrokeRec& stroke, SkPMColor4f color)
105             : GrPathShader(kTessellate_GrStrokeTessellateShader_ClassID, viewMatrix,
106                            (mode == Mode::kHardwareTessellation) ?
107                                    GrPrimitiveType::kPatches : GrPrimitiveType::kTriangleStrip,
108                            (mode == Mode::kHardwareTessellation) ? 1 : 0)
109             , fMode(mode)
110             , fShaderFlags(shaderFlags)
111             , fStroke(stroke)
112             , fColor(color) {
113         if (fMode == Mode::kHardwareTessellation) {
114             // A join calculates its starting angle using prevCtrlPtAttr.
115             fAttribs.emplace_back("prevCtrlPtAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
116             // pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
117             // with w=p3.x.
118             //
119             // If p0 == prevCtrlPtAttr, then no join is emitted.
120             //
121             // pts=[p0, p3, p3, p3] is a reserved pattern that means this patch is a join only,
122             // whose start and end tangents are (p0 - inputPrevCtrlPt) and (p3 - p0).
123             //
124             // pts=[p0, p0, p0, p3] is a reserved pattern that means this patch is a "bowtie", or
125             // double-sided round join, anchored on p0 and rotating from (p0 - prevCtrlPtAttr) to
126             // (p3 - p0).
127             fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
128             fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
129         } else {
130             // pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
131             // with w=p3.x.
132             //
133             // An empty stroke (p0==p1==p2==p3) is a special case that denotes a circle, or
134             // 180-degree point stroke.
135             fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
136             fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
137             if (fMode == Mode::kLog2Indirect) {
138                 // argsAttr.xy contains the lastControlPoint for setting up the join.
139                 //
140                 // "argsAttr.z=numTotalEdges" tells the shader the literal number of edges in the
141                 // triangle strip being rendered (i.e., it should be vertexCount/2). If
142                 // numTotalEdges is negative and the join type is "kRound", it also instructs the
143                 // shader to only allocate one segment the preceding round join.
144                 fAttribs.emplace_back("argsAttr", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
145             } else {
146                 SkASSERT(fMode == Mode::kFixedCount);
147                 // argsAttr contains the lastControlPoint for setting up the join.
148                 fAttribs.emplace_back("argsAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
149             }
150         }
151         if (fShaderFlags & ShaderFlags::kDynamicStroke) {
152             fAttribs.emplace_back("dynamicStrokeAttr", kFloat2_GrVertexAttribType,
153                                   kFloat2_GrSLType);
154         }
155         if (fShaderFlags & ShaderFlags::kDynamicColor) {
156             fAttribs.emplace_back("dynamicColorAttr",
157                                   (fShaderFlags & ShaderFlags::kWideColor)
158                                           ? kFloat4_GrVertexAttribType
159                                           : kUByte4_norm_GrVertexAttribType,
160                                   kHalf4_GrSLType);
161         }
162         if (fMode == Mode::kHardwareTessellation) {
163             this->setVertexAttributes(fAttribs.data(), fAttribs.count());
164         } else {
165             this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
166         }
167         SkASSERT(fAttribs.count() <= kMaxAttribCount);
168     }
169 
hasConics()170     bool hasConics() const { return fShaderFlags & ShaderFlags::kHasConics; }
hasDynamicStroke()171     bool hasDynamicStroke() const { return fShaderFlags & ShaderFlags::kDynamicStroke; }
hasDynamicColor()172     bool hasDynamicColor() const { return fShaderFlags & ShaderFlags::kDynamicColor; }
173 
174     // Used by GrFixedCountTessellator to configure the uniform value that tells the shader how many
175     // total edges are in the triangle strip.
setFixedCountNumTotalEdges(int value)176     void setFixedCountNumTotalEdges(int value) {
177         SkASSERT(fMode == Mode::kFixedCount);
178         fFixedCountNumTotalEdges = value;
179     }
180 
181 private:
name()182     const char* name() const override { return "GrStrokeTessellateShader"; }
183     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override;
184     GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
185 
186     const Mode fMode;
187     const ShaderFlags fShaderFlags;
188     const SkStrokeRec fStroke;
189     const SkPMColor4f fColor;
190 
191     constexpr static int kMaxAttribCount = 5;
192     SkSTArray<kMaxAttribCount, Attribute> fAttribs;
193 
194     // This is a uniform value used when fMode is kFixedCount that tells the shader how many total
195     // edges are in the triangle strip.
196     float fFixedCountNumTotalEdges = 0;
197 
198     class TessellationImpl;
199     class InstancedImpl;
200 };
201 
202 GR_MAKE_BITFIELD_CLASS_OPS(GrStrokeTessellateShader::ShaderFlags);
203 
204 #endif
205