diff options
author | Clément Foucault <foucault.clem@gmail.com> | 2022-01-26 17:55:33 +0300 |
---|---|---|
committer | Clément Foucault <foucault.clem@gmail.com> | 2022-01-26 20:10:59 +0300 |
commit | b42adab3a2b89eb02c5829a389eab9b2921f13b7 (patch) | |
tree | 7bc4395dac1982e23ee9186d0eaaebd36adeab1d /source/blender/gpu/intern/gpu_shader_dependency.cc | |
parent | e729abb0e2020acce814a22ad34cdf2254a5ca35 (diff) |
GPUShader: Add GLSL source modification pass to support enums
This uses a light parser / string modification pass to convert
C++ enum declaration syntax to GLSL compatible one.
GLSL having no support for enums, we are forced to convert the
enum values to a series of constant uints.
The parser (not really one by the way), being stupidly simple,
will not change anything to the values and thus make some C++
syntax (like omitting the values) not work.
The string replacement happens on all GLSL files on startup.
I did not measure significant changes in blender startup speed.
There is plans to do all of this at compile time.
We limit the scope of the search to `.h` and `.hh` files to prevent
confusing syntax in `.glsl` files.
There is basic error reporting with file, line and char logging
for easy debuggabiliy.
The requirements to use this enum sharing system are already listed in
`gpu_shader_shared_utils.h` and repeated on top of the preprocessor
function.
Diffstat (limited to 'source/blender/gpu/intern/gpu_shader_dependency.cc')
-rw-r--r-- | source/blender/gpu/intern/gpu_shader_dependency.cc | 203 |
1 files changed, 197 insertions, 6 deletions
diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc index 2d56e1cec05..2333590810f 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency.cc +++ b/source/blender/gpu/intern/gpu_shader_dependency.cc @@ -34,7 +34,7 @@ #include "gpu_shader_dependency_private.h" extern "C" { -#define SHADER_SOURCE(datatoc, filename) extern char datatoc[]; +#define SHADER_SOURCE(datatoc, filename, filepath) extern char datatoc[]; #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" #undef SHADER_SOURCE @@ -45,13 +45,16 @@ namespace blender::gpu { using GPUSourceDictionnary = Map<StringRef, struct GPUSource *>; struct GPUSource { + StringRefNull fullpath; StringRefNull filename; StringRefNull source; Vector<GPUSource *> dependencies; bool dependencies_init = false; shader::BuiltinBits builtins = (shader::BuiltinBits)0; + std::string processed_source; - GPUSource(const char *file, const char *datatoc) : filename(file), source(datatoc) + GPUSource(const char *path, const char *file, const char *datatoc) + : fullpath(path), filename(file), source(datatoc) { /* Scan for builtins. */ /* FIXME: This can trigger false positive caused by disabled #if blocks. */ @@ -99,6 +102,194 @@ struct GPUSource { if (source.find("gl_WorkGroupSize", 0)) { builtins |= shader::BuiltinBits::WORK_GROUP_SIZE; } + + /* TODO(fclem): We could do that at compile time. */ + /* Limit to shared header files to avoid the temptation to use C++ syntax in .glsl files. */ + if (filename.endswith(".h") || filename.endswith(".hh")) { + enum_preprocess(); + } + }; + + bool is_in_comment(const StringRef &input, int64_t offset) + { + return (input.rfind("/*", offset) > input.rfind("*/", offset)) || + (input.rfind("//", offset) > input.rfind("\n", offset)); + } + + template<bool check_whole_word = true, bool reversed = false, typename T> + int64_t find_str(const StringRef &input, const T keyword, int64_t offset = 0) + { + while (1) { + if constexpr (reversed) { + offset = input.rfind(keyword, offset); + } + else { + offset = input.find(keyword, offset); + } + if (offset > 0) { + if constexpr (check_whole_word) { + /* Fix false positive if something has "enum" as suffix. */ + char previous_char = input[offset - 1]; + if (!(ELEM(previous_char, '\n', '\t', ' ', ':'))) { + offset += (reversed) ? -1 : 1; + continue; + } + } + /* Fix case where the keyword is in a comment. */ + if (is_in_comment(input, offset)) { + offset += (reversed) ? -1 : 1; + continue; + } + } + return offset; + } + } + + void print_error(const StringRef &input, int64_t offset, const StringRef message) + { + std::cout << " error: " << message << "\n"; + StringRef sub = input.substr(0, offset); + int64_t line_number = std::count(sub.begin(), sub.end(), '\n') + 1; + int64_t line_end = input.find("\n", offset); + int64_t line_start = input.rfind("\n", offset) + 1; + int64_t char_number = offset - line_start + 1; + char line_prefix[16] = ""; + SNPRINTF(line_prefix, "%5ld | ", line_number); + + /* TODO Use clog. */ + + std::cout << fullpath << ":" << line_number << ":" << char_number; + + std::cout << " error: " << message << "\n"; + std::cout << line_prefix << input.substr(line_start, line_end - line_start) << "\n"; + std::cout << " | "; + for (int64_t i = 0; i < char_number - 1; i++) { + std::cout << " "; + } + std::cout << "^\n"; + } + + /** + * Transform C,C++ enum declaration into GLSL compatible defines and constants: + * + * \code{.cpp} + * enum eMyEnum : uint32_t { + * ENUM_1 = 0u, + * ENUM_2 = 1u, + * ENUM_3 = 2u, + * }; + * \endcode + * + * or + * + * \code{.c} + * enum eMyEnum { + * ENUM_1 = 0u, + * ENUM_2 = 1u, + * ENUM_3 = 2u, + * }; + * \endcode + * + * becomes + * + * \code{.glsl} + * #define eMyEnum uint + * const uint ENUM_1 = 0u, ENUM_2 = 1u, ENUM_3 = 2u; + * \endcode + * + * IMPORTANT: This has some requirements: + * - Enums needs to have underlying types specified to uint32_t to make them usable in UBO/SSBO. + * - All values needs to be specified using constant literals to avoid compiler differencies. + * - All values needs to have the 'u' suffix to avoid GLSL compiler errors. + */ + void enum_preprocess(void) + { + const StringRefNull input = source; + std::string output = ""; + int64_t cursor = 0; + int64_t last_pos = 0; + const bool is_cpp = filename.endswith(".hh"); + +#define find_keyword find_str<true, false> +#define find_token find_str<false, false> +#define rfind_token find_str<false, true> +#define CHECK(test_value, str, ofs, msg) \ + if ((test_value) == -1) { \ + print_error(str, ofs, msg); \ + cursor++; \ + continue; \ + } + + while (1) { + cursor = find_keyword(input, "enum ", cursor); + if (cursor == -1) { + break; + } + /* Output anything between 2 enums blocks. */ + output += input.substr(last_pos, cursor - last_pos); + + /* Extract enum type name. */ + int64_t name_start = input.find(" ", cursor); + + int64_t values_start = find_token(input, '{', cursor); + CHECK(values_start, input, cursor, "Malformed enum class. Expected \'{\' after typename."); + + StringRef enum_name = input.substr(name_start, values_start - name_start); + if (is_cpp) { + int64_t name_end = find_token(enum_name, ":"); + CHECK(name_end, input, name_start, "Expected \':\' after C++ enum name."); + + int64_t underlying_type = find_keyword(enum_name, "uint32_t", name_end); + CHECK(underlying_type, input, name_start, "C++ enums needs uint32_t underlying type."); + + enum_name = input.substr(name_start, name_end); + } + + output += "#define " + enum_name + " uint\n"; + + /* Extract enum values. */ + int64_t values_end = find_token(input, '}', values_start); + CHECK(values_end, input, cursor, "Malformed enum class. Expected \'}\' after values."); + + /* Skip opening brackets. */ + values_start += 1; + + StringRef enum_values = input.substr(values_start, values_end - values_start); + + /* Really poor check. Could be done better. */ + int64_t token = find_token(enum_values, '{'); + int64_t not_found = (token == -1) ? 0 : -1; + CHECK(not_found, input, values_start + token, "Unexpected \'{\' token inside enum values."); + + /* Do not capture the comma after the last value (if present). */ + int64_t last_equal = rfind_token(enum_values, '=', values_end); + int64_t last_comma = rfind_token(enum_values, ',', values_end); + if (last_comma > last_equal) { + enum_values = input.substr(values_start, last_comma); + } + + output += "const uint " + enum_values; + + int64_t semicolon_found = (input[values_end + 1] == ';') ? 0 : -1; + CHECK(semicolon_found, input, values_end + 1, "Expected \';\' after enum type declaration."); + + /* Skip the curly bracket but not the semicolon. */ + cursor = last_pos = values_end + 1; + } + /* If nothing has been changed, do not allocate processed_source. */ + if (last_pos == 0) { + return; + } + +#undef find_keyword +#undef find_token +#undef rfind_token + + if (last_pos != 0) { + output += input.substr(last_pos); + } + processed_source = output; + source = processed_source.c_str(); }; void init_dependencies(const GPUSourceDictionnary &dict) @@ -113,8 +304,8 @@ struct GPUSource { if (pos == -1) { return; } - int64_t start = source.find("(", pos) + 1; - int64_t end = source.find(")", pos); + int64_t start = source.find('(', pos) + 1; + int64_t end = source.find(')', pos); if (end == -1) { /* TODO Use clog. */ std::cout << "Error: " << filename << " : Malformed BLENDER_REQUIRE: Missing \")\"." @@ -161,8 +352,8 @@ void gpu_shader_dependency_init() { g_sources = new GPUSourceDictionnary(); -#define SHADER_SOURCE(datatoc, filename) \ - g_sources->add_new(filename, new GPUSource(filename, datatoc)); +#define SHADER_SOURCE(datatoc, filename, filepath) \ + g_sources->add_new(filename, new GPUSource(filepath, filename, datatoc)); #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" #undef SHADER_SOURCE |