From f6639cc4fc1fd9fbbe1454ce216d6d0c8fe1ebdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Mon, 8 Aug 2022 19:01:38 +0200 Subject: DRW: DebugDraw: Port module to C++ and add GPU capabilities This is a complete rewrite of the draw debug drawing module in C++. It uses `GPUStorageBuf` to store the data to be drawn and use indirect drawing. This makes it easier to do a mirror API for GPU shaders. The C++ API class is exposed through `draw_debug.hh` and should be used when possible in new code. However, the debug drawing will not work for platform not yet supporting `GPUStorageBuf`. Also keep in mind that this module must only be used in debug build for performance and compatibility reasons. --- source/blender/gpu/intern/gpu_shader.cc | 6 + .../blender/gpu/intern/gpu_shader_create_info.cc | 8 + .../blender/gpu/intern/gpu_shader_create_info.hh | 6 +- source/blender/gpu/intern/gpu_shader_dependency.cc | 265 +++++++++++++++++++-- source/blender/gpu/intern/gpu_shader_interface.hh | 21 ++ 5 files changed, 289 insertions(+), 17 deletions(-) (limited to 'source/blender/gpu/intern') diff --git a/source/blender/gpu/intern/gpu_shader.cc b/source/blender/gpu/intern/gpu_shader.cc index 8a630d9e3ca..08c768b28ba 100644 --- a/source/blender/gpu/intern/gpu_shader.cc +++ b/source/blender/gpu/intern/gpu_shader.cc @@ -578,6 +578,12 @@ int GPU_shader_get_builtin_block(GPUShader *shader, int builtin) return interface->ubo_builtin((GPUUniformBlockBuiltin)builtin); } +int GPU_shader_get_builtin_ssbo(GPUShader *shader, int builtin) +{ + ShaderInterface *interface = unwrap(shader)->interface; + return interface->ssbo_builtin((GPUStorageBufferBuiltin)builtin); +} + int GPU_shader_get_ssbo(GPUShader *shader, const char *name) { ShaderInterface *interface = unwrap(shader)->interface; diff --git a/source/blender/gpu/intern/gpu_shader_create_info.cc b/source/blender/gpu/intern/gpu_shader_create_info.cc index bc0731862cb..110b77f1f52 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.cc +++ b/source/blender/gpu/intern/gpu_shader_create_info.cc @@ -306,6 +306,14 @@ void gpu_shader_create_info_init() info->builtins_ |= gpu_shader_dependency_get_builtins(info->fragment_source_); info->builtins_ |= gpu_shader_dependency_get_builtins(info->geometry_source_); info->builtins_ |= gpu_shader_dependency_get_builtins(info->compute_source_); + + /* Automatically amend the create info for ease of use of the debug feature. */ + if ((info->builtins_ & BuiltinBits::USE_DEBUG_DRAW) == BuiltinBits::USE_DEBUG_DRAW) { + info->additional_info("draw_debug_draw"); + } + if ((info->builtins_ & BuiltinBits::USE_DEBUG_PRINT) == BuiltinBits::USE_DEBUG_PRINT) { + info->additional_info("draw_debug_print"); + } } } diff --git a/source/blender/gpu/intern/gpu_shader_create_info.hh b/source/blender/gpu/intern/gpu_shader_create_info.hh index fb8efbb209a..82defc436e0 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.hh +++ b/source/blender/gpu/intern/gpu_shader_create_info.hh @@ -127,8 +127,12 @@ enum class BuiltinBits { VERTEX_ID = (1 << 14), WORK_GROUP_ID = (1 << 15), WORK_GROUP_SIZE = (1 << 16), + + /* Not a builtin but a flag we use to tag shaders that use the debug features. */ + USE_DEBUG_DRAW = (1 << 29), + USE_DEBUG_PRINT = (1 << 30), }; -ENUM_OPERATORS(BuiltinBits, BuiltinBits::WORK_GROUP_SIZE); +ENUM_OPERATORS(BuiltinBits, BuiltinBits::USE_DEBUG_PRINT); /** * Follow convention described in: diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc index aff7df9ac33..953f656c2b5 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency.cc +++ b/source/blender/gpu/intern/gpu_shader_dependency.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "BLI_ghash.h" @@ -42,7 +43,7 @@ struct GPUSource { StringRefNull source; Vector dependencies; bool dependencies_init = false; - shader::BuiltinBits builtins = (shader::BuiltinBits)0; + shader::BuiltinBits builtins = shader::BuiltinBits::NONE; std::string processed_source; GPUSource(const char *path, @@ -54,46 +55,45 @@ struct GPUSource { /* Scan for builtins. */ /* FIXME: This can trigger false positive caused by disabled #if blocks. */ /* TODO(fclem): Could be made faster by scanning once. */ - if (source.find("gl_FragCoord", 0)) { + if (source.find("gl_FragCoord", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::FRAG_COORD; } - if (source.find("gl_FrontFacing", 0)) { + if (source.find("gl_FrontFacing", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::FRONT_FACING; } - if (source.find("gl_GlobalInvocationID", 0)) { + if (source.find("gl_GlobalInvocationID", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::GLOBAL_INVOCATION_ID; } - if (source.find("gl_InstanceID", 0)) { + if (source.find("gl_InstanceID", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::INSTANCE_ID; } - if (source.find("gl_LocalInvocationID", 0)) { + if (source.find("gl_LocalInvocationID", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::LOCAL_INVOCATION_ID; } - if (source.find("gl_LocalInvocationIndex", 0)) { + if (source.find("gl_LocalInvocationIndex", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::LOCAL_INVOCATION_INDEX; } - if (source.find("gl_NumWorkGroup", 0)) { + if (source.find("gl_NumWorkGroup", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::NUM_WORK_GROUP; } - if (source.find("gl_PointCoord", 0)) { + if (source.find("gl_PointCoord", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::POINT_COORD; } - if (source.find("gl_PointSize", 0)) { + if (source.find("gl_PointSize", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::POINT_SIZE; } - if (source.find("gl_PrimitiveID", 0)) { + if (source.find("gl_PrimitiveID", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::PRIMITIVE_ID; } - if (source.find("gl_VertexID", 0)) { + if (source.find("gl_VertexID", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::VERTEX_ID; } - if (source.find("gl_WorkGroupID", 0)) { + if (source.find("gl_WorkGroupID", 0) != StringRef::not_found) { builtins |= shader::BuiltinBits::WORK_GROUP_ID; } - if (source.find("gl_WorkGroupSize", 0)) { + if (source.find("gl_WorkGroupSize", 0) != StringRef::not_found) { 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")) { @@ -101,6 +101,18 @@ struct GPUSource { quote_preprocess(); } else { + if (source.find("'") != StringRef::not_found) { + char_literals_preprocess(); + } + if (source.find("drw_print") != StringRef::not_found) { + string_preprocess(); + } + if ((source.find("drw_debug_") != StringRef::not_found) && + /* Avoid theses two files where it makes no sense to add the dependency. */ + (filename != "common_debug_draw_lib.glsl" && + filename != "draw_debug_draw_display_vert.glsl")) { + builtins |= shader::BuiltinBits::USE_DEBUG_DRAW; + } check_no_quotes(); } @@ -522,6 +534,217 @@ struct GPUSource { } } + void char_literals_preprocess() + { + const StringRefNull input = source; + std::stringstream output; + int64_t cursor = -1; + int64_t last_pos = 0; + + while (true) { + cursor = find_token(input, '\'', cursor + 1); + if (cursor == -1) { + break; + } + /* Output anything between 2 print statement. */ + output << input.substr(last_pos, cursor - last_pos); + + /* Extract string. */ + int64_t char_start = cursor + 1; + int64_t char_end = find_token(input, '\'', char_start); + CHECK(char_end, input, cursor, "Malformed char literal. Missing ending `'`."); + + StringRef input_char = input.substr(char_start, char_end - char_start); + if (input_char.size() == 0) { + CHECK(-1, input, cursor, "Malformed char literal. Empty character constant"); + } + + uint8_t char_value = input_char[0]; + + if (input_char[0] == '\\') { + if (input_char[1] == 'n') { + char_value = '\n'; + } + else { + CHECK(-1, input, cursor, "Unsupported escaped character"); + } + } + else { + if (input_char.size() > 1) { + CHECK(-1, input, cursor, "Malformed char literal. Multi-character character constant"); + } + } + + char hex[8]; + SNPRINTF(hex, "0x%.2Xu", char_value); + output << hex; + + cursor = last_pos = char_end + 1; + } + /* If nothing has been changed, do not allocate processed_source. */ + if (last_pos == 0) { + return; + } + + if (last_pos != 0) { + output << input.substr(last_pos); + } + processed_source = output.str(); + source = processed_source.c_str(); + } + + /* Replace print(string) by equivalent drw_print_char4() sequence. */ + void string_preprocess() + { + const StringRefNull input = source; + std::stringstream output; + int64_t cursor = -1; + int64_t last_pos = 0; + + while (true) { + cursor = find_keyword(input, "drw_print", cursor + 1); + if (cursor == -1) { + break; + } + + bool do_endl = false; + StringRef func = input.substr(cursor); + if (func.startswith("drw_print(")) { + do_endl = true; + } + else if (func.startswith("drw_print_no_endl(")) { + do_endl = false; + } + else { + continue; + } + + /* Output anything between 2 print statement. */ + output << input.substr(last_pos, cursor - last_pos); + + /* Extract string. */ + int64_t str_start = input.find('(', cursor) + 1; + int64_t semicolon = find_token(input, ';', str_start + 1); + CHECK(semicolon, input, cursor, "Malformed print(). Missing `;` ."); + int64_t str_end = rfind_token(input, ')', semicolon); + if (str_end < str_start) { + CHECK(-1, input, cursor, "Malformed print(). Missing closing `)` ."); + } + + std::stringstream sub_output; + StringRef input_args = input.substr(str_start, str_end - str_start); + + auto print_string = [&](std::string str) -> int { + size_t len_before_pad = str.length(); + /* Pad string to uint size. */ + while (str.length() % 4 != 0) { + str += " "; + } + /* Keep everything in one line to not mess with the shader logs. */ + sub_output << "/* " << str << "*/"; + sub_output << "drw_print_string_start(" << len_before_pad << ");"; + for (size_t i = 0; i < len_before_pad; i += 4) { + uint8_t chars[4] = {*(reinterpret_cast(str.c_str()) + i + 0), + *(reinterpret_cast(str.c_str()) + i + 1), + *(reinterpret_cast(str.c_str()) + i + 2), + *(reinterpret_cast(str.c_str()) + i + 3)}; + if (i + 4 > len_before_pad) { + chars[len_before_pad - i] = '\0'; + } + char uint_hex[12]; + SNPRINTF(uint_hex, "0x%.2X%.2X%.2X%.2Xu", chars[3], chars[2], chars[1], chars[0]); + sub_output << "drw_print_char4(" << StringRefNull(uint_hex) << ");"; + } + return 0; + }; + + std::string func_args = input_args; + /* Workaround to support function call inside prints. We replace commas by a non control + * caracter $ in order to use simpler regex later. */ + bool string_scope = false; + int func_scope = 0; + for (char &c : func_args) { + if (c == '"') { + string_scope = !string_scope; + } + else if (!string_scope) { + if (c == '(') { + func_scope++; + } + else if (c == ')') { + func_scope--; + } + else if (c == ',' && func_scope != 0) { + c = '$'; + } + } + } + + const bool print_as_variable = (input_args[0] != '"') && find_token(input_args, ',') == -1; + if (print_as_variable) { + /* Variable or expression debuging. */ + std::string arg = input_args; + /* Pad align most values. */ + while (arg.length() % 4 != 0) { + arg += " "; + } + print_string(arg); + print_string("= "); + sub_output << "drw_print_value(" << input_args << ");"; + } + else { + const std::regex arg_regex( + /* String args. */ + "[\\s]*\"([^\r\n\t\f\v\"]*)\"" + /* OR. */ + "|" + /* value args. */ + "([^,]+)"); + std::smatch args_match; + std::string::const_iterator args_search_start(func_args.cbegin()); + while (std::regex_search(args_search_start, func_args.cend(), args_match, arg_regex)) { + args_search_start = args_match.suffix().first; + std::string arg_string = args_match[1].str(); + std::string arg_val = args_match[2].str(); + + if (arg_string.empty()) { + for (char &c : arg_val) { + if (c == '$') { + c = ','; + } + } + sub_output << "drw_print_value(" << arg_val << ");"; + } + else { + print_string(arg_string); + } + } + } + + if (do_endl) { + sub_output << "drw_print_newline();"; + } + + output << sub_output.str(); + + cursor = last_pos = str_end + 1; + } + /* If nothing has been changed, do not allocate processed_source. */ + if (last_pos == 0) { + return; + } + + if (filename != "common_debug_print_lib.glsl") { + builtins |= shader::BuiltinBits::USE_DEBUG_PRINT; + } + + if (last_pos != 0) { + output << input.substr(last_pos); + } + processed_source = output.str(); + source = processed_source.c_str(); + } + #undef find_keyword #undef rfind_keyword #undef find_token @@ -537,6 +760,15 @@ struct GPUSource { this->dependencies_init = true; int64_t pos = -1; + using namespace shader; + /* Auto dependency injection for debug capabilities. */ + if ((builtins & BuiltinBits::USE_DEBUG_DRAW) == BuiltinBits::USE_DEBUG_DRAW) { + dependencies.append_non_duplicates(dict.lookup("common_debug_draw_lib.glsl")); + } + if ((builtins & BuiltinBits::USE_DEBUG_PRINT) == BuiltinBits::USE_DEBUG_PRINT) { + dependencies.append_non_duplicates(dict.lookup("common_debug_print_lib.glsl")); + } + while (true) { GPUSource *dependency_source = nullptr; @@ -558,6 +790,7 @@ struct GPUSource { return 1; } } + /* Recursive. */ int result = dependency_source->init_dependencies(dict, g_functions); if (result != 0) { @@ -583,7 +816,7 @@ struct GPUSource { shader::BuiltinBits builtins_get() const { - shader::BuiltinBits out_builtins = shader::BuiltinBits::NONE; + shader::BuiltinBits out_builtins = builtins; for (auto *dep : dependencies) { out_builtins |= dep->builtins; } diff --git a/source/blender/gpu/intern/gpu_shader_interface.hh b/source/blender/gpu/intern/gpu_shader_interface.hh index 60344757b43..812244c9b3a 100644 --- a/source/blender/gpu/intern/gpu_shader_interface.hh +++ b/source/blender/gpu/intern/gpu_shader_interface.hh @@ -56,6 +56,7 @@ class ShaderInterface { /** Location of builtin uniforms. Fast access, no lookup needed. */ int32_t builtins_[GPU_NUM_UNIFORMS]; int32_t builtin_blocks_[GPU_NUM_UNIFORM_BLOCKS]; + int32_t builtin_buffers_[GPU_NUM_STORAGE_BUFFERS]; public: ShaderInterface(); @@ -116,9 +117,17 @@ class ShaderInterface { return builtin_blocks_[builtin]; } + /* Returns binding position. */ + inline int32_t ssbo_builtin(const GPUStorageBufferBuiltin builtin) const + { + BLI_assert(builtin >= 0 && builtin < GPU_NUM_STORAGE_BUFFERS); + return builtin_buffers_[builtin]; + } + protected: static inline const char *builtin_uniform_name(GPUUniformBuiltin u); static inline const char *builtin_uniform_block_name(GPUUniformBlockBuiltin u); + static inline const char *builtin_storage_block_name(GPUStorageBufferBuiltin u); inline uint32_t set_input_name(ShaderInput *input, char *name, uint32_t name_len) const; inline void copy_input_name(ShaderInput *input, @@ -212,6 +221,18 @@ inline const char *ShaderInterface::builtin_uniform_block_name(GPUUniformBlockBu } } +inline const char *ShaderInterface::builtin_storage_block_name(GPUStorageBufferBuiltin u) +{ + switch (u) { + case GPU_STORAGE_BUFFER_DEBUG_VERTS: + return "drw_debug_verts_buf"; + case GPU_STORAGE_BUFFER_DEBUG_PRINT: + return "drw_debug_print_buf"; + default: + return nullptr; + } +} + /* Returns string length including '\0' terminator. */ inline uint32_t ShaderInterface::set_input_name(ShaderInput *input, char *name, -- cgit v1.2.3