diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index d8b446bbc2..ca81c1ff48 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -637,6 +637,15 @@
+
+
+
+
+
+
+
+
+
@@ -1220,6 +1229,13 @@
+
+
+
+
+
+
+
diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt
index 0b79a77519..b46ac98d77 100644
--- a/Source/Core/VideoCommon/CMakeLists.txt
+++ b/Source/Core/VideoCommon/CMakeLists.txt
@@ -50,6 +50,21 @@ add_library(videocommon
GraphicsModSystem/Config/GraphicsTargetGroup.cpp
GraphicsModSystem/Config/GraphicsTargetGroup.h
GraphicsModSystem/Constants.h
+ GraphicsModSystem/Runtime/Actions/MoveAction.cpp
+ GraphicsModSystem/Runtime/Actions/MoveAction.h
+ GraphicsModSystem/Runtime/Actions/PrintAction.cpp
+ GraphicsModSystem/Runtime/Actions/PrintAction.h
+ GraphicsModSystem/Runtime/Actions/ScaleAction.cpp
+ GraphicsModSystem/Runtime/Actions/ScaleAction.h
+ GraphicsModSystem/Runtime/Actions/SkipAction.cpp
+ GraphicsModSystem/Runtime/Actions/SkipAction.h
+ GraphicsModSystem/Runtime/FBInfo.cpp
+ GraphicsModSystem/Runtime/FBInfo.h
+ GraphicsModSystem/Runtime/GraphicsModAction.h
+ GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp
+ GraphicsModSystem/Runtime/GraphicsModActionFactory.h
+ GraphicsModSystem/Runtime/GraphicsModManager.cpp
+ GraphicsModSystem/Runtime/GraphicsModManager.h
HiresTextures.cpp
HiresTextures.h
HiresTextures_DDSLoader.cpp
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp
new file mode 100644
index 0000000000..cc724c0480
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp
@@ -0,0 +1,47 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h"
+
+std::unique_ptr MoveAction::Create(const picojson::value& json_data)
+{
+ Common::Vec3 position_offset;
+ const auto& x = json_data.get("X");
+ if (x.is())
+ {
+ position_offset.x = static_cast(x.get());
+ }
+
+ const auto& y = json_data.get("Y");
+ if (y.is())
+ {
+ position_offset.y = static_cast(y.get());
+ }
+
+ const auto& z = json_data.get("Z");
+ if (z.is())
+ {
+ position_offset.z = static_cast(z.get());
+ }
+ return std::make_unique(position_offset);
+}
+
+MoveAction::MoveAction(Common::Vec3 position_offset) : m_position_offset(position_offset)
+{
+}
+
+void MoveAction::OnProjection(Common::Matrix44* matrix)
+{
+ if (!matrix)
+ return;
+
+ *matrix *= Common::Matrix44::Translate(m_position_offset);
+}
+
+void MoveAction::OnProjectionAndTexture(Common::Matrix44* matrix)
+{
+ if (!matrix)
+ return;
+
+ *matrix *= Common::Matrix44::Translate(m_position_offset);
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h
new file mode 100644
index 0000000000..768d6a9f38
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h
@@ -0,0 +1,22 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+
+#include
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+
+class MoveAction final : public GraphicsModAction
+{
+public:
+ static std::unique_ptr Create(const picojson::value& json_data);
+ explicit MoveAction(Common::Vec3 position_offset);
+ void OnProjection(Common::Matrix44* matrix) override;
+ void OnProjectionAndTexture(Common::Matrix44* matrix) override;
+
+private:
+ Common::Vec3 m_position_offset;
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.cpp
new file mode 100644
index 0000000000..e6f7d6b659
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.cpp
@@ -0,0 +1,36 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h"
+
+#include "Common/Logging/Log.h"
+
+void PrintAction::OnDrawStarted(bool*)
+{
+ INFO_LOG_FMT(VIDEO, "OnDrawStarted Called");
+}
+
+void PrintAction::OnEFB(bool*, u32 texture_width, u32 texture_height, u32* scaled_width,
+ u32* scaled_height)
+{
+ if (!scaled_width || !scaled_height)
+ return;
+
+ INFO_LOG_FMT(VIDEO, "OnEFB Called. Original [{}, {}], Scaled [{}, {}]", texture_width,
+ texture_height, *scaled_width, *scaled_height);
+}
+
+void PrintAction::OnProjection(Common::Matrix44*)
+{
+ INFO_LOG_FMT(VIDEO, "OnProjection Called");
+}
+
+void PrintAction::OnProjectionAndTexture(Common::Matrix44*)
+{
+ INFO_LOG_FMT(VIDEO, "OnProjectionAndTexture Called");
+}
+
+void PrintAction::OnTextureLoad()
+{
+ INFO_LOG_FMT(VIDEO, "OnTextureLoad Called");
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h
new file mode 100644
index 0000000000..6d8c123b68
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h
@@ -0,0 +1,17 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+
+class PrintAction final : public GraphicsModAction
+{
+public:
+ void OnDrawStarted(bool* skip) override;
+ void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
+ u32* scaled_height) override;
+ void OnProjection(Common::Matrix44* matrix) override;
+ void OnProjectionAndTexture(Common::Matrix44* matrix) override;
+ void OnTextureLoad() override;
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp
new file mode 100644
index 0000000000..d0cac24acb
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp
@@ -0,0 +1,59 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h"
+
+std::unique_ptr ScaleAction::Create(const picojson::value& json_data)
+{
+ Common::Vec3 scale;
+ const auto& x = json_data.get("X");
+ if (x.is())
+ {
+ scale.x = static_cast(x.get());
+ }
+
+ const auto& y = json_data.get("Y");
+ if (y.is())
+ {
+ scale.y = static_cast(y.get());
+ }
+
+ const auto& z = json_data.get("Z");
+ if (z.is())
+ {
+ scale.z = static_cast(z.get());
+ }
+ return std::make_unique(scale);
+}
+
+ScaleAction::ScaleAction(Common::Vec3 scale) : m_scale(scale)
+{
+}
+
+void ScaleAction::OnEFB(bool*, u32 texture_width, u32 texture_height, u32* scaled_width,
+ u32* scaled_height)
+{
+ if (scaled_width && m_scale.x > 0)
+ *scaled_width = texture_width * m_scale.x;
+
+ if (scaled_height && m_scale.y > 0)
+ *scaled_height = texture_height * m_scale.y;
+}
+
+void ScaleAction::OnProjection(Common::Matrix44* matrix)
+{
+ if (!matrix)
+ return;
+ auto& the_matrix = *matrix;
+ the_matrix.data[0] = the_matrix.data[0] * m_scale.x;
+ the_matrix.data[5] = the_matrix.data[5] * m_scale.y;
+}
+
+void ScaleAction::OnProjectionAndTexture(Common::Matrix44* matrix)
+{
+ if (!matrix)
+ return;
+ auto& the_matrix = *matrix;
+ the_matrix.data[0] = the_matrix.data[0] * m_scale.x;
+ the_matrix.data[5] = the_matrix.data[5] * m_scale.y;
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h
new file mode 100644
index 0000000000..cbe8744941
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h
@@ -0,0 +1,24 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+
+#include
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+
+class ScaleAction final : public GraphicsModAction
+{
+public:
+ static std::unique_ptr Create(const picojson::value& json_data);
+ explicit ScaleAction(Common::Vec3 scale);
+ void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
+ u32* scaled_height) override;
+ void OnProjection(Common::Matrix44* matrix) override;
+ void OnProjectionAndTexture(Common::Matrix44* matrix) override;
+
+private:
+ Common::Vec3 m_scale;
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp
new file mode 100644
index 0000000000..ae3551497f
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp
@@ -0,0 +1,20 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h"
+
+void SkipAction::OnDrawStarted(bool* skip)
+{
+ if (!skip)
+ return;
+
+ *skip = true;
+}
+
+void SkipAction::OnEFB(bool* skip, u32, u32, u32*, u32*)
+{
+ if (!skip)
+ return;
+
+ *skip = true;
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h
new file mode 100644
index 0000000000..5cd204a89d
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h
@@ -0,0 +1,14 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+
+class SkipAction final : public GraphicsModAction
+{
+public:
+ void OnDrawStarted(bool* skip) override;
+ void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
+ u32* scaled_height) override;
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.cpp
new file mode 100644
index 0000000000..5921c4a3e5
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.cpp
@@ -0,0 +1,22 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"
+
+#include "Common/Hash.h"
+
+u32 FBInfo::CalculateHash() const
+{
+ return Common::HashAdler32(reinterpret_cast(this), sizeof(FBInfo));
+}
+
+bool FBInfo::operator==(const FBInfo& other) const
+{
+ return m_height == other.m_height && m_width == other.m_width &&
+ m_texture_format == other.m_texture_format;
+}
+
+bool FBInfo::operator!=(const FBInfo& other) const
+{
+ return !(*this == other);
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.h
new file mode 100644
index 0000000000..48106e3a2a
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.h
@@ -0,0 +1,25 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "Common/CommonTypes.h"
+#include "VideoCommon/TextureDecoder.h"
+
+struct FBInfo
+{
+ u32 m_height = 0;
+ u32 m_width = 0;
+ TextureFormat m_texture_format = TextureFormat::I4;
+ u32 CalculateHash() const;
+ bool operator==(const FBInfo& other) const;
+ bool operator!=(const FBInfo& other) const;
+};
+
+struct FBInfoHasher
+{
+ std::size_t operator()(const FBInfo& fb_info) const noexcept
+ {
+ return static_cast(fb_info.CalculateHash());
+ }
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h
new file mode 100644
index 0000000000..54ea086a29
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h
@@ -0,0 +1,29 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "Common/CommonTypes.h"
+#include "Common/Matrix.h"
+
+class GraphicsModAction
+{
+public:
+ GraphicsModAction() = default;
+ virtual ~GraphicsModAction() = default;
+ GraphicsModAction(const GraphicsModAction&) = default;
+ GraphicsModAction(GraphicsModAction&&) = default;
+ GraphicsModAction& operator=(const GraphicsModAction&) = default;
+ GraphicsModAction& operator=(GraphicsModAction&&) = default;
+
+ virtual void OnDrawStarted(bool* skip) {}
+ virtual void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
+ u32* scaled_height)
+ {
+ }
+ virtual void OnXFB() {}
+ virtual void OnProjection(Common::Matrix44* matrix) {}
+ virtual void OnProjectionAndTexture(Common::Matrix44* matrix) {}
+ virtual void OnTextureLoad() {}
+ virtual void OnFrameEnd() {}
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp
new file mode 100644
index 0000000000..5eaac88c25
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp
@@ -0,0 +1,34 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
+
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h"
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h"
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h"
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h"
+
+namespace GraphicsModActionFactory
+{
+std::unique_ptr Create(std::string_view name, const picojson::value& json_data)
+{
+ if (name == "print")
+ {
+ return std::make_unique();
+ }
+ else if (name == "skip")
+ {
+ return std::make_unique();
+ }
+ else if (name == "move")
+ {
+ return MoveAction::Create(json_data);
+ }
+ else if (name == "scale")
+ {
+ return ScaleAction::Create(json_data);
+ }
+
+ return nullptr;
+}
+} // namespace GraphicsModActionFactory
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h
new file mode 100644
index 0000000000..3c9cc6d6a7
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h
@@ -0,0 +1,16 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+#include
+
+#include
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+
+namespace GraphicsModActionFactory
+{
+std::unique_ptr Create(std::string_view name, const picojson::value& json_data);
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp
new file mode 100644
index 0000000000..9e3d3e9e99
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp
@@ -0,0 +1,279 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
+
+#include
+#include
+#include
+
+#include "Common/Logging/Log.h"
+#include "Common/VariantUtil.h"
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
+#include "VideoCommon/TextureInfo.h"
+
+class GraphicsModManager::DecoratedAction final : public GraphicsModAction
+{
+public:
+ DecoratedAction(std::unique_ptr action, GraphicsModConfig mod)
+ : m_action_impl(std::move(action)), m_mod(std::move(mod))
+ {
+ }
+ void OnDrawStarted(bool* skip) override
+ {
+ if (!m_mod.m_enabled)
+ return;
+ m_action_impl->OnDrawStarted(skip);
+ }
+ void OnEFB(bool* skip, u32 texture_width, u32 texture_height, u32* scaled_width,
+ u32* scaled_height) override
+ {
+ if (!m_mod.m_enabled)
+ return;
+ m_action_impl->OnEFB(skip, texture_width, texture_height, scaled_width, scaled_height);
+ }
+ void OnProjection(Common::Matrix44* matrix) override
+ {
+ if (!m_mod.m_enabled)
+ return;
+ m_action_impl->OnProjection(matrix);
+ }
+ void OnProjectionAndTexture(Common::Matrix44* matrix) override
+ {
+ if (!m_mod.m_enabled)
+ return;
+ m_action_impl->OnProjectionAndTexture(matrix);
+ }
+ void OnTextureLoad() override
+ {
+ if (!m_mod.m_enabled)
+ return;
+ m_action_impl->OnTextureLoad();
+ }
+ void OnFrameEnd() override
+ {
+ if (!m_mod.m_enabled)
+ return;
+ m_action_impl->OnFrameEnd();
+ }
+
+private:
+ GraphicsModConfig m_mod;
+ std::unique_ptr m_action_impl;
+};
+
+const std::vector&
+GraphicsModManager::GetProjectionActions(ProjectionType projection_type) const
+{
+ if (const auto it = m_projection_target_to_actions.find(projection_type);
+ it != m_projection_target_to_actions.end())
+ {
+ return it->second;
+ }
+
+ return m_default;
+}
+
+const std::vector&
+GraphicsModManager::GetProjectionTextureActions(ProjectionType projection_type,
+ const std::string& texture_name) const
+{
+ const auto lookup = fmt::format("{}_{}", texture_name, static_cast(projection_type));
+ if (const auto it = m_projection_texture_target_to_actions.find(lookup);
+ it != m_projection_texture_target_to_actions.end())
+ {
+ return it->second;
+ }
+
+ return m_default;
+}
+
+const std::vector&
+GraphicsModManager::GetDrawStartedActions(const std::string& texture_name) const
+{
+ if (const auto it = m_draw_started_target_to_actions.find(texture_name);
+ it != m_draw_started_target_to_actions.end())
+ {
+ return it->second;
+ }
+
+ return m_default;
+}
+
+const std::vector&
+GraphicsModManager::GetTextureLoadActions(const std::string& texture_name) const
+{
+ if (const auto it = m_load_target_to_actions.find(texture_name);
+ it != m_load_target_to_actions.end())
+ {
+ return it->second;
+ }
+
+ return m_default;
+}
+
+const std::vector& GraphicsModManager::GetEFBActions(const FBInfo& efb) const
+{
+ if (const auto it = m_efb_target_to_actions.find(efb); it != m_efb_target_to_actions.end())
+ {
+ return it->second;
+ }
+
+ return m_default;
+}
+
+const std::vector& GraphicsModManager::GetXFBActions(const FBInfo& xfb) const
+{
+ if (const auto it = m_efb_target_to_actions.find(xfb); it != m_efb_target_to_actions.end())
+ {
+ return it->second;
+ }
+
+ return m_default;
+}
+
+void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
+{
+ Reset();
+
+ const auto& mods = config.GetMods();
+
+ std::map> group_to_targets;
+ for (const auto& mod : mods)
+ {
+ for (const GraphicsTargetGroupConfig& group : mod.m_groups)
+ {
+ if (m_groups.find(group.m_name) != m_groups.end())
+ {
+ WARN_LOG_FMT(
+ VIDEO,
+ "Specified graphics mod group '{}' for mod '{}' is already specified by another mod.",
+ group.m_name, mod.m_title);
+ }
+ m_groups.insert(group.m_name);
+
+ const auto internal_group = fmt::format("{}.{}", mod.m_title, group.m_name);
+ for (const GraphicsTargetConfig& target : group.m_targets)
+ {
+ group_to_targets[group.m_name].push_back(target);
+ group_to_targets[internal_group].push_back(target);
+ }
+ }
+ }
+
+ for (const auto& mod : mods)
+ {
+ for (const GraphicsModFeatureConfig& feature : mod.m_features)
+ {
+ const auto create_action = [](const std::string_view& action_name,
+ const picojson::value& json_data,
+ GraphicsModConfig mod) -> std::unique_ptr {
+ auto action = GraphicsModActionFactory::Create(action_name, json_data);
+ if (action == nullptr)
+ {
+ return nullptr;
+ }
+ return std::make_unique(std::move(action), std::move(mod));
+ };
+
+ const auto internal_group = fmt::format("{}.{}", mod.m_title, feature.m_group);
+
+ const auto add_target = [&](const GraphicsTargetConfig& target, GraphicsModConfig mod) {
+ auto action = create_action(feature.m_action, feature.m_action_data, std::move(mod));
+ if (action == nullptr)
+ {
+ WARN_LOG_FMT(VIDEO, "Failed to create action '{}' for group '{}'.", feature.m_action,
+ feature.m_group);
+ return;
+ }
+ m_actions.push_back(std::move(action));
+ std::visit(
+ overloaded{
+ [&](const DrawStartedTextureTarget& the_target) {
+ m_draw_started_target_to_actions[the_target.m_texture_info_string].push_back(
+ m_actions.back().get());
+ },
+ [&](const LoadTextureTarget& the_target) {
+ m_load_target_to_actions[the_target.m_texture_info_string].push_back(
+ m_actions.back().get());
+ },
+ [&](const EFBTarget& the_target) {
+ FBInfo info;
+ info.m_height = the_target.m_height;
+ info.m_width = the_target.m_width;
+ info.m_texture_format = the_target.m_texture_format;
+ m_efb_target_to_actions[info].push_back(m_actions.back().get());
+ },
+ [&](const XFBTarget& the_target) {
+ FBInfo info;
+ info.m_height = the_target.m_height;
+ info.m_width = the_target.m_width;
+ info.m_texture_format = the_target.m_texture_format;
+ m_xfb_target_to_actions[info].push_back(m_actions.back().get());
+ },
+ [&](const ProjectionTarget& the_target) {
+ if (the_target.m_texture_info_string)
+ {
+ const auto lookup = fmt::format("{}_{}", *the_target.m_texture_info_string,
+ static_cast(the_target.m_projection_type));
+ m_projection_texture_target_to_actions[lookup].push_back(
+ m_actions.back().get());
+ }
+ else
+ {
+ m_projection_target_to_actions[the_target.m_projection_type].push_back(
+ m_actions.back().get());
+ }
+ },
+ },
+ target);
+ };
+
+ // Prefer groups in the pack over groups from another pack
+ if (const auto local_it = group_to_targets.find(internal_group);
+ local_it != group_to_targets.end())
+ {
+ for (const GraphicsTargetConfig& target : local_it->second)
+ {
+ add_target(target, mod);
+ }
+ }
+ else if (const auto global_it = group_to_targets.find(feature.m_group);
+ global_it != group_to_targets.end())
+ {
+ for (const GraphicsTargetConfig& target : global_it->second)
+ {
+ add_target(target, mod);
+ }
+ }
+ else
+ {
+ WARN_LOG_FMT(VIDEO, "Specified graphics mod group '{}' was not found for mod '{}'",
+ feature.m_group, mod.m_title);
+ }
+ }
+ }
+}
+
+void GraphicsModManager::EndOfFrame()
+{
+ for (auto&& action : m_actions)
+ {
+ action->OnFrameEnd();
+ }
+}
+
+void GraphicsModManager::Reset()
+{
+ m_actions.clear();
+ m_groups.clear();
+ m_projection_target_to_actions.clear();
+ m_projection_texture_target_to_actions.clear();
+ m_draw_started_target_to_actions.clear();
+ m_load_target_to_actions.clear();
+ m_efb_target_to_actions.clear();
+ m_xfb_target_to_actions.clear();
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h
new file mode 100644
index 0000000000..5151b85dbd
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h
@@ -0,0 +1,54 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+#include "VideoCommon/TextureInfo.h"
+#include "VideoCommon/XFMemory.h"
+
+class GraphicsModGroupConfig;
+class GraphicsModManager
+{
+public:
+ const std::vector& GetProjectionActions(ProjectionType projection_type) const;
+ const std::vector&
+ GetProjectionTextureActions(ProjectionType projection_type,
+ const std::string& texture_name) const;
+ const std::vector&
+ GetDrawStartedActions(const std::string& texture_name) const;
+ const std::vector&
+ GetTextureLoadActions(const std::string& texture_name) const;
+ const std::vector& GetEFBActions(const FBInfo& efb) const;
+ const std::vector& GetXFBActions(const FBInfo& xfb) const;
+
+ void Load(const GraphicsModGroupConfig& config);
+
+ void EndOfFrame();
+
+private:
+ void Reset();
+
+ class DecoratedAction;
+
+ static inline const std::vector m_default = {};
+ std::list> m_actions;
+ std::unordered_map>
+ m_projection_target_to_actions;
+ std::unordered_map>
+ m_projection_texture_target_to_actions;
+ std::unordered_map> m_draw_started_target_to_actions;
+ std::unordered_map> m_load_target_to_actions;
+ std::unordered_map, FBInfoHasher> m_efb_target_to_actions;
+ std::unordered_map, FBInfoHasher> m_xfb_target_to_actions;
+
+ std::unordered_set m_groups;
+};