Blender V4.3
gpu_shader_dependency.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
12#include <algorithm>
13#include <iomanip>
14#include <iostream>
15#include <regex>
16#include <sstream>
17
18#include "BLI_ghash.h"
19#include "BLI_map.hh"
20#include "BLI_string.h"
21#include "BLI_string_ref.hh"
22
26
27#include "GPU_context.hh"
28
29extern "C" {
30#define SHADER_SOURCE(datatoc, filename, filepath) extern char datatoc[];
31#include "glsl_compositor_source_list.h"
32#include "glsl_draw_source_list.h"
33#include "glsl_gpu_source_list.h"
34#ifdef WITH_OCIO
35# include "glsl_ocio_source_list.h"
36#endif
37#undef SHADER_SOURCE
38}
39
40namespace blender::gpu {
41
42using GPUPrintFormatMap = Map<uint32_t, shader::PrintfFormat>;
43using GPUSourceDictionnary = Map<StringRef, struct GPUSource *>;
44using GPUFunctionDictionnary = Map<StringRef, GPUFunction *>;
45
46struct GPUSource {
47 StringRefNull fullpath;
48 StringRefNull filename;
49 StringRefNull source;
50 Vector<GPUSource *> dependencies;
51 bool dependencies_init = false;
52 shader::BuiltinBits builtins = shader::BuiltinBits::NONE;
53 std::string processed_source;
54
55 GPUSource(const char *path,
56 const char *file,
57 const char *datatoc,
58 GPUFunctionDictionnary *g_functions,
59 GPUPrintFormatMap *g_formats)
60 : fullpath(path), filename(file), source(datatoc)
61 {
62 /* Scan for builtins. */
63 /* FIXME: This can trigger false positive caused by disabled #if blocks. */
64 /* TODO(fclem): Could be made faster by scanning once. */
65 if (source.find("gl_FragCoord", 0) != StringRef::not_found) {
66 builtins |= shader::BuiltinBits::FRAG_COORD;
67 }
68 if (source.find("gl_FrontFacing", 0) != StringRef::not_found) {
69 builtins |= shader::BuiltinBits::FRONT_FACING;
70 }
71 if (source.find("gl_GlobalInvocationID", 0) != StringRef::not_found) {
72 builtins |= shader::BuiltinBits::GLOBAL_INVOCATION_ID;
73 }
74 if (source.find("gl_InstanceID", 0) != StringRef::not_found) {
75 builtins |= shader::BuiltinBits::INSTANCE_ID;
76 }
77 if (source.find("gl_LocalInvocationID", 0) != StringRef::not_found) {
78 builtins |= shader::BuiltinBits::LOCAL_INVOCATION_ID;
79 }
80 if (source.find("gl_LocalInvocationIndex", 0) != StringRef::not_found) {
81 builtins |= shader::BuiltinBits::LOCAL_INVOCATION_INDEX;
82 }
83 if (source.find("gl_NumWorkGroup", 0) != StringRef::not_found) {
84 builtins |= shader::BuiltinBits::NUM_WORK_GROUP;
85 }
86 if (source.find("gl_PointCoord", 0) != StringRef::not_found) {
87 builtins |= shader::BuiltinBits::POINT_COORD;
88 }
89 if (source.find("gl_PointSize", 0) != StringRef::not_found) {
90 builtins |= shader::BuiltinBits::POINT_SIZE;
91 }
92 if (source.find("gl_PrimitiveID", 0) != StringRef::not_found) {
93 builtins |= shader::BuiltinBits::PRIMITIVE_ID;
94 }
95 if (source.find("gl_VertexID", 0) != StringRef::not_found) {
96 builtins |= shader::BuiltinBits::VERTEX_ID;
97 }
98 if (source.find("gl_WorkGroupID", 0) != StringRef::not_found) {
99 builtins |= shader::BuiltinBits::WORK_GROUP_ID;
100 }
101 if (source.find("gl_WorkGroupSize", 0) != StringRef::not_found) {
102 builtins |= shader::BuiltinBits::WORK_GROUP_SIZE;
103 }
104 /* TODO(fclem): We could do that at compile time. */
105 /* Limit to shared header files to avoid the temptation to use C++ syntax in .glsl files. */
106 if (filename.endswith(".h") || filename.endswith(".hh")) {
107 enum_preprocess();
108 quote_preprocess();
109 small_types_check();
110 }
111 else {
112 if (source.find("'") != StringRef::not_found) {
113 char_literals_preprocess();
114 }
115#ifndef NDEBUG
116 if (source.find("drw_print") != StringRef::not_found) {
117 string_preprocess();
118 }
119 if ((source.find("drw_debug_") != StringRef::not_found) &&
120 /* Avoid this file as it is a false positive match (matches "drw_debug_print_buf"). */
121 filename != "draw_debug_print_display_vert.glsl" &&
122 /* Avoid these two files where it makes no sense to add the dependency. */
123 !ELEM(filename, "common_debug_draw_lib.glsl", "draw_debug_draw_display_vert.glsl"))
124 {
125 builtins |= shader::BuiltinBits::USE_DEBUG_DRAW;
126 }
127#endif
128#if GPU_SHADER_PRINTF_ENABLE
129 if (source.find("printf") != StringRef::not_found) {
130 printf_preprocess(g_formats);
131 builtins |= shader::BuiltinBits::USE_PRINTF;
132 }
133#else
134 (void)g_formats;
135#endif
136 check_no_quotes();
137 }
138
139 if (is_from_material_library()) {
140 material_functions_parse(g_functions);
141 }
142 };
143
144 static bool is_in_comment(const StringRef &input, int64_t offset)
145 {
146 return (input.rfind("/*", offset) > input.rfind("*/", offset)) ||
147 (input.rfind("//", offset) > input.rfind("\n", offset));
148 }
149
150 template<bool check_whole_word = true, bool reversed = false, typename T>
151 static int64_t find_str(const StringRef &input, const T keyword, int64_t offset = 0)
152 {
153 while (true) {
154 if constexpr (reversed) {
155 offset = input.rfind(keyword, offset);
156 }
157 else {
158 offset = input.find(keyword, offset);
159 }
160 if (offset > 0) {
161 if constexpr (check_whole_word) {
162 /* Fix false positive if something has "enum" as suffix. */
163 char previous_char = input[offset - 1];
164 if (!ELEM(previous_char, '\n', '\t', ' ', ':', '(', ',')) {
165 offset += (reversed) ? -1 : 1;
166 continue;
167 }
168 }
169 /* Fix case where the keyword is in a comment. */
170 if (is_in_comment(input, offset)) {
171 offset += (reversed) ? -1 : 1;
172 continue;
173 }
174 }
175 return offset;
176 }
177 }
178
179#define find_keyword find_str<true, false>
180#define rfind_keyword find_str<true, true>
181#define find_token find_str<false, false>
182#define rfind_token find_str<false, true>
183
184 void print_error(const StringRef &input, int64_t offset, const StringRef message)
185 {
186 StringRef sub = input.substr(0, offset);
187 int64_t line_number = std::count(sub.begin(), sub.end(), '\n') + 1;
188 int64_t line_end = input.find("\n", offset);
189 int64_t line_start = input.rfind("\n", offset) + 1;
190 int64_t char_number = offset - line_start + 1;
191
192 /* TODO Use clog. */
193
194 std::cerr << fullpath << ":" << line_number << ":" << char_number;
195
196 std::cerr << " error: " << message << "\n";
197 std::cerr << std::setw(5) << line_number << " | "
198 << input.substr(line_start, line_end - line_start) << "\n";
199 std::cerr << " | ";
200 for (int64_t i = 0; i < char_number - 1; i++) {
201 std::cerr << " ";
202 }
203 std::cerr << "^\n";
204 }
205
206#define CHECK(test_value, str, ofs, msg) \
207 if ((test_value) == -1) { \
208 print_error(str, ofs, msg); \
209 continue; \
210 }
211
217 void check_no_quotes()
218 {
219#ifndef NDEBUG
220 int64_t pos = -1;
221 do {
222 pos = source.find('"', pos + 1);
223 if (pos == -1) {
224 break;
225 }
226 if (!is_in_comment(source, pos)) {
227 print_error(source, pos, "Quote characters are forbidden in GLSL files");
228 }
229 } while (true);
230#endif
231 }
232
238 void quote_preprocess()
239 {
240 if (source.find_first_of('"') == -1) {
241 return;
242 }
243
244 processed_source = source;
245 std::replace(processed_source.begin(), processed_source.end(), '"', ' ');
246
247 source = processed_source.c_str();
248 }
249
253 void small_types_check()
254 {
255#ifndef NDEBUG
256 auto check_type = [&](StringRefNull type_str) {
257 int64_t cursor = -1;
258 while (true) {
259 cursor = find_keyword(source, type_str, cursor + 1);
260 if (cursor == -1) {
261 break;
262 }
263 print_error(source, cursor, "small types are forbidden in shader interfaces");
264 }
265 };
266 check_type("char ");
267 check_type("char2 ");
268 check_type("char3 ");
269 check_type("char4 ");
270 check_type("uchar ");
271 check_type("uchar2 ");
272 check_type("uchar3 ");
273 check_type("uchar4 ");
274 check_type("short ");
275 check_type("short2 ");
276 check_type("short3 ");
277 check_type("short4 ");
278 check_type("ushort ");
279 check_type("ushort2 ");
280 check_type("ushort3 ");
281 check_type("ushort4 ");
282 check_type("half ");
283 check_type("half2 ");
284 check_type("half3 ");
285 check_type("half4 ");
286#endif
287 }
288
322 void enum_preprocess()
323 {
324 const StringRefNull input = source;
325 std::string output;
326 int64_t cursor = -1;
327 int64_t last_pos = 0;
328 const bool is_cpp = filename.endswith(".hh");
329
330 /* Metal Shading language is based on C++ and supports C++-style enumerations.
331 * For these cases, we do not need to perform auto-replacement. */
332 if (is_cpp && GPU_backend_get_type() == GPU_BACKEND_METAL) {
333 return;
334 }
335
336 while (true) {
337 cursor = find_keyword(input, "enum ", cursor + 1);
338 if (cursor == -1) {
339 break;
340 }
341 /* Skip matches like `typedef enum myEnum myType;` */
342 if (cursor >= 8 && input.substr(cursor - 8, 8) == "typedef ") {
343 continue;
344 }
345 /* Output anything between 2 enums blocks. */
346 output += input.substr(last_pos, cursor - last_pos);
347
348 /* Extract enum type name. */
349 int64_t name_start = input.find(" ", cursor);
350
351 int64_t values_start = find_token(input, '{', cursor);
352 CHECK(values_start, input, cursor, "Malformed enum class. Expected \'{\' after typename.");
353
354 StringRef enum_name = input.substr(name_start, values_start - name_start);
355 if (is_cpp) {
356 int64_t name_end = find_token(enum_name, ":");
357 CHECK(name_end, input, name_start, "Expected \':\' after C++ enum name.");
358
359 int64_t underlying_type = find_keyword(enum_name, "uint32_t", name_end);
360 CHECK(underlying_type, input, name_start, "C++ enums needs uint32_t underlying type.");
361
362 enum_name = input.substr(name_start, name_end);
363 }
364
365 output += "#define " + enum_name + " uint\n";
366
367 /* Extract enum values. */
368 int64_t values_end = find_token(input, '}', values_start);
369 CHECK(values_end, input, cursor, "Malformed enum class. Expected \'}\' after values.");
370
371 /* Skip opening brackets. */
372 values_start += 1;
373
374 StringRef enum_values = input.substr(values_start, values_end - values_start);
375
376 /* Really poor check. Could be done better. */
377 int64_t token = find_token(enum_values, '{');
378 int64_t not_found = (token == -1) ? 0 : -1;
379 CHECK(not_found, input, values_start + token, "Unexpected \'{\' token inside enum values.");
380
381 /* Do not capture the comma after the last value (if present). */
382 int64_t last_equal = rfind_token(enum_values, '=', values_end);
383 int64_t last_comma = rfind_token(enum_values, ',', values_end);
384 if (last_comma > last_equal) {
385 enum_values = input.substr(values_start, last_comma);
386 }
387
388 output += "const uint " + enum_values;
389
390 int64_t semicolon_found = (input[values_end + 1] == ';') ? 0 : -1;
391 CHECK(semicolon_found, input, values_end + 1, "Expected \';\' after enum type declaration.");
392
393 /* Skip the curly bracket but not the semicolon. */
394 cursor = last_pos = values_end + 1;
395 }
396 /* If nothing has been changed, do not allocate processed_source. */
397 if (last_pos == 0) {
398 return;
399 }
400
401 if (last_pos != 0) {
402 output += input.substr(last_pos);
403 }
404
405 processed_source = output;
406 source = processed_source.c_str();
407 };
408
409 void material_functions_parse(GPUFunctionDictionnary *g_functions)
410 {
411 const StringRefNull input = source;
412
413 const char whitespace_chars[] = " \r\n\t";
414
415 auto function_parse = [&](const StringRef input,
416 int64_t &cursor,
417 StringRef &out_return_type,
418 StringRef &out_name,
419 StringRef &out_args) -> bool {
420 cursor = find_keyword(input, "void ", cursor + 1);
421 if (cursor == -1) {
422 return false;
423 }
424 int64_t arg_start = find_token(input, '(', cursor);
425 if (arg_start == -1) {
426 return false;
427 }
428 int64_t arg_end = find_token(input, ')', arg_start);
429 if (arg_end == -1) {
430 return false;
431 }
432 int64_t body_start = find_token(input, '{', arg_end);
433 int64_t next_semicolon = find_token(input, ';', arg_end);
434 if (body_start != -1 && next_semicolon != -1 && body_start > next_semicolon) {
435 /* Assert no prototypes but could also just skip them. */
436 BLI_assert_msg(false, "No prototypes allowed in node GLSL libraries.");
437 }
438 int64_t name_start = input.find_first_not_of(whitespace_chars, input.find(' ', cursor));
439 if (name_start == -1) {
440 return false;
441 }
442 int64_t name_end = input.find_last_not_of(whitespace_chars, arg_start);
443 if (name_end == -1) {
444 return false;
445 }
446 /* Only support void type for now. */
447 out_return_type = "void";
448 out_name = input.substr(name_start, name_end - name_start);
449 out_args = input.substr(arg_start + 1, arg_end - (arg_start + 1));
450 return true;
451 };
452
453 auto keyword_parse = [&](const StringRef str, int64_t &cursor) -> StringRef {
454 int64_t keyword_start = str.find_first_not_of(whitespace_chars, cursor);
455 if (keyword_start == -1) {
456 /* No keyword found. */
457 return str.substr(0, 0);
458 }
459 int64_t keyword_end = str.find_first_of(whitespace_chars, keyword_start);
460 if (keyword_end == -1) {
461 /* Last keyword. */
462 keyword_end = str.size();
463 }
464 cursor = keyword_end + 1;
465 return str.substr(keyword_start, keyword_end - keyword_start);
466 };
467
468 auto arg_parse = [&](const StringRef str,
469 int64_t &cursor,
470 StringRef &out_qualifier,
471 StringRef &out_type,
472 StringRef &out_name) -> bool {
473 int64_t arg_start = cursor + 1;
474 if (arg_start >= str.size()) {
475 return false;
476 }
477 cursor = find_token(str, ',', arg_start);
478 if (cursor == -1) {
479 /* Last argument. */
480 cursor = str.size();
481 }
482 const StringRef arg = str.substr(arg_start, cursor - arg_start);
483
484 int64_t keyword_cursor = 0;
485 out_qualifier = keyword_parse(arg, keyword_cursor);
486 out_type = keyword_parse(arg, keyword_cursor);
487 out_name = keyword_parse(arg, keyword_cursor);
488 if (out_name.is_empty()) {
489 /* No qualifier case. */
490 out_name = out_type;
491 out_type = out_qualifier;
492 out_qualifier = arg.substr(0, 0);
493 }
494 return true;
495 };
496
497 int64_t cursor = -1;
498 StringRef func_return_type, func_name, func_args;
499 while (function_parse(input, cursor, func_return_type, func_name, func_args)) {
500 /* Main functions needn't be handled because they are the entry point of the shader. */
501 if (func_name == "main") {
502 continue;
503 }
504
505 GPUFunction *func = MEM_new<GPUFunction>(__func__);
506 func_name.copy(func->name, sizeof(func->name));
507 func->source = reinterpret_cast<void *>(this);
508
509 bool insert = g_functions->add(func->name, func);
510
511 /* NOTE: We allow overloading non void function, but only if the function comes from the
512 * same file. Otherwise the dependency system breaks. */
513 if (!insert) {
514 GPUSource *other_source = reinterpret_cast<GPUSource *>(
515 g_functions->lookup(func_name)->source);
516 if (other_source != this) {
517 print_error(input,
518 source.find(func_name),
519 "Function redefinition or overload in two different files ...");
521 input, other_source->source.find(func_name), "... previous definition was here");
522 }
523 else {
524 /* Non-void function overload. */
525 MEM_delete(func);
526 }
527 continue;
528 }
529
530 if (func_return_type != "void") {
531 continue;
532 }
533
534 func->totparam = 0;
535 int64_t args_cursor = -1;
536 StringRef arg_qualifier, arg_type, arg_name;
537 while (arg_parse(func_args, args_cursor, arg_qualifier, arg_type, arg_name)) {
538
539 if (func->totparam >= ARRAY_SIZE(func->paramtype)) {
540 print_error(input, source.find(func_name), "Too much parameter in function");
541 break;
542 }
543
544 auto parse_qualifier = [](StringRef qualifier) -> GPUFunctionQual {
545 if (qualifier == "out") {
546 return FUNCTION_QUAL_OUT;
547 }
548 if (qualifier == "inout") {
549 return FUNCTION_QUAL_INOUT;
550 }
551 return FUNCTION_QUAL_IN;
552 };
553
554 auto parse_type = [](StringRef type) -> eGPUType {
555 if (type == "float") {
556 return GPU_FLOAT;
557 }
558 if (type == "vec2") {
559 return GPU_VEC2;
560 }
561 if (type == "vec3") {
562 return GPU_VEC3;
563 }
564 if (type == "vec4") {
565 return GPU_VEC4;
566 }
567 if (type == "mat3") {
568 return GPU_MAT3;
569 }
570 if (type == "mat4") {
571 return GPU_MAT4;
572 }
573 if (type == "sampler1DArray") {
574 return GPU_TEX1D_ARRAY;
575 }
576 if (type == "sampler2DArray") {
577 return GPU_TEX2D_ARRAY;
578 }
579 if (type == "sampler2D") {
580 return GPU_TEX2D;
581 }
582 if (type == "sampler3D") {
583 return GPU_TEX3D;
584 }
585 if (type == "Closure") {
586 return GPU_CLOSURE;
587 }
588 return GPU_NONE;
589 };
590
591 func->paramqual[func->totparam] = parse_qualifier(arg_qualifier);
592 func->paramtype[func->totparam] = parse_type(arg_type);
593
594 if (func->paramtype[func->totparam] == GPU_NONE) {
595 std::string err = "Unknown parameter type \"" + arg_type + "\"";
596 int64_t err_ofs = source.find(func_name);
597 err_ofs = find_keyword(source, arg_name, err_ofs);
598 err_ofs = rfind_keyword(source, arg_type, err_ofs);
599 print_error(input, err_ofs, err);
600 }
601
602 func->totparam++;
603 }
604 }
605 }
606
607 void char_literals_preprocess()
608 {
609 const StringRefNull input = source;
610 std::stringstream output;
611 int64_t cursor = -1;
612 int64_t last_pos = 0;
613
614 while (true) {
615 cursor = find_token(input, '\'', cursor + 1);
616 if (cursor == -1) {
617 break;
618 }
619 /* Output anything between 2 print statement. */
620 output << input.substr(last_pos, cursor - last_pos);
621
622 /* Extract string. */
623 int64_t char_start = cursor + 1;
624 int64_t char_end = find_token(input, '\'', char_start);
625 CHECK(char_end, input, cursor, "Malformed char literal. Missing ending `'`.");
626
627 StringRef input_char = input.substr(char_start, char_end - char_start);
628 if (input_char.is_empty()) {
629 CHECK(-1, input, cursor, "Malformed char literal. Empty character constant");
630 }
631
632 uint8_t char_value = input_char[0];
633
634 if (input_char[0] == '\\') {
635 if (input_char[1] == 'n') {
636 char_value = '\n';
637 }
638 else {
639 CHECK(-1, input, cursor, "Unsupported escaped character");
640 }
641 }
642 else {
643 if (input_char.size() > 1) {
644 CHECK(-1, input, cursor, "Malformed char literal. Multi-character character constant");
645 }
646 }
647
648 char hex[8];
649 SNPRINTF(hex, "0x%.2Xu", char_value);
650 output << hex;
651
652 cursor = last_pos = char_end + 1;
653 }
654 /* If nothing has been changed, do not allocate processed_source. */
655 if (last_pos == 0) {
656 return;
657 }
658
659 if (last_pos != 0) {
660 output << input.substr(last_pos);
661 }
662 processed_source = output.str();
663 source = processed_source.c_str();
664 }
665
666 /* Replace print(string) by equivalent drw_print_char4() sequence. */
667 void string_preprocess()
668 {
669 const StringRefNull input = source;
670 std::stringstream output;
671 int64_t cursor = -1;
672 int64_t last_pos = 0;
673
674 while (true) {
675 cursor = find_keyword(input, "drw_print", cursor + 1);
676 if (cursor == -1) {
677 break;
678 }
679
680 bool do_endl = false;
681 StringRef func = input.substr(cursor);
682 if (func.startswith("drw_print(")) {
683 do_endl = true;
684 }
685 else if (func.startswith("drw_print_no_endl(")) {
686 do_endl = false;
687 }
688 else {
689 continue;
690 }
691
692 /* Output anything between 2 print statement. */
693 output << input.substr(last_pos, cursor - last_pos);
694
695 /* Extract string. */
696 int64_t str_start = input.find('(', cursor) + 1;
697 int64_t semicolon = find_token(input, ';', str_start + 1);
698 CHECK(semicolon, input, cursor, "Malformed print(). Missing `;` .");
699 int64_t str_end = rfind_token(input, ')', semicolon);
700 if (str_end < str_start) {
701 CHECK(-1, input, cursor, "Malformed print(). Missing closing `)` .");
702 }
703
704 std::stringstream sub_output;
705 StringRef input_args = input.substr(str_start, str_end - str_start);
706
707 auto print_string = [&](std::string str) -> int {
708 size_t len_before_pad = str.length();
709 /* Pad string to uint size. */
710 while (str.length() % 4 != 0) {
711 str += " ";
712 }
713 /* Keep everything in one line to not mess with the shader logs. */
714 sub_output << "/* " << str << "*/";
715 sub_output << "drw_print_string_start(" << len_before_pad << ");";
716 for (size_t i = 0; i < len_before_pad; i += 4) {
717 uint8_t chars[4] = {*(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 0),
718 *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 1),
719 *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 2),
720 *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 3)};
721 if (i + 4 > len_before_pad) {
722 chars[len_before_pad - i] = '\0';
723 }
724 char uint_hex[12];
725 SNPRINTF(uint_hex, "0x%.2X%.2X%.2X%.2Xu", chars[3], chars[2], chars[1], chars[0]);
726 sub_output << "drw_print_char4(" << StringRefNull(uint_hex) << ");";
727 }
728 return 0;
729 };
730
731 std::string func_args = input_args;
732 /* Workaround to support function call inside prints. We replace commas by a non control
733 * character `$` in order to use simpler regex later. */
734 bool string_scope = false;
735 int func_scope = 0;
736 for (char &c : func_args) {
737 if (c == '"') {
738 string_scope = !string_scope;
739 }
740 else if (!string_scope) {
741 if (c == '(') {
742 func_scope++;
743 }
744 else if (c == ')') {
745 func_scope--;
746 }
747 else if (c == ',' && func_scope != 0) {
748 c = '$';
749 }
750 }
751 }
752
753 const bool print_as_variable = (input_args[0] != '"') && find_token(input_args, ',') == -1;
754 if (print_as_variable) {
755 /* Variable or expression debugging. */
756 std::string arg = input_args;
757 /* Pad align most values. */
758 while (arg.length() % 4 != 0) {
759 arg += " ";
760 }
761 print_string(arg);
762 print_string("= ");
763 sub_output << "drw_print_value(" << input_args << ");";
764 }
765 else {
766 const std::regex arg_regex(
767 /* String args. */
768 "[\\s]*\"([^\r\n\t\f\v\"]*)\""
769 /* OR. */
770 "|"
771 /* value args. */
772 "([^,]+)");
773 std::smatch args_match;
774 std::string::const_iterator args_search_start(func_args.cbegin());
775 while (std::regex_search(args_search_start, func_args.cend(), args_match, arg_regex)) {
776 args_search_start = args_match.suffix().first;
777 std::string arg_string = args_match[1].str();
778 std::string arg_val = args_match[2].str();
779
780 if (arg_string.empty()) {
781 for (char &c : arg_val) {
782 if (c == '$') {
783 c = ',';
784 }
785 }
786 sub_output << "drw_print_value(" << arg_val << ");";
787 }
788 else {
789 print_string(arg_string);
790 }
791 }
792 }
793
794 if (do_endl) {
795 sub_output << "drw_print_newline();";
796 }
797
798 output << sub_output.str();
799
800 cursor = last_pos = str_end + 1;
801 }
802 /* If nothing has been changed, do not allocate processed_source. */
803 if (last_pos == 0) {
804 return;
805 }
806
807 if (filename != "common_debug_print_lib.glsl") {
808 builtins |= shader::BuiltinBits::USE_DEBUG_PRINT;
809 }
810
811 if (last_pos != 0) {
812 output << input.substr(last_pos);
813 }
814 processed_source = output.str();
815 source = processed_source.c_str();
816 }
817
824 void printf_preprocess(GPUPrintFormatMap *format_map)
825 {
826 const StringRefNull input = source;
827 std::stringstream output;
828 int64_t cursor = -1;
829 int64_t last_pos = 0;
830
831 while (true) {
832 cursor = find_keyword(input, "printf(", cursor + 1);
833 if (cursor == -1) {
834 break;
835 }
836
837 /* Output anything between 2 print statement. */
838 output << input.substr(last_pos, cursor - last_pos);
839
840 /* Extract string. */
841 int64_t str_start = input.find('(', cursor) + 1;
842 int64_t semicolon = find_token(input, ';', str_start + 1);
843 CHECK(semicolon, input, cursor, "Malformed printf(). Missing `;` .");
844 int64_t str_end = rfind_token(input, ')', semicolon);
845 if (str_end < str_start) {
846 CHECK(-1, input, cursor, "Malformed printf(). Missing closing `)` .");
847 }
848
849 StringRef input_args = input.substr(str_start, str_end - str_start);
850
851 std::string func_args = input_args;
852 /* Workaround to support function call inside prints. We replace commas by a non control
853 * character `$` in order to use simpler regex later.
854 * Modify `"func %d,\n", func(a, b)` into `"func %d,\n", func(a$ b)` */
855 bool string_scope = false;
856 int func_scope = 0;
857 for (char &c : func_args) {
858 if (c == '"') {
859 string_scope = !string_scope;
860 }
861 else if (!string_scope) {
862 if (c == '(') {
863 func_scope++;
864 }
865 else if (c == ')') {
866 func_scope--;
867 }
868 else if (c == ',' && func_scope != 0) {
869 c = '$';
870 }
871 }
872 }
873
874 std::string format;
875 Vector<std::string> arguments;
876 {
877 const std::regex arg_regex(
878 /* String args. */
879 "[\\s]*\"([^\r\n\t\f\v\"]*)\""
880 /* OR. */
881 "|"
882 /* value args. */
883 "([^,]+)");
884 std::smatch args_match;
885 std::string::const_iterator args_search_start(func_args.cbegin());
886 while (std::regex_search(args_search_start, func_args.cend(), args_match, arg_regex)) {
887 args_search_start = args_match.suffix().first;
888 std::string arg_string = args_match[1].str();
889 std::string arg_val = args_match[2].str();
890
891 if (!arg_string.empty()) {
892 if (!format.empty()) {
893 CHECK(-1, input, cursor, "Format string is not the only string arg.");
894 }
895 if (!arguments.is_empty()) {
896 CHECK(-1, input, cursor, "Format string is not first argument.");
897 }
898 format = arg_string;
899 }
900 else {
901 for (char &c : arg_val) {
902 /* Mutate back functions arguments.*/
903 if (c == '$') {
904 c = ',';
905 }
906 }
907 arguments.append(arg_val);
908 }
909 }
910 }
911
912 if (format.empty()) {
913 CHECK(-1, input, cursor, "No format string found.");
914 return;
915 }
916 int format_arg_count = std::count(format.begin(), format.end(), '%');
917 if (format_arg_count > arguments.size()) {
918 CHECK(-1, input, cursor, "printf call has not enough arguments.");
919 return;
920 }
921 if (format_arg_count < arguments.size()) {
922 CHECK(-1, input, cursor, "printf call has too many arguments.");
923 return;
924 }
925
926 uint64_t format_hash_64 = hash_string(format);
927 uint32_t format_hash = uint32_t((format_hash_64 >> 32) ^ format_hash_64);
928
929 if (format_map->contains(format_hash)) {
930 if (format_map->lookup(format_hash).format_str != format) {
931 CHECK(-1, input, cursor, "printf format hash collision.");
932 }
933 else {
934 /* The format map already have the same format. */
935 }
936 }
937 else {
938 shader::PrintfFormat fmt;
939 /* Save for hash collision comparison. */
940 fmt.format_str = format;
941
942 /* Escape characters replacement. Do the most common ones. */
943 format = std::regex_replace(format, std::regex(R"(\\n)"), "\n");
944 format = std::regex_replace(format, std::regex(R"(\\v)"), "\v");
945 format = std::regex_replace(format, std::regex(R"(\\t)"), "\t");
946 format = std::regex_replace(format, std::regex(R"(\\')"), "\'");
947 format = std::regex_replace(format, std::regex(R"(\\")"), "\"");
948 format = std::regex_replace(format, std::regex(R"(\\\\)"), "\\");
949
950 shader::PrintfFormat::Block::ArgumentType type =
951 shader::PrintfFormat::Block::ArgumentType::NONE;
952 int64_t start = 0, end = 0;
953 while ((end = format.find_first_of('%', start + 1)) != -1) {
954 /* Add the previous block without the newly found % character. */
955 fmt.format_blocks.append({type, format.substr(start, end - start)});
956 /* Format type of the next block. */
957 /* TODO(fclem): This doesn't support advance formats like `%3.2f`. */
958 switch (format[end + 1]) {
959 case 'x':
960 case 'u':
961 type = shader::PrintfFormat::Block::ArgumentType::UINT;
962 break;
963 case 'd':
964 type = shader::PrintfFormat::Block::ArgumentType::INT;
965 break;
966 case 'f':
967 type = shader::PrintfFormat::Block::ArgumentType::FLOAT;
968 break;
969 default:
970 BLI_assert_msg(0, "Printing format unsupported");
971 break;
972 }
973 /* Start of the next block. */
974 start = end;
975 }
976 fmt.format_blocks.append({type, format.substr(start, format.size() - start)});
977
978 format_map->add(format_hash, fmt);
979 }
980
981 std::string sub_output = "print_header(" + std::to_string(format_hash) + "u, " +
982 std::to_string(format_arg_count) + "u)";
983 for (std::string &arg : arguments) {
984 sub_output = "print_data(" + sub_output + ", " + arg + ")";
985 }
986
987 output << sub_output << ";\n";
988
989 cursor = last_pos = str_end + 1;
990 }
991 /* If nothing has been changed, do not allocate processed_source. */
992 if (last_pos == 0) {
993 return;
994 }
995
996 if (last_pos != 0) {
997 output << input.substr(last_pos);
998 }
999 processed_source = output.str();
1000 source = processed_source.c_str();
1001 }
1002
1003#undef find_keyword
1004#undef rfind_keyword
1005#undef find_token
1006#undef rfind_token
1007
1008 /* Return 1 one error. */
1009 int init_dependencies(const GPUSourceDictionnary &dict,
1010 const GPUFunctionDictionnary &g_functions)
1011 {
1012 if (this->dependencies_init) {
1013 return 0;
1014 }
1015 this->dependencies_init = true;
1016 int64_t pos = -1;
1017
1018 using namespace shader;
1019 /* Auto dependency injection for debug capabilities. */
1020 if ((builtins & BuiltinBits::USE_PRINTF) == BuiltinBits::USE_PRINTF) {
1021 dependencies.append_non_duplicates(dict.lookup("gpu_shader_print_lib.glsl"));
1022 }
1023 if ((builtins & BuiltinBits::USE_DEBUG_DRAW) == BuiltinBits::USE_DEBUG_DRAW) {
1024 dependencies.append_non_duplicates(dict.lookup("common_debug_draw_lib.glsl"));
1025 }
1026 if ((builtins & BuiltinBits::USE_DEBUG_PRINT) == BuiltinBits::USE_DEBUG_PRINT) {
1027 dependencies.append_non_duplicates(dict.lookup("common_debug_print_lib.glsl"));
1028 }
1029
1030 while (true) {
1031 GPUSource *dependency_source = nullptr;
1032
1033 {
1034 pos = source.find("pragma BLENDER_REQUIRE(", pos + 1);
1035 if (pos == -1) {
1036 return 0;
1037 }
1038 int64_t start = source.find('(', pos) + 1;
1039 int64_t end = source.find(')', pos);
1040 if (end == -1) {
1041 print_error(source, start, "Malformed BLENDER_REQUIRE: Missing \")\" token");
1042 return 1;
1043 }
1044 StringRef dependency_name = source.substr(start, end - start);
1045 dependency_source = dict.lookup_default(dependency_name, nullptr);
1046 if (dependency_source == nullptr) {
1047 print_error(source, start, "Dependency not found");
1048 return 1;
1049 }
1050 }
1051
1052 /* Recursive. */
1053 int result = dependency_source->init_dependencies(dict, g_functions);
1054 if (result != 0) {
1055 return 1;
1056 }
1057
1058 for (auto *dep : dependency_source->dependencies) {
1059 dependencies.append_non_duplicates(dep);
1060 }
1061 dependencies.append_non_duplicates(dependency_source);
1062 }
1063 /* Precedes an eternal loop (quiet CLANG's `unreachable-code` warning). */
1065 return 0;
1066 }
1067
1068 /* Returns the final string with all includes done. */
1069 void build(Vector<const char *> &result) const
1070 {
1071 for (auto *dep : dependencies) {
1072 result.append(dep->source.c_str());
1073 }
1074 result.append(source.c_str());
1075 }
1076
1077 shader::BuiltinBits builtins_get() const
1078 {
1079 shader::BuiltinBits out_builtins = builtins;
1080 for (auto *dep : dependencies) {
1081 out_builtins |= dep->builtins;
1082 }
1083 return out_builtins;
1084 }
1085
1086 bool is_from_material_library() const
1087 {
1088 return (filename.startswith("gpu_shader_material_") ||
1089 filename.startswith("gpu_shader_common_") ||
1090 filename.startswith("gpu_shader_compositor_")) &&
1091 filename.endswith(".glsl");
1092 }
1093};
1094
1095} // namespace blender::gpu
1096
1097using namespace blender::gpu;
1098
1099static GPUPrintFormatMap *g_formats = nullptr;
1100static GPUSourceDictionnary *g_sources = nullptr;
1101static GPUFunctionDictionnary *g_functions = nullptr;
1102static bool force_printf_injection = false;
1103
1105{
1106 g_formats = new GPUPrintFormatMap();
1107 g_sources = new GPUSourceDictionnary();
1108 g_functions = new GPUFunctionDictionnary();
1109
1110#define SHADER_SOURCE(datatoc, filename, filepath) \
1111 g_sources->add_new(filename, new GPUSource(filepath, filename, datatoc, g_functions, g_formats));
1112#include "glsl_compositor_source_list.h"
1113#include "glsl_draw_source_list.h"
1114#include "glsl_gpu_source_list.h"
1115#ifdef WITH_OCIO
1116# include "glsl_ocio_source_list.h"
1117#endif
1118#undef SHADER_SOURCE
1119
1120 int errors = 0;
1121 for (auto *value : g_sources->values()) {
1122 errors += value->init_dependencies(*g_sources, *g_functions);
1123 }
1124 BLI_assert_msg(errors == 0, "Dependency errors detected: Aborting");
1125 UNUSED_VARS_NDEBUG(errors);
1126
1127#if GPU_SHADER_PRINTF_ENABLE
1128 if (!g_formats->is_empty()) {
1129 /* Detect if there is any printf in node lib files.
1130 * See gpu_shader_dependency_force_gpu_print_injection(). */
1131 for (auto *value : g_sources->values()) {
1132 if ((value->builtins & shader::BuiltinBits::USE_PRINTF) != shader::BuiltinBits::USE_PRINTF) {
1133 if (value->filename.startswith("gpu_shader_material_")) {
1134 force_printf_injection = true;
1135 break;
1136 }
1137 }
1138 }
1139 }
1140#endif
1141}
1142
1144{
1145 for (auto *value : g_sources->values()) {
1146 delete value;
1147 }
1148 for (auto *value : g_functions->values()) {
1149 MEM_delete(value);
1150 }
1151 delete g_formats;
1152 delete g_sources;
1153 delete g_functions;
1154 g_formats = nullptr;
1155 g_sources = nullptr;
1156 g_functions = nullptr;
1157}
1158
1159GPUFunction *gpu_material_library_use_function(GSet *used_libraries, const char *name)
1160{
1161 GPUFunction *function = g_functions->lookup_default(name, nullptr);
1162 BLI_assert_msg(function != nullptr, "Requested function not in the function library");
1163 GPUSource *source = reinterpret_cast<GPUSource *>(function->source);
1164 BLI_gset_add(used_libraries, const_cast<char *>(source->filename.c_str()));
1165 return function;
1166}
1167
1168namespace blender::gpu::shader {
1169
1171{
1172 /* WORKAROUND: We cannot know what shader will require printing if the printf is inside shader
1173 * node code. In this case, we just force injection inside all shaders. */
1174 return force_printf_injection;
1175}
1176
1178{
1179 return (g_formats != nullptr) && !g_formats->is_empty();
1180}
1181
1182const PrintfFormat &gpu_shader_dependency_get_printf_format(uint32_t format_hash)
1183{
1184 return g_formats->lookup(format_hash);
1185}
1186
1188{
1189 if (shader_source_name.is_empty()) {
1191 }
1192 if (g_sources->contains(shader_source_name) == false) {
1193 std::cerr << "Error: Could not find \"" << shader_source_name
1194 << "\" in the list of registered source.\n";
1195 BLI_assert(0);
1197 }
1198 GPUSource *source = g_sources->lookup(shader_source_name);
1199 return source->builtins_get();
1200}
1201
1203 const StringRefNull shader_source_name)
1204{
1205 Vector<const char *> result;
1206 GPUSource *src = g_sources->lookup_default(shader_source_name, nullptr);
1207 if (src == nullptr) {
1208 std::cerr << "Error source not found : " << shader_source_name << std::endl;
1209 }
1210 src->build(result);
1211 return result;
1212}
1213
1215{
1216 GPUSource *src = g_sources->lookup_default(shader_source_name, nullptr);
1217 if (src == nullptr) {
1218 std::cerr << "Error source not found : " << shader_source_name << std::endl;
1219 }
1220 return src->source;
1221}
1222
1224 const StringRefNull source_string)
1225{
1226 for (auto &source : g_sources->values()) {
1227 if (source->source.c_str() == source_string.c_str()) {
1228 return source->filename;
1229 }
1230 }
1231 return "";
1232}
1233
1234} // namespace blender::gpu::shader
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
struct GSet GSet
Definition BLI_ghash.h:341
bool BLI_gset_add(GSet *gs, void *key)
Definition BLI_ghash.c:966
void BLI_kdtree_nd_ insert(KDTree *tree, int index, const float co[KD_DIMS]) ATTR_NONNULL(1
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:597
#define ARRAY_SIZE(arr)
#define UNUSED_VARS_NDEBUG(...)
#define ELEM(...)
eGPUBackendType GPU_backend_get_type()
eGPUType
@ GPU_VEC2
@ GPU_MAT4
@ GPU_TEX1D_ARRAY
@ GPU_TEX2D_ARRAY
@ GPU_TEX2D
@ GPU_VEC4
@ GPU_NONE
@ GPU_CLOSURE
@ GPU_VEC3
@ GPU_MAT3
@ GPU_TEX3D
@ GPU_FLOAT
#define output
void build(btStridingMeshInterface *triangles, bool useQuantizedAabbCompression, const btVector3 &bvhAabbMin, const btVector3 &bvhAabbMax)
constexpr bool is_empty() const
constexpr const char * c_str() const
#define str(s)
GPUFunction * gpu_material_library_use_function(GSet *used_libraries, const char *name)
@ FUNCTION_QUAL_IN
@ FUNCTION_QUAL_OUT
@ FUNCTION_QUAL_INOUT
#define rfind_token
#define find_token
#define rfind_keyword
#define find_keyword
#define CHECK(test_value, str, ofs, msg)
void gpu_shader_dependency_init()
void gpu_shader_dependency_exit()
static uint hash_string(const char *str)
Definition hash.h:532
format
static void print_error(const char *message, va_list str_format_args)
StringRefNull gpu_shader_dependency_get_filename_from_source_string(const StringRefNull source_string)
Find the name of the file from which the given string was generated.
const PrintfFormat & gpu_shader_dependency_get_printf_format(uint32_t format_hash)
Vector< const char * > gpu_shader_dependency_get_resolved_source(const StringRefNull source_name)
BuiltinBits gpu_shader_dependency_get_builtins(const StringRefNull source_name)
bool gpu_shader_dependency_has_printf()
StringRefNull gpu_shader_dependency_get_source(const StringRefNull source_name)
bool gpu_shader_dependency_force_gpu_print_injection()
static OVERLAY_InstanceFormats g_formats
unsigned int uint32_t
Definition stdint.h:80
__int64 int64_t
Definition stdint.h:89
unsigned char uint8_t
Definition stdint.h:78
unsigned __int64 uint64_t
Definition stdint.h:90
eGPUType paramtype[MAX_PARAMETER]
char name[MAX_FUNCTION_NAME]
GPUFunctionQual paramqual[MAX_PARAMETER]
static const char hex[17]
Definition thumbs.cc:159