mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-24 03:11:09 +00:00
VideoCommon: graphics mods 2.0!
This commit is contained in:
parent
a6a1c2b129
commit
f79154b6f5
27 changed files with 1511 additions and 1268 deletions
|
@ -13,8 +13,9 @@
|
|||
|
||||
#include "VideoCommon/GraphicsModSystem/Constants.h"
|
||||
|
||||
std::optional<GraphicsModConfig> GraphicsModConfig::Create(const std::string& file_path,
|
||||
Source source)
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
std::optional<GraphicsMod> GraphicsMod::Create(const std::string& file_path)
|
||||
{
|
||||
picojson::value root;
|
||||
std::string error;
|
||||
|
@ -25,186 +26,112 @@ std::optional<GraphicsModConfig> GraphicsModConfig::Create(const std::string& fi
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
GraphicsModConfig result;
|
||||
if (!result.DeserializeFromConfig(root))
|
||||
GraphicsMod result;
|
||||
if (!result.Deserialize(root))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
result.m_source = source;
|
||||
if (source == Source::User)
|
||||
{
|
||||
const std::string base_path = File::GetUserPath(D_GRAPHICSMOD_IDX);
|
||||
if (base_path.size() > file_path.size())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load graphics mod json file '{}' due to it not matching the base path: {}",
|
||||
file_path, base_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
result.m_relative_path = file_path.substr(base_path.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string base_path = File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR;
|
||||
if (base_path.size() > file_path.size())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load graphics mod json file '{}' due to it not matching the base path: {}",
|
||||
file_path, base_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
result.m_relative_path = file_path.substr(base_path.size());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<GraphicsModConfig> GraphicsModConfig::Create(const picojson::object* obj)
|
||||
{
|
||||
if (!obj)
|
||||
return std::nullopt;
|
||||
|
||||
const auto source_it = obj->find("source");
|
||||
if (source_it == obj->end())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string source_str = source_it->second.to_str();
|
||||
|
||||
const auto path_it = obj->find("path");
|
||||
if (path_it == obj->end())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string relative_path = path_it->second.to_str();
|
||||
|
||||
if (source_str == "system")
|
||||
{
|
||||
return Create(fmt::format("{}{}{}", File::GetSysDirectory(), DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR,
|
||||
relative_path),
|
||||
Source::System);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Create(File::GetUserPath(D_GRAPHICSMOD_IDX) + relative_path, Source::User);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GraphicsModConfig::GetAbsolutePath() const
|
||||
{
|
||||
if (m_source == Source::System)
|
||||
{
|
||||
return WithUnifiedPathSeparators(fmt::format("{}{}{}", File::GetSysDirectory(),
|
||||
DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_relative_path));
|
||||
}
|
||||
else
|
||||
{
|
||||
return WithUnifiedPathSeparators(File::GetUserPath(D_GRAPHICSMOD_IDX) + m_relative_path);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsModConfig::SerializeToConfig(picojson::object& json_obj) const
|
||||
void GraphicsMod::Serialize(picojson::object& json_obj) const
|
||||
{
|
||||
picojson::object serialized_metadata;
|
||||
serialized_metadata.emplace("schema_version", static_cast<double>(m_schema_version));
|
||||
serialized_metadata.emplace("title", m_title);
|
||||
serialized_metadata.emplace("author", m_author);
|
||||
serialized_metadata.emplace("description", m_description);
|
||||
serialized_metadata.emplace("mod_version", m_mod_version);
|
||||
json_obj.emplace("meta", std::move(serialized_metadata));
|
||||
|
||||
picojson::array serialized_groups;
|
||||
for (const auto& group : m_groups)
|
||||
{
|
||||
picojson::object serialized_group;
|
||||
group.SerializeToConfig(serialized_group);
|
||||
serialized_groups.emplace_back(std::move(serialized_group));
|
||||
}
|
||||
json_obj.emplace("groups", std::move(serialized_groups));
|
||||
|
||||
picojson::array serialized_features;
|
||||
for (const auto& feature : m_features)
|
||||
{
|
||||
picojson::object serialized_feature;
|
||||
feature.SerializeToConfig(serialized_feature);
|
||||
serialized_features.emplace_back(std::move(serialized_feature));
|
||||
}
|
||||
json_obj.emplace("features", std::move(serialized_features));
|
||||
|
||||
picojson::array serialized_assets;
|
||||
for (const auto& asset : m_assets)
|
||||
{
|
||||
picojson::object serialized_asset;
|
||||
asset.SerializeToConfig(serialized_asset);
|
||||
asset.Serialize(serialized_asset);
|
||||
serialized_assets.emplace_back(std::move(serialized_asset));
|
||||
}
|
||||
json_obj.emplace("assets", std::move(serialized_assets));
|
||||
|
||||
picojson::array serialized_tags;
|
||||
for (const auto& tag : m_tags)
|
||||
{
|
||||
picojson::object serialized_tag;
|
||||
tag.Serialize(serialized_tag);
|
||||
serialized_tags.emplace_back(std::move(serialized_tag));
|
||||
}
|
||||
json_obj.emplace("tags", std::move(serialized_tags));
|
||||
|
||||
picojson::array serialized_targets;
|
||||
for (const auto& target : m_targets)
|
||||
{
|
||||
picojson::object serialized_target;
|
||||
SerializeTarget(serialized_target, target);
|
||||
serialized_targets.emplace_back(std::move(serialized_target));
|
||||
}
|
||||
json_obj.emplace("targets", std::move(serialized_targets));
|
||||
|
||||
picojson::array serialized_actions;
|
||||
for (const auto& action : m_actions)
|
||||
{
|
||||
picojson::object serialized_action;
|
||||
action.Serialize(serialized_action);
|
||||
serialized_actions.emplace_back(std::move(serialized_action));
|
||||
}
|
||||
json_obj.emplace("actions", std::move(serialized_actions));
|
||||
|
||||
picojson::object serialized_target_to_actions;
|
||||
for (const auto& [target_index, action_indexes] : m_target_index_to_action_indexes)
|
||||
{
|
||||
picojson::array serialized_action_indexes;
|
||||
for (const auto& action_index : action_indexes)
|
||||
{
|
||||
serialized_action_indexes.emplace_back(static_cast<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");
|
||||
if (meta.is<picojson::object>())
|
||||
{
|
||||
const auto& title = meta.get("title");
|
||||
if (title.is<std::string>())
|
||||
const auto& meta_obj = meta.get<picojson::object>();
|
||||
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");
|
||||
if (author.is<std::string>())
|
||||
{
|
||||
m_author = author.to_str();
|
||||
}
|
||||
|
||||
const auto& description = meta.get("description");
|
||||
if (description.is<std::string>())
|
||||
{
|
||||
m_description = description.to_str();
|
||||
}
|
||||
}
|
||||
|
||||
const auto& groups = value.get("groups");
|
||||
if (groups.is<picojson::array>())
|
||||
{
|
||||
for (const auto& group_val : groups.get<picojson::array>())
|
||||
{
|
||||
if (!group_val.is<picojson::object>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO, "Failed to load mod configuration file, specified group is not a json object");
|
||||
return false;
|
||||
}
|
||||
GraphicsTargetGroupConfig group;
|
||||
if (!group.DeserializeFromConfig(group_val.get<picojson::object>()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_groups.push_back(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));
|
||||
}
|
||||
m_title = ReadStringFromJson(meta_obj, "title").value_or("Unknown Mod");
|
||||
m_author = ReadStringFromJson(meta_obj, "author").value_or("Unknown");
|
||||
m_description = ReadStringFromJson(meta_obj, "description").value_or("");
|
||||
m_mod_version = ReadStringFromJson(meta_obj, "mod_version").value_or("v0.0.0");
|
||||
}
|
||||
|
||||
const auto& assets = value.get("assets");
|
||||
|
@ -218,8 +145,8 @@ bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value)
|
|||
VIDEO, "Failed to load mod configuration file, specified asset is not a json object");
|
||||
return false;
|
||||
}
|
||||
GraphicsModAssetConfig asset;
|
||||
if (!asset.DeserializeFromConfig(asset_val.get<picojson::object>()))
|
||||
GraphicsModAsset asset;
|
||||
if (!asset.Deserialize(asset_val.get<picojson::object>()))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace GraphicsModSystem::Config
|
||||
|
|
|
@ -3,44 +3,48 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
struct GraphicsModConfig
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"
|
||||
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
// Update this version when the hashing approach or data
|
||||
// changes
|
||||
static constexpr u16 LATEST_SCHEMA_VERSION = 1;
|
||||
|
||||
struct GraphicsMod
|
||||
{
|
||||
u16 m_schema_version = LATEST_SCHEMA_VERSION;
|
||||
|
||||
std::string m_title;
|
||||
std::string m_author;
|
||||
std::string m_description;
|
||||
bool m_enabled = false;
|
||||
u16 m_weight = 0;
|
||||
std::string m_relative_path;
|
||||
std::string m_mod_version;
|
||||
|
||||
enum class Source
|
||||
{
|
||||
User,
|
||||
System
|
||||
};
|
||||
Source m_source = Source::User;
|
||||
std::vector<GraphicsModAsset> m_assets;
|
||||
std::vector<GraphicsModTag> m_tags;
|
||||
std::vector<AnyTarget> m_targets;
|
||||
std::vector<GraphicsModAction> m_actions;
|
||||
std::map<u64, std::vector<u64>> m_target_index_to_action_indexes;
|
||||
std::map<std::string, std::vector<u64>> m_tag_name_to_action_indexes;
|
||||
|
||||
std::vector<GraphicsTargetGroupConfig> m_groups;
|
||||
std::vector<GraphicsModFeatureConfig> m_features;
|
||||
std::vector<GraphicsModAssetConfig> m_assets;
|
||||
HashPolicy m_default_hash_policy;
|
||||
|
||||
static std::optional<GraphicsModConfig> Create(const std::string& file, Source source);
|
||||
static std::optional<GraphicsModConfig> Create(const picojson::object* obj);
|
||||
static std::optional<GraphicsMod> Create(const std::string& file);
|
||||
|
||||
std::string GetAbsolutePath() const;
|
||||
|
||||
void SerializeToConfig(picojson::object& json_obj) const;
|
||||
bool DeserializeFromConfig(const picojson::value& value);
|
||||
|
||||
void SerializeToProfile(picojson::object* value) const;
|
||||
void DeserializeFromProfile(const picojson::object& value);
|
||||
void Serialize(picojson::object& json_obj) const;
|
||||
bool Deserialize(const picojson::value& value);
|
||||
};
|
||||
} // namespace GraphicsModSystem::Config
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h"
|
||||
|
||||
#include "Common/JsonUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
void GraphicsModAction::Serialize(picojson::object& json_obj) const
|
||||
{
|
||||
json_obj.emplace("factory_name", m_factory_name);
|
||||
json_obj.emplace("data", m_data);
|
||||
}
|
||||
|
||||
bool GraphicsModAction::Deserialize(const picojson::object& obj)
|
||||
{
|
||||
const auto factory_name = ReadStringFromJson(obj, "factory_name");
|
||||
if (!factory_name)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load mod configuration file, specified action's factory_name is not valid");
|
||||
return false;
|
||||
}
|
||||
m_factory_name = *factory_name;
|
||||
|
||||
if (auto data_iter = obj.find("data"); data_iter != obj.end())
|
||||
{
|
||||
m_data = data_iter->second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace GraphicsModSystem::Config
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
struct GraphicsModAction
|
||||
{
|
||||
std::string m_factory_name;
|
||||
picojson::value m_data;
|
||||
|
||||
void Serialize(picojson::object& json_obj) const;
|
||||
bool Deserialize(const picojson::object& json_obj);
|
||||
};
|
||||
} // namespace GraphicsModSystem::Config
|
|
@ -3,12 +3,15 @@
|
|||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
||||
|
||||
#include "Common/JsonUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
void GraphicsModAssetConfig::SerializeToConfig(picojson::object& json_obj) const
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
json_obj.emplace("name", m_asset_id);
|
||||
void GraphicsModAsset::Serialize(picojson::object& json_obj) const
|
||||
{
|
||||
json_obj.emplace("id", m_asset_id);
|
||||
|
||||
picojson::object serialized_data;
|
||||
for (const auto& [name, path] : m_map)
|
||||
|
@ -18,21 +21,16 @@ void GraphicsModAssetConfig::SerializeToConfig(picojson::object& json_obj) const
|
|||
json_obj.emplace("data", std::move(serialized_data));
|
||||
}
|
||||
|
||||
bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj)
|
||||
bool GraphicsModAsset::Deserialize(const picojson::object& obj)
|
||||
{
|
||||
auto name_iter = obj.find("name");
|
||||
if (name_iter == obj.end())
|
||||
const auto id = ReadStringFromJson(obj, "id");
|
||||
if (!id)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has no name");
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has an id "
|
||||
"that is not valid");
|
||||
return false;
|
||||
}
|
||||
if (!name_iter->second.is<std::string>())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has a name "
|
||||
"that is not a string");
|
||||
return false;
|
||||
}
|
||||
m_asset_id = name_iter->second.to_str();
|
||||
m_asset_id = *id;
|
||||
|
||||
auto data_iter = obj.find("data");
|
||||
if (data_iter == obj.end())
|
||||
|
@ -64,3 +62,4 @@ bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj)
|
|||
|
||||
return true;
|
||||
}
|
||||
} // namespace GraphicsModSystem::Config
|
||||
|
|
|
@ -9,11 +9,14 @@
|
|||
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
|
||||
struct GraphicsModAssetConfig
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
struct GraphicsModAsset
|
||||
{
|
||||
VideoCommon::CustomAssetLibrary::AssetID m_asset_id;
|
||||
VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map;
|
||||
|
||||
void SerializeToConfig(picojson::object& json_obj) const;
|
||||
bool DeserializeFromConfig(const picojson::object& obj);
|
||||
void Serialize(picojson::object& json_obj) const;
|
||||
bool Deserialize(const picojson::object& json_obj);
|
||||
};
|
||||
} // namespace GraphicsModSystem::Config
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h"
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
void GraphicsModFeatureConfig::SerializeToConfig(picojson::object& json_obj) const
|
||||
{
|
||||
json_obj.emplace("group", m_group);
|
||||
json_obj.emplace("action", m_action);
|
||||
json_obj.emplace("action_data", m_action_data);
|
||||
}
|
||||
|
||||
bool GraphicsModFeatureConfig::DeserializeFromConfig(const picojson::object& obj)
|
||||
{
|
||||
if (auto group_iter = obj.find("group"); group_iter != obj.end())
|
||||
{
|
||||
if (!group_iter->second.is<std::string>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load mod configuration file, specified feature's group is not a string");
|
||||
return false;
|
||||
}
|
||||
m_group = group_iter->second.get<std::string>();
|
||||
}
|
||||
|
||||
if (auto action_iter = obj.find("action"); action_iter != obj.end())
|
||||
{
|
||||
if (!action_iter->second.is<std::string>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load mod configuration file, specified feature's action is not a string");
|
||||
return false;
|
||||
}
|
||||
m_action = action_iter->second.get<std::string>();
|
||||
}
|
||||
|
||||
if (auto action_data_iter = obj.find("action_data"); action_data_iter != obj.end())
|
||||
{
|
||||
m_action_data = action_data_iter->second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GraphicsModFeatureConfig::SerializeToProfile(picojson::object*) const
|
||||
{
|
||||
}
|
||||
|
||||
void GraphicsModFeatureConfig::DeserializeFromProfile(const picojson::object&)
|
||||
{
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
struct GraphicsModFeatureConfig
|
||||
{
|
||||
std::string m_group;
|
||||
std::string m_action;
|
||||
picojson::value m_action_data;
|
||||
|
||||
void SerializeToConfig(picojson::object& json_obj) const;
|
||||
bool DeserializeFromConfig(const picojson::object& value);
|
||||
|
||||
void SerializeToProfile(picojson::object* value) const;
|
||||
void DeserializeFromProfile(const picojson::object& value);
|
||||
};
|
|
@ -8,6 +8,7 @@
|
|||
#include <string>
|
||||
|
||||
#include <picojson.h>
|
||||
#include <xxh3.h>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/FileSearch.h"
|
||||
|
@ -15,49 +16,184 @@
|
|||
#include "Common/JsonUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/VariantUtil.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Constants.h"
|
||||
#include "VideoCommon/HiresTextures.h"
|
||||
|
||||
GraphicsModGroupConfig::GraphicsModGroupConfig(std::string game_id) : m_game_id(std::move(game_id))
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
GraphicsModGroup::GraphicsModGroup(std::string game_id) : m_game_id(std::move(game_id))
|
||||
{
|
||||
}
|
||||
|
||||
GraphicsModGroupConfig::~GraphicsModGroupConfig() = default;
|
||||
|
||||
GraphicsModGroupConfig::GraphicsModGroupConfig(const GraphicsModGroupConfig&) = default;
|
||||
|
||||
GraphicsModGroupConfig::GraphicsModGroupConfig(GraphicsModGroupConfig&&) noexcept = default;
|
||||
|
||||
GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(const GraphicsModGroupConfig&) = default;
|
||||
|
||||
GraphicsModGroupConfig&
|
||||
GraphicsModGroupConfig::operator=(GraphicsModGroupConfig&&) noexcept = default;
|
||||
|
||||
void GraphicsModGroupConfig::Load()
|
||||
void GraphicsModGroup::Load()
|
||||
{
|
||||
const std::string file_path = GetPath();
|
||||
struct GraphicsModWithDir
|
||||
{
|
||||
Config::GraphicsMod m_mod;
|
||||
std::string m_path;
|
||||
};
|
||||
std::vector<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 (File::Exists(file_path))
|
||||
if (auto mod = GraphicsMod::Create(file))
|
||||
{
|
||||
if (mod->m_actions.empty() && mod->m_targets.empty())
|
||||
return;
|
||||
|
||||
// Actions can be empty for mods that just define
|
||||
// targets for use by other mods
|
||||
// These are mainly used by Dolphin default mods
|
||||
if (mod->m_actions.empty())
|
||||
{
|
||||
GraphicsModWithDir mod_with_dir;
|
||||
mod_with_dir.m_mod = std::move(*mod);
|
||||
mod_with_dir.m_path = dir;
|
||||
target_only_mods.push_back(std::move(mod_with_dir));
|
||||
return;
|
||||
}
|
||||
|
||||
// Targets can be empty for mods that define some actions
|
||||
// and a tag
|
||||
if (mod->m_targets.empty())
|
||||
{
|
||||
GraphicsModWithDir mod_with_dir;
|
||||
mod_with_dir.m_mod = std::move(*mod);
|
||||
mod_with_dir.m_path = dir;
|
||||
action_only_mods.push_back(std::move(mod_with_dir));
|
||||
return;
|
||||
}
|
||||
|
||||
std::string file_data;
|
||||
if (!File::ReadFileToString(file, file_data))
|
||||
return;
|
||||
GraphicsModGroup::GraphicsModWithMetadata mod_with_metadata;
|
||||
mod_with_metadata.m_path = dir;
|
||||
mod_with_metadata.m_mod = std::move(*mod);
|
||||
mod_with_metadata.m_id = XXH3_64bits(file_data.data(), file_data.size());
|
||||
m_graphics_mods.push_back(std::move(mod_with_metadata));
|
||||
}
|
||||
};
|
||||
|
||||
const std::set<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;
|
||||
std::string error;
|
||||
if (!JsonFromFile(file_path, &root, &error))
|
||||
if (!JsonFromFile(gameid_metadata, &root, &error))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load graphics mod group json file '{}' due to parse error: {}",
|
||||
file_path, error);
|
||||
gameid_metadata, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!root.is<picojson::object>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load graphics mod group json file '{}' due to root not being an object!",
|
||||
file_path);
|
||||
gameid_metadata);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -69,106 +205,73 @@ void GraphicsModGroupConfig::Load()
|
|||
if (mod_json.is<picojson::object>())
|
||||
{
|
||||
const auto& mod_json_obj = mod_json.get<picojson::object>();
|
||||
auto graphics_mod = GraphicsModConfig::Create(&mod_json_obj);
|
||||
if (!graphics_mod)
|
||||
const auto id_str = ReadStringFromJson(mod_json_obj, "id");
|
||||
if (!id_str)
|
||||
continue;
|
||||
u64 id;
|
||||
if (!TryParse(*id_str, &id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
graphics_mod->DeserializeFromProfile(mod_json_obj);
|
||||
|
||||
auto mod_full_path = graphics_mod->GetAbsolutePath();
|
||||
known_paths.insert(std::move(mod_full_path));
|
||||
m_graphics_mods.push_back(std::move(*graphics_mod));
|
||||
if (const auto iter = m_id_to_graphics_mod.find(id); iter != m_id_to_graphics_mod.end())
|
||||
{
|
||||
iter->second->m_weight = ReadNumericFromJson<u16>(mod_json_obj, "weight").value_or(0);
|
||||
iter->second->m_enabled = ReadBoolFromJson(mod_json_obj, "enabled").value_or(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto try_add_mod = [&known_paths, this](const std::string& dir,
|
||||
GraphicsModConfig::Source source) {
|
||||
auto file = dir + DIR_SEP + "metadata.json";
|
||||
UnifyPathSeparators(file);
|
||||
if (known_paths.contains(file))
|
||||
return;
|
||||
|
||||
if (auto mod = GraphicsModConfig::Create(file, source))
|
||||
m_graphics_mods.push_back(std::move(*mod));
|
||||
};
|
||||
|
||||
const std::set<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;
|
||||
}
|
||||
std::ranges::sort(m_graphics_mods, {}, &GraphicsModGroup::GraphicsModWithMetadata::m_weight);
|
||||
|
||||
m_change_count++;
|
||||
}
|
||||
|
||||
void GraphicsModGroupConfig::Save() const
|
||||
void GraphicsModGroup::Save() const
|
||||
{
|
||||
const std::string file_path = GetPath();
|
||||
std::ofstream json_stream;
|
||||
File::OpenFStream(json_stream, file_path, std::ios_base::out);
|
||||
if (!json_stream.is_open())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path);
|
||||
return;
|
||||
}
|
||||
|
||||
picojson::object serialized_root;
|
||||
picojson::array serialized_mods;
|
||||
for (const auto& mod : m_graphics_mods)
|
||||
for (const auto& [id, mod_ptr] : m_id_to_graphics_mod)
|
||||
{
|
||||
picojson::object serialized_mod;
|
||||
mod.SerializeToProfile(&serialized_mod);
|
||||
serialized_mod.emplace("id", std::to_string(id));
|
||||
serialized_mod.emplace("enabled", mod_ptr->m_enabled);
|
||||
serialized_mod.emplace("weight", static_cast<double>(mod_ptr->m_weight));
|
||||
serialized_mods.emplace_back(std::move(serialized_mod));
|
||||
}
|
||||
serialized_root.emplace("mods", std::move(serialized_mods));
|
||||
|
||||
const auto output = picojson::value{serialized_root}.serialize(true);
|
||||
json_stream << output;
|
||||
const auto file_path = GetPath();
|
||||
if (!JsonToFile(file_path, picojson::value{serialized_root}, true))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsModGroupConfig::SetChangeCount(u32 change_count)
|
||||
void GraphicsModGroup::SetChangeCount(u32 change_count)
|
||||
{
|
||||
m_change_count = change_count;
|
||||
}
|
||||
|
||||
u32 GraphicsModGroupConfig::GetChangeCount() const
|
||||
u32 GraphicsModGroup::GetChangeCount() const
|
||||
{
|
||||
return m_change_count;
|
||||
}
|
||||
|
||||
const std::vector<GraphicsModConfig>& GraphicsModGroupConfig::GetMods() const
|
||||
const std::vector<GraphicsModGroup::GraphicsModWithMetadata>& GraphicsModGroup::GetMods() const
|
||||
{
|
||||
return m_graphics_mods;
|
||||
}
|
||||
|
||||
std::vector<GraphicsModConfig>& GraphicsModGroupConfig::GetMods()
|
||||
std::vector<GraphicsModGroup::GraphicsModWithMetadata>& GraphicsModGroup::GetMods()
|
||||
{
|
||||
return m_graphics_mods;
|
||||
}
|
||||
|
||||
GraphicsModConfig* GraphicsModGroupConfig::GetMod(std::string_view absolute_path) const
|
||||
GraphicsModGroup::GraphicsModWithMetadata* GraphicsModGroup::GetMod(u64 id) const
|
||||
{
|
||||
if (const auto iter = m_path_to_graphics_mod.find(absolute_path);
|
||||
iter != m_path_to_graphics_mod.end())
|
||||
if (const auto iter = m_id_to_graphics_mod.find(id); iter != m_id_to_graphics_mod.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
|
@ -176,13 +279,14 @@ GraphicsModConfig* GraphicsModGroupConfig::GetMod(std::string_view absolute_path
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string& GraphicsModGroupConfig::GetGameID() const
|
||||
const std::string& GraphicsModGroup::GetGameID() const
|
||||
{
|
||||
return m_game_id;
|
||||
}
|
||||
|
||||
std::string GraphicsModGroupConfig::GetPath() const
|
||||
std::string GraphicsModGroup::GetPath() const
|
||||
{
|
||||
const std::string game_mod_root = File::GetUserPath(D_CONFIG_IDX) + GRAPHICSMOD_CONFIG_DIR;
|
||||
return fmt::format("{}/{}.json", game_mod_root, m_game_id);
|
||||
}
|
||||
} // namespace GraphicsModSystem::Config
|
||||
|
|
|
@ -5,24 +5,17 @@
|
|||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
|
||||
|
||||
struct GraphicsModConfig;
|
||||
|
||||
class GraphicsModGroupConfig
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
class GraphicsModGroup
|
||||
{
|
||||
public:
|
||||
explicit GraphicsModGroupConfig(std::string game_id);
|
||||
~GraphicsModGroupConfig();
|
||||
|
||||
GraphicsModGroupConfig(const GraphicsModGroupConfig&);
|
||||
GraphicsModGroupConfig(GraphicsModGroupConfig&&) noexcept;
|
||||
|
||||
GraphicsModGroupConfig& operator=(const GraphicsModGroupConfig&);
|
||||
GraphicsModGroupConfig& operator=(GraphicsModGroupConfig&&) noexcept;
|
||||
explicit GraphicsModGroup(std::string game_id);
|
||||
|
||||
void Load();
|
||||
void Save() const;
|
||||
|
@ -30,17 +23,27 @@ public:
|
|||
void SetChangeCount(u32 change_count);
|
||||
u32 GetChangeCount() const;
|
||||
|
||||
const std::vector<GraphicsModConfig>& GetMods() const;
|
||||
std::vector<GraphicsModConfig>& GetMods();
|
||||
struct GraphicsModWithMetadata
|
||||
{
|
||||
GraphicsMod m_mod;
|
||||
std::string m_path;
|
||||
u64 m_id = 0;
|
||||
bool m_enabled = false;
|
||||
u16 m_weight = 0;
|
||||
};
|
||||
|
||||
GraphicsModConfig* GetMod(std::string_view absolute_path) const;
|
||||
const std::vector<GraphicsModWithMetadata>& GetMods() const;
|
||||
std::vector<GraphicsModWithMetadata>& GetMods();
|
||||
|
||||
GraphicsModWithMetadata* GetMod(u64 id) const;
|
||||
|
||||
const std::string& GetGameID() const;
|
||||
|
||||
private:
|
||||
std::string GetPath() const;
|
||||
std::string m_game_id;
|
||||
std::vector<GraphicsModConfig> m_graphics_mods;
|
||||
std::map<std::string, GraphicsModConfig*, std::less<>> m_path_to_graphics_mod;
|
||||
std::vector<GraphicsModWithMetadata> m_graphics_mods;
|
||||
std::map<u64, GraphicsModWithMetadata*> m_id_to_graphics_mod;
|
||||
u32 m_change_count = 0;
|
||||
};
|
||||
} // namespace GraphicsModSystem::Config
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h"
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool ContainsAttribute(HashAttributes all_attributes, HashAttributes attribute)
|
||||
{
|
||||
return static_cast<std::underlying_type_t<HashAttributes>>(all_attributes & attribute) != 0;
|
||||
}
|
||||
|
||||
HashAttributes GetDefaultHashAttributes()
|
||||
{
|
||||
return HashAttributes::Blending | HashAttributes::Indices | HashAttributes::VertexLayout;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
HashAttributes operator|(HashAttributes lhs, HashAttributes rhs)
|
||||
{
|
||||
return static_cast<HashAttributes>(static_cast<std::underlying_type_t<HashAttributes>>(lhs) |
|
||||
static_cast<std::underlying_type_t<HashAttributes>>(rhs));
|
||||
}
|
||||
|
||||
HashAttributes operator&(HashAttributes lhs, HashAttributes rhs)
|
||||
{
|
||||
return static_cast<HashAttributes>(static_cast<std::underlying_type_t<HashAttributes>>(lhs) &
|
||||
static_cast<std::underlying_type_t<HashAttributes>>(rhs));
|
||||
}
|
||||
|
||||
HashPolicy GetDefaultHashPolicy()
|
||||
{
|
||||
HashPolicy policy;
|
||||
policy.attributes = GetDefaultHashAttributes();
|
||||
policy.first_texture_only = false;
|
||||
policy.version = 1;
|
||||
return policy;
|
||||
}
|
||||
|
||||
HashAttributes HashAttributesFromString(const std::string& str)
|
||||
{
|
||||
if (str == "")
|
||||
return GetDefaultHashAttributes();
|
||||
|
||||
HashAttributes attributes = static_cast<HashAttributes>(NoHashAttributes);
|
||||
auto parts = SplitString(str, ',');
|
||||
if (parts.empty())
|
||||
return GetDefaultHashAttributes();
|
||||
|
||||
for (auto& part : parts)
|
||||
{
|
||||
Common::ToLower(&part);
|
||||
if (part == "blending")
|
||||
{
|
||||
attributes = attributes | HashAttributes::Blending;
|
||||
}
|
||||
else if (part == "projection")
|
||||
{
|
||||
attributes = attributes | HashAttributes::Projection;
|
||||
}
|
||||
else if (part == "vertex_position")
|
||||
{
|
||||
attributes = attributes | HashAttributes::VertexPosition;
|
||||
}
|
||||
else if (part == "vertex_texcoords")
|
||||
{
|
||||
attributes = attributes | HashAttributes::VertexTexCoords;
|
||||
}
|
||||
else if (part == "vertex_layout")
|
||||
{
|
||||
attributes = attributes | HashAttributes::VertexLayout;
|
||||
}
|
||||
else if (part == "indices")
|
||||
{
|
||||
attributes = attributes | HashAttributes::Indices;
|
||||
}
|
||||
}
|
||||
if (static_cast<u16>(attributes) == NoHashAttributes)
|
||||
return GetDefaultHashAttributes();
|
||||
return attributes;
|
||||
}
|
||||
|
||||
std::string HashAttributesToString(HashAttributes attributes)
|
||||
{
|
||||
std::string result;
|
||||
if (ContainsAttribute(attributes, HashAttributes::Blending))
|
||||
{
|
||||
result += "blending,";
|
||||
}
|
||||
if (ContainsAttribute(attributes, HashAttributes::Projection))
|
||||
{
|
||||
result += "projection,";
|
||||
}
|
||||
if (ContainsAttribute(attributes, HashAttributes::VertexPosition))
|
||||
{
|
||||
result += "vertex_position,";
|
||||
}
|
||||
if (ContainsAttribute(attributes, HashAttributes::VertexTexCoords))
|
||||
{
|
||||
result += "vertex_texcoords,";
|
||||
}
|
||||
if (ContainsAttribute(attributes, HashAttributes::VertexLayout))
|
||||
{
|
||||
result += "vertex_layout,";
|
||||
}
|
||||
if (ContainsAttribute(attributes, HashAttributes::Indices))
|
||||
{
|
||||
result += "indices,";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace GraphicsModSystem::Config
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
constexpr u16 NoHashAttributes = 0;
|
||||
enum class HashAttributes : u16
|
||||
{
|
||||
Blending = 1u << 1,
|
||||
Projection = 1u << 2,
|
||||
VertexPosition = 1u << 3,
|
||||
VertexTexCoords = 1u << 4,
|
||||
VertexLayout = 1u << 5,
|
||||
Indices = 1u << 6
|
||||
};
|
||||
|
||||
struct HashPolicy
|
||||
{
|
||||
HashAttributes attributes;
|
||||
bool first_texture_only;
|
||||
u64 version;
|
||||
};
|
||||
|
||||
HashAttributes operator|(HashAttributes lhs, HashAttributes rhs);
|
||||
HashAttributes operator&(HashAttributes lhs, HashAttributes rhs);
|
||||
|
||||
HashPolicy GetDefaultHashPolicy();
|
||||
HashAttributes HashAttributesFromString(const std::string& str);
|
||||
std::string HashAttributesToString(HashAttributes attributes);
|
||||
|
||||
} // namespace GraphicsModSystem::Config
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h"
|
||||
|
||||
#include "Common/JsonUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
void GraphicsModTag::Serialize(picojson::object& obj) const
|
||||
{
|
||||
obj.emplace("name", m_name);
|
||||
obj.emplace("description", m_description);
|
||||
}
|
||||
|
||||
bool GraphicsModTag::Deserialize(const picojson::object& obj)
|
||||
{
|
||||
const auto name = ReadStringFromJson(obj, "name");
|
||||
if (!name)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load mod configuration file, specified tag has an invalid name");
|
||||
return false;
|
||||
}
|
||||
m_name = *name;
|
||||
m_description = ReadStringFromJson(obj, "description").value_or("");
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace GraphicsModSystem::Config
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "Common/Matrix.h"
|
||||
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
struct GraphicsModTag
|
||||
{
|
||||
std::string m_name;
|
||||
std::string m_description;
|
||||
Common::Vec3 m_color;
|
||||
|
||||
void Serialize(picojson::object& json_obj) const;
|
||||
bool Deserialize(const picojson::object& json_obj);
|
||||
};
|
||||
} // namespace GraphicsModSystem::Config
|
|
@ -3,308 +3,134 @@
|
|||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"
|
||||
|
||||
#include "Common/JsonUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/VariantUtil.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
|
||||
namespace
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
template <typename T, std::enable_if_t<std::is_base_of_v<FBTarget, T>, int> = 0>
|
||||
std::optional<T> DeserializeFBTargetFromConfig(const picojson::object& obj, std::string_view prefix)
|
||||
{
|
||||
T fb;
|
||||
const auto texture_filename_iter = obj.find("texture_filename");
|
||||
if (texture_filename_iter == obj.end())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load mod configuration file, option 'texture_filename' not found");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!texture_filename_iter->second.is<std::string>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load mod configuration file, option 'texture_filename' is not a string type");
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto texture_filename = texture_filename_iter->second.get<std::string>();
|
||||
const auto texture_filename_without_prefix = texture_filename.substr(prefix.size() + 1);
|
||||
const auto split_str_values = SplitString(texture_filename_without_prefix, '_');
|
||||
if (split_str_values.size() == 1)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is not valid");
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto split_width_height_values = SplitString(texture_filename_without_prefix, 'x');
|
||||
if (split_width_height_values.size() != 2)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
||||
"not valid, width and height separator found more matches than expected");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::size_t width_underscore_pos = split_width_height_values[0].find_last_of('_');
|
||||
std::string width_str;
|
||||
if (width_underscore_pos == std::string::npos)
|
||||
{
|
||||
width_str = split_width_height_values[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
width_str = split_width_height_values[0].substr(width_underscore_pos + 1);
|
||||
}
|
||||
if (!TryParse(width_str, &fb.m_width))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
||||
"not valid, width not a number");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::size_t height_underscore_pos = split_width_height_values[1].find_first_of('_');
|
||||
if (height_underscore_pos == std::string::npos ||
|
||||
height_underscore_pos == split_width_height_values[1].size() - 1)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
||||
"not valid, underscore after height is missing or incomplete");
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::string height_str = split_width_height_values[1].substr(0, height_underscore_pos);
|
||||
if (!TryParse(height_str, &fb.m_height))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
||||
"not valid, height not a number");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::size_t format_underscore_pos =
|
||||
split_width_height_values[1].find_first_of('_', height_underscore_pos + 1);
|
||||
|
||||
std::string format_str;
|
||||
if (format_underscore_pos == std::string::npos)
|
||||
{
|
||||
format_str = split_width_height_values[1].substr(height_underscore_pos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
format_str = split_width_height_values[1].substr(
|
||||
height_underscore_pos + 1, (format_underscore_pos - height_underscore_pos) - 1);
|
||||
}
|
||||
u32 format;
|
||||
if (!TryParse(format_str, &format))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
||||
"not valid, texture format is not a number");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!IsValidTextureFormat(static_cast<TextureFormat>(format)))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is "
|
||||
"not valid, texture format is not valid");
|
||||
return std::nullopt;
|
||||
}
|
||||
fb.m_texture_format = static_cast<TextureFormat>(format);
|
||||
|
||||
return fb;
|
||||
}
|
||||
std::optional<std::string> ExtractTextureFilenameForConfig(const picojson::object& obj)
|
||||
{
|
||||
const auto texture_filename_iter = obj.find("texture_filename");
|
||||
if (texture_filename_iter == obj.end())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load mod configuration file, option 'texture_filename' not found");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!texture_filename_iter->second.is<std::string>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load mod configuration file, option 'texture_filename' is not a string type");
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string texture_info = texture_filename_iter->second.get<std::string>();
|
||||
|
||||
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)
|
||||
void SerializeTarget(picojson::object& json_obj, const AnyTarget& target)
|
||||
{
|
||||
std::visit(overloaded{
|
||||
[&](const DrawStartedTextureTarget& the_target) {
|
||||
json_obj.emplace("type", "draw_started");
|
||||
json_obj.emplace("texture_filename", the_target.m_texture_info_string);
|
||||
[&](const IntTarget& the_target) {
|
||||
json_obj.emplace("type", "int");
|
||||
json_obj.emplace("id", std::to_string(the_target.m_target_id));
|
||||
json_obj.emplace("name", the_target.m_name);
|
||||
json_obj.emplace("tags", ToJsonArray(the_target.m_tag_names));
|
||||
},
|
||||
[&](const LoadTextureTarget& the_target) {
|
||||
json_obj.emplace("type", "load_texture");
|
||||
json_obj.emplace("texture_filename", the_target.m_texture_info_string);
|
||||
},
|
||||
[&](const CreateTextureTarget& the_target) {
|
||||
json_obj.emplace("type", "create_texture");
|
||||
json_obj.emplace("texture_filename", the_target.m_texture_info_string);
|
||||
},
|
||||
[&](const EFBTarget& the_target) {
|
||||
json_obj.emplace("type", "efb");
|
||||
json_obj.emplace("texture_filename",
|
||||
fmt::format("{}_{}x{}_{}", EFB_DUMP_PREFIX, the_target.m_width,
|
||||
the_target.m_height,
|
||||
static_cast<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);
|
||||
}
|
||||
[&](const StringTarget& the_target) {
|
||||
json_obj.emplace("type", "string");
|
||||
json_obj.emplace("id", the_target.m_target_id);
|
||||
json_obj.emplace("name", the_target.m_name);
|
||||
json_obj.emplace("tags", ToJsonArray(the_target.m_tag_names));
|
||||
},
|
||||
},
|
||||
target);
|
||||
}
|
||||
|
||||
std::optional<GraphicsTargetConfig> DeserializeTargetFromConfig(const picojson::object& obj)
|
||||
bool DeserializeTarget(const picojson::object& json_obj, AnyTarget& target)
|
||||
{
|
||||
const auto type_iter = obj.find("type");
|
||||
if (type_iter == obj.end())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'type' not found");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!type_iter->second.is<std::string>())
|
||||
const auto type = ReadStringFromJson(json_obj, "type");
|
||||
if (!type)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load mod configuration file, option 'type' is not a string type");
|
||||
return std::nullopt;
|
||||
"Failed to load mod configuration file, option 'type' was missing or invalid");
|
||||
return false;
|
||||
}
|
||||
const std::string& type = type_iter->second.get<std::string>();
|
||||
if (type == "draw_started")
|
||||
{
|
||||
std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
|
||||
if (!texture_info.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
DrawStartedTextureTarget target;
|
||||
target.m_texture_info_string = texture_info.value();
|
||||
return target;
|
||||
}
|
||||
else if (type == "load_texture")
|
||||
if (*type == "int")
|
||||
{
|
||||
std::optional<std::string> texture_info = ExtractTextureFilenameForConfig(obj);
|
||||
if (!texture_info.has_value())
|
||||
return std::nullopt;
|
||||
IntTarget i_target;
|
||||
i_target.m_name = ReadStringFromJson(json_obj, "name").value_or("");
|
||||
|
||||
LoadTextureTarget target;
|
||||
target.m_texture_info_string = texture_info.value();
|
||||
return target;
|
||||
}
|
||||
else if (type == "create_texture")
|
||||
{
|
||||
std::optional<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>())
|
||||
const std::string target_id_str = ReadStringFromJson(json_obj, "id").value_or("0");
|
||||
u64 target_id = 0;
|
||||
if (!TryParse(target_id_str, &target_id))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load mod configuration file, option 'value' is not a string type");
|
||||
return std::nullopt;
|
||||
"Failed to load graphics mod configuration file, option 'id' is invalid");
|
||||
return false;
|
||||
}
|
||||
const auto& value_str = value_iter->second.get<std::string>();
|
||||
if (value_str == "2d")
|
||||
i_target.m_target_id = target_id;
|
||||
if (i_target.m_target_id == 0)
|
||||
{
|
||||
target.m_projection_type = ProjectionType::Orthographic;
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load graphics mod configuration file, option 'id' is invalid");
|
||||
return false;
|
||||
}
|
||||
else if (value_str == "3d")
|
||||
if (const auto tags_iter = json_obj.find("tags"); tags_iter != json_obj.end())
|
||||
{
|
||||
target.m_projection_type = ProjectionType::Perspective;
|
||||
if (!tags_iter->second.is<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 "
|
||||
"value, valid values are: 2d, 3d");
|
||||
return std::nullopt;
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load graphics mod configuration file, option 'id' is invalid");
|
||||
return false;
|
||||
}
|
||||
return target;
|
||||
s_target.m_target_id = *id;
|
||||
if (const auto tags_iter = json_obj.find("tags"); tags_iter != json_obj.end())
|
||||
{
|
||||
if (!tags_iter->second.is<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
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load mod configuration file, option 'type' is not a valid value");
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load graphics mod configuration file, option 'type' is invalid value '{}'",
|
||||
*type);
|
||||
return false;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void SerializeTargetToProfile(picojson::object*, const GraphicsTargetConfig&)
|
||||
{
|
||||
// Added for consistency, no functionality as of now
|
||||
}
|
||||
|
||||
void DeserializeTargetFromProfile(const picojson::object&, GraphicsTargetConfig*)
|
||||
{
|
||||
// Added for consistency, no functionality as of now
|
||||
return true;
|
||||
}
|
||||
} // namespace GraphicsModSystem::Config
|
||||
|
|
|
@ -3,60 +3,34 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <picojson.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
|
||||
{
|
||||
u32 m_height = 0;
|
||||
u32 m_width = 0;
|
||||
TextureFormat m_texture_format = TextureFormat::I4;
|
||||
};
|
||||
|
||||
struct EFBTarget final : public FBTarget
|
||||
{
|
||||
};
|
||||
|
||||
struct XFBTarget final : public FBTarget
|
||||
{
|
||||
};
|
||||
|
||||
struct ProjectionTarget
|
||||
{
|
||||
std::optional<std::string> m_texture_info_string;
|
||||
ProjectionType m_projection_type = ProjectionType::Perspective;
|
||||
};
|
||||
|
||||
using GraphicsTargetConfig =
|
||||
std::variant<DrawStartedTextureTarget, LoadTextureTarget, 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);
|
||||
void SerializeTarget(picojson::object& json_obj, const AnyTarget& target);
|
||||
bool DeserializeTarget(const picojson::object& json_obj, AnyTarget& target);
|
||||
} // namespace GraphicsModSystem::Config
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h"
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
void GraphicsTargetGroupConfig::SerializeToConfig(picojson::object& json_obj) const
|
||||
{
|
||||
picojson::array serialized_targets;
|
||||
for (const auto& target : m_targets)
|
||||
{
|
||||
picojson::object serialized_target;
|
||||
SerializeTargetToConfig(serialized_target, target);
|
||||
serialized_targets.emplace_back(std::move(serialized_target));
|
||||
}
|
||||
json_obj.emplace("targets", std::move(serialized_targets));
|
||||
json_obj.emplace("name", m_name);
|
||||
}
|
||||
|
||||
bool GraphicsTargetGroupConfig::DeserializeFromConfig(const picojson::object& obj)
|
||||
{
|
||||
if (auto name_iter = obj.find("name"); name_iter != obj.end())
|
||||
{
|
||||
if (!name_iter->second.is<std::string>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO, "Failed to load mod configuration file, specified group's name is not a string");
|
||||
return false;
|
||||
}
|
||||
m_name = name_iter->second.get<std::string>();
|
||||
}
|
||||
|
||||
if (auto targets_iter = obj.find("targets"); targets_iter != obj.end())
|
||||
{
|
||||
if (!targets_iter->second.is<picojson::array>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load mod configuration file, specified group's targets is not an array");
|
||||
return false;
|
||||
}
|
||||
for (const auto& target_val : targets_iter->second.get<picojson::array>())
|
||||
{
|
||||
if (!target_val.is<picojson::object>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load shader configuration file, specified target is not a json object");
|
||||
return false;
|
||||
}
|
||||
const auto target = DeserializeTargetFromConfig(target_val.get<picojson::object>());
|
||||
if (!target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_targets.push_back(*target);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GraphicsTargetGroupConfig::SerializeToProfile(picojson::object* obj) const
|
||||
{
|
||||
if (!obj)
|
||||
return;
|
||||
auto& json_obj = *obj;
|
||||
picojson::array serialized_targets;
|
||||
for (const auto& target : m_targets)
|
||||
{
|
||||
picojson::object serialized_target;
|
||||
SerializeTargetToProfile(&serialized_target, target);
|
||||
serialized_targets.emplace_back(std::move(serialized_target));
|
||||
}
|
||||
json_obj.emplace("targets", std::move(serialized_targets));
|
||||
}
|
||||
|
||||
void GraphicsTargetGroupConfig::DeserializeFromProfile(const picojson::object& obj)
|
||||
{
|
||||
if (const auto it = obj.find("targets"); it != obj.end())
|
||||
{
|
||||
if (it->second.is<picojson::array>())
|
||||
{
|
||||
const auto& serialized_targets = it->second.get<picojson::array>();
|
||||
if (serialized_targets.size() != m_targets.size())
|
||||
return;
|
||||
|
||||
for (std::size_t i = 0; i < serialized_targets.size(); i++)
|
||||
{
|
||||
const auto& serialized_target_val = serialized_targets[i];
|
||||
if (serialized_target_val.is<picojson::object>())
|
||||
{
|
||||
const auto& serialized_target = serialized_target_val.get<picojson::object>();
|
||||
DeserializeTargetFromProfile(serialized_target, &m_targets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h"
|
||||
|
||||
struct GraphicsTargetGroupConfig
|
||||
{
|
||||
std::string m_name;
|
||||
std::vector<GraphicsTargetConfig> m_targets;
|
||||
|
||||
void SerializeToConfig(picojson::object& json_obj) const;
|
||||
bool DeserializeFromConfig(const picojson::object& obj);
|
||||
|
||||
void SerializeToProfile(picojson::object* obj) const;
|
||||
void DeserializeFromProfile(const picojson::object& obj);
|
||||
};
|
|
@ -4,8 +4,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
|
||||
static const inline std::string DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR =
|
||||
LOAD_DIR DIR_SEP GRAPHICSMOD_DIR DIR_SEP;
|
||||
|
||||
namespace GraphicsModSystem
|
||||
{
|
||||
static constexpr std::string_view internal_tag_prefix = "__internal_";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h"
|
||||
|
||||
#include "Common/SmallVector.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "VideoCommon/GXPipelineTypes.h"
|
||||
#include "VideoCommon/GeometryShaderManager.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/VertexLoaderManager.h"
|
||||
#include "VideoCommon/VertexShaderManager.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
|
||||
namespace GraphicsModSystem::Runtime
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool IsDrawGPUSkinned(NativeVertexFormat* format, PrimitiveType primitive_type)
|
||||
{
|
||||
if (primitive_type != PrimitiveType::Triangles && primitive_type != PrimitiveType::TriangleStrip)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const PortableVertexDeclaration vert_decl = format->GetVertexDeclaration();
|
||||
return vert_decl.posmtx.enable;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void GraphicsModBackend::OnTextureCreate(const TextureView& texture)
|
||||
{
|
||||
if (texture.texture_type == TextureType::XFB)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& custom_resource_manager = system.GetCustomResourceManager();
|
||||
custom_resource_manager.XFBTriggered(texture.hash_name);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsModBackend::OnFramePresented(const PresentInfo& present_info)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& custom_resource_manager = system.GetCustomResourceManager();
|
||||
custom_resource_manager.FramePresented(present_info);
|
||||
m_camera_manager.FrameUpdate(present_info.frame_count);
|
||||
}
|
||||
|
||||
void GraphicsModBackend::SetHostConfig(const ShaderHostConfig& config)
|
||||
{
|
||||
m_shader_host_config.bits = config.bits;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& custom_resource_manager = system.GetCustomResourceManager();
|
||||
custom_resource_manager.SetHostConfig(config);
|
||||
}
|
||||
|
||||
void GraphicsModBackend::ClearAdditionalCameras(const MathUtil::Rectangle<int>& rc,
|
||||
bool color_enable, bool alpha_enable, bool z_enable,
|
||||
u32 color, u32 z)
|
||||
{
|
||||
m_camera_manager.ClearCameraRenderTargets(rc, color_enable, alpha_enable, z_enable, color, z);
|
||||
}
|
||||
|
||||
void GraphicsModBackend::CustomDraw(const DrawDataView& draw_data,
|
||||
VertexManagerBase* vertex_manager,
|
||||
std::span<GraphicsModAction*> actions, DrawCallID draw_call)
|
||||
{
|
||||
bool skip = false;
|
||||
std::optional<Common::Matrix44> custom_transform;
|
||||
GraphicsModSystem::MaterialResource* material_resource = nullptr;
|
||||
GraphicsModSystem::MeshResource* mesh_resource = nullptr;
|
||||
bool ignore_mesh_transform = false;
|
||||
std::optional<GraphicsModSystem::Camera> camera;
|
||||
GraphicsModActionData::DrawStarted draw_started{draw_data,
|
||||
VertexLoaderManager::g_current_components,
|
||||
&skip,
|
||||
&material_resource,
|
||||
&mesh_resource,
|
||||
&ignore_mesh_transform,
|
||||
&custom_transform,
|
||||
&camera};
|
||||
|
||||
static Common::Matrix44 identity = Common::Matrix44::Identity();
|
||||
|
||||
for (const auto& action : actions)
|
||||
{
|
||||
action->OnDrawStarted(&draw_started);
|
||||
if (camera)
|
||||
{
|
||||
m_camera_manager.EnsureCamera(draw_call, *camera);
|
||||
}
|
||||
if (mesh_resource)
|
||||
{
|
||||
vertex_manager->DrawCustomMesh(mesh_resource, draw_data, custom_transform.value_or(identity),
|
||||
ignore_mesh_transform, m_camera_manager);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!skip)
|
||||
{
|
||||
vertex_manager->DrawEmulatedMesh(material_resource, draw_data,
|
||||
custom_transform.value_or(identity), m_camera_manager);
|
||||
}
|
||||
}
|
||||
|
||||
DrawCallID GraphicsModBackend::GetSkinnedDrawCallID(DrawCallID draw_call_id, MaterialID material_id,
|
||||
const DrawDataView& draw_data)
|
||||
{
|
||||
const bool is_draw_gpu_skinned =
|
||||
IsDrawGPUSkinned(draw_data.vertex_format, draw_data.uid->rasterization_state.primitive);
|
||||
if (is_draw_gpu_skinned && m_last_draw_gpu_skinned && m_last_material_id == material_id)
|
||||
{
|
||||
draw_call_id = m_last_draw_call_id;
|
||||
}
|
||||
m_last_draw_call_id = draw_call_id;
|
||||
m_last_material_id = material_id;
|
||||
m_last_draw_gpu_skinned = is_draw_gpu_skinned;
|
||||
|
||||
return draw_call_id;
|
||||
}
|
||||
} // namespace GraphicsModSystem::Runtime
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/CameraManager.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Types.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
#include "VideoCommon/ShaderGenCommon.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
class GraphicsModAction;
|
||||
namespace GraphicsModSystem::Runtime
|
||||
{
|
||||
class GraphicsModBackend
|
||||
{
|
||||
public:
|
||||
virtual ~GraphicsModBackend() = default;
|
||||
|
||||
virtual void OnDraw(const DrawDataView& draw_started, VertexManagerBase* vertex_manager) = 0;
|
||||
virtual void OnTextureUnload(TextureType texture_type, std::string_view texture_hash) = 0;
|
||||
virtual void OnTextureLoad(const TextureView& texture) = 0;
|
||||
virtual void OnTextureCreate(const TextureView& texture) = 0;
|
||||
virtual void OnLight() = 0;
|
||||
virtual void OnFramePresented(const PresentInfo& present_info);
|
||||
|
||||
virtual void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) = 0;
|
||||
virtual void ResetIndices() = 0;
|
||||
|
||||
void SetHostConfig(const ShaderHostConfig& config);
|
||||
|
||||
void ClearAdditionalCameras(const MathUtil::Rectangle<int>& rc, bool color_enable,
|
||||
bool alpha_enable, bool z_enable, u32 color, u32 z);
|
||||
|
||||
protected:
|
||||
void CustomDraw(const DrawDataView& draw_data, VertexManagerBase* vertex_manager,
|
||||
std::span<GraphicsModAction*> actions, DrawCallID draw_call);
|
||||
|
||||
// This will ensure gpu skinned draw calls are joined together
|
||||
DrawCallID GetSkinnedDrawCallID(DrawCallID draw_call_id, MaterialID material_id,
|
||||
const DrawDataView& draw_data);
|
||||
|
||||
ShaderHostConfig m_shader_host_config;
|
||||
|
||||
VideoCommon::CameraManager m_camera_manager;
|
||||
|
||||
private:
|
||||
bool m_last_draw_gpu_skinned = false;
|
||||
GraphicsModSystem::DrawCallID m_last_draw_call_id = GraphicsModSystem::DrawCallID::INVALID;
|
||||
GraphicsModSystem::MaterialID m_last_material_id = GraphicsModSystem::MaterialID::INVALID;
|
||||
};
|
||||
} // namespace GraphicsModSystem::Runtime
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <xxh3.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h"
|
||||
#include "VideoCommon/RenderState.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
|
||||
namespace GraphicsModSystem::Runtime
|
||||
{
|
||||
HashOutput GetDrawDataHash(const Config::HashPolicy& hash_policy, const DrawDataView& draw_data)
|
||||
{
|
||||
HashOutput output;
|
||||
|
||||
XXH3_state_t draw_hash_state;
|
||||
XXH3_INITSTATE(&draw_hash_state);
|
||||
XXH3_64bits_reset_withSeed(&draw_hash_state, static_cast<XXH64_hash_t>(1));
|
||||
|
||||
XXH3_state_t material_hash_state;
|
||||
XXH3_INITSTATE(&material_hash_state);
|
||||
XXH3_64bits_reset_withSeed(&material_hash_state, static_cast<XXH64_hash_t>(1));
|
||||
|
||||
if ((hash_policy.attributes & Config::HashAttributes::Projection) ==
|
||||
Config::HashAttributes::Projection)
|
||||
{
|
||||
XXH3_64bits_update(&draw_hash_state, &xfmem.projection.type, sizeof(ProjectionType));
|
||||
}
|
||||
|
||||
if ((hash_policy.attributes & Config::HashAttributes::Blending) ==
|
||||
Config::HashAttributes::Blending)
|
||||
{
|
||||
XXH3_64bits_update(&draw_hash_state, &bpmem.blendmode, sizeof(BlendMode));
|
||||
}
|
||||
|
||||
if ((hash_policy.attributes & Config::HashAttributes::Indices) == Config::HashAttributes::Indices)
|
||||
{
|
||||
XXH3_64bits_update(&draw_hash_state, draw_data.index_data.data(), draw_data.index_data.size());
|
||||
}
|
||||
|
||||
if ((hash_policy.attributes & Config::HashAttributes::VertexLayout) ==
|
||||
Config::HashAttributes::VertexLayout)
|
||||
{
|
||||
const u32 vertex_count = static_cast<u32>(draw_data.vertex_data.size());
|
||||
XXH3_64bits_update(&draw_hash_state, &vertex_count, sizeof(u32));
|
||||
}
|
||||
|
||||
if (hash_policy.first_texture_only)
|
||||
{
|
||||
auto& first_texture = draw_data.textures[0];
|
||||
XXH3_64bits_update(&draw_hash_state, first_texture.hash_name.data(),
|
||||
first_texture.hash_name.size());
|
||||
XXH3_64bits_update(&material_hash_state, first_texture.hash_name.data(),
|
||||
first_texture.hash_name.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::set<std::string_view> texture_hashes;
|
||||
for (const auto& texture : draw_data.textures)
|
||||
{
|
||||
texture_hashes.insert(texture.hash_name);
|
||||
}
|
||||
|
||||
for (const auto& texture_hash : texture_hashes)
|
||||
{
|
||||
XXH3_64bits_update(&draw_hash_state, texture_hash.data(), texture_hash.size());
|
||||
XXH3_64bits_update(&material_hash_state, texture_hash.data(), texture_hash.size());
|
||||
}
|
||||
}
|
||||
|
||||
output.draw_call_id = static_cast<DrawCallID>(XXH3_64bits_digest(&draw_hash_state));
|
||||
output.material_id = static_cast<MaterialID>(XXH3_64bits_digest(&material_hash_state));
|
||||
return output;
|
||||
}
|
||||
} // namespace GraphicsModSystem::Runtime
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Types.h"
|
||||
|
||||
namespace GraphicsModSystem
|
||||
{
|
||||
struct DrawDataView;
|
||||
}
|
||||
namespace GraphicsModSystem::Runtime
|
||||
{
|
||||
struct HashOutput
|
||||
{
|
||||
DrawCallID draw_call_id;
|
||||
MaterialID material_id;
|
||||
};
|
||||
|
||||
HashOutput GetDrawDataHash(const Config::HashPolicy& hash_policy, const DrawDataView& draw_data);
|
||||
} // namespace GraphicsModSystem::Runtime
|
|
@ -3,80 +3,16 @@
|
|||
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/VariantUtil.h"
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
std::unique_ptr<GraphicsModManager> g_graphics_mod_manager;
|
||||
|
||||
class GraphicsModManager::DecoratedAction final : public GraphicsModAction
|
||||
namespace GraphicsModSystem::Runtime
|
||||
{
|
||||
public:
|
||||
DecoratedAction(std::unique_ptr<GraphicsModAction> action, GraphicsModConfig mod)
|
||||
: m_action_impl(std::move(action)), m_mod(std::move(mod))
|
||||
{
|
||||
}
|
||||
void OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started) override
|
||||
{
|
||||
if (!m_mod.m_enabled)
|
||||
return;
|
||||
m_action_impl->OnDrawStarted(draw_started);
|
||||
}
|
||||
void OnEFB(GraphicsModActionData::EFB* efb) override
|
||||
{
|
||||
if (!m_mod.m_enabled)
|
||||
return;
|
||||
m_action_impl->OnEFB(efb);
|
||||
}
|
||||
void OnProjection(GraphicsModActionData::Projection* projection) override
|
||||
{
|
||||
if (!m_mod.m_enabled)
|
||||
return;
|
||||
m_action_impl->OnProjection(projection);
|
||||
}
|
||||
void OnProjectionAndTexture(GraphicsModActionData::Projection* projection) override
|
||||
{
|
||||
if (!m_mod.m_enabled)
|
||||
return;
|
||||
m_action_impl->OnProjectionAndTexture(projection);
|
||||
}
|
||||
void OnTextureLoad(GraphicsModActionData::TextureLoad* texture_load) override
|
||||
{
|
||||
if (!m_mod.m_enabled)
|
||||
return;
|
||||
m_action_impl->OnTextureLoad(texture_load);
|
||||
}
|
||||
void OnTextureCreate(GraphicsModActionData::TextureCreate* texture_create) override
|
||||
{
|
||||
if (!m_mod.m_enabled)
|
||||
return;
|
||||
m_action_impl->OnTextureCreate(texture_create);
|
||||
}
|
||||
void OnFrameEnd() override
|
||||
{
|
||||
if (!m_mod.m_enabled)
|
||||
return;
|
||||
m_action_impl->OnFrameEnd();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<GraphicsModAction> m_action_impl;
|
||||
GraphicsModConfig m_mod;
|
||||
};
|
||||
GraphicsModManager::GraphicsModManager() = default;
|
||||
GraphicsModManager::~GraphicsModManager() = default;
|
||||
|
||||
bool GraphicsModManager::Initialize()
|
||||
{
|
||||
|
@ -90,281 +26,64 @@ bool GraphicsModManager::Initialize()
|
|||
const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ?
|
||||
g_ActiveConfig.graphics_mod_config->GetChangeCount() :
|
||||
0;
|
||||
g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID());
|
||||
g_ActiveConfig.graphics_mod_config =
|
||||
Config::GraphicsModGroup(SConfig::GetInstance().GetGameID());
|
||||
g_ActiveConfig.graphics_mod_config->Load();
|
||||
g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes);
|
||||
g_graphics_mod_manager->Load(*g_ActiveConfig.graphics_mod_config);
|
||||
Load(*g_ActiveConfig.graphics_mod_config);
|
||||
|
||||
m_end_of_frame_event =
|
||||
AfterFrameEvent::Register([this](Core::System&) { EndOfFrame(); }, "ModManager");
|
||||
m_frame_present_event = AfterPresentEvent::Register(
|
||||
[this](const PresentInfo& present_info) { OnFramePresented(present_info); }, "ModManager");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GraphicsModManager::GetProjectionActions(ProjectionType projection_type) const
|
||||
void GraphicsModManager::Load(const Config::GraphicsModGroup& config)
|
||||
{
|
||||
if (const auto it = m_projection_target_to_actions.find(projection_type);
|
||||
it != m_projection_target_to_actions.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return m_default;
|
||||
m_backend = std::make_unique<GraphicsModRuntimeBackend>(config);
|
||||
}
|
||||
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GraphicsModManager::GetProjectionTextureActions(ProjectionType projection_type,
|
||||
const std::string& texture_name) const
|
||||
const GraphicsModBackend& GraphicsModManager::GetBackend() const
|
||||
{
|
||||
const auto lookup = fmt::format("{}_{}", texture_name, static_cast<int>(projection_type));
|
||||
if (const auto it = m_projection_texture_target_to_actions.find(lookup);
|
||||
it != m_projection_texture_target_to_actions.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
if (m_editor_backend)
|
||||
return *m_editor_backend;
|
||||
|
||||
return m_default;
|
||||
return *m_backend;
|
||||
}
|
||||
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GraphicsModManager::GetDrawStartedActions(const std::string& texture_name) const
|
||||
GraphicsModBackend& GraphicsModManager::GetBackend()
|
||||
{
|
||||
if (const auto it = m_draw_started_target_to_actions.find(texture_name);
|
||||
it != m_draw_started_target_to_actions.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
if (m_editor_backend)
|
||||
return *m_editor_backend;
|
||||
|
||||
return m_default;
|
||||
return *m_backend;
|
||||
}
|
||||
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GraphicsModManager::GetTextureLoadActions(const std::string& texture_name) const
|
||||
void GraphicsModManager::SetEditorBackend(std::unique_ptr<GraphicsModBackend> editor_backend)
|
||||
{
|
||||
if (const auto it = m_load_texture_target_to_actions.find(texture_name);
|
||||
it != m_load_texture_target_to_actions.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return m_default;
|
||||
m_editor_backend = std::move(editor_backend);
|
||||
}
|
||||
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GraphicsModManager::GetTextureCreateActions(const std::string& texture_name) const
|
||||
bool GraphicsModManager::IsEditorEnabled() const
|
||||
{
|
||||
if (const auto it = m_create_texture_target_to_actions.find(texture_name);
|
||||
it != m_create_texture_target_to_actions.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return m_default;
|
||||
return true;
|
||||
// return m_editor_backend.get() != nullptr;
|
||||
}
|
||||
|
||||
const std::vector<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);
|
||||
}
|
||||
|
||||
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())
|
||||
else
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return m_default;
|
||||
}
|
||||
|
||||
void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
|
||||
{
|
||||
Reset();
|
||||
|
||||
const auto& mods = config.GetMods();
|
||||
|
||||
auto filesystem_library = std::make_shared<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);
|
||||
}
|
||||
}
|
||||
m_backend->OnFramePresented(present_info);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsModManager::EndOfFrame()
|
||||
{
|
||||
for (auto&& action : m_actions)
|
||||
{
|
||||
action->OnFrameEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsModManager::Reset()
|
||||
{
|
||||
m_actions.clear();
|
||||
m_groups.clear();
|
||||
m_projection_target_to_actions.clear();
|
||||
m_projection_texture_target_to_actions.clear();
|
||||
m_draw_started_target_to_actions.clear();
|
||||
m_load_texture_target_to_actions.clear();
|
||||
m_create_texture_target_to_actions.clear();
|
||||
m_efb_target_to_actions.clear();
|
||||
m_xfb_target_to_actions.clear();
|
||||
}
|
||||
} // namespace GraphicsModSystem::Runtime
|
||||
|
|
|
@ -3,62 +3,39 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
#include "Common/HookableEvent.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h"
|
||||
|
||||
class GraphicsModGroupConfig;
|
||||
namespace GraphicsModSystem::Config
|
||||
{
|
||||
class GraphicsModGroup;
|
||||
}
|
||||
|
||||
namespace GraphicsModSystem::Runtime
|
||||
{
|
||||
class GraphicsModRuntimeBackend;
|
||||
class GraphicsModManager
|
||||
{
|
||||
public:
|
||||
GraphicsModManager();
|
||||
~GraphicsModManager();
|
||||
|
||||
bool Initialize();
|
||||
void Load(const GraphicsModSystem::Config::GraphicsModGroup& config);
|
||||
|
||||
const std::vector<GraphicsModAction*>& GetProjectionActions(ProjectionType projection_type) const;
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GetProjectionTextureActions(ProjectionType projection_type,
|
||||
const std::string& texture_name) const;
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GetDrawStartedActions(const std::string& texture_name) const;
|
||||
const std::vector<GraphicsModAction*>&
|
||||
GetTextureLoadActions(const std::string& texture_name) const;
|
||||
const std::vector<GraphicsModAction*>&
|
||||
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;
|
||||
const GraphicsModBackend& GetBackend() const;
|
||||
GraphicsModBackend& GetBackend();
|
||||
|
||||
void Load(const GraphicsModGroupConfig& config);
|
||||
void SetEditorBackend(std::unique_ptr<GraphicsModBackend> editor_backend);
|
||||
bool IsEditorEnabled() const;
|
||||
|
||||
private:
|
||||
void EndOfFrame();
|
||||
void Reset();
|
||||
void OnFramePresented(const PresentInfo& present_info);
|
||||
|
||||
class DecoratedAction;
|
||||
|
||||
static inline const std::vector<GraphicsModAction*> m_default = {};
|
||||
std::list<std::unique_ptr<GraphicsModAction>> m_actions;
|
||||
std::unordered_map<ProjectionType, std::vector<GraphicsModAction*>>
|
||||
m_projection_target_to_actions;
|
||||
std::unordered_map<std::string, std::vector<GraphicsModAction*>>
|
||||
m_projection_texture_target_to_actions;
|
||||
std::unordered_map<std::string, std::vector<GraphicsModAction*>> m_draw_started_target_to_actions;
|
||||
std::unordered_map<std::string, std::vector<GraphicsModAction*>> m_load_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;
|
||||
std::unique_ptr<GraphicsModRuntimeBackend> m_backend;
|
||||
std::unique_ptr<GraphicsModBackend> m_editor_backend;
|
||||
Common::EventHook m_frame_present_event;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<GraphicsModManager> g_graphics_mod_manager;
|
||||
} // namespace GraphicsModSystem::Runtime
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <variant>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/VariantUtil.h"
|
||||
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h"
|
||||
|
||||
namespace GraphicsModSystem::Runtime
|
||||
{
|
||||
GraphicsModRuntimeBackend::GraphicsModRuntimeBackend(const Config::GraphicsModGroup& config_data)
|
||||
{
|
||||
for (auto& config_mod : config_data.GetMods())
|
||||
{
|
||||
if (!config_mod.m_enabled)
|
||||
continue;
|
||||
|
||||
GraphicsMod runtime_mod;
|
||||
|
||||
runtime_mod.m_hash_policy = config_mod.m_mod.m_default_hash_policy;
|
||||
|
||||
auto filesystem_library = std::make_shared<VideoCommon::DirectFilesystemAssetLibrary>();
|
||||
|
||||
for (const auto& config_asset : config_mod.m_mod.m_assets)
|
||||
{
|
||||
auto asset_map = config_asset.m_map;
|
||||
for (auto& [k, v] : asset_map)
|
||||
{
|
||||
if (v.is_absolute())
|
||||
{
|
||||
WARN_LOG_FMT(VIDEO,
|
||||
"Specified graphics mod asset '{}' for mod '{}' has an absolute path, you "
|
||||
"shouldn't release this to users.",
|
||||
config_asset.m_asset_id, config_mod.m_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
v = std::filesystem::path{config_mod.m_path} / v;
|
||||
}
|
||||
}
|
||||
|
||||
filesystem_library->SetAssetIDMapData(config_asset.m_asset_id, std::move(asset_map));
|
||||
}
|
||||
|
||||
for (const auto& config_action : config_mod.m_mod.m_actions)
|
||||
{
|
||||
runtime_mod.m_actions.push_back(GraphicsModActionFactory::Create(
|
||||
config_action.m_factory_name, config_action.m_data, filesystem_library));
|
||||
}
|
||||
|
||||
std::map<std::string, std::vector<GraphicsModAction*>> tag_to_actions;
|
||||
for (const auto& [tag_name, action_indexes] : config_mod.m_mod.m_tag_name_to_action_indexes)
|
||||
{
|
||||
for (const auto& index : action_indexes)
|
||||
{
|
||||
tag_to_actions[tag_name].push_back(runtime_mod.m_actions[index].get());
|
||||
}
|
||||
}
|
||||
|
||||
struct IDWithTags
|
||||
{
|
||||
std::variant<DrawCallID, std::string> m_id;
|
||||
const std::vector<std::string>* m_tag_names;
|
||||
};
|
||||
std::vector<IDWithTags> targets;
|
||||
for (const auto& target : config_mod.m_mod.m_targets)
|
||||
{
|
||||
std::visit(overloaded{[&](const Config::IntTarget& int_target) {
|
||||
const DrawCallID id{int_target.m_target_id};
|
||||
runtime_mod.m_draw_id_to_actions.insert_or_assign(
|
||||
id, std::vector<GraphicsModAction*>{});
|
||||
targets.push_back(IDWithTags{id, &int_target.m_tag_names});
|
||||
},
|
||||
[&](const Config::StringTarget& str_target) {
|
||||
const auto id{str_target.m_target_id};
|
||||
runtime_mod.m_str_id_to_actions.insert_or_assign(
|
||||
id, std::vector<GraphicsModAction*>{});
|
||||
targets.push_back(IDWithTags{id, &str_target.m_tag_names});
|
||||
}},
|
||||
target);
|
||||
}
|
||||
|
||||
// Handle any specific actions set on targets
|
||||
for (std::size_t i = 0; i < targets.size(); i++)
|
||||
{
|
||||
const auto& target = targets[i];
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](DrawCallID draw_id) {
|
||||
auto& actions = runtime_mod.m_draw_id_to_actions[draw_id];
|
||||
|
||||
// First add all specific actions
|
||||
if (const auto iter = config_mod.m_mod.m_target_index_to_action_indexes.find(i);
|
||||
iter != config_mod.m_mod.m_target_index_to_action_indexes.end())
|
||||
{
|
||||
for (const auto& action_index : iter->second)
|
||||
{
|
||||
actions.push_back(runtime_mod.m_actions[action_index].get());
|
||||
}
|
||||
}
|
||||
|
||||
// Then add all tag actions
|
||||
for (const auto& tag_name : *target.m_tag_names)
|
||||
{
|
||||
auto& tag_actions = tag_to_actions[tag_name];
|
||||
actions.insert(actions.end(), tag_actions.begin(), tag_actions.end());
|
||||
}
|
||||
},
|
||||
[&](const std::string& str_id) {
|
||||
auto& actions = runtime_mod.m_str_id_to_actions[str_id];
|
||||
|
||||
// First add all specific actions
|
||||
if (const auto iter = config_mod.m_mod.m_target_index_to_action_indexes.find(i);
|
||||
iter != config_mod.m_mod.m_target_index_to_action_indexes.end())
|
||||
{
|
||||
for (const auto& action_index : iter->second)
|
||||
{
|
||||
actions.push_back(runtime_mod.m_actions[action_index].get());
|
||||
}
|
||||
}
|
||||
|
||||
// Then add all tag actions
|
||||
for (const auto& tag_name : *target.m_tag_names)
|
||||
{
|
||||
auto& tag_actions = tag_to_actions[tag_name];
|
||||
actions.insert(actions.end(), tag_actions.begin(), tag_actions.end());
|
||||
}
|
||||
}},
|
||||
target.m_id);
|
||||
}
|
||||
|
||||
m_mods.push_back(std::move(runtime_mod));
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsModRuntimeBackend::OnDraw(const DrawDataView& draw_data,
|
||||
VertexManagerBase* vertex_manager)
|
||||
{
|
||||
if (m_mods.empty())
|
||||
{
|
||||
vertex_manager->DrawEmulatedMesh(m_camera_manager);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& mod : m_mods)
|
||||
{
|
||||
const auto hash_output = GetDrawDataHash(mod.m_hash_policy, draw_data);
|
||||
const DrawCallID draw_call_id =
|
||||
GetSkinnedDrawCallID(hash_output.draw_call_id, hash_output.material_id, draw_data);
|
||||
|
||||
if (const auto iter = mod.m_draw_id_to_actions.find(draw_call_id);
|
||||
iter != mod.m_draw_id_to_actions.end())
|
||||
{
|
||||
CustomDraw(draw_data, vertex_manager, iter->second, draw_call_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
vertex_manager->DrawEmulatedMesh(m_camera_manager);
|
||||
}
|
||||
|
||||
void GraphicsModRuntimeBackend::OnTextureLoad(const TextureView& texture)
|
||||
{
|
||||
for (auto& mod : m_mods)
|
||||
{
|
||||
if (const auto iter = mod.m_str_id_to_actions.find(texture.hash_name);
|
||||
iter != mod.m_str_id_to_actions.end())
|
||||
{
|
||||
GraphicsModActionData::TextureLoad load{.texture_name = texture.hash_name};
|
||||
for (const auto& action : iter->second)
|
||||
{
|
||||
action->OnTextureLoad(&load);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsModRuntimeBackend::OnTextureUnload(TextureType, std::string_view)
|
||||
{
|
||||
}
|
||||
|
||||
void GraphicsModRuntimeBackend::OnTextureCreate(const TextureView& texture)
|
||||
{
|
||||
GraphicsModSystem::Runtime::GraphicsModBackend::OnTextureCreate(texture);
|
||||
|
||||
for (auto& mod : m_mods)
|
||||
{
|
||||
if (const auto iter = mod.m_str_id_to_actions.find(texture.hash_name);
|
||||
iter != mod.m_str_id_to_actions.end())
|
||||
{
|
||||
GraphicsModActionData::TextureCreate texture_create{.texture_name = texture.hash_name};
|
||||
for (const auto& action : iter->second)
|
||||
{
|
||||
action->OnTextureCreate(&texture_create);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsModRuntimeBackend::OnLight()
|
||||
{
|
||||
}
|
||||
|
||||
void GraphicsModRuntimeBackend::OnFramePresented(const PresentInfo& present_info)
|
||||
{
|
||||
GraphicsModSystem::Runtime::GraphicsModBackend::OnFramePresented(present_info);
|
||||
|
||||
for (auto& mod : m_mods)
|
||||
{
|
||||
for (const auto& action : mod.m_actions)
|
||||
{
|
||||
action->OnFrameEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsModRuntimeBackend::AddIndices(OpcodeDecoder::Primitive, u32)
|
||||
{
|
||||
}
|
||||
|
||||
void GraphicsModRuntimeBackend::ResetIndices()
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace GraphicsModSystem::Runtime
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Types.h"
|
||||
|
||||
namespace GraphicsModSystem::Runtime
|
||||
{
|
||||
class GraphicsModRuntimeBackend final : public GraphicsModBackend
|
||||
{
|
||||
public:
|
||||
explicit GraphicsModRuntimeBackend(const Config::GraphicsModGroup& config_data);
|
||||
void OnDraw(const DrawDataView& draw_data, VertexManagerBase* vertex_manager) override;
|
||||
void OnTextureLoad(const TextureView& texture) override;
|
||||
void OnTextureUnload(TextureType texture_type, std::string_view texture_hash) override;
|
||||
void OnTextureCreate(const TextureView& texture) override;
|
||||
void OnLight() override;
|
||||
void OnFramePresented(const PresentInfo& present_info) override;
|
||||
|
||||
void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) override;
|
||||
void ResetIndices() override;
|
||||
|
||||
private:
|
||||
struct GraphicsMod
|
||||
{
|
||||
GraphicsMod() = default;
|
||||
~GraphicsMod() = default;
|
||||
GraphicsMod(GraphicsMod&) = delete;
|
||||
GraphicsMod(GraphicsMod&&) noexcept = default;
|
||||
GraphicsMod& operator=(GraphicsMod&) = delete;
|
||||
GraphicsMod& operator=(GraphicsMod&&) noexcept = default;
|
||||
|
||||
Config::HashPolicy m_hash_policy;
|
||||
std::unordered_map<DrawCallID, std::vector<GraphicsModAction*>> m_draw_id_to_actions;
|
||||
|
||||
struct string_hash
|
||||
{
|
||||
using is_transparent = void;
|
||||
[[nodiscard]] size_t operator()(const char* txt) const
|
||||
{
|
||||
return std::hash<std::string_view>{}(txt);
|
||||
}
|
||||
[[nodiscard]] size_t operator()(std::string_view txt) const
|
||||
{
|
||||
return std::hash<std::string_view>{}(txt);
|
||||
}
|
||||
[[nodiscard]] size_t operator()(const std::string& txt) const
|
||||
{
|
||||
return std::hash<std::string>{}(txt);
|
||||
}
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::vector<GraphicsModAction*>, string_hash, std::equal_to<>>
|
||||
m_str_id_to_actions;
|
||||
std::vector<std::unique_ptr<GraphicsModAction>> m_actions;
|
||||
};
|
||||
std::vector<GraphicsMod> m_mods;
|
||||
};
|
||||
} // namespace GraphicsModSystem::Runtime
|
Loading…
Add table
Add a link
Reference in a new issue