diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index 085405be47..062d7f979a 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -680,6 +680,7 @@
+
@@ -1340,6 +1341,7 @@
+
diff --git a/Source/Core/VideoCommon/Assets/MaterialAsset.cpp b/Source/Core/VideoCommon/Assets/MaterialAsset.cpp
index f69c884f35..5f7b158334 100644
--- a/Source/Core/VideoCommon/Assets/MaterialAsset.cpp
+++ b/Source/Core/VideoCommon/Assets/MaterialAsset.cpp
@@ -12,7 +12,6 @@
#include "Common/StringUtil.h"
#include "Common/VariantUtil.h"
#include "VideoCommon/Assets/CustomAssetLibrary.h"
-#include "VideoCommon/ShaderGenCommon.h"
namespace VideoCommon
{
@@ -25,7 +24,7 @@ constexpr std::size_t MemorySize = sizeof(float) * 4;
template
bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::value& json_value,
- std::string_view code_name, MaterialProperty::Value* value)
+ MaterialProperty::Value* value)
{
static_assert(ElementCount <= 4, "Numeric data expected to be four elements or less");
if constexpr (ElementCount == 1)
@@ -33,9 +32,9 @@ bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::v
if (!json_value.is())
{
ERROR_LOG_FMT(VIDEO,
- "Asset id '{}' material has attribute '{}' where "
+ "Asset id '{}' material has attribute where "
"a double was expected but not provided.",
- asset_id, code_name);
+ asset_id);
return false;
}
@@ -46,9 +45,9 @@ bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::v
if (!json_value.is())
{
ERROR_LOG_FMT(VIDEO,
- "Asset id '{}' material has attribute '{}' where "
+ "Asset id '{}' material has attribute where "
"an array was expected but not provided.",
- asset_id, code_name);
+ asset_id);
return false;
}
@@ -57,18 +56,18 @@ bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::v
if (json_data.size() != ElementCount)
{
ERROR_LOG_FMT(VIDEO,
- "Asset id '{}' material has attribute '{}' with incorrect number "
+ "Asset id '{}' material has attribute with incorrect number "
"of elements, expected {}",
- asset_id, code_name, ElementCount);
+ asset_id, ElementCount);
return false;
}
if (!std::ranges::all_of(json_data, &picojson::value::is))
{
ERROR_LOG_FMT(VIDEO,
- "Asset id '{}' material has attribute '{}' where "
+ "Asset id '{}' material has attribute where "
"all elements are not of type double.",
- asset_id, code_name);
+ asset_id);
return false;
}
@@ -83,40 +82,40 @@ bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::v
return true;
}
bool ParsePropertyValue(const CustomAssetLibrary::AssetID& asset_id,
- const picojson::value& json_value, std::string_view code_name,
- std::string_view type, MaterialProperty::Value* value)
+ const picojson::value& json_value, std::string_view type,
+ MaterialProperty::Value* value)
{
if (type == "int")
{
- return ParseNumeric(asset_id, json_value, code_name, value);
+ return ParseNumeric(asset_id, json_value, value);
}
else if (type == "int2")
{
- return ParseNumeric(asset_id, json_value, code_name, value);
+ return ParseNumeric(asset_id, json_value, value);
}
else if (type == "int3")
{
- return ParseNumeric(asset_id, json_value, code_name, value);
+ return ParseNumeric(asset_id, json_value, value);
}
else if (type == "int4")
{
- return ParseNumeric(asset_id, json_value, code_name, value);
+ return ParseNumeric(asset_id, json_value, value);
}
else if (type == "float")
{
- return ParseNumeric(asset_id, json_value, code_name, value);
+ return ParseNumeric(asset_id, json_value, value);
}
else if (type == "float2")
{
- return ParseNumeric(asset_id, json_value, code_name, value);
+ return ParseNumeric(asset_id, json_value, value);
}
else if (type == "float3")
{
- return ParseNumeric(asset_id, json_value, code_name, value);
+ return ParseNumeric(asset_id, json_value, value);
}
else if (type == "float4")
{
- return ParseNumeric(asset_id, json_value, code_name, value);
+ return ParseNumeric(asset_id, json_value, value);
}
else if (type == "bool")
{
@@ -126,14 +125,6 @@ bool ParsePropertyValue(const CustomAssetLibrary::AssetID& asset_id,
return true;
}
}
- else if (type == "texture_asset")
- {
- if (json_value.is())
- {
- *value = json_value.get();
- return true;
- }
- }
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not valid for type '{}'",
asset_id, type);
@@ -173,30 +164,10 @@ bool ParseMaterialProperties(const CustomAssetLibrary::AssetID& asset_id,
std::string type = type_iter->second.to_str();
Common::ToLower(&type);
- 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, value_iter->second, property.m_code_name, type,
- &property.m_value))
+ if (!ParsePropertyValue(asset_id, value_iter->second, type, &property.m_value))
{
return false;
}
@@ -218,7 +189,6 @@ void MaterialProperty::WriteToMemory(u8*& buffer, const MaterialProperty& proper
};
std::visit(
overloaded{
- [&](const CustomAssetLibrary::AssetID&) {},
[&](s32 value) { write_memory(&value, sizeof(s32)); },
[&](const std::array& value) { write_memory(value.data(), sizeof(s32) * 2); },
[&](const std::array& value) { write_memory(value.data(), sizeof(s32) * 3); },
@@ -234,8 +204,7 @@ void MaterialProperty::WriteToMemory(u8*& buffer, const MaterialProperty& proper
std::size_t MaterialProperty::GetMemorySize(const MaterialProperty& property)
{
std::size_t result = 0;
- std::visit(overloaded{[&](const CustomAssetLibrary::AssetID&) {},
- [&](s32) { result = MemorySize; },
+ std::visit(overloaded{[&](s32 value) { result = MemorySize; },
[&](const std::array&) { result = MemorySize; },
[&](const std::array&) { result = MemorySize; },
[&](const std::array&) { result = MemorySize; },
@@ -249,71 +218,128 @@ std::size_t MaterialProperty::GetMemorySize(const MaterialProperty& property)
return result;
}
-void MaterialProperty::WriteAsShaderCode(ShaderCode& shader_source,
- const MaterialProperty& property)
-{
- const auto write_shader = [&](std::string_view type, u32 element_count) {
- if (element_count == 1)
- {
- shader_source.Write("{} {};\n", type, property.m_code_name);
- }
- else
- {
- shader_source.Write("{}{} {};\n", type, element_count, property.m_code_name);
- }
-
- for (std::size_t i = element_count; i < 4; i++)
- {
- shader_source.Write("{} {}_padding_{};\n", type, property.m_code_name, i + 1);
- }
- };
- std::visit(overloaded{[&](const CustomAssetLibrary::AssetID&) {},
- [&](s32 value) { write_shader("int", 1); },
- [&](const std::array& value) { write_shader("int", 2); },
- [&](const std::array& value) { write_shader("int", 3); },
- [&](const std::array& value) { write_shader("int", 4); },
- [&](float value) { write_shader("float", 1); },
- [&](const std::array& value) { write_shader("float", 2); },
- [&](const std::array& value) { write_shader("float", 3); },
- [&](const std::array& value) { write_shader("float", 4); },
- [&](bool value) { write_shader("bool", 1); }},
- property.m_value);
-}
-
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);
+ const auto parse_properties = [&](const char* name,
+ std::vector* properties) -> bool {
+ const auto properties_iter = json.find(name);
+ if (properties_iter == json.end())
+ {
+ ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' not found", asset_id, name);
+ return false;
+ }
+ if (!properties_iter->second.is())
+ {
+ ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' is not the right json type",
+ asset_id, name);
+ return false;
+ }
+ const auto& properties_array = properties_iter->second.get();
+
+ if (!ParseMaterialProperties(asset_id, properties_array, properties))
+ return false;
+ return true;
+ };
+
+ if (!parse_properties("properties", &data->properties))
return false;
- }
- if (!values_iter->second.is())
+
+ if (const auto shader_asset = ReadStringFromJson(json, "shader_asset"))
{
- ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'values' is not the right json type",
+ data->shader_asset = *shader_asset;
+ }
+ else
+ {
+ ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'shader_asset' not found or wrong 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())
+ if (const auto next_material_asset = ReadStringFromJson(json, "next_material_asset"))
{
- ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'shader_asset' not found", asset_id);
- return false;
+ data->next_material_asset = *next_material_asset;
}
- if (!shader_asset_iter->second.is())
+ else
{
ERROR_LOG_FMT(VIDEO,
- "Asset '{}' failed to parse json, 'shader_asset' is not the right json type",
+ "Asset '{}' failed to parse json, 'next_material_asset' not found or wrong type",
asset_id);
return false;
}
- data->shader_asset = shader_asset_iter->second.to_str();
+
+ if (const auto cull_mode = ReadNumericFromJson(json, "cull_mode"))
+ {
+ data->cull_mode = *cull_mode;
+ }
+
+ if (const auto depth_state = json.find("depth_state");
+ depth_state != json.end() && depth_state->second.is())
+ {
+ auto& json_obj = depth_state->second.get();
+ DepthState state;
+ state.test_enable = ReadNumericFromJson(json_obj, "test_enable").value_or(0);
+ state.update_enable = ReadNumericFromJson(json_obj, "update_enable").value_or(0);
+ state.func = ReadNumericFromJson(json_obj, "func").value_or(CompareMode::Never);
+ data->depth_state = state;
+ }
+
+ if (const auto blending_state = json.find("blending_state");
+ blending_state != json.end() && blending_state->second.is())
+ {
+ auto& json_obj = blending_state->second.get();
+ BlendingState state;
+ state.blend_enable = ReadNumericFromJson(json_obj, "blend_enable").value_or(0);
+ state.logic_op_enable = ReadNumericFromJson(json_obj, "logic_op_enable").value_or(0);
+ state.color_update = ReadNumericFromJson(json_obj, "color_update").value_or(0);
+ state.alpha_update = ReadNumericFromJson(json_obj, "alpha_update").value_or(0);
+ state.subtract = ReadNumericFromJson(json_obj, "subtract").value_or(0);
+ state.subtract_alpha = ReadNumericFromJson(json_obj, "subtract_alpha").value_or(0);
+ state.use_dual_src = ReadNumericFromJson(json_obj, "use_dual_src").value_or(0);
+ state.dst_factor =
+ ReadNumericFromJson(json_obj, "dst_factor").value_or(DstBlendFactor::Zero);
+ state.src_factor =
+ ReadNumericFromJson(json_obj, "src_factor").value_or(SrcBlendFactor::Zero);
+ state.dst_factor_alpha = ReadNumericFromJson(json_obj, "dst_factor_alpha")
+ .value_or(DstBlendFactor::Zero);
+ state.src_factor_alpha = ReadNumericFromJson(json_obj, "src_factor_alpha")
+ .value_or(SrcBlendFactor::Zero);
+ state.logic_mode =
+ ReadNumericFromJson(json_obj, "logic_mode").value_or(LogicOp::Clear);
+ data->blending_state = state;
+ }
+
+ const auto textures_iter = json.find("textures");
+ if (textures_iter == json.end())
+ {
+ ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'textures' not found", asset_id);
+ return false;
+ }
+ if (!textures_iter->second.is())
+ {
+ ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'textures' is not the right json type",
+ asset_id);
+ return false;
+ }
+ const auto& textures_array = textures_iter->second.get();
+ if (!std::ranges::all_of(textures_array, [](const picojson::value& json_data) {
+ return json_data.is();
+ }))
+ {
+ ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'textures' must contain objects",
+ asset_id);
+ return false;
+ }
+
+ for (const auto& texture_json : textures_array)
+ {
+ auto& texture_json_obj = texture_json.get();
+
+ TextureSamplerValue sampler_value;
+ if (!TextureSamplerValue::FromJson(texture_json_obj, &sampler_value))
+ return false;
+ data->textures.push_back(std::move(sampler_value));
+ }
return true;
}
@@ -325,17 +351,10 @@ void MaterialData::ToJson(picojson::object* obj, const MaterialData& data)
auto& json_obj = *obj;
- picojson::array json_properties;
- for (const auto& property : data.properties)
- {
+ const auto add_property = [](picojson::array* json_properties, const MaterialProperty& property) {
picojson::object json_property;
- json_property["code_name"] = picojson::value{property.m_code_name};
- std::visit(overloaded{[&](const CustomAssetLibrary::AssetID& value) {
- json_property["type"] = picojson::value{"texture_asset"};
- json_property["value"] = picojson::value{value};
- },
- [&](s32 value) {
+ std::visit(overloaded{[&](s32 value) {
json_property["type"] = picojson::value{"int"};
json_property["value"] = picojson::value{static_cast(value)};
},
@@ -373,11 +392,73 @@ void MaterialData::ToJson(picojson::object* obj, const MaterialData& data)
}},
property.m_value);
- json_properties.emplace_back(std::move(json_property));
+ json_properties->emplace_back(std::move(json_property));
+ };
+
+ picojson::array json_properties;
+ for (const auto& property : data.properties)
+ {
+ add_property(&json_properties, property);
+ }
+ json_obj.emplace("properties", std::move(json_properties));
+
+ json_obj.emplace("shader_asset", data.shader_asset);
+ json_obj.emplace("next_material_asset", data.next_material_asset);
+
+ if (data.cull_mode)
+ {
+ json_obj.emplace("cull_mode", static_cast(*data.cull_mode));
}
- json_obj["values"] = picojson::value{std::move(json_properties)};
- json_obj["shader_asset"] = picojson::value{data.shader_asset};
+ if (data.depth_state)
+ {
+ picojson::object depth_state_json;
+ depth_state_json.emplace("test_enable",
+ static_cast(data.depth_state->test_enable.Value()));
+ depth_state_json.emplace("update_enable",
+ static_cast(data.depth_state->update_enable.Value()));
+ depth_state_json.emplace("func", static_cast(data.depth_state->func.Value()));
+ json_obj.emplace("depth_state", depth_state_json);
+ }
+
+ if (data.blending_state)
+ {
+ picojson::object blending_state_json;
+ blending_state_json.emplace("blend_enable",
+ static_cast(data.blending_state->blend_enable.Value()));
+ blending_state_json.emplace("logic_op_enable",
+ static_cast(data.blending_state->logic_op_enable.Value()));
+ blending_state_json.emplace("color_update",
+ static_cast(data.blending_state->color_update.Value()));
+ blending_state_json.emplace("alpha_update",
+ static_cast(data.blending_state->alpha_update.Value()));
+ blending_state_json.emplace("subtract",
+ static_cast(data.blending_state->subtract.Value()));
+ blending_state_json.emplace("subtract_alpha",
+ static_cast(data.blending_state->subtract_alpha.Value()));
+ blending_state_json.emplace("use_dual_src",
+ static_cast(data.blending_state->use_dual_src.Value()));
+ blending_state_json.emplace("dst_factor",
+ static_cast(data.blending_state->dst_factor.Value()));
+ blending_state_json.emplace("src_factor",
+ static_cast(data.blending_state->src_factor.Value()));
+ blending_state_json.emplace("dst_factor_alpha",
+ static_cast(data.blending_state->dst_factor_alpha.Value()));
+ blending_state_json.emplace("src_factor_alpha",
+ static_cast(data.blending_state->src_factor_alpha.Value()));
+ blending_state_json.emplace("logic_mode",
+ static_cast(data.blending_state->logic_mode.Value()));
+ json_obj.emplace("blending_state", blending_state_json);
+ }
+
+ picojson::array json_textures;
+ for (const auto& texture : data.textures)
+ {
+ picojson::object json_texture;
+ TextureSamplerValue::ToJson(&json_texture, texture);
+ json_textures.emplace_back(std::move(json_texture));
+ }
+ json_obj.emplace("textures", json_textures);
}
CustomAssetLibrary::LoadInfo MaterialAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
diff --git a/Source/Core/VideoCommon/Assets/MaterialAsset.h b/Source/Core/VideoCommon/Assets/MaterialAsset.h
index 0e9a4558a5..0ba786087a 100644
--- a/Source/Core/VideoCommon/Assets/MaterialAsset.h
+++ b/Source/Core/VideoCommon/Assets/MaterialAsset.h
@@ -4,6 +4,7 @@
#pragma once
#include
+#include
#include
#include
#include
@@ -11,8 +12,10 @@
#include
#include "Common/CommonTypes.h"
-#include "Common/EnumFormatter.h"
#include "VideoCommon/Assets/CustomAsset.h"
+#include "VideoCommon/Assets/TextureSamplerValue.h"
+#include "VideoCommon/BPMemory.h"
+#include "VideoCommon/RenderState.h"
class ShaderCode;
@@ -22,11 +25,9 @@ struct MaterialProperty
{
static void WriteToMemory(u8*& buffer, const MaterialProperty& property);
static std::size_t GetMemorySize(const MaterialProperty& property);
- static void WriteAsShaderCode(ShaderCode& shader_source, const MaterialProperty& property);
- using Value = std::variant,
- std::array, std::array, float, std::array,
- std::array, std::array, bool>;
- std::string m_code_name;
+ using Value =
+ std::variant, std::array, std::array, float,
+ std::array, std::array, std::array, bool>;
Value m_value;
};
@@ -35,8 +36,15 @@ struct MaterialData
static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json,
MaterialData* data);
static void ToJson(picojson::object* obj, const MaterialData& data);
- std::string shader_asset;
+ CustomAssetLibrary::AssetID shader_asset;
+ CustomAssetLibrary::AssetID next_material_asset;
std::vector properties;
+
+ std::optional cull_mode;
+ std::optional depth_state;
+ std::optional blending_state;
+
+ std::vector textures;
};
// Much like Unity and Unreal materials, a Dolphin material does very little on its own
diff --git a/Source/Core/VideoCommon/Assets/TextureSamplerValue.cpp b/Source/Core/VideoCommon/Assets/TextureSamplerValue.cpp
new file mode 100644
index 0000000000..19fd4181a3
--- /dev/null
+++ b/Source/Core/VideoCommon/Assets/TextureSamplerValue.cpp
@@ -0,0 +1,60 @@
+// Copyright 2024 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "VideoCommon/Assets/TextureSamplerValue.h"
+
+#include
+
+#include "Common/JsonUtil.h"
+#include "Common/StringUtil.h"
+
+namespace VideoCommon
+{
+namespace
+{
+std::optional
+ReadSamplerOriginFromJSON(const picojson::object& json)
+{
+ auto sampler_origin = ReadStringFromJson(json, "sampler_origin").value_or("");
+ Common::ToLower(&sampler_origin);
+
+ if (sampler_origin == "asset")
+ {
+ return TextureSamplerValue::SamplerOrigin::Asset;
+ }
+ else if (sampler_origin == "texture_hash")
+ {
+ return TextureSamplerValue::SamplerOrigin::TextureHash;
+ }
+
+ return std::nullopt;
+}
+} // namespace
+std::string TextureSamplerValue::ToString(SamplerOrigin sampler_origin)
+{
+ if (sampler_origin == SamplerOrigin::Asset)
+ return "asset";
+
+ return "texture_hash";
+}
+
+bool TextureSamplerValue::FromJson(const picojson::object& json, TextureSamplerValue* data)
+{
+ data->asset = ReadStringFromJson(json, "asset").value_or("");
+ data->texture_hash = ReadStringFromJson(json, "texture_hash").value_or("");
+ data->sampler_origin =
+ ReadSamplerOriginFromJSON(json).value_or(TextureSamplerValue::SamplerOrigin::Asset);
+
+ return true;
+}
+
+void TextureSamplerValue::ToJson(picojson::object* obj, const TextureSamplerValue& data)
+{
+ if (!obj) [[unlikely]]
+ return;
+
+ obj->emplace("asset", data.asset);
+ obj->emplace("texture_hash", data.texture_hash);
+ obj->emplace("sampler_origin", ToString(data.sampler_origin));
+}
+} // namespace VideoCommon
diff --git a/Source/Core/VideoCommon/Assets/TextureSamplerValue.h b/Source/Core/VideoCommon/Assets/TextureSamplerValue.h
new file mode 100644
index 0000000000..11896559f5
--- /dev/null
+++ b/Source/Core/VideoCommon/Assets/TextureSamplerValue.h
@@ -0,0 +1,36 @@
+// Copyright 2024 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+
+#include
+
+#include "VideoCommon/Assets/CustomAssetLibrary.h"
+
+namespace VideoCommon
+{
+// A structure that provides metadata about a texture to a material
+struct TextureSamplerValue
+{
+ CustomAssetLibrary::AssetID asset;
+
+ // Where does the sampler originate from
+ // If 'Asset' is used, the sampler is pulled
+ // directly from the asset properties
+ // If 'TextureHash' is chosen, the sampler is pulled
+ // from the game with the cooresponding texture hash
+ enum class SamplerOrigin
+ {
+ Asset,
+ TextureHash
+ };
+ static std::string ToString(SamplerOrigin sampler_origin);
+ SamplerOrigin sampler_origin = SamplerOrigin::Asset;
+ std::string texture_hash;
+
+ static bool FromJson(const picojson::object& json, TextureSamplerValue* data);
+ static void ToJson(picojson::object* obj, const TextureSamplerValue& data);
+};
+} // namespace VideoCommon
diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt
index b4ba6ddf78..1bf87231e5 100644
--- a/Source/Core/VideoCommon/CMakeLists.txt
+++ b/Source/Core/VideoCommon/CMakeLists.txt
@@ -29,6 +29,8 @@ add_library(videocommon
Assets/TextureAsset.h
Assets/TextureAssetUtils.cpp
Assets/TextureAssetUtils.h
+ Assets/TextureSamplerValue.cpp
+ Assets/TextureSamplerValue.h
Assets/Types.h
Assets/WatchableFilesystemAssetLibrary.h
AsyncRequests.cpp