VideoCommon: graphics mods 2.0!

This commit is contained in:
iwubcode 2024-04-09 22:30:20 -05:00
commit f79154b6f5
27 changed files with 1511 additions and 1268 deletions

View file

@ -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>());
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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&)
{
}

View file

@ -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);
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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]);
}
}
}
}
}

View file

@ -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);
};

View file

@ -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_";
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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();
}

View file

@ -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;

View file

@ -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

View file

@ -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