summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon/GraphicsModSystem
diff options
context:
space:
mode:
authorJMC47 <JMC4789@gmail.com>2022-06-28 13:35:39 -0400
committerGitHub <noreply@github.com>2022-06-28 13:35:39 -0400
commit5d04e1e1de8248da8366f6f99ff4cdbe5467c789 (patch)
tree47ff1e96f099cef3443635b982d03e2bf1ce3f04 /Source/Core/VideoCommon/GraphicsModSystem
parente50e45f400856191eafb5f0c6044a8efbf152321 (diff)
parent8aef0015fd8903b92cf0aab8a98ee669ec97b5ef (diff)
Merge pull request #10518 from iwubcode/draw-mod
Introducing a 'GraphicsMod' system for creators
Diffstat (limited to 'Source/Core/VideoCommon/GraphicsModSystem')
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp291
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h45
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp48
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h20
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp191
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h46
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp254
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h56
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp88
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h22
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Constants.h11
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp47
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h22
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.cpp36
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h17
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp59
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h24
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp20
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h14
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.cpp22
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.h25
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h29
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp34
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h16
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp279
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h54
26 files changed, 1770 insertions, 0 deletions
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp
new file mode 100644
index 0000000000..c7dc5aab87
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp
@@ -0,0 +1,291 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
+
+#include <fmt/format.h>
+
+#include "Common/CommonPaths.h"
+#include "Common/FileUtil.h"
+#include "Common/Logging/Log.h"
+#include "Common/StringUtil.h"
+
+#include "VideoCommon/GraphicsModSystem/Constants.h"
+
+std::optional<GraphicsModConfig> GraphicsModConfig::Create(const std::string& file_path,
+ Source source)
+{
+ std::string json_data;
+ if (!File::ReadFileToString(file_path, json_data))
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod json file '{}'", file_path);
+ return std::nullopt;
+ }
+
+ picojson::value root;
+ const auto error = picojson::parse(root, json_data);
+
+ if (!error.empty())
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod json file '{}' due to parse error: {}",
+ file_path, error);
+ return std::nullopt;
+ }
+
+ GraphicsModConfig result;
+ if (!result.DeserializeFromConfig(root))
+ {
+ return std::nullopt;
+ }
+ result.m_source = source;
+ if (source == Source::User)
+ {
+ const std::string base_path = File::GetUserPath(D_GRAPHICSMOD_IDX);
+ if (base_path.size() > file_path.size())
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load graphics mod json file '{}' due to it not matching the base path: {}",
+ file_path, base_path);
+ return std::nullopt;
+ }
+ result.m_relative_path = file_path.substr(base_path.size());
+ }
+ else
+ {
+ const std::string base_path = File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR;
+ if (base_path.size() > file_path.size())
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load graphics mod json file '{}' due to it not matching the base path: {}",
+ file_path, base_path);
+ return std::nullopt;
+ }
+ result.m_relative_path = file_path.substr(base_path.size());
+ }
+
+ return result;
+}
+
+std::optional<GraphicsModConfig> GraphicsModConfig::Create(const picojson::object* obj)
+{
+ if (!obj)
+ return std::nullopt;
+
+ const auto source_it = obj->find("source");
+ if (source_it == obj->end())
+ {
+ return std::nullopt;
+ }
+ const std::string source_str = source_it->second.to_str();
+
+ const auto path_it = obj->find("path");
+ if (path_it == obj->end())
+ {
+ return std::nullopt;
+ }
+ const std::string relative_path = path_it->second.to_str();
+
+ if (source_str == "system")
+ {
+ return Create(fmt::format("{}{}{}", File::GetSysDirectory(), DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR,
+ relative_path),
+ Source::System);
+ }
+ else
+ {
+ return Create(File::GetUserPath(D_GRAPHICSMOD_IDX) + relative_path, Source::User);
+ }
+}
+
+std::string GraphicsModConfig::GetAbsolutePath() const
+{
+ if (m_source == Source::System)
+ {
+ return WithUnifiedPathSeparators(fmt::format("{}{}{}", File::GetSysDirectory(),
+ DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_relative_path));
+ }
+ else
+ {
+ return WithUnifiedPathSeparators(File::GetUserPath(D_GRAPHICSMOD_IDX) + m_relative_path);
+ }
+}
+
+bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value)
+{
+ const auto& meta = value.get("meta");
+ if (meta.is<picojson::object>())
+ {
+ const auto& title = meta.get("title");
+ if (title.is<std::string>())
+ {
+ m_title = title.to_str();
+ }
+
+ const auto& author = meta.get("author");
+ if (author.is<std::string>())
+ {
+ m_author = author.to_str();
+ }
+
+ const auto& description = meta.get("description");
+ if (description.is<std::string>())
+ {
+ m_description = description.to_str();
+ }
+ }
+
+ const auto& groups = value.get("groups");
+ if (groups.is<picojson::array>())
+ {
+ for (const auto& group_val : groups.get<picojson::array>())
+ {
+ if (!group_val.is<picojson::object>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO, "Failed to load mod configuration file, specified group is not a json object");
+ return false;
+ }
+ GraphicsTargetGroupConfig group;
+ if (!group.DeserializeFromConfig(group_val.get<picojson::object>()))
+ {
+ return false;
+ }
+
+ m_groups.push_back(group);
+ }
+ }
+
+ const auto& features = value.get("features");
+ if (features.is<picojson::array>())
+ {
+ for (const auto& feature_val : features.get<picojson::array>())
+ {
+ if (!feature_val.is<picojson::object>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO, "Failed to load mod configuration file, specified feature is not a json object");
+ return false;
+ }
+ GraphicsModFeatureConfig feature;
+ if (!feature.DeserializeFromConfig(feature_val.get<picojson::object>()))
+ {
+ return false;
+ }
+
+ m_features.push_back(feature);
+ }
+ }
+
+ return true;
+}
+
+void GraphicsModConfig::SerializeToProfile(picojson::object* obj) const
+{
+ if (!obj)
+ return;
+
+ auto& json_obj = *obj;
+ switch (m_source)
+ {
+ case Source::User:
+ {
+ json_obj["source"] = picojson::value{"user"};
+ }
+ break;
+ case Source::System:
+ {
+ json_obj["source"] = picojson::value{"system"};
+ }
+ break;
+ };
+
+ json_obj["path"] = picojson::value{m_relative_path};
+
+ picojson::array serialized_groups;
+ for (const auto& group : m_groups)
+ {
+ picojson::object serialized_group;
+ group.SerializeToProfile(&serialized_group);
+ serialized_groups.push_back(picojson::value{serialized_group});
+ }
+ json_obj["groups"] = picojson::value{serialized_groups};
+
+ picojson::array serialized_features;
+ for (const auto& feature : m_features)
+ {
+ picojson::object serialized_feature;
+ feature.SerializeToProfile(&serialized_feature);
+ serialized_features.push_back(picojson::value{serialized_feature});
+ }
+ json_obj["features"] = picojson::value{serialized_features};
+
+ json_obj["enabled"] = picojson::value{m_enabled};
+
+ json_obj["weight"] = picojson::value{static_cast<double>(m_weight)};
+}
+
+void GraphicsModConfig::DeserializeFromProfile(const picojson::object& obj)
+{
+ if (const auto it = obj.find("groups"); it != obj.end())
+ {
+ if (it->second.is<picojson::array>())
+ {
+ auto serialized_groups = it->second.get<picojson::array>();
+ if (serialized_groups.size() != m_groups.size())
+ return;
+
+ for (std::size_t i = 0; i < serialized_groups.size(); i++)
+ {
+ const auto& serialized_group_val = serialized_groups[i];
+ if (serialized_group_val.is<picojson::object>())
+ {
+ const auto& serialized_group = serialized_group_val.get<picojson::object>();
+ m_groups[i].DeserializeFromProfile(serialized_group);
+ }
+ }
+ }
+ }
+
+ if (const auto it = obj.find("features"); it != obj.end())
+ {
+ if (it->second.is<picojson::array>())
+ {
+ auto serialized_features = it->second.get<picojson::array>();
+ if (serialized_features.size() != m_features.size())
+ return;
+
+ for (std::size_t i = 0; i < serialized_features.size(); i++)
+ {
+ const auto& serialized_feature_val = serialized_features[i];
+ if (serialized_feature_val.is<picojson::object>())
+ {
+ const auto& serialized_feature = serialized_feature_val.get<picojson::object>();
+ m_features[i].DeserializeFromProfile(serialized_feature);
+ }
+ }
+ }
+ }
+
+ if (const auto it = obj.find("enabled"); it != obj.end())
+ {
+ if (it->second.is<bool>())
+ {
+ m_enabled = it->second.get<bool>();
+ }
+ }
+
+ if (const auto it = obj.find("weight"); it != obj.end())
+ {
+ if (it->second.is<double>())
+ {
+ m_weight = static_cast<u16>(it->second.get<double>());
+ }
+ }
+}
+
+bool GraphicsModConfig::operator<(const GraphicsModConfig& other) const
+{
+ return m_weight < other.m_weight;
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h
new file mode 100644
index 0000000000..f4f6859cb3
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h
@@ -0,0 +1,45 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <picojson.h>
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h"
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h"
+
+struct GraphicsModConfig
+{
+ std::string m_title;
+ std::string m_author;
+ std::string m_description;
+ bool m_enabled = false;
+ u16 m_weight = 0;
+ std::string m_relative_path;
+
+ enum class Source
+ {
+ User,
+ System
+ };
+ Source m_source = Source::User;
+
+ std::vector<GraphicsTargetGroupConfig> m_groups;
+ std::vector<GraphicsModFeatureConfig> m_features;
+
+ static std::optional<GraphicsModConfig> Create(const std::string& file, Source source);
+ static std::optional<GraphicsModConfig> Create(const picojson::object* obj);
+
+ std::string GetAbsolutePath() const;
+
+ bool DeserializeFromConfig(const picojson::value& value);
+
+ void SerializeToProfile(picojson::object* value) const;
+ void DeserializeFromProfile(const picojson::object& value);
+
+ bool operator<(const GraphicsModConfig& other) const;
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp
new file mode 100644
index 0000000000..3fa75eceba
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp
@@ -0,0 +1,48 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h"
+
+#include "Common/Logging/Log.h"
+
+bool GraphicsModFeatureConfig::DeserializeFromConfig(const picojson::object& obj)
+{
+ if (auto group_iter = obj.find("group"); group_iter != obj.end())
+ {
+ if (!group_iter->second.is<std::string>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load mod configuration file, specified feature's group is not a string");
+ return false;
+ }
+ m_group = group_iter->second.get<std::string>();
+ }
+
+ if (auto action_iter = obj.find("action"); action_iter != obj.end())
+ {
+ if (!action_iter->second.is<std::string>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load mod configuration file, specified feature's action is not a string");
+ return false;
+ }
+ m_action = action_iter->second.get<std::string>();
+ }
+
+ if (auto action_data_iter = obj.find("action_data"); action_data_iter != obj.end())
+ {
+ m_action_data = action_data_iter->second;
+ }
+
+ return true;
+}
+
+void GraphicsModFeatureConfig::SerializeToProfile(picojson::object*) const
+{
+}
+
+void GraphicsModFeatureConfig::DeserializeFromProfile(const picojson::object&)
+{
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h
new file mode 100644
index 0000000000..af71bf35a7
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h
@@ -0,0 +1,20 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include <picojson.h>
+
+struct GraphicsModFeatureConfig
+{
+ std::string m_group;
+ std::string m_action;
+ picojson::value m_action_data;
+
+ bool DeserializeFromConfig(const picojson::object& value);
+
+ void SerializeToProfile(picojson::object* value) const;
+ void DeserializeFromProfile(const picojson::object& value);
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp
new file mode 100644
index 0000000000..0d1ae30fb6
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp
@@ -0,0 +1,191 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
+
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "Common/CommonPaths.h"
+#include "Common/FileSearch.h"
+#include "Common/FileUtil.h"
+#include "Common/Logging/Log.h"
+#include "Common/StringUtil.h"
+#include "Core/ConfigManager.h"
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
+#include "VideoCommon/GraphicsModSystem/Constants.h"
+#include "VideoCommon/HiresTextures.h"
+
+GraphicsModGroupConfig::GraphicsModGroupConfig(const std::string& game_id) : m_game_id(game_id)
+{
+}
+
+GraphicsModGroupConfig::~GraphicsModGroupConfig() = default;
+
+GraphicsModGroupConfig::GraphicsModGroupConfig(const GraphicsModGroupConfig&) = default;
+
+GraphicsModGroupConfig::GraphicsModGroupConfig(GraphicsModGroupConfig&&) = default;
+
+GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(const GraphicsModGroupConfig&) = default;
+
+GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(GraphicsModGroupConfig&&) = default;
+
+void GraphicsModGroupConfig::Load()
+{
+ const std::string file_path = GetPath();
+
+ std::set<std::string> known_paths;
+ if (File::Exists(file_path))
+ {
+ std::string json_data;
+ if (!File::ReadFileToString(file_path, json_data))
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod group json file '{}'", file_path);
+ return;
+ }
+
+ picojson::value root;
+ const auto error = picojson::parse(root, json_data);
+
+ if (!error.empty())
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load graphics mod group json file '{}' due to parse error: {}",
+ file_path, error);
+ return;
+ }
+ if (!root.is<picojson::object>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load graphics mod group json file '{}' due to root not being an object!",
+ file_path);
+ return;
+ }
+
+ const auto& mods = root.get("mods");
+ if (mods.is<picojson::array>())
+ {
+ for (const auto& mod_json : mods.get<picojson::array>())
+ {
+ if (mod_json.is<picojson::object>())
+ {
+ const auto& mod_json_obj = mod_json.get<picojson::object>();
+ auto graphics_mod = GraphicsModConfig::Create(&mod_json_obj);
+ if (!graphics_mod)
+ {
+ continue;
+ }
+ graphics_mod->DeserializeFromProfile(mod_json_obj);
+
+ auto mod_full_path = graphics_mod->GetAbsolutePath();
+ known_paths.insert(std::move(mod_full_path));
+ m_graphics_mods.push_back(*graphics_mod);
+ }
+ }
+ }
+ }
+
+ const auto try_add_mod = [&known_paths, this](const std::string& dir,
+ GraphicsModConfig::Source source) {
+ auto file = dir + DIR_SEP + "metadata.json";
+ UnifyPathSeparators(file);
+ if (known_paths.find(file) != known_paths.end())
+ {
+ return;
+ }
+ const auto mod = GraphicsModConfig::Create(file, source);
+ if (mod)
+ {
+ m_graphics_mods.push_back(*mod);
+ }
+ };
+
+ const std::set<std::string> graphics_mod_user_directories =
+ GetTextureDirectoriesWithGameId(File::GetUserPath(D_GRAPHICSMOD_IDX), m_game_id);
+
+ for (const auto& graphics_mod_directory : graphics_mod_user_directories)
+ {
+ try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::User);
+ }
+
+ const std::set<std::string> graphics_mod_system_directories = GetTextureDirectoriesWithGameId(
+ File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_game_id);
+
+ for (const auto& graphics_mod_directory : graphics_mod_system_directories)
+ {
+ try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::System);
+ }
+
+ std::sort(m_graphics_mods.begin(), m_graphics_mods.end());
+ for (auto& mod : m_graphics_mods)
+ {
+ m_path_to_graphics_mod[mod.GetAbsolutePath()] = &mod;
+ }
+
+ m_change_count++;
+}
+
+void GraphicsModGroupConfig::Save() const
+{
+ const std::string file_path = GetPath();
+ std::ofstream json_stream;
+ File::OpenFStream(json_stream, file_path, std::ios_base::out);
+ if (!json_stream.is_open())
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path);
+ return;
+ }
+
+ picojson::object serialized_root;
+ picojson::array serialized_mods;
+ for (const auto& mod : m_graphics_mods)
+ {
+ picojson::object serialized_mod;
+ mod.SerializeToProfile(&serialized_mod);
+ serialized_mods.push_back(picojson::value{serialized_mod});
+ }
+ serialized_root["mods"] = picojson::value{serialized_mods};
+
+ const auto output = picojson::value{serialized_root}.serialize(true);
+ json_stream << output;
+}
+
+void GraphicsModGroupConfig::SetChangeCount(u32 change_count)
+{
+ m_change_count = change_count;
+}
+
+u32 GraphicsModGroupConfig::GetChangeCount() const
+{
+ return m_change_count;
+}
+
+const std::vector<GraphicsModConfig>& GraphicsModGroupConfig::GetMods() const
+{
+ return m_graphics_mods;
+}
+
+GraphicsModConfig* GraphicsModGroupConfig::GetMod(const std::string& absolute_path) const
+{
+ if (const auto iter = m_path_to_graphics_mod.find(absolute_path);
+ iter != m_path_to_graphics_mod.end())
+ {
+ return iter->second;
+ }
+
+ return nullptr;
+}
+
+const std::string& GraphicsModGroupConfig::GetGameID() const
+{
+ return m_game_id;
+}
+
+std::string GraphicsModGroupConfig::GetPath() const
+{
+ const std::string game_mod_root = File::GetUserPath(D_CONFIG_IDX) + GRAPHICSMOD_CONFIG_DIR;
+ return fmt::format("{}/{}.json", game_mod_root, m_game_id);
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h
new file mode 100644
index 0000000000..ace5127c58
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h
@@ -0,0 +1,46 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <picojson.h>
+
+#include "Common/CommonTypes.h"
+
+struct GraphicsModConfig;
+
+class GraphicsModGroupConfig
+{
+public:
+ explicit GraphicsModGroupConfig(const std::string& game_id);
+ ~GraphicsModGroupConfig();
+
+ GraphicsModGroupConfig(const GraphicsModGroupConfig&);
+ GraphicsModGroupConfig(GraphicsModGroupConfig&&);
+
+ GraphicsModGroupConfig& operator=(const GraphicsModGroupConfig&);
+ GraphicsModGroupConfig& operator=(GraphicsModGroupConfig&&);
+
+ void Load();
+ void Save() const;
+
+ void SetChangeCount(u32 change_count);
+ u32 GetChangeCount() const;
+
+ const std::vector<GraphicsModConfig>& GetMods() const;
+
+ GraphicsModConfig* GetMod(const std::string& absolute_path) const;
+
+ const std::string& GetGameID() const;
+
+private:
+ std::string GetPath() const;
+ std::string m_game_id;
+ std::vector<GraphicsModConfig> m_graphics_mods;
+ std::map<std::string, GraphicsModConfig*> m_path_to_graphics_mod;
+ u32 m_change_count = 0;
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp
new file mode 100644
index 0000000000..f55f184520
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp
@@ -0,0 +1,254 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"
+
+#include "Common/Logging/Log.h"
+#include "Common/StringUtil.h"
+#include "VideoCommon/TextureCacheBase.h"
+
+namespace
+{
+template <typename T, std::enable_if_t<std::is_base_of_v<FBTarget, T>, int> = 0>
+std::optional<T> DeserializeFBTargetFromConfig(const picojson::object& obj, std::string_view prefix)
+{
+ T fb;
+ const auto texture_filename_iter = obj.find("texture_filename");
+ if (texture_filename_iter == obj.end())
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load mod configuration file, option 'texture_filename' not found");
+ return std::nullopt;
+ }
+ if (!texture_filename_iter->second.is<std::string>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load mod configuration file, option 'texture_filename' is not a string type");
+ return std::nullopt;
+ }
+ const auto texture_filename = texture_filename_iter->second.get<std::string>();
+ const auto texture_filename_without_prefix = texture_filename.substr(prefix.size() + 1);
+ const auto split_str_values = SplitString(texture_filename_without_prefix, '_');
+ if (split_str_values.size() == 1)
+ {
+ ERROR_LOG_FMT(
+ VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is not valid");
+ return std::nullopt;
+ }
+ const auto split_width_height_values = SplitString(texture_filename_without_prefix, 'x');
+ if (split_width_height_values.size() != 2)
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
+ "not valid, width and height separator found more matches than expected");
+ return std::nullopt;
+ }
+
+ const std::size_t width_underscore_pos = split_width_height_values[0].find_last_of('_');
+ std::string width_str;
+ if (width_underscore_pos == std::string::npos)
+ {
+ width_str = split_width_height_values[0];
+ }
+ else
+ {
+ width_str = split_width_height_values[0].substr(width_underscore_pos + 1);
+ }
+ if (!TryParse(width_str, &fb.m_width))
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
+ "not valid, width not a number");
+ return std::nullopt;
+ }
+
+ const std::size_t height_underscore_pos = split_width_height_values[1].find_first_of('_');
+ if (height_underscore_pos == std::string::npos ||
+ height_underscore_pos == split_width_height_values[1].size() - 1)
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
+ "not valid, underscore after height is missing or incomplete");
+ return std::nullopt;
+ }
+ const std::string height_str = split_width_height_values[1].substr(0, height_underscore_pos);
+ if (!TryParse(height_str, &fb.m_height))
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
+ "not valid, height not a number");
+ return std::nullopt;
+ }
+
+ const std::size_t format_underscore_pos =
+ split_width_height_values[1].find_first_of('_', height_underscore_pos + 1);
+
+ std::string format_str;
+ if (format_underscore_pos == std::string::npos)
+ {
+ format_str = split_width_height_values[1].substr(height_underscore_pos + 1);
+ }
+ else
+ {
+ format_str = split_width_height_values[1].substr(
+ height_underscore_pos + 1, (format_underscore_pos - height_underscore_pos) - 1);
+ }
+ u32 format;
+ if (!TryParse(format_str, &format))
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
+ "not valid, texture format is not a number");
+ return std::nullopt;
+ }
+ if (!IsValidTextureFormat(static_cast<TextureFormat>(format)))
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
+ "not valid, texture format is not valid");
+ return std::nullopt;
+ }
+ fb.m_texture_format = static_cast<TextureFormat>(format);
+
+ return fb;
+}
+std::optional<std::string> ExtractTextureFilenameForConfig(const picojson::object& obj)
+{
+ const auto texture_filename_iter = obj.find("texture_filename");
+ if (texture_filename_iter == obj.end())
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load mod configuration file, option 'texture_filename' not found");
+ return std::nullopt;
+ }
+ if (!texture_filename_iter->second.is<std::string>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load mod configuration file, option 'texture_filename' is not a string type");
+ return std::nullopt;
+ }
+ std::string texture_info = texture_filename_iter->second.get<std::string>();
+ if (texture_info.find(EFB_DUMP_PREFIX) != std::string::npos)
+ {
+ const auto letter_c_pos = texture_info.find_first_of('n');
+ if (letter_c_pos == std::string::npos)
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' "
+ "is an efb without a count");
+ return std::nullopt;
+ }
+ texture_info =
+ texture_info.substr(letter_c_pos - 1, texture_info.find_first_of("_", letter_c_pos));
+ }
+ else if (texture_info.find(XFB_DUMP_PREFIX) != std::string::npos)
+ {
+ const auto letter_c_pos = texture_info.find_first_of('n');
+ if (letter_c_pos == std::string::npos)
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' "
+ "is an xfb without a count");
+ return std::nullopt;
+ }
+ texture_info =
+ texture_info.substr(letter_c_pos - 1, texture_info.find_first_of("_", letter_c_pos));
+ }
+ return texture_info;
+}
+} // namespace
+
+std::optional<GraphicsTargetConfig> DeserializeTargetFromConfig(const picojson::object& obj)
+{
+ const auto type_iter = obj.find("type");
+ if (type_iter == obj.end())
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'type' not found");
+ return std::nullopt;
+ }
+ if (!type_iter->second.is<std::string>())
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load mod configuration file, option 'type' is not a string type");
+ return std::nullopt;
+ }
+ const std::string& type = type_iter->second.get<std::string>();
+ if (type == "draw_started")
+ {
+ std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
+ if (!texture_info.has_value())
+ return std::nullopt;
+
+ DrawStartedTextureTarget target;
+ target.m_texture_info_string = texture_info.value();
+ return target;
+ }
+ else if (type == "load_texture")
+ {
+ std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
+ if (!texture_info.has_value())
+ return std::nullopt;
+
+ LoadTextureTarget target;
+ target.m_texture_info_string = texture_info.value();
+ return target;
+ }
+ else if (type == "efb")
+ {
+ return DeserializeFBTargetFromConfig<EFBTarget>(obj, EFB_DUMP_PREFIX);
+ }
+ else if (type == "xfb")
+ {
+ return DeserializeFBTargetFromConfig<XFBTarget>(obj, EFB_DUMP_PREFIX);
+ }
+ else if (type == "projection")
+ {
+ ProjectionTarget target;
+ const auto texture_iter = obj.find("texture_filename");
+ if (texture_iter != obj.end())
+ {
+ std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
+ if (!texture_info.has_value())
+ return std::nullopt;
+ target.m_texture_info_string = texture_info;
+ }
+ const auto value_iter = obj.find("value");
+ if (value_iter == obj.end())
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' not found");
+ return std::nullopt;
+ }
+ if (!value_iter->second.is<std::string>())
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load mod configuration file, option 'value' is not a string type");
+ return std::nullopt;
+ }
+ const auto& value_str = value_iter->second.get<std::string>();
+ if (value_str == "2d")
+ {
+ target.m_projection_type = ProjectionType::Orthographic;
+ }
+ else if (value_str == "3d")
+ {
+ target.m_projection_type = ProjectionType::Perspective;
+ }
+ else
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' is not a valid "
+ "value, valid values are: 2d, 3d");
+ return std::nullopt;
+ }
+ return target;
+ }
+ else
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load mod configuration file, option 'type' is not a valid value");
+ }
+ return std::nullopt;
+}
+
+void SerializeTargetToProfile(picojson::object*, const GraphicsTargetConfig&)
+{
+ // Added for consistency, no functionality as of now
+}
+
+void DeserializeTargetFromProfile(const picojson::object&, GraphicsTargetConfig*)
+{
+ // Added for consistency, no functionality as of now
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h
new file mode 100644
index 0000000000..67a349e78f
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h
@@ -0,0 +1,56 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <variant>
+
+#include <picojson.h>
+
+#include "Common/CommonTypes.h"
+#include "VideoCommon/TextureDecoder.h"
+#include "VideoCommon/XFMemory.h"
+
+struct TextureTarget
+{
+ std::string m_texture_info_string;
+};
+
+struct DrawStartedTextureTarget final : public TextureTarget
+{
+};
+
+struct LoadTextureTarget final : public TextureTarget
+{
+};
+
+struct FBTarget
+{
+ u32 m_height = 0;
+ u32 m_width = 0;
+ TextureFormat m_texture_format = TextureFormat::I4;
+};
+
+struct EFBTarget final : public FBTarget
+{
+};
+
+struct XFBTarget final : public FBTarget
+{
+};
+
+struct ProjectionTarget
+{
+ std::optional<std::string> m_texture_info_string;
+ ProjectionType m_projection_type = ProjectionType::Perspective;
+};
+
+using GraphicsTargetConfig = std::variant<DrawStartedTextureTarget, LoadTextureTarget, EFBTarget,
+ XFBTarget, ProjectionTarget>;
+
+std::optional<GraphicsTargetConfig> DeserializeTargetFromConfig(const picojson::object& obj);
+
+void SerializeTargetToProfile(picojson::object* obj, const GraphicsTargetConfig& target);
+void DeserializeTargetFromProfile(const picojson::object& obj, GraphicsTargetConfig* target);
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp
new file mode 100644
index 0000000000..753f304cb5
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp
@@ -0,0 +1,88 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h"
+
+#include "Common/Logging/Log.h"
+
+bool GraphicsTargetGroupConfig::DeserializeFromConfig(const picojson::object& obj)
+{
+ if (auto name_iter = obj.find("name"); name_iter != obj.end())
+ {
+ if (!name_iter->second.is<std::string>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO, "Failed to load mod configuration file, specified group's name is not a string");
+ return false;
+ }
+ m_name = name_iter->second.get<std::string>();
+ }
+
+ if (auto targets_iter = obj.find("targets"); targets_iter != obj.end())
+ {
+ if (!targets_iter->second.is<picojson::array>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load mod configuration file, specified group's targets is not an array");
+ return false;
+ }
+ for (const auto& target_val : targets_iter->second.get<picojson::array>())
+ {
+ if (!target_val.is<picojson::object>())
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load shader configuration file, specified target is not a json object");
+ return false;
+ }
+ const auto target = DeserializeTargetFromConfig(target_val.get<picojson::object>());
+ if (!target)
+ {
+ return false;
+ }
+
+ m_targets.push_back(*target);
+ }
+ }
+
+ return true;
+}
+
+void GraphicsTargetGroupConfig::SerializeToProfile(picojson::object* obj) const
+{
+ if (!obj)
+ return;
+ auto& json_obj = *obj;
+ picojson::array serialized_targets;
+ for (const auto& target : m_targets)
+ {
+ picojson::object serialized_target;
+ SerializeTargetToProfile(&serialized_target, target);
+ serialized_targets.push_back(picojson::value{serialized_target});
+ }
+ json_obj["targets"] = picojson::value{serialized_targets};
+}
+
+void GraphicsTargetGroupConfig::DeserializeFromProfile(const picojson::object& obj)
+{
+ if (const auto it = obj.find("targets"); it != obj.end())
+ {
+ if (it->second.is<picojson::array>())
+ {
+ auto serialized_targets = it->second.get<picojson::array>();
+ if (serialized_targets.size() != m_targets.size())
+ return;
+
+ for (std::size_t i = 0; i < serialized_targets.size(); i++)
+ {
+ const auto& serialized_target_val = serialized_targets[i];
+ if (serialized_target_val.is<picojson::object>())
+ {
+ const auto& serialized_target = serialized_target_val.get<picojson::object>();
+ DeserializeTargetFromProfile(serialized_target, &m_targets[i]);
+ }
+ }
+ }
+ }
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h
new file mode 100644
index 0000000000..2db2517175
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h
@@ -0,0 +1,22 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <picojson.h>
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"
+
+struct GraphicsTargetGroupConfig
+{
+ std::string m_name;
+ std::vector<GraphicsTargetConfig> m_targets;
+
+ bool DeserializeFromConfig(const picojson::object& obj);
+
+ void SerializeToProfile(picojson::object* obj) const;
+ void DeserializeFromProfile(const picojson::object& obj);
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Constants.h b/Source/Core/VideoCommon/GraphicsModSystem/Constants.h
new file mode 100644
index 0000000000..5c73c3e962
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Constants.h
@@ -0,0 +1,11 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "Common/CommonPaths.h"
+
+static const inline std::string DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR =
+ LOAD_DIR DIR_SEP GRAPHICSMOD_DIR DIR_SEP;
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> MoveAction::Create(const picojson::value& json_data)
+{
+ Common::Vec3 position_offset;
+ const auto& x = json_data.get("X");
+ if (x.is<double>())
+ {
+ position_offset.x = static_cast<float>(x.get<double>());
+ }
+
+ const auto& y = json_data.get("Y");
+ if (y.is<double>())
+ {
+ position_offset.y = static_cast<float>(y.get<double>());
+ }
+
+ const auto& z = json_data.get("Z");
+ if (z.is<double>())
+ {
+ position_offset.z = static_cast<float>(z.get<double>());
+ }
+ return std::make_unique<MoveAction>(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 <memory>
+
+#include <picojson.h>
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+
+class MoveAction final : public GraphicsModAction
+{
+public:
+ static std::unique_ptr<MoveAction> 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> ScaleAction::Create(const picojson::value& json_data)
+{
+ Common::Vec3 scale;
+ const auto& x = json_data.get("X");
+ if (x.is<double>())
+ {
+ scale.x = static_cast<float>(x.get<double>());
+ }
+
+ const auto& y = json_data.get("Y");
+ if (y.is<double>())
+ {
+ scale.y = static_cast<float>(y.get<double>());
+ }
+
+ const auto& z = json_data.get("Z");
+ if (z.is<double>())
+ {
+ scale.z = static_cast<float>(z.get<double>());
+ }
+ return std::make_unique<ScaleAction>(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 <memory>
+
+#include <picojson.h>
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+
+class ScaleAction final : public GraphicsModAction
+{
+public:
+ static std::unique_ptr<ScaleAction> 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<const u8*>(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<std::size_t>(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<GraphicsModAction> Create(std::string_view name, const picojson::value& json_data)
+{
+ if (name == "print")
+ {
+ return std::make_unique<PrintAction>();
+ }
+ else if (name == "skip")
+ {
+ return std::make_unique<SkipAction>();
+ }
+ 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 <memory>
+#include <string_view>
+
+#include <picojson.h>
+
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+
+namespace GraphicsModActionFactory
+{
+std::unique_ptr<GraphicsModAction> 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 <string>
+#include <string_view>
+#include <variant>
+
+#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<GraphicsModAction> 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<GraphicsModAction> m_action_impl;
+};
+
+const std::vector<GraphicsModAction*>&
+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<GraphicsModAction*>&
+GraphicsModManager::GetProjectionTextureActions(ProjectionType projection_type,
+ const std::string& texture_name) const
+{
+ const auto lookup = fmt::format("{}_{}", texture_name, static_cast<int>(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<GraphicsModAction*>&
+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<GraphicsModAction*>&
+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<GraphicsModAction*>& 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<GraphicsModAction*>& 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<std::string, std::vector<GraphicsTargetConfig>> 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<GraphicsModAction> {
+ auto action = GraphicsModActionFactory::Create(action_name, json_data);
+ if (action == nullptr)
+ {
+ return nullptr;
+ }
+ return std::make_unique<DecoratedAction>(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<int>(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 <list>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#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<GraphicsModAction*>& GetProjectionActions(ProjectionType projection_type) const;
+ const std::vector<GraphicsModAction*>&
+ GetProjectionTextureActions(ProjectionType projection_type,
+ const std::string& texture_name) const;
+ const std::vector<GraphicsModAction*>&
+ GetDrawStartedActions(const std::string& texture_name) const;
+ const std::vector<GraphicsModAction*>&
+ GetTextureLoadActions(const std::string& texture_name) const;
+ const std::vector<GraphicsModAction*>& GetEFBActions(const FBInfo& efb) const;
+ const std::vector<GraphicsModAction*>& GetXFBActions(const FBInfo& xfb) const;
+
+ void Load(const GraphicsModGroupConfig& config);
+
+ void EndOfFrame();
+
+private:
+ void Reset();
+
+ class DecoratedAction;
+
+ static inline const std::vector<GraphicsModAction*> m_default = {};
+ std::list<std::unique_ptr<GraphicsModAction>> m_actions;
+ std::unordered_map<ProjectionType, std::vector<GraphicsModAction*>>
+ m_projection_target_to_actions;
+ std::unordered_map<std::string, std::vector<GraphicsModAction*>>
+ m_projection_texture_target_to_actions;
+ std::unordered_map<std::string, std::vector<GraphicsModAction*>> m_draw_started_target_to_actions;
+ std::unordered_map<std::string, std::vector<GraphicsModAction*>> m_load_target_to_actions;
+ std::unordered_map<FBInfo, std::vector<GraphicsModAction*>, FBInfoHasher> m_efb_target_to_actions;
+ std::unordered_map<FBInfo, std::vector<GraphicsModAction*>, FBInfoHasher> m_xfb_target_to_actions;
+
+ std::unordered_set<std::string> m_groups;
+};