• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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 #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
9 
10 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
11 #include "src/gpu/tessellate/Tessellation.h"
12 #include "src/gpu/tessellate/WangsFormula.h"
13 
14 using skgpu::PatchAttribs;
15 
16 namespace {
17 
18 // Converts keywords from shared SkSL strings to native GLSL keywords.
19 constexpr static char kSkSLTypeDefs[] = R"(
20 #define float4x3 mat4x3
21 #define float4x2 mat4x2
22 #define float3x2 mat3x2
23 #define float2x2 mat2
24 #define float2 vec2
25 #define float3 vec3
26 #define float4 vec4
27 )";
28 
29 // Uses GPU tessellation shaders to linearize, triangulate, and render cubic "wedge" patches. A
30 // wedge is a 5-point patch consisting of 4 cubic control points, plus an anchor point fanning from
31 // the center of the curve's resident contour.
32 // TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
33 class HardwareWedgeShader : public GrPathTessellationShader {
34 public:
HardwareWedgeShader(const SkMatrix & viewMatrix,const SkPMColor4f & color,PatchAttribs attribs)35     HardwareWedgeShader(const SkMatrix& viewMatrix,
36                         const SkPMColor4f& color,
37                         PatchAttribs attribs)
38             : GrPathTessellationShader(kTessellate_HardwareWedgeShader_ClassID,
39                                        GrPrimitiveType::kPatches, 5, viewMatrix, color, attribs) {
40         constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
41                                                      kFloat2_GrSLType};
42         this->setVertexAttributes(&kInputPointAttrib, 1);
43         SkASSERT(this->vertexStride() * 5 ==
44                  sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
45     }
46 
maxTessellationSegments(const GrShaderCaps & shaderCaps) const47     int maxTessellationSegments(const GrShaderCaps& shaderCaps) const override {
48         return shaderCaps.maxTessellationSegments();
49     }
50 
51 private:
name() const52     const char* name() const final { return "tessellate_HardwareWedgeShader"; }
addToKey(const GrShaderCaps &,GrProcessorKeyBuilder *) const53     void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
54     std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const final;
55 };
56 
makeProgramImpl(const GrShaderCaps &) const57 std::unique_ptr<GrGeometryProcessor::ProgramImpl> HardwareWedgeShader::makeProgramImpl(
58         const GrShaderCaps&) const {
59     class Impl : public GrPathTessellationShader::Impl {
60         void emitVertexCode(const GrShaderCaps&,
61                             const GrPathTessellationShader&,
62                             GrGLSLVertexBuilder* v,
63                             GrGLSLVaryingHandler*,
64                             GrGPArgs*) override {
65             v->declareGlobal(GrShaderVar("vsPt", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
66             v->codeAppend(R"(
67             // If y is infinity then x is a conic weight. Don't transform.
68             vsPt = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
69         }
70         SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
71                                           const char* versionAndExtensionDecls,
72                                           const GrGLSLUniformHandler&,
73                                           const GrShaderCaps& shaderCaps) const override {
74             SkString code(versionAndExtensionDecls);
75             code.appendf(R"(
76             #define MAX_TESSELLATION_SEGMENTS %i)", shaderCaps.maxTessellationSegments());
77             code.appendf(R"(
78             #define PRECISION %f)", skgpu::kTessellationPrecision);
79             code.append(kSkSLTypeDefs);
80             code.append(skgpu::wangs_formula::as_sksl());
81 
82             code.append(R"(
83             layout(vertices = 1) out;
84 
85             in vec2 vsPt[];
86             patch out mat4x2 rationalCubicXY;
87             patch out float rationalCubicW;
88             patch out vec2 fanpoint;
89 
90             void main() {
91                 mat4x2 P = mat4x2(vsPt[0], vsPt[1], vsPt[2], vsPt[3]);
92                 float numSegments;
93                 if (isinf(P[3].y)) {
94                     // This is a conic.
95                     float w = P[3].x;
96                     numSegments = wangs_formula_conic(PRECISION, P[0], P[1], P[2], w);
97                     // Convert to a rational cubic in projected form.
98                     rationalCubicXY = mat4x2(P[0],
99                                              mix(vec4(P[0], P[2]), (P[1] * w).xyxy, 2.0/3.0),
100                                              P[2]);
101                     rationalCubicW = fma(w, 2.0/3.0, 1.0/3.0);
102                 } else {
103                     // This is a cubic.
104                     numSegments = wangs_formula_cubic(PRECISION, P[0], P[1], P[2], P[3], mat2(1));
105                     rationalCubicXY = P;
106                     rationalCubicW = 1;
107                 }
108                 fanpoint = vsPt[4];
109 
110                 // Tessellate the first side of the patch into numSegments triangles.
111                 gl_TessLevelOuter[0] = min(numSegments, MAX_TESSELLATION_SEGMENTS);
112 
113                 // Leave the other two sides of the patch as single segments.
114                 gl_TessLevelOuter[1] = 1.0;
115                 gl_TessLevelOuter[2] = 1.0;
116 
117                 // Changing the inner level to 1 when numSegments == 1 collapses the entire
118                 // patch to a single triangle. Otherwise, we need an inner level of 2 so our curve
119                 // triangles have an interior point to originate from.
120                 gl_TessLevelInner[0] = min(numSegments, 2.0);
121             })");
122 
123             return code;
124         }
125         SkString getTessEvaluationShaderGLSL(const GrGeometryProcessor&,
126                                              const char* versionAndExtensionDecls,
127                                              const GrGLSLUniformHandler&,
128                                              const GrShaderCaps&) const override {
129             SkString code(versionAndExtensionDecls);
130             code.append(kSkSLTypeDefs);
131             code.append(kEvalRationalCubicFn);
132             code.append(R"(
133             layout(triangles, equal_spacing, ccw) in;
134 
135             uniform vec4 sk_RTAdjust;
136 
137             patch in mat4x2 rationalCubicXY;
138             patch in float rationalCubicW;
139             patch in vec2 fanpoint;
140 
141             void main() {
142                 // Locate our parametric point of interest. It is equal to the barycentric
143                 // y-coordinate if we are a vertex on the tessellated edge of the triangle patch,
144                 // 0.5 if we are the patch's interior vertex, or N/A if we are the fan point.
145                 // NOTE: We are on the tessellated edge when the barycentric x-coordinate == 0.
146                 float T = (gl_TessCoord.x == 0.0) ? gl_TessCoord.y : 0.5;
147 
148                 mat4x3 P = mat4x3(rationalCubicXY[0], 1,
149                                   rationalCubicXY[1], rationalCubicW,
150                                   rationalCubicXY[2], rationalCubicW,
151                                   rationalCubicXY[3], 1);
152                 vec2 vertexpos = eval_rational_cubic(P, T);
153 
154                 if (gl_TessCoord.x == 1.0) {
155                     // We are the anchor point that fans from the center of the curve's contour.
156                     vertexpos = fanpoint;
157                 } else if (gl_TessCoord.x != 0.0) {
158                     // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
159                     vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
160                 }
161 
162                 gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
163             })");
164 
165             return code;
166         }
167     };
168     return std::make_unique<Impl>();
169 }
170 
171 // Uses GPU tessellation shaders to linearize, triangulate, and render standalone closed cubics.
172 // TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
173 class HardwareCurveShader : public GrPathTessellationShader {
174 public:
HardwareCurveShader(const SkMatrix & viewMatrix,const SkPMColor4f & color,PatchAttribs attribs)175     HardwareCurveShader(const SkMatrix& viewMatrix,
176                         const SkPMColor4f& color,
177                         PatchAttribs attribs)
178             : GrPathTessellationShader(kTessellate_HardwareCurveShader_ClassID,
179                                        GrPrimitiveType::kPatches, 4, viewMatrix, color,
180                                        attribs) {
181         constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
182                                                      kFloat2_GrSLType};
183         this->setVertexAttributes(&kInputPointAttrib, 1);
184         SkASSERT(this->vertexStride() * 4 ==
185                  sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
186     }
187 
maxTessellationSegments(const GrShaderCaps & shaderCaps) const188     int maxTessellationSegments(const GrShaderCaps& shaderCaps) const override {
189         // This shader tessellates T=0..(1/2) on the first side of the canonical triangle and
190         // T=(1/2)..1 on the second side. This means we get double the max tessellation segments for
191         // the range T=0..1.
192         return shaderCaps.maxTessellationSegments() * 2;
193     }
194 
195 private:
name() const196     const char* name() const final { return "tessellate_HardwareCurveShader"; }
addToKey(const GrShaderCaps &,GrProcessorKeyBuilder *) const197     void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
198     std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const final;
199 };
200 
makeProgramImpl(const GrShaderCaps &) const201 std::unique_ptr<GrGeometryProcessor::ProgramImpl> HardwareCurveShader::makeProgramImpl(
202         const GrShaderCaps&) const {
203     class Impl : public GrPathTessellationShader::Impl {
204         void emitVertexCode(const GrShaderCaps&,
205                             const GrPathTessellationShader&,
206                             GrGLSLVertexBuilder* v,
207                             GrGLSLVaryingHandler*,
208                             GrGPArgs*) override {
209             v->declareGlobal(GrShaderVar("P", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
210             v->codeAppend(R"(
211             // If y is infinity then x is a conic weight. Don't transform.
212             P = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
213         }
214         SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
215                                           const char* versionAndExtensionDecls,
216                                           const GrGLSLUniformHandler&,
217                                           const GrShaderCaps& shaderCaps) const override {
218             SkString code(versionAndExtensionDecls);
219             code.appendf(R"(
220             #define MAX_TESSELLATION_SEGMENTS %i)", shaderCaps.maxTessellationSegments());
221             code.appendf(R"(
222             #define PRECISION %f)", skgpu::kTessellationPrecision);
223             code.append(kSkSLTypeDefs);
224             code.append(skgpu::wangs_formula::as_sksl());
225             code.append(R"(
226             layout(vertices = 1) out;
227 
228             in vec2 P[];
229             patch out mat4x2 rationalCubicXY;
230             patch out float rationalCubicW;
231 
232             void main() {
233                 float w = -1;  // w<0 means a cubic.
234                 vec2 p1w = P[1];
235                 if (isinf(P[3].y)) {
236                     // This patch is actually a conic. Project to homogeneous space.
237                     w = P[3].x;
238                     p1w *= w;
239                 }
240 
241                 // Chop the curve at T=1/2.
242                 vec2 ab = (P[0] + p1w) * .5;
243                 vec2 bc = (p1w + P[2]) * .5;
244                 vec2 cd = (P[2] + P[3]) * .5;
245                 vec2 abc = (ab + bc) * .5;
246                 vec2 bcd = (bc + cd) * .5;
247                 vec2 abcd = (abc + bcd) * .5;
248 
249                 float n0, n1;
250                 if (w < 0 || isinf(w)) {
251                     if (w < 0) {
252                         // The patch is a cubic. Calculate how many segments are required to
253                         // linearize each half of the curve.
254                         n0 = wangs_formula_cubic(PRECISION, P[0], ab, abc, abcd, mat2(1));
255                         n1 = wangs_formula_cubic(PRECISION, abcd, bcd, cd, P[3], mat2(1));
256                         rationalCubicW = 1;
257                     } else {
258                         // The patch is a triangle (a conic with infinite weight).
259                         n0 = n1 = 1;
260                         rationalCubicW = -1;  // In the next stage, rationalCubicW<0 means triangle.
261                     }
262                     rationalCubicXY = mat4x2(P[0], P[1], P[2], P[3]);
263                 } else {
264                     // The patch is a conic. Unproject p0..5. w1 == w2 == w3 when chopping at .5.
265                     // (See SkConic::chopAt().)
266                     float r = 2.0 / (1.0 + w);
267                     ab *= r, bc *= r, abc *= r;
268                     // Put in "standard form" where w0 == w2 == w4 == 1.
269                     float w_ = inversesqrt(r);  // Both halves have the same w' when chopping at .5.
270                     // Calculate how many segments are needed to linearize each half of the curve.
271                     n0 = wangs_formula_conic(PRECISION, P[0], ab, abc, w_);
272                     n1 = wangs_formula_conic(PRECISION, abc, bc, P[2], w_);
273                     // Covert the conic to a rational cubic in projected form.
274                     rationalCubicXY = mat4x2(P[0],
275                                              mix(float4(P[0],P[2]), p1w.xyxy, 2.0/3.0),
276                                              P[2]);
277                     rationalCubicW = fma(w, 2.0/3.0, 1.0/3.0);
278                 }
279 
280                 gl_TessLevelOuter[0] = min(n1, MAX_TESSELLATION_SEGMENTS);
281                 gl_TessLevelOuter[1] = 1.0;
282                 gl_TessLevelOuter[2] = min(n0, MAX_TESSELLATION_SEGMENTS);
283 
284                 // Changing the inner level to 1 when n0 == n1 == 1 collapses the entire patch to a
285                 // single triangle. Otherwise, we need an inner level of 2 so our curve triangles
286                 // have an interior point to originate from.
287                 gl_TessLevelInner[0] = min(max(n0, n1), 2.0);
288             })");
289 
290             return code;
291         }
292         SkString getTessEvaluationShaderGLSL(const GrGeometryProcessor&,
293                                              const char* versionAndExtensionDecls,
294                                              const GrGLSLUniformHandler&,
295                                              const GrShaderCaps&) const override {
296             SkString code(versionAndExtensionDecls);
297             code.append(kSkSLTypeDefs);
298             code.append(kEvalRationalCubicFn);
299             code.append(R"(
300             layout(triangles, equal_spacing, ccw) in;
301 
302             uniform vec4 sk_RTAdjust;
303 
304             patch in mat4x2 rationalCubicXY;
305             patch in float rationalCubicW;
306 
307             void main() {
308                 vec2 vertexpos;
309                 if (rationalCubicW < 0) {  // rationalCubicW < 0 means a triangle now.
310                     vertexpos = (gl_TessCoord.x != 0) ? rationalCubicXY[0]
311                               : (gl_TessCoord.y != 0) ? rationalCubicXY[1]
312                                                       : rationalCubicXY[2];
313                 } else {
314                     // Locate our parametric point of interest. T ramps from [0..1/2] on the left
315                     // edge of the triangle, and [1/2..1] on the right. If we are the patch's
316                     // interior vertex, then we want T=1/2. Since the barycentric coords are
317                     // (1/3, 1/3, 1/3) at the interior vertex, the below fma() works in all 3
318                     // scenarios.
319                     float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
320 
321                     mat4x3 P = mat4x3(rationalCubicXY[0], 1,
322                                       rationalCubicXY[1], rationalCubicW,
323                                       rationalCubicXY[2], rationalCubicW,
324                                       rationalCubicXY[3], 1);
325                     vertexpos = eval_rational_cubic(P, T);
326                     if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
327                         // We are the interior point of the patch; center it inside
328                         // [C(0), C(.5), C(1)].
329                         vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
330                     }
331                 }
332 
333                 gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
334             })");
335 
336             return code;
337         }
338     };
339     return std::make_unique<Impl>();
340 }
341 
342 }  // namespace
343 
MakeHardwareTessellationShader(SkArenaAlloc * arena,const SkMatrix & viewMatrix,const SkPMColor4f & color,PatchAttribs attribs)344 GrPathTessellationShader* GrPathTessellationShader::MakeHardwareTessellationShader(
345         SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color,
346         PatchAttribs attribs) {
347     SkASSERT(!(attribs & PatchAttribs::kColor));  // Not yet implemented.
348     SkASSERT(!(attribs & PatchAttribs::kExplicitCurveType));  // Not yet implemented.
349     if (attribs & PatchAttribs::kFanPoint) {
350         return arena->make<HardwareWedgeShader>(viewMatrix, color, attribs);
351     } else {
352         return arena->make<HardwareCurveShader>(viewMatrix, color, attribs);
353     }
354 }
355