summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon/GraphicsModSystem
diff options
context:
space:
mode:
authorAdmiral H. Curtiss <pikachu025@gmail.com>2023-08-22 02:18:40 +0200
committerGitHub <noreply@github.com>2023-08-22 02:18:40 +0200
commitdbe6a5f7e4caca71de9d9ba86916f82c0bc21683 (patch)
tree84fd8c6f664f1df62350f9d7b5aa9d110b6aee02 /Source/Core/VideoCommon/GraphicsModSystem
parent3451cb1ca2583f9c2b02d0d216355047787c134b (diff)
parent55061216855af89f21eee1174fa81d927bc94991 (diff)
Merge pull request #11300 from iwubcode/custom-shaders
VideoCommon: add a graphics mod action that allows you to modify the game's base rendering
Diffstat (limited to 'Source/Core/VideoCommon/GraphicsModSystem')
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp21
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h2
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp52
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h18
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp449
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h54
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp376
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h144
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h5
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp7
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h3
-rw-r--r--Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp38
12 files changed, 1161 insertions, 8 deletions
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp
index c7dc5aab87..b04190ad67 100644
--- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp
@@ -178,6 +178,27 @@ bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value)
}
}
+ const auto& assets = value.get("assets");
+ if (assets.is<picojson::array>())
+ {
+ for (const auto& asset_val : assets.get<picojson::array>())
+ {
+ if (!asset_val.is<picojson::object>())
+ {
+ ERROR_LOG_FMT(
+ 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>()))
+ {
+ return false;
+ }
+
+ m_assets.push_back(std::move(asset));
+ }
+ }
+
return true;
}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h
index f4f6859cb3..953af6201b 100644
--- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h
@@ -9,6 +9,7 @@
#include <picojson.h>
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h"
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h"
@@ -30,6 +31,7 @@ struct GraphicsModConfig
std::vector<GraphicsTargetGroupConfig> m_groups;
std::vector<GraphicsModFeatureConfig> m_features;
+ std::vector<GraphicsModAssetConfig> m_assets;
static std::optional<GraphicsModConfig> Create(const std::string& file, Source source);
static std::optional<GraphicsModConfig> Create(const picojson::object* obj);
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp
new file mode 100644
index 0000000000..fb5572b3da
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp
@@ -0,0 +1,52 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
+
+#include "Common/Logging/Log.h"
+
+bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj)
+{
+ auto name_iter = obj.find("name");
+ if (name_iter == obj.end())
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has no name");
+ 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_name = name_iter->second.to_str();
+
+ auto data_iter = obj.find("data");
+ if (data_iter == obj.end())
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset '{}' has no data",
+ m_name);
+ return false;
+ }
+ if (!data_iter->second.is<picojson::object>())
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load mod configuration file, specified asset '{}' has data "
+ "that is not an object",
+ m_name);
+ return false;
+ }
+ for (const auto& [key, value] : data_iter->second.get<picojson::object>())
+ {
+ if (!value.is<std::string>())
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load mod configuration file, specified asset '{}' has data "
+ "with a value for key '{}' that is not a string",
+ m_name, key);
+ }
+ m_map[key] = value.to_str();
+ }
+
+ return true;
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h
new file mode 100644
index 0000000000..b38ba792cc
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h
@@ -0,0 +1,18 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include <picojson.h>
+
+#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
+
+struct GraphicsModAssetConfig
+{
+ std::string m_name;
+ VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map;
+
+ bool DeserializeFromConfig(const picojson::object& obj);
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp
new file mode 100644
index 0000000000..b774324a58
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp
@@ -0,0 +1,449 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h"
+
+#include <algorithm>
+#include <array>
+
+#include <fmt/format.h>
+
+#include "Common/FileUtil.h"
+#include "Common/Logging/Log.h"
+#include "Common/StringUtil.h"
+#include "Core/System.h"
+
+#include "VideoCommon/AbstractGfx.h"
+#include "VideoCommon/Assets/CustomAssetLoader.h"
+#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
+#include "VideoCommon/ShaderGenCommon.h"
+#include "VideoCommon/TextureCacheBase.h"
+
+namespace
+{
+bool IsQualifier(std::string_view value)
+{
+ static std::array<std::string_view, 7> qualifiers = {"attribute", "const", "highp", "lowp",
+ "mediump", "uniform", "varying"};
+ return std::find(qualifiers.begin(), qualifiers.end(), value) != qualifiers.end();
+}
+
+bool IsBuiltInMacro(std::string_view value)
+{
+ static std::array<std::string_view, 5> built_in = {"__LINE__", "__FILE__", "__VERSION__",
+ "GL_core_profile", "GL_compatibility_profile"};
+ return std::find(built_in.begin(), built_in.end(), value) != built_in.end();
+}
+
+std::vector<std::string> GlobalConflicts(std::string_view source)
+{
+ std::string_view last_identifier = "";
+ std::vector<std::string> global_result;
+ u32 scope = 0;
+ for (u32 i = 0; i < source.size(); i++)
+ {
+ // If we're out of global scope, we don't care
+ // about any of the details
+ if (scope > 0)
+ {
+ if (source[i] == '{')
+ {
+ scope++;
+ }
+ else if (source[i] == '}')
+ {
+ scope--;
+ }
+ continue;
+ }
+
+ const auto parse_identifier = [&]() {
+ const u32 start = i;
+ for (; i < source.size(); i++)
+ {
+ if (!Common::IsAlpha(source[i]) && source[i] != '_' && !std::isdigit(source[i]))
+ break;
+ }
+ u32 end = i;
+ i--; // unwind
+ return source.substr(start, end - start);
+ };
+
+ if (Common::IsAlpha(source[i]) || source[i] == '_')
+ {
+ const std::string_view identifier = parse_identifier();
+ if (IsQualifier(identifier))
+ continue;
+ if (IsBuiltInMacro(identifier))
+ continue;
+ last_identifier = identifier;
+ }
+ else if (source[i] == '#')
+ {
+ const auto parse_until_end_of_preprocessor = [&]() {
+ bool continue_until_next_newline = false;
+ for (; i < source.size(); i++)
+ {
+ if (source[i] == '\n')
+ {
+ if (continue_until_next_newline)
+ continue_until_next_newline = false;
+ else
+ break;
+ }
+ else if (source[i] == '\\')
+ {
+ continue_until_next_newline = true;
+ }
+ }
+ };
+ i++;
+ const std::string_view identifier = parse_identifier();
+ if (identifier == "define")
+ {
+ i++;
+ // skip whitespace
+ while (source[i] == ' ')
+ {
+ i++;
+ }
+ global_result.push_back(std::string{parse_identifier()});
+ parse_until_end_of_preprocessor();
+ }
+ else
+ {
+ parse_until_end_of_preprocessor();
+ }
+ }
+ else if (source[i] == '{')
+ {
+ scope++;
+ }
+ else if (source[i] == '(')
+ {
+ // Unlikely the user will be using layouts but...
+ if (last_identifier == "layout")
+ continue;
+
+ // Since we handle equality, we can assume the identifier
+ // before '(' is a function definition
+ global_result.push_back(std::string{last_identifier});
+ }
+ else if (source[i] == '=')
+ {
+ global_result.push_back(std::string{last_identifier});
+ i++;
+ for (; i < source.size(); i++)
+ {
+ if (source[i] == ';')
+ break;
+ }
+ }
+ else if (source[i] == '/')
+ {
+ if ((i + 1) >= source.size())
+ continue;
+
+ if (source[i + 1] == '/')
+ {
+ // Go to end of line...
+ for (; i < source.size(); i++)
+ {
+ if (source[i] == '\n')
+ break;
+ }
+ }
+ else if (source[i + 1] == '*')
+ {
+ // Multiline, look for first '*/'
+ for (; i < source.size(); i++)
+ {
+ if (source[i] == '/' && source[i - 1] == '*')
+ break;
+ }
+ }
+ }
+ }
+
+ // Sort the conflicts from largest to smallest string
+ // this way we can ensure smaller strings that are a substring
+ // of the larger string are able to be replaced appropriately
+ std::sort(global_result.begin(), global_result.end(),
+ [](const std::string& first, const std::string& second) {
+ return first.size() > second.size();
+ });
+ return global_result;
+}
+
+void WriteDefines(ShaderCode* out, const std::vector<std::string>& texture_code_names,
+ u32 texture_unit)
+{
+ for (std::size_t i = 0; i < texture_code_names.size(); i++)
+ {
+ const auto& code_name = texture_code_names[i];
+ out->Write("#define {}_UNIT_{{0}} {}\n", code_name, texture_unit);
+ out->Write(
+ "#define {0}_COORD_{{0}} float3(data.texcoord[data.texmap_to_texcoord_index[{1}]].xy, "
+ "{2})\n",
+ code_name, texture_unit, i + 1);
+ }
+}
+
+} // namespace
+
+std::unique_ptr<CustomPipelineAction>
+CustomPipelineAction::Create(const picojson::value& json_data,
+ std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
+{
+ std::vector<CustomPipelineAction::PipelinePassPassDescription> pipeline_passes;
+
+ const auto& passes_json = json_data.get("passes");
+ if (passes_json.is<picojson::array>())
+ {
+ for (const auto& passes_json_val : passes_json.get<picojson::array>())
+ {
+ CustomPipelineAction::PipelinePassPassDescription pipeline_pass;
+ if (!passes_json_val.is<picojson::object>())
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load custom pipeline action, 'passes' has an array value that "
+ "is not an object!");
+ return nullptr;
+ }
+
+ auto pass = passes_json_val.get<picojson::object>();
+ if (!pass.contains("pixel_material_asset"))
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Failed to load custom pipeline action, 'passes' value missing required "
+ "field 'pixel_material_asset'");
+ return nullptr;
+ }
+
+ auto pixel_material_asset_json = pass["pixel_material_asset"];
+ if (!pixel_material_asset_json.is<std::string>())
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load custom pipeline action, 'passes' field "
+ "'pixel_material_asset' is not a string!");
+ return nullptr;
+ }
+ pipeline_pass.m_pixel_material_asset = pixel_material_asset_json.to_str();
+ pipeline_passes.push_back(std::move(pipeline_pass));
+ }
+ }
+
+ if (pipeline_passes.empty())
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to load custom pipeline action, must specify at least one pass");
+ return nullptr;
+ }
+
+ if (pipeline_passes.size() > 1)
+ {
+ ERROR_LOG_FMT(
+ VIDEO,
+ "Failed to load custom pipeline action, multiple passes are not currently supported");
+ return nullptr;
+ }
+
+ return std::make_unique<CustomPipelineAction>(std::move(library), std::move(pipeline_passes));
+}
+
+CustomPipelineAction::CustomPipelineAction(
+ std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
+ std::vector<PipelinePassPassDescription> pass_descriptions)
+ : m_library(std::move(library)), m_passes_config(std::move(pass_descriptions))
+{
+ m_passes.resize(m_passes_config.size());
+}
+
+CustomPipelineAction::~CustomPipelineAction() = default;
+
+void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started)
+{
+ if (!draw_started) [[unlikely]]
+ return;
+
+ if (!draw_started->custom_pixel_shader) [[unlikely]]
+ return;
+
+ if (!m_valid)
+ return;
+
+ if (m_passes.empty()) [[unlikely]]
+ return;
+
+ // For now assume a single pass
+ auto& pass = m_passes[0];
+
+ if (!pass.m_pixel_shader.m_asset) [[unlikely]]
+ return;
+
+ const auto shader_data = pass.m_pixel_shader.m_asset->GetData();
+ if (shader_data)
+ {
+ if (pass.m_pixel_shader.m_asset->GetLastLoadedTime() > pass.m_pixel_shader.m_cached_write_time)
+ {
+ const auto material = pass.m_pixel_material.m_asset->GetData();
+ if (!material)
+ return;
+
+ pass.m_pixel_shader.m_cached_write_time = pass.m_pixel_shader.m_asset->GetLastLoadedTime();
+
+ for (const auto& prop : material->properties)
+ {
+ if (!shader_data->m_properties.contains(prop.m_code_name))
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Custom pipeline has material asset '{}' that has property '{}'"
+ "that is not on shader asset '{}'",
+ pass.m_pixel_material.m_asset->GetAssetId(), prop.m_code_name,
+ pass.m_pixel_shader.m_asset->GetAssetId());
+ return;
+ }
+ }
+
+ // Calculate shader details
+ std::string color_shader_data =
+ ReplaceAll(shader_data->m_shader_source, "custom_main", CUSTOM_PIXELSHADER_COLOR_FUNC);
+ const auto global_conflicts = GlobalConflicts(color_shader_data);
+ color_shader_data = ReplaceAll(color_shader_data, "\r\n", "\n");
+ color_shader_data = ReplaceAll(color_shader_data, "{", "{{");
+ color_shader_data = ReplaceAll(color_shader_data, "}", "}}");
+ // First replace global conflicts with dummy strings
+ // This avoids the problem where a shorter word
+ // is in a longer word, ex two functions: 'execute' and 'execute_fast'
+ for (std::size_t i = 0; i < global_conflicts.size(); i++)
+ {
+ const std::string& identifier = global_conflicts[i];
+ color_shader_data =
+ ReplaceAll(color_shader_data, identifier, fmt::format("_{0}_DOLPHIN_TEMP_{0}_", i));
+ }
+ // Now replace the temporaries with the actual value
+ for (std::size_t i = 0; i < global_conflicts.size(); i++)
+ {
+ const std::string& identifier = global_conflicts[i];
+ color_shader_data = ReplaceAll(color_shader_data, fmt::format("_{0}_DOLPHIN_TEMP_{0}_", i),
+ fmt::format("{}_{{0}}", identifier));
+ }
+
+ for (const auto& texture_code_name : m_texture_code_names)
+ {
+ color_shader_data =
+ ReplaceAll(color_shader_data, fmt::format("{}_COORD", texture_code_name),
+ fmt::format("{}_COORD_{{0}}", texture_code_name));
+ color_shader_data = ReplaceAll(color_shader_data, fmt::format("{}_UNIT", texture_code_name),
+ fmt::format("{}_UNIT_{{0}}", texture_code_name));
+ }
+
+ m_last_generated_shader_code = ShaderCode{};
+ WriteDefines(&m_last_generated_shader_code, m_texture_code_names, draw_started->texture_unit);
+ m_last_generated_shader_code.Write("{}", color_shader_data);
+ }
+ CustomPixelShader custom_pixel_shader;
+ custom_pixel_shader.custom_shader = m_last_generated_shader_code.GetBuffer();
+ *draw_started->custom_pixel_shader = custom_pixel_shader;
+ }
+}
+
+void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate* create)
+{
+ if (!create->custom_textures) [[unlikely]]
+ return;
+
+ if (!create->additional_dependencies) [[unlikely]]
+ return;
+
+ if (m_passes_config.empty()) [[unlikely]]
+ return;
+
+ if (m_passes.empty()) [[unlikely]]
+ return;
+
+ m_valid = true;
+ auto& loader = Core::System::GetInstance().GetCustomAssetLoader();
+
+ // For now assume a single pass
+ const auto& pass_config = m_passes_config[0];
+ auto& pass = m_passes[0];
+
+ if (!pass.m_pixel_material.m_asset)
+ {
+ pass.m_pixel_material.m_asset =
+ loader.LoadMaterial(pass_config.m_pixel_material_asset, m_library);
+ pass.m_pixel_material.m_cached_write_time = pass.m_pixel_material.m_asset->GetLastLoadedTime();
+ }
+ create->additional_dependencies->push_back(VideoCommon::CachedAsset<VideoCommon::CustomAsset>{
+ pass.m_pixel_material.m_asset, pass.m_pixel_material.m_asset->GetLastLoadedTime()});
+
+ const auto material_data = pass.m_pixel_material.m_asset->GetData();
+ if (!material_data)
+ return;
+
+ if (!pass.m_pixel_shader.m_asset || pass.m_pixel_material.m_asset->GetLastLoadedTime() >
+ pass.m_pixel_material.m_cached_write_time)
+ {
+ pass.m_pixel_shader.m_asset = loader.LoadPixelShader(material_data->shader_asset, m_library);
+ // Note: the asset timestamp will be updated in the draw command
+ }
+ create->additional_dependencies->push_back(VideoCommon::CachedAsset<VideoCommon::CustomAsset>{
+ pass.m_pixel_shader.m_asset, pass.m_pixel_shader.m_asset->GetLastLoadedTime()});
+
+ m_texture_code_names.clear();
+ std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> game_assets;
+ for (const auto& property : material_data->properties)
+ {
+ if (property.m_type == VideoCommon::MaterialProperty::Type::Type_TextureAsset)
+ {
+ if (property.m_value)
+ {
+ if (auto* value = std::get_if<std::string>(&*property.m_value))
+ {
+ auto asset = loader.LoadGameTexture(*value, m_library);
+ if (asset)
+ {
+ const auto loaded_time = asset->GetLastLoadedTime();
+ game_assets.push_back(VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>{
+ std::move(asset), loaded_time});
+ m_texture_code_names.push_back(property.m_code_name);
+ }
+ }
+ }
+ }
+ }
+ // Note: we swap here instead of doing a clear + append of the member
+ // variable so that any loaded assets from previous iterations
+ // won't be let go
+ std::swap(pass.m_game_textures, game_assets);
+
+ for (auto& game_texture : pass.m_game_textures)
+ {
+ if (game_texture.m_asset)
+ {
+ auto data = game_texture.m_asset->GetData();
+ if (data)
+ {
+ if (create->texture_width != data->m_levels[0].width ||
+ create->texture_height != data->m_levels[0].height)
+ {
+ ERROR_LOG_FMT(VIDEO,
+ "Custom pipeline for texture '{}' has asset '{}' that does not match "
+ "the width/height of the texture loaded. Texture {}x{} vs asset {}x{}",
+ create->texture_name, game_texture.m_asset->GetAssetId(),
+ create->texture_width, create->texture_height, data->m_levels[0].width,
+ data->m_levels[0].height);
+ m_valid = false;
+ }
+ }
+ else
+ {
+ m_valid = false;
+ }
+ }
+ }
+
+ // TODO: compare game textures and shader requirements
+
+ create->custom_textures->insert(create->custom_textures->end(), pass.m_game_textures.begin(),
+ pass.m_game_textures.end());
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h
new file mode 100644
index 0000000000..4760da3124
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h
@@ -0,0 +1,54 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <picojson.h>
+
+#include "VideoCommon/AbstractTexture.h"
+#include "VideoCommon/Assets/CustomAssetLibrary.h"
+#include "VideoCommon/Assets/MaterialAsset.h"
+#include "VideoCommon/Assets/ShaderAsset.h"
+#include "VideoCommon/Assets/TextureAsset.h"
+#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
+#include "VideoCommon/ShaderGenCommon.h"
+
+class CustomPipelineAction final : public GraphicsModAction
+{
+public:
+ struct PipelinePassPassDescription
+ {
+ std::string m_pixel_material_asset;
+ };
+
+ static std::unique_ptr<CustomPipelineAction>
+ Create(const picojson::value& json_data,
+ std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
+ CustomPipelineAction(std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
+ std::vector<PipelinePassPassDescription> pass_descriptions);
+ ~CustomPipelineAction();
+ void OnDrawStarted(GraphicsModActionData::DrawStarted*) override;
+ void OnTextureCreate(GraphicsModActionData::TextureCreate*) override;
+
+private:
+ std::shared_ptr<VideoCommon::CustomAssetLibrary> m_library;
+ std::vector<PipelinePassPassDescription> m_passes_config;
+ struct PipelinePass
+ {
+ VideoCommon::CachedAsset<VideoCommon::MaterialAsset> m_pixel_material;
+ VideoCommon::CachedAsset<VideoCommon::PixelShaderAsset> m_pixel_shader;
+ std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> m_game_textures;
+ };
+ std::vector<PipelinePass> m_passes;
+
+ ShaderCode m_last_generated_shader_code;
+
+ bool m_valid = true;
+
+ std::vector<std::string> m_texture_code_names;
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp
new file mode 100644
index 0000000000..27112846c6
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp
@@ -0,0 +1,376 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h"
+#include "VideoCommon/AbstractGfx.h"
+#include "VideoCommon/VideoConfig.h"
+
+CustomShaderCache::CustomShaderCache()
+{
+ m_api_type = g_ActiveConfig.backend_info.api_type;
+ m_host_config.bits = ShaderHostConfig::GetCurrent().bits;
+
+ m_async_shader_compiler = g_gfx->CreateAsyncShaderCompiler();
+ m_async_shader_compiler->StartWorkerThreads(1); // TODO
+
+ m_async_uber_shader_compiler = g_gfx->CreateAsyncShaderCompiler();
+ m_async_uber_shader_compiler->StartWorkerThreads(1); // TODO
+
+ m_frame_end_handler =
+ AfterFrameEvent::Register([this] { RetrieveAsyncShaders(); }, "RetreiveAsyncShaders");
+}
+
+CustomShaderCache::~CustomShaderCache()
+{
+ if (m_async_shader_compiler)
+ m_async_shader_compiler->StopWorkerThreads();
+
+ if (m_async_uber_shader_compiler)
+ m_async_uber_shader_compiler->StopWorkerThreads();
+}
+
+void CustomShaderCache::RetrieveAsyncShaders()
+{
+ m_async_shader_compiler->RetrieveWorkItems();
+ m_async_uber_shader_compiler->RetrieveWorkItems();
+}
+
+void CustomShaderCache::Reload()
+{
+ while (m_async_shader_compiler->HasPendingWork() || m_async_shader_compiler->HasCompletedWork())
+ {
+ m_async_shader_compiler->RetrieveWorkItems();
+ }
+
+ while (m_async_uber_shader_compiler->HasPendingWork() ||
+ m_async_uber_shader_compiler->HasCompletedWork())
+ {
+ m_async_uber_shader_compiler->RetrieveWorkItems();
+ }
+
+ m_ps_cache = {};
+ m_uber_ps_cache = {};
+ m_pipeline_cache = {};
+ m_uber_pipeline_cache = {};
+}
+
+std::optional<const AbstractPipeline*>
+CustomShaderCache::GetPipelineAsync(const VideoCommon::GXPipelineUid& uid,
+ const CustomShaderInstance& custom_shaders,
+ const AbstractPipelineConfig& pipeline_config)
+{
+ if (auto holder = m_pipeline_cache.GetHolder(uid, custom_shaders))
+ {
+ if (holder->pending)
+ return std::nullopt;
+ return holder->value.get();
+ }
+ AsyncCreatePipeline(uid, custom_shaders, pipeline_config);
+ return std::nullopt;
+}
+
+std::optional<const AbstractPipeline*>
+CustomShaderCache::GetPipelineAsync(const VideoCommon::GXUberPipelineUid& uid,
+ const CustomShaderInstance& custom_shaders,
+ const AbstractPipelineConfig& pipeline_config)
+{
+ if (auto holder = m_uber_pipeline_cache.GetHolder(uid, custom_shaders))
+ {
+ if (holder->pending)
+ return std::nullopt;
+ return holder->value.get();
+ }
+ AsyncCreatePipeline(uid, custom_shaders, pipeline_config);
+ return std::nullopt;
+}
+
+void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid,
+
+ const CustomShaderInstance& custom_shaders,
+ const AbstractPipelineConfig& pipeline_config)
+{
+ class PipelineWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
+ {
+ public:
+ PipelineWorkItem(CustomShaderCache* shader_cache, const VideoCommon::GXPipelineUid& uid,
+ const CustomShaderInstance& custom_shaders, PipelineIterator iterator,
+ const AbstractPipelineConfig& pipeline_config)
+ : m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator),
+ m_custom_shaders(custom_shaders), m_config(pipeline_config)
+ {
+ SetStagesReady();
+ }
+
+ void SetStagesReady()
+ {
+ m_stages_ready = true;
+
+ PixelShaderUid ps_uid = m_uid.ps_uid;
+ ClearUnusedPixelShaderUidBits(m_shader_cache->m_api_type, m_shader_cache->m_host_config,
+ &ps_uid);
+
+ if (auto holder = m_shader_cache->m_ps_cache.GetHolder(ps_uid, m_custom_shaders))
+ {
+ // If the pixel shader is no longer pending compilation
+ // and the shader compilation succeeded, set
+ // the pipeline to use the new pixel shader.
+ // Otherwise, use the existing shader.
+ if (!holder->pending && holder->value.get())
+ {
+ m_config.pixel_shader = holder->value.get();
+ }
+ m_stages_ready &= !holder->pending;
+ }
+ else
+ {
+ m_stages_ready &= false;
+ m_shader_cache->QueuePixelShaderCompile(ps_uid, m_custom_shaders);
+ }
+ }
+
+ bool Compile() override
+ {
+ if (m_stages_ready)
+ {
+ m_pipeline = g_gfx->CreatePipeline(m_config);
+ }
+ return true;
+ }
+
+ void Retrieve() override
+ {
+ if (m_stages_ready)
+ {
+ m_shader_cache->NotifyPipelineFinished(m_iterator, std::move(m_pipeline));
+ }
+ else
+ {
+ // Re-queue for next frame.
+ auto wi = m_shader_cache->m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(
+ m_shader_cache, m_uid, m_custom_shaders, m_iterator, m_config);
+ m_shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi), 0);
+ }
+ }
+
+ private:
+ CustomShaderCache* m_shader_cache;
+ std::unique_ptr<AbstractPipeline> m_pipeline;
+ VideoCommon::GXPipelineUid m_uid;
+ PipelineIterator m_iterator;
+ AbstractPipelineConfig m_config;
+ CustomShaderInstance m_custom_shaders;
+ bool m_stages_ready;
+ };
+
+ auto list_iter = m_pipeline_cache.InsertElement(uid, custom_shaders);
+ auto work_item = m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(
+ this, uid, custom_shaders, list_iter, pipeline_config);
+ m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0);
+}
+
+void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXUberPipelineUid& uid,
+
+ const CustomShaderInstance& custom_shaders,
+ const AbstractPipelineConfig& pipeline_config)
+{
+ class PipelineWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
+ {
+ public:
+ PipelineWorkItem(CustomShaderCache* shader_cache, const VideoCommon::GXUberPipelineUid& uid,
+ const CustomShaderInstance& custom_shaders, UberPipelineIterator iterator,
+ const AbstractPipelineConfig& pipeline_config)
+ : m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator),
+ m_custom_shaders(custom_shaders), m_config(pipeline_config)
+ {
+ SetStagesReady();
+ }
+
+ void SetStagesReady()
+ {
+ m_stages_ready = true;
+
+ UberShader::PixelShaderUid ps_uid = m_uid.ps_uid;
+ ClearUnusedPixelShaderUidBits(m_shader_cache->m_api_type, m_shader_cache->m_host_config,
+ &ps_uid);
+
+ if (auto holder = m_shader_cache->m_uber_ps_cache.GetHolder(ps_uid, m_custom_shaders))
+ {
+ if (!holder->pending && holder->value.get())
+ {
+ m_config.pixel_shader = holder->value.get();
+ }
+ m_stages_ready &= !holder->pending;
+ }
+ else
+ {
+ m_stages_ready &= false;
+ m_shader_cache->QueuePixelShaderCompile(ps_uid, m_custom_shaders);
+ }
+ }
+
+ bool Compile() override
+ {
+ if (m_stages_ready)
+ {
+ if (m_config.pixel_shader == nullptr || m_config.vertex_shader == nullptr)
+ return false;
+
+ m_pipeline = g_gfx->CreatePipeline(m_config);
+ }
+ return true;
+ }
+
+ void Retrieve() override
+ {
+ if (m_stages_ready)
+ {
+ m_shader_cache->NotifyPipelineFinished(m_iterator, std::move(m_pipeline));
+ }
+ else
+ {
+ // Re-queue for next frame.
+ auto wi = m_shader_cache->m_async_uber_shader_compiler->CreateWorkItem<PipelineWorkItem>(
+ m_shader_cache, m_uid, m_custom_shaders, m_iterator, m_config);
+ m_shader_cache->m_async_uber_shader_compiler->QueueWorkItem(std::move(wi), 0);
+ }
+ }
+
+ private:
+ CustomShaderCache* m_shader_cache;
+ std::unique_ptr<AbstractPipeline> m_pipeline;
+ VideoCommon::GXUberPipelineUid m_uid;
+ UberPipelineIterator m_iterator;
+ AbstractPipelineConfig m_config;
+ CustomShaderInstance m_custom_shaders;
+ bool m_stages_ready;
+ };
+
+ auto list_iter = m_uber_pipeline_cache.InsertElement(uid, custom_shaders);
+ auto work_item = m_async_uber_shader_compiler->CreateWorkItem<PipelineWorkItem>(
+ this, uid, custom_shaders, list_iter, pipeline_config);
+ m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0);
+}
+
+void CustomShaderCache::NotifyPipelineFinished(PipelineIterator iterator,
+ std::unique_ptr<AbstractPipeline> pipeline)
+{
+ iterator->second.pending = false;
+ iterator->second.value = std::move(pipeline);
+}
+
+void CustomShaderCache::NotifyPipelineFinished(UberPipelineIterator iterator,
+ std::unique_ptr<AbstractPipeline> pipeline)
+{
+ iterator->second.pending = false;
+ iterator->second.value = std::move(pipeline);
+}
+
+void CustomShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid,
+
+ const CustomShaderInstance& custom_shaders)
+{
+ class PixelShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
+ {
+ public:
+ PixelShaderWorkItem(CustomShaderCache* shader_cache, const PixelShaderUid& uid,
+ const CustomShaderInstance& custom_shaders, PixelShaderIterator iter)
+ : m_shader_cache(shader_cache), m_uid(uid), m_custom_shaders(custom_shaders), m_iter(iter)
+ {
+ }
+
+ bool Compile() override
+ {
+ m_shader = m_shader_cache->CompilePixelShader(m_uid, m_custom_shaders);
+ return true;
+ }
+
+ void Retrieve() override
+ {
+ m_shader_cache->NotifyPixelShaderFinished(m_iter, std::move(m_shader));
+ }
+
+ private:
+ CustomShaderCache* m_shader_cache;
+ std::unique_ptr<AbstractShader> m_shader;
+ PixelShaderUid m_uid;
+ CustomShaderInstance m_custom_shaders;
+ PixelShaderIterator m_iter;
+ };
+
+ auto list_iter = m_ps_cache.InsertElement(uid, custom_shaders);
+ auto work_item = m_async_shader_compiler->CreateWorkItem<PixelShaderWorkItem>(
+ this, uid, custom_shaders, list_iter);
+ m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0);
+}
+
+void CustomShaderCache::QueuePixelShaderCompile(const UberShader::PixelShaderUid& uid,
+
+ const CustomShaderInstance& custom_shaders)
+{
+ class PixelShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
+ {
+ public:
+ PixelShaderWorkItem(CustomShaderCache* shader_cache, const UberShader::PixelShaderUid& uid,
+ const CustomShaderInstance& custom_shaders, UberPixelShaderIterator iter)
+ : m_shader_cache(shader_cache), m_uid(uid), m_custom_shaders(custom_shaders), m_iter(iter)
+ {
+ }
+
+ bool Compile() override
+ {
+ m_shader = m_shader_cache->CompilePixelShader(m_uid, m_custom_shaders);
+ return true;
+ }
+
+ void Retrieve() override
+ {
+ m_shader_cache->NotifyPixelShaderFinished(m_iter, std::move(m_shader));
+ }
+
+ private:
+ CustomShaderCache* m_shader_cache;
+ std::unique_ptr<AbstractShader> m_shader;
+ UberShader::PixelShaderUid m_uid;
+ CustomShaderInstance m_custom_shaders;
+ UberPixelShaderIterator m_iter;
+ };
+
+ auto list_iter = m_uber_ps_cache.InsertElement(uid, custom_shaders);
+ auto work_item = m_async_uber_shader_compiler->CreateWorkItem<PixelShaderWorkItem>(
+ this, uid, custom_shaders, list_iter);
+ m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0);
+}
+
+std::unique_ptr<AbstractShader>
+CustomShaderCache::CompilePixelShader(const PixelShaderUid& uid,
+ const CustomShaderInstance& custom_shaders) const
+{
+ const ShaderCode source_code = GeneratePixelShaderCode(
+ m_api_type, m_host_config, uid.GetUidData(), custom_shaders.pixel_contents);
+ return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
+ "Custom Pixel Shader");
+}
+
+std::unique_ptr<AbstractShader>
+CustomShaderCache::CompilePixelShader(const UberShader::PixelShaderUid& uid,
+ const CustomShaderInstance& custom_shaders) const
+{
+ const ShaderCode source_code =
+ GenPixelShader(m_api_type, m_host_config, uid.GetUidData(), custom_shaders.pixel_contents);
+ return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
+ "Custom Uber Pixel Shader");
+}
+
+void CustomShaderCache::NotifyPixelShaderFinished(PixelShaderIterator iterator,
+ std::unique_ptr<AbstractShader> shader)
+{
+ iterator->second.pending = false;
+ iterator->second.value = std::move(shader);
+}
+
+void CustomShaderCache::NotifyPixelShaderFinished(UberPixelShaderIterator iterator,
+ std::unique_ptr<AbstractShader> shader)
+{
+ iterator->second.pending = false;
+ iterator->second.value = std::move(shader);
+}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h
new file mode 100644
index 0000000000..ff2aba2823
--- /dev/null
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h
@@ -0,0 +1,144 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <list>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+
+#include "VideoCommon/AbstractPipeline.h"
+#include "VideoCommon/AbstractShader.h"
+#include "VideoCommon/AsyncShaderCompiler.h"
+#include "VideoCommon/GXPipelineTypes.h"
+#include "VideoCommon/PixelShaderGen.h"
+#include "VideoCommon/ShaderGenCommon.h"
+#include "VideoCommon/UberShaderPixel.h"
+#include "VideoCommon/VideoEvents.h"
+
+struct CustomShaderInstance
+{
+ CustomPixelShaderContents pixel_contents;
+
+ bool operator==(const CustomShaderInstance& other) const = default;
+};
+
+class CustomShaderCache
+{
+public:
+ CustomShaderCache();
+ ~CustomShaderCache();
+ CustomShaderCache(const CustomShaderCache&) = delete;
+ CustomShaderCache(CustomShaderCache&&) = delete;
+ CustomShaderCache& operator=(const CustomShaderCache&) = delete;
+ CustomShaderCache& operator=(CustomShaderCache&&) = delete;
+
+ // Changes the shader host config. Shaders should be reloaded afterwards.
+ void SetHostConfig(const ShaderHostConfig& host_config) { m_host_config.bits = host_config.bits; }
+
+ // Retrieves all pending shaders/pipelines from the async compiler.
+ void RetrieveAsyncShaders();
+
+ // Reloads/recreates all shaders and pipelines.
+ void Reload();
+
+ // The optional will be empty if this pipeline is now background compiling.
+ std::optional<const AbstractPipeline*>
+ GetPipelineAsync(const VideoCommon::GXPipelineUid& uid,
+ const CustomShaderInstance& custom_shaders,
+ const AbstractPipelineConfig& pipeline_config);
+ std::optional<const AbstractPipeline*>
+ GetPipelineAsync(const VideoCommon::GXUberPipelineUid& uid,
+ const CustomShaderInstance& custom_shaders,
+ const AbstractPipelineConfig& pipeline_config);
+
+private:
+ // Configuration bits.
+ APIType m_api_type = APIType::Nothing;
+ ShaderHostConfig m_host_config = {};
+ std::unique_ptr<VideoCommon::AsyncShaderCompiler> m_async_shader_compiler;
+ std::unique_ptr<VideoCommon::AsyncShaderCompiler> m_async_uber_shader_compiler;
+
+ void AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid,
+ const CustomShaderInstance& custom_shaders,
+ const AbstractPipelineConfig& pipeline_config);
+ void AsyncCreatePipeline(const VideoCommon::GXUberPipelineUid& uid,
+ const CustomShaderInstance& custom_shaders,
+ const AbstractPipelineConfig& pipeline_config);
+
+ // Shader/Pipeline cache helper
+ template <typename Uid, typename ValueType>
+ struct Cache
+ {
+ struct CacheHolder
+ {
+ std::unique_ptr<ValueType> value = nullptr;
+ bool pending = true;
+ };
+ using CacheElement = std::pair<CustomShaderInstance, CacheHolder>;
+ using CacheList = std::list<CacheElement>;
+ std::map<Uid, CacheList> uid_to_cachelist;
+
+ const CacheHolder* GetHolder(const Uid& uid, const CustomShaderInstance& custom_shaders) const
+ {
+ if (auto uuid_it = uid_to_cachelist.find(uid); uuid_it != uid_to_cachelist.end())
+ {
+ for (const auto& [custom_shader_val, holder] : uuid_it->second)
+ {
+ if (custom_shaders == custom_shader_val)
+ {
+ return &holder;
+ }
+ }
+ }
+
+ return nullptr;
+ }
+
+ typename CacheList::iterator InsertElement(const Uid& uid,
+ const CustomShaderInstance& custom_shaders)
+ {
+ CacheList& cachelist = uid_to_cachelist[uid];
+ CacheElement e{custom_shaders, CacheHolder{}};
+ return cachelist.emplace(cachelist.begin(), std::move(e));
+ }
+ };
+
+ Cache<PixelShaderUid, AbstractShader> m_ps_cache;
+ Cache<UberShader::PixelShaderUid, AbstractShader> m_uber_ps_cache;
+ Cache<VideoCommon::GXPipelineUid, AbstractPipeline> m_pipeline_cache;
+ Cache<VideoCommon::GXUberPipelineUid, AbstractPipeline> m_uber_pipeline_cache;
+
+ using PipelineIterator = Cache<VideoCommon::GXPipelineUid, AbstractPipeline>::CacheList::iterator;
+ using UberPipelineIterator =
+ Cache<VideoCommon::GXUberPipelineUid, AbstractPipeline>::CacheList::iterator;
+ using PixelShaderIterator = Cache<PixelShaderUid, AbstractShader>::CacheList::iterator;
+ using UberPixelShaderIterator =
+ Cache<UberShader::PixelShaderUid, AbstractShader>::CacheList::iterator;
+
+ void NotifyPipelineFinished(PipelineIterator iterator,
+ std::unique_ptr<AbstractPipeline> pipeline);
+ void NotifyPipelineFinished(UberPipelineIterator iterator,
+ std::unique_ptr<AbstractPipeline> pipeline);
+
+ std::unique_ptr<AbstractShader>
+ CompilePixelShader(const PixelShaderUid& uid, const CustomShaderInstance& custom_shaders) const;
+ void NotifyPixelShaderFinished(PixelShaderIterator iterator,
+ std::unique_ptr<AbstractShader> shader);
+ std::unique_ptr<AbstractShader>
+ CompilePixelShader(const UberShader::PixelShaderUid& uid,
+ const CustomShaderInstance& custom_shaders) const;
+ void NotifyPixelShaderFinished(UberPixelShaderIterator iterator,
+ std::unique_ptr<AbstractShader> shader);
+
+ void QueuePixelShaderCompile(const PixelShaderUid& uid,
+ const CustomShaderInstance& custom_shaders);
+ void QueuePixelShaderCompile(const UberShader::PixelShaderUid& uid,
+ const CustomShaderInstance& custom_shaders);
+
+ Common::EventHook m_frame_end_handler;
+};
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h
index 2b1408dcc9..a9efb6a0dd 100644
--- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h
@@ -3,18 +3,23 @@
#pragma once
+#include <array>
+#include <optional>
#include <string_view>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/Matrix.h"
#include "VideoCommon/Assets/TextureAsset.h"
+#include "VideoCommon/PixelShaderGen.h"
namespace GraphicsModActionData
{
struct DrawStarted
{
+ u32 texture_unit;
bool* skip;
+ std::optional<CustomPixelShader>* custom_pixel_shader;
};
struct EFB
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp
index 7b97155d8a..6ff64aa038 100644
--- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp
@@ -3,6 +3,7 @@
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
+#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h"
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h"
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h"
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h"
@@ -11,7 +12,7 @@
namespace GraphicsModActionFactory
{
std::unique_ptr<GraphicsModAction> Create(std::string_view name, const picojson::value& json_data,
- std::string_view path)
+ std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
{
if (name == "print")
{
@@ -29,6 +30,10 @@ std::unique_ptr<GraphicsModAction> Create(std::string_view name, const picojson:
{
return ScaleAction::Create(json_data);
}
+ else if (name == "custom_pipeline")
+ {
+ return CustomPipelineAction::Create(json_data, std::move(library));
+ }
return nullptr;
}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h
index 7709d75617..069533d6d1 100644
--- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h
@@ -8,10 +8,11 @@
#include <picojson.h>
+#include "VideoCommon/Assets/CustomAssetLibrary.h"
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
namespace GraphicsModActionFactory
{
std::unique_ptr<GraphicsModAction> Create(std::string_view name, const picojson::value& json_data,
- std::string_view path);
+ std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
}
diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp
index 3e4cd6e4e6..0d2f0fe347 100644
--- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp
+++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp
@@ -13,7 +13,9 @@
#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"
@@ -187,6 +189,8 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
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)
{
@@ -208,6 +212,29 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
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_name, mod.m_title);
+ }
+ else
+ {
+ v = std::filesystem::path{base_path} / v;
+ }
+ }
+
+ filesystem_library->SetAssetIDMapData(asset.m_name, std::move(asset_map));
+ }
}
for (const auto& mod : mods)
@@ -215,12 +242,11 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
for (const GraphicsModFeatureConfig& feature : mod.m_features)
{
const auto create_action =
- [](const std::string_view& action_name, const picojson::value& json_data,
- GraphicsModConfig mod_config) -> std::unique_ptr<GraphicsModAction> {
- std::string base_path;
- SplitPath(mod_config.GetAbsolutePath(), &base_path, nullptr, nullptr);
-
- auto action = GraphicsModActionFactory::Create(action_name, json_data, base_path);
+ [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;