• 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/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.
17 constexpr 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.
27 constexpr 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 
37 class GrStencilPathShader::Impl : public GrGLSLGeometryProcessor {
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)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                     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::kOut_TypeModifier));
57             args.fVertBuilder->codeAppendf("P = %s;", vertexPos.c_str());
58         }
59 
60         // No fragment shader.
61     }
62 
setData(const GrGLSLProgramDataManager & pdman,const GrPrimitiveProcessor & primProc,const CoordTransformRange & transformRange)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 
createGLSLInstance(const GrShaderCaps &) const74 GrGLSLPrimitiveProcessor* GrStencilPathShader::createGLSLInstance(const GrShaderCaps&) const {
75     return new Impl;
76 }
77 
getTessControlShaderGLSL(const char * versionAndExtensionDecls,const GrShaderCaps &) const78 SkString 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 
getTessEvaluationShaderGLSL(const char * versionAndExtensionDecls,const GrShaderCaps &) const118 SkString 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 
getTessControlShaderGLSL(const char * versionAndExtensionDecls,const GrShaderCaps &) const150 SkString 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 
getTessEvaluationShaderGLSL(const char * versionAndExtensionDecls,const GrShaderCaps &) const186 SkString 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