diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 4ddf550edf..1b44f15ec4 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -637,6 +637,7 @@ + @@ -1253,6 +1254,7 @@ + diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h index 196372fab8..32e8020ae0 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h @@ -11,6 +11,7 @@ namespace VideoCommon { class CustomTextureData; +struct MaterialData; struct PixelShaderData; // This class provides functionality to load @@ -49,5 +50,8 @@ public: // Loads a pixel shader virtual LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) = 0; + + // Loads a material + virtual LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) = 0; }; } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp index ce76a368d2..f579a5eb4b 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp @@ -97,4 +97,11 @@ CustomAssetLoader::LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id, { return LoadOrCreateAsset(asset_id, m_pixel_shaders, std::move(library)); } + +std::shared_ptr +CustomAssetLoader::LoadMaterial(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library) +{ + return LoadOrCreateAsset(asset_id, m_materials, std::move(library)); +} } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h index cca83af960..fe86a40835 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/MaterialAsset.h" #include "VideoCommon/Assets/ShaderAsset.h" #include "VideoCommon/Assets/TextureAsset.h" @@ -46,6 +47,9 @@ public: std::shared_ptr LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id, std::shared_ptr library); + std::shared_ptr LoadMaterial(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library); + private: // TODO C++20: use a 'derived_from' concept against 'CustomAsset' when available template @@ -79,6 +83,7 @@ private: std::map> m_textures; std::map> m_game_textures; std::map> m_pixel_shaders; + std::map> m_materials; 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 c234b4088e..cc21549e54 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/MaterialAsset.h" #include "VideoCommon/Assets/ShaderAsset.h" namespace VideoCommon @@ -144,6 +145,61 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadPixelShader(const return LoadInfo{approx_mem_size, GetLastAssetWriteTime(asset_id)}; } +CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const AssetID& asset_id, + MaterialData* data) +{ + const auto asset_map = GetAssetMapForID(asset_id); + + // Material is expected to have one asset mapped + if (asset_map.empty() || asset_map.size() > 1) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - material expected to have one file mapped!", asset_id); + return {}; + } + const auto& asset_path = asset_map.begin()->second; + + std::string json_data; + if (!File::ReadFileToString(asset_path.string(), json_data)) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - material failed to load the json file '{}',", + asset_id, asset_path.string()); + return {}; + } + + picojson::value root; + const auto error = picojson::parse(root, json_data); + + if (!error.empty()) + { + ERROR_LOG_FMT( + VIDEO, + "Asset '{}' error - material failed to load the json file '{}', due to parse error: {}", + asset_id, asset_path.string(), error); + return {}; + } + if (!root.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - material failed to load the json file '{}', due to root not " + "being an object!", + asset_id, asset_path.string()); + return {}; + } + + const auto& root_obj = root.get(); + + if (!MaterialData::FromJson(asset_id, root_obj, data)) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - material failed to load the json file '{}', as material " + "json could not be parsed!", + asset_id, asset_path.string()); + return {}; + } + + return LoadInfo{json_data.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 9a842c6988..eeedb42d68 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h @@ -23,6 +23,7 @@ public: LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override; LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) override; + LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* 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/MaterialAsset.cpp b/Source/Core/VideoCommon/Assets/MaterialAsset.cpp new file mode 100644 index 0000000000..c5e5718492 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/MaterialAsset.cpp @@ -0,0 +1,166 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Assets/MaterialAsset.h" + +#include + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" + +namespace VideoCommon +{ +namespace +{ +bool ParsePropertyValue(const CustomAssetLibrary::AssetID& asset_id, MaterialProperty::Type type, + const picojson::value& json_value, + std::optional* value) +{ + switch (type) + { + case MaterialProperty::Type::Type_TextureAsset: + { + if (json_value.is()) + { + *value = json_value.to_str(); + return true; + } + } + break; + }; + + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not valid for type '{}'", + asset_id, type); + return false; +} + +bool ParseMaterialProperties(const CustomAssetLibrary::AssetID& asset_id, + const picojson::array& values_data, + std::vector* material_property) +{ + for (const auto& value_data : values_data) + { + VideoCommon::MaterialProperty property; + if (!value_data.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not the right json type", + asset_id); + return false; + } + const auto& value_data_obj = value_data.get(); + + const auto type_iter = value_data_obj.find("type"); + if (type_iter == value_data_obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value entry 'type' not found", + asset_id); + return false; + } + if (!type_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse the json, value entry 'type' is not " + "the right json type", + asset_id); + return false; + } + std::string type = type_iter->second.to_str(); + Common::ToLower(&type); + if (type == "texture_asset") + { + property.m_type = MaterialProperty::Type::Type_TextureAsset; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse the json, value entry 'type' is " + "an invalid option", + asset_id); + return false; + } + + const auto code_name_iter = value_data_obj.find("code_name"); + if (code_name_iter == value_data_obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse the json, value entry " + "'code_name' not found", + asset_id); + return false; + } + if (!code_name_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse the json, value entry 'code_name' is not " + "the right json type", + asset_id); + return false; + } + property.m_code_name = code_name_iter->second.to_str(); + + const auto value_iter = value_data_obj.find("value"); + if (value_iter != value_data_obj.end()) + { + if (!ParsePropertyValue(asset_id, property.m_type, value_iter->second, &property.m_value)) + return false; + } + + material_property->push_back(std::move(property)); + } + + return true; +} +} // namespace +bool MaterialData::FromJson(const CustomAssetLibrary::AssetID& asset_id, + const picojson::object& json, MaterialData* data) +{ + const auto values_iter = json.find("values"); + if (values_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'values' not found", asset_id); + return false; + } + if (!values_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'values' is not the right json type", + asset_id); + return false; + } + const auto& values_array = values_iter->second.get(); + + if (!ParseMaterialProperties(asset_id, values_array, &data->properties)) + return false; + + const auto shader_asset_iter = json.find("shader_asset"); + if (shader_asset_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'shader_asset' not found", asset_id); + return false; + } + if (!shader_asset_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, 'shader_asset' is not the right json type", + asset_id); + return false; + } + data->shader_asset = shader_asset_iter->second.to_str(); + + return true; +} + +CustomAssetLibrary::LoadInfo MaterialAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) +{ + auto potential_data = std::make_shared(); + const auto loaded_info = m_owning_library->LoadMaterial(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/MaterialAsset.h b/Source/Core/VideoCommon/Assets/MaterialAsset.h new file mode 100644 index 0000000000..498d9dae0f --- /dev/null +++ b/Source/Core/VideoCommon/Assets/MaterialAsset.h @@ -0,0 +1,61 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" +#include "Common/EnumFormatter.h" +#include "VideoCommon/Assets/CustomAsset.h" + +namespace VideoCommon +{ +struct MaterialProperty +{ + using Value = std::variant; + enum class Type + { + Type_Undefined, + Type_TextureAsset, + Type_Max = Type_TextureAsset + }; + std::string m_code_name; + Type m_type = Type::Type_Undefined; + std::optional m_value; +}; + +struct MaterialData +{ + static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, + MaterialData* data); + std::string shader_asset; + std::vector properties; +}; + +// Much like Unity and Unreal materials, a Dolphin material does very little on its own +// Its sole purpose is to provide data (through properties) that are used in conjunction +// with a shader asset that is provided by name. It is up to user of this asset to +// use the two together to create the relevant runtime data +class MaterialAsset final : public CustomLoadableAsset +{ +public: + using CustomLoadableAsset::CustomLoadableAsset; + +private: + CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; +}; + +} // namespace VideoCommon + +template <> +struct fmt::formatter + : EnumFormatter +{ + constexpr formatter() : EnumFormatter({"Undefined", "Texture"}) {} +}; diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 6f12cb80ba..ba3b67faba 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/MaterialAsset.cpp + Assets/MaterialAsset.h Assets/ShaderAsset.cpp Assets/ShaderAsset.h Assets/TextureAsset.cpp