diff --git a/documents/patching-shader.md b/documents/patching-shader.md new file mode 100644 index 00000000..613e89bf --- /dev/null +++ b/documents/patching-shader.md @@ -0,0 +1,19 @@ + + +### Install Vulkan SDK and \*ensure `spirv-cross` and `glslc` are in PATH\*. + +1. Enable `dumpShaders` in config.toml + +2. Run `spirv-cross -V fs_0x000000.spv --output fs_0x000000.glsl` to decompile the SPIR-V IR to GLSL. + +3. Edit the GLSL file as you wish + +4. To compile back to SPIR-V, run (change the _**-fshader-stage**_ to correct stage): + `glslc --target-env=vulkan1.3 --target-spv=spv1.6 -fshader-stage=frag fs_0x000000.glsl -o fs_0x000000.spv` + +5. Put the updated .spv file to `shader/patch` folder with the same name as the original shader + +6. Enable `patchShaders` in config.toml \ No newline at end of file diff --git a/src/common/config.cpp b/src/common/config.cpp index c0158399..5c2c8cda 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -52,6 +52,7 @@ static bool isAutoUpdate = false; static bool isNullGpu = false; static bool shouldCopyGPUBuffers = false; static bool shouldDumpShaders = false; +static bool shouldPatchShaders = true; static u32 vblankDivider = 1; static bool vkValidation = false; static bool vkValidationSync = false; @@ -178,6 +179,10 @@ bool dumpShaders() { return shouldDumpShaders; } +bool patchShaders() { + return shouldPatchShaders; +} + bool isRdocEnabled() { return rdocEnable; } @@ -546,6 +551,7 @@ void load(const std::filesystem::path& path) { isNullGpu = toml::find_or(gpu, "nullGpu", false); shouldCopyGPUBuffers = toml::find_or(gpu, "copyGPUBuffers", false); shouldDumpShaders = toml::find_or(gpu, "dumpShaders", false); + shouldPatchShaders = toml::find_or(gpu, "patchShaders", true); vblankDivider = toml::find_or(gpu, "vblankDivider", 1); } @@ -646,6 +652,7 @@ void save(const std::filesystem::path& path) { data["GPU"]["nullGpu"] = isNullGpu; data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers; data["GPU"]["dumpShaders"] = shouldDumpShaders; + data["GPU"]["patchShaders"] = shouldPatchShaders; data["GPU"]["vblankDivider"] = vblankDivider; data["Vulkan"]["gpuId"] = gpuId; data["Vulkan"]["validation"] = vkValidation; diff --git a/src/common/config.h b/src/common/config.h index 9c71c96a..40011574 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -42,6 +42,7 @@ bool autoUpdate(); bool nullGpu(); bool copyGPUCmdBuffers(); bool dumpShaders(); +bool patchShaders(); bool isRdocEnabled(); u32 vblankDiv(); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index b576d478..d2220dec 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -406,8 +406,13 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, DumpShader(code, info.pgm_hash, info.stage, perm_idx, "bin"); const auto ir_program = Shader::TranslateProgram(code, pools, info, runtime_info, profile); - const auto spv = Shader::Backend::SPIRV::EmitSPIRV(profile, runtime_info, ir_program, binding); + auto spv = Shader::Backend::SPIRV::EmitSPIRV(profile, runtime_info, ir_program, binding); DumpShader(spv, info.pgm_hash, info.stage, perm_idx, "spv"); + auto patch = GetShaderPatch(info.pgm_hash, info.stage, perm_idx, "spv"); + if (patch) { + spv = *patch; + LOG_INFO(Loader, "Loaded patch for {} shader {:#x}", info.stage, info.pgm_hash); + } const auto module = CompileSPV(spv, instance.GetDevice()); const auto name = fmt::format("{}_{:#x}_{}", info.stage, info.pgm_hash, perm_idx); @@ -465,4 +470,27 @@ void PipelineCache::DumpShader(std::span code, u64 hash, Shader::Stag file.WriteSpan(code); } +std::optional> PipelineCache::GetShaderPatch(u64 hash, Shader::Stage stage, + size_t perm_idx, + std::string_view ext) { + if (!Config::patchShaders()) { + return {}; + } + + using namespace Common::FS; + const auto patch_dir = GetUserPath(PathType::ShaderDir) / "patch"; + if (!std::filesystem::exists(patch_dir)) { + std::filesystem::create_directories(patch_dir); + } + const auto filename = fmt::format("{}_{:#018x}_{}.{}", stage, hash, perm_idx, ext); + const auto filepath = patch_dir / filename; + if (!std::filesystem::exists(filepath)) { + return {}; + } + const auto file = IOFile{patch_dir / filename, FileAccessMode::Read}; + std::vector code(file.GetSize() / sizeof(u32)); + file.Read(code); + return code; +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 43facbae..662bcbd8 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -56,6 +56,8 @@ private: void DumpShader(std::span code, u64 hash, Shader::Stage stage, size_t perm_idx, std::string_view ext); + std::optional> GetShaderPatch(u64 hash, Shader::Stage stage, size_t perm_idx, + std::string_view ext); vk::ShaderModule CompileModule(Shader::Info& info, const Shader::RuntimeInfo& runtime_info, std::span code, size_t perm_idx, Shader::Backend::Bindings& binding);