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/GrStencilPathShader.h"
9
10#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
11#include "src/gpu/glsl/GrGLSLVarying.h"
12#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
13
14// Wang's formula for cubics (1985) gives us the number of evenly spaced (in the
15// parametric sense) line segments that are guaranteed to be within a distance of
16// "MAX_LINEARIZATION_ERROR" from the actual curve.
17constexpr char kWangsFormulaCubicFn[] = R"(
18 #define MAX_LINEARIZATION_ERROR 0.25 // 1/4 pixel
19 float wangs_formula_cubic(vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
20 float k = (3.0 * 2.0) / (8.0 * MAX_LINEARIZATION_ERROR);
21 float f = sqrt(k * length(max(abs(p2 - p1*2.0 + p0),
22 abs(p3 - p2*2.0 + p1))));
23 return max(1.0, ceil(f));
24 })";
25
26// Evaluate our point of interest using numerically stable mix() operations.
27constexpr char kEvalCubicFn[] = R"(
28 vec2 eval_cubic(mat4x2 P, float T) {
29 vec2 ab = mix(P[0], P[1], T);
30 vec2 bc = mix(P[1], P[2], T);
31 vec2 cd = mix(P[2], P[3], T);
32 vec2 abc = mix(ab, bc, T);
33 vec2 bcd = mix(bc, cd, T);
34 return mix(abc, bcd, T);
35 })";
36
37class GrStencilPathShader::Impl : public GrGLSLGeometryProcessor {
38 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
39 const auto& shader = args.fGP.cast<GrStencilPathShader>();
40 args.fVaryingHandler->emitAttributes(shader);
41
42 GrShaderVar vertexPos = (*shader.vertexAttributes().begin()).asShaderVar();
43 if (!shader.viewMatrix().isIdentity()) {
44 const char* viewMatrix;
45 fViewMatrixUniform = args.fUniformHandler->addUniform(
46 nullptr, kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
47 args.fVertBuilder->codeAppendf(
48 "float2 vertexpos = (%s * float3(point, 1)).xy;", viewMatrix);
49 vertexPos.set(kFloat2_GrSLType, "vertexpos");
50 }
51
52 if (!shader.willUseTessellationShaders()) {
53 gpArgs->fPositionVar = vertexPos;
54 } else {
55 args.fVertBuilder->declareGlobal(GrShaderVar(
56 "P", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
57 args.fVertBuilder->codeAppendf("P = %s;", vertexPos.c_str());
58 }
59
60 // No fragment shader.
61 }
62
63 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
64 const CoordTransformRange& transformRange) override {
65 const auto& shader = primProc.cast<GrStencilPathShader>();
66 if (!shader.viewMatrix().isIdentity()) {
67 pdman.setSkMatrix(fViewMatrixUniform, shader.viewMatrix());
68 }
69 }
70
71 GrGLSLUniformHandler::UniformHandle fViewMatrixUniform;
72};
73
74GrGLSLPrimitiveProcessor* GrStencilPathShader::createGLSLInstance(const GrShaderCaps&) const {
75 return new Impl;
76}
77
78SkString GrStencilCubicShader::getTessControlShaderGLSL(const char* versionAndExtensionDecls,
79 const GrShaderCaps&) const {
80 SkString code(versionAndExtensionDecls);
81 code.append(kWangsFormulaCubicFn);
82 code.append(R"(
83 layout(vertices = 1) out;
84
85 in vec2 P[];
86 out vec4 X[];
87 out vec4 Y[];
88
89 void main() {
90 // Chop the curve at T=1/2.
91 vec2 ab = mix(P[0], P[1], .5);
92 vec2 bc = mix(P[1], P[2], .5);
93 vec2 cd = mix(P[2], P[3], .5);
94 vec2 abc = mix(ab, bc, .5);
95 vec2 bcd = mix(bc, cd, .5);
96 vec2 abcd = mix(abc, bcd, .5);
97
98 // Calculate how many triangles we need to linearize each half of the curve.
99 float l0 = wangs_formula_cubic(P[0], ab, abc, abcd);
100 float l1 = wangs_formula_cubic(abcd, bcd, cd, P[3]);
101
102 gl_TessLevelOuter[0] = l1;
103 gl_TessLevelOuter[1] = 1.0;
104 gl_TessLevelOuter[2] = l0;
105
106 // Changing the inner level to 1 when l0 == l1 == 1 collapses the entire patch to a
107 // single triangle. Otherwise, we need an inner level of 2 so our curve triangles
108 // have an interior point to originate from.
109 gl_TessLevelInner[0] = min(max(l0, l1), 2.0);
110
111 X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x);
112 Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y);
113 })");
114
115 return code;
116}
117
118SkString GrStencilCubicShader::getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls,
119 const GrShaderCaps&) const {
120 SkString code(versionAndExtensionDecls);
121 code.append(kEvalCubicFn);
122 code.append(R"(
123 layout(triangles, equal_spacing, ccw) in;
124
125 uniform vec4 sk_RTAdjust;
126
127 in vec4 X[];
128 in vec4 Y[];
129
130 void main() {
131 // Locate our parametric point of interest. T ramps from [0..1/2] on the left edge
132 // of the triangle, and [1/2..1] on the right. If we are the patch's interior
133 // vertex, then we want T=1/2. Since the barycentric coords are (1/3, 1/3, 1/3) at
134 // the interior vertex, the below fma() works in all 3 scenarios.
135 float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
136
137 mat4x2 P = transpose(mat2x4(X[0], Y[0]));
138 vec2 vertexpos = eval_cubic(P, T);
139 if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
140 // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
141 vertexpos = (P[0] + vertexpos + P[3]) / 3.0;
142 }
143
144 gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
145 })");
146
147 return code;
148}
149
150SkString GrStencilWedgeShader::getTessControlShaderGLSL(const char* versionAndExtensionDecls,
151 const GrShaderCaps&) const {
152 SkString code(versionAndExtensionDecls);
153 code.append(kWangsFormulaCubicFn);
154 code.append(R"(
155 layout(vertices = 1) out;
156
157 in vec2 P[];
158 out vec4 X[];
159 out vec4 Y[];
160 out vec2 fanpoint[];
161
162 void main() {
163 // Calculate how many triangles we need to linearize the curve.
164 float num_segments = wangs_formula_cubic(P[0], P[1], P[2], P[3]);
165
166 // Tessellate the first side of the patch into num_segments triangles.
167 gl_TessLevelOuter[0] = num_segments;
168
169 // Leave the other two sides of the patch as single segments.
170 gl_TessLevelOuter[1] = 1.0;
171 gl_TessLevelOuter[2] = 1.0;
172
173 // Changing the inner level to 1 when num_segments == 1 collapses the entire
174 // patch to a single triangle. Otherwise, we need an inner level of 2 so our curve
175 // triangles have an interior point to originate from.
176 gl_TessLevelInner[0] = min(num_segments, 2.0);
177
178 X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x);
179 Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y);
180 fanpoint[gl_InvocationID /*== 0*/] = P[4];
181 })");
182
183 return code;
184}
185
186SkString GrStencilWedgeShader::getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls,
187 const GrShaderCaps&) const {
188 SkString code(versionAndExtensionDecls);
189 code.append(kEvalCubicFn);
190 code.append(R"(
191 layout(triangles, equal_spacing, ccw) in;
192
193 uniform vec4 sk_RTAdjust;
194
195 in vec4 X[];
196 in vec4 Y[];
197 in vec2 fanpoint[];
198
199 void main() {
200 // Locate our parametric point of interest. It is equal to the barycentric
201 // y-coordinate if we are a vertex on the tessellated edge of the triangle patch,
202 // 0.5 if we are the patch's interior vertex, or N/A if we are the fan point.
203 // NOTE: We are on the tessellated edge when the barycentric x-coordinate == 0.
204 float T = (gl_TessCoord.x == 0.0) ? gl_TessCoord.y : 0.5;
205
206 mat4x2 P = transpose(mat2x4(X[0], Y[0]));
207 vec2 vertexpos = eval_cubic(P, T);
208 if (gl_TessCoord.x == 1.0) {
209 // We are the anchor point that fans from the center of the curve's contour.
210 vertexpos = fanpoint[0];
211 } else if (gl_TessCoord.x != 0.0) {
212 // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
213 vertexpos = (P[0] + vertexpos + P[3]) / 3.0;
214 }
215
216 gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
217 })");
218
219 return code;
220}
221