From d2fdb22b93c0977dc0d92d4b158ffc7853b05bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Fri, 19 Jul 2024 15:48:00 +0200 Subject: [PATCH] GPU: Add support for in shader printf This allows much easier debugging of shader programs. Usage is as simple as adding `printf` calls inside shaders. example: `printf("Formating %d\n", my_var);` Contrary to the `drw_print`, this is not limited to draw manager shader dispatch/draws. It is compatible with any shader inside blender. Most notably, this doesn't need a viewport to display. So this can be used to debug render pipeline. Data formating is currently limited to only `%x`, `%d`, `%u` and `%f`. This could be easily extended if this is really needed. There is no type checking, so values are directly reinterpreted as specified by the printf format. The current approach for making this work is to bind a storage buffer inside `GPU_shader_bind`, making it available to any shader that needs it. The storage buffer is downloaded back to CPU after a frame or a render step and the content printed to the console. This scheduling means that you cannot rely on these printfs to detect crashes. We could add a mode to force flushing at shader binding to avoid this limitation. The values are written from the shaders in binary form and only formated on the CPU. This avoid issues with manual printing like with `drw_print`. Pull Request: https://projects.blender.org/blender/blender/pulls/125071 --- source/blender/gpu/CMakeLists.txt | 2 + source/blender/gpu/intern/gpu_context.cc | 6 + .../blender/gpu/intern/gpu_context_private.hh | 2 + source/blender/gpu/intern/gpu_shader.cc | 5 + .../gpu/intern/gpu_shader_create_info.cc | 8 + .../gpu/intern/gpu_shader_create_info.hh | 12 + .../gpu/intern/gpu_shader_dependency.cc | 236 +++++++++++++++++- .../intern/gpu_shader_dependency_private.hh | 22 ++ source/blender/gpu/intern/gpu_shader_log.cc | 75 ++++++ .../blender/gpu/intern/gpu_shader_private.hh | 4 + .../shaders/common/gpu_shader_print_lib.glsl | 27 ++ .../shaders/infos/gpu_shader_print_info.hh | 15 ++ 12 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 source/blender/gpu/shaders/common/gpu_shader_print_lib.glsl create mode 100644 source/blender/gpu/shaders/infos/gpu_shader_print_info.hh diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 258a740b76a..dbe9cad5c18 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -521,6 +521,7 @@ set(GLSL_SRC shaders/common/gpu_shader_math_matrix_lib.glsl shaders/common/gpu_shader_math_rotation_lib.glsl shaders/common/gpu_shader_math_vector_lib.glsl + shaders/common/gpu_shader_print_lib.glsl shaders/common/gpu_shader_shared_exponent_lib.glsl shaders/common/gpu_shader_smaa_lib.glsl shaders/common/gpu_shader_test_lib.glsl @@ -791,6 +792,7 @@ set(SRC_SHADER_CREATE_INFOS shaders/infos/gpu_shader_instance_varying_color_varying_size_info.hh shaders/infos/gpu_shader_keyframe_shape_info.hh shaders/infos/gpu_shader_line_dashed_uniform_color_info.hh + shaders/infos/gpu_shader_print_info.hh shaders/infos/gpu_shader_sequencer_info.hh shaders/infos/gpu_shader_simple_lighting_info.hh shaders/infos/gpu_shader_text_info.hh diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index 4c49fa6ad7e..6c7298b0940 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -24,6 +24,7 @@ #include "gpu_context_private.hh" #include "gpu_matrix_private.hh" #include "gpu_private.hh" +#include "gpu_shader_private.hh" #ifdef WITH_OPENGL_BACKEND # include "gl_backend.hh" @@ -114,6 +115,7 @@ GPUContext *GPU_context_create(void *ghost_window, void *ghost_context) void GPU_context_discard(GPUContext *ctx_) { Context *ctx = unwrap(ctx_); + printf_end(ctx); delete ctx; active_ctx = nullptr; @@ -133,6 +135,7 @@ void GPU_context_active_set(GPUContext *ctx_) Context *ctx = unwrap(ctx_); if (active_ctx) { + printf_end(active_ctx); active_ctx->deactivate(); } @@ -140,6 +143,7 @@ void GPU_context_active_set(GPUContext *ctx_) if (ctx) { ctx->activate(); + printf_begin(ctx); } } @@ -214,7 +218,9 @@ void GPU_render_step() GPUBackend *backend = GPUBackend::get(); BLI_assert(backend); if (backend) { + printf_end(active_ctx); backend->render_step(); + printf_begin(active_ctx); } } diff --git a/source/blender/gpu/intern/gpu_context_private.hh b/source/blender/gpu/intern/gpu_context_private.hh index 208d672c877..521bbedc571 100644 --- a/source/blender/gpu/intern/gpu_context_private.hh +++ b/source/blender/gpu/intern/gpu_context_private.hh @@ -60,6 +60,8 @@ class Context { static int context_counter; int context_id = 0; + GPUStorageBuf *printf_buf = nullptr; + protected: /** Thread on which this context is active. */ pthread_t thread_; diff --git a/source/blender/gpu/intern/gpu_shader.cc b/source/blender/gpu/intern/gpu_shader.cc index cf69d286fda..cd79392ced3 100644 --- a/source/blender/gpu/intern/gpu_shader.cc +++ b/source/blender/gpu/intern/gpu_shader.cc @@ -383,6 +383,11 @@ void GPU_shader_bind(GPUShader *gpu_shader) GPU_matrix_bind(gpu_shader); } } +#if GPU_SHADER_PRINTF_ENABLE + if (ctx->printf_buf) { + GPU_storagebuf_bind(ctx->printf_buf, GPU_SHADER_PRINTF_SLOT); + } +#endif } void GPU_shader_unbind() diff --git a/source/blender/gpu/intern/gpu_shader_create_info.cc b/source/blender/gpu/intern/gpu_shader_create_info.cc index daae989dfe0..53b1ea11bb6 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.cc +++ b/source/blender/gpu/intern/gpu_shader_create_info.cc @@ -548,6 +548,14 @@ void gpu_shader_create_info_init() info->builtins_ |= gpu_shader_dependency_get_builtins(info->geometry_source_); info->builtins_ |= gpu_shader_dependency_get_builtins(info->compute_source_); +#if GPU_SHADER_PRINTF_ENABLE + if ((info->builtins_ & BuiltinBits::USE_PRINTF) == BuiltinBits::USE_PRINTF || + gpu_shader_dependency_force_gpu_print_injection()) + { + info->additional_info("gpu_print"); + } +#endif + #ifndef NDEBUG /* Automatically amend the create info for ease of use of the debug feature. */ if ((info->builtins_ & BuiltinBits::USE_DEBUG_DRAW) == BuiltinBits::USE_DEBUG_DRAW) { diff --git a/source/blender/gpu/intern/gpu_shader_create_info.hh b/source/blender/gpu/intern/gpu_shader_create_info.hh index f3c34be577d..8687ef0dfe2 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.hh +++ b/source/blender/gpu/intern/gpu_shader_create_info.hh @@ -22,6 +22,17 @@ #include +/* Force enable printf support in release build. */ +#define GPU_FORCE_ENABLE_SHADER_PRINTF 0 + +#if !defined(NDEBUG) || GPU_FORCE_ENABLE_SHADER_PRINTF +# define GPU_SHADER_PRINTF_ENABLE 1 +#else +# define GPU_SHADER_PRINTF_ENABLE 0 +#endif +#define GPU_SHADER_PRINTF_SLOT 13 +#define GPU_SHADER_PRINTF_MAX_CAPACITY (1024 * 4) + namespace blender::gpu::shader { /* Helps intellisense / auto-completion. */ @@ -174,6 +185,7 @@ enum class BuiltinBits { TEXTURE_ATOMIC = (1 << 18), /* Not a builtin but a flag we use to tag shaders that use the debug features. */ + USE_PRINTF = (1 << 28), USE_DEBUG_DRAW = (1 << 29), USE_DEBUG_PRINT = (1 << 30), }; diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc index 254c291fb25..bf40f267f3e 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency.cc +++ b/source/blender/gpu/intern/gpu_shader_dependency.cc @@ -39,6 +39,7 @@ extern "C" { namespace blender::gpu { +using GPUPrintFormatMap = Map; using GPUSourceDictionnary = Map; using GPUFunctionDictionnary = Map; @@ -54,7 +55,8 @@ struct GPUSource { GPUSource(const char *path, const char *file, const char *datatoc, - GPUFunctionDictionnary *g_functions) + GPUFunctionDictionnary *g_functions, + GPUPrintFormatMap *g_formats) : fullpath(path), filename(file), source(datatoc) { /* Scan for builtins. */ @@ -122,6 +124,12 @@ struct GPUSource { { builtins |= shader::BuiltinBits::USE_DEBUG_DRAW; } +#endif +#if GPU_SHADER_PRINTF_ENABLE + if (source.find("printf") != StringRef::not_found) { + printf_preprocess(g_formats); + builtins |= shader::BuiltinBits::USE_PRINTF; + } #endif check_no_quotes(); } @@ -805,6 +813,191 @@ struct GPUSource { source = processed_source.c_str(); } + /** + * Preprocess printf statement for correct shader code generation. + * `printf(format, data1, data2)` + * gets replaced by + * `print_data(print_data(print_header(format_hash, 2), data1), data2)`. + */ + void printf_preprocess(GPUPrintFormatMap *format_map) + { + const StringRefNull input = source; + std::stringstream output; + int64_t cursor = -1; + int64_t last_pos = 0; + + while (true) { + cursor = find_keyword(input, "printf(", cursor + 1); + if (cursor == -1) { + break; + } + + /* 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 printf(). Missing `;` ."); + int64_t str_end = rfind_token(input, ')', semicolon); + if (str_end < str_start) { + CHECK(-1, input, cursor, "Malformed printf(). Missing closing `)` ."); + } + + StringRef input_args = input.substr(str_start, str_end - str_start); + + std::string func_args = input_args; + /* Workaround to support function call inside prints. We replace commas by a non control + * character `$` in order to use simpler regex later. + * Modify `"func %d,\n", func(a, b)` into `"func %d,\n", func(a$ b)` */ + 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 = '$'; + } + } + } + + std::string format; + Vector arguments; + { + 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()) { + if (!format.empty()) { + CHECK(-1, input, cursor, "Format string is not the only string arg."); + } + if (!arguments.is_empty()) { + CHECK(-1, input, cursor, "Format string is not first argument."); + } + format = arg_string; + } + else { + for (char &c : arg_val) { + /* Mutate back functions arguments.*/ + if (c == '$') { + c = ','; + } + } + arguments.append(arg_val); + } + } + } + + if (format.empty()) { + CHECK(-1, input, cursor, "No format string found."); + return; + } + int format_arg_count = std::count(format.begin(), format.end(), '%'); + if (format_arg_count > arguments.size()) { + CHECK(-1, input, cursor, "printf call has not enough arguments."); + return; + } + if (format_arg_count < arguments.size()) { + CHECK(-1, input, cursor, "printf call has too many arguments."); + return; + } + + uint64_t format_hash_64 = hash_string(format); + uint32_t format_hash = uint32_t((format_hash_64 >> 32) ^ format_hash_64); + + if (format_map->contains(format_hash)) { + if (format_map->lookup(format_hash).format_str != format) { + CHECK(-1, input, cursor, "printf format hash collision."); + } + else { + /* The format map already have the same format. */ + } + } + else { + shader::PrintfFormat fmt; + /* Save for hash collision comparison. */ + fmt.format_str = format; + + /* Escape characters replacement. Do the most common ones. */ + format = std::regex_replace(format, std::regex(R"(\\n)"), "\n"); + format = std::regex_replace(format, std::regex(R"(\\v)"), "\v"); + format = std::regex_replace(format, std::regex(R"(\\t)"), "\t"); + format = std::regex_replace(format, std::regex(R"(\\')"), "\'"); + format = std::regex_replace(format, std::regex(R"(\\")"), "\""); + format = std::regex_replace(format, std::regex(R"(\\\\)"), "\\"); + + shader::PrintfFormat::Block::ArgumentType type = + shader::PrintfFormat::Block::ArgumentType::NONE; + int64_t start = 0, end = 0; + while ((end = format.find_first_of('%', start + 1)) != -1) { + /* Add the previous block without the newly found % character. */ + fmt.format_blocks.append({type, format.substr(start, end - start)}); + /* Format type of the next block. */ + /* TODO(fclem): This doesn't support advance formats like `%3.2f`. */ + switch (format[end + 1]) { + case 'x': + case 'u': + type = shader::PrintfFormat::Block::ArgumentType::UINT; + break; + case 'd': + type = shader::PrintfFormat::Block::ArgumentType::INT; + break; + case 'f': + type = shader::PrintfFormat::Block::ArgumentType::FLOAT; + break; + default: + BLI_assert_msg(0, "Printing format unsupported"); + break; + } + /* Start of the next block. */ + start = end; + } + fmt.format_blocks.append({type, format.substr(start, format.size() - start)}); + + format_map->add(format_hash, fmt); + } + + std::string sub_output = "print_header(" + std::to_string(format_hash) + "u, " + + std::to_string(format_arg_count) + "u)"; + for (std::string &arg : arguments) { + sub_output = "print_data(" + sub_output + ", " + arg + ")"; + } + + output << sub_output << ";\n"; + + cursor = last_pos = str_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(); + } + #undef find_keyword #undef rfind_keyword #undef find_token @@ -822,6 +1015,9 @@ struct GPUSource { using namespace shader; /* Auto dependency injection for debug capabilities. */ + if ((builtins & BuiltinBits::USE_PRINTF) == BuiltinBits::USE_PRINTF) { + dependencies.append_non_duplicates(dict.lookup("gpu_shader_print_lib.glsl")); + } if ((builtins & BuiltinBits::USE_DEBUG_DRAW) == BuiltinBits::USE_DEBUG_DRAW) { dependencies.append_non_duplicates(dict.lookup("common_debug_draw_lib.glsl")); } @@ -898,16 +1094,19 @@ struct GPUSource { using namespace blender::gpu; +static GPUPrintFormatMap *g_formats = nullptr; static GPUSourceDictionnary *g_sources = nullptr; static GPUFunctionDictionnary *g_functions = nullptr; +static bool force_printf_injection = false; void gpu_shader_dependency_init() { + g_formats = new GPUPrintFormatMap(); g_sources = new GPUSourceDictionnary(); g_functions = new GPUFunctionDictionnary(); #define SHADER_SOURCE(datatoc, filename, filepath) \ - g_sources->add_new(filename, new GPUSource(filepath, filename, datatoc, g_functions)); + g_sources->add_new(filename, new GPUSource(filepath, filename, datatoc, g_functions, g_formats)); #include "glsl_compositor_source_list.h" #include "glsl_draw_source_list.h" #include "glsl_gpu_source_list.h" @@ -922,6 +1121,21 @@ void gpu_shader_dependency_init() } BLI_assert_msg(errors == 0, "Dependency errors detected: Aborting"); UNUSED_VARS_NDEBUG(errors); + +#if GPU_SHADER_PRINTF_ENABLE + if (!g_formats->is_empty()) { + /* Detect if there is any printf in node lib files. + * See gpu_shader_dependency_force_gpu_print_injection(). */ + for (auto *value : g_sources->values()) { + if ((value->builtins & shader::BuiltinBits::USE_PRINTF) != shader::BuiltinBits::USE_PRINTF) { + if (value->filename.startswith("gpu_shader_material_")) { + force_printf_injection = true; + break; + } + } + } + } +#endif } void gpu_shader_dependency_exit() @@ -932,6 +1146,7 @@ void gpu_shader_dependency_exit() for (auto *value : g_functions->values()) { MEM_delete(value); } + delete g_formats; delete g_sources; delete g_functions; } @@ -947,6 +1162,23 @@ GPUFunction *gpu_material_library_use_function(GSet *used_libraries, const char namespace blender::gpu::shader { +bool gpu_shader_dependency_force_gpu_print_injection() +{ + /* WORKAROUND: We cannot know what shader will require printing if the printf is inside shader + * node code. In this case, we just force injection inside all shaders. */ + return force_printf_injection; +} + +bool gpu_shader_dependency_has_printf() +{ + return (g_formats != nullptr) && !g_formats->is_empty(); +} + +const PrintfFormat &gpu_shader_dependency_get_printf_format(uint32_t format_hash) +{ + return g_formats->lookup(format_hash); +} + BuiltinBits gpu_shader_dependency_get_builtins(const StringRefNull shader_source_name) { if (shader_source_name.is_empty()) { diff --git a/source/blender/gpu/intern/gpu_shader_dependency_private.hh b/source/blender/gpu/intern/gpu_shader_dependency_private.hh index e6f3a312c3c..7f7fa668e87 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency_private.hh +++ b/source/blender/gpu/intern/gpu_shader_dependency_private.hh @@ -24,6 +24,28 @@ namespace blender::gpu::shader { BuiltinBits gpu_shader_dependency_get_builtins(const StringRefNull source_name); +/* Returns true is any shader code has a printf statement. */ +bool gpu_shader_dependency_has_printf(); + +bool gpu_shader_dependency_force_gpu_print_injection(); + +struct PrintfFormat { + struct Block { + enum ArgumentType { + NONE = 0, + UINT, + INT, + FLOAT, + } type = NONE; + std::string fmt; + }; + + Vector format_blocks; + std::string format_str; +}; + +const PrintfFormat &gpu_shader_dependency_get_printf_format(uint32_t format_hash); + Vector gpu_shader_dependency_get_resolved_source(const StringRefNull source_name); StringRefNull gpu_shader_dependency_get_source(const StringRefNull source_name); diff --git a/source/blender/gpu/intern/gpu_shader_log.cc b/source/blender/gpu/intern/gpu_shader_log.cc index 00b9d08f07d..4c60be4ea4b 100644 --- a/source/blender/gpu/intern/gpu_shader_log.cc +++ b/source/blender/gpu/intern/gpu_shader_log.cc @@ -13,6 +13,9 @@ #include "BLI_string_utils.hh" #include "BLI_vector.hh" +#include "GPU_storage_buffer.hh" + +#include "gpu_context_private.hh" #include "gpu_shader_dependency_private.hh" #include "gpu_shader_private.hh" @@ -321,4 +324,76 @@ int GPULogParser::parse_number(const char *log_line, const char **r_new_position /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Shader Debug Printf + * \{ */ + +void printf_begin(Context *ctx) +{ + if (ctx == nullptr) { + return; + } + if (!shader::gpu_shader_dependency_has_printf()) { + return; + } + BLI_assert(ctx->printf_buf == nullptr); + ctx->printf_buf = GPU_storagebuf_create(GPU_SHADER_PRINTF_MAX_CAPACITY * sizeof(uint32_t)); + GPU_storagebuf_clear_to_zero(ctx->printf_buf); +} + +void printf_end(Context *ctx) +{ + if (ctx == nullptr) { + return; + } + if (ctx->printf_buf == nullptr) { + return; + } + + Vector data(GPU_SHADER_PRINTF_MAX_CAPACITY); + GPU_storagebuf_read(ctx->printf_buf, data.data()); + GPU_storagebuf_free(ctx->printf_buf); + ctx->printf_buf = nullptr; + + uint32_t data_len = data[0]; + if (data_len == 0) { + return; + } + if (data_len >= GPU_SHADER_PRINTF_MAX_CAPACITY) { + printf("Printf buffer overflow.\n"); + /* TODO(fclem): We can still read the uncorrupted part. */ + return; + } + + int cursor = 1; + while (cursor < data_len + 1) { + uint32_t format_hash = data[cursor++]; + + const shader::PrintfFormat &format = shader::gpu_shader_dependency_get_printf_format( + format_hash); + + for (const shader::PrintfFormat::Block &block : format.format_blocks) { + switch (block.type) { + case shader::PrintfFormat::Block::NONE: + printf("%s", block.fmt.c_str()); + break; + case shader::PrintfFormat::Block::UINT: + printf(block.fmt.c_str(), *reinterpret_cast(&data[cursor++])); + break; + case shader::PrintfFormat::Block::INT: + printf(block.fmt.c_str(), *reinterpret_cast(&data[cursor++])); + break; + case shader::PrintfFormat::Block::FLOAT: + printf(block.fmt.c_str(), *reinterpret_cast(&data[cursor++])); + break; + default: + BLI_assert_unreachable(); + break; + } + } + } +} + +/** \} */ + } // namespace blender::gpu diff --git a/source/blender/gpu/intern/gpu_shader_private.hh b/source/blender/gpu/intern/gpu_shader_private.hh index d823ca1d2bf..04189ff923c 100644 --- a/source/blender/gpu/intern/gpu_shader_private.hh +++ b/source/blender/gpu/intern/gpu_shader_private.hh @@ -24,6 +24,7 @@ namespace blender { namespace gpu { class GPULogParser; +class Context; /* Set to 1 to log the full source of shaders that fail to compile. */ #define DEBUG_LOG_SHADER_SRC_ON_ERROR 0 @@ -256,6 +257,9 @@ class GPULogParser { MEM_CXX_CLASS_ALLOC_FUNCS("GPULogParser"); }; +void printf_begin(Context *ctx); +void printf_end(Context *ctx); + } // namespace gpu } // namespace blender diff --git a/source/blender/gpu/shaders/common/gpu_shader_print_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_print_lib.glsl new file mode 100644 index 00000000000..f647ad64041 --- /dev/null +++ b/source/blender/gpu/shaders/common/gpu_shader_print_lib.glsl @@ -0,0 +1,27 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +uint print_data(uint offset, uint data) +{ + if (offset < GPU_SHADER_PRINTF_MAX_CAPACITY) { + gpu_print_buf[offset] = data; + } + return offset + 1u; +} + +uint print_data(uint offset, int data) +{ + return print_data(offset, uint(data)); +} + +uint print_data(uint offset, float data) +{ + return print_data(offset, floatBitsToUint(data)); +} + +uint print_header(uint format_hash, const uint data_len) +{ + uint offset = atomicAdd(gpu_print_buf[0], 1u + data_len) + 1u; + return print_data(offset, format_hash); +} diff --git a/source/blender/gpu/shaders/infos/gpu_shader_print_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_print_info.hh new file mode 100644 index 00000000000..37defa7d433 --- /dev/null +++ b/source/blender/gpu/shaders/infos/gpu_shader_print_info.hh @@ -0,0 +1,15 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup gpu + */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(gpu_print) + .storage_buf( + GPU_SHADER_PRINTF_SLOT, Qualifier::READ_WRITE, "uint", "gpu_print_buf[]", Frequency::PASS) + .define("GPU_SHADER_PRINTF_MAX_CAPACITY", STRINGIFY(GPU_SHADER_PRINTF_MAX_CAPACITY)) + .define("GPU_PRINT");