diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 6d5a254c..4658d0ef 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -37,6 +37,10 @@ std::vector SplitString(const std::string& str, char delimiter) { return output; } +std::string_view U8stringToString(std::u8string_view u8str) { + return std::string_view{reinterpret_cast(u8str.data()), u8str.size()}; +} + #ifdef _WIN32 static std::wstring CPToUTF16(u32 code_page, std::string_view input) { const auto size = diff --git a/src/common/string_util.h b/src/common/string_util.h index 23e82b93..18972de4 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -16,6 +16,8 @@ void ToLowerInPlace(std::string& str); std::vector SplitString(const std::string& str, char delimiter); +std::string_view U8stringToString(std::u8string_view u8str); + #ifdef _WIN32 [[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input); [[nodiscard]] std::wstring UTF8ToUTF16W(std::string_view str); diff --git a/src/core/debug_state.cpp b/src/core/debug_state.cpp index 562cb62e..64962492 100644 --- a/src/core/debug_state.cpp +++ b/src/core/debug_state.cpp @@ -177,9 +177,10 @@ void DebugStateImpl::PushRegsDump(uintptr_t base_addr, uintptr_t header_addr, } } -void DebugStateImpl::CollectShader(const std::string& name, std::span spv, - std::span raw_code) { - shader_dump_list.emplace_back(name, std::vector{spv.begin(), spv.end()}, - std::vector{raw_code.begin(), raw_code.end()}); - std::ranges::sort(shader_dump_list, {}, &ShaderDump::name); +void DebugStateImpl::CollectShader(const std::string& name, vk::ShaderModule module, + std::span spv, std::span raw_code, + std::span patch_spv, bool is_patched) { + shader_dump_list.emplace_back(name, module, std::vector{spv.begin(), spv.end()}, + std::vector{raw_code.begin(), raw_code.end()}, + std::vector{patch_spv.begin(), patch_spv.end()}, is_patched); } diff --git a/src/core/debug_state.h b/src/core/debug_state.h index 759755b5..fa2e5cd9 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -12,7 +12,7 @@ #include "common/types.h" #include "video_core/amdgpu/liverpool.h" -#include "video_core/renderer_vulkan/vk_pipeline_cache.h" +#include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -76,29 +76,46 @@ struct FrameDump { struct ShaderDump { std::string name; + vk::ShaderModule module; + std::vector spv; - std::vector raw_code; + std::vector isa; + std::vector patch_spv; + std::string patch_source{}; + + bool loaded_data = false; + bool is_patched = false; std::string cache_spv_disasm{}; - std::string cache_raw_disasm{}; + std::string cache_isa_disasm{}; + std::string cache_patch_disasm{}; - ShaderDump(std::string name, std::vector spv, std::vector raw_code) - : name(std::move(name)), spv(std::move(spv)), raw_code(std::move(raw_code)) {} + ShaderDump(std::string name, vk::ShaderModule module, std::vector spv, + std::vector isa, std::vector patch_spv, bool is_patched) + : name(std::move(name)), module(module), spv(std::move(spv)), isa(std::move(isa)), + patch_spv(std::move(patch_spv)), is_patched(is_patched) {} ShaderDump(const ShaderDump& other) = delete; ShaderDump(ShaderDump&& other) noexcept - : name{std::move(other.name)}, spv{std::move(other.spv)}, - raw_code{std::move(other.raw_code)}, cache_spv_disasm{std::move(other.cache_spv_disasm)}, - cache_raw_disasm{std::move(other.cache_raw_disasm)} {} + : name{std::move(other.name)}, module{std::move(other.module)}, spv{std::move(other.spv)}, + isa{std::move(other.isa)}, patch_spv{std::move(other.patch_spv)}, + patch_source{std::move(other.patch_source)}, + cache_spv_disasm{std::move(other.cache_spv_disasm)}, + cache_isa_disasm{std::move(other.cache_isa_disasm)}, + cache_patch_disasm{std::move(other.cache_patch_disasm)} {} ShaderDump& operator=(const ShaderDump& other) = delete; ShaderDump& operator=(ShaderDump&& other) noexcept { if (this == &other) return *this; name = std::move(other.name); + module = std::move(other.module); spv = std::move(other.spv); - raw_code = std::move(other.raw_code); + isa = std::move(other.isa); + patch_spv = std::move(other.patch_spv); + patch_source = std::move(other.patch_source); cache_spv_disasm = std::move(other.cache_spv_disasm); - cache_raw_disasm = std::move(other.cache_raw_disasm); + cache_isa_disasm = std::move(other.cache_isa_disasm); + cache_patch_disasm = std::move(other.cache_patch_disasm); return *this; } }; @@ -186,8 +203,9 @@ public: void PushRegsDump(uintptr_t base_addr, uintptr_t header_addr, const AmdGpu::Liverpool::Regs& regs, bool is_compute = false); - void CollectShader(const std::string& name, std::span spv, - std::span raw_code); + void CollectShader(const std::string& name, vk::ShaderModule module, std::span spv, + std::span raw_code, std::span patch_spv, + bool is_patched); }; } // namespace DebugStateType diff --git a/src/core/devtools/options.h b/src/core/devtools/options.h index 70e1d137..a859a2ee 100644 --- a/src/core/devtools/options.h +++ b/src/core/devtools/options.h @@ -10,8 +10,8 @@ struct ImGuiTextBuffer; namespace Core::Devtools { struct TOptions { - std::string disassembler_cli_isa{"clrxdisasm --raw \"{src}\""}; - std::string disassembler_cli_spv{"spirv-cross -V \"{src}\""}; + std::string disassembler_cli_isa{"clrxdisasm --raw {src}"}; + std::string disassembler_cli_spv{"spirv-cross -V {src}"}; bool frame_dump_render_on_collapse{false}; }; diff --git a/src/core/devtools/widget/common.h b/src/core/devtools/widget/common.h index 5f669eb6..75eb5530 100644 --- a/src/core/devtools/widget/common.h +++ b/src/core/devtools/widget/common.h @@ -117,7 +117,7 @@ static bool IsDrawCall(AmdGpu::PM4ItOpcode opcode) { inline std::optional exec_cli(const char* cli) { std::array buffer{}; std::string output; - const auto f = popen(cli, "r"); + const auto f = popen(cli, "rt"); if (!f) { pclose(f); return {}; @@ -129,21 +129,27 @@ inline std::optional exec_cli(const char* cli) { return output; } -inline std::string RunDisassembler(const std::string& disassembler_cli, - const std::vector& shader_code) { +template +inline std::string RunDisassembler(const std::string& disassembler_cli, const T& shader_code, + bool* success = nullptr) { std::string shader_dis; if (disassembler_cli.empty()) { shader_dis = "No disassembler set"; + if (success) { + *success = false; + } } else { auto bin_path = std::filesystem::temp_directory_path() / "shadps4_tmp_shader.bin"; constexpr std::string_view src_arg = "{src}"; - std::string cli = disassembler_cli; + std::string cli = disassembler_cli + " 2>&1"; const auto pos = cli.find(src_arg); if (pos == std::string::npos) { - DebugState.ShowDebugMessage("Disassembler CLI does not contain {src} argument\n" + - disassembler_cli); + shader_dis = "Disassembler CLI does not contain {src} argument"; + if (success) { + *success = false; + } } else { cli.replace(pos, src_arg.size(), "\"" + bin_path.string() + "\""); Common::FS::IOFile file(bin_path, Common::FS::FileAccessMode::Write); @@ -151,9 +157,16 @@ inline std::string RunDisassembler(const std::string& disassembler_cli, file.Close(); auto result = exec_cli(cli.c_str()); - shader_dis = result.value_or("Could not disassemble shader"); - if (shader_dis.empty()) { - shader_dis = "Disassembly empty or failed"; + if (result) { + shader_dis = result.value(); + if (success) { + *success = true; + } + } else { + if (success) { + *success = false; + } + shader_dis = "Could not disassemble shader"; } std::filesystem::remove(bin_path); diff --git a/src/core/devtools/widget/shader_list.cpp b/src/core/devtools/widget/shader_list.cpp index b056880d..80c93971 100644 --- a/src/core/devtools/widget/shader_list.cpp +++ b/src/core/devtools/widget/shader_list.cpp @@ -1,66 +1,221 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include "shader_list.h" #include #include "common.h" #include "common/config.h" +#include "common/path_util.h" +#include "common/string_util.h" #include "core/debug_state.h" #include "core/devtools/options.h" #include "imgui/imgui_std.h" +#include "sdl_window.h" +#include "video_core/renderer_vulkan/vk_presenter.h" +#include "video_core/renderer_vulkan/vk_rasterizer.h" + +extern std::unique_ptr presenter; using namespace ImGui; namespace Core::Devtools::Widget { -void ShaderList::DrawShader(DebugStateType::ShaderDump& value) { - if (!loaded_data) { - loaded_data = true; - if (value.cache_raw_disasm.empty()) { - value.cache_raw_disasm = RunDisassembler(Options.disassembler_cli_isa, value.raw_code); - } - isa_editor.SetText(value.cache_raw_disasm); +ShaderList::Selection::Selection(int index) : index(index) { + isa_editor.SetPalette(TextEditor::GetDarkPalette()); + isa_editor.SetReadOnly(true); + glsl_editor.SetPalette(TextEditor::GetDarkPalette()); + glsl_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::GLSL()); + presenter->GetWindow().RequestKeyboard(); +} +ShaderList::Selection::~Selection() { + presenter->GetWindow().ReleaseKeyboard(); +} + +void ShaderList::Selection::ReloadShader(DebugStateType::ShaderDump& value) { + auto& spv = value.is_patched ? value.patch_spv : value.spv; + if (spv.empty()) { + return; + } + auto& cache = presenter->GetRasterizer().GetPipelineCache(); + if (const auto m = cache.ReplaceShader(value.module, spv); m) { + value.module = *m; + } +} + +bool ShaderList::Selection::DrawShader(DebugStateType::ShaderDump& value) { + if (!value.loaded_data) { + value.loaded_data = true; + if (value.cache_isa_disasm.empty()) { + value.cache_isa_disasm = RunDisassembler(Options.disassembler_cli_isa, value.isa); + } if (value.cache_spv_disasm.empty()) { value.cache_spv_disasm = RunDisassembler(Options.disassembler_cli_spv, value.spv); } - spv_editor.SetText(value.cache_spv_disasm); + if (!value.patch_spv.empty() && value.cache_patch_disasm.empty()) { + value.cache_patch_disasm = RunDisassembler("spirv-dis {src}", value.patch_spv); + } + patch_path = + Common::FS::GetUserPath(Common::FS::PathType::ShaderDir) / "patch" / value.name; + patch_bin_path = patch_path; + patch_bin_path += ".spv"; + patch_path += ".glsl"; + if (std::filesystem::exists(patch_path)) { + std::ifstream file{patch_path}; + value.patch_source = + std::string{std::istreambuf_iterator{file}, std::istreambuf_iterator{}}; + } + + value.is_patched = !value.patch_spv.empty(); + if (!value.is_patched) { // No patch + isa_editor.SetText(value.cache_isa_disasm); + glsl_editor.SetText(value.cache_spv_disasm); + } else { + isa_editor.SetText(value.cache_patch_disasm); + isa_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::SPIRV()); + glsl_editor.SetText(value.patch_source); + glsl_editor.SetReadOnly(false); + } } - if (SmallButton("<-")) { - selected_shader = -1; + char name[64]; + snprintf(name, sizeof(name), "Shader %s", value.name.c_str()); + SetNextWindowSize({450.0f, 600.0f}, ImGuiCond_FirstUseEver); + if (!Begin(name, &open, ImGuiWindowFlags_NoNav)) { + End(); + return open; } - SameLine(); + Text("%s", value.name.c_str()); SameLine(0.0f, 7.0f); - if (BeginCombo("Shader type", showing_isa ? "ISA" : "SPIRV", ImGuiComboFlags_WidthFitPreview)) { - if (Selectable("SPIRV")) { - showing_isa = false; + if (Checkbox("Enable patch", &value.is_patched)) { + if (value.is_patched) { + if (value.patch_source.empty()) { + value.patch_source = value.cache_spv_disasm; + } + isa_editor.SetText(value.cache_patch_disasm); + isa_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::SPIRV()); + glsl_editor.SetText(value.patch_source); + glsl_editor.SetReadOnly(false); + if (!value.patch_spv.empty()) { + ReloadShader(value); + } + } else { + isa_editor.SetText(value.cache_isa_disasm); + isa_editor.SetLanguageDefinition(TextEditor::LanguageDefinition()); + glsl_editor.SetText(value.cache_spv_disasm); + glsl_editor.SetReadOnly(true); + ReloadShader(value); } - if (Selectable("ISA")) { - showing_isa = true; - } - EndCombo(); } - if (showing_isa) { - isa_editor.Render("ISA", GetContentRegionAvail()); + if (value.is_patched) { + if (BeginCombo("Shader type", showing_bin ? "SPIRV" : "GLSL", + ImGuiComboFlags_WidthFitPreview)) { + if (Selectable("GLSL")) { + showing_bin = false; + } + if (Selectable("SPIRV")) { + showing_bin = true; + } + EndCombo(); + } } else { - spv_editor.Render("SPIRV", GetContentRegionAvail()); + if (BeginCombo("Shader type", showing_bin ? "ISA" : "GLSL", + ImGuiComboFlags_WidthFitPreview)) { + if (Selectable("GLSL")) { + showing_bin = false; + } + if (Selectable("ISA")) { + showing_bin = true; + } + EndCombo(); + } } -} -ShaderList::ShaderList() { - isa_editor.SetPalette(TextEditor::GetDarkPalette()); - isa_editor.SetReadOnly(true); - spv_editor.SetPalette(TextEditor::GetDarkPalette()); - spv_editor.SetReadOnly(true); - spv_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::GLSL()); + if (value.is_patched) { + bool save = false; + bool compile = false; + SameLine(0.0f, 3.0f); + if (Button("Save")) { + save = true; + } + SameLine(); + if (Button("Save & Compile")) { + save = true; + compile = true; + } + if (save) { + value.patch_source = glsl_editor.GetText(); + std::ofstream file{patch_path, std::ios::binary | std::ios::trunc}; + file << value.patch_source; + std::string msg = "Patch saved to "; + msg += Common::U8stringToString(patch_path.u8string()); + DebugState.ShowDebugMessage(msg); + } + if (compile) { + static std::map stage_arg = { + {"vs", "vert"}, + {"gs", "geom"}, + {"fs", "frag"}, + {"cs", "comp"}, + }; + auto stage = stage_arg.find(value.name.substr(0, 2)); + if (stage == stage_arg.end()) { + DebugState.ShowDebugMessage(std::string{"Invalid shader stage: "} + + value.name.substr(0, 2)); + } else { + std::string cmd = + fmt::format("glslc --target-env=vulkan1.3 --target-spv=spv1.6 " + "-fshader-stage={} {{src}} -o \"{}\"", + stage->second, Common::U8stringToString(patch_bin_path.u8string())); + bool success = false; + auto res = RunDisassembler(cmd, value.patch_source, &success); + if (!res.empty() || !success) { + DebugState.ShowDebugMessage("Compilation failed:\n" + res); + } else { + Common::FS::IOFile file{patch_bin_path, Common::FS::FileAccessMode::Read}; + value.patch_spv.resize(file.GetSize() / sizeof(u32)); + file.Read(value.patch_spv); + value.cache_patch_disasm = + RunDisassembler("spirv-dis {src}", value.patch_spv, &success); + if (!success) { + DebugState.ShowDebugMessage("Decompilation failed (Compile was ok):\n" + + res); + } else { + isa_editor.SetText(value.cache_patch_disasm); + ReloadShader(value); + } + } + } + } + } + + if (showing_bin) { + isa_editor.Render(value.is_patched ? "SPIRV" : "ISA", GetContentRegionAvail()); + } else { + glsl_editor.Render("GLSL", GetContentRegionAvail()); + } + + End(); + return open; } void ShaderList::Draw() { + for (auto it = open_shaders.begin(); it != open_shaders.end();) { + auto& selection = *it; + auto& shader = DebugState.shader_dump_list[selection.index]; + if (!selection.DrawShader(shader)) { + it = open_shaders.erase(it); + } else { + ++it; + } + } + SetNextWindowSize({500.0f, 600.0f}, ImGuiCond_FirstUseEver); if (!Begin("Shader list", &open)) { End(); @@ -73,18 +228,19 @@ void ShaderList::Draw() { return; } - if (selected_shader >= 0) { - DrawShader(DebugState.shader_dump_list[selected_shader]); - End(); - return; - } - auto width = GetContentRegionAvail().x; int i = 0; for (const auto& shader : DebugState.shader_dump_list) { - if (ButtonEx(shader.name.c_str(), {width, 20.0f}, ImGuiButtonFlags_NoHoveredOnFocus)) { - selected_shader = i; - loaded_data = false; + char name[128]; + if (shader.is_patched) { + snprintf(name, sizeof(name), "%s (PATCH ON)", shader.name.c_str()); + } else if (!shader.patch_spv.empty()) { + snprintf(name, sizeof(name), "%s (PATCH OFF)", shader.name.c_str()); + } else { + snprintf(name, sizeof(name), "%s", shader.name.c_str()); + } + if (ButtonEx(name, {width, 20.0f}, ImGuiButtonFlags_NoHoveredOnFocus)) { + open_shaders.emplace_back(i); } i++; } diff --git a/src/core/devtools/widget/shader_list.h b/src/core/devtools/widget/shader_list.h index 5a47f656..2534ded3 100644 --- a/src/core/devtools/widget/shader_list.h +++ b/src/core/devtools/widget/shader_list.h @@ -6,20 +6,32 @@ #include "core/debug_state.h" #include "text_editor.h" +#include + namespace Core::Devtools::Widget { class ShaderList { - int selected_shader = -1; - TextEditor isa_editor{}; - TextEditor spv_editor{}; - bool loaded_data = false; - bool showing_isa = false; + struct Selection { + explicit Selection(int index); + ~Selection(); - void DrawShader(DebugStateType::ShaderDump& value); + void ReloadShader(DebugStateType::ShaderDump& value); + + bool DrawShader(DebugStateType::ShaderDump& value); + + int index; + TextEditor isa_editor{}; + TextEditor glsl_editor{}; + bool open = true; + bool showing_bin = false; + + std::filesystem::path patch_path; + std::filesystem::path patch_bin_path; + }; + + std::vector open_shaders{}; public: - ShaderList(); - bool open = false; void Draw(); diff --git a/src/core/devtools/widget/text_editor.cpp b/src/core/devtools/widget/text_editor.cpp index 07f2f658..7171cac4 100644 --- a/src/core/devtools/widget/text_editor.cpp +++ b/src/core/devtools/widget/text_editor.cpp @@ -1059,7 +1059,8 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) { if (!mIgnoreImGuiChild) ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | - ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove); + ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoNav); if (mHandleKeyboardInputs) { HandleKeyboardInputs(); @@ -2331,4 +2332,50 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() { return langDef; } +// Source: https://github.com/dfranx/ImGuiColorTextEdit/blob/master/TextEditor.cpp +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SPIRV() { + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) { + /* + langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ + \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); + langDef.mTokenRegexStrings.push_back(std::make_pair("\\'\\\\?[^\\']\\'", PaletteIndex::CharLiteral)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/\\;\\,\\.]", + PaletteIndex::Punctuation)); + */ + + langDef.mTokenRegexStrings.push_back(std::make_pair( + "L?\\\"(\\\\.|[^\\\"])*\\\"", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back( + std::make_pair("[ =\\t]Op[a-zA-Z]*", PaletteIndex::Keyword)); + langDef.mTokenRegexStrings.push_back( + std::make_pair("%[_a-zA-Z0-9]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair( + "0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = ";"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = false; + + langDef.mName = "SPIR-V"; + + inited = true; + } + return langDef; +} + } // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/text_editor.h b/src/core/devtools/widget/text_editor.h index 5c3f29f1..aa81d0d2 100644 --- a/src/core/devtools/widget/text_editor.h +++ b/src/core/devtools/widget/text_editor.h @@ -161,6 +161,7 @@ public: : mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true) {} static const LanguageDefinition& GLSL(); + static const LanguageDefinition& SPIRV(); }; TextEditor(); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index d95e8d63..f6b57436 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -168,6 +168,21 @@ void WindowSDL::InitTimers() { SDL_AddTimer(100, &PollController, controller); } +void WindowSDL::RequestKeyboard() { + if (keyboard_grab == 0) { + SDL_StartTextInput(window); + } + keyboard_grab++; +} + +void WindowSDL::ReleaseKeyboard() { + ASSERT(keyboard_grab > 0); + keyboard_grab--; + if (keyboard_grab == 0) { + SDL_StopTextInput(window); + } +} + void WindowSDL::OnResize() { SDL_GetWindowSizeInPixels(window, &width, &height); ImGui::Core::OnResize(); diff --git a/src/sdl_window.h b/src/sdl_window.h index 78d0e582..78d4bbc3 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -41,6 +41,8 @@ struct WindowSystemInfo { }; class WindowSDL { + int keyboard_grab = 0; + public: explicit WindowSDL(s32 width, s32 height, Input::GameController* controller, std::string_view window_title); @@ -69,6 +71,9 @@ public: void WaitEvent(); void InitTimers(); + void RequestKeyboard(); + void ReleaseKeyboard(); + private: void OnResize(); void OnKeyPress(const SDL_Event* event); diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 09d4e419..8d495ab0 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -13,7 +13,7 @@ namespace Vulkan { ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler_, DescriptorHeap& desc_heap_, vk::PipelineCache pipeline_cache, - u64 compute_key_, const Shader::Info& info_, + ComputePipelineKey compute_key_, const Shader::Info& info_, vk::ShaderModule module) : Pipeline{instance_, scheduler_, desc_heap_, pipeline_cache, true}, compute_key{compute_key_} { auto& info = stages[int(Shader::Stage::Compute)]; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index ca429b58..1c28e461 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -17,15 +17,33 @@ class Instance; class Scheduler; class DescriptorHeap; +struct ComputePipelineKey { + size_t value; + + friend bool operator==(const ComputePipelineKey& lhs, const ComputePipelineKey& rhs) { + return lhs.value == rhs.value; + } + friend bool operator!=(const ComputePipelineKey& lhs, const ComputePipelineKey& rhs) { + return !(lhs == rhs); + } +}; + class ComputePipeline : public Pipeline { public: ComputePipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap, - vk::PipelineCache pipeline_cache, u64 compute_key, const Shader::Info& info, - vk::ShaderModule module); + vk::PipelineCache pipeline_cache, ComputePipelineKey compute_key, + const Shader::Info& info, vk::ShaderModule module); ~ComputePipeline(); private: - u64 compute_key; + ComputePipelineKey compute_key; }; } // namespace Vulkan + +template <> +struct std::hash { + std::size_t operator()(const Vulkan::ComputePipelineKey& key) const noexcept { + return std::hash{}(key.value); + } +}; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 91ffe4ea..2834fceb 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + #include #include "common/types.h" diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 53bdc79a..276e4ef2 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -189,10 +189,19 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() { } const auto [it, is_new] = graphics_pipelines.try_emplace(graphics_key); if (is_new) { - it.value() = graphics_pipeline_pool.Create(instance, scheduler, desc_heap, graphics_key, - *pipeline_cache, infos, fetch_shader, modules); + it.value() = + std::make_unique(instance, scheduler, desc_heap, graphics_key, + *pipeline_cache, infos, fetch_shader, modules); + if (Config::collectShadersForDebug()) { + for (auto stage = 0; stage < MaxShaderStages; ++stage) { + if (infos[stage]) { + auto& m = modules[stage]; + module_related_pipelines[m].emplace_back(graphics_key); + } + } + } } - return it->second; + return it->second.get(); } const ComputePipeline* PipelineCache::GetComputePipeline() { @@ -201,10 +210,14 @@ const ComputePipeline* PipelineCache::GetComputePipeline() { } const auto [it, is_new] = compute_pipelines.try_emplace(compute_key); if (is_new) { - it.value() = compute_pipeline_pool.Create(instance, scheduler, desc_heap, *pipeline_cache, - compute_key, *infos[0], modules[0]); + it.value() = std::make_unique( + instance, scheduler, desc_heap, *pipeline_cache, compute_key, *infos[0], modules[0]); + if (Config::collectShadersForDebug()) { + auto& m = modules[0]; + module_related_pipelines[m].emplace_back(compute_key); + } } - return it->second; + return it->second.get(); } bool PipelineCache::RefreshGraphicsKey() { @@ -401,7 +414,7 @@ bool PipelineCache::RefreshComputeKey() { Shader::Backend::Bindings binding{}; const auto* cs_pgm = &liverpool->regs.cs_program; const auto cs_params = Liverpool::GetParams(*cs_pgm); - std::tie(infos[0], modules[0], fetch_shader, compute_key) = + std::tie(infos[0], modules[0], fetch_shader, compute_key.value) = GetProgram(Shader::Stage::Compute, cs_params, binding); return true; } @@ -417,17 +430,23 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, const auto ir_program = Shader::TranslateProgram(code, pools, info, runtime_info, profile); auto spv = Shader::Backend::SPIRV::EmitSPIRV(profile, runtime_info, ir_program, binding); DumpShader(spv, info.pgm_hash, info.stage, perm_idx, "spv"); + + vk::ShaderModule module; + auto patch = GetShaderPatch(info.pgm_hash, info.stage, perm_idx, "spv"); - if (patch) { - spv = *patch; + const bool is_patched = patch && Config::patchShaders(); + if (is_patched) { LOG_INFO(Loader, "Loaded patch for {} shader {:#x}", info.stage, info.pgm_hash); + module = CompileSPV(*patch, instance.GetDevice()); + } else { + module = CompileSPV(spv, instance.GetDevice()); } - const auto module = CompileSPV(spv, instance.GetDevice()); - const auto name = fmt::format("{}_{:#x}_{}", info.stage, info.pgm_hash, perm_idx); + const auto name = fmt::format("{}_{:#018x}_{}", info.stage, info.pgm_hash, perm_idx); Vulkan::SetObjectName(instance.GetDevice(), module, name); if (Config::collectShadersForDebug()) { - DebugState.CollectShader(name, spv, code); + DebugState.CollectShader(name, module, spv, code, patch ? *patch : std::span{}, + is_patched); } return module; } @@ -438,17 +457,17 @@ PipelineCache::GetProgram(Shader::Stage stage, Shader::ShaderParams params, const auto runtime_info = BuildRuntimeInfo(stage); auto [it_pgm, new_program] = program_cache.try_emplace(params.hash); if (new_program) { - Program* program = program_pool.Create(stage, params); + it_pgm.value() = std::make_unique(stage, params); + auto& program = it_pgm.value(); auto start = binding; const auto module = CompileModule(program->info, runtime_info, params.code, 0, binding); const auto spec = Shader::StageSpecialization(program->info, runtime_info, profile, start); program->AddPermut(module, std::move(spec)); - it_pgm.value() = program; return std::make_tuple(&program->info, module, spec.fetch_shader_data, HashCombine(params.hash, 0)); } - Program* program = it_pgm->second; + auto& program = it_pgm.value(); auto& info = program->info; info.RefreshFlatBuf(); const auto spec = Shader::StageSpecialization(info, runtime_info, profile, binding); @@ -469,6 +488,34 @@ PipelineCache::GetProgram(Shader::Stage stage, Shader::ShaderParams params, HashCombine(params.hash, perm_idx)); } +std::optional PipelineCache::ReplaceShader(vk::ShaderModule module, + std::span spv_code) { + std::optional new_module{}; + for (const auto& [_, program] : program_cache) { + for (auto& m : program->modules) { + if (m.module == module) { + const auto& d = instance.GetDevice(); + d.destroyShaderModule(m.module); + m.module = CompileSPV(spv_code, d); + new_module = m.module; + } + } + } + if (module_related_pipelines.contains(module)) { + auto& pipeline_keys = module_related_pipelines[module]; + for (auto& key : pipeline_keys) { + if (std::holds_alternative(key)) { + auto& graphics_key = std::get(key); + graphics_pipelines.erase(graphics_key); + } else if (std::holds_alternative(key)) { + auto& compute_key = std::get(key); + compute_pipelines.erase(compute_key); + } + } + } + return new_module; +} + void PipelineCache::DumpShader(std::span code, u64 hash, Shader::Stage stage, size_t perm_idx, std::string_view ext) { if (!Config::dumpShaders()) { @@ -488,9 +535,6 @@ void PipelineCache::DumpShader(std::span code, u64 hash, Shader::Stag 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"; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index e4a8abd4..c5c2fc98 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "shader_recompiler/profile.h" #include "shader_recompiler/recompiler.h" @@ -11,6 +12,13 @@ #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_resource_pool.h" +template <> +struct std::hash { + std::size_t operator()(const vk::ShaderModule& module) const noexcept { + return std::hash{}(reinterpret_cast((VkShaderModule)module)); + } +}; + namespace Shader { struct Info; } @@ -52,6 +60,9 @@ public: GetProgram(Shader::Stage stage, Shader::ShaderParams params, Shader::Backend::Bindings& binding); + std::optional ReplaceShader(vk::ShaderModule module, + std::span spv_code); + private: bool RefreshGraphicsKey(); bool RefreshComputeKey(); @@ -74,17 +85,19 @@ private: vk::UniquePipelineLayout pipeline_layout; Shader::Profile profile{}; Shader::Pools pools; - tsl::robin_map program_cache; - Common::ObjectPool program_pool; - Common::ObjectPool graphics_pipeline_pool; - Common::ObjectPool compute_pipeline_pool; - tsl::robin_map compute_pipelines; - tsl::robin_map graphics_pipelines; + tsl::robin_map> program_cache; + tsl::robin_map> compute_pipelines; + tsl::robin_map> graphics_pipelines; std::array infos{}; std::array modules{}; std::optional fetch_shader{}; GraphicsPipelineKey graphics_key{}; - u64 compute_key{}; + ComputePipelineKey compute_key{}; + + // Only if Config::collectShadersForDebug() + tsl::robin_map>> + module_related_pipelines; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_presenter.h b/src/video_core/renderer_vulkan/vk_presenter.h index 4d9226de..4c29af0f 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.h +++ b/src/video_core/renderer_vulkan/vk_presenter.h @@ -53,6 +53,10 @@ public: return pp_settings.gamma; } + Frontend::WindowSDL& GetWindow() const { + return window; + } + Frame* PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute, VAddr cpu_address, bool is_eop) { auto desc = VideoCore::TextureCache::VideoOutDesc{attribute, cpu_address}; @@ -90,6 +94,10 @@ public: draw_scheduler.Flush(info); } + Rasterizer& GetRasterizer() const { + return *rasterizer.get(); + } + private: void CreatePostProcessPipeline(); Frame* PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop = true); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index fe8aceba..1936276a 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -54,6 +54,10 @@ public: u64 Flush(); void Finish(); + PipelineCache& GetPipelineCache() { + return pipeline_cache; + } + private: RenderState PrepareRenderState(u32 mrt_mask); void BeginRendering(const GraphicsPipeline& pipeline, RenderState& state);