diff --git a/pcsx2-qt/Settings/GraphicsOnScreenDisplaySettingsTab.ui b/pcsx2-qt/Settings/GraphicsOnScreenDisplaySettingsTab.ui index 6071f522aa..cb9cdf3a35 100644 --- a/pcsx2-qt/Settings/GraphicsOnScreenDisplaySettingsTab.ui +++ b/pcsx2-qt/Settings/GraphicsOnScreenDisplaySettingsTab.ui @@ -149,6 +149,13 @@ + + + + Show Texture Replacement Status + + + @@ -391,6 +398,7 @@ showInputs showVideoCapture showInputRec + showTextureReplacements warnAboutUnsafeSettings diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index 593b2a610b..3a0a04f1b9 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -241,6 +241,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog, SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showInputs, "EmuCore/GS", "OsdShowInputs", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showVideoCapture, "EmuCore/GS", "OsdShowVideoCapture", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showInputRec, "EmuCore/GS", "OsdShowInputRec", true); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showTextureReplacements, "EmuCore/GS", "OsdShowTextureReplacements", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.warnAboutUnsafeSettings, "EmuCore", "OsdWarnAboutUnsafeSettings", true); ////////////////////////////////////////////////////////////////////////// @@ -769,6 +770,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog, dialog()->registerWidgetHelp(m_osd.showInputRec, tr("Show Input Recording Status"), tr("Checked"), tr("Shows the status of the currently active input recording in the top-right corner of the display..")); + dialog()->registerWidgetHelp(m_osd.showTextureReplacements, tr("Show Texture Replacement Status"), tr("Checked"), + tr("Shows the status of the number of dumped and loaded texture replacements in the top-right corner of the display.")); + dialog()->registerWidgetHelp(m_osd.warnAboutUnsafeSettings, tr("Warn About Unsafe Settings"), tr("Checked"), tr("Displays warnings when settings are enabled which may break games.")); diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 26d36d00b6..cdd49113f8 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -749,6 +749,7 @@ struct Pcsx2Config OsdShowInputs : 1, OsdShowVideoCapture : 1, OsdShowInputRec : 1, + OsdShowTextureReplacements : 1, HWSpinGPUForReadbacks : 1, HWSpinCPUForReadbacks : 1, GPUPaletteConversion : 1, diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 545e2f2fab..3e8aec9fca 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -1134,6 +1134,7 @@ static void HotkeyToggleOSD() GSConfig.OsdShowInputs ^= EmuConfig.GS.OsdShowInputs; GSConfig.OsdShowInputRec ^= EmuConfig.GS.OsdShowInputRec; GSConfig.OsdShowVideoCapture ^= EmuConfig.GS.OsdShowVideoCapture; + GSConfig.OsdShowTextureReplacements ^= EmuConfig.GS.OsdShowTextureReplacements; GSConfig.OsdMessagesPos = GSConfig.OsdMessagesPos == OsdOverlayPos::None ? EmuConfig.GS.OsdMessagesPos : OsdOverlayPos::None; diff --git a/pcsx2/GS/Renderers/HW/GSTextureReplacements.cpp b/pcsx2/GS/Renderers/HW/GSTextureReplacements.cpp index d2fe9bf7e0..eb373225eb 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureReplacements.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureReplacements.cpp @@ -124,6 +124,7 @@ namespace GSTextureReplacements /// Textures that have been dumped, to save stat() calls. static std::unordered_set s_dumped_textures; + static std::mutex s_dumped_textures_mutex; /// Lookup map of texture names to replacements, if they exist. static std::unordered_map s_replacement_texture_filenames; @@ -802,10 +803,13 @@ void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash { // check if it's been dumped or replaced already const TextureName name(CreateTextureName(hash, level)); - if (s_dumped_textures.find(name) != s_dumped_textures.end() || s_replacement_texture_filenames.find(name) != s_replacement_texture_filenames.end()) - return; + { + std::unique_lock lock(s_dumped_textures_mutex); + if (s_dumped_textures.find(name) != s_dumped_textures.end() || s_replacement_texture_filenames.find(name) != s_replacement_texture_filenames.end()) + return; - s_dumped_textures.insert(name); + s_dumped_textures.insert(name); + } // already exists on disk? std::string filename(GetDumpFilename(name, level)); @@ -842,9 +846,22 @@ void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash void GSTextureReplacements::ClearDumpedTextureList() { + std::unique_lock lock(s_dumped_textures_mutex); s_dumped_textures.clear(); } +u32 GSTextureReplacements::GetDumpedTextureCount() +{ + std::unique_lock lock(s_dumped_textures_mutex); + return static_cast(s_dumped_textures.size()); +} + +u32 GSTextureReplacements::GetLoadedTextureCount() +{ + std::unique_lock lock(s_replacement_texture_cache_mutex); + return static_cast(s_replacement_texture_cache.size()); +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Worker Thread //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/pcsx2/GS/Renderers/HW/GSTextureReplacements.h b/pcsx2/GS/Renderers/HW/GSTextureReplacements.h index f07c1d1a4c..a0a7b73b60 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureReplacements.h +++ b/pcsx2/GS/Renderers/HW/GSTextureReplacements.h @@ -47,6 +47,12 @@ namespace GSTextureReplacements GSTextureCache::SourceRegion region, GSLocalMemory& mem, u32 level); void ClearDumpedTextureList(); + /// Get the number of textures that have been dumped. + u32 GetDumpedTextureCount(); + + /// Get the number of replacement textures that have been loaded/cached. + u32 GetLoadedTextureCount(); + /// Loader will take a filename and interpret the format (e.g. DDS, PNG, etc). using ReplacementTextureLoader = bool (*)(const std::string& filename, GSTextureReplacements::ReplacementTexture* tex, bool only_base_image); ReplacementTextureLoader GetLoader(const std::string_view filename); diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index dd0626e941..4bf593ab66 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -4254,7 +4254,6 @@ void FullscreenUI::DrawInterfaceSettingsPage() DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_SLIDERS, "Show Settings"), FSUI_CSTR("Shows the current configuration in the bottom-right corner of the display."), "EmuCore/GS", "OsdShowSettings", false); - bool show_settings = (bsi->GetBoolValue("EmuCore/GS", "OsdShowSettings", false) == false); DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_HAMMER, "Show Patches"), FSUI_CSTR("Shows the amount of currently active patches/cheats on the bottom-right corner of the display."), "EmuCore/GS", @@ -4268,6 +4267,9 @@ void FullscreenUI::DrawInterfaceSettingsPage() DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_KEYBOARD, "Show Input Recording Status"), FSUI_CSTR("Shows the status of the currently active input recording."), "EmuCore/GS", "OsdShowInputRec", true); + DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_IMAGES, "Show Texture Replacement Status"), + FSUI_CSTR("Shows the number of dumped and loaded texture replacements on the OSD."), "EmuCore/GS", + "OsdShowTextureReplacements", true); DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TRIANGLE_EXCLAMATION, "Warn About Unsafe Settings"), FSUI_CSTR("Displays warnings when settings are enabled which may break games."), "EmuCore", "WarnAboutUnsafeSettings", true); diff --git a/pcsx2/ImGui/ImGuiOverlays.cpp b/pcsx2/ImGui/ImGuiOverlays.cpp index 47cbfa8d53..998f2d48b8 100644 --- a/pcsx2/ImGui/ImGuiOverlays.cpp +++ b/pcsx2/ImGui/ImGuiOverlays.cpp @@ -9,6 +9,7 @@ #include "GS/GSCapture.h" #include "GS/GSVector.h" #include "GS/Renderers/Common/GSDevice.h" +#include "GS/Renderers/HW/GSTextureReplacements.h" #include "Host.h" #include "IconsFontAwesome6.h" #include "IconsPromptFont.h" @@ -148,6 +149,7 @@ namespace ImGuiManager static void DrawInputsOverlay(float scale, float margin, float spacing); static void DrawInputRecordingOverlay(float& position_y, float scale, float margin, float spacing); static void DrawVideoCaptureOverlay(float& position_y, float scale, float margin, float spacing); + static void DrawTextureReplacementsOverlay(float& position_y, float scale, float margin, float spacing); } // namespace ImGuiManager static std::tuple GetMinMax(std::span values) @@ -946,6 +948,46 @@ __ri void ImGuiManager::DrawVideoCaptureOverlay(float& position_y, float scale, position_y += std::max(icon_size.y, text_size.y) + spacing; } +__ri void ImGuiManager::DrawTextureReplacementsOverlay(float& position_y, float scale, float margin, float spacing) +{ + if (!GSConfig.OsdShowTextureReplacements || + FullscreenUI::HasActiveWindow()) + return; + + const bool dumping_active = GSConfig.DumpReplaceableTextures; + const bool replacement_active = GSConfig.LoadTextureReplacements; + + if (!dumping_active && !replacement_active) + return; + + const float shadow_offset = std::ceil(scale); + ImFont* const standard_font = ImGuiManager::GetStandardFont(); + const float font_size = ImGuiManager::GetFontSizeStandard(); + ImDrawList* dl = ImGui::GetBackgroundDrawList(); + + SmallString texture_line; + if (replacement_active) + { + const u32 loaded_count = GSTextureReplacements::GetLoadedTextureCount(); + texture_line.format("{} Replaced: {}", ICON_FA_IMAGES, loaded_count); + } + if (dumping_active) + { + if (!texture_line.empty()) + texture_line.append(" | "); + const u32 dumped_count = GSTextureReplacements::GetDumpedTextureCount(); + texture_line.append_format("{} Dumped: {}", ICON_FA_DOWNLOAD, dumped_count); + } + + ImVec2 text_size = standard_font->CalcTextSizeA(font_size, std::numeric_limits::max(), -1.0f, texture_line.c_str(), nullptr, nullptr); + const ImVec2 text_pos(GetWindowWidth() - margin - text_size.x, position_y); + + dl->AddText(standard_font, font_size, ImVec2(text_pos.x + shadow_offset, text_pos.y + shadow_offset), IM_COL32(0, 0, 0, 100), texture_line.c_str()); + dl->AddText(standard_font, font_size, text_pos, white_color, texture_line.c_str()); + + position_y += text_size.y + spacing; +} + namespace SaveStateSelectorUI { namespace @@ -1381,6 +1423,7 @@ void ImGuiManager::RenderOverlays() DrawVideoCaptureOverlay(position_y, scale, margin, spacing); DrawInputRecordingOverlay(position_y, scale, margin, spacing); + DrawTextureReplacementsOverlay(position_y, scale, margin, spacing); if (GSConfig.OsdPerformancePos != OsdOverlayPos::None) DrawPerformanceOverlay(position_y, scale, margin, spacing); DrawSettingsOverlay(scale, margin, spacing); diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 667bcfbbe6..378178cd93 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -741,6 +741,7 @@ Pcsx2Config::GSOptions::GSOptions() OsdShowInputs = false; OsdShowVideoCapture = true; OsdShowInputRec = true; + OsdShowTextureReplacements = true; HWDownloadMode = GSHardwareDownloadMode::Enabled; HWSpinGPUForReadbacks = false; @@ -960,6 +961,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBool(OsdShowHardwareInfo); SettingsWrapBitBool(OsdShowVideoCapture); SettingsWrapBitBool(OsdShowInputRec); + SettingsWrapBitBool(OsdShowTextureReplacements); SettingsWrapBitBool(HWSpinGPUForReadbacks); SettingsWrapBitBool(HWSpinCPUForReadbacks);