From f79154b6f5841af20cf57af9b9ef1adaa7d246b1 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Tue, 9 Apr 2024 22:30:20 -0500 Subject: [PATCH] VideoCommon: graphics mods 2.0! --- .../GraphicsModSystem/Config/GraphicsMod.cpp | 479 ++++++++---------- .../GraphicsModSystem/Config/GraphicsMod.h | 54 +- .../Config/GraphicsModAction.cpp | 36 ++ .../Config/GraphicsModAction.h | 21 + .../Config/GraphicsModAsset.cpp | 25 +- .../Config/GraphicsModAsset.h | 9 +- .../Config/GraphicsModFeature.cpp | 55 -- .../Config/GraphicsModFeature.h | 21 - .../Config/GraphicsModGroup.cpp | 268 +++++++--- .../Config/GraphicsModGroup.h | 37 +- .../Config/GraphicsModHashPolicy.cpp | 117 +++++ .../Config/GraphicsModHashPolicy.h | 38 ++ .../Config/GraphicsModTag.cpp | 31 ++ .../GraphicsModSystem/Config/GraphicsModTag.h | 23 + .../Config/GraphicsTarget.cpp | 370 ++++---------- .../GraphicsModSystem/Config/GraphicsTarget.h | 54 +- .../Config/GraphicsTargetGroup.cpp | 101 ---- .../Config/GraphicsTargetGroup.h | 23 - .../VideoCommon/GraphicsModSystem/Constants.h | 6 + .../Runtime/GraphicsModBackend.cpp | 126 +++++ .../Runtime/GraphicsModBackend.h | 57 +++ .../Runtime/GraphicsModHash.cpp | 80 +++ .../Runtime/GraphicsModHash.h | 22 + .../Runtime/GraphicsModManager.cpp | 349 ++----------- .../Runtime/GraphicsModManager.h | 69 +-- .../Runtime/GraphicsModRuntimeBackend.cpp | 236 +++++++++ .../Runtime/GraphicsModRuntimeBackend.h | 72 +++ 27 files changed, 1511 insertions(+), 1268 deletions(-) create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h delete mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp delete mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h delete mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp delete mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp index ef71ba6eb8..a7b8860367 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp @@ -13,8 +13,9 @@ #include "VideoCommon/GraphicsModSystem/Constants.h" -std::optional GraphicsModConfig::Create(const std::string& file_path, - Source source) +namespace GraphicsModSystem::Config +{ +std::optional GraphicsMod::Create(const std::string& file_path) { picojson::value root; std::string error; @@ -25,186 +26,112 @@ std::optional GraphicsModConfig::Create(const std::string& fi return std::nullopt; } - GraphicsModConfig result; - if (!result.DeserializeFromConfig(root)) + GraphicsMod result; + if (!result.Deserialize(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::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); - } -} - -void GraphicsModConfig::SerializeToConfig(picojson::object& json_obj) const +void GraphicsMod::Serialize(picojson::object& json_obj) const { picojson::object serialized_metadata; + serialized_metadata.emplace("schema_version", static_cast(m_schema_version)); serialized_metadata.emplace("title", m_title); serialized_metadata.emplace("author", m_author); serialized_metadata.emplace("description", m_description); + serialized_metadata.emplace("mod_version", m_mod_version); json_obj.emplace("meta", std::move(serialized_metadata)); - picojson::array serialized_groups; - for (const auto& group : m_groups) - { - picojson::object serialized_group; - group.SerializeToConfig(serialized_group); - serialized_groups.emplace_back(std::move(serialized_group)); - } - json_obj.emplace("groups", std::move(serialized_groups)); - - picojson::array serialized_features; - for (const auto& feature : m_features) - { - picojson::object serialized_feature; - feature.SerializeToConfig(serialized_feature); - serialized_features.emplace_back(std::move(serialized_feature)); - } - json_obj.emplace("features", std::move(serialized_features)); - picojson::array serialized_assets; for (const auto& asset : m_assets) { picojson::object serialized_asset; - asset.SerializeToConfig(serialized_asset); + asset.Serialize(serialized_asset); serialized_assets.emplace_back(std::move(serialized_asset)); } json_obj.emplace("assets", std::move(serialized_assets)); + + picojson::array serialized_tags; + for (const auto& tag : m_tags) + { + picojson::object serialized_tag; + tag.Serialize(serialized_tag); + serialized_tags.emplace_back(std::move(serialized_tag)); + } + json_obj.emplace("tags", std::move(serialized_tags)); + + picojson::array serialized_targets; + for (const auto& target : m_targets) + { + picojson::object serialized_target; + SerializeTarget(serialized_target, target); + serialized_targets.emplace_back(std::move(serialized_target)); + } + json_obj.emplace("targets", std::move(serialized_targets)); + + picojson::array serialized_actions; + for (const auto& action : m_actions) + { + picojson::object serialized_action; + action.Serialize(serialized_action); + serialized_actions.emplace_back(std::move(serialized_action)); + } + json_obj.emplace("actions", std::move(serialized_actions)); + + picojson::object serialized_target_to_actions; + for (const auto& [target_index, action_indexes] : m_target_index_to_action_indexes) + { + picojson::array serialized_action_indexes; + for (const auto& action_index : action_indexes) + { + serialized_action_indexes.emplace_back(static_cast(action_index)); + } + serialized_target_to_actions.emplace(std::to_string(target_index), + std::move(serialized_action_indexes)); + } + json_obj.emplace("target_to_actions", serialized_target_to_actions); + + picojson::object serialized_tag_to_actions; + for (const auto& [tag_name, action_indexes] : m_tag_name_to_action_indexes) + { + picojson::array serialized_action_indexes; + for (const auto& action_index : action_indexes) + { + serialized_action_indexes.emplace_back(static_cast(action_index)); + } + serialized_tag_to_actions.emplace(tag_name, std::move(serialized_action_indexes)); + } + json_obj.emplace("tag_to_actions", serialized_tag_to_actions); + + picojson::object serialized_hash_policy; + serialized_hash_policy.emplace("attributes", + HashAttributesToString(m_default_hash_policy.attributes)); + json_obj.emplace("default_hash_policy", serialized_hash_policy); } -bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value) +bool GraphicsMod::Deserialize(const picojson::value& value) { const auto& meta = value.get("meta"); if (meta.is()) { - const auto& title = meta.get("title"); - if (title.is()) + const auto& meta_obj = meta.get(); + m_schema_version = ReadNumericFromJson(meta_obj, "schema_version").value_or(0); + if (m_schema_version != LATEST_SCHEMA_VERSION) { - m_title = title.to_str(); + // For now error, we can handle schema migrations in the future? + ERROR_LOG_FMT(VIDEO, + "Failed to deserialize graphics mod data, schema_version was '{}' but latest " + "version is '{}'", + m_schema_version, LATEST_SCHEMA_VERSION); + return false; } - const auto& author = meta.get("author"); - if (author.is()) - { - m_author = author.to_str(); - } - - const auto& description = meta.get("description"); - if (description.is()) - { - m_description = description.to_str(); - } - } - - const auto& groups = value.get("groups"); - if (groups.is()) - { - for (const auto& group_val : groups.get()) - { - if (!group_val.is()) - { - 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())) - { - return false; - } - - m_groups.push_back(std::move(group)); - } - } - - const auto& features = value.get("features"); - if (features.is()) - { - for (const auto& feature_val : features.get()) - { - if (!feature_val.is()) - { - 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())) - { - return false; - } - - m_features.push_back(std::move(feature)); - } + m_title = ReadStringFromJson(meta_obj, "title").value_or("Unknown Mod"); + m_author = ReadStringFromJson(meta_obj, "author").value_or("Unknown"); + m_description = ReadStringFromJson(meta_obj, "description").value_or(""); + m_mod_version = ReadStringFromJson(meta_obj, "mod_version").value_or("v0.0.0"); } const auto& assets = value.get("assets"); @@ -218,8 +145,8 @@ bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value) VIDEO, "Failed to load mod configuration file, specified asset is not a json object"); return false; } - GraphicsModAssetConfig asset; - if (!asset.DeserializeFromConfig(asset_val.get())) + GraphicsModAsset asset; + if (!asset.Deserialize(asset_val.get())) { return false; } @@ -228,105 +155,147 @@ bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value) } } + const auto& tags = value.get("tags"); + if (tags.is()) + { + for (const auto& tag_val : tags.get()) + { + if (!tag_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified tag is not a json object"); + return false; + } + GraphicsModTag tag; + if (!tag.Deserialize(tag_val.get())) + { + return false; + } + + m_tags.push_back(std::move(tag)); + } + } + + const auto& targets = value.get("targets"); + if (targets.is()) + { + for (const auto& target_val : targets.get()) + { + if (!target_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified target is not a json object"); + return false; + } + + AnyTarget target; + if (!DeserializeTarget(target_val.get(), target)) + return false; + + m_targets.push_back(std::move(target)); + } + } + + const auto& actions = value.get("actions"); + if (actions.is()) + { + for (const auto& action_val : actions.get()) + { + if (!action_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified action is not a json object"); + return false; + } + GraphicsModAction action; + if (!action.Deserialize(action_val.get())) + { + return false; + } + + m_actions.push_back(std::move(action)); + } + } + + const auto& target_to_actions = value.get("target_to_actions"); + if (target_to_actions.is()) + { + for (const auto& [key, action_indexes_val] : target_to_actions.get()) + { + u64 target_index = 0; + if (!TryParse(key, &target_index)) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified target index is not a number"); + return false; + } + + auto& action_indexes = m_target_index_to_action_indexes[target_index]; + if (!action_indexes_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified target index '{}' has " + "a non-array action index value", + target_index); + } + + for (const auto& action_index_val : action_indexes_val.get()) + { + if (!action_index_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified target index '{}' has " + "a non numeric action index", + target_index); + return false; + } + action_indexes.push_back(static_cast(action_index_val.get())); + } + } + } + + const auto& tag_to_actions = value.get("tag_to_actions"); + if (tag_to_actions.is()) + { + for (const auto& [tag, action_indexes_val] : tag_to_actions.get()) + { + auto& action_indexes = m_tag_name_to_action_indexes[tag]; + if (!action_indexes_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified tag '{}' has " + "a non-array action index value", + tag); + } + + for (const auto& action_index_val : action_indexes_val.get()) + { + if (!action_index_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified tag '{}' has " + "a non numeric action index", + tag); + return false; + } + action_indexes.push_back(static_cast(action_index_val.get())); + } + } + } + + m_default_hash_policy = GetDefaultHashPolicy(); + + const auto& default_hash_policy_json = value.get("default_hash_policy"); + if (default_hash_policy_json.is()) + { + auto& default_hash_policy_json_obj = default_hash_policy_json.get(); + const auto attributes = ReadStringFromJson(default_hash_policy_json_obj, "attributes"); + if (attributes) + { + m_default_hash_policy.attributes = HashAttributesFromString(*attributes); + } + } + return true; } - -void GraphicsModConfig::SerializeToProfile(picojson::object* obj) const -{ - if (!obj) - return; - - auto& json_obj = *obj; - switch (m_source) - { - case Source::User: - json_obj.emplace("source", "user"); - break; - case Source::System: - json_obj.emplace("source", "system"); - break; - } - - json_obj.emplace("path", m_relative_path); - - picojson::array serialized_groups; - for (const auto& group : m_groups) - { - picojson::object serialized_group; - group.SerializeToProfile(&serialized_group); - serialized_groups.emplace_back(std::move(serialized_group)); - } - json_obj.emplace("groups", std::move(serialized_groups)); - - picojson::array serialized_features; - for (const auto& feature : m_features) - { - picojson::object serialized_feature; - feature.SerializeToProfile(&serialized_feature); - serialized_features.emplace_back(std::move(serialized_feature)); - } - json_obj.emplace("features", std::move(serialized_features)); - - json_obj.emplace("enabled", m_enabled); - - json_obj.emplace("weight", static_cast(m_weight)); -} - -void GraphicsModConfig::DeserializeFromProfile(const picojson::object& obj) -{ - if (const auto it = obj.find("groups"); it != obj.end()) - { - if (it->second.is()) - { - const auto& serialized_groups = it->second.get(); - 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()) - { - const auto& serialized_group = serialized_group_val.get(); - m_groups[i].DeserializeFromProfile(serialized_group); - } - } - } - } - - if (const auto it = obj.find("features"); it != obj.end()) - { - if (it->second.is()) - { - const auto& serialized_features = it->second.get(); - 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()) - { - const auto& serialized_feature = serialized_feature_val.get(); - m_features[i].DeserializeFromProfile(serialized_feature); - } - } - } - } - - if (const auto it = obj.find("enabled"); it != obj.end()) - { - if (it->second.is()) - { - m_enabled = it->second.get(); - } - } - - if (const auto it = obj.find("weight"); it != obj.end()) - { - if (it->second.is()) - { - m_weight = static_cast(it->second.get()); - } - } -} +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h index f845c612ad..bc34cb2186 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h @@ -3,44 +3,48 @@ #pragma once +#include #include #include #include #include -#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h" +#include "Common/CommonTypes.h" -struct GraphicsModConfig +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" + +namespace GraphicsModSystem::Config { +// Update this version when the hashing approach or data +// changes +static constexpr u16 LATEST_SCHEMA_VERSION = 1; + +struct GraphicsMod +{ + u16 m_schema_version = LATEST_SCHEMA_VERSION; + 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; + std::string m_mod_version; - enum class Source - { - User, - System - }; - Source m_source = Source::User; + std::vector m_assets; + std::vector m_tags; + std::vector m_targets; + std::vector m_actions; + std::map> m_target_index_to_action_indexes; + std::map> m_tag_name_to_action_indexes; - std::vector m_groups; - std::vector m_features; - std::vector m_assets; + HashPolicy m_default_hash_policy; - static std::optional Create(const std::string& file, Source source); - static std::optional Create(const picojson::object* obj); + static std::optional Create(const std::string& file); - std::string GetAbsolutePath() const; - - void SerializeToConfig(picojson::object& json_obj) const; - bool DeserializeFromConfig(const picojson::value& value); - - void SerializeToProfile(picojson::object* value) const; - void DeserializeFromProfile(const picojson::object& value); + void Serialize(picojson::object& json_obj) const; + bool Deserialize(const picojson::value& value); }; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.cpp new file mode 100644 index 0000000000..971c7809b3 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.cpp @@ -0,0 +1,36 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h" + +#include "Common/JsonUtil.h" +#include "Common/Logging/Log.h" + +namespace GraphicsModSystem::Config +{ +void GraphicsModAction::Serialize(picojson::object& json_obj) const +{ + json_obj.emplace("factory_name", m_factory_name); + json_obj.emplace("data", m_data); +} + +bool GraphicsModAction::Deserialize(const picojson::object& obj) +{ + const auto factory_name = ReadStringFromJson(obj, "factory_name"); + if (!factory_name) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, specified action's factory_name is not valid"); + return false; + } + m_factory_name = *factory_name; + + if (auto data_iter = obj.find("data"); data_iter != obj.end()) + { + m_data = data_iter->second; + } + + return true; +} +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h new file mode 100644 index 0000000000..d4f8c9c965 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h @@ -0,0 +1,21 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +namespace GraphicsModSystem::Config +{ +struct GraphicsModAction +{ + std::string m_factory_name; + picojson::value m_data; + + void Serialize(picojson::object& json_obj) const; + bool Deserialize(const picojson::object& json_obj); +}; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp index 75840fcbf8..9d8f10fab9 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp @@ -3,12 +3,15 @@ #include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" +#include "Common/JsonUtil.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" -void GraphicsModAssetConfig::SerializeToConfig(picojson::object& json_obj) const +namespace GraphicsModSystem::Config { - json_obj.emplace("name", m_asset_id); +void GraphicsModAsset::Serialize(picojson::object& json_obj) const +{ + json_obj.emplace("id", m_asset_id); picojson::object serialized_data; for (const auto& [name, path] : m_map) @@ -18,21 +21,16 @@ void GraphicsModAssetConfig::SerializeToConfig(picojson::object& json_obj) const json_obj.emplace("data", std::move(serialized_data)); } -bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj) +bool GraphicsModAsset::Deserialize(const picojson::object& obj) { - auto name_iter = obj.find("name"); - if (name_iter == obj.end()) + const auto id = ReadStringFromJson(obj, "id"); + if (!id) { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has no name"); + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has an id " + "that is not valid"); return false; } - if (!name_iter->second.is()) - { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has a name " - "that is not a string"); - return false; - } - m_asset_id = name_iter->second.to_str(); + m_asset_id = *id; auto data_iter = obj.find("data"); if (data_iter == obj.end()) @@ -64,3 +62,4 @@ bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj) return true; } +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h index 53d8d71892..3203099804 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h @@ -9,11 +9,14 @@ #include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" -struct GraphicsModAssetConfig +namespace GraphicsModSystem::Config +{ +struct GraphicsModAsset { VideoCommon::CustomAssetLibrary::AssetID m_asset_id; VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map; - void SerializeToConfig(picojson::object& json_obj) const; - bool DeserializeFromConfig(const picojson::object& obj); + void Serialize(picojson::object& json_obj) const; + bool Deserialize(const picojson::object& json_obj); }; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp deleted file mode 100644 index 62dc68e54c..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h" - -#include "Common/Logging/Log.h" - -void GraphicsModFeatureConfig::SerializeToConfig(picojson::object& json_obj) const -{ - json_obj.emplace("group", m_group); - json_obj.emplace("action", m_action); - json_obj.emplace("action_data", m_action_data); -} - -bool GraphicsModFeatureConfig::DeserializeFromConfig(const picojson::object& obj) -{ - if (auto group_iter = obj.find("group"); group_iter != obj.end()) - { - if (!group_iter->second.is()) - { - 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(); - } - - if (auto action_iter = obj.find("action"); action_iter != obj.end()) - { - if (!action_iter->second.is()) - { - 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(); - } - - 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 deleted file mode 100644 index 5204eaef57..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include - -struct GraphicsModFeatureConfig -{ - std::string m_group; - std::string m_action; - picojson::value m_action_data; - - void SerializeToConfig(picojson::object& json_obj) const; - 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 index 3c234e9ec1..c1afdb5ccf 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "Common/CommonPaths.h" #include "Common/FileSearch.h" @@ -15,49 +16,184 @@ #include "Common/JsonUtil.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" +#include "Common/VariantUtil.h" #include "Core/ConfigManager.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" #include "VideoCommon/GraphicsModSystem/Constants.h" #include "VideoCommon/HiresTextures.h" -GraphicsModGroupConfig::GraphicsModGroupConfig(std::string game_id) : m_game_id(std::move(game_id)) +namespace GraphicsModSystem::Config +{ +GraphicsModGroup::GraphicsModGroup(std::string game_id) : m_game_id(std::move(game_id)) { } -GraphicsModGroupConfig::~GraphicsModGroupConfig() = default; - -GraphicsModGroupConfig::GraphicsModGroupConfig(const GraphicsModGroupConfig&) = default; - -GraphicsModGroupConfig::GraphicsModGroupConfig(GraphicsModGroupConfig&&) noexcept = default; - -GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(const GraphicsModGroupConfig&) = default; - -GraphicsModGroupConfig& -GraphicsModGroupConfig::operator=(GraphicsModGroupConfig&&) noexcept = default; - -void GraphicsModGroupConfig::Load() +void GraphicsModGroup::Load() { - const std::string file_path = GetPath(); + struct GraphicsModWithDir + { + Config::GraphicsMod m_mod; + std::string m_path; + }; + std::vector action_only_mods; + std::vector target_only_mods; + const auto try_add_mod = [&](const std::string& dir) { + auto file = dir + DIR_SEP + "metadata.json"; + UnifyPathSeparators(file); - std::set known_paths; - if (File::Exists(file_path)) + if (auto mod = GraphicsMod::Create(file)) + { + if (mod->m_actions.empty() && mod->m_targets.empty()) + return; + + // Actions can be empty for mods that just define + // targets for use by other mods + // These are mainly used by Dolphin default mods + if (mod->m_actions.empty()) + { + GraphicsModWithDir mod_with_dir; + mod_with_dir.m_mod = std::move(*mod); + mod_with_dir.m_path = dir; + target_only_mods.push_back(std::move(mod_with_dir)); + return; + } + + // Targets can be empty for mods that define some actions + // and a tag + if (mod->m_targets.empty()) + { + GraphicsModWithDir mod_with_dir; + mod_with_dir.m_mod = std::move(*mod); + mod_with_dir.m_path = dir; + action_only_mods.push_back(std::move(mod_with_dir)); + return; + } + + std::string file_data; + if (!File::ReadFileToString(file, file_data)) + return; + GraphicsModGroup::GraphicsModWithMetadata mod_with_metadata; + mod_with_metadata.m_path = dir; + mod_with_metadata.m_mod = std::move(*mod); + mod_with_metadata.m_id = XXH3_64bits(file_data.data(), file_data.size()); + m_graphics_mods.push_back(std::move(mod_with_metadata)); + } + }; + + const std::set 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); + } + + const std::set 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); + } + + // Now build some mods that are combination of multiple mods + // (typically used by Dolphin built-in mods) + for (const auto& action_only_mod_with_dir : action_only_mods) + { + // The way the action only mods interact with the target only mods is through tags + // If there are no tags, then no reason to care about this mod + if (action_only_mod_with_dir.m_mod.m_actions.empty() || + action_only_mod_with_dir.m_mod.m_tag_name_to_action_indexes.empty()) + { + continue; + } + + XXH3_state_t id_hash; + XXH3_INITSTATE(&id_hash); + XXH3_64bits_reset_withSeed(&id_hash, static_cast(1)); + + const auto action_only_file = action_only_mod_with_dir.m_path + DIR_SEP + "metadata.json"; + std::string action_only_file_data; + if (!File::ReadFileToString(action_only_file, action_only_file_data)) + continue; + XXH3_64bits_update(&id_hash, action_only_file_data.data(), action_only_file_data.size()); + + GraphicsMod combined_mod = action_only_mod_with_dir.m_mod; + + for (const auto& target_only_mod_with_dir : target_only_mods) + { + bool target_found = false; + for (const auto& target : target_only_mod_with_dir.m_mod.m_targets) + { + const auto contains_tag_name = [&](const GenericTarget& underlying_target) { + return std::any_of( + underlying_target.m_tag_names.begin(), underlying_target.m_tag_names.end(), + [&](const auto& tag_name) { + return action_only_mod_with_dir.m_mod.m_tag_name_to_action_indexes.contains( + tag_name); + }); + }; + + // Only be interested in this mod's target if the tag + // exists in the action mod's tag list + std::visit(overloaded{[&](const Config::IntTarget& int_target) { + if (contains_tag_name(int_target)) + { + combined_mod.m_targets.push_back(target); + target_found = true; + } + }, + [&](const Config::StringTarget& str_target) { + if (contains_tag_name(str_target)) + { + combined_mod.m_targets.push_back(target); + target_found = true; + } + }}, + target); + } + + if (target_found) + { + const auto file = target_only_mod_with_dir.m_path + DIR_SEP + "metadata.json"; + std::string target_file_data; + if (!File::ReadFileToString(file, target_file_data)) + continue; + XXH3_64bits_update(&id_hash, target_file_data.data(), target_file_data.size()); + } + } + + GraphicsModGroup::GraphicsModWithMetadata mod_with_metadata; + mod_with_metadata.m_path = action_only_mod_with_dir.m_path; + mod_with_metadata.m_mod = std::move(combined_mod); + mod_with_metadata.m_id = XXH3_64bits_digest(&id_hash); + m_graphics_mods.push_back(std::move(mod_with_metadata)); + } + + for (auto& mod : m_graphics_mods) + { + m_id_to_graphics_mod[mod.m_id] = &mod; + } + + const auto gameid_metadata = GetPath(); + if (File::Exists(gameid_metadata)) { picojson::value root; std::string error; - if (!JsonFromFile(file_path, &root, &error)) + if (!JsonFromFile(gameid_metadata, &root, &error)) { ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod group json file '{}' due to parse error: {}", - file_path, error); + gameid_metadata, error); return; } + if (!root.is()) { ERROR_LOG_FMT( VIDEO, "Failed to load graphics mod group json file '{}' due to root not being an object!", - file_path); + gameid_metadata); return; } @@ -69,106 +205,73 @@ void GraphicsModGroupConfig::Load() if (mod_json.is()) { const auto& mod_json_obj = mod_json.get(); - auto graphics_mod = GraphicsModConfig::Create(&mod_json_obj); - if (!graphics_mod) + const auto id_str = ReadStringFromJson(mod_json_obj, "id"); + if (!id_str) + continue; + u64 id; + if (!TryParse(*id_str, &id)) { 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(std::move(*graphics_mod)); + if (const auto iter = m_id_to_graphics_mod.find(id); iter != m_id_to_graphics_mod.end()) + { + iter->second->m_weight = ReadNumericFromJson(mod_json_obj, "weight").value_or(0); + iter->second->m_enabled = ReadBoolFromJson(mod_json_obj, "enabled").value_or(false); + } } } } } - 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.contains(file)) - return; - - if (auto mod = GraphicsModConfig::Create(file, source)) - m_graphics_mods.push_back(std::move(*mod)); - }; - - const std::set 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 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::ranges::sort(m_graphics_mods, {}, &GraphicsModConfig::m_weight); - for (auto& mod : m_graphics_mods) - { - m_path_to_graphics_mod[mod.GetAbsolutePath()] = &mod; - } + std::ranges::sort(m_graphics_mods, {}, &GraphicsModGroup::GraphicsModWithMetadata::m_weight); m_change_count++; } -void GraphicsModGroupConfig::Save() const +void GraphicsModGroup::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) + for (const auto& [id, mod_ptr] : m_id_to_graphics_mod) { picojson::object serialized_mod; - mod.SerializeToProfile(&serialized_mod); + serialized_mod.emplace("id", std::to_string(id)); + serialized_mod.emplace("enabled", mod_ptr->m_enabled); + serialized_mod.emplace("weight", static_cast(mod_ptr->m_weight)); serialized_mods.emplace_back(std::move(serialized_mod)); } serialized_root.emplace("mods", std::move(serialized_mods)); - const auto output = picojson::value{serialized_root}.serialize(true); - json_stream << output; + const auto file_path = GetPath(); + if (!JsonToFile(file_path, picojson::value{serialized_root}, true)) + { + ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path); + } } -void GraphicsModGroupConfig::SetChangeCount(u32 change_count) +void GraphicsModGroup::SetChangeCount(u32 change_count) { m_change_count = change_count; } -u32 GraphicsModGroupConfig::GetChangeCount() const +u32 GraphicsModGroup::GetChangeCount() const { return m_change_count; } -const std::vector& GraphicsModGroupConfig::GetMods() const +const std::vector& GraphicsModGroup::GetMods() const { return m_graphics_mods; } -std::vector& GraphicsModGroupConfig::GetMods() +std::vector& GraphicsModGroup::GetMods() { return m_graphics_mods; } -GraphicsModConfig* GraphicsModGroupConfig::GetMod(std::string_view absolute_path) const +GraphicsModGroup::GraphicsModWithMetadata* GraphicsModGroup::GetMod(u64 id) const { - if (const auto iter = m_path_to_graphics_mod.find(absolute_path); - iter != m_path_to_graphics_mod.end()) + if (const auto iter = m_id_to_graphics_mod.find(id); iter != m_id_to_graphics_mod.end()) { return iter->second; } @@ -176,13 +279,14 @@ GraphicsModConfig* GraphicsModGroupConfig::GetMod(std::string_view absolute_path return nullptr; } -const std::string& GraphicsModGroupConfig::GetGameID() const +const std::string& GraphicsModGroup::GetGameID() const { return m_game_id; } -std::string GraphicsModGroupConfig::GetPath() const +std::string GraphicsModGroup::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); } +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h index b2178db3f6..0e97a11cf2 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h @@ -5,24 +5,17 @@ #include #include -#include #include #include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" -struct GraphicsModConfig; - -class GraphicsModGroupConfig +namespace GraphicsModSystem::Config +{ +class GraphicsModGroup { public: - explicit GraphicsModGroupConfig(std::string game_id); - ~GraphicsModGroupConfig(); - - GraphicsModGroupConfig(const GraphicsModGroupConfig&); - GraphicsModGroupConfig(GraphicsModGroupConfig&&) noexcept; - - GraphicsModGroupConfig& operator=(const GraphicsModGroupConfig&); - GraphicsModGroupConfig& operator=(GraphicsModGroupConfig&&) noexcept; + explicit GraphicsModGroup(std::string game_id); void Load(); void Save() const; @@ -30,17 +23,27 @@ public: void SetChangeCount(u32 change_count); u32 GetChangeCount() const; - const std::vector& GetMods() const; - std::vector& GetMods(); + struct GraphicsModWithMetadata + { + GraphicsMod m_mod; + std::string m_path; + u64 m_id = 0; + bool m_enabled = false; + u16 m_weight = 0; + }; - GraphicsModConfig* GetMod(std::string_view absolute_path) const; + const std::vector& GetMods() const; + std::vector& GetMods(); + + GraphicsModWithMetadata* GetMod(u64 id) const; const std::string& GetGameID() const; private: std::string GetPath() const; std::string m_game_id; - std::vector m_graphics_mods; - std::map> m_path_to_graphics_mod; + std::vector m_graphics_mods; + std::map m_id_to_graphics_mod; u32 m_change_count = 0; }; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.cpp new file mode 100644 index 0000000000..2169ab89a9 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.cpp @@ -0,0 +1,117 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h" + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" + +namespace GraphicsModSystem::Config +{ +namespace +{ +bool ContainsAttribute(HashAttributes all_attributes, HashAttributes attribute) +{ + return static_cast>(all_attributes & attribute) != 0; +} + +HashAttributes GetDefaultHashAttributes() +{ + return HashAttributes::Blending | HashAttributes::Indices | HashAttributes::VertexLayout; +} +} // namespace + +HashAttributes operator|(HashAttributes lhs, HashAttributes rhs) +{ + return static_cast(static_cast>(lhs) | + static_cast>(rhs)); +} + +HashAttributes operator&(HashAttributes lhs, HashAttributes rhs) +{ + return static_cast(static_cast>(lhs) & + static_cast>(rhs)); +} + +HashPolicy GetDefaultHashPolicy() +{ + HashPolicy policy; + policy.attributes = GetDefaultHashAttributes(); + policy.first_texture_only = false; + policy.version = 1; + return policy; +} + +HashAttributes HashAttributesFromString(const std::string& str) +{ + if (str == "") + return GetDefaultHashAttributes(); + + HashAttributes attributes = static_cast(NoHashAttributes); + auto parts = SplitString(str, ','); + if (parts.empty()) + return GetDefaultHashAttributes(); + + for (auto& part : parts) + { + Common::ToLower(&part); + if (part == "blending") + { + attributes = attributes | HashAttributes::Blending; + } + else if (part == "projection") + { + attributes = attributes | HashAttributes::Projection; + } + else if (part == "vertex_position") + { + attributes = attributes | HashAttributes::VertexPosition; + } + else if (part == "vertex_texcoords") + { + attributes = attributes | HashAttributes::VertexTexCoords; + } + else if (part == "vertex_layout") + { + attributes = attributes | HashAttributes::VertexLayout; + } + else if (part == "indices") + { + attributes = attributes | HashAttributes::Indices; + } + } + if (static_cast(attributes) == NoHashAttributes) + return GetDefaultHashAttributes(); + return attributes; +} + +std::string HashAttributesToString(HashAttributes attributes) +{ + std::string result; + if (ContainsAttribute(attributes, HashAttributes::Blending)) + { + result += "blending,"; + } + if (ContainsAttribute(attributes, HashAttributes::Projection)) + { + result += "projection,"; + } + if (ContainsAttribute(attributes, HashAttributes::VertexPosition)) + { + result += "vertex_position,"; + } + if (ContainsAttribute(attributes, HashAttributes::VertexTexCoords)) + { + result += "vertex_texcoords,"; + } + if (ContainsAttribute(attributes, HashAttributes::VertexLayout)) + { + result += "vertex_layout,"; + } + if (ContainsAttribute(attributes, HashAttributes::Indices)) + { + result += "indices,"; + } + return result; +} +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h new file mode 100644 index 0000000000..005167974d --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h @@ -0,0 +1,38 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" + +namespace GraphicsModSystem::Config +{ +constexpr u16 NoHashAttributes = 0; +enum class HashAttributes : u16 +{ + Blending = 1u << 1, + Projection = 1u << 2, + VertexPosition = 1u << 3, + VertexTexCoords = 1u << 4, + VertexLayout = 1u << 5, + Indices = 1u << 6 +}; + +struct HashPolicy +{ + HashAttributes attributes; + bool first_texture_only; + u64 version; +}; + +HashAttributes operator|(HashAttributes lhs, HashAttributes rhs); +HashAttributes operator&(HashAttributes lhs, HashAttributes rhs); + +HashPolicy GetDefaultHashPolicy(); +HashAttributes HashAttributesFromString(const std::string& str); +std::string HashAttributesToString(HashAttributes attributes); + +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.cpp new file mode 100644 index 0000000000..c059fa3060 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.cpp @@ -0,0 +1,31 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h" + +#include "Common/JsonUtil.h" +#include "Common/Logging/Log.h" + +namespace GraphicsModSystem::Config +{ +void GraphicsModTag::Serialize(picojson::object& obj) const +{ + obj.emplace("name", m_name); + obj.emplace("description", m_description); +} + +bool GraphicsModTag::Deserialize(const picojson::object& obj) +{ + const auto name = ReadStringFromJson(obj, "name"); + if (!name) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified tag has an invalid name"); + return false; + } + m_name = *name; + m_description = ReadStringFromJson(obj, "description").value_or(""); + + return true; +} +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h new file mode 100644 index 0000000000..055db11f9a --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h @@ -0,0 +1,23 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include "Common/Matrix.h" + +namespace GraphicsModSystem::Config +{ +struct GraphicsModTag +{ + std::string m_name; + std::string m_description; + Common::Vec3 m_color; + + void Serialize(picojson::object& json_obj) const; + bool Deserialize(const picojson::object& json_obj); +}; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp index b9f5ef8f29..0cdf80360a 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp @@ -3,308 +3,134 @@ #include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" +#include "Common/JsonUtil.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "Common/VariantUtil.h" -#include "VideoCommon/TextureCacheBase.h" -namespace +namespace GraphicsModSystem::Config { -template , int> = 0> -std::optional 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()) - { - 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(); - 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(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(format); - - return fb; -} -std::optional 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()) - { - 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(); - - const auto handle_fb_texture = - [&texture_info](std::string_view type) -> std::optional { - const auto letter_n_pos = texture_info.find("_n"); - if (letter_n_pos == std::string::npos) - { - ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, value in 'texture_filename' " - "is {} without a count", - type); - return std::nullopt; - } - - const auto post_underscore = texture_info.find_first_of('_', letter_n_pos + 2); - if (post_underscore == std::string::npos) - return texture_info.erase(letter_n_pos, texture_info.size() - letter_n_pos); - else - return texture_info.erase(letter_n_pos, post_underscore - letter_n_pos); - }; - - if (texture_info.starts_with(EFB_DUMP_PREFIX)) - return handle_fb_texture("an efb"); - else if (texture_info.starts_with(XFB_DUMP_PREFIX)) - return handle_fb_texture("a xfb"); - return texture_info; -} -} // namespace - -void SerializeTargetToConfig(picojson::object& json_obj, const GraphicsTargetConfig& target) +void SerializeTarget(picojson::object& json_obj, const AnyTarget& target) { std::visit(overloaded{ - [&](const DrawStartedTextureTarget& the_target) { - json_obj.emplace("type", "draw_started"); - json_obj.emplace("texture_filename", the_target.m_texture_info_string); + [&](const IntTarget& the_target) { + json_obj.emplace("type", "int"); + json_obj.emplace("id", std::to_string(the_target.m_target_id)); + json_obj.emplace("name", the_target.m_name); + json_obj.emplace("tags", ToJsonArray(the_target.m_tag_names)); }, - [&](const LoadTextureTarget& the_target) { - json_obj.emplace("type", "load_texture"); - json_obj.emplace("texture_filename", the_target.m_texture_info_string); - }, - [&](const CreateTextureTarget& the_target) { - json_obj.emplace("type", "create_texture"); - json_obj.emplace("texture_filename", the_target.m_texture_info_string); - }, - [&](const EFBTarget& the_target) { - json_obj.emplace("type", "efb"); - json_obj.emplace("texture_filename", - fmt::format("{}_{}x{}_{}", EFB_DUMP_PREFIX, the_target.m_width, - the_target.m_height, - static_cast(the_target.m_texture_format))); - }, - [&](const XFBTarget& the_target) { - json_obj.emplace("type", "xfb"); - json_obj.emplace("texture_filename", - fmt::format("{}_{}x{}_{}", XFB_DUMP_PREFIX, the_target.m_width, - the_target.m_height, - static_cast(the_target.m_texture_format))); - }, - [&](const ProjectionTarget& the_target) { - const char* type_name = "3d"; - if (the_target.m_projection_type == ProjectionType::Orthographic) - type_name = "2d"; - - json_obj.emplace("type", type_name); - - if (the_target.m_texture_info_string) - { - json_obj.emplace("texture_filename", *the_target.m_texture_info_string); - } + [&](const StringTarget& the_target) { + json_obj.emplace("type", "string"); + json_obj.emplace("id", the_target.m_target_id); + json_obj.emplace("name", the_target.m_name); + json_obj.emplace("tags", ToJsonArray(the_target.m_tag_names)); }, }, target); } - -std::optional DeserializeTargetFromConfig(const picojson::object& obj) +bool DeserializeTarget(const picojson::object& json_obj, AnyTarget& target) { - 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()) + const auto type = ReadStringFromJson(json_obj, "type"); + if (!type) { ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, option 'type' is not a string type"); - return std::nullopt; + "Failed to load mod configuration file, option 'type' was missing or invalid"); + return false; } - const std::string& type = type_iter->second.get(); - if (type == "draw_started") - { - std::optional 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") + if (*type == "int") { - std::optional texture_info = ExtractTextureFilenameForConfig(obj); - if (!texture_info.has_value()) - return std::nullopt; + IntTarget i_target; + i_target.m_name = ReadStringFromJson(json_obj, "name").value_or(""); - LoadTextureTarget target; - target.m_texture_info_string = texture_info.value(); - return target; - } - else if (type == "create_texture") - { - std::optional texture_info = ExtractTextureFilenameForConfig(obj); - if (!texture_info.has_value()) - return std::nullopt; - - CreateTextureTarget target; - target.m_texture_info_string = texture_info.value(); - return target; - } - else if (type == "efb") - { - return DeserializeFBTargetFromConfig(obj, EFB_DUMP_PREFIX); - } - else if (type == "xfb") - { - return DeserializeFBTargetFromConfig(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 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()) + const std::string target_id_str = ReadStringFromJson(json_obj, "id").value_or("0"); + u64 target_id = 0; + if (!TryParse(target_id_str, &target_id)) { ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, option 'value' is not a string type"); - return std::nullopt; + "Failed to load graphics mod configuration file, option 'id' is invalid"); + return false; } - const auto& value_str = value_iter->second.get(); - if (value_str == "2d") + i_target.m_target_id = target_id; + if (i_target.m_target_id == 0) { - target.m_projection_type = ProjectionType::Orthographic; + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod configuration file, option 'id' is invalid"); + return false; } - else if (value_str == "3d") + if (const auto tags_iter = json_obj.find("tags"); tags_iter != json_obj.end()) { - target.m_projection_type = ProjectionType::Perspective; + if (!tags_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod configuration file, option 'tags' is not an array type"); + return false; + } + + const auto& tags_json = tags_iter->second.get(); + if (!std::all_of(tags_json.begin(), tags_json.end(), + [](const picojson::value& value) { return value.is(); })) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod configuration file, all tags are not strings"); + return false; + } + + for (const auto& tag_json : tags_json) + { + i_target.m_tag_names.push_back(tag_json.to_str()); + } } - else + target = i_target; + } + else if (*type == "string") + { + StringTarget s_target; + s_target.m_name = ReadStringFromJson(json_obj, "name").value_or(""); + + const auto id = ReadStringFromJson(json_obj, "id"); + if (!id) { - 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; + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod configuration file, option 'id' is invalid"); + return false; } - return target; + s_target.m_target_id = *id; + if (const auto tags_iter = json_obj.find("tags"); tags_iter != json_obj.end()) + { + if (!tags_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod configuration file, option 'tags' is not an array type"); + return false; + } + + const auto& tags_json = tags_iter->second.get(); + if (!std::all_of(tags_json.begin(), tags_json.end(), + [](const picojson::value& value) { return value.is(); })) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod configuration file, all tags are not strings"); + return false; + } + + for (const auto& tag_json : tags_json) + { + s_target.m_tag_names.push_back(tag_json.to_str()); + } + } + target = s_target; } else { - ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, option 'type' is not a valid value"); + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod configuration file, option 'type' is invalid value '{}'", + *type); + return false; } - 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 + return true; } +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h index 740da00727..ce9405732f 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h @@ -3,60 +3,34 @@ #pragma once -#include #include #include +#include #include #include "Common/CommonTypes.h" -#include "VideoCommon/TextureDecoder.h" -#include "VideoCommon/XFMemory.h" -struct TextureTarget +namespace GraphicsModSystem::Config { - std::string m_texture_info_string; +struct GenericTarget +{ + std::string m_name; + std::vector m_tag_names; }; -struct DrawStartedTextureTarget final : public TextureTarget +struct StringTarget final : public GenericTarget { + std::string m_target_id; }; -struct LoadTextureTarget final : public TextureTarget +struct IntTarget final : public GenericTarget { + u64 m_target_id; }; -struct CreateTextureTarget final : public TextureTarget -{ -}; +using AnyTarget = std::variant; -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 m_texture_info_string; - ProjectionType m_projection_type = ProjectionType::Perspective; -}; - -using GraphicsTargetConfig = - std::variant; - -void SerializeTargetToConfig(picojson::object& json_obj, const GraphicsTargetConfig& target); -std::optional DeserializeTargetFromConfig(const picojson::object& obj); - -void SerializeTargetToProfile(picojson::object* obj, const GraphicsTargetConfig& target); -void DeserializeTargetFromProfile(const picojson::object& obj, GraphicsTargetConfig* target); +void SerializeTarget(picojson::object& json_obj, const AnyTarget& target); +bool DeserializeTarget(const picojson::object& json_obj, AnyTarget& target); +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp deleted file mode 100644 index 3ac1b108cd..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h" - -#include "Common/Logging/Log.h" - -void GraphicsTargetGroupConfig::SerializeToConfig(picojson::object& json_obj) const -{ - picojson::array serialized_targets; - for (const auto& target : m_targets) - { - picojson::object serialized_target; - SerializeTargetToConfig(serialized_target, target); - serialized_targets.emplace_back(std::move(serialized_target)); - } - json_obj.emplace("targets", std::move(serialized_targets)); - json_obj.emplace("name", m_name); -} - -bool GraphicsTargetGroupConfig::DeserializeFromConfig(const picojson::object& obj) -{ - if (auto name_iter = obj.find("name"); name_iter != obj.end()) - { - if (!name_iter->second.is()) - { - 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(); - } - - if (auto targets_iter = obj.find("targets"); targets_iter != obj.end()) - { - if (!targets_iter->second.is()) - { - 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()) - { - if (!target_val.is()) - { - 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()); - 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.emplace_back(std::move(serialized_target)); - } - json_obj.emplace("targets", std::move(serialized_targets)); -} - -void GraphicsTargetGroupConfig::DeserializeFromProfile(const picojson::object& obj) -{ - if (const auto it = obj.find("targets"); it != obj.end()) - { - if (it->second.is()) - { - const auto& serialized_targets = it->second.get(); - 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()) - { - const auto& serialized_target = serialized_target_val.get(); - DeserializeTargetFromProfile(serialized_target, &m_targets[i]); - } - } - } - } -} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h deleted file mode 100644 index d0034ef30b..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include - -#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" - -struct GraphicsTargetGroupConfig -{ - std::string m_name; - std::vector m_targets; - - void SerializeToConfig(picojson::object& json_obj) const; - 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 index 5c73c3e962..3828550798 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Constants.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Constants.h @@ -4,8 +4,14 @@ #pragma once #include +#include #include "Common/CommonPaths.h" static const inline std::string DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR = LOAD_DIR DIR_SEP GRAPHICSMOD_DIR DIR_SEP; + +namespace GraphicsModSystem +{ +static constexpr std::string_view internal_tag_prefix = "__internal_"; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.cpp new file mode 100644 index 0000000000..d935d6867e --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.cpp @@ -0,0 +1,126 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" + +#include "Common/SmallVector.h" +#include "Core/System.h" + +#include "VideoCommon/GXPipelineTypes.h" +#include "VideoCommon/GeometryShaderManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/PixelShaderManager.h" +#include "VideoCommon/VertexLoaderManager.h" +#include "VideoCommon/VertexShaderManager.h" +#include "VideoCommon/XFMemory.h" + +namespace GraphicsModSystem::Runtime +{ +namespace +{ +bool IsDrawGPUSkinned(NativeVertexFormat* format, PrimitiveType primitive_type) +{ + if (primitive_type != PrimitiveType::Triangles && primitive_type != PrimitiveType::TriangleStrip) + { + return false; + } + + const PortableVertexDeclaration vert_decl = format->GetVertexDeclaration(); + return vert_decl.posmtx.enable; +} +} // namespace + +void GraphicsModBackend::OnTextureCreate(const TextureView& texture) +{ + if (texture.texture_type == TextureType::XFB) + { + auto& system = Core::System::GetInstance(); + auto& custom_resource_manager = system.GetCustomResourceManager(); + custom_resource_manager.XFBTriggered(texture.hash_name); + } +} + +void GraphicsModBackend::OnFramePresented(const PresentInfo& present_info) +{ + auto& system = Core::System::GetInstance(); + auto& custom_resource_manager = system.GetCustomResourceManager(); + custom_resource_manager.FramePresented(present_info); + m_camera_manager.FrameUpdate(present_info.frame_count); +} + +void GraphicsModBackend::SetHostConfig(const ShaderHostConfig& config) +{ + m_shader_host_config.bits = config.bits; + + auto& system = Core::System::GetInstance(); + auto& custom_resource_manager = system.GetCustomResourceManager(); + custom_resource_manager.SetHostConfig(config); +} + +void GraphicsModBackend::ClearAdditionalCameras(const MathUtil::Rectangle& rc, + bool color_enable, bool alpha_enable, bool z_enable, + u32 color, u32 z) +{ + m_camera_manager.ClearCameraRenderTargets(rc, color_enable, alpha_enable, z_enable, color, z); +} + +void GraphicsModBackend::CustomDraw(const DrawDataView& draw_data, + VertexManagerBase* vertex_manager, + std::span actions, DrawCallID draw_call) +{ + bool skip = false; + std::optional custom_transform; + GraphicsModSystem::MaterialResource* material_resource = nullptr; + GraphicsModSystem::MeshResource* mesh_resource = nullptr; + bool ignore_mesh_transform = false; + std::optional camera; + GraphicsModActionData::DrawStarted draw_started{draw_data, + VertexLoaderManager::g_current_components, + &skip, + &material_resource, + &mesh_resource, + &ignore_mesh_transform, + &custom_transform, + &camera}; + + static Common::Matrix44 identity = Common::Matrix44::Identity(); + + for (const auto& action : actions) + { + action->OnDrawStarted(&draw_started); + if (camera) + { + m_camera_manager.EnsureCamera(draw_call, *camera); + } + if (mesh_resource) + { + vertex_manager->DrawCustomMesh(mesh_resource, draw_data, custom_transform.value_or(identity), + ignore_mesh_transform, m_camera_manager); + return; + } + } + + if (!skip) + { + vertex_manager->DrawEmulatedMesh(material_resource, draw_data, + custom_transform.value_or(identity), m_camera_manager); + } +} + +DrawCallID GraphicsModBackend::GetSkinnedDrawCallID(DrawCallID draw_call_id, MaterialID material_id, + const DrawDataView& draw_data) +{ + const bool is_draw_gpu_skinned = + IsDrawGPUSkinned(draw_data.vertex_format, draw_data.uid->rasterization_state.primitive); + if (is_draw_gpu_skinned && m_last_draw_gpu_skinned && m_last_material_id == material_id) + { + draw_call_id = m_last_draw_call_id; + } + m_last_draw_call_id = draw_call_id; + m_last_material_id = material_id; + m_last_draw_gpu_skinned = is_draw_gpu_skinned; + + return draw_call_id; +} +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h new file mode 100644 index 0000000000..9b4ff09092 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h @@ -0,0 +1,57 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CameraManager.h" +#include "VideoCommon/GraphicsModSystem/Types.h" +#include "VideoCommon/OpcodeDecoding.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoEvents.h" + +class GraphicsModAction; +namespace GraphicsModSystem::Runtime +{ +class GraphicsModBackend +{ +public: + virtual ~GraphicsModBackend() = default; + + virtual void OnDraw(const DrawDataView& draw_started, VertexManagerBase* vertex_manager) = 0; + virtual void OnTextureUnload(TextureType texture_type, std::string_view texture_hash) = 0; + virtual void OnTextureLoad(const TextureView& texture) = 0; + virtual void OnTextureCreate(const TextureView& texture) = 0; + virtual void OnLight() = 0; + virtual void OnFramePresented(const PresentInfo& present_info); + + virtual void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) = 0; + virtual void ResetIndices() = 0; + + void SetHostConfig(const ShaderHostConfig& config); + + void ClearAdditionalCameras(const MathUtil::Rectangle& rc, bool color_enable, + bool alpha_enable, bool z_enable, u32 color, u32 z); + +protected: + void CustomDraw(const DrawDataView& draw_data, VertexManagerBase* vertex_manager, + std::span actions, DrawCallID draw_call); + + // This will ensure gpu skinned draw calls are joined together + DrawCallID GetSkinnedDrawCallID(DrawCallID draw_call_id, MaterialID material_id, + const DrawDataView& draw_data); + + ShaderHostConfig m_shader_host_config; + + VideoCommon::CameraManager m_camera_manager; + +private: + bool m_last_draw_gpu_skinned = false; + GraphicsModSystem::DrawCallID m_last_draw_call_id = GraphicsModSystem::DrawCallID::INVALID; + GraphicsModSystem::MaterialID m_last_material_id = GraphicsModSystem::MaterialID::INVALID; +}; +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.cpp new file mode 100644 index 0000000000..eee63f2abf --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.cpp @@ -0,0 +1,80 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h" + +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/XFMemory.h" + +namespace GraphicsModSystem::Runtime +{ +HashOutput GetDrawDataHash(const Config::HashPolicy& hash_policy, const DrawDataView& draw_data) +{ + HashOutput output; + + XXH3_state_t draw_hash_state; + XXH3_INITSTATE(&draw_hash_state); + XXH3_64bits_reset_withSeed(&draw_hash_state, static_cast(1)); + + XXH3_state_t material_hash_state; + XXH3_INITSTATE(&material_hash_state); + XXH3_64bits_reset_withSeed(&material_hash_state, static_cast(1)); + + if ((hash_policy.attributes & Config::HashAttributes::Projection) == + Config::HashAttributes::Projection) + { + XXH3_64bits_update(&draw_hash_state, &xfmem.projection.type, sizeof(ProjectionType)); + } + + if ((hash_policy.attributes & Config::HashAttributes::Blending) == + Config::HashAttributes::Blending) + { + XXH3_64bits_update(&draw_hash_state, &bpmem.blendmode, sizeof(BlendMode)); + } + + if ((hash_policy.attributes & Config::HashAttributes::Indices) == Config::HashAttributes::Indices) + { + XXH3_64bits_update(&draw_hash_state, draw_data.index_data.data(), draw_data.index_data.size()); + } + + if ((hash_policy.attributes & Config::HashAttributes::VertexLayout) == + Config::HashAttributes::VertexLayout) + { + const u32 vertex_count = static_cast(draw_data.vertex_data.size()); + XXH3_64bits_update(&draw_hash_state, &vertex_count, sizeof(u32)); + } + + if (hash_policy.first_texture_only) + { + auto& first_texture = draw_data.textures[0]; + XXH3_64bits_update(&draw_hash_state, first_texture.hash_name.data(), + first_texture.hash_name.size()); + XXH3_64bits_update(&material_hash_state, first_texture.hash_name.data(), + first_texture.hash_name.size()); + } + else + { + std::set texture_hashes; + for (const auto& texture : draw_data.textures) + { + texture_hashes.insert(texture.hash_name); + } + + for (const auto& texture_hash : texture_hashes) + { + XXH3_64bits_update(&draw_hash_state, texture_hash.data(), texture_hash.size()); + XXH3_64bits_update(&material_hash_state, texture_hash.data(), texture_hash.size()); + } + } + + output.draw_call_id = static_cast(XXH3_64bits_digest(&draw_hash_state)); + output.material_id = static_cast(XXH3_64bits_digest(&material_hash_state)); + return output; +} +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h new file mode 100644 index 0000000000..60d744e9d3 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h @@ -0,0 +1,22 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModSystem +{ +struct DrawDataView; +} +namespace GraphicsModSystem::Runtime +{ +struct HashOutput +{ + DrawCallID draw_call_id; + MaterialID material_id; +}; + +HashOutput GetDrawDataHash(const Config::HashPolicy& hash_policy, const DrawDataView& draw_data); +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp index 0d070a4310..5490fab921 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp @@ -3,80 +3,16 @@ #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" -#include -#include -#include - -#include "Common/Logging/Log.h" -#include "Common/StringUtil.h" -#include "Common/VariantUtil.h" - #include "Core/ConfigManager.h" -#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" #include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" -#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" -#include "VideoCommon/TextureInfo.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h" #include "VideoCommon/VideoConfig.h" -std::unique_ptr g_graphics_mod_manager; - -class GraphicsModManager::DecoratedAction final : public GraphicsModAction +namespace GraphicsModSystem::Runtime { -public: - DecoratedAction(std::unique_ptr action, GraphicsModConfig mod) - : m_action_impl(std::move(action)), m_mod(std::move(mod)) - { - } - void OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnDrawStarted(draw_started); - } - void OnEFB(GraphicsModActionData::EFB* efb) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnEFB(efb); - } - void OnProjection(GraphicsModActionData::Projection* projection) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnProjection(projection); - } - void OnProjectionAndTexture(GraphicsModActionData::Projection* projection) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnProjectionAndTexture(projection); - } - void OnTextureLoad(GraphicsModActionData::TextureLoad* texture_load) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnTextureLoad(texture_load); - } - void OnTextureCreate(GraphicsModActionData::TextureCreate* texture_create) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnTextureCreate(texture_create); - } - void OnFrameEnd() override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnFrameEnd(); - } - -private: - std::unique_ptr m_action_impl; - GraphicsModConfig m_mod; -}; +GraphicsModManager::GraphicsModManager() = default; +GraphicsModManager::~GraphicsModManager() = default; bool GraphicsModManager::Initialize() { @@ -90,281 +26,64 @@ bool GraphicsModManager::Initialize() 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 = + Config::GraphicsModGroup(SConfig::GetInstance().GetGameID()); g_ActiveConfig.graphics_mod_config->Load(); g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes); - g_graphics_mod_manager->Load(*g_ActiveConfig.graphics_mod_config); + Load(*g_ActiveConfig.graphics_mod_config); - m_end_of_frame_event = - AfterFrameEvent::Register([this](Core::System&) { EndOfFrame(); }, "ModManager"); + m_frame_present_event = AfterPresentEvent::Register( + [this](const PresentInfo& present_info) { OnFramePresented(present_info); }, "ModManager"); } return true; } -const std::vector& -GraphicsModManager::GetProjectionActions(ProjectionType projection_type) const +void GraphicsModManager::Load(const Config::GraphicsModGroup& config) { - 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; + m_backend = std::make_unique(config); } -const std::vector& -GraphicsModManager::GetProjectionTextureActions(ProjectionType projection_type, - const std::string& texture_name) const +const GraphicsModBackend& GraphicsModManager::GetBackend() const { - const auto lookup = fmt::format("{}_{}", texture_name, static_cast(projection_type)); - if (const auto it = m_projection_texture_target_to_actions.find(lookup); - it != m_projection_texture_target_to_actions.end()) - { - return it->second; - } + if (m_editor_backend) + return *m_editor_backend; - return m_default; + return *m_backend; } -const std::vector& -GraphicsModManager::GetDrawStartedActions(const std::string& texture_name) const +GraphicsModBackend& GraphicsModManager::GetBackend() { - if (const auto it = m_draw_started_target_to_actions.find(texture_name); - it != m_draw_started_target_to_actions.end()) - { - return it->second; - } + if (m_editor_backend) + return *m_editor_backend; - return m_default; + return *m_backend; } -const std::vector& -GraphicsModManager::GetTextureLoadActions(const std::string& texture_name) const +void GraphicsModManager::SetEditorBackend(std::unique_ptr editor_backend) { - if (const auto it = m_load_texture_target_to_actions.find(texture_name); - it != m_load_texture_target_to_actions.end()) - { - return it->second; - } - - return m_default; + m_editor_backend = std::move(editor_backend); } -const std::vector& -GraphicsModManager::GetTextureCreateActions(const std::string& texture_name) const +bool GraphicsModManager::IsEditorEnabled() const { - if (const auto it = m_create_texture_target_to_actions.find(texture_name); - it != m_create_texture_target_to_actions.end()) - { - return it->second; - } - - return m_default; + return true; + // return m_editor_backend.get() != nullptr; } -const std::vector& GraphicsModManager::GetEFBActions(const FBInfo& efb) const +void GraphicsModManager::OnFramePresented(const PresentInfo& present_info) { - if (const auto it = m_efb_target_to_actions.find(efb); it != m_efb_target_to_actions.end()) + // Ignore duplicates + if (present_info.reason == PresentInfo::PresentReason::VideoInterfaceDuplicate) + return; + + if (m_editor_backend) { - return it->second; + m_editor_backend->OnFramePresented(present_info); } - - return m_default; -} - -const std::vector& GraphicsModManager::GetXFBActions(const FBInfo& xfb) const -{ - if (const auto it = m_efb_target_to_actions.find(xfb); it != m_efb_target_to_actions.end()) + else { - return it->second; - } - - return m_default; -} - -void GraphicsModManager::Load(const GraphicsModGroupConfig& config) -{ - Reset(); - - const auto& mods = config.GetMods(); - - auto filesystem_library = std::make_shared(); - - std::map> group_to_targets; - for (const auto& mod : mods) - { - for (const GraphicsTargetGroupConfig& group : mod.m_groups) - { - if (m_groups.contains(group.m_name)) - { - 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); - } - } - - std::string base_path; - SplitPath(mod.GetAbsolutePath(), &base_path, nullptr, nullptr); - for (const GraphicsModAssetConfig& asset : mod.m_assets) - { - auto asset_map = asset.m_map; - for (auto& [k, v] : asset_map) - { - if (v.is_absolute()) - { - WARN_LOG_FMT(VIDEO, - "Specified graphics mod asset '{}' for mod '{}' has an absolute path, you " - "shouldn't release this to users.", - asset.m_asset_id, mod.m_title); - } - else - { - v = std::filesystem::path{base_path} / v; - } - } - - filesystem_library->SetAssetIDMapData(asset.m_asset_id, std::move(asset_map)); - } - } - - for (const auto& mod : mods) - { - for (const GraphicsModFeatureConfig& feature : mod.m_features) - { - const auto create_action = - [filesystem_library](const std::string_view& action_name, - const picojson::value& json_data, - GraphicsModConfig mod_config) -> std::unique_ptr { - auto action = - GraphicsModActionFactory::Create(action_name, json_data, std::move(filesystem_library)); - if (action == nullptr) - { - return nullptr; - } - return std::make_unique(std::move(action), std::move(mod_config)); - }; - - const auto internal_group = fmt::format("{}.{}", mod.m_title, feature.m_group); - - const auto add_target = [&](const GraphicsTargetConfig& target) { - 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_texture_target_to_actions[the_target.m_texture_info_string].push_back( - m_actions.back().get()); - }, - [&](const CreateTextureTarget& the_target) { - m_create_texture_target_to_actions[the_target.m_texture_info_string].push_back( - m_actions.back().get()); - }, - [&](const EFBTarget& the_target) { - FBInfo info; - info.m_height = the_target.m_height; - info.m_width = the_target.m_width; - info.m_texture_format = the_target.m_texture_format; - m_efb_target_to_actions[info].push_back(m_actions.back().get()); - }, - [&](const XFBTarget& the_target) { - FBInfo info; - info.m_height = the_target.m_height; - info.m_width = the_target.m_width; - info.m_texture_format = the_target.m_texture_format; - m_xfb_target_to_actions[info].push_back(m_actions.back().get()); - }, - [&](const ProjectionTarget& the_target) { - if (the_target.m_texture_info_string) - { - const auto lookup = fmt::format("{}_{}", *the_target.m_texture_info_string, - static_cast(the_target.m_projection_type)); - m_projection_texture_target_to_actions[lookup].push_back( - m_actions.back().get()); - } - else - { - m_projection_target_to_actions[the_target.m_projection_type].push_back( - m_actions.back().get()); - } - }, - }, - target); - }; - - const auto add_action = [&](GraphicsModConfig mod_config) -> bool { - auto action = create_action(feature.m_action, feature.m_action_data, std::move(mod_config)); - if (action == nullptr) - { - WARN_LOG_FMT(VIDEO, "Failed to create action '{}' for group '{}'.", feature.m_action, - feature.m_group); - return false; - } - m_actions.push_back(std::move(action)); - return true; - }; - - // 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()) - { - if (add_action(mod)) - { - for (const GraphicsTargetConfig& target : local_it->second) - { - add_target(target); - } - } - } - else if (const auto global_it = group_to_targets.find(feature.m_group); - global_it != group_to_targets.end()) - { - if (add_action(mod)) - { - for (const GraphicsTargetConfig& target : global_it->second) - { - add_target(target); - } - } - } - else - { - WARN_LOG_FMT(VIDEO, "Specified graphics mod group '{}' was not found for mod '{}'", - feature.m_group, mod.m_title); - } - } + m_backend->OnFramePresented(present_info); } } - -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_texture_target_to_actions.clear(); - m_create_texture_target_to_actions.clear(); - m_efb_target_to_actions.clear(); - m_xfb_target_to_actions.clear(); -} +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h index 2d67b4db76..572b1db6b2 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h @@ -3,62 +3,39 @@ #pragma once -#include #include -#include -#include -#include -#include -#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h" -#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" -#include "VideoCommon/TextureInfo.h" -#include "VideoCommon/VideoEvents.h" -#include "VideoCommon/XFMemory.h" +#include "Common/HookableEvent.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" -class GraphicsModGroupConfig; +namespace GraphicsModSystem::Config +{ +class GraphicsModGroup; +} + +namespace GraphicsModSystem::Runtime +{ +class GraphicsModRuntimeBackend; class GraphicsModManager { public: + GraphicsModManager(); + ~GraphicsModManager(); + bool Initialize(); + void Load(const GraphicsModSystem::Config::GraphicsModGroup& config); - const std::vector& GetProjectionActions(ProjectionType projection_type) const; - const std::vector& - GetProjectionTextureActions(ProjectionType projection_type, - const std::string& texture_name) const; - const std::vector& - GetDrawStartedActions(const std::string& texture_name) const; - const std::vector& - GetTextureLoadActions(const std::string& texture_name) const; - const std::vector& - GetTextureCreateActions(const std::string& texture_name) const; - const std::vector& GetEFBActions(const FBInfo& efb) const; - const std::vector& GetXFBActions(const FBInfo& xfb) const; + const GraphicsModBackend& GetBackend() const; + GraphicsModBackend& GetBackend(); - void Load(const GraphicsModGroupConfig& config); + void SetEditorBackend(std::unique_ptr editor_backend); + bool IsEditorEnabled() const; private: - void EndOfFrame(); - void Reset(); + void OnFramePresented(const PresentInfo& present_info); - class DecoratedAction; - - static inline const std::vector m_default = {}; - std::list> m_actions; - std::unordered_map> - m_projection_target_to_actions; - std::unordered_map> - m_projection_texture_target_to_actions; - std::unordered_map> m_draw_started_target_to_actions; - std::unordered_map> m_load_texture_target_to_actions; - std::unordered_map> - m_create_texture_target_to_actions; - std::unordered_map, FBInfoHasher> m_efb_target_to_actions; - std::unordered_map, FBInfoHasher> m_xfb_target_to_actions; - - std::unordered_set m_groups; - - Common::EventHook m_end_of_frame_event; + std::unique_ptr m_backend; + std::unique_ptr m_editor_backend; + Common::EventHook m_frame_present_event; }; - -extern std::unique_ptr g_graphics_mod_manager; +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.cpp new file mode 100644 index 0000000000..5fdc0055cb --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.cpp @@ -0,0 +1,236 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h" + +#include +#include + +#include "Common/Logging/Log.h" +#include "Common/VariantUtil.h" + +#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h" + +namespace GraphicsModSystem::Runtime +{ +GraphicsModRuntimeBackend::GraphicsModRuntimeBackend(const Config::GraphicsModGroup& config_data) +{ + for (auto& config_mod : config_data.GetMods()) + { + if (!config_mod.m_enabled) + continue; + + GraphicsMod runtime_mod; + + runtime_mod.m_hash_policy = config_mod.m_mod.m_default_hash_policy; + + auto filesystem_library = std::make_shared(); + + for (const auto& config_asset : config_mod.m_mod.m_assets) + { + auto asset_map = config_asset.m_map; + for (auto& [k, v] : asset_map) + { + if (v.is_absolute()) + { + WARN_LOG_FMT(VIDEO, + "Specified graphics mod asset '{}' for mod '{}' has an absolute path, you " + "shouldn't release this to users.", + config_asset.m_asset_id, config_mod.m_id); + } + else + { + v = std::filesystem::path{config_mod.m_path} / v; + } + } + + filesystem_library->SetAssetIDMapData(config_asset.m_asset_id, std::move(asset_map)); + } + + for (const auto& config_action : config_mod.m_mod.m_actions) + { + runtime_mod.m_actions.push_back(GraphicsModActionFactory::Create( + config_action.m_factory_name, config_action.m_data, filesystem_library)); + } + + std::map> tag_to_actions; + for (const auto& [tag_name, action_indexes] : config_mod.m_mod.m_tag_name_to_action_indexes) + { + for (const auto& index : action_indexes) + { + tag_to_actions[tag_name].push_back(runtime_mod.m_actions[index].get()); + } + } + + struct IDWithTags + { + std::variant m_id; + const std::vector* m_tag_names; + }; + std::vector targets; + for (const auto& target : config_mod.m_mod.m_targets) + { + std::visit(overloaded{[&](const Config::IntTarget& int_target) { + const DrawCallID id{int_target.m_target_id}; + runtime_mod.m_draw_id_to_actions.insert_or_assign( + id, std::vector{}); + targets.push_back(IDWithTags{id, &int_target.m_tag_names}); + }, + [&](const Config::StringTarget& str_target) { + const auto id{str_target.m_target_id}; + runtime_mod.m_str_id_to_actions.insert_or_assign( + id, std::vector{}); + targets.push_back(IDWithTags{id, &str_target.m_tag_names}); + }}, + target); + } + + // Handle any specific actions set on targets + for (std::size_t i = 0; i < targets.size(); i++) + { + const auto& target = targets[i]; + std::visit( + overloaded{ + [&](DrawCallID draw_id) { + auto& actions = runtime_mod.m_draw_id_to_actions[draw_id]; + + // First add all specific actions + if (const auto iter = config_mod.m_mod.m_target_index_to_action_indexes.find(i); + iter != config_mod.m_mod.m_target_index_to_action_indexes.end()) + { + for (const auto& action_index : iter->second) + { + actions.push_back(runtime_mod.m_actions[action_index].get()); + } + } + + // Then add all tag actions + for (const auto& tag_name : *target.m_tag_names) + { + auto& tag_actions = tag_to_actions[tag_name]; + actions.insert(actions.end(), tag_actions.begin(), tag_actions.end()); + } + }, + [&](const std::string& str_id) { + auto& actions = runtime_mod.m_str_id_to_actions[str_id]; + + // First add all specific actions + if (const auto iter = config_mod.m_mod.m_target_index_to_action_indexes.find(i); + iter != config_mod.m_mod.m_target_index_to_action_indexes.end()) + { + for (const auto& action_index : iter->second) + { + actions.push_back(runtime_mod.m_actions[action_index].get()); + } + } + + // Then add all tag actions + for (const auto& tag_name : *target.m_tag_names) + { + auto& tag_actions = tag_to_actions[tag_name]; + actions.insert(actions.end(), tag_actions.begin(), tag_actions.end()); + } + }}, + target.m_id); + } + + m_mods.push_back(std::move(runtime_mod)); + } +} + +void GraphicsModRuntimeBackend::OnDraw(const DrawDataView& draw_data, + VertexManagerBase* vertex_manager) +{ + if (m_mods.empty()) + { + vertex_manager->DrawEmulatedMesh(m_camera_manager); + return; + } + + for (auto& mod : m_mods) + { + const auto hash_output = GetDrawDataHash(mod.m_hash_policy, draw_data); + const DrawCallID draw_call_id = + GetSkinnedDrawCallID(hash_output.draw_call_id, hash_output.material_id, draw_data); + + if (const auto iter = mod.m_draw_id_to_actions.find(draw_call_id); + iter != mod.m_draw_id_to_actions.end()) + { + CustomDraw(draw_data, vertex_manager, iter->second, draw_call_id); + return; + } + } + + vertex_manager->DrawEmulatedMesh(m_camera_manager); +} + +void GraphicsModRuntimeBackend::OnTextureLoad(const TextureView& texture) +{ + for (auto& mod : m_mods) + { + if (const auto iter = mod.m_str_id_to_actions.find(texture.hash_name); + iter != mod.m_str_id_to_actions.end()) + { + GraphicsModActionData::TextureLoad load{.texture_name = texture.hash_name}; + for (const auto& action : iter->second) + { + action->OnTextureLoad(&load); + } + break; + } + } +} + +void GraphicsModRuntimeBackend::OnTextureUnload(TextureType, std::string_view) +{ +} + +void GraphicsModRuntimeBackend::OnTextureCreate(const TextureView& texture) +{ + GraphicsModSystem::Runtime::GraphicsModBackend::OnTextureCreate(texture); + + for (auto& mod : m_mods) + { + if (const auto iter = mod.m_str_id_to_actions.find(texture.hash_name); + iter != mod.m_str_id_to_actions.end()) + { + GraphicsModActionData::TextureCreate texture_create{.texture_name = texture.hash_name}; + for (const auto& action : iter->second) + { + action->OnTextureCreate(&texture_create); + } + break; + } + } +} + +void GraphicsModRuntimeBackend::OnLight() +{ +} + +void GraphicsModRuntimeBackend::OnFramePresented(const PresentInfo& present_info) +{ + GraphicsModSystem::Runtime::GraphicsModBackend::OnFramePresented(present_info); + + for (auto& mod : m_mods) + { + for (const auto& action : mod.m_actions) + { + action->OnFrameEnd(); + } + } +} + +void GraphicsModRuntimeBackend::AddIndices(OpcodeDecoder::Primitive, u32) +{ +} + +void GraphicsModRuntimeBackend::ResetIndices() +{ +} + +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h new file mode 100644 index 0000000000..c44d308dc7 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h @@ -0,0 +1,72 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModSystem::Runtime +{ +class GraphicsModRuntimeBackend final : public GraphicsModBackend +{ +public: + explicit GraphicsModRuntimeBackend(const Config::GraphicsModGroup& config_data); + void OnDraw(const DrawDataView& draw_data, VertexManagerBase* vertex_manager) override; + void OnTextureLoad(const TextureView& texture) override; + void OnTextureUnload(TextureType texture_type, std::string_view texture_hash) override; + void OnTextureCreate(const TextureView& texture) override; + void OnLight() override; + void OnFramePresented(const PresentInfo& present_info) override; + + void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) override; + void ResetIndices() override; + +private: + struct GraphicsMod + { + GraphicsMod() = default; + ~GraphicsMod() = default; + GraphicsMod(GraphicsMod&) = delete; + GraphicsMod(GraphicsMod&&) noexcept = default; + GraphicsMod& operator=(GraphicsMod&) = delete; + GraphicsMod& operator=(GraphicsMod&&) noexcept = default; + + Config::HashPolicy m_hash_policy; + std::unordered_map> m_draw_id_to_actions; + + struct string_hash + { + using is_transparent = void; + [[nodiscard]] size_t operator()(const char* txt) const + { + return std::hash{}(txt); + } + [[nodiscard]] size_t operator()(std::string_view txt) const + { + return std::hash{}(txt); + } + [[nodiscard]] size_t operator()(const std::string& txt) const + { + return std::hash{}(txt); + } + }; + + std::unordered_map, string_hash, std::equal_to<>> + m_str_id_to_actions; + std::vector> m_actions; + }; + std::vector m_mods; +}; +} // namespace GraphicsModSystem::Runtime