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