1 /*
2 * Copyright 2021 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 "include/core/SkCustomMesh.h"
9
10 #ifdef SK_ENABLE_SKSL
11
12 #include "src/core/SkCustomMeshPriv.h"
13 #include "src/gpu/GrShaderCaps.h"
14 #include "src/sksl/SkSLCompiler.h"
15 #include "src/sksl/SkSLSharedCompiler.h"
16 #include "src/sksl/ir/SkSLFunctionDefinition.h"
17 #include "src/sksl/ir/SkSLProgram.h"
18 #include "src/sksl/ir/SkSLVarDeclarations.h"
19
20 #include <locale>
21
22 using Attribute = SkCustomMeshSpecification::Attribute;
23 using Varying = SkCustomMeshSpecification::Varying;
24
25 #define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)}
26
27 #define RETURN_ERROR(...) return std::make_tuple(false, SkStringPrintf(__VA_ARGS__))
28
29 #define RETURN_SUCCESS return std::make_tuple(true, SkString{})
30
has_main(const SkSL::Program & p)31 static bool has_main(const SkSL::Program& p) {
32 for (const SkSL::ProgramElement* elem : p.elements()) {
33 if (elem->is<SkSL::FunctionDefinition>()) {
34 const SkSL::FunctionDefinition& defn = elem->as<SkSL::FunctionDefinition>();
35 const SkSL::FunctionDeclaration& decl = defn.declaration();
36 if (decl.isMain()) {
37 return true;
38 }
39 }
40 }
41 return false;
42 }
43
44 using ColorType = SkCustomMeshSpecificationPriv::ColorType;
45
46 static std::tuple<ColorType, bool>
get_fs_color_type_and_local_coords(const SkSL::Program & fsProgram)47 get_fs_color_type_and_local_coords(const SkSL::Program& fsProgram) {
48 for (const SkSL::ProgramElement* elem : fsProgram.elements()) {
49 if (elem->is<SkSL::FunctionDefinition>()) {
50 const SkSL::FunctionDefinition& defn = elem->as<SkSL::FunctionDefinition>();
51 const SkSL::FunctionDeclaration& decl = defn.declaration();
52 if (decl.isMain()) {
53
54 SkCustomMeshSpecificationPriv::ColorType ct;
55 SkASSERT(decl.parameters().size() == 1 || decl.parameters().size() == 2);
56 if (decl.parameters().size() == 1) {
57 ct = ColorType::kNone;
58 } else {
59 const SkSL::Type& paramType = decl.parameters()[1]->type();
60 SkASSERT(paramType.matches(*fsProgram.fContext->fTypes.fHalf4) ||
61 paramType.matches(*fsProgram.fContext->fTypes.fFloat4));
62 ct = paramType.matches(*fsProgram.fContext->fTypes.fHalf4)
63 ? ColorType::kHalf4
64 : ColorType::kFloat4;
65 }
66
67 const SkSL::Type& returnType = decl.returnType();
68 SkASSERT(returnType.matches(*fsProgram.fContext->fTypes.fVoid) ||
69 returnType.matches(*fsProgram.fContext->fTypes.fFloat2));
70 bool hasLocalCoords = returnType.matches(*fsProgram.fContext->fTypes.fFloat2);
71
72 return std::make_tuple(ct, hasLocalCoords);
73 }
74 }
75 }
76 SkUNREACHABLE;
77 }
78
79 // This is a non-exhaustive check for the validity of a variable name. The SkSL compiler will
80 // actually process the name. We're just guarding against having multiple tokens embedded in the
81 // name before we put it into a struct definition.
check_name(const SkString & name)82 static bool check_name(const SkString& name) {
83 if (name.isEmpty()) {
84 return false;
85 }
86 for (size_t i = 0; i < name.size(); ++i) {
87 if (name[i] != '_' && !std::isalnum(name[i], std::locale::classic())) {
88 return false;
89 }
90 }
91 return true;
92 }
93
attribute_type_size(Attribute::Type type)94 static size_t attribute_type_size(Attribute::Type type) {
95 switch (type) {
96 case Attribute::Type::kFloat: return 4;
97 case Attribute::Type::kFloat2: return 2*4;
98 case Attribute::Type::kFloat3: return 3*4;
99 case Attribute::Type::kFloat4: return 4*4;
100 case Attribute::Type::kUByte4_unorm: return 4;
101 }
102 SkUNREACHABLE;
103 };
104
attribute_type_string(Attribute::Type type)105 static const char* attribute_type_string(Attribute::Type type) {
106 switch (type) {
107 case Attribute::Type::kFloat: return "float";
108 case Attribute::Type::kFloat2: return "float2";
109 case Attribute::Type::kFloat3: return "float3";
110 case Attribute::Type::kFloat4: return "float4";
111 case Attribute::Type::kUByte4_unorm: return "half4";
112 }
113 SkUNREACHABLE;
114 };
115
varying_type_string(Varying::Type type)116 static const char* varying_type_string(Varying::Type type) {
117 switch (type) {
118 case Varying::Type::kFloat: return "float";
119 case Varying::Type::kFloat2: return "float2";
120 case Varying::Type::kFloat3: return "float3";
121 case Varying::Type::kFloat4: return "float4";
122 case Varying::Type::kHalf: return "half";
123 case Varying::Type::kHalf2: return "half2";
124 case Varying::Type::kHalf3: return "half3";
125 case Varying::Type::kHalf4: return "half4";
126 }
127 SkUNREACHABLE;
128 };
129
130 std::tuple<bool, SkString>
check_vertex_offsets_and_stride(SkSpan<const Attribute> attributes,size_t stride)131 check_vertex_offsets_and_stride(SkSpan<const Attribute> attributes,
132 size_t stride) {
133 // Vulkan 1.0 has a minimum maximum attribute count of 2048.
134 static_assert(SkCustomMeshSpecification::kMaxStride <= 2048);
135 // ES 2 has a max of 8.
136 static_assert(SkCustomMeshSpecification::kMaxAttributes <= 8);
137 // Four bytes alignment is required by Metal.
138 static_assert(SkCustomMeshSpecification::kStrideAlignment >= 4);
139 static_assert(SkCustomMeshSpecification::kOffsetAlignment >= 4);
140 // ES2 has a minimum maximum of 8. We may need one for a broken gl_FragCoord workaround and
141 // one for local coords.
142 static_assert(SkCustomMeshSpecification::kMaxVaryings <= 6);
143
144 if (attributes.empty()) {
145 RETURN_ERROR("At least 1 attribute is required.");
146 }
147 if (attributes.size() > SkCustomMeshSpecification::kMaxAttributes) {
148 RETURN_ERROR("A maximum of %zu attributes is allowed.",
149 SkCustomMeshSpecification::kMaxAttributes);
150 }
151 static_assert(SkIsPow2(SkCustomMeshSpecification::kStrideAlignment));
152 if (stride == 0 || stride & (SkCustomMeshSpecification::kStrideAlignment - 1)) {
153 RETURN_ERROR("Vertex stride must be a non-zero multiple of %zu.",
154 SkCustomMeshSpecification::kStrideAlignment);
155 }
156 if (stride > SkCustomMeshSpecification::kMaxStride) {
157 RETURN_ERROR("Stride cannot exceed %zu.", SkCustomMeshSpecification::kMaxStride);
158 }
159 for (const auto& a : attributes) {
160 if (a.offset & (SkCustomMeshSpecification::kOffsetAlignment - 1)) {
161 RETURN_ERROR("Attribute offset must be a multiple of %zu.",
162 SkCustomMeshSpecification::kOffsetAlignment);
163 }
164 // This equivalent to vertexAttributeAccessBeyondStride==VK_FALSE in
165 // VK_KHR_portability_subset. First check is to avoid overflow in second check.
166 if (a.offset >= stride || a.offset + attribute_type_size(a.type) > stride) {
167 RETURN_ERROR("Attribute offset plus size cannot exceed stride.");
168 }
169 }
170 RETURN_SUCCESS;
171 }
172
Make(SkSpan<const Attribute> attributes,size_t vertexStride,SkSpan<const Varying> varyings,const SkString & vs,const SkString & fs,sk_sp<SkColorSpace> cs,SkAlphaType at)173 SkCustomMeshSpecification::Result SkCustomMeshSpecification::Make(
174 SkSpan<const Attribute> attributes,
175 size_t vertexStride,
176 SkSpan<const Varying> varyings,
177 const SkString& vs,
178 const SkString& fs,
179 sk_sp<SkColorSpace> cs,
180 SkAlphaType at) {
181 SkString attributesStruct("struct Attributes {\n");
182 for (const auto& a : attributes) {
183 attributesStruct.appendf(" %s %s;\n", attribute_type_string(a.type), a.name.c_str());
184 }
185 attributesStruct.append("};\n");
186
187 SkString varyingStruct("struct Varyings {\n");
188 for (const auto& v : varyings) {
189 varyingStruct.appendf(" %s %s;\n", varying_type_string(v.type), v.name.c_str());
190 }
191 // Throw in an unused variable to avoid an empty struct, which is illegal.
192 if (varyings.empty()) {
193 varyingStruct.append(" bool _empty_;\n");
194 }
195 varyingStruct.append("};\n");
196
197 SkString fullVS;
198 fullVS.append(varyingStruct.c_str());
199 fullVS.append(attributesStruct.c_str());
200 fullVS.append(vs.c_str());
201
202 SkString fullFS;
203 fullFS.append(varyingStruct.c_str());
204 fullFS.append(fs.c_str());
205
206 return MakeFromSourceWithStructs(attributes,
207 vertexStride,
208 varyings,
209 fullVS,
210 fullFS,
211 std::move(cs),
212 at);
213 }
214
MakeFromSourceWithStructs(SkSpan<const Attribute> attributes,size_t stride,SkSpan<const Varying> varyings,const SkString & vs,const SkString & fs,sk_sp<SkColorSpace> cs,SkAlphaType at)215 SkCustomMeshSpecification::Result SkCustomMeshSpecification::MakeFromSourceWithStructs(
216 SkSpan<const Attribute> attributes,
217 size_t stride,
218 SkSpan<const Varying> varyings,
219 const SkString& vs,
220 const SkString& fs,
221 sk_sp<SkColorSpace> cs,
222 SkAlphaType at) {
223 if (auto [ok, error] = check_vertex_offsets_and_stride(attributes, stride); !ok) {
224 return {nullptr, error};
225 }
226
227 for (const auto& a : attributes) {
228 if (!check_name(a.name)) {
229 RETURN_FAILURE("\"%s\" is not a valid attribute name.", a.name.c_str());
230 }
231 }
232
233 if (varyings.size() > kMaxVaryings) {
234 RETURN_FAILURE("A maximum of %zu varyings is allowed.", kMaxVaryings);
235 }
236
237 for (const auto& v : varyings) {
238 if (!check_name(v.name)) {
239 return {nullptr, SkStringPrintf("\"%s\" is not a valid varying name.", v.name.c_str())};
240 }
241 }
242
243 SkSL::SharedCompiler compiler;
244 SkSL::Program::Settings settings;
245 settings.fEnforceES2Restrictions = true;
246 std::unique_ptr<SkSL::Program> vsProgram = compiler->convertProgram(
247 SkSL::ProgramKind::kCustomMeshVertex,
248 std::string(vs.c_str()),
249 settings);
250 if (!vsProgram) {
251 RETURN_FAILURE("VS: %s", compiler->errorText().c_str());
252 }
253 if (!has_main(*vsProgram)) {
254 RETURN_FAILURE("Vertex shader must have main function.");
255 }
256 if (SkSL::Analysis::CallsColorTransformIntrinsics(*vsProgram)) {
257 RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders");
258 }
259
260 std::unique_ptr<SkSL::Program> fsProgram = compiler->convertProgram(
261 SkSL::ProgramKind::kCustomMeshFragment,
262 std::string(fs.c_str()),
263 settings);
264
265 if (!fsProgram) {
266 RETURN_FAILURE("FS: %s", compiler->errorText().c_str());
267 }
268 if (!has_main(*fsProgram)) {
269 RETURN_FAILURE("Fragment shader must have main function.");
270 }
271 if (SkSL::Analysis::CallsColorTransformIntrinsics(*fsProgram)) {
272 RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders");
273 }
274
275 auto [ct, hasLocalCoords] = get_fs_color_type_and_local_coords(*fsProgram);
276
277 if (ct == ColorType::kNone) {
278 cs = nullptr;
279 at = kPremul_SkAlphaType;
280 } else {
281 if (!cs) {
282 return {nullptr, SkString{"Must provide a color space if FS returns a color."}};
283 }
284 if (at == kUnknown_SkAlphaType) {
285 return {nullptr, SkString{"Must provide a valid alpha type if FS returns a color."}};
286 }
287 }
288
289 return {sk_sp<SkCustomMeshSpecification>(new SkCustomMeshSpecification(attributes,
290 stride,
291 varyings,
292 std::move(vsProgram),
293 std::move(fsProgram),
294 ct,
295 hasLocalCoords,
296 std::move(cs),
297 at)),
298 /*error=*/ {}};
299 }
300
301 SkCustomMeshSpecification::~SkCustomMeshSpecification() = default;
302
SkCustomMeshSpecification(SkSpan<const Attribute> attributes,size_t stride,SkSpan<const Varying> varyings,std::unique_ptr<SkSL::Program> vs,std::unique_ptr<SkSL::Program> fs,ColorType ct,bool hasLocalCoords,sk_sp<SkColorSpace> cs,SkAlphaType at)303 SkCustomMeshSpecification::SkCustomMeshSpecification(SkSpan<const Attribute> attributes,
304 size_t stride,
305 SkSpan<const Varying> varyings,
306 std::unique_ptr<SkSL::Program> vs,
307 std::unique_ptr<SkSL::Program> fs,
308 ColorType ct,
309 bool hasLocalCoords,
310 sk_sp<SkColorSpace> cs,
311 SkAlphaType at)
312 : fAttributes(attributes.begin(), attributes.end())
313 , fVaryings(varyings.begin(), varyings.end())
314 , fVS(std::move(vs))
315 , fFS(std::move(fs))
316 , fStride(stride)
317 , fColorType(ct)
318 , fHasLocalCoords(hasLocalCoords)
319 , fColorSpace(std::move(cs))
320 , fAlphaType(at) {
321 fHash = SkOpts::hash_fn(fVS->fSource->c_str(), fVS->fSource->size(), 0);
322 fHash = SkOpts::hash_fn(fFS->fSource->c_str(), fFS->fSource->size(), fHash);
323
324 // The attributes and varyings SkSL struct declarations are included in the program source.
325 // However, the attribute offsets and types need to be included, the latter because the SkSL
326 // struct definition has the GPU type but not the CPU data format.
327 for (const auto& a : fAttributes) {
328 fHash = SkOpts::hash_fn(&a.offset, sizeof(a.offset), fHash);
329 fHash = SkOpts::hash_fn(&a.type, sizeof(a.type), fHash);
330 }
331
332 fHash = SkOpts::hash_fn(&stride, sizeof(stride), fHash);
333
334 uint64_t csHash = fColorSpace ? fColorSpace->hash() : 0;
335 fHash = SkOpts::hash_fn(&csHash, sizeof(csHash), fHash);
336
337 auto atInt = static_cast<uint32_t>(fAlphaType);
338 fHash = SkOpts::hash_fn(&atInt, sizeof(atInt), fHash);
339 }
340 #endif //SK_ENABLE_SKSL
341