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