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);