Merge pull request #7251 from FernandoS27/shader-dump
ShaderDecompiler: Add a debug option to dump the game's shaders.
This commit is contained in:
		| @@ -597,6 +597,7 @@ struct Values { | ||||
|     BasicSetting<std::string> program_args{std::string(), "program_args"}; | ||||
|     BasicSetting<bool> dump_exefs{false, "dump_exefs"}; | ||||
|     BasicSetting<bool> dump_nso{false, "dump_nso"}; | ||||
|     BasicSetting<bool> dump_shaders{false, "dump_shaders"}; | ||||
|     BasicSetting<bool> enable_fs_access_log{false, "enable_fs_access_log"}; | ||||
|     BasicSetting<bool> reporting_services{false, "reporting_services"}; | ||||
|     BasicSetting<bool> quest_flag{false, "quest_flag"}; | ||||
|   | ||||
| @@ -31,6 +31,8 @@ public: | ||||
|  | ||||
|     [[nodiscard]] virtual std::array<u32, 3> WorkgroupSize() const = 0; | ||||
|  | ||||
|     virtual void Dump(u64 hash) = 0; | ||||
|  | ||||
|     [[nodiscard]] const ProgramHeader& SPH() const noexcept { | ||||
|         return sph; | ||||
|     } | ||||
|   | ||||
| @@ -425,6 +425,11 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline( | ||||
|  | ||||
|         const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))}; | ||||
|         Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); | ||||
|  | ||||
|         if (Settings::values.dump_shaders) { | ||||
|             env.Dump(key.unique_hashes[index]); | ||||
|         } | ||||
|  | ||||
|         if (!uses_vertex_a || index != 1) { | ||||
|             // Normal path | ||||
|             programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info); | ||||
| @@ -511,8 +516,12 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline( | ||||
|     LOG_INFO(Render_OpenGL, "0x{:016x}", key.Hash()); | ||||
|  | ||||
|     Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()}; | ||||
|     auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; | ||||
|  | ||||
|     if (Settings::values.dump_shaders) { | ||||
|         env.Dump(key.Hash()); | ||||
|     } | ||||
|  | ||||
|     auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; | ||||
|     const u32 num_storage_buffers{Shader::NumDescriptors(program.info.storage_buffers_descriptors)}; | ||||
|     Shader::RuntimeInfo info; | ||||
|     info.glasm_use_storage_buffers = num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks(); | ||||
|   | ||||
| @@ -517,6 +517,9 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline( | ||||
|  | ||||
|         const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))}; | ||||
|         Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); | ||||
|         if (Settings::values.dump_shaders) { | ||||
|             env.Dump(key.unique_hashes[index]); | ||||
|         } | ||||
|         if (!uses_vertex_a || index != 1) { | ||||
|             // Normal path | ||||
|             programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info); | ||||
| @@ -613,6 +616,12 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline( | ||||
|     LOG_INFO(Render_Vulkan, "0x{:016x}", key.Hash()); | ||||
|  | ||||
|     Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()}; | ||||
|  | ||||
|     // Dump it before error. | ||||
|     if (Settings::values.dump_shaders) { | ||||
|         env.Dump(key.Hash()); | ||||
|     } | ||||
|  | ||||
|     auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; | ||||
|     const std::vector<u32> code{EmitSPIRV(profile, program)}; | ||||
|     device.SaveShader(code); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <bit> | ||||
| #include <filesystem> | ||||
| #include <fstream> | ||||
| #include <memory> | ||||
| @@ -14,6 +15,7 @@ | ||||
| #include "common/common_types.h" | ||||
| #include "common/div_ceil.h" | ||||
| #include "common/fs/fs.h" | ||||
| #include "common/fs/path_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "shader_recompiler/environment.h" | ||||
| #include "video_core/engines/kepler_compute.h" | ||||
| @@ -57,6 +59,47 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| static std::string_view StageToPrefix(Shader::Stage stage) { | ||||
|     switch (stage) { | ||||
|     case Shader::Stage::VertexB: | ||||
|         return "VB"; | ||||
|     case Shader::Stage::TessellationControl: | ||||
|         return "TC"; | ||||
|     case Shader::Stage::TessellationEval: | ||||
|         return "TE"; | ||||
|     case Shader::Stage::Geometry: | ||||
|         return "GS"; | ||||
|     case Shader::Stage::Fragment: | ||||
|         return "FS"; | ||||
|     case Shader::Stage::Compute: | ||||
|         return "CS"; | ||||
|     case Shader::Stage::VertexA: | ||||
|         return "VA"; | ||||
|     default: | ||||
|         return "UK"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void DumpImpl(u64 hash, const u64* code, u32 read_highest, u32 read_lowest, | ||||
|                      u32 initial_offset, Shader::Stage stage) { | ||||
|     const auto shader_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)}; | ||||
|     const auto base_dir{shader_dir / "shaders"}; | ||||
|     if (!Common::FS::CreateDir(shader_dir) || !Common::FS::CreateDir(base_dir)) { | ||||
|         LOG_ERROR(Common_Filesystem, "Failed to create shader dump directories"); | ||||
|         return; | ||||
|     } | ||||
|     const auto prefix = StageToPrefix(stage); | ||||
|     const auto name{base_dir / fmt::format("{}{:016x}.ash", prefix, hash)}; | ||||
|     const size_t real_size = read_highest - read_lowest + initial_offset; | ||||
|     const size_t padding_needed = ((32 - (real_size % 32)) % 32); | ||||
|     std::fstream shader_file(name, std::ios::out | std::ios::binary); | ||||
|     const size_t jump_index = initial_offset / sizeof(u64); | ||||
|     shader_file.write(reinterpret_cast<const char*>(code + jump_index), real_size); | ||||
|     for (size_t i = 0; i < padding_needed; i++) { | ||||
|         shader_file.put(0); | ||||
|     } | ||||
| } | ||||
|  | ||||
| GenericEnvironment::GenericEnvironment(Tegra::MemoryManager& gpu_memory_, GPUVAddr program_base_, | ||||
|                                        u32 start_address_) | ||||
|     : gpu_memory{&gpu_memory_}, program_base{program_base_} { | ||||
| @@ -128,6 +171,10 @@ u64 GenericEnvironment::CalculateHash() const { | ||||
|     return Common::CityHash64(data.get(), size); | ||||
| } | ||||
|  | ||||
| void GenericEnvironment::Dump(u64 hash) { | ||||
|     DumpImpl(hash, code.data(), read_highest, read_lowest, initial_offset, stage); | ||||
| } | ||||
|  | ||||
| void GenericEnvironment::Serialize(std::ofstream& file) const { | ||||
|     const u64 code_size{static_cast<u64>(CachedSize())}; | ||||
|     const u64 num_texture_types{static_cast<u64>(texture_types.size())}; | ||||
| @@ -207,6 +254,7 @@ GraphicsEnvironment::GraphicsEnvironment(Tegra::Engines::Maxwell3D& maxwell3d_, | ||||
|                                          u32 start_address_) | ||||
|     : GenericEnvironment{gpu_memory_, program_base_, start_address_}, maxwell3d{&maxwell3d_} { | ||||
|     gpu_memory->ReadBlock(program_base + start_address, &sph, sizeof(sph)); | ||||
|     initial_offset = sizeof(sph); | ||||
|     gp_passthrough_mask = maxwell3d->regs.gp_passthrough_mask; | ||||
|     switch (program) { | ||||
|     case Maxwell::ShaderProgram::VertexA: | ||||
| @@ -323,14 +371,20 @@ void FileEnvironment::Deserialize(std::ifstream& file) { | ||||
|     if (stage == Shader::Stage::Compute) { | ||||
|         file.read(reinterpret_cast<char*>(&workgroup_size), sizeof(workgroup_size)) | ||||
|             .read(reinterpret_cast<char*>(&shared_memory_size), sizeof(shared_memory_size)); | ||||
|         initial_offset = 0; | ||||
|     } else { | ||||
|         file.read(reinterpret_cast<char*>(&sph), sizeof(sph)); | ||||
|         initial_offset = sizeof(sph); | ||||
|         if (stage == Shader::Stage::Geometry) { | ||||
|             file.read(reinterpret_cast<char*>(&gp_passthrough_mask), sizeof(gp_passthrough_mask)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void FileEnvironment::Dump(u64 [[maybe_unused]] hash) { | ||||
|     DumpImpl(hash, code.get(), read_highest, read_lowest, initial_offset, stage); | ||||
| } | ||||
|  | ||||
| u64 FileEnvironment::ReadInstruction(u32 address) { | ||||
|     if (address < read_lowest || address > read_highest) { | ||||
|         throw Shader::LogicError("Out of bounds address {}", address); | ||||
|   | ||||
| @@ -57,6 +57,8 @@ public: | ||||
|  | ||||
|     [[nodiscard]] u64 CalculateHash() const; | ||||
|  | ||||
|     void Dump(u64 hash) override; | ||||
|  | ||||
|     void Serialize(std::ofstream& file) const; | ||||
|  | ||||
| protected: | ||||
| @@ -82,6 +84,7 @@ protected: | ||||
|  | ||||
|     u32 cached_lowest = std::numeric_limits<u32>::max(); | ||||
|     u32 cached_highest = 0; | ||||
|     u32 initial_offset = 0; | ||||
|  | ||||
|     bool has_unbound_instructions = false; | ||||
| }; | ||||
| @@ -149,6 +152,8 @@ public: | ||||
|  | ||||
|     [[nodiscard]] std::array<u32, 3> WorkgroupSize() const override; | ||||
|  | ||||
|     void Dump(u64 hash) override; | ||||
|  | ||||
| private: | ||||
|     std::unique_ptr<u64[]> code; | ||||
|     std::unordered_map<u32, Shader::TextureType> texture_types; | ||||
| @@ -159,6 +164,7 @@ private: | ||||
|     u32 texture_bound{}; | ||||
|     u32 read_lowest{}; | ||||
|     u32 read_highest{}; | ||||
|     u32 initial_offset{}; | ||||
| }; | ||||
|  | ||||
| void SerializePipeline(std::span<const char> key, std::span<const GenericEnvironment* const> envs, | ||||
|   | ||||
| @@ -51,6 +51,8 @@ void ConfigureDebug::SetConfiguration() { | ||||
|     ui->enable_cpu_debugging->setChecked(Settings::values.cpu_debug_mode.GetValue()); | ||||
|     ui->enable_nsight_aftermath->setEnabled(runtime_lock); | ||||
|     ui->enable_nsight_aftermath->setChecked(Settings::values.enable_nsight_aftermath.GetValue()); | ||||
|     ui->dump_shaders->setEnabled(runtime_lock); | ||||
|     ui->dump_shaders->setChecked(Settings::values.dump_shaders.GetValue()); | ||||
|     ui->disable_macro_jit->setEnabled(runtime_lock); | ||||
|     ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit.GetValue()); | ||||
|     ui->disable_loop_safety_checks->setEnabled(runtime_lock); | ||||
| @@ -73,6 +75,7 @@ void ConfigureDebug::ApplyConfiguration() { | ||||
|     Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); | ||||
|     Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); | ||||
|     Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); | ||||
|     Settings::values.dump_shaders = ui->dump_shaders->isChecked(); | ||||
|     Settings::values.disable_shader_loop_safety_checks = | ||||
|         ui->disable_loop_safety_checks->isChecked(); | ||||
|     Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); | ||||
|   | ||||
| @@ -105,6 +105,19 @@ | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="1"> | ||||
|        <widget class="QCheckBox" name="dump_shaders"> | ||||
|         <property name="enabled"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|         <property name="toolTip"> | ||||
|          <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Dump Game Shaders</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="0" column="1"> | ||||
|        <widget class="QCheckBox" name="disable_macro_jit"> | ||||
|         <property name="enabled"> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user