diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index fb0ecd791ae..3770e3fb9b8 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -232,6 +232,7 @@ set(VULKAN_SRC vulkan/vk_shader.cc vulkan/vk_shader_interface.cc vulkan/vk_shader_log.cc + vulkan/vk_shader_modules.cc vulkan/vk_staging_buffer.cc vulkan/vk_state_manager.cc vulkan/vk_storage_buffer.cc @@ -299,6 +300,7 @@ set(VULKAN_SRC vulkan/vk_shader.hh vulkan/vk_shader_interface.hh vulkan/vk_shader_log.hh + vulkan/vk_shader_modules.hh vulkan/vk_staging_buffer.hh vulkan/vk_state_manager.hh vulkan/vk_storage_buffer.hh diff --git a/source/blender/gpu/vulkan/vk_device.cc b/source/blender/gpu/vulkan/vk_device.cc index 17019fd9a58..2d1d59fafc0 100644 --- a/source/blender/gpu/vulkan/vk_device.cc +++ b/source/blender/gpu/vulkan/vk_device.cc @@ -49,6 +49,7 @@ void VKDevice::deinit() samplers_.free(); destroy_discarded_resources(); pipelines.free_data(); + shader_modules.free_data(); vkDestroyPipelineCache(vk_device_, vk_pipeline_cache_, vk_allocation_callbacks); descriptor_set_layouts_.deinit(); vmaDestroyAllocator(mem_allocator_); diff --git a/source/blender/gpu/vulkan/vk_device.hh b/source/blender/gpu/vulkan/vk_device.hh index ceaeda95aa1..fd6aeb28374 100644 --- a/source/blender/gpu/vulkan/vk_device.hh +++ b/source/blender/gpu/vulkan/vk_device.hh @@ -19,6 +19,7 @@ #include "vk_descriptor_set_layouts.hh" #include "vk_pipeline_pool.hh" #include "vk_samplers.hh" +#include "vk_shader_modules.hh" #include "vk_timeline_semaphore.hh" namespace blender::gpu { @@ -112,6 +113,7 @@ class VKDevice : public NonCopyable { public: render_graph::VKResourceStateTracker resources; + VKShaderModules shader_modules; VKPipelinePool pipelines; /** diff --git a/source/blender/gpu/vulkan/vk_shader.cc b/source/blender/gpu/vulkan/vk_shader.cc index 5e2bcd3d750..f71127cb08f 100644 --- a/source/blender/gpu/vulkan/vk_shader.cc +++ b/source/blender/gpu/vulkan/vk_shader.cc @@ -479,113 +479,37 @@ static std::string main_function_wrapper(std::string &pre_main, std::string &pos return ss.str(); } -static const std::string to_stage_name(shaderc_shader_kind stage) -{ - switch (stage) { - case shaderc_vertex_shader: - return std::string("vertex"); - case shaderc_geometry_shader: - return std::string("geometry"); - case shaderc_fragment_shader: - return std::string("fragment"); - case shaderc_compute_shader: - return std::string("compute"); - - default: - BLI_assert_msg(false, "Do not know how to convert shaderc_shader_kind to stage name."); - break; - } - return std::string("unknown stage"); -} - -static std::string combine_sources(Span sources) -{ - char *sources_combined = BLI_string_join_arrayN((const char **)sources.data(), sources.size()); - std::string result(sources_combined); - MEM_freeN(sources_combined); - return result; -} - -Vector VKShader::compile_glsl_to_spirv(Span sources, - shaderc_shader_kind stage) -{ - std::string combined_sources = combine_sources(sources); - VKBackend &backend = VKBackend::get(); - shaderc::Compiler &compiler = backend.get_shaderc_compiler(); - shaderc::CompileOptions options; - options.SetOptimizationLevel(shaderc_optimization_level_performance); - options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2); - if (G.debug & G_DEBUG_GPU_RENDERDOC) { - options.SetOptimizationLevel(shaderc_optimization_level_zero); - options.SetGenerateDebugInfo(); - } - - shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv( - combined_sources, stage, name, options); - if (module.GetNumErrors() != 0 || module.GetNumWarnings() != 0) { - std::string log = module.GetErrorMessage(); - Vector logcstr(log.c_str(), log.c_str() + log.size() + 1); - - VKLogParser parser; - print_log(sources, - logcstr.data(), - to_stage_name(stage).c_str(), - module.GetCompilationStatus() != shaderc_compilation_status_success, - &parser); - } - - if (module.GetCompilationStatus() != shaderc_compilation_status_success) { - compilation_failed_ = true; - return Vector(); - } - - return Vector(module.cbegin(), module.cend()); -} - -void VKShader::build_shader_module(Span spirv_module, VkShaderModule *r_shader_module) -{ - VK_ALLOCATION_CALLBACKS; - - VkShaderModuleCreateInfo create_info = {}; - create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - create_info.codeSize = spirv_module.size() * sizeof(uint32_t); - create_info.pCode = spirv_module.data(); - - const VKDevice &device = VKBackend::get().device_get(); - VkResult result = vkCreateShaderModule( - device.device_get(), &create_info, vk_allocation_callbacks, r_shader_module); - if (result == VK_SUCCESS) { - debug::object_label(*r_shader_module, name); - } - else { - compilation_failed_ = true; - *r_shader_module = VK_NULL_HANDLE; - } -} - VKShader::VKShader(const char *name) : Shader(name) { context_ = VKContext::get(); } +void VKShader::init(const shader::ShaderCreateInfo &info) +{ + create_info_ = &info; + VKShaderInterface *vk_interface = new VKShaderInterface(); + vk_interface->init(info); + interface = vk_interface; +} + VKShader::~VKShader() { VK_ALLOCATION_CALLBACKS - const VKDevice &device = VKBackend::get().device_get(); + VKDevice &device = VKBackend::get().device_get(); if (vertex_module_ != VK_NULL_HANDLE) { - vkDestroyShaderModule(device.device_get(), vertex_module_, vk_allocation_callbacks); + device.shader_modules.destruct(vertex_module_); vertex_module_ = VK_NULL_HANDLE; } if (geometry_module_ != VK_NULL_HANDLE) { - vkDestroyShaderModule(device.device_get(), geometry_module_, vk_allocation_callbacks); + device.shader_modules.destruct(geometry_module_); geometry_module_ = VK_NULL_HANDLE; } if (fragment_module_ != VK_NULL_HANDLE) { - vkDestroyShaderModule(device.device_get(), fragment_module_, vk_allocation_callbacks); + device.shader_modules.destruct(fragment_module_); fragment_module_ = VK_NULL_HANDLE; } if (compute_module_ != VK_NULL_HANDLE) { - vkDestroyShaderModule(device.device_get(), compute_module_, vk_allocation_callbacks); + device.shader_modules.destruct(compute_module_); compute_module_ = VK_NULL_HANDLE; } if (vk_pipeline_layout_ != VK_NULL_HANDLE) { @@ -594,22 +518,16 @@ VKShader::~VKShader() } /* Reset not owning handles. */ vk_descriptor_set_layout_ = VK_NULL_HANDLE; + create_info_ = nullptr; } void VKShader::build_shader_module(MutableSpan sources, shaderc_shader_kind stage, VkShaderModule *r_shader_module) { - BLI_assert_msg(ELEM(stage, - shaderc_vertex_shader, - shaderc_geometry_shader, - shaderc_fragment_shader, - shaderc_compute_shader), - "Only forced ShaderC shader kinds are supported."); - const VKDevice &device = VKBackend::get().device_get(); + VKDevice &device = VKBackend::get().device_get(); sources[SOURCES_INDEX_VERSION] = device.glsl_patch_get(); - Vector spirv_module = compile_glsl_to_spirv(sources, stage); - build_shader_module(spirv_module, r_shader_module); + device.shader_modules.construct(*this, *create_info_, sources, stage, r_shader_module); } void VKShader::vertex_shader_from_glsl(MutableSpan sources) @@ -651,18 +569,23 @@ bool VKShader::finalize(const shader::ShaderCreateInfo *info) geometry_shader_from_glsl(sources); } - VKShaderInterface *vk_interface = new VKShaderInterface(); - vk_interface->init(*info); + /* Create infos of none static compilation shaders can be destructed after the shader are + * compiled. Better to reset the value to not confuse developers during debugging. */ + if (!create_info_->do_static_compilation_) { + create_info_ = nullptr; + } + + const VKShaderInterface &vk_interface = interface_get(); VKDevice &device = VKBackend::get().device_get(); - if (!finalize_descriptor_set_layouts(device, *vk_interface)) { + if (!finalize_descriptor_set_layouts(device, vk_interface)) { return false; } - if (!finalize_pipeline_layout(device.device_get(), *vk_interface)) { + if (!finalize_pipeline_layout(device.device_get(), vk_interface)) { return false; } - push_constants = VKPushConstants(&vk_interface->push_constants_layout_get()); + push_constants = VKPushConstants(&vk_interface.push_constants_layout_get()); bool result; if (use_render_graph) { @@ -686,11 +609,9 @@ bool VKShader::finalize(const shader::ShaderCreateInfo *info) } } - if (result) { - interface = vk_interface; - } - else { - delete vk_interface; + if (!result) { + delete interface; + interface = nullptr; } return result; } @@ -798,8 +719,7 @@ void VKShader::uniform_int(int location, int comp_len, int array_size, const int std::string VKShader::resources_declare(const shader::ShaderCreateInfo &info) const { - VKShaderInterface interface; - interface.init(info); + const VKShaderInterface &vk_interface = interface_get(); std::stringstream ss; ss << "\n/* Specialization Constants (pass-through). */\n"; @@ -830,16 +750,16 @@ std::string VKShader::resources_declare(const shader::ShaderCreateInfo &info) co ss << "\n/* Pass Resources. */\n"; for (const ShaderCreateInfo::Resource &res : info.pass_resources_) { - print_resource(ss, interface, res); + print_resource(ss, vk_interface, res); } ss << "\n/* Batch Resources. */\n"; for (const ShaderCreateInfo::Resource &res : info.batch_resources_) { - print_resource(ss, interface, res); + print_resource(ss, vk_interface, res); } /* Push constants. */ - const VKPushConstants::Layout &push_constants_layout = interface.push_constants_layout_get(); + const VKPushConstants::Layout &push_constants_layout = vk_interface.push_constants_layout_get(); const VKPushConstants::StorageType push_constants_storage = push_constants_layout.storage_type_get(); if (push_constants_storage != VKPushConstants::StorageType::NONE) { diff --git a/source/blender/gpu/vulkan/vk_shader.hh b/source/blender/gpu/vulkan/vk_shader.hh index 3776abf6163..7e825e2b3a9 100644 --- a/source/blender/gpu/vulkan/vk_shader.hh +++ b/source/blender/gpu/vulkan/vk_shader.hh @@ -18,8 +18,11 @@ namespace blender::gpu { class VKShaderInterface; +class VKShaderModules; class VKShader : public Shader { + friend class VKShaderModules; + private: VKContext *context_ = nullptr; VkShaderModule vertex_module_ = VK_NULL_HANDLE; @@ -43,13 +46,16 @@ class VKShader : public Shader { */ VkPipeline vk_pipeline_ = VK_NULL_HANDLE; + /** Create info used to construct this shader. */ + const shader::ShaderCreateInfo *create_info_ = nullptr; + public: VKPushConstants push_constants; VKShader(const char *name); virtual ~VKShader(); - void init(const shader::ShaderCreateInfo & /*info*/) override {} + void init(const shader::ShaderCreateInfo &info) override; void vertex_shader_from_glsl(MutableSpan sources) override; void geometry_shader_from_glsl(MutableSpan sources) override; @@ -128,8 +134,6 @@ class VKShader : public Shader { } private: - Vector compile_glsl_to_spirv(Span sources, shaderc_shader_kind kind); - void build_shader_module(Span spirv_module, VkShaderModule *r_shader_module); void build_shader_module(MutableSpan sources, shaderc_shader_kind stage, VkShaderModule *r_shader_module); diff --git a/source/blender/gpu/vulkan/vk_shader_modules.cc b/source/blender/gpu/vulkan/vk_shader_modules.cc new file mode 100644 index 00000000000..995080c275f --- /dev/null +++ b/source/blender/gpu/vulkan/vk_shader_modules.cc @@ -0,0 +1,215 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup gpu + */ + +#include "BLI_hash.hh" +#include "BLI_string_utils.hh" +#include "BLI_time.h" + +#include "gpu_shader_create_info.hh" + +#include "vk_backend.hh" +#include "vk_memory.hh" +#include "vk_shader.hh" +#include "vk_shader_log.hh" +#include "vk_shader_modules.hh" + +#include + +namespace blender::gpu { + +static const std::string to_stage_name(shaderc_shader_kind stage) +{ + switch (stage) { + case shaderc_vertex_shader: + return std::string("vertex"); + case shaderc_geometry_shader: + return std::string("geometry"); + case shaderc_fragment_shader: + return std::string("fragment"); + case shaderc_compute_shader: + return std::string("compute"); + + default: + BLI_assert_msg(false, "Do not know how to convert shaderc_shader_kind to stage name."); + break; + } + return std::string("unknown stage"); +} + +bool VKShaderModules::construct(VKShader &shader, + const shader::ShaderCreateInfo &info, + Span sources, + shaderc_shader_kind stage, + VkShaderModule *r_shader_module) +{ + BLI_assert_msg(ELEM(stage, + shaderc_vertex_shader, + shaderc_geometry_shader, + shaderc_fragment_shader, + shaderc_compute_shader), + "Only forced ShaderC shader kinds are supported."); + + Vector spirv_module; + if (!compile_glsl_to_spirv(shader, sources, stage, spirv_module)) { + r_shader_module = VK_NULL_HANDLE; + return false; + } + + const bool use_module_cache = !info.do_static_compilation_; + uint64_t cache_key = 0; + if (use_module_cache) { + cache_key = spirv_module.hash(); + VkShaderModule *shader_module = cache_.lookup_ptr(cache_key); + if (shader_module) { + printf("%s: %s.%s\n", __func__, info.name_.c_str(), to_stage_name(stage).c_str()); + stats_.shader_modules_reused += 1; + *r_shader_module = *shader_module; + return true; + } + } + + if (!build_shader_module(shader, spirv_module, r_shader_module)) { + r_shader_module = VK_NULL_HANDLE; + return false; + } + + if (use_module_cache) { + cache_.add_new(cache_key, *r_shader_module); + cached_values_.add_new(*r_shader_module); + } + return true; +} + +void VKShaderModules::destruct(VkShaderModule vk_shader_module) +{ + if (cached_values_.contains(vk_shader_module)) { + return; + } + + VK_ALLOCATION_CALLBACKS + const VKDevice &device = VKBackend::get().device_get(); + vkDestroyShaderModule(device.device_get(), vk_shader_module, vk_allocation_callbacks); +} + +void VKShaderModules::free_data() +{ + debug_print(); + + VK_ALLOCATION_CALLBACKS + const VKDevice &device = VKBackend::get().device_get(); + for (VkShaderModule vk_shader_module : cached_values_) { + vkDestroyShaderModule(device.device_get(), vk_shader_module, vk_allocation_callbacks); + } + cache_.clear(); + cached_values_.clear(); +} + +/* -------------------------------------------------------------------- */ +/** \name Frontend compilation (GLSL -> SpirV) + * + * \{ */ + +static std::string combine_sources(Span sources) +{ + char *sources_combined = BLI_string_join_arrayN((const char **)sources.data(), sources.size()); + std::string result(sources_combined); + MEM_freeN(sources_combined); + return result; +} + +bool VKShaderModules::compile_glsl_to_spirv(VKShader &shader, + Span sources, + shaderc_shader_kind stage, + Vector &r_compiled_spirv) +{ + const double start_time = BLI_time_now_seconds(); + std::string combined_sources = combine_sources(sources); + VKBackend &backend = VKBackend::get(); + shaderc::Compiler &compiler = backend.get_shaderc_compiler(); + shaderc::CompileOptions options; + options.SetOptimizationLevel(shaderc_optimization_level_performance); + options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2); + if (G.debug & G_DEBUG_GPU_RENDERDOC) { + options.SetOptimizationLevel(shaderc_optimization_level_zero); + options.SetGenerateDebugInfo(); + } + + shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv( + combined_sources, stage, shader.name, options); + if (module.GetNumErrors() != 0 || module.GetNumWarnings() != 0) { + std::string log = module.GetErrorMessage(); + Vector logcstr(log.c_str(), log.c_str() + log.size() + 1); + + VKLogParser parser; + shader.print_log(sources, + logcstr.data(), + to_stage_name(stage).c_str(), + module.GetCompilationStatus() != shaderc_compilation_status_success, + &parser); + } + stats_.glsl_to_spirv_time += BLI_time_now_seconds() - start_time; + r_compiled_spirv.clear(); + if (module.GetCompilationStatus() != shaderc_compilation_status_success) { + return false; + } + + r_compiled_spirv.extend(module.cbegin(), module.cend()); + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Frontend compilation (SpirV -> ShaderModule) + * + * \{ */ +bool VKShaderModules::build_shader_module(const VKShader &shader, + Span spirv_module, + VkShaderModule *r_shader_module) +{ + const double start_time = BLI_time_now_seconds(); + VK_ALLOCATION_CALLBACKS; + + VkShaderModuleCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.codeSize = spirv_module.size() * sizeof(uint32_t); + create_info.pCode = spirv_module.data(); + + const VKDevice &device = VKBackend::get().device_get(); + VkResult result = vkCreateShaderModule( + device.device_get(), &create_info, vk_allocation_callbacks, r_shader_module); + stats_.spirv_to_shader_module_time += BLI_time_now_seconds() - start_time; + if (result == VK_SUCCESS) { + debug::object_label(*r_shader_module, shader.name); + return true; + } + else { + *r_shader_module = VK_NULL_HANDLE; + return false; + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Debugging and statistics + * + * \{ */ + +void VKShaderModules::debug_print() const +{ + std::stringstream ss; + ss << "VKShaderModules(reused_shader_modules=" << stats_.shader_modules_reused + << ", glsl_spirv_time=" << stats_.glsl_to_spirv_time << "s" + << ", spirv_shader_module_time" << stats_.spirv_to_shader_module_time << "s)\n"; + std::cout << ss.str(); +} + +/** \} */ + +} // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_shader_modules.hh b/source/blender/gpu/vulkan/vk_shader_modules.hh new file mode 100644 index 00000000000..a5f5922db3b --- /dev/null +++ b/source/blender/gpu/vulkan/vk_shader_modules.hh @@ -0,0 +1,65 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup gpu + */ + +#pragma once + +#include + +#include "GPU_shader.hh" + +#include "vk_common.hh" + +#include "shaderc/shaderc.hpp" + +namespace blender::gpu { +class VKShader; +namespace shader { +class ShaderCreateInfo; +} + +/** + * Pool of shader modules. + * + * Responsibility of VKShaderModules: + * - Reusing of VkShaderModule between shaders to reduce compilation and pipeline creation + * times. + * - Loading of precompiled spirv bytecode. + */ +class VKShaderModules { + private: + Map cache_; + Set cached_values_; + + struct { + double glsl_to_spirv_time = 0.0; + double spirv_to_shader_module_time = 0.0; + int64_t shader_modules_reused = 0; + } stats_; + + public: + bool construct(VKShader &shader, + const shader::ShaderCreateInfo &info, + Span sources, + shaderc_shader_kind stage, + VkShaderModule *r_shader_module); + void destruct(VkShaderModule vk_shader_module); + void free_data(); + + void debug_print() const; + + private: + bool compile_glsl_to_spirv(VKShader &shader, + Span sources, + shaderc_shader_kind stage, + Vector &r_compiled_spirv); + bool build_shader_module(const VKShader &shader, + Span spirv_module, + VkShaderModule *r_shader_module); +}; + +} // namespace blender::gpu