mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-24 19:27:27 +00:00
VideoCommon: graphics mods 2.0!
This commit is contained in:
parent
a6a1c2b129
commit
f79154b6f5
27 changed files with 1511 additions and 1268 deletions
|
@ -13,8 +13,9 @@
|
||||||
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Constants.h"
|
#include "VideoCommon/GraphicsModSystem/Constants.h"
|
||||||
|
|
||||||
std::optional<GraphicsModConfig> GraphicsModConfig::Create(const std::string& file_path,
|
namespace GraphicsModSystem::Config
|
||||||
Source source)
|
{
|
||||||
|
std::optional<GraphicsMod> GraphicsMod::Create(const std::string& file_path)
|
||||||
{
|
{
|
||||||
picojson::value root;
|
picojson::value root;
|
||||||
std::string error;
|
std::string error;
|
||||||
|
@ -25,186 +26,112 @@ std::optional<GraphicsModConfig> GraphicsModConfig::Create(const std::string& fi
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsModConfig result;
|
GraphicsMod result;
|
||||||
if (!result.DeserializeFromConfig(root))
|
if (!result.Deserialize(root))
|
||||||
{
|
{
|
||||||
return std::nullopt;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<GraphicsModConfig> GraphicsModConfig::Create(const picojson::object* obj)
|
void GraphicsMod::Serialize(picojson::object& json_obj) const
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
{
|
||||||
picojson::object serialized_metadata;
|
picojson::object serialized_metadata;
|
||||||
|
serialized_metadata.emplace("schema_version", static_cast<double>(m_schema_version));
|
||||||
serialized_metadata.emplace("title", m_title);
|
serialized_metadata.emplace("title", m_title);
|
||||||
serialized_metadata.emplace("author", m_author);
|
serialized_metadata.emplace("author", m_author);
|
||||||
serialized_metadata.emplace("description", m_description);
|
serialized_metadata.emplace("description", m_description);
|
||||||
|
serialized_metadata.emplace("mod_version", m_mod_version);
|
||||||
json_obj.emplace("meta", std::move(serialized_metadata));
|
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;
|
picojson::array serialized_assets;
|
||||||
for (const auto& asset : m_assets)
|
for (const auto& asset : m_assets)
|
||||||
{
|
{
|
||||||
picojson::object serialized_asset;
|
picojson::object serialized_asset;
|
||||||
asset.SerializeToConfig(serialized_asset);
|
asset.Serialize(serialized_asset);
|
||||||
serialized_assets.emplace_back(std::move(serialized_asset));
|
serialized_assets.emplace_back(std::move(serialized_asset));
|
||||||
}
|
}
|
||||||
json_obj.emplace("assets", std::move(serialized_assets));
|
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<double>(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<double>(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");
|
const auto& meta = value.get("meta");
|
||||||
if (meta.is<picojson::object>())
|
if (meta.is<picojson::object>())
|
||||||
{
|
{
|
||||||
const auto& title = meta.get("title");
|
const auto& meta_obj = meta.get<picojson::object>();
|
||||||
if (title.is<std::string>())
|
m_schema_version = ReadNumericFromJson<u16>(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");
|
m_title = ReadStringFromJson(meta_obj, "title").value_or("Unknown Mod");
|
||||||
if (author.is<std::string>())
|
m_author = ReadStringFromJson(meta_obj, "author").value_or("Unknown");
|
||||||
{
|
m_description = ReadStringFromJson(meta_obj, "description").value_or("");
|
||||||
m_author = author.to_str();
|
m_mod_version = ReadStringFromJson(meta_obj, "mod_version").value_or("v0.0.0");
|
||||||
}
|
|
||||||
|
|
||||||
const auto& description = meta.get("description");
|
|
||||||
if (description.is<std::string>())
|
|
||||||
{
|
|
||||||
m_description = description.to_str();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& groups = value.get("groups");
|
|
||||||
if (groups.is<picojson::array>())
|
|
||||||
{
|
|
||||||
for (const auto& group_val : groups.get<picojson::array>())
|
|
||||||
{
|
|
||||||
if (!group_val.is<picojson::object>())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO, "Failed to load mod configuration file, specified group is not a json object");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
GraphicsTargetGroupConfig group;
|
|
||||||
if (!group.DeserializeFromConfig(group_val.get<picojson::object>()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_groups.push_back(std::move(group));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& features = value.get("features");
|
|
||||||
if (features.is<picojson::array>())
|
|
||||||
{
|
|
||||||
for (const auto& feature_val : features.get<picojson::array>())
|
|
||||||
{
|
|
||||||
if (!feature_val.is<picojson::object>())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO, "Failed to load mod configuration file, specified feature is not a json object");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
GraphicsModFeatureConfig feature;
|
|
||||||
if (!feature.DeserializeFromConfig(feature_val.get<picojson::object>()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_features.push_back(std::move(feature));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& assets = value.get("assets");
|
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");
|
VIDEO, "Failed to load mod configuration file, specified asset is not a json object");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
GraphicsModAssetConfig asset;
|
GraphicsModAsset asset;
|
||||||
if (!asset.DeserializeFromConfig(asset_val.get<picojson::object>()))
|
if (!asset.Deserialize(asset_val.get<picojson::object>()))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -228,105 +155,147 @@ bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& tags = value.get("tags");
|
||||||
|
if (tags.is<picojson::array>())
|
||||||
|
{
|
||||||
|
for (const auto& tag_val : tags.get<picojson::array>())
|
||||||
|
{
|
||||||
|
if (!tag_val.is<picojson::object>())
|
||||||
|
{
|
||||||
|
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<picojson::object>()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_tags.push_back(std::move(tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& targets = value.get("targets");
|
||||||
|
if (targets.is<picojson::array>())
|
||||||
|
{
|
||||||
|
for (const auto& target_val : targets.get<picojson::array>())
|
||||||
|
{
|
||||||
|
if (!target_val.is<picojson::object>())
|
||||||
|
{
|
||||||
|
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<picojson::object>(), target))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_targets.push_back(std::move(target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& actions = value.get("actions");
|
||||||
|
if (actions.is<picojson::array>())
|
||||||
|
{
|
||||||
|
for (const auto& action_val : actions.get<picojson::array>())
|
||||||
|
{
|
||||||
|
if (!action_val.is<picojson::object>())
|
||||||
|
{
|
||||||
|
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<picojson::object>()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_actions.push_back(std::move(action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& target_to_actions = value.get("target_to_actions");
|
||||||
|
if (target_to_actions.is<picojson::object>())
|
||||||
|
{
|
||||||
|
for (const auto& [key, action_indexes_val] : target_to_actions.get<picojson::object>())
|
||||||
|
{
|
||||||
|
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<picojson::array>())
|
||||||
|
{
|
||||||
|
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<picojson::array>())
|
||||||
|
{
|
||||||
|
if (!action_index_val.is<double>())
|
||||||
|
{
|
||||||
|
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<u64>(action_index_val.get<double>()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& tag_to_actions = value.get("tag_to_actions");
|
||||||
|
if (tag_to_actions.is<picojson::object>())
|
||||||
|
{
|
||||||
|
for (const auto& [tag, action_indexes_val] : tag_to_actions.get<picojson::object>())
|
||||||
|
{
|
||||||
|
auto& action_indexes = m_tag_name_to_action_indexes[tag];
|
||||||
|
if (!action_indexes_val.is<picojson::array>())
|
||||||
|
{
|
||||||
|
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<picojson::array>())
|
||||||
|
{
|
||||||
|
if (!action_index_val.is<double>())
|
||||||
|
{
|
||||||
|
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<u64>(action_index_val.get<double>()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_default_hash_policy = GetDefaultHashPolicy();
|
||||||
|
|
||||||
|
const auto& default_hash_policy_json = value.get("default_hash_policy");
|
||||||
|
if (default_hash_policy_json.is<picojson::object>())
|
||||||
|
{
|
||||||
|
auto& default_hash_policy_json_obj = default_hash_policy_json.get<picojson::object>();
|
||||||
|
const auto attributes = ReadStringFromJson(default_hash_policy_json_obj, "attributes");
|
||||||
|
if (attributes)
|
||||||
|
{
|
||||||
|
m_default_hash_policy.attributes = HashAttributesFromString(*attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} // namespace GraphicsModSystem::Config
|
||||||
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<double>(m_weight));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsModConfig::DeserializeFromProfile(const picojson::object& obj)
|
|
||||||
{
|
|
||||||
if (const auto it = obj.find("groups"); it != obj.end())
|
|
||||||
{
|
|
||||||
if (it->second.is<picojson::array>())
|
|
||||||
{
|
|
||||||
const auto& serialized_groups = it->second.get<picojson::array>();
|
|
||||||
if (serialized_groups.size() != m_groups.size())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < serialized_groups.size(); i++)
|
|
||||||
{
|
|
||||||
const auto& serialized_group_val = serialized_groups[i];
|
|
||||||
if (serialized_group_val.is<picojson::object>())
|
|
||||||
{
|
|
||||||
const auto& serialized_group = serialized_group_val.get<picojson::object>();
|
|
||||||
m_groups[i].DeserializeFromProfile(serialized_group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto it = obj.find("features"); it != obj.end())
|
|
||||||
{
|
|
||||||
if (it->second.is<picojson::array>())
|
|
||||||
{
|
|
||||||
const auto& serialized_features = it->second.get<picojson::array>();
|
|
||||||
if (serialized_features.size() != m_features.size())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < serialized_features.size(); i++)
|
|
||||||
{
|
|
||||||
const auto& serialized_feature_val = serialized_features[i];
|
|
||||||
if (serialized_feature_val.is<picojson::object>())
|
|
||||||
{
|
|
||||||
const auto& serialized_feature = serialized_feature_val.get<picojson::object>();
|
|
||||||
m_features[i].DeserializeFromProfile(serialized_feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto it = obj.find("enabled"); it != obj.end())
|
|
||||||
{
|
|
||||||
if (it->second.is<bool>())
|
|
||||||
{
|
|
||||||
m_enabled = it->second.get<bool>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto it = obj.find("weight"); it != obj.end())
|
|
||||||
{
|
|
||||||
if (it->second.is<double>())
|
|
||||||
{
|
|
||||||
m_weight = static_cast<u16>(it->second.get<double>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,44 +3,48 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <picojson.h>
|
#include <picojson.h>
|
||||||
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h"
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.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_title;
|
||||||
std::string m_author;
|
std::string m_author;
|
||||||
std::string m_description;
|
std::string m_description;
|
||||||
bool m_enabled = false;
|
std::string m_mod_version;
|
||||||
u16 m_weight = 0;
|
|
||||||
std::string m_relative_path;
|
|
||||||
|
|
||||||
enum class Source
|
std::vector<GraphicsModAsset> m_assets;
|
||||||
{
|
std::vector<GraphicsModTag> m_tags;
|
||||||
User,
|
std::vector<AnyTarget> m_targets;
|
||||||
System
|
std::vector<GraphicsModAction> m_actions;
|
||||||
};
|
std::map<u64, std::vector<u64>> m_target_index_to_action_indexes;
|
||||||
Source m_source = Source::User;
|
std::map<std::string, std::vector<u64>> m_tag_name_to_action_indexes;
|
||||||
|
|
||||||
std::vector<GraphicsTargetGroupConfig> m_groups;
|
HashPolicy m_default_hash_policy;
|
||||||
std::vector<GraphicsModFeatureConfig> m_features;
|
|
||||||
std::vector<GraphicsModAssetConfig> m_assets;
|
|
||||||
|
|
||||||
static std::optional<GraphicsModConfig> Create(const std::string& file, Source source);
|
static std::optional<GraphicsMod> Create(const std::string& file);
|
||||||
static std::optional<GraphicsModConfig> Create(const picojson::object* obj);
|
|
||||||
|
|
||||||
std::string GetAbsolutePath() const;
|
void Serialize(picojson::object& json_obj) const;
|
||||||
|
bool Deserialize(const picojson::value& value);
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
} // namespace GraphicsModSystem::Config
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <picojson.h>
|
||||||
|
|
||||||
|
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
|
|
@ -3,12 +3,15 @@
|
||||||
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
||||||
|
|
||||||
|
#include "Common/JsonUtil.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/StringUtil.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;
|
picojson::object serialized_data;
|
||||||
for (const auto& [name, path] : m_map)
|
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));
|
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");
|
const auto id = ReadStringFromJson(obj, "id");
|
||||||
if (name_iter == obj.end())
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (!name_iter->second.is<std::string>())
|
m_asset_id = *id;
|
||||||
{
|
|
||||||
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();
|
|
||||||
|
|
||||||
auto data_iter = obj.find("data");
|
auto data_iter = obj.find("data");
|
||||||
if (data_iter == obj.end())
|
if (data_iter == obj.end())
|
||||||
|
@ -64,3 +62,4 @@ bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj)
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} // namespace GraphicsModSystem::Config
|
||||||
|
|
|
@ -9,11 +9,14 @@
|
||||||
|
|
||||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||||
|
|
||||||
struct GraphicsModAssetConfig
|
namespace GraphicsModSystem::Config
|
||||||
|
{
|
||||||
|
struct GraphicsModAsset
|
||||||
{
|
{
|
||||||
VideoCommon::CustomAssetLibrary::AssetID m_asset_id;
|
VideoCommon::CustomAssetLibrary::AssetID m_asset_id;
|
||||||
VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map;
|
VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map;
|
||||||
|
|
||||||
void SerializeToConfig(picojson::object& json_obj) const;
|
void Serialize(picojson::object& json_obj) const;
|
||||||
bool DeserializeFromConfig(const picojson::object& obj);
|
bool Deserialize(const picojson::object& json_obj);
|
||||||
};
|
};
|
||||||
|
} // namespace GraphicsModSystem::Config
|
||||||
|
|
|
@ -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<std::string>())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO,
|
|
||||||
"Failed to load mod configuration file, specified feature's group is not a string");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_group = group_iter->second.get<std::string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto action_iter = obj.find("action"); action_iter != obj.end())
|
|
||||||
{
|
|
||||||
if (!action_iter->second.is<std::string>())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO,
|
|
||||||
"Failed to load mod configuration file, specified feature's action is not a string");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_action = action_iter->second.get<std::string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto action_data_iter = obj.find("action_data"); action_data_iter != obj.end())
|
|
||||||
{
|
|
||||||
m_action_data = action_data_iter->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsModFeatureConfig::SerializeToProfile(picojson::object*) const
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsModFeatureConfig::DeserializeFromProfile(const picojson::object&)
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
// Copyright 2022 Dolphin Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <picojson.h>
|
|
||||||
|
|
||||||
struct GraphicsModFeatureConfig
|
|
||||||
{
|
|
||||||
std::string m_group;
|
|
||||||
std::string m_action;
|
|
||||||
picojson::value m_action_data;
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <picojson.h>
|
#include <picojson.h>
|
||||||
|
#include <xxh3.h>
|
||||||
|
|
||||||
#include "Common/CommonPaths.h"
|
#include "Common/CommonPaths.h"
|
||||||
#include "Common/FileSearch.h"
|
#include "Common/FileSearch.h"
|
||||||
|
@ -15,49 +16,184 @@
|
||||||
#include "Common/JsonUtil.h"
|
#include "Common/JsonUtil.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
#include "Common/VariantUtil.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Constants.h"
|
#include "VideoCommon/GraphicsModSystem/Constants.h"
|
||||||
#include "VideoCommon/HiresTextures.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;
|
void GraphicsModGroup::Load()
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
const std::string file_path = GetPath();
|
struct GraphicsModWithDir
|
||||||
|
{
|
||||||
|
Config::GraphicsMod m_mod;
|
||||||
|
std::string m_path;
|
||||||
|
};
|
||||||
|
std::vector<GraphicsModWithDir> action_only_mods;
|
||||||
|
std::vector<GraphicsModWithDir> target_only_mods;
|
||||||
|
const auto try_add_mod = [&](const std::string& dir) {
|
||||||
|
auto file = dir + DIR_SEP + "metadata.json";
|
||||||
|
UnifyPathSeparators(file);
|
||||||
|
|
||||||
std::set<std::string> known_paths;
|
if (auto mod = GraphicsMod::Create(file))
|
||||||
if (File::Exists(file_path))
|
{
|
||||||
|
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<std::string> graphics_mod_user_directories =
|
||||||
|
GetTextureDirectoriesWithGameId(File::GetUserPath(D_GRAPHICSMOD_IDX), m_game_id);
|
||||||
|
|
||||||
|
for (const auto& graphics_mod_directory : graphics_mod_user_directories)
|
||||||
|
{
|
||||||
|
try_add_mod(graphics_mod_directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::set<std::string> graphics_mod_system_directories = GetTextureDirectoriesWithGameId(
|
||||||
|
File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_game_id);
|
||||||
|
|
||||||
|
for (const auto& graphics_mod_directory : graphics_mod_system_directories)
|
||||||
|
{
|
||||||
|
try_add_mod(graphics_mod_directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<XXH64_hash_t>(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;
|
picojson::value root;
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!JsonFromFile(file_path, &root, &error))
|
if (!JsonFromFile(gameid_metadata, &root, &error))
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(VIDEO,
|
ERROR_LOG_FMT(VIDEO,
|
||||||
"Failed to load graphics mod group json file '{}' due to parse error: {}",
|
"Failed to load graphics mod group json file '{}' due to parse error: {}",
|
||||||
file_path, error);
|
gameid_metadata, error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!root.is<picojson::object>())
|
if (!root.is<picojson::object>())
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(
|
ERROR_LOG_FMT(
|
||||||
VIDEO,
|
VIDEO,
|
||||||
"Failed to load graphics mod group json file '{}' due to root not being an object!",
|
"Failed to load graphics mod group json file '{}' due to root not being an object!",
|
||||||
file_path);
|
gameid_metadata);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,106 +205,73 @@ void GraphicsModGroupConfig::Load()
|
||||||
if (mod_json.is<picojson::object>())
|
if (mod_json.is<picojson::object>())
|
||||||
{
|
{
|
||||||
const auto& mod_json_obj = mod_json.get<picojson::object>();
|
const auto& mod_json_obj = mod_json.get<picojson::object>();
|
||||||
auto graphics_mod = GraphicsModConfig::Create(&mod_json_obj);
|
const auto id_str = ReadStringFromJson(mod_json_obj, "id");
|
||||||
if (!graphics_mod)
|
if (!id_str)
|
||||||
|
continue;
|
||||||
|
u64 id;
|
||||||
|
if (!TryParse(*id_str, &id))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
graphics_mod->DeserializeFromProfile(mod_json_obj);
|
if (const auto iter = m_id_to_graphics_mod.find(id); iter != m_id_to_graphics_mod.end())
|
||||||
|
{
|
||||||
auto mod_full_path = graphics_mod->GetAbsolutePath();
|
iter->second->m_weight = ReadNumericFromJson<u16>(mod_json_obj, "weight").value_or(0);
|
||||||
known_paths.insert(std::move(mod_full_path));
|
iter->second->m_enabled = ReadBoolFromJson(mod_json_obj, "enabled").value_or(false);
|
||||||
m_graphics_mods.push_back(std::move(*graphics_mod));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto try_add_mod = [&known_paths, this](const std::string& dir,
|
std::ranges::sort(m_graphics_mods, {}, &GraphicsModGroup::GraphicsModWithMetadata::m_weight);
|
||||||
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<std::string> graphics_mod_user_directories =
|
|
||||||
GetTextureDirectoriesWithGameId(File::GetUserPath(D_GRAPHICSMOD_IDX), m_game_id);
|
|
||||||
|
|
||||||
for (const auto& graphics_mod_directory : graphics_mod_user_directories)
|
|
||||||
{
|
|
||||||
try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::User);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::set<std::string> graphics_mod_system_directories = GetTextureDirectoriesWithGameId(
|
|
||||||
File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_game_id);
|
|
||||||
|
|
||||||
for (const auto& graphics_mod_directory : graphics_mod_system_directories)
|
|
||||||
{
|
|
||||||
try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::System);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ranges::sort(m_graphics_mods, {}, &GraphicsModConfig::m_weight);
|
|
||||||
for (auto& mod : m_graphics_mods)
|
|
||||||
{
|
|
||||||
m_path_to_graphics_mod[mod.GetAbsolutePath()] = &mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_change_count++;
|
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::object serialized_root;
|
||||||
picojson::array serialized_mods;
|
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;
|
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<double>(mod_ptr->m_weight));
|
||||||
serialized_mods.emplace_back(std::move(serialized_mod));
|
serialized_mods.emplace_back(std::move(serialized_mod));
|
||||||
}
|
}
|
||||||
serialized_root.emplace("mods", std::move(serialized_mods));
|
serialized_root.emplace("mods", std::move(serialized_mods));
|
||||||
|
|
||||||
const auto output = picojson::value{serialized_root}.serialize(true);
|
const auto file_path = GetPath();
|
||||||
json_stream << output;
|
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;
|
m_change_count = change_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 GraphicsModGroupConfig::GetChangeCount() const
|
u32 GraphicsModGroup::GetChangeCount() const
|
||||||
{
|
{
|
||||||
return m_change_count;
|
return m_change_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GraphicsModConfig>& GraphicsModGroupConfig::GetMods() const
|
const std::vector<GraphicsModGroup::GraphicsModWithMetadata>& GraphicsModGroup::GetMods() const
|
||||||
{
|
{
|
||||||
return m_graphics_mods;
|
return m_graphics_mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<GraphicsModConfig>& GraphicsModGroupConfig::GetMods()
|
std::vector<GraphicsModGroup::GraphicsModWithMetadata>& GraphicsModGroup::GetMods()
|
||||||
{
|
{
|
||||||
return m_graphics_mods;
|
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);
|
if (const auto iter = m_id_to_graphics_mod.find(id); iter != m_id_to_graphics_mod.end())
|
||||||
iter != m_path_to_graphics_mod.end())
|
|
||||||
{
|
{
|
||||||
return iter->second;
|
return iter->second;
|
||||||
}
|
}
|
||||||
|
@ -176,13 +279,14 @@ GraphicsModConfig* GraphicsModGroupConfig::GetMod(std::string_view absolute_path
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& GraphicsModGroupConfig::GetGameID() const
|
const std::string& GraphicsModGroup::GetGameID() const
|
||||||
{
|
{
|
||||||
return m_game_id;
|
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;
|
const std::string game_mod_root = File::GetUserPath(D_CONFIG_IDX) + GRAPHICSMOD_CONFIG_DIR;
|
||||||
return fmt::format("{}/{}.json", game_mod_root, m_game_id);
|
return fmt::format("{}/{}.json", game_mod_root, m_game_id);
|
||||||
}
|
}
|
||||||
|
} // namespace GraphicsModSystem::Config
|
||||||
|
|
|
@ -5,24 +5,17 @@
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
|
||||||
|
|
||||||
struct GraphicsModConfig;
|
namespace GraphicsModSystem::Config
|
||||||
|
{
|
||||||
class GraphicsModGroupConfig
|
class GraphicsModGroup
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit GraphicsModGroupConfig(std::string game_id);
|
explicit GraphicsModGroup(std::string game_id);
|
||||||
~GraphicsModGroupConfig();
|
|
||||||
|
|
||||||
GraphicsModGroupConfig(const GraphicsModGroupConfig&);
|
|
||||||
GraphicsModGroupConfig(GraphicsModGroupConfig&&) noexcept;
|
|
||||||
|
|
||||||
GraphicsModGroupConfig& operator=(const GraphicsModGroupConfig&);
|
|
||||||
GraphicsModGroupConfig& operator=(GraphicsModGroupConfig&&) noexcept;
|
|
||||||
|
|
||||||
void Load();
|
void Load();
|
||||||
void Save() const;
|
void Save() const;
|
||||||
|
@ -30,17 +23,27 @@ public:
|
||||||
void SetChangeCount(u32 change_count);
|
void SetChangeCount(u32 change_count);
|
||||||
u32 GetChangeCount() const;
|
u32 GetChangeCount() const;
|
||||||
|
|
||||||
const std::vector<GraphicsModConfig>& GetMods() const;
|
struct GraphicsModWithMetadata
|
||||||
std::vector<GraphicsModConfig>& GetMods();
|
{
|
||||||
|
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<GraphicsModWithMetadata>& GetMods() const;
|
||||||
|
std::vector<GraphicsModWithMetadata>& GetMods();
|
||||||
|
|
||||||
|
GraphicsModWithMetadata* GetMod(u64 id) const;
|
||||||
|
|
||||||
const std::string& GetGameID() const;
|
const std::string& GetGameID() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string GetPath() const;
|
std::string GetPath() const;
|
||||||
std::string m_game_id;
|
std::string m_game_id;
|
||||||
std::vector<GraphicsModConfig> m_graphics_mods;
|
std::vector<GraphicsModWithMetadata> m_graphics_mods;
|
||||||
std::map<std::string, GraphicsModConfig*, std::less<>> m_path_to_graphics_mod;
|
std::map<u64, GraphicsModWithMetadata*> m_id_to_graphics_mod;
|
||||||
u32 m_change_count = 0;
|
u32 m_change_count = 0;
|
||||||
};
|
};
|
||||||
|
} // namespace GraphicsModSystem::Config
|
||||||
|
|
|
@ -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<std::underlying_type_t<HashAttributes>>(all_attributes & attribute) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashAttributes GetDefaultHashAttributes()
|
||||||
|
{
|
||||||
|
return HashAttributes::Blending | HashAttributes::Indices | HashAttributes::VertexLayout;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HashAttributes operator|(HashAttributes lhs, HashAttributes rhs)
|
||||||
|
{
|
||||||
|
return static_cast<HashAttributes>(static_cast<std::underlying_type_t<HashAttributes>>(lhs) |
|
||||||
|
static_cast<std::underlying_type_t<HashAttributes>>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
HashAttributes operator&(HashAttributes lhs, HashAttributes rhs)
|
||||||
|
{
|
||||||
|
return static_cast<HashAttributes>(static_cast<std::underlying_type_t<HashAttributes>>(lhs) &
|
||||||
|
static_cast<std::underlying_type_t<HashAttributes>>(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<HashAttributes>(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<u16>(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
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#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
|
|
@ -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
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <picojson.h>
|
||||||
|
|
||||||
|
#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
|
|
@ -3,308 +3,134 @@
|
||||||
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"
|
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"
|
||||||
|
|
||||||
|
#include "Common/JsonUtil.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Common/VariantUtil.h"
|
#include "Common/VariantUtil.h"
|
||||||
#include "VideoCommon/TextureCacheBase.h"
|
|
||||||
|
|
||||||
namespace
|
namespace GraphicsModSystem::Config
|
||||||
{
|
{
|
||||||
template <typename T, std::enable_if_t<std::is_base_of_v<FBTarget, T>, int> = 0>
|
void SerializeTarget(picojson::object& json_obj, const AnyTarget& target)
|
||||||
std::optional<T> DeserializeFBTargetFromConfig(const picojson::object& obj, std::string_view prefix)
|
|
||||||
{
|
|
||||||
T fb;
|
|
||||||
const auto texture_filename_iter = obj.find("texture_filename");
|
|
||||||
if (texture_filename_iter == obj.end())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO,
|
|
||||||
"Failed to load mod configuration file, option 'texture_filename' not found");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
if (!texture_filename_iter->second.is<std::string>())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO,
|
|
||||||
"Failed to load mod configuration file, option 'texture_filename' is not a string type");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto texture_filename = texture_filename_iter->second.get<std::string>();
|
|
||||||
const auto texture_filename_without_prefix = texture_filename.substr(prefix.size() + 1);
|
|
||||||
const auto split_str_values = SplitString(texture_filename_without_prefix, '_');
|
|
||||||
if (split_str_values.size() == 1)
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is not valid");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto split_width_height_values = SplitString(texture_filename_without_prefix, 'x');
|
|
||||||
if (split_width_height_values.size() != 2)
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
|
||||||
"not valid, width and height separator found more matches than expected");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t width_underscore_pos = split_width_height_values[0].find_last_of('_');
|
|
||||||
std::string width_str;
|
|
||||||
if (width_underscore_pos == std::string::npos)
|
|
||||||
{
|
|
||||||
width_str = split_width_height_values[0];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
width_str = split_width_height_values[0].substr(width_underscore_pos + 1);
|
|
||||||
}
|
|
||||||
if (!TryParse(width_str, &fb.m_width))
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
|
||||||
"not valid, width not a number");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t height_underscore_pos = split_width_height_values[1].find_first_of('_');
|
|
||||||
if (height_underscore_pos == std::string::npos ||
|
|
||||||
height_underscore_pos == split_width_height_values[1].size() - 1)
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
|
||||||
"not valid, underscore after height is missing or incomplete");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const std::string height_str = split_width_height_values[1].substr(0, height_underscore_pos);
|
|
||||||
if (!TryParse(height_str, &fb.m_height))
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
|
||||||
"not valid, height not a number");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t format_underscore_pos =
|
|
||||||
split_width_height_values[1].find_first_of('_', height_underscore_pos + 1);
|
|
||||||
|
|
||||||
std::string format_str;
|
|
||||||
if (format_underscore_pos == std::string::npos)
|
|
||||||
{
|
|
||||||
format_str = split_width_height_values[1].substr(height_underscore_pos + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
format_str = split_width_height_values[1].substr(
|
|
||||||
height_underscore_pos + 1, (format_underscore_pos - height_underscore_pos) - 1);
|
|
||||||
}
|
|
||||||
u32 format;
|
|
||||||
if (!TryParse(format_str, &format))
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
|
||||||
"not valid, texture format is not a number");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
if (!IsValidTextureFormat(static_cast<TextureFormat>(format)))
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
|
||||||
"not valid, texture format is not valid");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
fb.m_texture_format = static_cast<TextureFormat>(format);
|
|
||||||
|
|
||||||
return fb;
|
|
||||||
}
|
|
||||||
std::optional<std::string> ExtractTextureFilenameForConfig(const picojson::object& obj)
|
|
||||||
{
|
|
||||||
const auto texture_filename_iter = obj.find("texture_filename");
|
|
||||||
if (texture_filename_iter == obj.end())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO,
|
|
||||||
"Failed to load mod configuration file, option 'texture_filename' not found");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
if (!texture_filename_iter->second.is<std::string>())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO,
|
|
||||||
"Failed to load mod configuration file, option 'texture_filename' is not a string type");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
std::string texture_info = texture_filename_iter->second.get<std::string>();
|
|
||||||
|
|
||||||
const auto handle_fb_texture =
|
|
||||||
[&texture_info](std::string_view type) -> std::optional<std::string> {
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
std::visit(overloaded{
|
std::visit(overloaded{
|
||||||
[&](const DrawStartedTextureTarget& the_target) {
|
[&](const IntTarget& the_target) {
|
||||||
json_obj.emplace("type", "draw_started");
|
json_obj.emplace("type", "int");
|
||||||
json_obj.emplace("texture_filename", the_target.m_texture_info_string);
|
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) {
|
[&](const StringTarget& the_target) {
|
||||||
json_obj.emplace("type", "load_texture");
|
json_obj.emplace("type", "string");
|
||||||
json_obj.emplace("texture_filename", the_target.m_texture_info_string);
|
json_obj.emplace("id", the_target.m_target_id);
|
||||||
},
|
json_obj.emplace("name", the_target.m_name);
|
||||||
[&](const CreateTextureTarget& the_target) {
|
json_obj.emplace("tags", ToJsonArray(the_target.m_tag_names));
|
||||||
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<int>(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<int>(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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
target);
|
target);
|
||||||
}
|
}
|
||||||
|
bool DeserializeTarget(const picojson::object& json_obj, AnyTarget& target)
|
||||||
std::optional<GraphicsTargetConfig> DeserializeTargetFromConfig(const picojson::object& obj)
|
|
||||||
{
|
{
|
||||||
const auto type_iter = obj.find("type");
|
const auto type = ReadStringFromJson(json_obj, "type");
|
||||||
if (type_iter == obj.end())
|
if (!type)
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'type' not found");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
if (!type_iter->second.is<std::string>())
|
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(VIDEO,
|
ERROR_LOG_FMT(VIDEO,
|
||||||
"Failed to load mod configuration file, option 'type' is not a string type");
|
"Failed to load mod configuration file, option 'type' was missing or invalid");
|
||||||
return std::nullopt;
|
return false;
|
||||||
}
|
}
|
||||||
const std::string& type = type_iter->second.get<std::string>();
|
|
||||||
if (type == "draw_started")
|
|
||||||
{
|
|
||||||
std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
|
|
||||||
if (!texture_info.has_value())
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
DrawStartedTextureTarget target;
|
if (*type == "int")
|
||||||
target.m_texture_info_string = texture_info.value();
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
else if (type == "load_texture")
|
|
||||||
{
|
{
|
||||||
std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
|
IntTarget i_target;
|
||||||
if (!texture_info.has_value())
|
i_target.m_name = ReadStringFromJson(json_obj, "name").value_or("");
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
LoadTextureTarget target;
|
const std::string target_id_str = ReadStringFromJson(json_obj, "id").value_or("0");
|
||||||
target.m_texture_info_string = texture_info.value();
|
u64 target_id = 0;
|
||||||
return target;
|
if (!TryParse(target_id_str, &target_id))
|
||||||
}
|
|
||||||
else if (type == "create_texture")
|
|
||||||
{
|
|
||||||
std::optional<std::string> 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<EFBTarget>(obj, EFB_DUMP_PREFIX);
|
|
||||||
}
|
|
||||||
else if (type == "xfb")
|
|
||||||
{
|
|
||||||
return DeserializeFBTargetFromConfig<XFBTarget>(obj, EFB_DUMP_PREFIX);
|
|
||||||
}
|
|
||||||
else if (type == "projection")
|
|
||||||
{
|
|
||||||
ProjectionTarget target;
|
|
||||||
const auto texture_iter = obj.find("texture_filename");
|
|
||||||
if (texture_iter != obj.end())
|
|
||||||
{
|
|
||||||
std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
|
|
||||||
if (!texture_info.has_value())
|
|
||||||
return std::nullopt;
|
|
||||||
target.m_texture_info_string = texture_info;
|
|
||||||
}
|
|
||||||
const auto value_iter = obj.find("value");
|
|
||||||
if (value_iter == obj.end())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' not found");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
if (!value_iter->second.is<std::string>())
|
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(VIDEO,
|
ERROR_LOG_FMT(VIDEO,
|
||||||
"Failed to load mod configuration file, option 'value' is not a string type");
|
"Failed to load graphics mod configuration file, option 'id' is invalid");
|
||||||
return std::nullopt;
|
return false;
|
||||||
}
|
}
|
||||||
const auto& value_str = value_iter->second.get<std::string>();
|
i_target.m_target_id = target_id;
|
||||||
if (value_str == "2d")
|
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<picojson::array>())
|
||||||
|
{
|
||||||
|
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<picojson::array>();
|
||||||
|
if (!std::all_of(tags_json.begin(), tags_json.end(),
|
||||||
|
[](const picojson::value& value) { return value.is<std::string>(); }))
|
||||||
|
{
|
||||||
|
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 "
|
ERROR_LOG_FMT(VIDEO,
|
||||||
"value, valid values are: 2d, 3d");
|
"Failed to load graphics mod configuration file, option 'id' is invalid");
|
||||||
return std::nullopt;
|
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<picojson::array>())
|
||||||
|
{
|
||||||
|
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<picojson::array>();
|
||||||
|
if (!std::all_of(tags_json.begin(), tags_json.end(),
|
||||||
|
[](const picojson::value& value) { return value.is<std::string>(); }))
|
||||||
|
{
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(VIDEO,
|
ERROR_LOG_FMT(
|
||||||
"Failed to load mod configuration file, option 'type' is not a valid value");
|
VIDEO,
|
||||||
|
"Failed to load graphics mod configuration file, option 'type' is invalid value '{}'",
|
||||||
|
*type);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
} // namespace GraphicsModSystem::Config
|
||||||
|
|
|
@ -3,60 +3,34 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <picojson.h>
|
#include <picojson.h>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#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<std::string> 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<StringTarget, IntTarget>;
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FBTarget
|
void SerializeTarget(picojson::object& json_obj, const AnyTarget& target);
|
||||||
{
|
bool DeserializeTarget(const picojson::object& json_obj, AnyTarget& target);
|
||||||
u32 m_height = 0;
|
} // namespace GraphicsModSystem::Config
|
||||||
u32 m_width = 0;
|
|
||||||
TextureFormat m_texture_format = TextureFormat::I4;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EFBTarget final : public FBTarget
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
struct XFBTarget final : public FBTarget
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ProjectionTarget
|
|
||||||
{
|
|
||||||
std::optional<std::string> m_texture_info_string;
|
|
||||||
ProjectionType m_projection_type = ProjectionType::Perspective;
|
|
||||||
};
|
|
||||||
|
|
||||||
using GraphicsTargetConfig =
|
|
||||||
std::variant<DrawStartedTextureTarget, LoadTextureTarget, CreateTextureTarget, EFBTarget,
|
|
||||||
XFBTarget, ProjectionTarget>;
|
|
||||||
|
|
||||||
void SerializeTargetToConfig(picojson::object& json_obj, const GraphicsTargetConfig& target);
|
|
||||||
std::optional<GraphicsTargetConfig> DeserializeTargetFromConfig(const picojson::object& obj);
|
|
||||||
|
|
||||||
void SerializeTargetToProfile(picojson::object* obj, const GraphicsTargetConfig& target);
|
|
||||||
void DeserializeTargetFromProfile(const picojson::object& obj, GraphicsTargetConfig* target);
|
|
||||||
|
|
|
@ -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<std::string>())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO, "Failed to load mod configuration file, specified group's name is not a string");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_name = name_iter->second.get<std::string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto targets_iter = obj.find("targets"); targets_iter != obj.end())
|
|
||||||
{
|
|
||||||
if (!targets_iter->second.is<picojson::array>())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO,
|
|
||||||
"Failed to load mod configuration file, specified group's targets is not an array");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const auto& target_val : targets_iter->second.get<picojson::array>())
|
|
||||||
{
|
|
||||||
if (!target_val.is<picojson::object>())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(
|
|
||||||
VIDEO,
|
|
||||||
"Failed to load shader configuration file, specified target is not a json object");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const auto target = DeserializeTargetFromConfig(target_val.get<picojson::object>());
|
|
||||||
if (!target)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_targets.push_back(*target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsTargetGroupConfig::SerializeToProfile(picojson::object* obj) const
|
|
||||||
{
|
|
||||||
if (!obj)
|
|
||||||
return;
|
|
||||||
auto& json_obj = *obj;
|
|
||||||
picojson::array serialized_targets;
|
|
||||||
for (const auto& target : m_targets)
|
|
||||||
{
|
|
||||||
picojson::object serialized_target;
|
|
||||||
SerializeTargetToProfile(&serialized_target, target);
|
|
||||||
serialized_targets.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<picojson::array>())
|
|
||||||
{
|
|
||||||
const auto& serialized_targets = it->second.get<picojson::array>();
|
|
||||||
if (serialized_targets.size() != m_targets.size())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < serialized_targets.size(); i++)
|
|
||||||
{
|
|
||||||
const auto& serialized_target_val = serialized_targets[i];
|
|
||||||
if (serialized_target_val.is<picojson::object>())
|
|
||||||
{
|
|
||||||
const auto& serialized_target = serialized_target_val.get<picojson::object>();
|
|
||||||
DeserializeTargetFromProfile(serialized_target, &m_targets[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright 2022 Dolphin Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <picojson.h>
|
|
||||||
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"
|
|
||||||
|
|
||||||
struct GraphicsTargetGroupConfig
|
|
||||||
{
|
|
||||||
std::string m_name;
|
|
||||||
std::vector<GraphicsTargetConfig> m_targets;
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
|
@ -4,8 +4,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include "Common/CommonPaths.h"
|
#include "Common/CommonPaths.h"
|
||||||
|
|
||||||
static const inline std::string DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR =
|
static const inline std::string DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR =
|
||||||
LOAD_DIR DIR_SEP GRAPHICSMOD_DIR DIR_SEP;
|
LOAD_DIR DIR_SEP GRAPHICSMOD_DIR DIR_SEP;
|
||||||
|
|
||||||
|
namespace GraphicsModSystem
|
||||||
|
{
|
||||||
|
static constexpr std::string_view internal_tag_prefix = "__internal_";
|
||||||
|
}
|
||||||
|
|
|
@ -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<int>& 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<GraphicsModAction*> actions, DrawCallID draw_call)
|
||||||
|
{
|
||||||
|
bool skip = false;
|
||||||
|
std::optional<Common::Matrix44> custom_transform;
|
||||||
|
GraphicsModSystem::MaterialResource* material_resource = nullptr;
|
||||||
|
GraphicsModSystem::MeshResource* mesh_resource = nullptr;
|
||||||
|
bool ignore_mesh_transform = false;
|
||||||
|
std::optional<GraphicsModSystem::Camera> 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
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#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<int>& 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<GraphicsModAction*> 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
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <xxh3.h>
|
||||||
|
|
||||||
|
#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<XXH64_hash_t>(1));
|
||||||
|
|
||||||
|
XXH3_state_t material_hash_state;
|
||||||
|
XXH3_INITSTATE(&material_hash_state);
|
||||||
|
XXH3_64bits_reset_withSeed(&material_hash_state, static_cast<XXH64_hash_t>(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<u32>(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<std::string_view> 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<DrawCallID>(XXH3_64bits_digest(&draw_hash_state));
|
||||||
|
output.material_id = static_cast<MaterialID>(XXH3_64bits_digest(&material_hash_state));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
} // namespace GraphicsModSystem::Runtime
|
|
@ -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
|
|
@ -3,80 +3,16 @@
|
||||||
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "Common/Logging/Log.h"
|
|
||||||
#include "Common/StringUtil.h"
|
|
||||||
#include "Common/VariantUtil.h"
|
|
||||||
|
|
||||||
#include "Core/ConfigManager.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/Config/GraphicsModGroup.h"
|
||||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
|
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h"
|
||||||
#include "VideoCommon/TextureInfo.h"
|
|
||||||
#include "VideoCommon/VideoConfig.h"
|
#include "VideoCommon/VideoConfig.h"
|
||||||
|
|
||||||
std::unique_ptr<GraphicsModManager> g_graphics_mod_manager;
|
namespace GraphicsModSystem::Runtime
|
||||||
|
|
||||||
class GraphicsModManager::DecoratedAction final : public GraphicsModAction
|
|
||||||
{
|
{
|
||||||
public:
|
GraphicsModManager::GraphicsModManager() = default;
|
||||||
DecoratedAction(std::unique_ptr<GraphicsModAction> action, GraphicsModConfig mod)
|
GraphicsModManager::~GraphicsModManager() = default;
|
||||||
: 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<GraphicsModAction> m_action_impl;
|
|
||||||
GraphicsModConfig m_mod;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool GraphicsModManager::Initialize()
|
bool GraphicsModManager::Initialize()
|
||||||
{
|
{
|
||||||
|
@ -90,281 +26,64 @@ bool GraphicsModManager::Initialize()
|
||||||
const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ?
|
const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ?
|
||||||
g_ActiveConfig.graphics_mod_config->GetChangeCount() :
|
g_ActiveConfig.graphics_mod_config->GetChangeCount() :
|
||||||
0;
|
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->Load();
|
||||||
g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes);
|
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 =
|
m_frame_present_event = AfterPresentEvent::Register(
|
||||||
AfterFrameEvent::Register([this](Core::System&) { EndOfFrame(); }, "ModManager");
|
[this](const PresentInfo& present_info) { OnFramePresented(present_info); }, "ModManager");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GraphicsModAction*>&
|
void GraphicsModManager::Load(const Config::GraphicsModGroup& config)
|
||||||
GraphicsModManager::GetProjectionActions(ProjectionType projection_type) const
|
|
||||||
{
|
{
|
||||||
if (const auto it = m_projection_target_to_actions.find(projection_type);
|
m_backend = std::make_unique<GraphicsModRuntimeBackend>(config);
|
||||||
it != m_projection_target_to_actions.end())
|
|
||||||
{
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GraphicsModAction*>&
|
const GraphicsModBackend& GraphicsModManager::GetBackend() const
|
||||||
GraphicsModManager::GetProjectionTextureActions(ProjectionType projection_type,
|
|
||||||
const std::string& texture_name) const
|
|
||||||
{
|
{
|
||||||
const auto lookup = fmt::format("{}_{}", texture_name, static_cast<int>(projection_type));
|
if (m_editor_backend)
|
||||||
if (const auto it = m_projection_texture_target_to_actions.find(lookup);
|
return *m_editor_backend;
|
||||||
it != m_projection_texture_target_to_actions.end())
|
|
||||||
{
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_default;
|
return *m_backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GraphicsModAction*>&
|
GraphicsModBackend& GraphicsModManager::GetBackend()
|
||||||
GraphicsModManager::GetDrawStartedActions(const std::string& texture_name) const
|
|
||||||
{
|
{
|
||||||
if (const auto it = m_draw_started_target_to_actions.find(texture_name);
|
if (m_editor_backend)
|
||||||
it != m_draw_started_target_to_actions.end())
|
return *m_editor_backend;
|
||||||
{
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_default;
|
return *m_backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GraphicsModAction*>&
|
void GraphicsModManager::SetEditorBackend(std::unique_ptr<GraphicsModBackend> editor_backend)
|
||||||
GraphicsModManager::GetTextureLoadActions(const std::string& texture_name) const
|
|
||||||
{
|
{
|
||||||
if (const auto it = m_load_texture_target_to_actions.find(texture_name);
|
m_editor_backend = std::move(editor_backend);
|
||||||
it != m_load_texture_target_to_actions.end())
|
|
||||||
{
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GraphicsModAction*>&
|
bool GraphicsModManager::IsEditorEnabled() const
|
||||||
GraphicsModManager::GetTextureCreateActions(const std::string& texture_name) const
|
|
||||||
{
|
{
|
||||||
if (const auto it = m_create_texture_target_to_actions.find(texture_name);
|
return true;
|
||||||
it != m_create_texture_target_to_actions.end())
|
// return m_editor_backend.get() != nullptr;
|
||||||
{
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GraphicsModAction*>& 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);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
return m_default;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<GraphicsModAction*>& GraphicsModManager::GetXFBActions(const FBInfo& xfb) const
|
|
||||||
{
|
|
||||||
if (const auto it = m_efb_target_to_actions.find(xfb); it != m_efb_target_to_actions.end())
|
|
||||||
{
|
{
|
||||||
return it->second;
|
m_backend->OnFramePresented(present_info);
|
||||||
}
|
|
||||||
|
|
||||||
return m_default;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
|
|
||||||
{
|
|
||||||
Reset();
|
|
||||||
|
|
||||||
const auto& mods = config.GetMods();
|
|
||||||
|
|
||||||
auto filesystem_library = std::make_shared<VideoCommon::DirectFilesystemAssetLibrary>();
|
|
||||||
|
|
||||||
std::map<std::string, std::vector<GraphicsTargetConfig>> group_to_targets;
|
|
||||||
for (const auto& mod : mods)
|
|
||||||
{
|
|
||||||
for (const GraphicsTargetGroupConfig& group : mod.m_groups)
|
|
||||||
{
|
|
||||||
if (m_groups.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<GraphicsModAction> {
|
|
||||||
auto action =
|
|
||||||
GraphicsModActionFactory::Create(action_name, json_data, std::move(filesystem_library));
|
|
||||||
if (action == nullptr)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return std::make_unique<DecoratedAction>(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<int>(the_target.m_projection_type));
|
|
||||||
m_projection_texture_target_to_actions[lookup].push_back(
|
|
||||||
m_actions.back().get());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_projection_target_to_actions[the_target.m_projection_type].push_back(
|
|
||||||
m_actions.back().get());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
target);
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // namespace GraphicsModSystem::Runtime
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,62 +3,39 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"
|
#include "Common/HookableEvent.h"
|
||||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h"
|
||||||
#include "VideoCommon/TextureInfo.h"
|
|
||||||
#include "VideoCommon/VideoEvents.h"
|
|
||||||
#include "VideoCommon/XFMemory.h"
|
|
||||||
|
|
||||||
class GraphicsModGroupConfig;
|
namespace GraphicsModSystem::Config
|
||||||
|
{
|
||||||
|
class GraphicsModGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GraphicsModSystem::Runtime
|
||||||
|
{
|
||||||
|
class GraphicsModRuntimeBackend;
|
||||||
class GraphicsModManager
|
class GraphicsModManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
GraphicsModManager();
|
||||||
|
~GraphicsModManager();
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
void Load(const GraphicsModSystem::Config::GraphicsModGroup& config);
|
||||||
|
|
||||||
const std::vector<GraphicsModAction*>& GetProjectionActions(ProjectionType projection_type) const;
|
const GraphicsModBackend& GetBackend() const;
|
||||||
const std::vector<GraphicsModAction*>&
|
GraphicsModBackend& GetBackend();
|
||||||
GetProjectionTextureActions(ProjectionType projection_type,
|
|
||||||
const std::string& texture_name) const;
|
|
||||||
const std::vector<GraphicsModAction*>&
|
|
||||||
GetDrawStartedActions(const std::string& texture_name) const;
|
|
||||||
const std::vector<GraphicsModAction*>&
|
|
||||||
GetTextureLoadActions(const std::string& texture_name) const;
|
|
||||||
const std::vector<GraphicsModAction*>&
|
|
||||||
GetTextureCreateActions(const std::string& texture_name) const;
|
|
||||||
const std::vector<GraphicsModAction*>& GetEFBActions(const FBInfo& efb) const;
|
|
||||||
const std::vector<GraphicsModAction*>& GetXFBActions(const FBInfo& xfb) const;
|
|
||||||
|
|
||||||
void Load(const GraphicsModGroupConfig& config);
|
void SetEditorBackend(std::unique_ptr<GraphicsModBackend> editor_backend);
|
||||||
|
bool IsEditorEnabled() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void EndOfFrame();
|
void OnFramePresented(const PresentInfo& present_info);
|
||||||
void Reset();
|
|
||||||
|
|
||||||
class DecoratedAction;
|
std::unique_ptr<GraphicsModRuntimeBackend> m_backend;
|
||||||
|
std::unique_ptr<GraphicsModBackend> m_editor_backend;
|
||||||
static inline const std::vector<GraphicsModAction*> m_default = {};
|
Common::EventHook m_frame_present_event;
|
||||||
std::list<std::unique_ptr<GraphicsModAction>> m_actions;
|
|
||||||
std::unordered_map<ProjectionType, std::vector<GraphicsModAction*>>
|
|
||||||
m_projection_target_to_actions;
|
|
||||||
std::unordered_map<std::string, std::vector<GraphicsModAction*>>
|
|
||||||
m_projection_texture_target_to_actions;
|
|
||||||
std::unordered_map<std::string, std::vector<GraphicsModAction*>> m_draw_started_target_to_actions;
|
|
||||||
std::unordered_map<std::string, std::vector<GraphicsModAction*>> m_load_texture_target_to_actions;
|
|
||||||
std::unordered_map<std::string, std::vector<GraphicsModAction*>>
|
|
||||||
m_create_texture_target_to_actions;
|
|
||||||
std::unordered_map<FBInfo, std::vector<GraphicsModAction*>, FBInfoHasher> m_efb_target_to_actions;
|
|
||||||
std::unordered_map<FBInfo, std::vector<GraphicsModAction*>, FBInfoHasher> m_xfb_target_to_actions;
|
|
||||||
|
|
||||||
std::unordered_set<std::string> m_groups;
|
|
||||||
|
|
||||||
Common::EventHook m_end_of_frame_event;
|
|
||||||
};
|
};
|
||||||
|
} // namespace GraphicsModSystem::Runtime
|
||||||
extern std::unique_ptr<GraphicsModManager> g_graphics_mod_manager;
|
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#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<VideoCommon::DirectFilesystemAssetLibrary>();
|
||||||
|
|
||||||
|
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<std::string, std::vector<GraphicsModAction*>> 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<DrawCallID, std::string> m_id;
|
||||||
|
const std::vector<std::string>* m_tag_names;
|
||||||
|
};
|
||||||
|
std::vector<IDWithTags> 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<GraphicsModAction*>{});
|
||||||
|
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<GraphicsModAction*>{});
|
||||||
|
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
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<DrawCallID, std::vector<GraphicsModAction*>> m_draw_id_to_actions;
|
||||||
|
|
||||||
|
struct string_hash
|
||||||
|
{
|
||||||
|
using is_transparent = void;
|
||||||
|
[[nodiscard]] size_t operator()(const char* txt) const
|
||||||
|
{
|
||||||
|
return std::hash<std::string_view>{}(txt);
|
||||||
|
}
|
||||||
|
[[nodiscard]] size_t operator()(std::string_view txt) const
|
||||||
|
{
|
||||||
|
return std::hash<std::string_view>{}(txt);
|
||||||
|
}
|
||||||
|
[[nodiscard]] size_t operator()(const std::string& txt) const
|
||||||
|
{
|
||||||
|
return std::hash<std::string>{}(txt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::vector<GraphicsModAction*>, string_hash, std::equal_to<>>
|
||||||
|
m_str_id_to_actions;
|
||||||
|
std::vector<std::unique_ptr<GraphicsModAction>> m_actions;
|
||||||
|
};
|
||||||
|
std::vector<GraphicsMod> m_mods;
|
||||||
|
};
|
||||||
|
} // namespace GraphicsModSystem::Runtime
|
Loading…
Add table
Add a link
Reference in a new issue