diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 1a398f3bf5..4ddf550edf 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -637,6 +637,7 @@ + @@ -1252,6 +1253,7 @@ + diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h index eb78e9f770..196372fab8 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h @@ -11,6 +11,7 @@ namespace VideoCommon { class CustomTextureData; +struct PixelShaderData; // This class provides functionality to load // specific data (like textures). Where this data @@ -45,5 +46,8 @@ public: // Loads a texture as a game texture, providing additional checks like confirming // each mip level size is correct and that the format is consistent across the data LoadInfo LoadGameTexture(const AssetID& asset_id, CustomTextureData* data); + + // Loads a pixel shader + virtual LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) = 0; }; } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp index bb404064d2..ce76a368d2 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp @@ -90,4 +90,11 @@ CustomAssetLoader::LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id, { return LoadOrCreateAsset(asset_id, m_game_textures, std::move(library)); } + +std::shared_ptr +CustomAssetLoader::LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library) +{ + return LoadOrCreateAsset(asset_id, m_pixel_shaders, std::move(library)); +} } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h index 6e4596b8b2..cca83af960 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h @@ -12,6 +12,7 @@ #include "Common/Flag.h" #include "Common/WorkQueueThread.h" #include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/Assets/ShaderAsset.h" #include "VideoCommon/Assets/TextureAsset.h" namespace VideoCommon @@ -42,6 +43,9 @@ public: std::shared_ptr LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id, std::shared_ptr library); + std::shared_ptr LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library); + private: // TODO C++20: use a 'derived_from' concept against 'CustomAsset' when available template @@ -74,6 +78,7 @@ private: std::map> m_textures; std::map> m_game_textures; + std::map> m_pixel_shaders; std::thread m_asset_monitor_thread; Common::Flag m_asset_monitor_thread_shutdown; diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp index 9c126bf9e1..c234b4088e 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp @@ -10,6 +10,7 @@ #include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/Assets/ShaderAsset.h" namespace VideoCommon { @@ -49,6 +50,100 @@ DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) con return {}; } +CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadPixelShader(const AssetID& asset_id, + PixelShaderData* data) +{ + const auto asset_map = GetAssetMapForID(asset_id); + + // Asset map for a pixel shader is the shader and some metadata + if (asset_map.size() != 2) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have two files mapped!", asset_id); + return {}; + } + + const auto metadata = asset_map.find("metadata"); + const auto shader = asset_map.find("shader"); + if (metadata == asset_map.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a metadata entry mapped!", asset_id); + return {}; + } + + if (shader == asset_map.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a shader entry mapped!", asset_id); + return {}; + } + + std::size_t metadata_size; + { + std::error_code ec; + metadata_size = std::filesystem::file_size(metadata->second, ec); + if (ec) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - failed to get shader metadata file size with error '{}'!", + asset_id, ec); + return {}; + } + } + std::size_t shader_size; + { + std::error_code ec; + shader_size = std::filesystem::file_size(shader->second, ec); + if (ec) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - failed to get shader source file size with error '{}'!", + asset_id, ec); + return {}; + } + } + const auto approx_mem_size = metadata_size + shader_size; + + if (!File::ReadFileToString(shader->second.string(), data->m_shader_source)) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the shader file '{}',", asset_id, + shader->second.string()); + return {}; + } + + std::string json_data; + if (!File::ReadFileToString(metadata->second.string(), json_data)) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the json file '{}',", asset_id, + metadata->second.string()); + return {}; + } + + picojson::value root; + const auto error = picojson::parse(root, json_data); + + if (!error.empty()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - failed to load the json file '{}', due to parse error: {}", + asset_id, metadata->second.string(), error); + return {}; + } + if (!root.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Asset '{}' error - failed to load the json file '{}', due to root not being an object!", + asset_id, metadata->second.string()); + return {}; + } + + const auto& root_obj = root.get(); + + if (!PixelShaderData::FromJson(asset_id, root_obj, data)) + return {}; + + return LoadInfo{approx_mem_size, GetLastAssetWriteTime(asset_id)}; +} + CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id, CustomTextureData* data) { diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h index 4f02d107cf..9a842c6988 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h @@ -22,6 +22,7 @@ public: using AssetMap = std::map; LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override; + LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) override; // Gets the latest time from amongst all the files in the asset map TimeType GetLastAssetWriteTime(const AssetID& asset_id) const override; diff --git a/Source/Core/VideoCommon/Assets/ShaderAsset.cpp b/Source/Core/VideoCommon/Assets/ShaderAsset.cpp new file mode 100644 index 0000000000..d09e572bab --- /dev/null +++ b/Source/Core/VideoCommon/Assets/ShaderAsset.cpp @@ -0,0 +1,147 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Assets/ShaderAsset.h" + +#include "Common/Logging/Log.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" + +namespace VideoCommon +{ +bool ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + const picojson::array& properties_data, + std::map* shader_properties) +{ + if (!shader_properties) [[unlikely]] + return false; + + for (const auto& property_data : properties_data) + { + VideoCommon::ShaderProperty property; + if (!property_data.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property is not the right json type", + asset_id); + return false; + } + const auto& property_data_obj = property_data.get(); + + const auto type_iter = property_data_obj.find("type"); + if (type_iter == property_data_obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property entry 'type' not found", + asset_id); + return false; + } + if (!type_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'type' is not " + "the right json type", + asset_id); + return false; + } + std::string type = type_iter->second.to_str(); + std::transform(type.begin(), type.end(), type.begin(), + [](unsigned char c) { return std::tolower(c); }); + + if (type == "sampler2d") + { + property.m_type = ShaderProperty::Type::Type_Sampler2D; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'description' is " + "an invalid option", + asset_id); + return false; + } + + const auto description_iter = property_data_obj.find("description"); + if (description_iter == property_data_obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'description' not found", + asset_id); + return false; + } + if (!description_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'description' is not " + "the right json type", + asset_id); + return false; + } + property.m_description = description_iter->second.to_str(); + + const auto code_name_iter = property_data_obj.find("code_name"); + if (code_name_iter == property_data_obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property entry 'code_name' not found", + asset_id); + return false; + } + if (!code_name_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'code_name' is not " + "the right json type", + asset_id); + return false; + } + shader_properties->try_emplace(code_name_iter->second.to_str(), std::move(property)); + } + + return true; +} + +bool PixelShaderData::FromJson(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + const picojson::object& json, PixelShaderData* data) +{ + const auto properties_iter = json.find("properties"); + if (properties_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'properties' not found", asset_id); + return false; + } + if (!properties_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'properties' is not the right json type", + asset_id); + return false; + } + const auto& properties_array = properties_iter->second.get(); + if (!ParseShaderProperties(asset_id, properties_array, &data->m_properties)) + return false; + + for (const auto& [name, property] : data->m_properties) + { + if (data->m_shader_source.find(name) == std::string::npos) + { + ERROR_LOG_FMT( + VIDEO, + "Asset '{}' failed to parse json, the code name '{}' defined in the metadata was not " + "found in the shader source", + asset_id, name); + return false; + } + } + + return true; +} +CustomAssetLibrary::LoadInfo PixelShaderAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) +{ + auto potential_data = std::make_shared(); + const auto loaded_info = m_owning_library->LoadPixelShader(asset_id, potential_data.get()); + if (loaded_info.m_bytes_loaded == 0) + return {}; + { + std::lock_guard lk(m_data_lock); + m_loaded = true; + m_data = std::move(potential_data); + } + return loaded_info; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/ShaderAsset.h b/Source/Core/VideoCommon/Assets/ShaderAsset.h new file mode 100644 index 0000000000..98712f0d78 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/ShaderAsset.h @@ -0,0 +1,47 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "VideoCommon/Assets/CustomAsset.h" + +namespace VideoCommon +{ +struct ShaderProperty +{ + enum class Type + { + Type_Undefined, + Type_Sampler2D, + Type_Max = Type_Sampler2D + }; + Type m_type; + std::string m_description; +}; +struct PixelShaderData +{ + static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, + PixelShaderData* data); + + // These shader properties describe the input that the + // shader expects to expose. The key is text + // expected to be in the shader code and the propery + // describes various details about the input + std::map m_properties; + std::string m_shader_source; +}; + +class PixelShaderAsset final : public CustomLoadableAsset +{ +public: + using CustomLoadableAsset::CustomLoadableAsset; + +private: + CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index acb011cbb1..6f12cb80ba 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -18,6 +18,8 @@ add_library(videocommon Assets/CustomTextureData.h Assets/DirectFilesystemAssetLibrary.cpp Assets/DirectFilesystemAssetLibrary.h + Assets/ShaderAsset.cpp + Assets/ShaderAsset.h Assets/TextureAsset.cpp Assets/TextureAsset.h AsyncRequests.cpp