WIP: Vulkan: Reuse shader modules. #122044

Closed
Jeroen Bakker wants to merge 3 commits from Jeroen-Bakker:vulkan/shader-modules into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
7 changed files with 325 additions and 116 deletions

View File

@ -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

View File

@ -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_);

View File

@ -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;
/**

View File

@ -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<const char *> 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<uint32_t> VKShader::compile_glsl_to_spirv(Span<const char *> 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<char> 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<uint32_t>();
}
return Vector<uint32_t>(module.cbegin(), module.cend());
}
void VKShader::build_shader_module(Span<uint32_t> 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<const char *> 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<uint32_t> 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<const char *> 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) {

View File

@ -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<const char *> sources) override;
void geometry_shader_from_glsl(MutableSpan<const char *> sources) override;
@ -128,8 +134,6 @@ class VKShader : public Shader {
}
private:
Vector<uint32_t> compile_glsl_to_spirv(Span<const char *> sources, shaderc_shader_kind kind);
void build_shader_module(Span<uint32_t> spirv_module, VkShaderModule *r_shader_module);
void build_shader_module(MutableSpan<const char *> sources,
shaderc_shader_kind stage,
VkShaderModule *r_shader_module);

View File

@ -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 <sstream>
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<const char *> 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<uint32_t> 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<const char *> 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<const char *> sources,
shaderc_shader_kind stage,
Vector<uint32_t> &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<char> 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<uint32_t> 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

View File

@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include <BLI_map.hh>
#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<uint64_t, VkShaderModule> cache_;
Set<VkShaderModule> 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<const char *> 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<const char *> sources,
shaderc_shader_kind stage,
Vector<uint32_t> &r_compiled_spirv);
bool build_shader_module(const VKShader &shader,
Span<uint32_t> spirv_module,
VkShaderModule *r_shader_module);
};
} // namespace blender::gpu