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_
37603789RDD::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
0 commit comments