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