Skip to content

Commit 666bcb2

Browse files
committed
Merge pull request #111452 from DarioSamo/re-spirv
Use re-spirv in the Vulkan driver to optimize shaders.
2 parents bed803f + cf00643 commit 666bcb2

File tree

18 files changed

+9022
-7724
lines changed

18 files changed

+9022
-7724
lines changed

drivers/metal/SCsub

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ env_metal = env.Clone()
99

1010
thirdparty_obj = []
1111

12+
thirdparty_spirv_headers_dir = "#thirdparty/spirv-headers/"
1213
thirdparty_dir = "#thirdparty/spirv-cross/"
1314
thirdparty_sources = [
1415
"spirv_cfg.cpp",
@@ -22,6 +23,7 @@ thirdparty_sources = [
2223
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
2324

2425
env_metal.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include"])
26+
env_metal.Prepend(CPPPATH=[thirdparty_spirv_headers_dir + "include/spirv/unified1"])
2527

2628
# Must enable exceptions for SPIRV-Cross; otherwise, it will abort the process on errors.
2729
if "-fno-exceptions" in env_metal["CXXFLAGS"]:

drivers/vulkan/SCsub

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Import("env")
66
thirdparty_obj = []
77
thirdparty_dir = "#thirdparty/vulkan"
88
thirdparty_volk_dir = "#thirdparty/volk"
9+
thirdparty_spirv_headers_dir = "#thirdparty/spirv-headers"
10+
thirdparty_respirv_dir = "#thirdparty/re-spirv"
911

1012
# Use bundled Vulkan headers
1113
env.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include"])
@@ -50,6 +52,12 @@ elif env["platform"] == "macos" or env["platform"] == "ios":
5052

5153
env_thirdparty_vma.add_source_files(thirdparty_obj, thirdparty_sources_vma)
5254

55+
# Build re-spirv
56+
env_thirdparty_respirv = env.Clone()
57+
env_thirdparty_respirv.Prepend(CPPPATH=[thirdparty_spirv_headers_dir + "/include"])
58+
env_thirdparty_respirv.disable_warnings()
59+
thirdparty_sources_respirv = [thirdparty_respirv_dir + "/re-spirv.cpp"]
60+
env_thirdparty_respirv.add_source_files(thirdparty_obj, thirdparty_sources_respirv)
5361

5462
env.drivers_sources += thirdparty_obj
5563

drivers/vulkan/rendering_device_driver_vulkan.cpp

Lines changed: 190 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,27 @@
5050

5151
#define PRINT_NATIVE_COMMANDS 0
5252

53+
// Enable the use of re-spirv for optimizing shaders after applying specialization constants.
54+
#define RESPV_ENABLED 1
55+
56+
// Only enable function inlining for re-spirv when dealing with a shader that uses specialization constants.
57+
#define RESPV_ONLY_INLINE_SHADERS_WITH_SPEC_CONSTANTS 1
58+
59+
// Print additional information about every shader optimized with re-spirv.
60+
#define RESPV_VERBOSE 0
61+
62+
// Disable dead code elimination when using re-spirv.
63+
#define RESPV_DONT_REMOVE_DEAD_CODE 0
64+
65+
// Record numerous statistics about pipeline creation such as time and shader sizes. When combined with enabling
66+
// and disabling re-spirv, this can be used to measure its effects.
67+
#define RECORD_PIPELINE_STATISTICS 0
68+
69+
#if RECORD_PIPELINE_STATISTICS
70+
#include "core/io/file_access.h"
71+
#define RECORD_PIPELINE_STATISTICS_PATH "./pipelines.csv"
72+
#endif
73+
5374
/*****************/
5475
/**** GENERIC ****/
5576
/*****************/
@@ -1637,6 +1658,14 @@ Error RenderingDeviceDriverVulkan::initialize(uint32_t p_device_index, uint32_t
16371658

16381659
shader_container_format.set_debug_info_enabled(Engine::get_singleton()->is_generate_spirv_debug_info_enabled());
16391660

1661+
#if RECORD_PIPELINE_STATISTICS
1662+
pipeline_statistics.file_access = FileAccess::open(RECORD_PIPELINE_STATISTICS_PATH, FileAccess::WRITE);
1663+
ERR_FAIL_NULL_V_MSG(pipeline_statistics.file_access, ERR_CANT_CREATE, "Unable to write pipeline statistics file.");
1664+
1665+
pipeline_statistics.file_access->store_csv_line({ "name", "hash", "stage", "spec", "glslang", "re-spirv", "time" });
1666+
pipeline_statistics.file_access->flush();
1667+
#endif
1668+
16401669
return OK;
16411670
}
16421671

@@ -3760,6 +3789,8 @@ static VkShaderStageFlagBits RD_STAGE_TO_VK_SHADER_STAGE_BITS[RDD::SHADER_STAGE_
37603789
RDD::ShaderID RenderingDeviceDriverVulkan::shader_create_from_container(const Ref<RenderingShaderContainer> &p_shader_container, const Vector<ImmutableSampler> &p_immutable_samplers) {
37613790
ShaderReflection shader_refl = p_shader_container->get_shader_reflection();
37623791
ShaderInfo shader_info;
3792+
shader_info.name = p_shader_container->shader_name.get_data();
3793+
37633794
for (uint32_t i = 0; i < SHADER_STAGE_MAX; i++) {
37643795
if (shader_refl.push_constant_stages.has_flag((ShaderStage)(1 << i))) {
37653796
shader_info.vk_push_constant_stages |= RD_STAGE_TO_VK_SHADER_STAGE_BITS[i];
@@ -3846,9 +3877,20 @@ RDD::ShaderID RenderingDeviceDriverVulkan::shader_create_from_container(const Re
38463877
VkResult res;
38473878
String error_text;
38483879
Vector<uint8_t> decompressed_code;
3849-
Vector<uint8_t> decoded_spirv;
38503880
VkShaderModule vk_module;
3851-
for (int i = 0; i < shader_refl.stages_vector.size(); i++) {
3881+
PackedByteArray decoded_spirv;
3882+
const bool use_respv = RESPV_ENABLED && !shader_container_format.get_debug_info_enabled();
3883+
const bool store_respv = use_respv && !shader_refl.specialization_constants.is_empty();
3884+
const int64_t stage_count = shader_refl.stages_vector.size();
3885+
shader_info.vk_stages_create_info.reserve(stage_count);
3886+
shader_info.spirv_stage_bytes.reserve(stage_count);
3887+
shader_info.original_stage_size.reserve(stage_count);
3888+
3889+
if (store_respv) {
3890+
shader_info.respv_stage_shaders.reserve(stage_count);
3891+
}
3892+
3893+
for (int i = 0; i < stage_count; i++) {
38523894
const RenderingShaderContainer::Shader &shader = p_shader_container->shaders[i];
38533895
bool requires_decompression = (shader.code_decompressed_size > 0);
38543896
if (requires_decompression) {
@@ -3878,6 +3920,31 @@ RDD::ShaderID RenderingDeviceDriverVulkan::shader_create_from_container(const Re
38783920
memcpy(decoded_spirv.ptrw(), smolv_input, decoded_spirv.size());
38793921
}
38803922

3923+
shader_info.original_stage_size.push_back(decoded_spirv.size());
3924+
3925+
if (use_respv) {
3926+
const bool inline_data = store_respv || !RESPV_ONLY_INLINE_SHADERS_WITH_SPEC_CONSTANTS;
3927+
respv::Shader respv_shader(decoded_spirv.ptr(), decoded_spirv.size(), inline_data);
3928+
if (store_respv) {
3929+
shader_info.respv_stage_shaders.push_back(respv_shader);
3930+
} else {
3931+
std::vector<uint8_t> respv_optimized_data;
3932+
if (respv::Optimizer::run(respv_shader, nullptr, 0, respv_optimized_data)) {
3933+
#if RESPV_VERBOSE
3934+
print_line(vformat("re-spirv transformed the shader from %d bytes to %d bytes.", decoded_spirv.size(), respv_optimized_data.size()));
3935+
#endif
3936+
decoded_spirv.resize(respv_optimized_data.size());
3937+
memcpy(decoded_spirv.ptrw(), respv_optimized_data.data(), respv_optimized_data.size());
3938+
} else {
3939+
#if RESPV_VERBOSE
3940+
print_line("re-spirv failed to optimize the shader.");
3941+
#endif
3942+
}
3943+
}
3944+
}
3945+
3946+
shader_info.spirv_stage_bytes.push_back(decoded_spirv);
3947+
38813948
VkShaderModuleCreateInfo shader_module_create_info = {};
38823949
shader_module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
38833950
shader_module_create_info.codeSize = decoded_spirv.size();
@@ -5508,26 +5575,104 @@ RDD::PipelineID RenderingDeviceDriverVulkan::render_pipeline_create(
55085575
"Cannot create pipeline without shader module, please make sure shader modules are destroyed only after all associated pipelines are created.");
55095576
VkPipelineShaderStageCreateInfo *vk_pipeline_stages = ALLOCA_ARRAY(VkPipelineShaderStageCreateInfo, shader_info->vk_stages_create_info.size());
55105577

5578+
thread_local std::vector<uint8_t> respv_optimized_data;
5579+
thread_local LocalVector<respv::SpecConstant> respv_spec_constants;
5580+
thread_local LocalVector<VkShaderModule> respv_shader_modules;
5581+
thread_local LocalVector<VkSpecializationMapEntry> specialization_entries;
5582+
5583+
#if RECORD_PIPELINE_STATISTICS
5584+
thread_local LocalVector<uint64_t> respv_run_time;
5585+
thread_local LocalVector<uint64_t> respv_size;
5586+
uint32_t stage_count = shader_info->vk_stages_create_info.size();
5587+
respv_run_time.clear();
5588+
respv_size.clear();
5589+
respv_run_time.resize_initialized(stage_count);
5590+
respv_size.resize_initialized(stage_count);
5591+
#endif
5592+
5593+
respv_shader_modules.clear();
5594+
specialization_entries.clear();
5595+
55115596
for (uint32_t i = 0; i < shader_info->vk_stages_create_info.size(); i++) {
55125597
vk_pipeline_stages[i] = shader_info->vk_stages_create_info[i];
55135598

55145599
if (p_specialization_constants.size()) {
5515-
VkSpecializationMapEntry *specialization_map_entries = ALLOCA_ARRAY(VkSpecializationMapEntry, p_specialization_constants.size());
5516-
for (uint32_t j = 0; j < p_specialization_constants.size(); j++) {
5517-
specialization_map_entries[j] = {};
5518-
specialization_map_entries[j].constantID = p_specialization_constants[j].constant_id;
5519-
specialization_map_entries[j].offset = (const char *)&p_specialization_constants[j].int_value - (const char *)p_specialization_constants.ptr();
5520-
specialization_map_entries[j].size = sizeof(uint32_t);
5600+
bool use_pipeline_spec_constants = true;
5601+
if ((i < shader_info->respv_stage_shaders.size()) && !shader_info->respv_stage_shaders[i].empty()) {
5602+
#if RECORD_PIPELINE_STATISTICS
5603+
uint64_t respv_start_time = OS::get_singleton()->get_ticks_usec();
5604+
#endif
5605+
// Attempt to optimize the shader using re-spirv before relying on the driver.
5606+
respv_spec_constants.resize(p_specialization_constants.size());
5607+
for (uint32_t j = 0; j < p_specialization_constants.size(); j++) {
5608+
respv_spec_constants[j].specId = p_specialization_constants[j].constant_id;
5609+
respv_spec_constants[j].values.resize(1);
5610+
respv_spec_constants[j].values[0] = p_specialization_constants[j].int_value;
5611+
}
5612+
5613+
respv::Options respv_options;
5614+
#if RESPV_DONT_REMOVE_DEAD_CODE
5615+
respv_options.removeDeadCode = false;
5616+
#endif
5617+
if (respv::Optimizer::run(shader_info->respv_stage_shaders[i], respv_spec_constants.ptr(), respv_spec_constants.size(), respv_optimized_data, respv_options)) {
5618+
#if RESPV_VERBOSE
5619+
String spec_constants;
5620+
for (uint32_t j = 0; j < p_specialization_constants.size(); j++) {
5621+
spec_constants += vformat("%d: %d", p_specialization_constants[j].constant_id, p_specialization_constants[j].int_value);
5622+
if (j < p_specialization_constants.size() - 1) {
5623+
spec_constants += ", ";
5624+
}
5625+
}
5626+
5627+
print_line(vformat("re-spirv transformed the shader from %d bytes to %d bytes with constants %s (%d).", shader_info->spirv_stage_bytes[i].size(), respv_optimized_data.size(), spec_constants, p_shader.id));
5628+
#endif
5629+
5630+
// Create the shader module with the optimized output.
5631+
VkShaderModule shader_module = VK_NULL_HANDLE;
5632+
VkShaderModuleCreateInfo shader_module_create_info = {};
5633+
shader_module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
5634+
shader_module_create_info.pCode = (const uint32_t *)(respv_optimized_data.data());
5635+
shader_module_create_info.codeSize = respv_optimized_data.size();
5636+
VkResult err = vkCreateShaderModule(vk_device, &shader_module_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_SHADER_MODULE), &shader_module);
5637+
if (err == VK_SUCCESS) {
5638+
// Replace the module used in the creation info.
5639+
vk_pipeline_stages[i].module = shader_module;
5640+
respv_shader_modules.push_back(shader_module);
5641+
use_pipeline_spec_constants = false;
5642+
}
5643+
5644+
#if RECORD_PIPELINE_STATISTICS
5645+
respv_run_time[i] = OS::get_singleton()->get_ticks_usec() - respv_start_time;
5646+
respv_size[i] = respv_optimized_data.size();
5647+
#endif
5648+
} else {
5649+
#if RESPV_VERBOSE
5650+
print_line("re-spirv failed to optimize the shader.");
5651+
#endif
5652+
}
55215653
}
55225654

5523-
VkSpecializationInfo *specialization_info = ALLOCA_SINGLE(VkSpecializationInfo);
5524-
*specialization_info = {};
5525-
specialization_info->dataSize = p_specialization_constants.size() * sizeof(PipelineSpecializationConstant);
5526-
specialization_info->pData = p_specialization_constants.ptr();
5527-
specialization_info->mapEntryCount = p_specialization_constants.size();
5528-
specialization_info->pMapEntries = specialization_map_entries;
5655+
if (use_pipeline_spec_constants) {
5656+
// Use specialization constants through the driver.
5657+
if (specialization_entries.is_empty()) {
5658+
specialization_entries.resize(p_specialization_constants.size());
5659+
for (uint32_t j = 0; j < p_specialization_constants.size(); j++) {
5660+
specialization_entries[j] = {};
5661+
specialization_entries[j].constantID = p_specialization_constants[j].constant_id;
5662+
specialization_entries[j].offset = (const char *)&p_specialization_constants[j].int_value - (const char *)p_specialization_constants.ptr();
5663+
specialization_entries[j].size = sizeof(uint32_t);
5664+
}
5665+
}
55295666

5530-
vk_pipeline_stages[i].pSpecializationInfo = specialization_info;
5667+
VkSpecializationInfo *specialization_info = ALLOCA_SINGLE(VkSpecializationInfo);
5668+
*specialization_info = {};
5669+
specialization_info->dataSize = p_specialization_constants.size() * sizeof(PipelineSpecializationConstant);
5670+
specialization_info->pData = p_specialization_constants.ptr();
5671+
specialization_info->mapEntryCount = specialization_entries.size();
5672+
specialization_info->pMapEntries = specialization_entries.ptr();
5673+
5674+
vk_pipeline_stages[i].pSpecializationInfo = specialization_info;
5675+
}
55315676
}
55325677
}
55335678

@@ -5546,12 +5691,41 @@ RDD::PipelineID RenderingDeviceDriverVulkan::render_pipeline_create(
55465691
pipeline_create_info.renderPass = render_pass->vk_render_pass;
55475692
pipeline_create_info.subpass = p_render_subpass;
55485693

5549-
// ---
5694+
#if RECORD_PIPELINE_STATISTICS
5695+
uint64_t pipeline_start_time = OS::get_singleton()->get_ticks_usec();
5696+
#endif
55505697

55515698
VkPipeline vk_pipeline = VK_NULL_HANDLE;
55525699
VkResult err = vkCreateGraphicsPipelines(vk_device, pipelines_cache.vk_cache, 1, &pipeline_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_PIPELINE), &vk_pipeline);
55535700
ERR_FAIL_COND_V_MSG(err, PipelineID(), "vkCreateGraphicsPipelines failed with error " + itos(err) + ".");
55545701

5702+
#if RECORD_PIPELINE_STATISTICS
5703+
{
5704+
MutexLock lock(pipeline_statistics.file_access_mutex);
5705+
uint64_t pipeline_creation_time = OS::get_singleton()->get_ticks_usec() - pipeline_start_time;
5706+
for (uint32_t i = 0; i < shader_info->vk_stages_create_info.size(); i++) {
5707+
PackedStringArray csv_array = {
5708+
shader_info->name,
5709+
String::num_uint64(hash_murmur3_buffer(shader_info->spirv_stage_bytes[i].ptr(), shader_info->spirv_stage_bytes[i].size())),
5710+
String::num_uint64(i),
5711+
String::num_uint64(respv_size[i] > 0),
5712+
String::num_uint64(shader_info->original_stage_size[i]),
5713+
String::num_uint64(respv_size[i] > 0 ? respv_size[i] : shader_info->spirv_stage_bytes[i].size()),
5714+
String::num_uint64(respv_run_time[i] + pipeline_creation_time)
5715+
};
5716+
5717+
pipeline_statistics.file_access->store_csv_line(csv_array);
5718+
}
5719+
5720+
pipeline_statistics.file_access->flush();
5721+
}
5722+
#endif
5723+
5724+
// Destroy any modules created temporarily by re-spirv.
5725+
for (VkShaderModule vk_module : respv_shader_modules) {
5726+
vkDestroyShaderModule(vk_device, vk_module, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_SHADER_MODULE));
5727+
}
5728+
55555729
return PipelineID(vk_pipeline);
55565730
}
55575731

drivers/vulkan/rendering_device_driver_vulkan.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#define _DEBUG
4242
#endif
4343
#endif
44+
#include "thirdparty/re-spirv/re-spirv.h"
4445
#include "thirdparty/vulkan/vk_mem_alloc.h"
4546

4647
#include "drivers/vulkan/godot_vulkan.h"
@@ -156,6 +157,13 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
156157

157158
PendingFlushes pending_flushes;
158159

160+
struct PipelineStatistics {
161+
Ref<FileAccess> file_access;
162+
Mutex file_access_mutex;
163+
};
164+
165+
PipelineStatistics pipeline_statistics;
166+
159167
void _register_requested_device_extension(const CharString &p_extension_name, bool p_required);
160168
Error _initialize_device_extensions();
161169
Error _check_device_features();
@@ -437,9 +445,13 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
437445
/****************/
438446
private:
439447
struct ShaderInfo {
448+
String name;
440449
VkShaderStageFlags vk_push_constant_stages = 0;
441450
TightLocalVector<VkPipelineShaderStageCreateInfo> vk_stages_create_info;
442451
TightLocalVector<VkDescriptorSetLayout> vk_descriptor_set_layouts;
452+
TightLocalVector<respv::Shader> respv_stage_shaders;
453+
TightLocalVector<Vector<uint8_t>> spirv_stage_bytes;
454+
TightLocalVector<uint64_t> original_stage_size;
443455
VkPipelineLayout vk_pipeline_layout = VK_NULL_HANDLE;
444456
};
445457

drivers/vulkan/rendering_shader_container_vulkan.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ void RenderingShaderContainerFormatVulkan::set_debug_info_enabled(bool p_debug_i
108108
debug_info_enabled = p_debug_info_enabled;
109109
}
110110

111+
bool RenderingShaderContainerFormatVulkan::get_debug_info_enabled() const {
112+
return debug_info_enabled;
113+
}
114+
111115
RenderingShaderContainerFormatVulkan::RenderingShaderContainerFormatVulkan() {}
112116

113117
RenderingShaderContainerFormatVulkan::~RenderingShaderContainerFormatVulkan() {}

drivers/vulkan/rendering_shader_container_vulkan.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class RenderingShaderContainerFormatVulkan : public RenderingShaderContainerForm
6262
virtual ShaderLanguageVersion get_shader_language_version() const override;
6363
virtual ShaderSpirvVersion get_shader_spirv_version() const override;
6464
void set_debug_info_enabled(bool p_debug_info_enabled);
65+
bool get_debug_info_enabled() const;
6566
RenderingShaderContainerFormatVulkan();
6667
virtual ~RenderingShaderContainerFormatVulkan();
6768
};

modules/glslang/SCsub

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ thirdparty_obj = []
1212

1313
if env["builtin_glslang"]:
1414
thirdparty_dir = "#thirdparty/glslang/"
15+
thirdparty_spirv_headers_dir = "#thirdparty/spirv-headers/"
1516
thirdparty_sources = [
1617
"glslang/GenericCodeGen/CodeGen.cpp",
1718
"glslang/GenericCodeGen/Link.cpp",
@@ -63,6 +64,7 @@ if env["builtin_glslang"]:
6364
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
6465

6566
env_glslang.Prepend(CPPPATH=[thirdparty_dir, "#thirdparty"])
67+
env_glslang.Prepend(CPPPATH=[thirdparty_spirv_headers_dir + "include/spirv/unified1"])
6668

6769
env_glslang.Append(CPPDEFINES=[("ENABLE_OPT", 0)])
6870

0 commit comments

Comments
 (0)