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 static 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 static 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 {
38protected:
39 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
40 const auto& shader = args.fGP.cast<GrStencilPathShader>();
41 args.fVaryingHandler->emitAttributes(shader);
42
43 GrShaderVar vertexPos = (*shader.vertexAttributes().begin()).asShaderVar();
44 if (!shader.viewMatrix().isIdentity()) {
45 const char* viewMatrix;
46 fViewMatrixUniform = args.fUniformHandler->addUniform(
47 nullptr, kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
48 args.fVertBuilder->codeAppendf(
49 "float2 vertexpos = (%s * float3(inputPoint, 1)).xy;", viewMatrix);
50 vertexPos.set(kFloat2_GrSLType, "vertexpos");
51 }
52
53 if (!shader.willUseTessellationShaders()) {
54 gpArgs->fPositionVar = vertexPos;
55 } else {
56 args.fVertBuilder->declareGlobal(GrShaderVar(
57 "P", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
58 args.fVertBuilder->codeAppendf("P = %s;", vertexPos.c_str());
59 }
60
61 // No fragment shader.
62 }
63
64 void setData(const GrGLSLProgramDataManager& pdman,
65 const GrPrimitiveProcessor& primProc) override {
66 const auto& shader = primProc.cast<GrStencilPathShader>();
67 if (!shader.viewMatrix().isIdentity()) {
68 pdman.setSkMatrix(fViewMatrixUniform, shader.viewMatrix());
69 }
70 }
71
72 GrGLSLUniformHandler::UniformHandle fViewMatrixUniform;
73};
74
75GrGLSLPrimitiveProcessor* GrStencilPathShader::createGLSLInstance(const GrShaderCaps&) const {
76 return new Impl;
77}
78
79SkString GrCubicTessellateShader::getTessControlShaderGLSL(const GrGLSLPrimitiveProcessor*,
80 const char* versionAndExtensionDecls,
81 const GrGLSLUniformHandler&,
82 const GrShaderCaps&) const {
83 SkString code(versionAndExtensionDecls);
84 code.append(kWangsFormulaCubicFn);
85 code.append(R"(
86 layout(vertices = 1) out;
87
88 in vec2 P[];
89 out vec4 X[];
90 out vec4 Y[];
91
92 void main() {
93 // Chop the curve at T=1/2.
94 vec2 ab = mix(P[0], P[1], .5);
95 vec2 bc = mix(P[1], P[2], .5);
96 vec2 cd = mix(P[2], P[3], .5);
97 vec2 abc = mix(ab, bc, .5);
98 vec2 bcd = mix(bc, cd, .5);
99 vec2 abcd = mix(abc, bcd, .5);
100
101 // Calculate how many triangles we need to linearize each half of the curve.
102 float l0 = wangs_formula_cubic(P[0], ab, abc, abcd);
103 float l1 = wangs_formula_cubic(abcd, bcd, cd, P[3]);
104
105 gl_TessLevelOuter[0] = l1;
106 gl_TessLevelOuter[1] = 1.0;
107 gl_TessLevelOuter[2] = l0;
108
109 // Changing the inner level to 1 when l0 == l1 == 1 collapses the entire patch to a
110 // single triangle. Otherwise, we need an inner level of 2 so our curve triangles
111 // have an interior point to originate from.
112 gl_TessLevelInner[0] = min(max(l0, l1), 2.0);
113
114 X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x);
115 Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y);
116 })");
117
118 return code;
119}
120
121SkString GrCubicTessellateShader::getTessEvaluationShaderGLSL(
122 const GrGLSLPrimitiveProcessor*, const char* versionAndExtensionDecls,
123 const GrGLSLUniformHandler&, const GrShaderCaps&) const {
124 SkString code(versionAndExtensionDecls);
125 code.append(kEvalCubicFn);
126 code.append(R"(
127 layout(triangles, equal_spacing, ccw) in;
128
129 uniform vec4 sk_RTAdjust;
130
131 in vec4 X[];
132 in vec4 Y[];
133
134 void main() {
135 // Locate our parametric point of interest. T ramps from [0..1/2] on the left edge
136 // of the triangle, and [1/2..1] on the right. If we are the patch's interior
137 // vertex, then we want T=1/2. Since the barycentric coords are (1/3, 1/3, 1/3) at
138 // the interior vertex, the below fma() works in all 3 scenarios.
139 float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
140
141 mat4x2 P = transpose(mat2x4(X[0], Y[0]));
142 vec2 vertexpos = eval_cubic(P, T);
143 if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
144 // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
145 vertexpos = (P[0] + vertexpos + P[3]) / 3.0;
146 }
147
148 gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
149 })");
150
151 return code;
152}
153
154SkString GrWedgeTessellateShader::getTessControlShaderGLSL(const GrGLSLPrimitiveProcessor*,
155 const char* versionAndExtensionDecls,
156 const GrGLSLUniformHandler&,
157 const GrShaderCaps&) const {
158 SkString code(versionAndExtensionDecls);
159 code.append(kWangsFormulaCubicFn);
160 code.append(R"(
161 layout(vertices = 1) out;
162
163 in vec2 P[];
164 out vec4 X[];
165 out vec4 Y[];
166 out vec2 fanpoint[];
167
168 void main() {
169 // Calculate how many triangles we need to linearize the curve.
170 float num_segments = wangs_formula_cubic(P[0], P[1], P[2], P[3]);
171
172 // Tessellate the first side of the patch into num_segments triangles.
173 gl_TessLevelOuter[0] = num_segments;
174
175 // Leave the other two sides of the patch as single segments.
176 gl_TessLevelOuter[1] = 1.0;
177 gl_TessLevelOuter[2] = 1.0;
178
179 // Changing the inner level to 1 when num_segments == 1 collapses the entire
180 // patch to a single triangle. Otherwise, we need an inner level of 2 so our curve
181 // triangles have an interior point to originate from.
182 gl_TessLevelInner[0] = min(num_segments, 2.0);
183
184 X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x);
185 Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y);
186 fanpoint[gl_InvocationID /*== 0*/] = P[4];
187 })");
188
189 return code;
190}
191
192SkString GrWedgeTessellateShader::getTessEvaluationShaderGLSL(
193 const GrGLSLPrimitiveProcessor*, const char* versionAndExtensionDecls,
194 const GrGLSLUniformHandler&, const GrShaderCaps&) const {
195 SkString code(versionAndExtensionDecls);
196 code.append(kEvalCubicFn);
197 code.append(R"(
198 layout(triangles, equal_spacing, ccw) in;
199
200 uniform vec4 sk_RTAdjust;
201
202 in vec4 X[];
203 in vec4 Y[];
204 in vec2 fanpoint[];
205
206 void main() {
207 // Locate our parametric point of interest. It is equal to the barycentric
208 // y-coordinate if we are a vertex on the tessellated edge of the triangle patch,
209 // 0.5 if we are the patch's interior vertex, or N/A if we are the fan point.
210 // NOTE: We are on the tessellated edge when the barycentric x-coordinate == 0.
211 float T = (gl_TessCoord.x == 0.0) ? gl_TessCoord.y : 0.5;
212
213 mat4x2 P = transpose(mat2x4(X[0], Y[0]));
214 vec2 vertexpos = eval_cubic(P, T);
215 if (gl_TessCoord.x == 1.0) {
216 // We are the anchor point that fans from the center of the curve's contour.
217 vertexpos = fanpoint[0];
218 } else if (gl_TessCoord.x != 0.0) {
219 // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
220 vertexpos = (P[0] + vertexpos + P[3]) / 3.0;
221 }
222
223 gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
224 })");
225
226 return code;
227}
228
229constexpr static int kMaxResolveLevel = GrTessellationPathRenderer::kMaxResolveLevel;
230
231GR_DECLARE_STATIC_UNIQUE_KEY(gMiddleOutIndexBufferKey);
232
233sk_sp<const GrGpuBuffer> GrMiddleOutCubicShader::FindOrMakeMiddleOutIndexBuffer(
234 GrResourceProvider* resourceProvider) {
235 GR_DEFINE_STATIC_UNIQUE_KEY(gMiddleOutIndexBufferKey);
236 if (auto buffer = resourceProvider->findByUniqueKey<GrGpuBuffer>(gMiddleOutIndexBufferKey)) {
237 return std::move(buffer);
238 }
239
240 // One explicit triangle at index 0, and one middle-out cubic with kMaxResolveLevel line
241 // segments beginning at index 3.
242 constexpr static int kIndexCount = 3 + NumVerticesAtResolveLevel(kMaxResolveLevel);
243 auto buffer = resourceProvider->createBuffer(
244 kIndexCount * sizeof(uint16_t), GrGpuBufferType::kIndex, kStatic_GrAccessPattern);
245 if (!buffer) {
246 return nullptr;
247 }
248
249 // We shouldn't bin and/or cache static buffers.
250 SkASSERT(buffer->size() == kIndexCount * sizeof(uint16_t));
251 SkASSERT(!buffer->resourcePriv().getScratchKey().isValid());
252 auto indexData = static_cast<uint16_t*>(buffer->map());
253 SkAutoTMalloc<uint16_t> stagingBuffer;
254 if (!indexData) {
255 SkASSERT(!buffer->isMapped());
256 indexData = stagingBuffer.reset(kIndexCount);
257 }
258
259 // Indices 0,1,2 contain special values that emit points P0, P1, and P2 respectively. (When the
260 // vertex shader is fed an index value larger than (1 << kMaxResolveLevel), it emits
261 // P[index % 4].)
262 int i = 0;
263 indexData[i++] = (1 << kMaxResolveLevel) + 4; // % 4 == 0
264 indexData[i++] = (1 << kMaxResolveLevel) + 5; // % 4 == 1
265 indexData[i++] = (1 << kMaxResolveLevel) + 6; // % 4 == 2
266
267 // Starting at index 3, we triangulate a cubic with 2^kMaxResolveLevel line segments. Each
268 // index value corresponds to parametric value T=(index / 2^kMaxResolveLevel). Since the
269 // triangles are arranged in "middle-out" order, we will be able to conveniently control the
270 // resolveLevel by changing only the indexCount.
271 for (uint16_t advance = 1 << (kMaxResolveLevel - 1); advance; advance >>= 1) {
272 uint16_t T = 0;
273 do {
274 indexData[i++] = T;
275 indexData[i++] = (T += advance);
276 indexData[i++] = (T += advance);
277 } while (T != (1 << kMaxResolveLevel));
278 }
279 SkASSERT(i == kIndexCount);
280
281 if (buffer->isMapped()) {
282 buffer->unmap();
283 } else {
284 buffer->updateData(stagingBuffer, kIndexCount * sizeof(uint16_t));
285 }
286 buffer->resourcePriv().setUniqueKey(gMiddleOutIndexBufferKey);
287 return std::move(buffer);
288}
289
290class GrMiddleOutCubicShader::Impl : public GrStencilPathShader::Impl {
291 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
292 const auto& shader = args.fGP.cast<GrMiddleOutCubicShader>();
293 args.fVaryingHandler->emitAttributes(shader);
294 args.fVertBuilder->defineConstant("kMaxResolveLevel", kMaxResolveLevel);
295 args.fVertBuilder->codeAppend(R"(
296 float4x2 P = float4x2(inputPoints_0_1, inputPoints_2_3);
297 float2 point;
298 if (sk_VertexID > (1 << kMaxResolveLevel)) {
299 // This is a special index value that wants us to emit a specific point.
300 point = P[sk_VertexID & 3];
301 } else {)");
302 // Evaluate the cubic at T=(sk_VertexID / 2^kMaxResolveLevel).
303 if (args.fShaderCaps->fpManipulationSupport()) {
304 args.fVertBuilder->codeAppend(R"(
305 float T = ldexp(sk_VertexID, -kMaxResolveLevel);)");
306 } else {
307 args.fVertBuilder->codeAppend(R"(
308 float T = sk_VertexID / float(1 << kMaxResolveLevel);)");
309 }
310 args.fVertBuilder->codeAppend(R"(
311 float2 ab = mix(P[0], P[1], T);
312 float2 bc = mix(P[1], P[2], T);
313 float2 cd = mix(P[2], P[3], T);
314 float2 abc = mix(ab, bc, T);
315 float2 bcd = mix(bc, cd, T);
316 point = mix(abc, bcd, T);
317 })");
318
319 GrShaderVar vertexPos("point", kFloat2_GrSLType);
320 if (!shader.viewMatrix().isIdentity()) {
321 const char* viewMatrix;
322 fViewMatrixUniform = args.fUniformHandler->addUniform(
323 nullptr, kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
324 args.fVertBuilder->codeAppendf(R"(
325 float2 transformedPoint = (%s * float3(point, 1)).xy;)", viewMatrix);
326 vertexPos.set(kFloat2_GrSLType, "transformedPoint");
327 }
328 gpArgs->fPositionVar = vertexPos;
329 // No fragment shader.
330 }
331};
332
333GrGLSLPrimitiveProcessor* GrMiddleOutCubicShader::createGLSLInstance(const GrShaderCaps&) const {
334 return new Impl;
335}
336