diff options
Diffstat (limited to 'Source/Core/VideoCommon/GraphicsModSystem')
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; |
