diff options
| author | Admiral H. Curtiss <pikachu025@gmail.com> | 2023-08-22 02:18:40 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-22 02:18:40 +0200 |
| commit | dbe6a5f7e4caca71de9d9ba86916f82c0bc21683 (patch) | |
| tree | 84fd8c6f664f1df62350f9d7b5aa9d110b6aee02 /Source/Core | |
| parent | 3451cb1ca2583f9c2b02d0d216355047787c134b (diff) | |
| parent | 55061216855af89f21eee1174fa81d927bc94991 (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')
27 files changed, 2325 insertions, 80 deletions
diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 1b44f15ec4..967b6ac9ad 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -663,15 +663,18 @@ <ClInclude Include="VideoCommon\GeometryShaderGen.h" /> <ClInclude Include="VideoCommon\GeometryShaderManager.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsMod.h" /> + <ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsModAsset.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsModFeature.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsModGroup.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsTarget.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Config\GraphicsTargetGroup.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Constants.h" /> + <ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\CustomPipelineAction.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\MoveAction.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\PrintAction.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\ScaleAction.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\SkipAction.h" /> + <ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\CustomShaderCache.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\FBInfo.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModAction.h" /> <ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModActionData.h" /> @@ -1276,14 +1279,17 @@ <ClCompile Include="VideoCommon\GeometryShaderGen.cpp" /> <ClCompile Include="VideoCommon\GeometryShaderManager.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsMod.cpp" /> + <ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsModAsset.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsModFeature.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsModGroup.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsTarget.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Config\GraphicsTargetGroup.cpp" /> + <ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\CustomPipelineAction.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\MoveAction.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\PrintAction.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\ScaleAction.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\SkipAction.cpp" /> + <ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\CustomShaderCache.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\FBInfo.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModActionFactory.cpp" /> <ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModManager.cpp" /> diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index ba3b67faba..46f1955524 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -64,6 +64,8 @@ add_library(videocommon GeometryShaderManager.h GraphicsModSystem/Config/GraphicsMod.cpp GraphicsModSystem/Config/GraphicsMod.h + GraphicsModSystem/Config/GraphicsModAsset.cpp + GraphicsModSystem/Config/GraphicsModAsset.h GraphicsModSystem/Config/GraphicsModFeature.cpp GraphicsModSystem/Config/GraphicsModFeature.h GraphicsModSystem/Config/GraphicsModGroup.cpp @@ -73,6 +75,8 @@ add_library(videocommon GraphicsModSystem/Config/GraphicsTargetGroup.cpp GraphicsModSystem/Config/GraphicsTargetGroup.h GraphicsModSystem/Constants.h + GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp + GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h GraphicsModSystem/Runtime/Actions/MoveAction.cpp GraphicsModSystem/Runtime/Actions/MoveAction.h GraphicsModSystem/Runtime/Actions/PrintAction.cpp @@ -81,6 +85,8 @@ add_library(videocommon GraphicsModSystem/Runtime/Actions/ScaleAction.h GraphicsModSystem/Runtime/Actions/SkipAction.cpp GraphicsModSystem/Runtime/Actions/SkipAction.h + GraphicsModSystem/Runtime/CustomShaderCache.cpp + GraphicsModSystem/Runtime/CustomShaderCache.h GraphicsModSystem/Runtime/FBInfo.cpp GraphicsModSystem/Runtime/FBInfo.h GraphicsModSystem/Runtime/GraphicsModAction.h diff --git a/Source/Core/VideoCommon/ConstantManager.h b/Source/Core/VideoCommon/ConstantManager.h index 88c25a9823..b8c65aaefb 100644 --- a/Source/Core/VideoCommon/ConstantManager.h +++ b/Source/Core/VideoCommon/ConstantManager.h @@ -58,6 +58,8 @@ struct alignas(16) PixelShaderConstants // For shader_framebuffer_fetch logic ops: u32 logic_op_enable; // bool LogicOp logic_op_mode; + // For custom shaders... + u32 time_ms; }; struct alignas(16) VertexShaderConstants 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; diff --git a/Source/Core/VideoCommon/LightingShaderGen.cpp b/Source/Core/VideoCommon/LightingShaderGen.cpp index b92e63fe9e..646f7277b5 100644 --- a/Source/Core/VideoCommon/LightingShaderGen.cpp +++ b/Source/Core/VideoCommon/LightingShaderGen.cpp @@ -175,3 +175,180 @@ void GetLightingShaderUid(LightingUidData& uid_data) } } } + +void GenerateCustomLightingHeaderDetails(ShaderCode* out, u32 enablelighting, u32 light_mask) +{ + u32 light_count = 0; + for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++) + { + if ((enablelighting & (1 << j)) != 0) // Color lights + { + for (int i = 0; i < 8; ++i) + { + if ((light_mask & (1 << (i + 8 * j))) != 0) + { + light_count++; + } + } + } + if ((enablelighting & (1 << (j + 2))) != 0) // Alpha lights + { + for (int i = 0; i < 8; ++i) + { + if ((light_mask & (1 << (i + 8 * (j + 2)))) != 0) + { + light_count++; + } + } + } + } + if (light_count > 0) + { + out->Write("\tCustomShaderLightData[{}] light;\n", light_count); + } + else + { + // Cheat so shaders compile + out->Write("\tCustomShaderLightData[1] light;\n", light_count); + } + out->Write("\tint light_count;\n"); +} + +void GenerateCustomLightingImplementation(ShaderCode* out, const LightingUidData& uid_data, + std::string_view in_color_name) +{ + auto generate_lighting = [](ShaderCode* out, const LightingUidData& uid_data, int index, + int litchan_index, u32 channel_index, u32 custom_light_index, + bool alpha) { + const auto attnfunc = + static_cast<AttenuationFunc>((uid_data.attnfunc >> (2 * litchan_index)) & 0x3); + + const std::string_view light_type = alpha ? "alpha" : "color"; + const std::string name = fmt::format("lights_chan{}_{}", channel_index, light_type); + + out->Write("\t{{\n"); + out->Write("\t\tcustom_data.{}[{}].direction = " LIGHT_DIR ".xyz;\n", name, custom_light_index, + LIGHT_DIR_PARAMS(index)); + out->Write("\t\tcustom_data.{}[{}].position = " LIGHT_POS ".xyz;\n", name, custom_light_index, + LIGHT_POS_PARAMS(index)); + out->Write("\t\tcustom_data.{}[{}].cosatt = " LIGHT_COSATT ";\n", name, custom_light_index, + LIGHT_COSATT_PARAMS(index)); + out->Write("\t\tcustom_data.{}[{}].distatt = " LIGHT_DISTATT ";\n", name, custom_light_index, + LIGHT_DISTATT_PARAMS(index)); + out->Write("\t\tcustom_data.{}[{}].attenuation_type = {};\n", name, custom_light_index, + static_cast<u32>(attnfunc)); + if (alpha) + { + out->Write("\t\tcustom_data.{}[{}].color = float3(" LIGHT_COL + ") / float3(255.0, 255.0, 255.0);\n", + name, custom_light_index, LIGHT_COL_PARAMS(index, alpha ? "a" : "rgb")); + } + else + { + out->Write("\t\tcustom_data.{}[{}].color = " LIGHT_COL " / float3(255.0, 255.0, 255.0);\n", + name, custom_light_index, LIGHT_COL_PARAMS(index, alpha ? "a" : "rgb")); + } + out->Write("\t}}\n"); + }; + + for (u32 i = 0; i < 8; i++) + { + for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++) + { + out->Write("\tcustom_data.lights_chan{}_color[{}].direction = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_color[{}].position = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_color[{}].color = float3(0, 0, 0);\n", channel_index, + i); + out->Write("\tcustom_data.lights_chan{}_color[{}].cosatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_color[{}].distatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_color[{}].attenuation_type = 0;\n", channel_index, i); + + out->Write("\tcustom_data.lights_chan{}_alpha[{}].direction = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].position = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].color = float3(0, 0, 0);\n", channel_index, + i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].cosatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].distatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = 0;\n", channel_index, i); + } + } + + for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++) + { + const bool colormatsource = !!(uid_data.matsource & (1 << j)); + if (colormatsource) // from vertex + out->Write("custom_data.base_material[{}] = {}{};\n", j, in_color_name, j); + else // from color + out->Write("custom_data.base_material[{}] = {}[{}] / 255.0;\n", j, I_MATERIALS, j + 2); + + if ((uid_data.enablelighting & (1 << j)) != 0) + { + if ((uid_data.ambsource & (1 << j)) != 0) // from vertex + out->Write("custom_data.ambient_lighting[{}] = {}{};\n", j, in_color_name, j); + else // from color + out->Write("custom_data.ambient_lighting[{}] = {}[{}] / 255.0;\n", j, I_MATERIALS, j); + } + else + { + out->Write("custom_data.ambient_lighting[{}] = float4(1, 1, 1, 1);\n", j); + } + + // check if alpha is different + const bool alphamatsource = !!(uid_data.matsource & (1 << (j + 2))); + if (alphamatsource != colormatsource) + { + if (alphamatsource) // from vertex + out->Write("custom_data.base_material[{}].w = {}{}.w;\n", j, in_color_name, j); + else // from color + out->Write("custom_data.base_material[{}].w = {}[{}].w / 255.0;\n", j, I_MATERIALS, j + 2); + } + + if ((uid_data.enablelighting & (1 << (j + 2))) != 0) + { + if ((uid_data.ambsource & (1 << (j + 2))) != 0) // from vertex + out->Write("custom_data.ambient_lighting[{}].w = {}{}.w;\n", j, in_color_name, j); + else // from color + out->Write("custom_data.ambient_lighting[{}].w = {}[{}].w / 255.0;\n", j, I_MATERIALS, j); + } + else + { + out->Write("custom_data.ambient_lighting[{}].w = 1;\n", j); + } + + u32 light_count = 0; + if ((uid_data.enablelighting & (1 << j)) != 0) // Color lights + { + for (int i = 0; i < 8; ++i) + { + if ((uid_data.light_mask & (1 << (i + 8 * j))) != 0) + { + generate_lighting(out, uid_data, i, j, j, light_count, false); + light_count++; + } + } + } + out->Write("\tcustom_data.light_chan{}_color_count = {};\n", j, light_count); + + light_count = 0; + if ((uid_data.enablelighting & (1 << (j + 2))) != 0) // Alpha lights + { + for (int i = 0; i < 8; ++i) + { + if ((uid_data.light_mask & (1 << (i + 8 * (j + 2)))) != 0) + { + generate_lighting(out, uid_data, i, j + 2, j, light_count, true); + light_count++; + } + } + } + out->Write("\tcustom_data.light_chan{}_alpha_count = {};\n", j, light_count); + } +} diff --git a/Source/Core/VideoCommon/LightingShaderGen.h b/Source/Core/VideoCommon/LightingShaderGen.h index a34c04df74..b06ec40c4a 100644 --- a/Source/Core/VideoCommon/LightingShaderGen.h +++ b/Source/Core/VideoCommon/LightingShaderGen.h @@ -47,3 +47,7 @@ constexpr char s_lighting_struct[] = "struct Light {\n" void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_data, std::string_view in_color_name, std::string_view dest); void GetLightingShaderUid(LightingUidData& uid_data); + +void GenerateCustomLightingHeaderDetails(ShaderCode* out, u32 enablelighting, u32 light_mask); +void GenerateCustomLightingImplementation(ShaderCode* out, const LightingUidData& uid_data, + std::string_view in_color_name); diff --git a/Source/Core/VideoCommon/PixelShaderGen.cpp b/Source/Core/VideoCommon/PixelShaderGen.cpp index 0b96e49f4a..69fdc36efc 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.cpp +++ b/Source/Core/VideoCommon/PixelShaderGen.cpp @@ -3,6 +3,7 @@ #include "VideoCommon/PixelShaderGen.h" +#include <algorithm> #include <cmath> #include <cstdio> #include <fmt/format.h> @@ -130,6 +131,17 @@ constexpr Common::EnumMap<const char*, TevColorArg::Zero> tev_c_input_table{ "int3(0,0,0)", // ZERO }; +constexpr Common::EnumMap<const char*, TevColorArg::Zero> tev_c_input_type{ + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC", +}; + constexpr Common::EnumMap<const char*, TevAlphaArg::Zero> tev_a_input_table{ "prev.a", // APREV, "c0.a", // A0, @@ -141,6 +153,13 @@ constexpr Common::EnumMap<const char*, TevAlphaArg::Zero> tev_a_input_table{ "0", // ZERO }; +constexpr Common::EnumMap<const char*, TevAlphaArg::Zero> tev_a_input_type{ + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC", +}; + constexpr Common::EnumMap<const char*, RasColorChan::Zero> tev_ras_table{ "iround(col0 * 255.0)", "iround(col1 * 255.0)", @@ -387,6 +406,7 @@ void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type, "\tbool blend_subtract_alpha;\n" "\tbool logic_op_enable;\n" "\tuint logic_op_mode;\n" + "\tuint time_ms;\n" "}};\n\n"); out.Write("#define bpmem_combiners(i) (bpmem_pack1[(i)].xy)\n" "#define bpmem_tevind(i) (bpmem_pack1[(i)].z)\n" @@ -732,8 +752,131 @@ uint WrapCoord(int coord, uint wrap, int size) {{ } } +void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_stages, bool per_pixel_lighting, + const pixel_shader_uid_data* uid_data) +{ + out->Write("\tCustomShaderData custom_data;\n"); + + if (per_pixel_lighting) + { + out->Write("\tcustom_data.position = WorldPos;\n"); + out->Write("\tcustom_data.normal = Normal;\n"); + } + else + { + out->Write("\tcustom_data.position = float3(0, 0, 0);\n"); + out->Write("\tcustom_data.normal = float3(0, 0, 0);\n"); + } + + if (uid_data->genMode_numtexgens == 0) [[unlikely]] + { + out->Write("\tcustom_data.texcoord[0] = float3(0, 0, 0);\n"); + } + else + { + for (u32 i = 0; i < uid_data->genMode_numtexgens; ++i) + { + out->Write("\tif (tex{0}.z == 0.0)\n", i); + out->Write("\t{{\n"); + out->Write("\t\tcustom_data.texcoord[{0}] = tex{0};\n", i); + out->Write("\t}}\n"); + out->Write("\telse {{\n"); + out->Write("\t\tcustom_data.texcoord[{0}] = float3(tex{0}.xy / tex{0}.z, 0);\n", i); + out->Write("\t}}\n"); + } + } + + for (u32 i = 0; i < 8; i++) + { + // Shader compilation complains if every index isn't initialized + out->Write("\tcustom_data.texmap_to_texcoord_index[{0}] = 0;\n", i); + } + + for (u32 i = 0; i < uid_data->genMode_numindstages; ++i) + { + if ((uid_data->nIndirectStagesUsed & (1U << i)) != 0) + { + u32 texcoord = uid_data->GetTevindirefCoord(i); + const u32 texmap = uid_data->GetTevindirefMap(i); + + // Quirk: when the tex coord is not less than the number of tex gens (i.e. the tex coord does + // not exist), then tex coord 0 is used (though sometimes glitchy effects happen on console). + // This affects the Mario portrait in Luigi's Mansion, where the developers forgot to set + // the number of tex gens to 2 (bug 11462). + if (texcoord >= uid_data->genMode_numtexgens) + texcoord = 0; + + out->Write("\tcustom_data.texmap_to_texcoord_index[{}] = {};\n", texmap, texcoord); + } + } + out->Write("\tcustom_data.texcoord_count = {};\n", uid_data->genMode_numtexgens); + + // Try and do a best guess on what the texcoord index is + // Note: one issue with this would be textures that are used + // multiple times in the same draw but with different texture coordinates. + // In that scenario, only the last texture coordinate would be defined. + // This issue can be seen in how Rogue Squadron 2 does bump mapping + for (u32 i = 0; i < num_stages; i++) + { + auto& tevstage = uid_data->stagehash[i]; + // Quirk: when the tex coord is not less than the number of tex gens (i.e. the tex coord does + // not exist), then tex coord 0 is used (though sometimes glitchy effects happen on console). + u32 texcoord = tevstage.tevorders_texcoord; + const bool has_tex_coord = texcoord < uid_data->genMode_numtexgens; + if (!has_tex_coord) + texcoord = 0; + + out->Write("\tcustom_data.texmap_to_texcoord_index[{}] = {};\n", tevstage.tevorders_texmap, + texcoord); + } + + GenerateCustomLightingImplementation(out, uid_data->lighting, "colors_"); + + for (u32 i = 0; i < 16; i++) + { + // Shader compilation complains if every struct isn't initialized + + // Color Input + for (u32 j = 0; j < 4; j++) + { + out->Write("\tcustom_data.tev_stages[{}].input_color[{}].input_type = " + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n", + i, j); + out->Write("\tcustom_data.tev_stages[{}].input_color[{}].value = " + "float3(0, 0, 0);\n", + i, j); + } + + // Alpha Input + for (u32 j = 0; j < 4; j++) + { + out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].input_type = " + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n", + i, j); + out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].value = " + "float(0);\n", + i, j); + } + + // Texmap + out->Write("\tcustom_data.tev_stages[{}].texmap = 0u;\n", i); + + // Output + out->Write("\tcustom_data.tev_stages[{}].output_color = " + "float4(0, 0, 0, 0);\n", + i); + } + + // Actual data will be filled out in the tev stage code, just set the + // stage count for now + out->Write("\tcustom_data.tev_stage_count = {};\n", num_stages); + + // Time + out->Write("\tcustom_data.time_ms = time_ms;\n"); +} + static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, int n, - APIType api_type, bool stereo); + APIType api_type, bool stereo, bool has_custom_shaders); static void WriteTevRegular(ShaderCode& out, std::string_view components, TevBias bias, TevOp op, bool clamp, TevScale scale); static void WriteAlphaTest(ShaderCode& out, const pixel_shader_uid_data* uid_data, APIType api_type, @@ -746,7 +889,8 @@ static void WriteColor(ShaderCode& out, APIType api_type, const pixel_shader_uid static void WriteBlend(ShaderCode& out, const pixel_shader_uid_data* uid_data); ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& host_config, - const pixel_shader_uid_data* uid_data) + const pixel_shader_uid_data* uid_data, + const CustomPixelShaderContents& custom_details) { ShaderCode out; @@ -762,8 +906,17 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos // Stuff that is shared between ubershaders and pixelgen. WriteBitfieldExtractHeader(out, api_type, host_config); + WritePixelShaderCommonHeader(out, api_type, host_config, uid_data->bounding_box); + // Custom shader details + WriteCustomShaderStructDef(&out, uid_data->genMode_numtexgens); + for (std::size_t i = 0; i < custom_details.shaders.size(); i++) + { + const auto& shader_details = custom_details.shaders[i]; + out.Write(fmt::runtime(shader_details.custom_shader), i); + } + out.Write("\n#define sampleTextureWrapper(texmap, uv, layer) " "sampleTexture(texmap, samp[texmap], uv, layer)\n"); @@ -892,6 +1045,14 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos out.Write("void main()\n{{\n"); out.Write("\tfloat4 rawpos = gl_FragCoord;\n"); + bool has_custom_shaders = false; + if (std::any_of(custom_details.shaders.begin(), custom_details.shaders.end(), + [](const std::optional<CustomPixelShader>& ps) { return ps.has_value(); })) + { + WriteCustomShaderStructImpl(&out, numStages, per_pixel_lighting, uid_data); + has_custom_shaders = true; + } + if (use_framebuffer_fetch) { // Store off a copy of the initial framebuffer value. @@ -1013,7 +1174,7 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos for (u32 i = 0; i < numStages; i++) { // Build the equation for this stage - WriteStage(out, uid_data, i, api_type, stereo); + WriteStage(out, uid_data, i, api_type, stereo, has_custom_shaders); } { @@ -1146,7 +1307,21 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos // Write the color and alpha values to the framebuffer // If using shader blend, we still use the separate alpha - WriteColor(out, api_type, uid_data, !uid_data->no_dual_src || uid_data->blend_enable); + const bool use_dual_source = !uid_data->no_dual_src || uid_data->blend_enable; + WriteColor(out, api_type, uid_data, use_dual_source); + + for (std::size_t i = 0; i < custom_details.shaders.size(); i++) + { + const auto& shader_details = custom_details.shaders[i]; + + if (!shader_details.custom_shader.empty()) + { + out.Write("\t{{\n"); + out.Write("\t\tcustom_data.final_color = ocol0;\n"); + out.Write("\t\tocol0.xyz = {}_{}(custom_data).xyz;\n", CUSTOM_PIXELSHADER_COLOR_FUNC, i); + out.Write("\t}}\n\n"); + } + } if (uid_data->blend_enable) WriteBlend(out, uid_data); @@ -1162,7 +1337,7 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos } static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, int n, - APIType api_type, bool stereo) + APIType api_type, bool stereo, bool has_custom_shaders) { using Common::EnumMap; @@ -1556,6 +1731,58 @@ static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, i out.Write(", -1024, 1023)"); out.Write(";\n"); + + if (has_custom_shaders) + { + // Color input + out.Write( + "\tcustom_data.tev_stages[{}].input_color[0].value = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_input_table[cc.a]); + out.Write("\tcustom_data.tev_stages[{}].input_color[0].input_type = {};\n", n, + tev_c_input_type[cc.a]); + out.Write( + "\tcustom_data.tev_stages[{}].input_color[1].value = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_input_table[cc.b]); + out.Write("\tcustom_data.tev_stages[{}].input_color[1].input_type = {};\n", n, + tev_c_input_type[cc.b]); + out.Write( + "\tcustom_data.tev_stages[{}].input_color[2].value = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_input_table[cc.c]); + out.Write("\tcustom_data.tev_stages[{}].input_color[2].input_type = {};\n", n, + tev_c_input_type[cc.c]); + out.Write( + "\tcustom_data.tev_stages[{}].input_color[3].value = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_input_table[cc.d]); + out.Write("\tcustom_data.tev_stages[{}].input_color[3].input_type = {};\n", n, + tev_c_input_type[cc.d]); + + // Alpha input + out.Write("\tcustom_data.tev_stages[{}].input_alpha[0].value = {} / float(255.0);\n", n, + tev_a_input_table[ac.a]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[0].input_type = {};\n", n, + tev_a_input_type[ac.a]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[1].value = {} / float(255.0);\n", n, + tev_a_input_table[ac.b]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[1].input_type = {};\n", n, + tev_a_input_type[ac.b]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[2].value = {} / float(255.0);\n", n, + tev_a_input_table[ac.c]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[2].input_type = {};\n", n, + tev_a_input_type[ac.c]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[3].value = {} / float(255.0);\n", n, + tev_a_input_table[ac.d]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[3].input_type = {};\n", n, + tev_a_input_type[ac.d]); + + // Texmap + out.Write("\tcustom_data.tev_stages[{}].texmap = {}u;\n", n, stage.tevorders_texmap); + + // Output + out.Write("\tcustom_data.tev_stages[{}].output_color.rgb = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_output_table[cc.dest]); + out.Write("\tcustom_data.tev_stages[{}].output_color.a = {} / float(255.0);\n", n, + tev_a_output_table[ac.dest]); + } } static void WriteTevRegular(ShaderCode& out, std::string_view components, TevBias bias, TevOp op, diff --git a/Source/Core/VideoCommon/PixelShaderGen.h b/Source/Core/VideoCommon/PixelShaderGen.h index 84f1e27f74..456c72c2cd 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.h +++ b/Source/Core/VideoCommon/PixelShaderGen.h @@ -158,8 +158,12 @@ struct pixel_shader_uid_data using PixelShaderUid = ShaderUid<pixel_shader_uid_data>; +void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_stages, bool per_pixel_lighting, + const pixel_shader_uid_data* uid_data); + ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& host_config, - const pixel_shader_uid_data* uid_data); + const pixel_shader_uid_data* uid_data, + const CustomPixelShaderContents& custom_details); void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type, const ShaderHostConfig& host_config, bool bounding_box); void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& host_config, diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index d1deef8424..7d11645949 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -449,7 +449,7 @@ ShaderCache::CompileVertexUberShader(const UberShader::VertexShaderUid& uid) con std::unique_ptr<AbstractShader> ShaderCache::CompilePixelShader(const PixelShaderUid& uid) const { const ShaderCode source_code = - GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData()); + GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData(), {}); return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer()); } @@ -457,7 +457,7 @@ std::unique_ptr<AbstractShader> ShaderCache::CompilePixelUberShader(const UberShader::PixelShaderUid& uid) const { const ShaderCode source_code = - UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData()); + UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData(), {}); return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), fmt::to_string(*uid.GetUidData())); } diff --git a/Source/Core/VideoCommon/ShaderGenCommon.cpp b/Source/Core/VideoCommon/ShaderGenCommon.cpp index 234703d7d5..e4922f3e3e 100644 --- a/Source/Core/VideoCommon/ShaderGenCommon.cpp +++ b/Source/Core/VideoCommon/ShaderGenCommon.cpp @@ -10,6 +10,7 @@ #include "Core/ConfigManager.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" +#include "VideoCommon/XFMemory.h" ShaderHostConfig ShaderHostConfig::GetCurrent() { @@ -362,3 +363,90 @@ const char* GetInterpolationQualifier(bool msaa, bool ssaa, bool in_glsl_interfa return "sample"; } } + +void WriteCustomShaderStructDef(ShaderCode* out, u32 numtexgens) +{ + // Bump this when there are breaking changes to the API + out->Write("#define CUSTOM_SHADER_API_VERSION 1;\n"); + + // CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE "enum" values + out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_NONE = {};\n", + static_cast<u32>(AttenuationFunc::None)); + out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT = {};\n", + static_cast<u32>(AttenuationFunc::Spec)); + out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_DIR = {};\n", + static_cast<u32>(AttenuationFunc::Dir)); + out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_SPOT = {};\n", + static_cast<u32>(AttenuationFunc::Spot)); + + out->Write("struct CustomShaderLightData\n"); + out->Write("{{\n"); + out->Write("\tfloat3 position;\n"); + out->Write("\tfloat3 direction;\n"); + out->Write("\tfloat3 color;\n"); + out->Write("\tuint attenuation_type;\n"); + out->Write("\tfloat4 cosatt;\n"); + out->Write("\tfloat4 distatt;\n"); + out->Write("}};\n\n"); + + // CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE "enum" values + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV = 0;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR = 1;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX = 2;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS = 3;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST = 4;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC = 5;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED = 6;\n"); + + out->Write("struct CustomShaderTevStageInputColor\n"); + out->Write("{{\n"); + out->Write("\tuint input_type;\n"); + out->Write("\tfloat3 value;\n"); + out->Write("}};\n\n"); + + out->Write("struct CustomShaderTevStageInputAlpha\n"); + out->Write("{{\n"); + out->Write("\tuint input_type;\n"); + out->Write("\tfloat value;\n"); + out->Write("}};\n\n"); + + out->Write("struct CustomShaderTevStage\n"); + out->Write("{{\n"); + out->Write("\tCustomShaderTevStageInputColor[4] input_color;\n"); + out->Write("\tCustomShaderTevStageInputAlpha[4] input_alpha;\n"); + out->Write("\tuint texmap;\n"); + out->Write("\tfloat4 output_color;\n"); + out->Write("}};\n\n"); + + // Custom structure for data we pass to custom shader hooks + out->Write("struct CustomShaderData\n"); + out->Write("{{\n"); + out->Write("\tfloat3 position;\n"); + out->Write("\tfloat3 normal;\n"); + if (numtexgens == 0) + { + // Cheat so shaders compile + out->Write("\tfloat3[1] texcoord;\n"); + } + else + { + out->Write("\tfloat3[{}] texcoord;\n", numtexgens); + } + out->Write("\tuint texcoord_count;\n"); + out->Write("\tuint[8] texmap_to_texcoord_index;\n"); + out->Write("\tCustomShaderLightData[8] lights_chan0_color;\n"); + out->Write("\tCustomShaderLightData[8] lights_chan0_alpha;\n"); + out->Write("\tCustomShaderLightData[8] lights_chan1_color;\n"); + out->Write("\tCustomShaderLightData[8] lights_chan1_alpha;\n"); + out->Write("\tfloat4[2] ambient_lighting;\n"); + out->Write("\tfloat4[2] base_material;\n"); + out->Write("\tuint light_chan0_color_count;\n"); + out->Write("\tuint light_chan0_alpha_count;\n"); + out->Write("\tuint light_chan1_color_count;\n"); + out->Write("\tuint light_chan1_alpha_count;\n"); + out->Write("\tCustomShaderTevStage[16] tev_stages;\n"); + out->Write("\tuint tev_stage_count;\n"); + out->Write("\tfloat4 final_color;\n"); + out->Write("\tuint time_ms;\n"); + out->Write("}};\n\n"); +} diff --git a/Source/Core/VideoCommon/ShaderGenCommon.h b/Source/Core/VideoCommon/ShaderGenCommon.h index 7112dd4523..60e4b3cef9 100644 --- a/Source/Core/VideoCommon/ShaderGenCommon.h +++ b/Source/Core/VideoCommon/ShaderGenCommon.h @@ -7,6 +7,7 @@ #include <functional> #include <iterator> #include <string> +#include <string_view> #include <type_traits> #include <vector> @@ -327,3 +328,21 @@ static const char s_geometry_shader_uniforms[] = "\tfloat4 " I_STEREOPARAMS ";\n "\tfloat4 " I_LINEPTPARAMS ";\n" "\tint4 " I_TEXOFFSET ";\n" "\tuint vs_expand;\n"; + +constexpr std::string_view CUSTOM_PIXELSHADER_COLOR_FUNC = "customShaderColor"; + +struct CustomPixelShader +{ + std::string custom_shader; + + bool operator==(const CustomPixelShader& other) const = default; +}; + +struct CustomPixelShaderContents +{ + std::vector<CustomPixelShader> shaders; + + bool operator==(const CustomPixelShaderContents& other) const = default; +}; + +void WriteCustomShaderStructDef(ShaderCode* out, u32 numtexgens); diff --git a/Source/Core/VideoCommon/UberShaderPixel.cpp b/Source/Core/VideoCommon/UberShaderPixel.cpp index f54e42bf60..bcddde4adc 100644 --- a/Source/Core/VideoCommon/UberShaderPixel.cpp +++ b/Source/Core/VideoCommon/UberShaderPixel.cpp @@ -17,6 +17,260 @@ namespace UberShader { +namespace +{ +void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_texgen, bool per_pixel_lighting) +{ + out->Write("\tCustomShaderData custom_data;\n"); + if (per_pixel_lighting) + { + out->Write("\tcustom_data.position = WorldPos;\n"); + out->Write("\tcustom_data.normal = Normal;\n"); + } + else + { + out->Write("\tcustom_data.position = float3(0, 0, 0);\n"); + out->Write("\tcustom_data.normal = float3(0, 0, 0);\n"); + } + + if (num_texgen == 0) [[unlikely]] + { + out->Write("\tcustom_data.texcoord[0] = float3(0, 0, 0);\n"); + } + else + { + for (u32 i = 0; i < num_texgen; ++i) + { + out->Write("\tif (tex{0}.z == 0.0)\n", i); + out->Write("\t{{\n"); + out->Write("\t\tcustom_data.texcoord[{0}] = tex{0};\n", i); + out->Write("\t}}\n"); + out->Write("\telse {{\n"); + out->Write("\t\tcustom_data.texcoord[{0}] = float3(tex{0}.xy / tex{0}.z, 0);\n", i); + out->Write("\t}}\n"); + } + } + + out->Write("\tcustom_data.texcoord_count = {};\n", num_texgen); + + for (u32 i = 0; i < 8; i++) + { + // Shader compilation complains if every index isn't initialized + out->Write("\tcustom_data.texmap_to_texcoord_index[{0}] = {0};\n", i); + } + + for (u32 i = 0; i < NUM_XF_COLOR_CHANNELS; i++) + { + out->Write("\tcustom_data.base_material[{}] = vec4(0, 0, 0, 1);\n", i); + out->Write("\tcustom_data.ambient_lighting[{}] = vec4(0, 0, 0, 1);\n", i); + + // Shader compilation errors can throw if not everything is initialized + for (u32 light_count_index = 0; light_count_index < 8; light_count_index++) + { + // Color + out->Write("\tcustom_data.lights_chan{}_color[{}].direction = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].position = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].color = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].cosatt = float4(0, 0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].distatt = float4(0, 0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].attenuation_type = 0;\n", i, + light_count_index); + + // Alpha + out->Write("\tcustom_data.lights_chan{}_alpha[{}].direction = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].position = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].color = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].cosatt = float4(0, 0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].distatt = float4(0, 0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = 0;\n", i, + light_count_index); + } + + out->Write("\tcustom_data.light_chan{}_color_count = 0;\n", i); + out->Write("\tcustom_data.light_chan{}_alpha_count = 0;\n", i); + } + + if (num_texgen > 0) [[likely]] + { + out->Write("\n"); + out->Write("\tfor(uint stage = 0u; stage <= num_stages; stage++)\n"); + out->Write("\t{{\n"); + out->Write("\t\tStageState ss;\n"); + out->Write("\t\tss.order = bpmem_tevorder(stage>>1);\n"); + out->Write("\t\tif ((stage & 1u) == 1u)\n"); + out->Write("\t\t\tss.order = ss.order >> {};\n\n", + int(TwoTevStageOrders().enable_tex_odd.StartBit() - + TwoTevStageOrders().enable_tex_even.StartBit())); + out->Write("\t\tuint texmap = {};\n", + BitfieldExtract<&TwoTevStageOrders::texcoord_even>("ss.order")); + // Shader compilation is weird, shader arrays can't use indexing by variable + // to set values unless the variable is an index in a for loop. + // So instead we have to do this if check nonsense + for (u32 i = 0; i < 8; i++) + { + out->Write("\t\tif (texmap == {})\n", i); + out->Write("\t\t{{\n"); + out->Write("\t\t\tcustom_data.texmap_to_texcoord_index[{}] = selectTexCoordIndex(texmap);\n", + i); + out->Write("\t\t}}\n"); + } + out->Write("\t}}\n"); + } + + out->Write("\tuint light_count = 0;\n"); + out->Write("\tfor (uint chan = 0u; chan < {}u; chan++)\n", NUM_XF_COLOR_CHANNELS); + out->Write("\t{{\n"); + out->Write("\t\tuint colorreg = xfmem_color(chan);\n"); + out->Write("\t\tuint alphareg = xfmem_alpha(chan);\n"); + for (const auto& color_type : std::array<std::string_view, 2>{"colorreg", "alphareg"}) + { + if (color_type == "colorreg") + { + out->Write("\t\tcustom_data.base_material[0] = " I_MATERIALS "[2u] / 255.0; \n"); + out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type)); + out->Write("\t\t\tcustom_data.base_material[0] = colors_0; \n"); + } + else + { + out->Write("custom_data.base_material[1].w = " I_MATERIALS "[3u].w / 255.0; \n"); + out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type)); + out->Write("\t\t\tcustom_data.base_material[1].w = colors_1.w; \n"); + } + out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type)); + out->Write("\t\t{{\n"); + out->Write("\t\t\tuint light_mask = {} | ({} << 4u);\n", + BitfieldExtract<&LitChannel::lightMask0_3>(color_type), + BitfieldExtract<&LitChannel::lightMask4_7>(color_type)); + out->Write("\t\t\tuint attnfunc = {};\n", BitfieldExtract<&LitChannel::attnfunc>(color_type)); + out->Write("\t\t\tfor (uint light_index = 0u; light_index < 8u; light_index++)\n"); + out->Write("\t\t\t{{\n"); + out->Write("\t\t\t\tif ((light_mask & (1u << light_index)) != 0u)\n"); + out->Write("\t\t\t\t{{\n"); + // Shader compilation is weird, shader arrays can't use indexing by variable + // to set values unless the variable is an index in a for loop. + // So instead we have to do this if check nonsense + for (u32 light_count_index = 0; light_count_index < 8; light_count_index++) + { + out->Write("\t\t\t\t\tif (light_index == {})\n", light_count_index); + out->Write("\t\t\t\t\t{{\n"); + if (color_type == "colorreg") + { + for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++) + { + out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index); + out->Write("\t\t\t\t\t\t{{\n"); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].direction = " I_LIGHTS + "[light_index].dir.xyz;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].position = " I_LIGHTS + "[light_index].pos.xyz;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].cosatt = " I_LIGHTS + "[light_index].cosatt;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].distatt = " I_LIGHTS + "[light_index].distatt;\n", + channel_index, light_count_index); + out->Write( + "\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].attenuation_type = attnfunc;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].color = " I_LIGHTS + "[light_index].color.rgb / float3(255.0, 255.0, 255.0);\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_color_count += 1;\n", channel_index); + out->Write("\t\t\t\t\t\t}}\n"); + } + } + else + { + for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++) + { + out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index); + out->Write("\t\t\t\t\t\t{{\n"); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].direction = " I_LIGHTS + "[light_index].dir.xyz;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].position = " I_LIGHTS + "[light_index].pos.xyz;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].cosatt = " I_LIGHTS + "[light_index].cosatt;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].distatt = " I_LIGHTS + "[light_index].distatt;\n", + channel_index, light_count_index); + out->Write( + "\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = attnfunc;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].color = float3(" I_LIGHTS + "[light_index].color.a) / float3(255.0, 255.0, 255.0);\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_alpha_count += 1;\n", channel_index); + out->Write("\t\t\t\t\t\t}}\n"); + } + } + + out->Write("\t\t\t\t\t}}\n"); + } + out->Write("\t\t\t\t}}\n"); + out->Write("\t\t\t}}\n"); + out->Write("\t\t}}\n"); + } + out->Write("\t}}\n"); + + for (u32 i = 0; i < 16; i++) + { + // Shader compilation complains if every struct isn't initialized + + // Color Input + for (u32 j = 0; j < 4; j++) + { + out->Write("\tcustom_data.tev_stages[{}].input_color[{}].input_type = " + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n", + i, j); + out->Write("\tcustom_data.tev_stages[{}].input_color[{}].value = " + "float3(0, 0, 0);\n", + i, j); + } + + // Alpha Input + for (u32 j = 0; j < 4; j++) + { + out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].input_type = " + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n", + i, j); + out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].value = " + "float(0);\n", + i, j); + } + + // Texmap + out->Write("\tcustom_data.tev_stages[{}].texmap = 0u;\n", i); + + // Output + out->Write("\tcustom_data.tev_stages[{}].output_color = " + "float4(0, 0, 0, 0);\n", + i); + } + + // Actual data will be filled out in the tev stage code, just set the + // stage count for now + out->Write("\tcustom_data.tev_stage_count = num_stages;\n"); + + // Time + out->Write("\tcustom_data.time_ms = time_ms;\n"); +} +} // namespace PixelShaderUid GetPixelShaderUid() { PixelShaderUid out; @@ -56,7 +310,8 @@ void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& hos } ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, - const pixel_ubershader_uid_data* uid_data) + const pixel_ubershader_uid_data* uid_data, + const CustomPixelShaderContents& custom_details) { const bool per_pixel_lighting = host_config.per_pixel_lighting; const bool msaa = host_config.msaa; @@ -76,6 +331,12 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, out.Write("// {}\n", *uid_data); WriteBitfieldExtractHeader(out, api_type, host_config); WritePixelShaderCommonHeader(out, api_type, host_config, bounding_box); + WriteCustomShaderStructDef(&out, numTexgen); + for (std::size_t i = 0; i < custom_details.shaders.size(); i++) + { + const auto& shader_details = custom_details.shaders[i]; + out.Write(fmt::runtime(shader_details.custom_shader), i); + } if (per_pixel_lighting) WriteLightingFunction(out); @@ -228,6 +489,68 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, } out.Write("}}\n\n"); + + out.Write("uint selectTexCoordIndex(uint texmap)"); + out.Write("{{\n"); + + if (api_type == APIType::D3D) + { + out.Write(" switch (texmap) {{\n"); + for (u32 i = 0; i < numTexgen; i++) + { + out.Write(" case {}u:\n" + " return {};\n", + i, i); + } + out.Write(" default:\n" + " return 0;\n" + " }}\n"); + } + else + { + out.Write(" if (texmap >= {}u) {{\n", numTexgen); + out.Write(" return 0;\n" + " }}\n"); + if (numTexgen > 4) + out.Write(" if (texmap < 4u) {{\n"); + if (numTexgen > 2) + out.Write(" if (texmap < 2u) {{\n"); + if (numTexgen > 1) + out.Write(" return (texmap == 0u) ? 0 : 1;\n"); + else + out.Write(" return 0;\n"); + if (numTexgen > 2) + { + out.Write(" }} else {{\n"); // >= 2 < min(4, numTexgen) + if (numTexgen > 3) + out.Write(" return (texmap == 2u) ? 2 : 3;\n"); + else + out.Write(" return 2;\n"); + out.Write(" }}\n"); + } + if (numTexgen > 4) + { + out.Write(" }} else {{\n"); // >= 4 < min(8, numTexgen) + if (numTexgen > 6) + out.Write(" if (texmap < 6u) {{\n"); + if (numTexgen > 5) + out.Write(" return (texmap == 4u) ? 4 : 5;\n"); + else + out.Write(" return 4;\n"); + if (numTexgen > 6) + { + out.Write(" }} else {{\n"); // >= 6 < min(8, numTexgen) + if (numTexgen > 7) + out.Write(" return (texmap == 6u) ? 6 : 7;\n"); + else + out.Write(" return 6;\n"); + out.Write(" }}\n"); + } + out.Write(" }}\n"); + } + } + + out.Write("}}\n\n"); } // ===================== @@ -316,43 +639,43 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, // TEV's Special Lerp // ====================== const auto WriteTevLerp = [&out](std::string_view components) { - out.Write( - "// TEV's Linear Interpolate, plus bias, add/subtract and scale\n" - "int{0} tevLerp{0}(int{0} A, int{0} B, int{0} C, int{0} D, uint bias, bool op, " - "uint scale) {{\n" - " // Scale C from 0..255 to 0..256\n" - " C += C >> 7;\n" - "\n" - " // Add bias to D\n" - " if (bias == 1u) D += 128;\n" - " else if (bias == 2u) D -= 128;\n" - "\n" - " int{0} lerp = (A << 8) + (B - A)*C;\n" - " if (scale != 3u) {{\n" - " lerp = lerp << scale;\n" - " D = D << scale;\n" - " }}\n" - "\n" - " // TODO: Is this rounding bias still added when the scale is divide by 2? Currently we " - "do not apply it.\n" - " if (scale != 3u)\n" - " lerp = lerp + (op ? 127 : 128);\n" - "\n" - " int{0} result = lerp >> 8;\n" - "\n" - " // Add/Subtract D\n" - " if (op) // Subtract\n" - " result = D - result;\n" - " else // Add\n" - " result = D + result;\n" - "\n" - " // Most of the Scale was moved inside the lerp for improved precision\n" - " // But we still do the divide by 2 here\n" - " if (scale == 3u)\n" - " result = result >> 1;\n" - " return result;\n" - "}}\n\n", - components); + out.Write("// TEV's Linear Interpolate, plus bias, add/subtract and scale\n" + "int{0} tevLerp{0}(int{0} A, int{0} B, int{0} C, int{0} D, uint bias, bool op, " + "uint scale) {{\n" + " // Scale C from 0..255 to 0..256\n" + " C += C >> 7;\n" + "\n" + " // Add bias to D\n" + " if (bias == 1u) D += 128;\n" + " else if (bias == 2u) D -= 128;\n" + "\n" + " int{0} lerp = (A << 8) + (B - A)*C;\n" + " if (scale != 3u) {{\n" + " lerp = lerp << scale;\n" + " D = D << scale;\n" + " }}\n" + "\n" + " // TODO: Is this rounding bias still added when the scale is divide by 2? " + "Currently we " + "do not apply it.\n" + " if (scale != 3u)\n" + " lerp = lerp + (op ? 127 : 128);\n" + "\n" + " int{0} result = lerp >> 8;\n" + "\n" + " // Add/Subtract D\n" + " if (op) // Subtract\n" + " result = D - result;\n" + " else // Add\n" + " result = D + result;\n" + "\n" + " // Most of the Scale was moved inside the lerp for improved precision\n" + " // But we still do the divide by 2 here\n" + " if (scale == 3u)\n" + " result = result >> 1;\n" + " return result;\n" + "}}\n\n", + components); }; WriteTevLerp(""); // int WriteTevLerp("3"); // int3 @@ -437,6 +760,25 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, "return int3(0, 0, 0);", // ZERO }; + static constexpr Common::EnumMap<std::string_view, TevColorArg::Zero> tev_c_input_type{ + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;", + }; + static constexpr Common::EnumMap<std::string_view, TevAlphaArg::Zero> tev_a_input_table{ "return s.Reg[0].a;", // APREV, "return s.Reg[1].a;", // A0, @@ -448,6 +790,17 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, "return 0;", // ZERO }; + static constexpr Common::EnumMap<std::string_view, TevAlphaArg::Zero> tev_a_input_type{ + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;", + }; + static constexpr Common::EnumMap<std::string_view, TevOutput::Color2> tev_regs_lookup_table{ "return s.Reg[0];", "return s.Reg[1];", @@ -489,6 +842,16 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, out.Write("}}\n" "\n"); + out.Write("// Helper function for Custom Shader Input Type\n" + "uint getColorInputType(uint index) {{\n"); + WriteSwitch(out, api_type, "index", tev_c_input_type, 2, false); + out.Write("}}\n" + "\n" + "uint getAlphaInputType(uint index) {{\n"); + WriteSwitch(out, api_type, "index", tev_a_input_type, 2, false); + out.Write("}}\n" + "\n"); + // Since the fixed-point texture coodinate variables aren't global, we need to pass // them to the select function. This applies to all backends. if (numTexgen > 0) @@ -505,6 +868,17 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, out.Write("void main()\n{{\n"); out.Write(" float4 rawpos = gl_FragCoord;\n"); + out.Write(" uint num_stages = {};\n\n", + BitfieldExtract<&GenMode::numtevstages>("bpmem_genmode")); + + bool has_custom_shader_details = false; + if (std::any_of(custom_details.shaders.begin(), custom_details.shaders.end(), + [](const std::optional<CustomPixelShader>& ps) { return ps.has_value(); })) + { + WriteCustomShaderStructImpl(&out, numTexgen, per_pixel_lighting); + has_custom_shader_details = true; + } + if (use_framebuffer_fetch) { // Store off a copy of the initial framebuffer value. @@ -563,9 +937,6 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, " // o.colors_1 = float4(0.0, 0.0, 0.0, 0.0);\n"); } - out.Write(" uint num_stages = {};\n\n", - BitfieldExtract<&GenMode::numtevstages>("bpmem_genmode")); - out.Write(" // Main tev loop\n"); out.Write(" for(uint stage = 0u; stage <= num_stages; stage++)\n" @@ -618,9 +989,9 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, // indirect texture stage is enabled). If the matrix is off, the result doesn't matter; if the // indirect texture stage is disabled, the result is undefined (and produces a glitchy pattern // on hardware, different from this). - // For the undefined case, we just skip applying the indirect operation, which is close enough. - // Viewtiful Joe hits the undefined case (bug 12525). - // Wrapping and add to previous still apply in this case (and when the stage is disabled). + // For the undefined case, we just skip applying the indirect operation, which is close + // enough. Viewtiful Joe hits the undefined case (bug 12525). Wrapping and add to previous + // still apply in this case (and when the stage is disabled). out.Write(" if (bpmem_iref(bt) != 0u) {{\n"); out.Write(" int3 indcoord;\n"); LookupIndirectTexture("indcoord", "bt"); @@ -826,7 +1197,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, " alpha_B = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_b) & 255;\n" " }};\n" " int alpha_C = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_c) & 255;\n" - " int alpha_D = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_d); // 10 bits " + " int alpha_D = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_d); // 10 " + "bits " "+ sign\n" "\n", // TODO: do we need to sign extend? color_input_prefix); @@ -857,9 +1229,81 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, "\n" " // Write result to the correct input register of the next stage\n"); WriteSwitch(out, api_type, "alpha_dest", tev_a_set_table, 6, true); - out.Write(" }}\n" - " }} // Main TEV loop\n" - "\n"); + if (has_custom_shader_details) + { + for (u32 stage_index = 0; stage_index < 16; stage_index++) + { + out.Write("\tif (stage == {}u) {{\n", stage_index); + // Color input + out.Write("\t\tcustom_data.tev_stages[{}].input_color[0].value = color_A / float3(255.0, " + "255.0, 255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[0].input_type = " + "getColorInputType(color_a);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[1].value = color_B / float3(255.0, " + "255.0, 255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[1].input_type = " + "getColorInputType(color_b);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[2].value = color_C / float3(255.0, " + "255.0, 255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[2].input_type = " + "getColorInputType(color_c);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[3].value = color_D / float3(255.0, " + "255.0, 255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[3].input_type = " + "getColorInputType(color_c);\n", + stage_index); + + // Alpha input + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[0].value = alpha_A / float(255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[0].input_type = " + "getAlphaInputType(alpha_a);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[1].value = alpha_B / float(255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[1].input_type = " + "getAlphaInputType(alpha_b);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[2].value = alpha_C / float(255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[2].input_type = " + "getAlphaInputType(alpha_c);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[3].value = alpha_D / float(255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[3].input_type = " + "getAlphaInputType(alpha_d);\n", + stage_index); + + if (numTexgen != 0) + { + // Texmap + out.Write("\t\tif (texture_enabled) {{\n"); + out.Write("\t\t\tuint sampler_num = {};\n", + BitfieldExtract<&TwoTevStageOrders::texmap_even>("ss.order")); + out.Write("\t\tcustom_data.tev_stages[{}].texmap = sampler_num;\n", stage_index); + out.Write("\t\t}}\n"); + } + + // Output + out.Write("\t\tcustom_data.tev_stages[{}].output_color.rgb = color / float3(255.0, 255.0, " + "255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].output_color.a = alpha / float(255.0);\n", + stage_index); + out.Write("\t}}\n"); + } + } + out.Write(" }}\n"); + out.Write(" }} // Main TEV loop\n"); + out.Write("\n"); // Select the output color and alpha registers from the last stage. out.Write(" int4 TevResult;\n"); @@ -942,8 +1386,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, { // Instead of using discard, fetch the framebuffer's color value and use it as the output // for this fragment. - out.Write( - " #define discard_fragment {{ real_ocol0 = float4(initial_ocol0.xyz, 1.0); return; }}\n"); + out.Write(" #define discard_fragment {{ real_ocol0 = float4(initial_ocol0.xyz, 1.0); " + "return; }}\n"); } else { @@ -1109,8 +1553,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, " }}\n"); } - // Some backends require that the shader outputs be uint when writing to a uint render target for - // logic op. + // Some backends require that the shader outputs be uint when writing to a uint render target + // for logic op. if (uid_data->uint_output) { out.Write(" if (bpmem_rgba6_format)\n" @@ -1142,6 +1586,19 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, } } + for (std::size_t i = 0; i < custom_details.shaders.size(); i++) + { + const auto& shader_details = custom_details.shaders[i]; + + if (!shader_details.custom_shader.empty()) + { + out.Write("\t{{\n"); + out.Write("\t\tcustom_data.final_color = ocol0;\n"); + out.Write("\t\tocol0.xyz = {}_{}(custom_data).xyz;\n", CUSTOM_PIXELSHADER_COLOR_FUNC, i); + out.Write("\t}}\n\n"); + } + } + if (bounding_box) { out.Write(" if (bpmem_bounding_box) {{\n" @@ -1209,13 +1666,13 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, WriteSwitch(out, api_type, "blend_dst_factor", blendDstFactor, 4, true); WriteSwitch(out, api_type, "blend_dst_factor_alpha", blendDstFactorAlpha, 4, true); - out.Write( - " float4 blend_result;\n" - " if (blend_subtract)\n" - " blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb - ocol0.rgb * blend_src.rgb;\n" - " else\n" - " blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb + ocol0.rgb * " - "blend_src.rgb;\n"); + out.Write(" float4 blend_result;\n" + " if (blend_subtract)\n" + " blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb - ocol0.rgb * " + "blend_src.rgb;\n" + " else\n" + " blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb + ocol0.rgb * " + "blend_src.rgb;\n"); out.Write(" if (blend_subtract_alpha)\n" " blend_result.a = initial_ocol0.a * blend_dst.a - ocol0.a * blend_src.a;\n" diff --git a/Source/Core/VideoCommon/UberShaderPixel.h b/Source/Core/VideoCommon/UberShaderPixel.h index aa3d6c7625..80560688ca 100644 --- a/Source/Core/VideoCommon/UberShaderPixel.h +++ b/Source/Core/VideoCommon/UberShaderPixel.h @@ -29,7 +29,8 @@ using PixelShaderUid = ShaderUid<pixel_ubershader_uid_data>; PixelShaderUid GetPixelShaderUid(); ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, - const pixel_ubershader_uid_data* uid_data); + const pixel_ubershader_uid_data* uid_data, + const CustomPixelShaderContents& custom_details); void EnumeratePixelShaderUids(const std::function<void(const PixelShaderUid&)>& callback); void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& host_config, diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 0b184e1da9..564eb684dc 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -15,6 +15,7 @@ #include "Core/ConfigManager.h" #include "Core/DolphinAnalytics.h" +#include "Core/HW/SystemTimers.h" #include "Core/System.h" #include "VideoCommon/AbstractGfx.h" @@ -23,12 +24,14 @@ #include "VideoCommon/DataReader.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/GeometryShaderManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/NativeVertexFormat.h" #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PerfQueryBase.h" +#include "VideoCommon/PixelShaderGen.h" #include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TextureCacheBase.h" @@ -105,7 +108,10 @@ VertexManagerBase::~VertexManagerBase() = default; bool VertexManagerBase::Initialize() { m_frame_end_event = AfterFrameEvent::Register([this] { OnEndFrame(); }, "VertexManagerBase"); + m_after_present_event = AfterPresentEvent::Register( + [this](PresentInfo& pi) { m_ticks_elapsed = pi.emulated_timestamp; }, "VertexManagerBase"); m_index_generator.Init(); + m_custom_shader_cache = std::make_unique<CustomShaderCache>(); m_cpu_cull.Init(); return true; } @@ -523,10 +529,18 @@ void VertexManagerBase::Flush() auto& geometry_shader_manager = system.GetGeometryShaderManager(); auto& vertex_shader_manager = system.GetVertexShaderManager(); + if (g_ActiveConfig.bGraphicMods) + { + const double seconds_elapsed = + static_cast<double>(m_ticks_elapsed) / SystemTimers::GetTicksPerSecond(); + pixel_shader_manager.constants.time_ms = seconds_elapsed * 1000; + } + CalculateBinormals(VertexLoaderManager::GetCurrentVertexFormat()); // Calculate ZSlope for zfreeze const auto used_textures = UsedTextures(); std::vector<std::string> texture_names; + std::vector<u32> texture_units; if (!m_cull_all) { if (!g_ActiveConfig.bGraphicMods) @@ -543,7 +557,12 @@ void VertexManagerBase::Flush() const auto cache_entry = g_texture_cache->Load(TextureInfo::FromStage(i)); if (cache_entry) { - texture_names.push_back(cache_entry->texture_info_name); + if (std::find(texture_names.begin(), texture_names.end(), + cache_entry->texture_info_name) == texture_names.end()) + { + texture_names.push_back(cache_entry->texture_info_name); + texture_units.push_back(i); + } } } } @@ -562,13 +581,24 @@ void VertexManagerBase::Flush() if (!m_cull_all) { - for (const auto& texture_name : texture_names) + CustomPixelShaderContents custom_pixel_shader_contents; + std::optional<CustomPixelShader> custom_pixel_shader; + std::vector<std::string> custom_pixel_texture_names; + for (int i = 0; i < texture_names.size(); i++) { + const std::string& texture_name = texture_names[i]; + const u32 texture_unit = texture_units[i]; bool skip = false; - GraphicsModActionData::DrawStarted draw_started{&skip}; + GraphicsModActionData::DrawStarted draw_started{texture_unit, &skip, &custom_pixel_shader}; for (const auto& action : g_graphics_mod_manager->GetDrawStartedActions(texture_name)) { action->OnDrawStarted(&draw_started); + if (custom_pixel_shader) + { + custom_pixel_shader_contents.shaders.push_back(*custom_pixel_shader); + custom_pixel_texture_names.push_back(texture_name); + } + custom_pixel_shader = std::nullopt; } if (skip == true) return; @@ -610,7 +640,65 @@ void VertexManagerBase::Flush() UpdatePipelineObject(); if (m_current_pipeline_object) { - g_gfx->SetPipeline(m_current_pipeline_object); + const AbstractPipeline* current_pipeline = m_current_pipeline_object; + if (!custom_pixel_shader_contents.shaders.empty()) + { + CustomShaderInstance custom_shaders; + custom_shaders.pixel_contents = std::move(custom_pixel_shader_contents); + + switch (g_ActiveConfig.iShaderCompilationMode) + { + case ShaderCompilationMode::Synchronous: + case ShaderCompilationMode::AsynchronousSkipRendering: + { + if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_pipeline_config, custom_shaders, m_current_pipeline_object->m_config)) + { + current_pipeline = *pipeline; + } + } + break; + case ShaderCompilationMode::SynchronousUberShaders: + { + // D3D has issues compiling large custom ubershaders + // use specialized shaders instead + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_pipeline_config, custom_shaders, m_current_pipeline_object->m_config)) + { + current_pipeline = *pipeline; + } + } + else + { + if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_uber_pipeline_config, custom_shaders, + m_current_pipeline_object->m_config)) + { + current_pipeline = *pipeline; + } + } + } + break; + case ShaderCompilationMode::AsynchronousUberShaders: + { + if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_pipeline_config, custom_shaders, m_current_pipeline_object->m_config)) + { + current_pipeline = *pipeline; + } + else if (auto uber_pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_uber_pipeline_config, custom_shaders, + m_current_pipeline_object->m_config)) + { + current_pipeline = *uber_pipeline; + } + } + break; + }; + } + g_gfx->SetPipeline(current_pipeline); if (PerfQueryBase::ShouldEmulate()) g_perf_query->EnableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP); @@ -1006,3 +1094,9 @@ void VertexManagerBase::OnEndFrame() // state changes the specialized shader will not take over. InvalidatePipelineObject(); } + +void VertexManagerBase::NotifyCustomShaderCacheOfHostChange(const ShaderHostConfig& host_config) +{ + m_custom_shader_cache->SetHostConfig(host_config); + m_custom_shader_cache->Reload(); +} diff --git a/Source/Core/VideoCommon/VertexManagerBase.h b/Source/Core/VideoCommon/VertexManagerBase.h index 775d8e2787..ebb857ff21 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.h +++ b/Source/Core/VideoCommon/VertexManagerBase.h @@ -15,6 +15,7 @@ #include "VideoCommon/ShaderCache.h" #include "VideoCommon/VideoEvents.h" +class CustomShaderCache; class DataReader; class NativeVertexFormat; class PointerWrap; @@ -128,6 +129,7 @@ public: m_current_pipeline_object = nullptr; m_pipeline_config_changed = true; } + void NotifyCustomShaderCacheOfHostChange(const ShaderHostConfig& host_config); // Utility pipeline drawing (e.g. EFB copies, post-processing, UI). virtual void UploadUtilityUniforms(const void* uniforms, u32 uniforms_size); @@ -230,7 +232,11 @@ private: std::vector<u32> m_scheduled_command_buffer_kicks; bool m_allow_background_execution = true; + std::unique_ptr<CustomShaderCache> m_custom_shader_cache; + u64 m_ticks_elapsed; + Common::EventHook m_frame_end_event; + Common::EventHook m_after_present_event; }; extern std::unique_ptr<VertexManagerBase> g_vertex_manager; diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 83dc5eb53e..02db6db9f7 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -353,6 +353,7 @@ void CheckForConfigChanges() { OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL); g_vertex_manager->InvalidatePipelineObject(); + g_vertex_manager->NotifyCustomShaderCacheOfHostChange(new_host_config); g_shader_cache->SetHostConfig(new_host_config); g_shader_cache->Reload(); g_framebuffer_manager->RecompileShaders(); |
