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