diff options
| author | JMC47 <JMC4789@gmail.com> | 2022-06-28 13:35:39 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-06-28 13:35:39 -0400 |
| commit | 5d04e1e1de8248da8366f6f99ff4cdbe5467c789 (patch) | |
| tree | 47ff1e96f099cef3443635b982d03e2bf1ce3f04 /Source/Core/VideoCommon | |
| parent | e50e45f400856191eafb5f0c6044a8efbf152321 (diff) | |
| parent | 8aef0015fd8903b92cf0aab8a98ee669ec97b5ef (diff) | |
Merge pull request #10518 from iwubcode/draw-mod
Introducing a 'GraphicsMod' system for creators
Diffstat (limited to 'Source/Core/VideoCommon')
42 files changed, 2078 insertions, 69 deletions
diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index ec8f89c829..b46ac98d77 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -39,6 +39,32 @@ add_library(videocommon GeometryShaderGen.h GeometryShaderManager.cpp GeometryShaderManager.h + GraphicsModSystem/Config/GraphicsMod.cpp + GraphicsModSystem/Config/GraphicsMod.h + GraphicsModSystem/Config/GraphicsModFeature.cpp + GraphicsModSystem/Config/GraphicsModFeature.h + GraphicsModSystem/Config/GraphicsModGroup.cpp + GraphicsModSystem/Config/GraphicsModGroup.h + GraphicsModSystem/Config/GraphicsTarget.cpp + GraphicsModSystem/Config/GraphicsTarget.h + 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/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; +}; diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index 85c9cfc238..9636bfe651 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -213,7 +213,7 @@ void HiresTexture::Prefetch() 10000); } -std::string HiresTexture::GenBaseName(TextureInfo& texture_info, bool dump) +std::string HiresTexture::GenBaseName(const TextureInfo& texture_info, bool dump) { if (!dump && s_textureMap.empty()) return ""; @@ -261,7 +261,7 @@ u32 HiresTexture::CalculateMipCount(u32 width, u32 height) return mip_count; } -std::shared_ptr<HiresTexture> HiresTexture::Search(TextureInfo& texture_info) +std::shared_ptr<HiresTexture> HiresTexture::Search(const TextureInfo& texture_info) { const std::string base_filename = GenBaseName(texture_info); @@ -433,17 +433,17 @@ std::set<std::string> GetTextureDirectoriesWithGameId(const std::string& root_di } } - const auto match_gameid = [game_id](const std::string& filename) { + const auto match_gameid_or_all = [game_id](const std::string& filename) { std::string basename; SplitPath(filename, nullptr, &basename, nullptr); - return basename == game_id || basename == game_id.substr(0, 3); + return basename == game_id || basename == game_id.substr(0, 3) || basename == "all"; }; // Look for any other directories that might be specific to the given gameid const auto files = Common::DoFileSearch({root_directory}, {".txt"}, true); for (const auto& file : files) { - if (match_gameid(file)) + if (match_gameid_or_all(file)) { // The following code is used to calculate the top directory // of a found gameid.txt file diff --git a/Source/Core/VideoCommon/HiresTextures.h b/Source/Core/VideoCommon/HiresTextures.h index 78e40460db..bcd889e18c 100644 --- a/Source/Core/VideoCommon/HiresTextures.h +++ b/Source/Core/VideoCommon/HiresTextures.h @@ -25,9 +25,9 @@ public: static void Clear(); static void Shutdown(); - static std::shared_ptr<HiresTexture> Search(TextureInfo& texture_info); + static std::shared_ptr<HiresTexture> Search(const TextureInfo& texture_info); - static std::string GenBaseName(TextureInfo& texture_info, bool dump = false); + static std::string GenBaseName(const TextureInfo& texture_info, bool dump = false); static u32 CalculateMipCount(u32 width, u32 height); diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index dbad9711f1..412461d409 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -67,6 +67,7 @@ #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/FramebufferShaderGen.h" #include "VideoCommon/FreeLookCamera.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" #include "VideoCommon/NetPlayChatUI.h" #include "VideoCommon/NetPlayGolfUI.h" #include "VideoCommon/OnScreenDisplay.h" @@ -135,6 +136,22 @@ bool Renderer::Initialize() return false; } + if (g_ActiveConfig.bGraphicMods) + { + // If a config change occurred in a previous session, + // remember the old change count value. By setting + // our current change count to the old value, we + // avoid loading the stale data when we + // check for config changes. + const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ? + g_ActiveConfig.graphics_mod_config->GetChangeCount() : + 0; + g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); + g_ActiveConfig.graphics_mod_config->Load(); + g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes); + m_graphics_mod_manager.Load(*g_ActiveConfig.graphics_mod_config); + } + return true; } @@ -465,12 +482,27 @@ void Renderer::CheckForConfigChanges() const bool old_force_filtering = g_ActiveConfig.bForceFiltering; const bool old_vsync = g_ActiveConfig.bVSyncActive; const bool old_bbox = g_ActiveConfig.bBBoxEnable; + const u32 old_game_mod_changes = + g_ActiveConfig.graphics_mod_config ? g_ActiveConfig.graphics_mod_config->GetChangeCount() : 0; + const bool old_graphics_mods_enabled = g_ActiveConfig.bGraphicMods; UpdateActiveConfig(); FreeLook::UpdateActiveConfig(); g_freelook_camera.SetControlType(FreeLook::GetActiveConfig().camera_config.control_type); + if (g_ActiveConfig.bGraphicMods && !old_graphics_mods_enabled) + { + g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); + g_ActiveConfig.graphics_mod_config->Load(); + } + + if (g_ActiveConfig.graphics_mod_config && + (old_game_mod_changes != g_ActiveConfig.graphics_mod_config->GetChangeCount())) + { + m_graphics_mod_manager.Load(*g_ActiveConfig.graphics_mod_config); + } + // Update texture cache settings with any changed options. g_texture_cache->OnConfigChanged(g_ActiveConfig); @@ -1309,6 +1341,11 @@ void Renderer::Swap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, u6 // behind the renderer. FlushFrameDump(); + if (g_ActiveConfig.bGraphicMods) + { + m_graphics_mod_manager.EndOfFrame(); + } + if (xfb_addr && fb_width && fb_stride && fb_height) { // Get the current XFB from texture cache @@ -1830,3 +1867,8 @@ std::unique_ptr<VideoCommon::AsyncShaderCompiler> Renderer::CreateAsyncShaderCom { return std::make_unique<VideoCommon::AsyncShaderCompiler>(); } + +const GraphicsModManager& Renderer::GetGraphicsModManager() const +{ + return m_graphics_mod_manager; +} diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 380225b133..403d028525 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -30,6 +30,7 @@ #include "VideoCommon/BPMemory.h" #include "VideoCommon/FPSCounter.h" #include "VideoCommon/FrameDump.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/TextureConfig.h" @@ -268,6 +269,8 @@ public: // Will forcibly reload all textures on the next swap void ForceReloadTextures(); + const GraphicsModManager& GetGraphicsModManager() const; + protected: // Bitmask containing information about which configuration has changed for the backend. enum ConfigChangeBits : u32 @@ -447,6 +450,8 @@ private: std::unique_ptr<NetPlayChatUI> m_netplay_chat_ui; Common::Flag m_force_reload_textures; + + GraphicsModManager m_graphics_mod_manager; }; extern std::unique_ptr<Renderer> g_renderer; diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 0ed5ffe014..9eb6e09792 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -36,6 +36,7 @@ #include "VideoCommon/AbstractStagingTexture.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h" #include "VideoCommon/HiresTextures.h" #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PixelShaderManager.h" @@ -55,6 +56,8 @@ static const u64 TEXHASH_INVALID = 0; static const int TEXTURE_KILL_THRESHOLD = 64; static const int TEXTURE_POOL_KILL_THRESHOLD = 3; +static int xfb_count = 0; + std::unique_ptr<TextureCacheBase> g_texture_cache; TextureCacheBase::TCacheEntry::TCacheEntry(std::unique_ptr<AbstractTexture> tex, @@ -153,6 +156,9 @@ void TextureCacheBase::OnConfigChanged(const VideoConfig& config) HiresTexture::Update(); } + const u32 change_count = + config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0; + // TODO: Invalidating texcache is really stupid in some of these cases if (config.iSafeTextureCache_ColorSamples != backup_config.color_samples || config.bTexFmtOverlayEnable != backup_config.texfmt_overlay || @@ -160,7 +166,9 @@ void TextureCacheBase::OnConfigChanged(const VideoConfig& config) config.bHiresTextures != backup_config.hires_textures || config.bEnableGPUTextureDecoding != backup_config.gpu_texture_decoding || config.bDisableCopyToVRAM != backup_config.disable_vram_copies || - config.bArbitraryMipmapDetection != backup_config.arbitrary_mipmap_detection) + config.bArbitraryMipmapDetection != backup_config.arbitrary_mipmap_detection || + config.bGraphicMods != backup_config.graphics_mods || + change_count != backup_config.graphics_mod_change_count) { Invalidate(); TexDecoder_SetTexFmtOverlayOptions(config.bTexFmtOverlayEnable, config.bTexFmtOverlayCenter); @@ -255,6 +263,9 @@ void TextureCacheBase::SetBackupConfig(const VideoConfig& config) backup_config.gpu_texture_decoding = config.bEnableGPUTextureDecoding; backup_config.disable_vram_copies = config.bDisableCopyToVRAM; backup_config.arbitrary_mipmap_detection = config.bArbitraryMipmapDetection; + backup_config.graphics_mods = config.bGraphicMods; + backup_config.graphics_mod_change_count = + config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0; } TextureCacheBase::TCacheEntry* @@ -1205,15 +1216,15 @@ private: std::vector<Level> levels; }; -TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage) +TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture_info) { // if this stage was not invalidated by changes to texture registers, keep the current texture - if (TMEM::IsValid(stage) && bound_textures[stage]) + if (TMEM::IsValid(texture_info.GetStage()) && bound_textures[texture_info.GetStage()]) { - TCacheEntry* entry = bound_textures[stage]; + TCacheEntry* entry = bound_textures[texture_info.GetStage()]; // If the TMEM configuration is such that this texture is more or less guaranteed to still // be in TMEM, then we know we can reuse the old entry without even hashing the memory - if (TMEM::IsCached(stage)) + if (TMEM::IsCached(texture_info.GetStage())) { return entry; } @@ -1226,26 +1237,29 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage) } } - TextureInfo texture_info = TextureInfo::FromStage(stage); - auto entry = GetTexture(g_ActiveConfig.iSafeTextureCache_ColorSamples, texture_info); if (!entry) return nullptr; entry->frameCount = FRAMECOUNT_INVALID; - bound_textures[stage] = entry; + if (entry->texture_info_name.empty() && g_ActiveConfig.bGraphicMods) + { + entry->texture_info_name = texture_info.CalculateTextureName().GetFullName(); + } + bound_textures[texture_info.GetStage()] = entry; // We need to keep track of invalided textures until they have actually been replaced or // re-loaded - TMEM::Bind(stage, entry->NumBlocksX(), entry->NumBlocksY(), entry->GetNumLevels() > 1, - entry->format == TextureFormat::RGBA8); + TMEM::Bind(texture_info.GetStage(), entry->NumBlocksX(), entry->NumBlocksY(), + entry->GetNumLevels() > 1, entry->format == TextureFormat::RGBA8); return entry; } TextureCacheBase::TCacheEntry* -TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, TextureInfo& texture_info) +TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, + const TextureInfo& texture_info) { u32 expanded_width = texture_info.GetExpandedWidth(); u32 expanded_height = texture_info.GetExpandedHeight(); @@ -1764,12 +1778,20 @@ TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, SETSTAT(g_stats.num_textures_alive, static_cast<int>(textures_by_address.size())); INCSTAT(g_stats.num_textures_uploaded); - if (g_ActiveConfig.bDumpXFBTarget) + if (g_ActiveConfig.bDumpXFBTarget || g_ActiveConfig.bGraphicMods) { - // While this isn't really an xfb copy, we can treat it as such for dumping purposes - static int xfb_count = 0; - entry->texture->Save( - fmt::format("{}xfb_loaded_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), xfb_count++), 0); + const std::string id = fmt::format("{}x{}", width, height); + if (g_ActiveConfig.bGraphicMods) + { + entry->texture_info_name = fmt::format("{}_{}", XFB_DUMP_PREFIX, id); + } + + if (g_ActiveConfig.bDumpXFBTarget) + { + entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), + XFB_DUMP_PREFIX, xfb_count++, id), + 0); + } } GetDisplayRectForXFBEntry(entry, width, height, display_rect); @@ -2119,6 +2141,35 @@ void TextureCacheBase::CopyRenderTargetToTexture( const u32 bytes_per_row = num_blocks_x * bytes_per_block; const u32 covered_range = num_blocks_y * dstStride; + if (g_ActiveConfig.bGraphicMods) + { + FBInfo info; + info.m_width = tex_w; + info.m_height = tex_h; + info.m_texture_format = baseFormat; + if (is_xfb_copy) + { + for (const auto action : g_renderer->GetGraphicsModManager().GetXFBActions(info)) + { + action->OnXFB(); + } + } + else + { + bool skip = false; + for (const auto action : g_renderer->GetGraphicsModManager().GetEFBActions(info)) + { + action->OnEFB(&skip, tex_w, tex_h, &scaled_tex_w, &scaled_tex_h); + } + if (skip == true) + { + if (copy_to_ram) + UninitializeEFBMemory(dst, dstStride, bytes_per_row, num_blocks_y); + return; + } + } + } + if (dstStride < bytes_per_row) { // This kind of efb copy results in a scrambled image. @@ -2168,20 +2219,38 @@ void TextureCacheBase::CopyRenderTargetToTexture( isIntensity, gamma, clamp_top, clamp_bottom, GetVRAMCopyFilterCoefficients(filter_coefficients)); - if (g_ActiveConfig.bDumpEFBTarget && !is_xfb_copy) + if (is_xfb_copy && (g_ActiveConfig.bDumpXFBTarget || g_ActiveConfig.bGraphicMods)) { - static int efb_count = 0; - entry->texture->Save( - fmt::format("{}efb_frame_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), efb_count++), - 0); - } + const std::string id = fmt::format("{}x{}", tex_w, tex_h); + if (g_ActiveConfig.bGraphicMods) + { + entry->texture_info_name = fmt::format("{}_{}", XFB_DUMP_PREFIX, id); + } - if (g_ActiveConfig.bDumpXFBTarget && is_xfb_copy) + if (g_ActiveConfig.bDumpXFBTarget) + { + entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", + File::GetUserPath(D_DUMPTEXTURES_IDX), XFB_DUMP_PREFIX, + xfb_count++, id), + 0); + } + } + else if (g_ActiveConfig.bDumpEFBTarget || g_ActiveConfig.bGraphicMods) { - static int xfb_count = 0; - entry->texture->Save( - fmt::format("{}xfb_copy_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), xfb_count++), - 0); + const std::string id = fmt::format("{}x{}_{}", tex_w, tex_h, static_cast<int>(baseFormat)); + if (g_ActiveConfig.bGraphicMods) + { + entry->texture_info_name = fmt::format("{}_{}", EFB_DUMP_PREFIX, id); + } + + if (g_ActiveConfig.bDumpEFBTarget) + { + static int efb_count = 0; + entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", + File::GetUserPath(D_DUMPTEXTURES_IDX), EFB_DUMP_PREFIX, + efb_count++, id), + 0); + } } } } @@ -2225,15 +2294,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( } else { - // Hack: Most games don't actually need the correct texture data in RAM - // and we can just keep a copy in VRAM. We zero the memory so we - // can check it hasn't changed before using our copy in VRAM. - u8* ptr = dst; - for (u32 i = 0; i < num_blocks_y; i++) - { - std::memset(ptr, 0, bytes_per_row); - ptr += dstStride; - } + UninitializeEFBMemory(dst, dstStride, bytes_per_row, num_blocks_y); } } @@ -2403,6 +2464,20 @@ void TextureCacheBase::ReleaseEFBCopyStagingTexture(std::unique_ptr<AbstractStag m_efb_copy_staging_texture_pool.push_back(std::move(tex)); } +void TextureCacheBase::UninitializeEFBMemory(u8* dst, u32 stride, u32 bytes_per_row, + u32 num_blocks_y) +{ + // Hack: Most games don't actually need the correct texture data in RAM + // and we can just keep a copy in VRAM. We zero the memory so we + // can check it hasn't changed before using our copy in VRAM. + u8* ptr = dst; + for (u32 i = 0; i < num_blocks_y; i++) + { + std::memset(ptr, 0, bytes_per_row); + ptr += stride; + } +} + void TextureCacheBase::UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_row, u32 num_blocks_y) { diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index daf7e292d1..287f3b840f 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -10,6 +10,7 @@ #include <memory> #include <optional> #include <string> +#include <string_view> #include <tuple> #include <unordered_map> #include <unordered_set> @@ -29,6 +30,9 @@ class AbstractStagingTexture; class PointerWrap; struct VideoConfig; +constexpr std::string_view EFB_DUMP_PREFIX = "efb1"; +constexpr std::string_view XFB_DUMP_PREFIX = "xfb1"; + struct TextureAndTLUTFormat { TextureAndTLUTFormat(TextureFormat texfmt_ = TextureFormat::I4, @@ -151,6 +155,8 @@ public: u32 pending_efb_copy_height = 0; bool pending_efb_copy_invalidated = false; + std::string texture_info_name = ""; + explicit TCacheEntry(std::unique_ptr<AbstractTexture> tex, std::unique_ptr<AbstractFramebuffer> fb); @@ -235,8 +241,9 @@ public: void Invalidate(); - TCacheEntry* Load(const u32 stage); - TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize, TextureInfo& texture_info); + TCacheEntry* Load(const TextureInfo& texture_info); + TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize, + const TextureInfo& texture_info); TCacheEntry* GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, MathUtil::Rectangle<int>* display_rect); @@ -327,6 +334,7 @@ private: TexAddrCache::iterator InvalidateTexture(TexAddrCache::iterator t_iter, bool discard_pending_efb_copy = false); + void UninitializeEFBMemory(u8* dst, u32 stride, u32 bytes_per_row, u32 num_blocks_y); void UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_row, u32 num_blocks_y); // Precomputing the coefficients for the previous, current, and next lines for the copy filter. @@ -369,6 +377,8 @@ private: bool gpu_texture_decoding; bool disable_vram_copies; bool arbitrary_mipmap_detection; + bool graphics_mods; + u32 graphics_mod_change_count; }; BackupConfig backup_config = {}; diff --git a/Source/Core/VideoCommon/TextureDecoder.h b/Source/Core/VideoCommon/TextureDecoder.h index a00e95d8f2..dcc7513d43 100644 --- a/Source/Core/VideoCommon/TextureDecoder.h +++ b/Source/Core/VideoCommon/TextureDecoder.h @@ -50,6 +50,11 @@ static inline bool IsColorIndexed(TextureFormat format) format == TextureFormat::C14X2; } +static inline bool IsValidTextureFormat(TextureFormat format) +{ + return format <= TextureFormat::RGBA8 || IsColorIndexed(format) || format == TextureFormat::CMPR; +} + // The EFB Copy pipeline looks like: // // 1. Read EFB -> 2. Select color/depth -> 3. Downscale (optional) diff --git a/Source/Core/VideoCommon/TextureInfo.cpp b/Source/Core/VideoCommon/TextureInfo.cpp index 696f5b904c..ff117c6926 100644 --- a/Source/Core/VideoCommon/TextureInfo.cpp +++ b/Source/Core/VideoCommon/TextureInfo.cpp @@ -39,20 +39,20 @@ TextureInfo TextureInfo::FromStage(u32 stage) if (from_tmem) { - return TextureInfo(&texMem[tmem_address_even], tlut_ptr, address, texture_format, tlut_format, - width, height, true, &texMem[tmem_address_odd], &texMem[tmem_address_even], - mip_count); + return TextureInfo(stage, &texMem[tmem_address_even], tlut_ptr, address, texture_format, + tlut_format, width, height, true, &texMem[tmem_address_odd], + &texMem[tmem_address_even], mip_count); } - return TextureInfo(Memory::GetPointer(address), tlut_ptr, address, texture_format, tlut_format, - width, height, false, nullptr, nullptr, mip_count); + return TextureInfo(stage, Memory::GetPointer(address), tlut_ptr, address, texture_format, + tlut_format, width, height, false, nullptr, nullptr, mip_count); } -TextureInfo::TextureInfo(const u8* ptr, const u8* tlut_ptr, u32 address, +TextureInfo::TextureInfo(u32 stage, const u8* ptr, const u8* tlut_ptr, u32 address, TextureFormat texture_format, TLUTFormat tlut_format, u32 width, u32 height, bool from_tmem, const u8* tmem_odd, const u8* tmem_even, std::optional<u32> mip_count) - : m_ptr(ptr), m_tlut_ptr(tlut_ptr), m_address(address), m_from_tmem(from_tmem), + : m_stage(stage), m_ptr(ptr), m_tlut_ptr(tlut_ptr), m_address(address), m_from_tmem(from_tmem), m_tmem_odd(tmem_odd), m_texture_format(texture_format), m_tlut_format(tlut_format), m_raw_width(width), m_raw_height(height) { @@ -100,7 +100,7 @@ std::string TextureInfo::NameDetails::GetFullName() const return fmt::format("{}_{}{}_{}", base_name, texture_name, tlut_name, format_name); } -TextureInfo::NameDetails TextureInfo::CalculateTextureName() +TextureInfo::NameDetails TextureInfo::CalculateTextureName() const { if (!m_ptr) return NameDetails{}; @@ -240,6 +240,11 @@ u32 TextureInfo::GetRawHeight() const return m_raw_height; } +u32 TextureInfo::GetStage() const +{ + return m_stage; +} + bool TextureInfo::HasMipMaps() const { return !m_mip_levels.empty(); diff --git a/Source/Core/VideoCommon/TextureInfo.h b/Source/Core/VideoCommon/TextureInfo.h index 109715ba45..940b09b3cb 100644 --- a/Source/Core/VideoCommon/TextureInfo.h +++ b/Source/Core/VideoCommon/TextureInfo.h @@ -17,9 +17,10 @@ class TextureInfo { public: static TextureInfo FromStage(u32 stage); - TextureInfo(const u8* ptr, const u8* tlut_ptr, u32 address, TextureFormat texture_format, - TLUTFormat tlut_format, u32 width, u32 height, bool from_tmem, const u8* tmem_odd, - const u8* tmem_even, std::optional<u32> mip_count); + TextureInfo(u32 stage, const u8* ptr, const u8* tlut_ptr, u32 address, + TextureFormat texture_format, TLUTFormat tlut_format, u32 width, u32 height, + bool from_tmem, const u8* tmem_odd, const u8* tmem_even, + std::optional<u32> mip_count); struct NameDetails { @@ -30,7 +31,7 @@ public: std::string GetFullName() const; }; - NameDetails CalculateTextureName(); + NameDetails CalculateTextureName() const; const u8* GetData() const; const u8* GetTlutAddress() const; @@ -55,6 +56,8 @@ public: u32 GetRawWidth() const; u32 GetRawHeight() const; + u32 GetStage() const; + class MipLevel { public: @@ -115,4 +118,6 @@ private: u32 m_block_height; u32 m_expanded_height; u32 m_raw_height; + + u32 m_stage; }; diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 72f02927b5..b3d6f86686 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -7,7 +7,6 @@ #include <cmath> #include <memory> -#include "Common/BitSet.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/EnumMap.h" @@ -30,6 +29,7 @@ #include "VideoCommon/RenderBase.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/TextureInfo.h" #include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/VideoBackendBase.h" @@ -337,7 +337,7 @@ bool VertexManagerBase::UploadTexelBuffer(const void* data, u32 data_size, Texel return false; } -void VertexManagerBase::LoadTextures() +BitSet32 VertexManagerBase::UsedTextures() const { BitSet32 usedtextures; for (u32 i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) @@ -349,10 +349,7 @@ void VertexManagerBase::LoadTextures() if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages) usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true; - for (unsigned int i : usedtextures) - g_texture_cache->Load(i); - - g_texture_cache->BindTextures(usedtextures); + return usedtextures; } void VertexManagerBase::Flush() @@ -455,7 +452,30 @@ void VertexManagerBase::Flush() CalculateBinormals(VertexLoaderManager::GetCurrentVertexFormat()); // Calculate ZSlope for zfreeze - VertexShaderManager::SetConstants(); + const auto used_textures = UsedTextures(); + std::vector<std::string> texture_names; + if (!m_cull_all) + { + if (!g_ActiveConfig.bGraphicMods) + { + for (const u32 i : used_textures) + { + g_texture_cache->Load(TextureInfo::FromStage(i)); + } + } + else + { + for (const u32 i : used_textures) + { + const auto cache_entry = g_texture_cache->Load(TextureInfo::FromStage(i)); + if (cache_entry) + { + texture_names.push_back(cache_entry->texture_info_name); + } + } + } + } + VertexShaderManager::SetConstants(texture_names); if (!bpmem.genMode.zfreeze) { // Must be done after VertexShaderManager::SetConstants() @@ -469,6 +489,18 @@ void VertexManagerBase::Flush() if (!m_cull_all) { + for (const auto& texture_name : texture_names) + { + bool skip = false; + for (const auto action : + g_renderer->GetGraphicsModManager().GetDrawStartedActions(texture_name)) + { + action->OnDrawStarted(&skip); + } + if (skip == true) + return; + } + // Now the vertices can be flushed to the GPU. Everything following the CommitBuffer() call // must be careful to not upload any utility vertices, as the binding will be lost otherwise. const u32 num_indices = m_index_generator.GetIndexLen(); @@ -480,7 +512,7 @@ void VertexManagerBase::Flush() // Texture loading can cause palettes to be applied (-> uniforms -> draws). // Palette application does not use vertices, only a full-screen quad, so this is okay. // Same with GPU texture decoding, which uses compute shaders. - LoadTextures(); + g_texture_cache->BindTextures(used_textures); // Now we can upload uniforms, as nothing else will override them. GeometryShaderManager::SetConstants(); diff --git a/Source/Core/VideoCommon/VertexManagerBase.h b/Source/Core/VideoCommon/VertexManagerBase.h index e4d029e9c7..7efdec2ebd 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.h +++ b/Source/Core/VideoCommon/VertexManagerBase.h @@ -6,6 +6,7 @@ #include <memory> #include <vector> +#include "Common/BitSet.h" #include "Common/CommonTypes.h" #include "Common/MathUtil.h" #include "VideoCommon/IndexGenerator.h" @@ -173,7 +174,8 @@ protected: void CalculateZSlope(NativeVertexFormat* format); void CalculateBinormals(NativeVertexFormat* format); - void LoadTextures(); + + BitSet32 UsedTextures() const; u8* m_cur_buffer_pointer = nullptr; u8* m_base_buffer_pointer = nullptr; diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index 6a7636d587..680d1ad3ee 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -85,7 +85,7 @@ void VertexShaderManager::Dirty() // Syncs the shader constant buffers with xfmem // TODO: A cleaner way to control the matrices without making a mess in the parameters field -void VertexShaderManager::SetConstants() +void VertexShaderManager::SetConstants(const std::vector<std::string>& textures) { if (constants.missing_color_hex != g_ActiveConfig.iMissingColorValue) { @@ -302,7 +302,27 @@ void VertexShaderManager::SetConstants() g_stats.AddScissorRect(); } - if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty()) + std::vector<GraphicsModAction*> projection_actions; + if (g_ActiveConfig.bGraphicMods) + { + for (const auto action : + g_renderer->GetGraphicsModManager().GetProjectionActions(xfmem.projection.type)) + { + projection_actions.push_back(action); + } + + for (const auto& texture : textures) + { + for (const auto action : g_renderer->GetGraphicsModManager().GetProjectionTextureActions( + xfmem.projection.type, texture)) + { + projection_actions.push_back(action); + } + } + } + + if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty() || + !projection_actions.empty()) { bProjectionChanged = false; @@ -384,6 +404,11 @@ void VertexShaderManager::SetConstants() if (g_freelook_camera.IsActive() && xfmem.projection.type == ProjectionType::Perspective) corrected_matrix *= g_freelook_camera.GetView(); + for (auto action : projection_actions) + { + action->OnProjection(&corrected_matrix); + } + memcpy(constants.projection.data(), corrected_matrix.data.data(), 4 * sizeof(float4)); g_freelook_camera.GetController()->SetClean(); diff --git a/Source/Core/VideoCommon/VertexShaderManager.h b/Source/Core/VideoCommon/VertexShaderManager.h index 90df8722ac..3bddf28fb0 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.h +++ b/Source/Core/VideoCommon/VertexShaderManager.h @@ -4,6 +4,7 @@ #pragma once #include <string> +#include <vector> #include "Common/CommonTypes.h" #include "VideoCommon/ConstantManager.h" @@ -19,7 +20,7 @@ public: static void DoState(PointerWrap& p); // constant management - static void SetConstants(); + static void SetConstants(const std::vector<std::string>& textures); static void InvalidateXFRange(int start, int end); static void SetTexMatrixChangedA(u32 value); diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 58893c9267..c1ff163f9c 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -145,6 +145,8 @@ void VideoConfig::Refresh() bFastTextureSampling = Config::Get(Config::GFX_HACK_FAST_TEXTURE_SAMPLING); bPerfQueriesEnable = Config::Get(Config::GFX_PERF_QUERIES_ENABLE); + + bGraphicMods = Config::Get(Config::GFX_MODS_ENABLE); } void VideoConfig::VerifyValidity() diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index f557f7bfe7..6ce7bed379 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -9,10 +9,12 @@ #pragma once +#include <optional> #include <string> #include <vector> #include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" #include "VideoCommon/VideoCommon.h" // Log in two categories, and save three other options in the same byte @@ -111,6 +113,8 @@ struct VideoConfig final bool bBorderlessFullscreen = false; bool bEnableGPUTextureDecoding = false; int iBitrateKbps = 0; + bool bGraphicMods = false; + std::optional<GraphicsModGroupConfig> graphics_mod_config; // Hacks bool bEFBAccessEnable = false; |
