Blender V4.5
glsl_preprocess.hh
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#pragma once
10
11#include <cstdint>
12#include <functional>
13#include <regex>
14#include <sstream>
15#include <string>
16#include <vector>
17
19
20/* Metadata extracted from shader source file.
21 * These are then converted to their GPU module equivalent. */
22/* TODO(fclem): Make GPU enums standalone and directly use them instead of using separate enums
23 * and types. */
24namespace metadata {
25
26/* Compile-time hashing function which converts string to a 64bit hash. */
27constexpr static uint64_t hash(const char *name)
28{
29 uint64_t hash = 2166136261u;
30 while (*name) {
31 hash = hash * 16777619u;
32 hash = hash ^ *name;
33 ++name;
34 }
35 return hash;
36}
37
38static uint64_t hash(const std::string &name)
39{
40 return hash(name.c_str());
41}
42
44 FragCoord = hash("gl_FragCoord"),
45 FrontFacing = hash("gl_FrontFacing"),
46 GlobalInvocationID = hash("gl_GlobalInvocationID"),
47 InstanceID = hash("gl_InstanceID"),
48 LocalInvocationID = hash("gl_LocalInvocationID"),
49 LocalInvocationIndex = hash("gl_LocalInvocationIndex"),
50 NumWorkGroup = hash("gl_NumWorkGroup"),
51 PointCoord = hash("gl_PointCoord"),
52 PointSize = hash("gl_PointSize"),
53 PrimitiveID = hash("gl_PrimitiveID"),
54 VertexID = hash("gl_VertexID"),
55 WorkGroupID = hash("gl_WorkGroupID"),
56 WorkGroupSize = hash("gl_WorkGroupSize"),
57 drw_debug = hash("drw_debug_"),
58 printf = hash("printf"),
59 assert = hash("assert"),
60};
61
63 in = hash("in"),
64 out = hash("out"),
65 inout = hash("inout"),
66};
67
68enum Type : uint64_t {
69 float1 = hash("float"),
70 float2 = hash("float2"),
71 float3 = hash("float3"),
72 float4 = hash("float4"),
73 float3x3 = hash("float3x3"),
74 float4x4 = hash("float4x4"),
75 sampler1DArray = hash("sampler1DArray"),
76 sampler2DArray = hash("sampler2DArray"),
77 sampler2D = hash("sampler2D"),
78 sampler3D = hash("sampler3D"),
79 Closure = hash("Closure"),
80};
81
86
88 std::string name;
89 std::vector<ArgumentFormat> arguments;
90};
91
93 uint32_t hash;
94 std::string format;
95};
96
97struct Source {
98 std::vector<Builtin> builtins;
99 /* Note: Could be a set, but for now the order matters. */
100 std::vector<std::string> dependencies;
101 std::vector<PrintfFormat> printf_formats;
102 std::vector<FunctionFormat> functions;
103
104 std::string serialize(const std::string &function_name) const
105 {
106 std::stringstream ss;
107 ss << "static void " << function_name
108 << "(GPUSource &source, GPUFunctionDictionnary *g_functions, GPUPrintFormatMap *g_formats) "
109 "{\n";
110 for (auto function : functions) {
111 ss << " {\n";
112 ss << " Vector<metadata::ArgumentFormat> args = {\n";
113 for (auto arg : function.arguments) {
114 ss << " "
115 << "metadata::ArgumentFormat{"
116 << "metadata::Qualifier(" << std::to_string(uint64_t(arg.qualifier)) << "LLU), "
117 << "metadata::Type(" << std::to_string(uint64_t(arg.type)) << "LLU)"
118 << "},\n";
119 }
120 ss << " };\n";
121 ss << " source.add_function(\"" << function.name << "\", args, g_functions);\n";
122 ss << " }\n";
123 }
124 for (auto builtin : builtins) {
125 ss << " source.add_builtin(metadata::Builtin(" << std::to_string(builtin) << "LLU));\n";
126 }
127 for (auto dependency : dependencies) {
128 ss << " source.add_dependency(\"" << dependency << "\");\n";
129 }
130 for (auto format : printf_formats) {
131 ss << " source.add_printf_format(uint32_t(" << std::to_string(format.hash) << "), "
132 << format.format << ", g_formats);\n";
133 }
134 /* Avoid warnings. */
135 ss << " UNUSED_VARS(source, g_functions, g_formats);\n";
136 ss << "}\n";
137 return ss.str();
138 }
139};
140
141} // namespace metadata
142
151 using uint64_t = std::uint64_t;
152 using report_callback = std::function<void(const std::smatch &, const char *)>;
153 struct SharedVar {
154 std::string type;
155 std::string name;
156 std::string array;
157 };
158
159 std::vector<SharedVar> shared_vars_;
160
161 metadata::Source metadata;
162
163 public:
169 /* Same as GLSL but enable partial C++ feature support like template, references,
170 * include system, etc ... */
172 };
173
174 static SourceLanguage language_from_filename(const std::string &filename)
175 {
176 if (filename.find(".msl") != std::string::npos) {
177 return MSL;
178 }
179 if (filename.find(".glsl") != std::string::npos) {
180 return GLSL;
181 }
182 if (filename.find(".hh") != std::string::npos) {
183 return CPP;
184 }
185 return UNKNOWN;
186 }
187
188 /* Takes a whole source file and output processed source. */
189 std::string process(SourceLanguage language,
190 std::string str,
191 const std::string &filename,
192 bool do_parse_function,
193 bool do_small_type_linting,
194 report_callback report_error,
195 metadata::Source &r_metadata)
196 {
197 if (language == UNKNOWN) {
198 report_error(std::smatch(), "Unknown file type");
199 return "";
200 }
201 str = remove_comments(str, report_error);
202 threadgroup_variables_parsing(str);
203 parse_builtins(str, filename);
204 if (language == BLENDER_GLSL || language == CPP) {
205 if (do_parse_function) {
206 parse_library_functions(str);
207 }
208 if (language == BLENDER_GLSL) {
209 include_parse(str, report_error);
210 pragma_once_linting(str, filename, report_error);
211 }
212 str = preprocessor_directive_mutation(str);
213 str = swizzle_function_mutation(str);
214 if (language == BLENDER_GLSL) {
215 str = loop_unroll(str, report_error);
216 str = assert_processing(str, filename);
217 static_strings_parsing(str);
218 str = static_strings_mutation(str);
219 str = printf_processing(str, report_error);
220 quote_linting(str, report_error);
221 }
222 global_scope_constant_linting(str, report_error);
223 matrix_constructor_linting(str, report_error);
224 array_constructor_linting(str, report_error);
225 if (do_small_type_linting) {
226 small_type_linting(str, report_error);
227 }
228 str = remove_quotes(str);
229 if (language == BLENDER_GLSL) {
230 str = using_mutation(str, report_error);
231 str = namespace_mutation(str, report_error);
232 str = namespace_separator_mutation(str);
233 }
234 str = enum_macro_injection(str);
235 str = default_argument_mutation(str);
236 str = argument_reference_mutation(str);
237 str = variable_reference_mutation(str, report_error);
238 str = template_definition_mutation(str, report_error);
239 str = template_call_mutation(str);
240 }
241#ifdef __APPLE__ /* Limiting to Apple hardware since GLSL compilers might have issues. */
242 if (language == GLSL) {
243 str = matrix_constructor_mutation(str);
244 }
245#endif
246 str = argument_decorator_macro_injection(str);
247 str = array_constructor_macro_injection(str);
248 r_metadata = metadata;
249 return line_directive_prefix(filename) + str + threadgroup_variables_suffix();
250 }
251
252 /* Variant use for python shaders. */
253 std::string process(const std::string &str)
254 {
255 auto no_err_report = [](std::smatch, const char *) {};
256 metadata::Source unused;
257 return process(GLSL, str, "", false, false, no_err_report, unused);
258 }
259
260 private:
261 using regex_callback = std::function<void(const std::smatch &)>;
262
263 /* Helper to make the code more readable in parsing functions. */
264 void regex_global_search(const std::string &str,
265 const std::regex &regex,
266 regex_callback callback)
267 {
268 using namespace std;
269 string::const_iterator it = str.begin();
270 for (smatch match; regex_search(it, str.end(), match, regex); it = match.suffix().first) {
271 callback(match);
272 }
273 }
274
275 template<typename ReportErrorF>
276 std::string remove_comments(const std::string &str, const ReportErrorF &report_error)
277 {
278 std::string out_str = str;
279 {
280 /* Multi-line comments. */
281 size_t start, end = 0;
282 while ((start = out_str.find("/*", end)) != std::string::npos) {
283 end = out_str.find("*/", start + 2);
284 if (end == std::string::npos) {
285 break;
286 }
287 for (size_t i = start; i < end + 2; ++i) {
288 if (out_str[i] != '\n') {
289 out_str[i] = ' ';
290 }
291 }
292 }
293
294 if (end == std::string::npos) {
295 /* TODO(fclem): Add line / char position to report. */
296 report_error(std::smatch(), "Malformed multi-line comment.");
297 return out_str;
298 }
299 }
300 {
301 /* Single-line comments. */
302 size_t start, end = 0;
303 while ((start = out_str.find("//", end)) != std::string::npos) {
304 end = out_str.find('\n', start + 2);
305 if (end == std::string::npos) {
306 break;
307 }
308 for (size_t i = start; i < end; ++i) {
309 out_str[i] = ' ';
310 }
311 }
312
313 if (end == std::string::npos) {
314 /* TODO(fclem): Add line / char position to report. */
315 report_error(std::smatch(), "Malformed single line comment, missing newline.");
316 return out_str;
317 }
318 }
319 /* Remove trailing white space as they make the subsequent regex much slower. */
320 std::regex regex(R"((\ )*?\n)");
321 return std::regex_replace(out_str, regex, "\n");
322 }
323
324 std::string template_definition_mutation(const std::string &str, report_callback &report_error)
325 {
326 if (str.find("template") == std::string::npos) {
327 return str;
328 }
329
330 std::string out_str = str;
331 {
332 /* Transform template definition into macro declaration. */
333 std::regex regex(R"(template<([\w\d\n\,\ ]+)>(\s\w+\s)(\w+)\‍()");
334 out_str = std::regex_replace(out_str, regex, "#define $3_TEMPLATE($1)$2$3@(");
335 }
336 {
337 /* Add backslash for each newline in template macro. */
338 size_t start, end = 0;
339 while ((start = out_str.find("_TEMPLATE(", end)) != std::string::npos) {
340 /* Remove parameter type from macro argument list. */
341 end = out_str.find(")", start);
342 std::string arg_list = out_str.substr(start, end - start);
343 arg_list = std::regex_replace(arg_list, std::regex(R"(\w+ (\w+))"), "$1");
344 out_str.replace(start, end - start, arg_list);
345
346 std::string template_body = get_content_between_balanced_pair(
347 out_str.substr(start), '{', '}');
348 if (template_body.empty()) {
349 /* Empty body is unlikely to happen. This limitation can be worked-around by using a noop
350 * comment inside the function body. */
351 report_error(
352 std::smatch(),
353 "Template function declaration is missing closing bracket or has empty body.");
354 break;
355 }
356 size_t body_end = out_str.find('{', start) + 1 + template_body.size();
357 /* Contains "_TEMPLATE(macro_args) void fn@(fn_args) { body;". */
358 std::string macro_body = out_str.substr(start, body_end - start);
359
360 macro_body = std::regex_replace(macro_body, std::regex(R"(\n)"), " \\\n");
361
362 std::string macro_args = get_content_between_balanced_pair(macro_body, '(', ')');
363 /* Find function argument list.
364 * Skip first 10 chars to skip "_TEMPLATE" and the argument list. */
365 std::string fn_args = get_content_between_balanced_pair(
366 macro_body.substr(10 + macro_args.length() + 1), '(', ')');
367 /* Remove white-spaces. */
368 macro_args = std::regex_replace(macro_args, std::regex(R"(\s)"), "");
369 std::vector<std::string> macro_args_split = split_string(macro_args, ',');
370 /* Append arguments inside the function name. */
371 std::string fn_name_suffix = "_";
372 bool all_args_in_function_signature = true;
373 for (std::string macro_arg : macro_args_split) {
374 fn_name_suffix += "##" + macro_arg + "##_";
375 /* Search macro arguments inside the function arguments types. */
376 if (std::regex_search(fn_args, std::regex(R"(\b)" + macro_arg + R"(\b)")) == false) {
377 all_args_in_function_signature = false;
378 }
379 }
380 if (all_args_in_function_signature) {
381 /* No need for suffix. Use overload for type deduction.
382 * Otherwise, we require full explicit template call. */
383 fn_name_suffix = "";
384 }
385 size_t end_of_fn_name = macro_body.find("@");
386 macro_body.replace(end_of_fn_name, 1, fn_name_suffix);
387
388 out_str.replace(start, body_end - start, macro_body);
389 }
390 }
391 {
392 /* Replace explicit instantiation by macro call. */
393 /* Only `template ret_t fn<T>(args);` syntax is supported. */
394 std::regex regex_instance(R"(template \w+ (\w+)<([\w+\,\ \n]+)>\‍(([\w+\ \,\n]+)\);)");
395 /* Notice the stupid way of keeping the number of lines the same by copying the argument list
396 * inside a multi-line comment. */
397 out_str = std::regex_replace(out_str, regex_instance, "$1_TEMPLATE($2)/*$3*/");
398 }
399 {
400 /* Check if there is no remaining declaration and instantiation that were not processed. */
401 if (out_str.find("template<") != std::string::npos) {
402 std::regex regex_declaration(R"(\btemplate<)");
403 regex_global_search(out_str, regex_declaration, [&](const std::smatch &match) {
404 report_error(match, "Template declaration unsupported syntax");
405 });
406 }
407 if (out_str.find("template ") != std::string::npos) {
408 std::regex regex_instance(R"(\btemplate )");
409 regex_global_search(out_str, regex_instance, [&](const std::smatch &match) {
410 report_error(match, "Template instantiation unsupported syntax");
411 });
412 }
413 }
414 return out_str;
415 }
416
417 std::string template_call_mutation(std::string &str)
418 {
419 while (true) {
420 std::smatch match;
421 if (std::regex_search(str, match, std::regex(R"(([\w\d]+)<([\w\d\n, ]+)>)")) == false) {
422 break;
423 }
424 const std::string template_name = match[1].str();
425 const std::string template_args = match[2].str();
426
427 std::string replacement = "TEMPLATE_GLUE" +
428 std::to_string(char_count(template_args, ',') + 1) + "(" +
429 template_name + ", " + template_args + ")";
430
431 replace_all(str, match[0].str(), replacement);
432 }
433 return str;
434 }
435
436 std::string remove_quotes(const std::string &str)
437 {
438 return std::regex_replace(str, std::regex(R"(["'])"), " ");
439 }
440
441 void include_parse(const std::string &str, report_callback report_error)
442 {
443 /* Parse include directive before removing them. */
444 std::regex regex(R"(#(\s*)include\s*\"(\w+\.\w+)\")");
445
446 regex_global_search(str, regex, [&](const std::smatch &match) {
447 std::string indent = match[1].str();
448 /* Assert that includes are not nested in other preprocessor directives. */
449 if (!indent.empty()) {
450 report_error(match, "#include directives must not be inside #if clause");
451 }
452 std::string dependency_name = match[2].str();
453 /* Assert that includes are at the top of the file. */
454 if (dependency_name == "gpu_glsl_cpp_stubs.hh") {
455 /* Skip GLSL-C++ stubs. They are only for IDE linting. */
456 return;
457 }
458 if (dependency_name.find("info.hh") != std::string::npos) {
459 /* Skip info files. They are only for IDE linting. */
460 return;
461 }
462 metadata.dependencies.emplace_back(dependency_name);
463 });
464 }
465
466 void pragma_once_linting(const std::string &str,
467 const std::string &filename,
468 report_callback report_error)
469 {
470 if (filename.find("_lib.") == std::string::npos) {
471 return;
472 }
473 if (str.find("\n#pragma once") == std::string::npos) {
474 std::smatch match;
475 report_error(match, "Library files must contain #pragma once directive.");
476 }
477 }
478
479 std::string loop_unroll(const std::string &str, report_callback report_error)
480 {
481 if (str.find("[[gpu::unroll") == std::string::npos) {
482 return str;
483 }
484
485 struct Loop {
486 /* `[[gpu::unroll]] for (int i = 0; i < 10; i++)` */
487 std::string definition;
488 /* `{ some_computation(i); }` */
489 std::string body;
490 /* `int i = 0` */
491 std::string init_statement;
492 /* `i < 10` */
493 std::string test_statement;
494 /* `i++` */
495 std::string iter_statement;
496 /* Spaces and newline between loop start and body. */
497 std::string body_prefix;
498 /* Spaces before the loop definition. */
499 std::string indent;
500 /* `10` */
501 int64_t iter_count;
502 /* Line at which the loop was defined. */
503 int64_t definition_line;
504 /* Line at which the body starts. */
505 int64_t body_line;
506 /* Line at which the body ends. */
507 int64_t end_line;
508 };
509
510 std::vector<Loop> loops;
511
512 auto add_loop = [&](Loop &loop,
513 const std::smatch &match,
514 int64_t line,
515 int64_t lines_in_content) {
516 std::string suffix = match.suffix().str();
517 loop.body = get_content_between_balanced_pair(loop.definition + suffix, '{', '}');
518 loop.body = '{' + loop.body + '}';
519 loop.definition_line = line - lines_in_content;
520 loop.body_line = line;
521 loop.end_line = loop.body_line + line_count(loop.body);
522
523 /* Check that there is no unsupported keywords in the loop body. */
524 if (loop.body.find(" break;") != std::string::npos ||
525 loop.body.find(" continue;") != std::string::npos)
526 {
527 /* Expensive check. Remove other loops and switch scopes inside the unrolled loop scope and
528 * check again to avoid false positive. */
529 std::string modified_body = loop.body;
530
531 std::regex regex_loop(R"( (for|while|do) )");
532 regex_global_search(loop.body, regex_loop, [&](const std::smatch &match) {
533 std::string inner_scope = get_content_between_balanced_pair(match.suffix(), '{', '}');
534 replace_all(modified_body, inner_scope, "");
535 });
536
537 /* Checks if `continue` exists, even in switch statement inside the unrolled loop scope. */
538 if (modified_body.find(" continue;") != std::string::npos) {
539 report_error(match, "Error: Unrolled loop cannot contain \"continue\" statement.");
540 }
541
542 std::regex regex_switch(R"( switch )");
543 regex_global_search(loop.body, regex_switch, [&](const std::smatch &match) {
544 std::string inner_scope = get_content_between_balanced_pair(match.suffix(), '{', '}');
545 replace_all(modified_body, inner_scope, "");
546 });
547
548 /* Checks if `break` exists inside the unrolled loop scope. */
549 if (modified_body.find(" break;") != std::string::npos) {
550 report_error(match, "Error: Unrolled loop cannot contain \"break\" statement.");
551 }
552 }
553 loops.emplace_back(loop);
554 };
555
556 /* Parse the loop syntax. */
557 {
558 /* [[gpu::unroll]]. */
559 std::regex regex(R"(( *))"
560 R"(\‍[\‍[gpu::unroll\‍]\‍])"
561 R"(\s*for\s*\‍()"
562 R"(\s*((?:uint|int)\s+(\w+)\s+=\s+(-?\d+));)" /* Init statement. */
563 R"(\s*((\w+)\s+(>|<)(=?)\s+(-?\d+)))" /* Conditional statement. */
564 R"(\s*(?:&&)?\s*([^;)]+)?;)" /* Extra conditional statement. */
565 R"(\s*(((\w+)(\+\+|\-\-))[^\)]*))" /* Iteration statement. */
566 R"(\)(\s*))");
567
568 int64_t line = 0;
569
570 regex_global_search(str, regex, [&](const std::smatch &match) {
571 std::string counter_1 = match[3].str();
572 std::string counter_2 = match[6].str();
573 std::string counter_3 = match[13].str();
574
575 std::string content = match[0].str();
576 int64_t lines_in_content = line_count(content);
577
578 line += line_count(match.prefix().str()) + lines_in_content;
579
580 if ((counter_1 != counter_2) || (counter_1 != counter_3)) {
581 report_error(match, "Error: Non matching loop counter variable.");
582 return;
583 }
584
585 Loop loop;
586
587 int64_t init = std::stol(match[4].str());
588 int64_t end = std::stol(match[9].str());
589 /* TODO(fclem): Support arbitrary strides (aka, arbitrary iter statement). */
590 loop.iter_count = std::abs(end - init);
591
592 std::string condition = match[7].str();
593 if (condition.empty()) {
594 report_error(match, "Error: Unsupported condition in unrolled loop.");
595 }
596
597 std::string equal = match[8].str();
598 if (equal == "=") {
599 loop.iter_count += 1;
600 }
601
602 std::string iter = match[14].str();
603 if (iter == "++") {
604 if (condition == ">") {
605 report_error(match, "Error: Unsupported condition in unrolled loop.");
606 }
607 }
608 else if (iter == "--") {
609 if (condition == "<") {
610 report_error(match, "Error: Unsupported condition in unrolled loop.");
611 }
612 }
613 else {
614 report_error(match, "Error: Unsupported for loop expression. Expecting ++ or --");
615 }
616
617 loop.definition = content;
618 loop.indent = match[1].str();
619 loop.init_statement = match[2].str();
620 if (!match[10].str().empty()) {
621 loop.test_statement = "if (" + match[10].str() + ") ";
622 }
623 loop.iter_statement = match[11].str();
624 loop.body_prefix = match[15].str();
625
626 add_loop(loop, match, line, lines_in_content);
627 });
628 }
629 {
630 /* [[gpu::unroll(n)]]. */
631 std::regex regex(R"(( *))"
632 R"(\‍[\‍[gpu::unroll\‍((\d+)\)\‍]\‍])"
633 R"(\s*for\s*\‍()"
634 R"(\s*([^;]*);)"
635 R"(\s*([^;]*);)"
636 R"(\s*([^)]*))"
637 R"(\)(\s*))");
638
639 int64_t line = 0;
640
641 regex_global_search(str, regex, [&](const std::smatch &match) {
642 std::string content = match[0].str();
643
644 int64_t lines_in_content = line_count(content);
645
646 line += line_count(match.prefix().str()) + lines_in_content;
647
648 Loop loop;
649 loop.iter_count = std::stol(match[2].str());
650 loop.definition = content;
651 loop.indent = match[1].str();
652 loop.init_statement = match[3].str();
653 loop.test_statement = "if (" + match[4].str() + ") ";
654 loop.iter_statement = match[5].str();
655 loop.body_prefix = match[13].str();
656
657 add_loop(loop, match, line, lines_in_content);
658 });
659 }
660
661 std::string out = str;
662
663 /* Copy paste loop iterations. */
664 for (const Loop &loop : loops) {
665 std::string replacement = loop.indent + "{ " + loop.init_statement + ";";
666 for (int64_t i = 0; i < loop.iter_count; i++) {
667 replacement += std::string("\n#line ") + std::to_string(loop.body_line + 1) + "\n";
668 replacement += loop.indent + loop.test_statement + loop.body;
669 replacement += std::string("\n#line ") + std::to_string(loop.definition_line + 1) + "\n";
670 replacement += loop.indent + loop.iter_statement + ";";
671 if (i == loop.iter_count - 1) {
672 replacement += std::string("\n#line ") + std::to_string(loop.end_line + 1) + "\n";
673 replacement += loop.indent + "}";
674 }
675 }
676
677 std::string replaced = loop.definition + loop.body;
678
679 /* Replace all occurrences in case of recursive unrolling. */
680 replace_all(out, replaced, replacement);
681 }
682
683 /* Check for remaining keywords. */
684 if (out.find("[[gpu::unroll") != std::string::npos) {
685 regex_global_search(str, std::regex(R"(\‍[\‍[gpu::unroll)"), [&](const std::smatch &match) {
686 report_error(match, "Error: Incompatible format for [[gpu::unroll]].");
687 });
688 }
689
690 return out;
691 }
692
693 std::string namespace_mutation(const std::string &str, report_callback report_error)
694 {
695 if (str.find("namespace") == std::string::npos) {
696 return str;
697 }
698
699 std::string out = str;
700
701 /* Parse each namespace declaration. */
702 std::regex regex(R"(namespace (\w+(?:\:\:\w+)*))");
703 regex_global_search(str, regex, [&](const std::smatch &match) {
704 std::string namespace_name = match[1].str();
705 std::string content = get_content_between_balanced_pair(match.suffix().str(), '{', '}');
706
707 if (content.find("namespace") != std::string::npos) {
708 report_error(match, "Nested namespaces are unsupported.");
709 return;
710 }
711
712 std::string out_content = content;
713
714 /* Parse all global symbols (struct / functions) inside the content. */
715 std::regex regex(R"([\n>] ?(?:const )?(\w+) (\w+)\‍(?)");
716 regex_global_search(content, regex, [&](const std::smatch &match) {
717 std::string return_type = match[1].str();
718 if (return_type == "template") {
719 /* Matched a template instantiation. */
720 return;
721 }
722 std::string function = match[2].str();
723 /* Replace all occurrences of the non-namespace specified symbol.
724 * Reject symbols that contain the target symbol name. */
725 std::regex regex(R"(([^:\w]))" + function + R"(([\s\‍(<]))");
726 out_content = std::regex_replace(
727 out_content, regex, "$1" + namespace_name + "::" + function + "$2");
728 });
729
730 replace_all(out, "namespace " + namespace_name + " {" + content + "}", out_content);
731 });
732
733 return out;
734 }
735
736 /* Needs to run before namespace mutation so that `using` have more precedence. */
737 std::string using_mutation(const std::string &str, report_callback report_error)
738 {
739 using namespace std;
740
741 if (str.find("using ") == string::npos) {
742 return str;
743 }
744
745 if (str.find("using namespace ") != string::npos) {
746 regex_global_search(str, regex(R"(\busing namespace\b)"), [&](const smatch &match) {
747 report_error(match,
748 "Unsupported `using namespace`. "
749 "Add individual `using` directives for each needed symbol.");
750 });
751 return str;
752 }
753
754 string next_str = str;
755
756 string out_str;
757 /* Using namespace symbol. Example: `using A::B;` */
758 /* Using as type alias. Example: `using S = A::B;` */
759 regex regex_using(R"(\busing (?:(\w+) = )?(([\w\:<>]+)::(\w+));)");
760
761 smatch match;
762 while (regex_search(next_str, match, regex_using)) {
763 const string using_definition = match[0].str();
764 const string alias = match[1].str();
765 const string to = match[2].str();
766 const string namespace_prefix = match[3].str();
767 const string symbol = match[4].str();
768 const string prefix = match.prefix().str();
769 const string suffix = match.suffix().str();
770
771 out_str += prefix;
772 /* Assumes formatted input. */
773 if (prefix.back() == '\n') {
774 /* Using the keyword in global or at namespace scope. */
775 const string parent_scope = get_content_between_balanced_pair(
776 out_str + '}', '{', '}', true);
777 if (parent_scope.empty()) {
778 report_error(match, "The `using` keyword is not allowed in global scope.");
779 break;
780 }
781 /* Ensure we are bringing symbols from the same namespace.
782 * Otherwise we can have different shadowing outcome between shader and C++. */
783 const string ns_keyword = "namespace ";
784 size_t pos = out_str.rfind(ns_keyword, out_str.size() - parent_scope.size());
785 if (pos == string::npos) {
786 report_error(match, "Couldn't find `namespace` keyword at beginning of scope.");
787 break;
788 }
789 size_t start = pos + ns_keyword.size();
790 size_t end = out_str.size() - parent_scope.size() - start - 2;
791 const string namespace_scope = out_str.substr(start, end);
792 if (namespace_scope != namespace_prefix) {
793 report_error(
794 match,
795 "The `using` keyword is only allowed in namespace scope to make visible symbols "
796 "from the same namespace declared in another scope, potentially from another "
797 "file.");
798 break;
799 }
800 }
802 next_str = using_definition + suffix;
803 /* Assignments do not allow to alias functions symbols. */
804 const bool replace_fn = alias.empty();
805 /* Replace the alias (the left part of the assignment) or the last symbol. */
806 const string from = !alias.empty() ? alias : symbol;
807 /* Replace all occurrences of the non-namespace specified symbol.
808 * Reject symbols that contain the target symbol name. */
812 const regex regex(R"(([^:\w]))" + from + R"(([\s)" + (replace_fn ? R"(\‍()" : "") + "])");
813 const string in_scope = get_content_between_balanced_pair('{' + suffix, '{', '}');
814 const string out_scope = regex_replace(in_scope, regex, "$1" + to + "$2");
815 replace_all(next_str, using_definition + in_scope, out_scope);
816 }
817 out_str += next_str;
818
819 /* Verify all using were processed. */
820 if (out_str.find("using ") != string::npos) {
821 regex_global_search(out_str, regex(R"(\busing\b)"), [&](const smatch &match) {
822 report_error(match, "Unsupported `using` keyword usage.");
823 });
824 }
825 return out_str;
826 }
827
828 std::string namespace_separator_mutation(const std::string &str)
829 {
830 std::string out = str;
831
832 /* Global namespace reference. */
833 replace_all(out, " ::", " ");
834 /* Specific namespace reference.
835 * Cannot use `__` because of some compilers complaining about reserved symbols. */
836 replace_all(out, "::", "_");
837 return out;
838 }
839
840 std::string preprocessor_directive_mutation(const std::string &str)
841 {
842 /* Remove unsupported directives.` */
843 std::regex regex(R"(#\s*(?:include|pragma once)[^\n]*)");
844 return std::regex_replace(str, regex, "");
845 }
846
847 std::string swizzle_function_mutation(const std::string &str)
848 {
849 /* Change C++ swizzle functions into plain swizzle. */
850 std::regex regex(R"((\.[rgbaxyzw]{2,4})\‍(\))");
851 /* Keep character count the same. Replace parenthesis by spaces. */
852 return std::regex_replace(str, regex, "$1 ");
853 }
854
855 void threadgroup_variables_parsing(const std::string &str)
856 {
857 std::regex regex(R"(shared\s+(\w+)\s+(\w+)([^;]*);)");
858 regex_global_search(str, regex, [&](const std::smatch &match) {
859 shared_vars_.push_back({match[1].str(), match[2].str(), match[3].str()});
860 });
861 }
862
863 void parse_library_functions(const std::string &str)
864 {
865 using namespace metadata;
866 std::regex regex_func(R"(void\s+(\w+)\s*\‍(([^)]+\))\s*\{)");
867 regex_global_search(str, regex_func, [&](const std::smatch &match) {
868 std::string name = match[1].str();
869 std::string args = match[2].str();
870
871 FunctionFormat fn;
872 fn.name = name;
873
874 std::regex regex_arg(R"((?:(const|in|out|inout)\s)?(\w+)\s([\w\‍[\‍]]+)(?:,|\)))");
875 regex_global_search(args, regex_arg, [&](const std::smatch &arg) {
876 std::string qualifier = arg[1].str();
877 std::string type = arg[2].str();
878 if (qualifier.empty() || qualifier == "const") {
879 qualifier = "in";
880 }
881 fn.arguments.emplace_back(
882 ArgumentFormat{metadata::Qualifier(hash(qualifier)), metadata::Type(hash(type))});
883 });
884 metadata.functions.emplace_back(fn);
885 });
886 }
887
888 void parse_builtins(const std::string &str, const std::string &filename)
889 {
890 const bool skip_drw_debug = filename.find("draw_debug_draw_lib.glsl") != std::string::npos ||
891 filename.find("draw_debug_draw_display_vert.glsl") !=
892 std::string::npos;
893 using namespace metadata;
894 /* TODO: This can trigger false positive caused by disabled #if blocks. */
895 std::string tokens[] = {"gl_FragCoord",
896 "gl_FrontFacing",
897 "gl_GlobalInvocationID",
898 "gl_InstanceID",
899 "gl_LocalInvocationID",
900 "gl_LocalInvocationIndex",
901 "gl_NumWorkGroup",
902 "gl_PointCoord",
903 "gl_PointSize",
904 "gl_PrimitiveID",
905 "gl_VertexID",
906 "gl_WorkGroupID",
907 "gl_WorkGroupSize",
908 "drw_debug_",
909#ifdef WITH_GPU_SHADER_ASSERT
910 "assert",
911#endif
912 "printf"};
913 for (auto &token : tokens) {
914 if (skip_drw_debug && token == "drw_debug_") {
915 continue;
916 }
917 if (str.find(token) != std::string::npos) {
918 metadata.builtins.emplace_back(Builtin(hash(token)));
919 }
920 }
921 }
922
923 template<typename ReportErrorF>
924 std::string printf_processing(const std::string &str, const ReportErrorF &report_error)
925 {
926 std::string out_str = str;
927 {
928 /* Example: `printf(2, b, f(c, d));` > `printf(2@ b@ f(c@ d))$` */
929 size_t start, end = 0;
930 while ((start = out_str.find("printf(", end)) != std::string::npos) {
931 end = out_str.find(';', start);
932 if (end == std::string::npos) {
933 break;
934 }
935 out_str[end] = '$';
936 int bracket_depth = 0;
937 int arg_len = 0;
938 for (size_t i = start; i < end; ++i) {
939 if (out_str[i] == '(') {
940 bracket_depth++;
941 }
942 else if (out_str[i] == ')') {
943 bracket_depth--;
944 }
945 else if (bracket_depth == 1 && out_str[i] == ',') {
946 out_str[i] = '@';
947 arg_len++;
948 }
949 }
950 if (arg_len > 99) {
951 report_error(std::smatch(), "Too many parameters in printf. Max is 99.");
952 break;
953 }
954 /* Encode number of arg in the `ntf` of `printf`. */
955 out_str[start + sizeof("printf") - 4] = '$';
956 out_str[start + sizeof("printf") - 3] = ((arg_len / 10) > 0) ? ('0' + arg_len / 10) : '$';
957 out_str[start + sizeof("printf") - 2] = '0' + arg_len % 10;
958 }
959 if (end == 0) {
960 /* No printf in source. */
961 return str;
962 }
963 }
964 /* Example: `pri$$1(2@ b)$` > `{int c_ = print_header(1, 2); c_ = print_data(c_, b); }` */
965 {
966 std::regex regex(R"(pri\$\$?(\d{1,2})\‍()");
967 out_str = std::regex_replace(out_str, regex, "{uint c_ = print_header($1u, ");
968 }
969 {
970 std::regex regex(R"(\@)");
971 out_str = std::regex_replace(out_str, regex, "); c_ = print_data(c_,");
972 }
973 {
974 std::regex regex(R"(\$)");
975 out_str = std::regex_replace(out_str, regex, "; }");
976 }
977 return out_str;
978 }
979
980 std::string assert_processing(const std::string &str, const std::string &filepath)
981 {
982 std::string filename = std::regex_replace(filepath, std::regex(R"((?:.*)\/(.*))"), "$1");
983 /* Example: `assert(i < 0)` > `if (!(i < 0)) { printf(...); }` */
984 std::regex regex(R"(\bassert\‍(([^;]*)\))");
985 std::string replacement;
986#ifdef WITH_GPU_SHADER_ASSERT
987 replacement = "if (!($1)) { printf(\"Assertion failed: ($1), file " + filename +
988 ", line %d, thread (%u,%u,%u).\\n\", __LINE__, GPU_THREAD.x, GPU_THREAD.y, "
989 "GPU_THREAD.z); }";
990#else
991 (void)filename;
992#endif
993 return std::regex_replace(str, regex, replacement);
994 }
995
996 /* String hash are outputted inside GLSL and needs to fit 32 bits. */
997 static uint32_t hash_string(const std::string &str)
998 {
999 uint64_t hash_64 = metadata::hash(str);
1000 uint32_t hash_32 = uint32_t(hash_64 ^ (hash_64 >> 32));
1001 return hash_32;
1002 }
1003
1004 void static_strings_parsing(const std::string &str)
1005 {
1006 using namespace metadata;
1007 /* Matches any character inside a pair of un-escaped quote. */
1008 std::regex regex(R"("(?:[^"])*")");
1009 regex_global_search(str, regex, [&](const std::smatch &match) {
1010 std::string format = match[0].str();
1011 metadata.printf_formats.emplace_back(metadata::PrintfFormat{hash_string(format), format});
1012 });
1013 }
1014
1015 std::string static_strings_mutation(std::string str)
1016 {
1017 /* Replaces all matches by the respective string hash. */
1018 for (const metadata::PrintfFormat &format : metadata.printf_formats) {
1019 const std::string &str_var = format.format;
1020 std::regex escape_regex(R"([\\\.\^\$\+\‍(\)\‍[\‍]\{\}\|\?\*])");
1021 std::string str_regex = std::regex_replace(str_var, escape_regex, "\\$&");
1022
1023 std::regex regex(str_regex);
1024 str = std::regex_replace(str, regex, std::to_string(hash_string(str_var)) + 'u');
1025 }
1026 return str;
1027 }
1028
1029 std::string enum_macro_injection(std::string str)
1030 {
1060 {
1061 /* Replaces all matches by the respective string hash. */
1062 std::regex regex(R"(enum\s+((\w+)\s*(?:\:\s*\w+\s*)?)\{(\n[^}]+)\n\};)");
1063 str = std::regex_replace(str,
1064 regex,
1065 "_enum_decl(_$1)$3 _enum_end\n"
1066 "#define $2 _enum_type(_$2)");
1067 }
1068 {
1069 /* Remove trailing comma if any. */
1070 std::regex regex(R"(,(\s*_enum_end))");
1071 str = std::regex_replace(str, regex, "$1");
1072 }
1073 return str;
1074 }
1075
1080 std::string default_argument_mutation(std::string str)
1081 {
1082 using namespace std;
1083 int match = 0;
1085 str, [&](int /*parenthesis_depth*/, int /*bracket_depth*/, char & /*c*/) { match++; });
1086
1087 if (match == 0) {
1088 /* No mutation to do. Early out as the following regex is expensive. */
1089 return str;
1090 }
1091
1092 vector<pair<string, string>> mutations;
1093
1094 int64_t line = 0;
1095
1096 /* Matches function definition. */
1097 regex regex_func(R"(\n((\w+)\s+(\w+)\s*\‍()([^{]+))");
1098 regex_global_search(str, regex_func, [&](const smatch &match) {
1099 const string prefix = match[1].str();
1100 const string return_type = match[2].str();
1101 const string func_name = match[3].str();
1102 const string args = get_content_between_balanced_pair('(' + match[4].str(), '(', ')');
1103 const string suffix = ")\n{";
1104
1105 int64_t lines_in_content = line_count(match[0].str());
1106 line += line_count(match.prefix().str()) + lines_in_content;
1107
1108 if (args.find('=') == string::npos) {
1109 return;
1110 }
1111
1112 const bool has_non_void_return_type = return_type != "void";
1113
1114 string line_directive = "#line " + to_string(line - lines_in_content + 2) + "\n";
1115
1116 vector<string> args_split = split_string_not_between_balanced_pair(args, ',', '(', ')');
1117 string overloads;
1118 string args_defined;
1119 string args_called;
1120
1121 /* Rewrite original definition without defaults. */
1122 string with_default = match[0].str();
1123 string no_default = with_default;
1124
1125 for (const string &arg : args_split) {
1126 regex regex(R"(((?:const )?\w+)\s+(\w+)( = (.+))?)");
1127 smatch match;
1128 regex_search(arg, match, regex);
1129
1130 string arg_type = match[1].str();
1131 string arg_name = match[2].str();
1132 string arg_assign = match[3].str();
1133 string arg_value = match[4].str();
1134
1135 if (!arg_value.empty()) {
1136 string body = func_name + "(" + args_called + arg_value + ");";
1137 if (has_non_void_return_type) {
1138 body = " return " + body;
1139 }
1140 else {
1141 body = " " + body;
1142 }
1143
1144 overloads = line_directive + prefix + args_defined + suffix + '\n' + line_directive +
1145 body + "\n}\n" + overloads;
1146
1147 replace_all(no_default, arg_assign, "");
1148 }
1149 if (!args_defined.empty()) {
1150 args_defined += ", ";
1151 }
1152 args_defined += arg_type + ' ' + arg_name;
1153 args_called += arg_name + ", ";
1154 }
1155
1156 /* Get function body to put the overload after it. */
1157 string body_content = '{' +
1158 get_content_between_balanced_pair(match.suffix().str(), '{', '}') +
1159 "}\n";
1160
1161 string last_line_directive =
1162 "#line " + to_string(line - lines_in_content + line_count(body_content) + 3) + "\n";
1163
1164 mutations.emplace_back(with_default + body_content,
1165 no_default + body_content + overloads + last_line_directive);
1166 });
1167
1168 for (auto mutation : mutations) {
1169 replace_all(str, mutation.first, mutation.second);
1170 }
1171 return str;
1172 }
1173
1174 /* Used to make GLSL matrix constructor compatible with MSL in pyGPU shaders.
1175 * This syntax is not supported in blender's own shaders. */
1176 std::string matrix_constructor_mutation(const std::string &str)
1177 {
1178 if (str.find("mat") == std::string::npos) {
1179 return str;
1180 }
1181 /* Example: `mat2(x)` > `mat2x2(x)` */
1182 std::regex regex_parenthesis(R"(\bmat([234])\‍()");
1183 std::string out = std::regex_replace(str, regex_parenthesis, "mat$1x$1(");
1184 /* Only process square matrices since this is the only types we overload the constructors. */
1185 /* Example: `mat2x2(x)` > `__mat2x2(x)` */
1186 std::regex regex(R"(\bmat(2x2|3x3|4x4)\‍()");
1187 return std::regex_replace(out, regex, "__mat$1(");
1188 }
1189
1190 /* To be run before `argument_decorator_macro_injection()`. */
1191 std::string argument_reference_mutation(std::string &str)
1192 {
1193 /* Next two REGEX checks are expensive. Check if they are needed at all. */
1194 bool valid_match = false;
1195 reference_search(str, [&](int parenthesis_depth, int bracket_depth, char &c) {
1196 /* Check if inside a function signature.
1197 * Check parenthesis_depth == 2 for array references. */
1198 if ((parenthesis_depth == 1 || parenthesis_depth == 2) && bracket_depth == 0) {
1199 valid_match = true;
1200 /* Modify the & into @ to make sure we only match these references in the regex
1201 * below. @ being forbidden in the shader language, it is safe to use a temp
1202 * character. */
1203 c = '@';
1204 }
1205 });
1206 if (!valid_match) {
1207 return str;
1208 }
1209 /* Remove parenthesis first. */
1210 /* Example: `float (&var)[2]` > `float &var[2]` */
1211 std::regex regex_parenthesis(R"((\w+ )\‍(@(\w+)\))");
1212 std::string out = std::regex_replace(str, regex_parenthesis, "$1@$2");
1213 /* Example: `const float &var[2]` > `inout float var[2]` */
1214 std::regex regex(R"((?:const)?(\s*)(\w+)\s+\@(\w+)(\‍[\d*\‍])?)");
1215 return std::regex_replace(out, regex, "$1 inout $2 $3$4");
1216 }
1217
1218 /* To be run after `argument_reference_mutation()`. */
1219 std::string variable_reference_mutation(const std::string &str, report_callback report_error)
1220 {
1221 using namespace std;
1222 /* Processing regex and logic is expensive. Check if they are needed at all. */
1223 bool valid_match = false;
1224 string next_str = str;
1225 reference_search(next_str, [&](int parenthesis_depth, int /*bracket_depth*/, char &c) {
1226 /* Check if inside a function body. */
1227 if (parenthesis_depth == 0) {
1228 valid_match = true;
1229 /* Modify the & into @ to make sure we only match these references in the regex
1230 * below. @ being forbidden in the shader language, it is safe to use a temp
1231 * character. */
1232 c = '@';
1233 }
1234 });
1235 if (!valid_match) {
1236 return str;
1237 }
1238 string out_str;
1239 /* Example: `const float &var = value;` */
1240 regex regex_ref(R"(\ ?(?:const)?\s*\w+\s+\@(\w+) =\s*([^;]+);)");
1241
1242 smatch match;
1243 while (regex_search(next_str, match, regex_ref)) {
1244 const string definition = match[0].str();
1245 const string name = match[1].str();
1246 const string value = match[2].str();
1247 const string prefix = match.prefix().str();
1248 const string suffix = match.suffix().str();
1249
1250 out_str += prefix;
1251
1252 /* Assert definition doesn't contain any side effect. */
1253 if (value.find("++") != string::npos || value.find("--") != string::npos) {
1254 report_error(match, "Reference definitions cannot have side effects.");
1255 return str;
1256 }
1257 if (value.find("(") != string::npos) {
1258 report_error(match, "Reference definitions cannot contain function calls.");
1259 return str;
1260 }
1261 if (value.find("[") != string::npos) {
1262 const string index_var = get_content_between_balanced_pair(value, '[', ']');
1263
1264 if (index_var.find(' ') != string::npos) {
1265 report_error(match,
1266 "Array subscript inside reference declaration must be a single variable or "
1267 "a constant, not an expression.");
1268 return str;
1269 }
1270
1271 /* Add a space to avoid empty scope breaking the loop. */
1272 string scope_depth = " }";
1273 bool found_var = false;
1274 while (!found_var) {
1275 string scope = get_content_between_balanced_pair(out_str + scope_depth, '{', '}', true);
1276 scope_depth += '}';
1277
1278 if (scope.empty()) {
1279 break;
1280 }
1281 /* Remove nested scopes. Avoid variable shadowing to mess with the detection. */
1282 scope = regex_replace(scope, regex(R"(\{[^\}]*\})"), "{}");
1283 /* Search if index variable definition qualifies it as `const`. */
1284 regex regex_definition(R"((const)? \w+ )" + index_var + " =");
1285 smatch match_definition;
1286 if (regex_search(scope, match_definition, regex_definition)) {
1287 found_var = true;
1288 if (match_definition[1].matched == false) {
1289 report_error(match, "Array subscript variable must be declared as const qualified.");
1290 return str;
1291 }
1292 }
1293 }
1294 if (!found_var) {
1295 report_error(match,
1296 "Cannot locate array subscript variable declaration. "
1297 "If it is a global variable, assign it to a temporary const variable for "
1298 "indexing inside the reference.");
1299 return str;
1300 }
1301 }
1302
1303 /* Find scope this definition is active in. */
1304 const string scope = get_content_between_balanced_pair('{' + suffix, '{', '}');
1305 if (scope.empty()) {
1306 report_error(match, "Reference is defined inside a global or unterminated scope.");
1307 return str;
1308 }
1309 string original = definition + scope;
1310 string modified = original;
1311
1312 /* Replace definition by nothing. Keep number of lines. */
1313 string newlines(line_count(definition), '\n');
1314 replace_all(modified, definition, newlines);
1315 /* Replace every occurrence of the reference. Avoid matching other symbols like class members
1316 * and functions with the same name. */
1317 modified = regex_replace(
1318 modified, regex(R"(([^\.])\b)" + name + R"(\b([^(]))"), "$1" + value + "$2");
1319
1321 next_str = definition + suffix;
1322
1323 /* Replace whole modified scope in output string. */
1324 replace_all(next_str, original, modified);
1325 }
1326 out_str += next_str;
1327 return out_str;
1328 }
1329
1330 std::string argument_decorator_macro_injection(const std::string &str)
1331 {
1332 /* Example: `out float var[2]` > `out float _out_sta var _out_end[2]` */
1333 std::regex regex(R"((out|inout|in|shared)\s+(\w+)\s+(\w+))");
1334 return std::regex_replace(str, regex, "$1 $2 _$1_sta $3 _$1_end");
1335 }
1336
1337 std::string array_constructor_macro_injection(const std::string &str)
1338 {
1339 /* Example: `= float[2](0.0, 0.0)` > `= ARRAY_T(float) ARRAY_V(0.0, 0.0)` */
1340 std::regex regex(R"(=\s*(\w+)\s*\‍[[^\‍]]*\‍]\s*\‍()");
1341 return std::regex_replace(str, regex, "= ARRAY_T($1) ARRAY_V(");
1342 }
1343
1344 /* TODO(fclem): Too many false positive and false negative to be applied to python shaders. */
1345 void matrix_constructor_linting(const std::string &str, report_callback report_error)
1346 {
1347 /* The following regex is expensive. Do a quick early out scan. */
1348 if (str.find("mat") == std::string::npos && str.find("float") == std::string::npos) {
1349 return;
1350 }
1351 /* Example: `mat4(other_mat)`. */
1352 std::regex regex(R"(\s(?:mat(?:\d|\dx\d)|float\dx\d)\‍()");
1353 regex_global_search(str, regex, [&](const std::smatch &match) {
1354 std::string args = get_content_between_balanced_pair("(" + match.suffix().str(), '(', ')');
1355 int arg_count = split_string_not_between_balanced_pair(args, ',', '(', ')').size();
1356 bool has_floating_point_arg = args.find('.') != std::string::npos;
1357 /* TODO(fclem): Check if arg count matches matrix type. */
1358 if (arg_count != 1 || has_floating_point_arg) {
1359 return;
1360 }
1361 /* This only catches some invalid usage. For the rest, the CI will catch them. */
1362 const char *msg =
1363 "Matrix constructor is not cross API compatible. "
1364 "Use to_floatNxM to reshape the matrix or use other constructors instead.";
1365 report_error(match, msg);
1366 });
1367 }
1368
1369 /* Assume formatted source with our code style. Cannot be applied to python shaders. */
1370 void global_scope_constant_linting(const std::string &str, report_callback report_error)
1371 {
1372 /* Example: `const uint global_var = 1u;`. Matches if not indented (i.e. inside a scope). */
1373 std::regex regex(R"(const \w+ \w+ =)");
1374 regex_global_search(str, regex, [&](const std::smatch &match) {
1375 /* Positive look-behind is not supported in #std::regex. Do it manually. */
1376 if (match.prefix().str().back() == '\n') {
1377 const char *msg =
1378 "Global scope constant expression found. These get allocated per-thread in MSL. "
1379 "Use Macro's or uniforms instead.";
1380 report_error(match, msg);
1381 }
1382 });
1383 }
1384
1385 void quote_linting(const std::string &str, report_callback report_error)
1386 {
1387 std::regex regex(R"(["'])");
1388 regex_global_search(str, regex, [&](const std::smatch &match) {
1389 /* This only catches some invalid usage. For the rest, the CI will catch them. */
1390 const char *msg = "Quotes are forbidden in GLSL.";
1391 report_error(match, msg);
1392 });
1393 }
1394
1395 void array_constructor_linting(const std::string &str, report_callback report_error)
1396 {
1397 std::regex regex(R"(=\s*(\w+)\s*\‍[[^\‍]]*\‍]\s*\‍()");
1398 regex_global_search(str, regex, [&](const std::smatch &match) {
1399 /* This only catches some invalid usage. For the rest, the CI will catch them. */
1400 const char *msg =
1401 "Array constructor is not cross API compatible. Use type_array instead of type[].";
1402 report_error(match, msg);
1403 });
1404 }
1405
1406 template<typename ReportErrorF>
1407 void small_type_linting(const std::string &str, const ReportErrorF &report_error)
1408 {
1409 std::regex regex(R"(\su?(char|short|half)(2|3|4)?\s)");
1410 regex_global_search(str, regex, [&](const std::smatch &match) {
1411 report_error(match, "Small types are forbidden in shader interfaces.");
1412 });
1413 }
1414
1415 std::string threadgroup_variables_suffix()
1416 {
1417 if (shared_vars_.empty()) {
1418 return "";
1419 }
1420
1421 std::stringstream suffix;
1435 suffix << "\n";
1436 /* Arguments of the wrapper class constructor. */
1437 suffix << "#undef MSL_SHARED_VARS_ARGS\n";
1438 /* References assignment inside wrapper class constructor. */
1439 suffix << "#undef MSL_SHARED_VARS_ASSIGN\n";
1440 /* Declaration of threadgroup variables in entry point function. */
1441 suffix << "#undef MSL_SHARED_VARS_DECLARE\n";
1442 /* Arguments for wrapper class constructor call. */
1443 suffix << "#undef MSL_SHARED_VARS_PASS\n";
1444
1483 std::stringstream args, assign, declare, pass;
1484
1485 bool first = true;
1486 for (SharedVar &var : shared_vars_) {
1487 char sep = first ? ' ' : ',';
1488 /* */
1489 args << sep << "threadgroup " << var.type << "(&_" << var.name << ")" << var.array;
1490 assign << (first ? ':' : ',') << var.name << "(_" << var.name << ")";
1491 declare << "threadgroup " << var.type << ' ' << var.name << var.array << ";";
1492 pass << sep << var.name;
1493 first = false;
1494 }
1495
1496 suffix << "#define MSL_SHARED_VARS_ARGS " << args.str() << "\n";
1497 suffix << "#define MSL_SHARED_VARS_ASSIGN " << assign.str() << "\n";
1498 suffix << "#define MSL_SHARED_VARS_DECLARE " << declare.str() << "\n";
1499 suffix << "#define MSL_SHARED_VARS_PASS (" << pass.str() << ")\n";
1500 suffix << "\n";
1501
1502 return suffix.str();
1503 }
1504
1505 std::string line_directive_prefix(const std::string &filepath)
1506 {
1507 std::string filename = std::regex_replace(filepath, std::regex(R"((?:.*)\/(.*))"), "$1");
1508
1509 std::stringstream suffix;
1510 suffix << "#line 1 ";
1511#ifdef __APPLE__
1512 /* For now, only Metal supports filename in line directive.
1513 * There is no way to know the actual backend, so we assume Apple uses Metal. */
1514 /* TODO(fclem): We could make it work using a macro to choose between the filename and the hash
1515 * at runtime. i.e.: `FILENAME_MACRO(12546546541, 'filename.glsl')` This should work for both
1516 * MSL and GLSL. */
1517 if (!filename.empty()) {
1518 suffix << "\"" << filename << "\"";
1519 }
1520#else
1521 uint64_t hash_value = metadata::hash(filename);
1522 /* Fold the value so it fits the GLSL spec. */
1523 hash_value = (hash_value ^ (hash_value >> 32)) & (~uint64_t(0) >> 33);
1524 suffix << std::to_string(uint64_t(hash_value));
1525#endif
1526 suffix << "\n";
1527 return suffix.str();
1528 }
1529
1530 /* Made public for unit testing purpose. */
1531 public:
1532 static std::string get_content_between_balanced_pair(const std::string &input,
1533 char start_delimiter,
1534 char end_delimiter,
1535 const bool backwards = false)
1536 {
1537 int balance = 0;
1538 size_t start = std::string::npos;
1539 size_t end = std::string::npos;
1540
1541 if (backwards) {
1542 std::swap(start_delimiter, end_delimiter);
1543 }
1544
1545 for (size_t i = 0; i < input.length(); ++i) {
1546 size_t idx = backwards ? (input.length() - 1) - i : i;
1547 if (input[idx] == start_delimiter) {
1548 if (balance == 0) {
1549 start = idx;
1550 }
1551 balance++;
1552 }
1553 else if (input[idx] == end_delimiter) {
1554 balance--;
1555 if (balance == 0 && start != std::string::npos) {
1556 end = idx;
1557 if (backwards) {
1558 std::swap(start, end);
1559 }
1560 return input.substr(start + 1, end - start - 1);
1561 }
1562 }
1563 }
1564 return "";
1565 }
1566
1567 /* Replaces all occurrences of `from` by `to` between `start_delimiter`
1568 * and `end_delimiter` even inside nested delimiters pair. */
1569 static std::string replace_char_between_balanced_pair(const std::string &input,
1570 const char start_delimiter,
1571 const char end_delimiter,
1572 const char from,
1573 const char to)
1574 {
1575 int depth = 0;
1576
1577 std::string str = input;
1578 for (char &string_char : str) {
1579 if (string_char == start_delimiter) {
1580 depth++;
1581 }
1582 else if (string_char == end_delimiter) {
1583 depth--;
1584 }
1585 else if (depth > 0 && string_char == from) {
1586 string_char = to;
1587 }
1588 }
1589 return str;
1590 }
1591
1592 /* Function to split a string by a delimiter and return a vector of substrings. */
1593 static std::vector<std::string> split_string(const std::string &str, const char delimiter)
1594 {
1595 std::vector<std::string> substrings;
1596 std::stringstream ss(str);
1597 std::string item;
1598
1599 while (std::getline(ss, item, delimiter)) {
1600 substrings.push_back(item);
1601 }
1602 return substrings;
1603 }
1604
1605 /* Similar to split_string but only split if the delimiter is not between any pair_start and
1606 * pair_end. */
1607 static std::vector<std::string> split_string_not_between_balanced_pair(const std::string &str,
1608 const char delimiter,
1609 const char pair_start,
1610 const char pair_end)
1611 {
1612 const char safe_char = '@';
1613 const std::string safe_str = replace_char_between_balanced_pair(
1614 str, pair_start, pair_end, delimiter, safe_char);
1615 std::vector<std::string> split = split_string(safe_str, delimiter);
1616 for (std::string &str : split) {
1617 replace_all(str, safe_char, delimiter);
1618 }
1619 return split;
1620 }
1621
1622 static void replace_all(std::string &str, const std::string &from, const std::string &to)
1623 {
1624 if (from.empty()) {
1625 return;
1626 }
1627 size_t start_pos = 0;
1628 while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
1629 str.replace(start_pos, from.length(), to);
1630 start_pos += to.length();
1631 }
1632 }
1633
1634 static void replace_all(std::string &str, const char from, const char to)
1635 {
1636 for (char &string_char : str) {
1637 if (string_char == from) {
1638 string_char = to;
1639 }
1640 }
1641 }
1642
1643 static int64_t char_count(const std::string &str, char c)
1644 {
1645 return std::count(str.begin(), str.end(), c);
1646 }
1647
1648 static int64_t line_count(const std::string &str)
1649 {
1650 return char_count(str, '\n');
1651 }
1652
1653 /* Match any reference definition (e.g. `int &a = b`).
1654 * Call the callback function for each `&` character that matches a reference definition.
1655 * Expects the input `str` to be formatted with balanced parenthesis and curly brackets. */
1656 static void reference_search(std::string &str, std::function<void(int, int, char &)> callback)
1657 {
1659 str, '&', [&](size_t pos, int parenthesis_depth, int bracket_depth, char &c) {
1660 if (pos > 0 && pos <= str.length() - 2) {
1661 /* This is made safe by the previous check. */
1662 char prev_char = str[pos - 1];
1663 char next_char = str[pos + 1];
1664 /* Validate it is not an operator (`&`, `&&`, `&=`). */
1665 if (prev_char == ' ' || prev_char == '(') {
1666 if (next_char != ' ' && next_char != '\n' && next_char != '&' && next_char != '=') {
1667 callback(parenthesis_depth, bracket_depth, c);
1668 }
1669 }
1670 }
1671 });
1672 }
1673
1674 /* Match any default argument definition (e.g. `void func(int a = 0)`).
1675 * Call the callback function for each `=` character inside a function argument list.
1676 * Expects the input `str` to be formatted with balanced parenthesis and curly brackets. */
1677 static void default_argument_search(std::string &str,
1678 std::function<void(int, int, char &)> callback)
1679 {
1681 str, '=', [&](size_t pos, int parenthesis_depth, int bracket_depth, char &c) {
1682 if (pos > 0 && pos <= str.length() - 2) {
1683 /* This is made safe by the previous check. */
1684 char prev_char = str[pos - 1];
1685 char next_char = str[pos + 1];
1686 /* Validate it is not an operator (`==`, `<=`, `>=`). Expects formatted input. */
1687 if (prev_char == ' ' && next_char == ' ') {
1688 if (parenthesis_depth == 1 && bracket_depth == 0) {
1689 callback(parenthesis_depth, bracket_depth, c);
1690 }
1691 }
1692 }
1693 });
1694 }
1695
1696 /* Scan through a string matching for every occurrence of a character.
1697 * Calls the callback with the context in which the match occurs. */
1698 static void scopes_scan_for_char(std::string &str,
1699 char search_char,
1700 std::function<void(size_t, int, int, char &)> callback)
1701 {
1702 size_t pos = 0;
1703 int parenthesis_depth = 0;
1704 int bracket_depth = 0;
1705 for (char &c : str) {
1706 if (c == search_char) {
1707 callback(pos, parenthesis_depth, bracket_depth, c);
1708 }
1709 else if (c == '(') {
1710 parenthesis_depth++;
1711 }
1712 else if (c == ')') {
1713 parenthesis_depth--;
1714 }
1715 else if (c == '{') {
1716 bracket_depth++;
1717 }
1718 else if (c == '}') {
1719 bracket_depth--;
1720 }
1721 pos++;
1722 }
1723 }
1724};
1725
1726} // namespace blender::gpu::shader
void BLI_kdtree_nd_ balance(KDTree *tree) ATTR_NONNULL(1)
static void split(const char *text, const char *seps, char ***str, int *count)
void init()
long long int int64_t
unsigned long long int uint64_t
static void replace_all(std::string &str, const char from, const char to)
std::string process(SourceLanguage language, std::string str, const std::string &filename, bool do_parse_function, bool do_small_type_linting, report_callback report_error, metadata::Source &r_metadata)
static std::string get_content_between_balanced_pair(const std::string &input, char start_delimiter, char end_delimiter, const bool backwards=false)
std::string process(const std::string &str)
static void reference_search(std::string &str, std::function< void(int, int, char &)> callback)
static void default_argument_search(std::string &str, std::function< void(int, int, char &)> callback)
static std::string replace_char_between_balanced_pair(const std::string &input, const char start_delimiter, const char end_delimiter, const char from, const char to)
static void scopes_scan_for_char(std::string &str, char search_char, std::function< void(size_t, int, int, char &)> callback)
static SourceLanguage language_from_filename(const std::string &filename)
static int64_t char_count(const std::string &str, char c)
static int64_t line_count(const std::string &str)
static void replace_all(std::string &str, const std::string &from, const std::string &to)
static std::vector< std::string > split_string_not_between_balanced_pair(const std::string &str, const char delimiter, const char pair_start, const char pair_end)
static std::vector< std::string > split_string(const std::string &str, const char delimiter)
#define str(s)
uint pos
#define input
#define out
VecBase< bool, D > equal(VecOp< T, D >, VecOp< T, D >) RET
format
static constexpr uint64_t hash(const char *name)
const char * to_string(ShaderStage stage)
Definition mtl_shader.mm:52
#define hash
Definition noise_c.cc:154
static string line_directive(const SourceReplaceState &state, const string &path, const size_t line_number)
Definition path.cpp:797
std::string serialize(const std::string &function_name) const
std::vector< FunctionFormat > functions
std::vector< std::string > dependencies
std::vector< PrintfFormat > printf_formats
i
Definition text_draw.cc:230