From 440999d7789b54066380b3f13f1845d74a839a6e Mon Sep 17 00:00:00 2001 From: iwubcode Date: Fri, 22 Aug 2025 19:09:39 -0500 Subject: [PATCH] VideoCommon: update material asset to support render state properties, support multi-pass, and textures are now split out (as well as supporting a way to calculate sampler origin) --- Source/Core/DolphinLib.props | 2 + .../Core/VideoCommon/Assets/MaterialAsset.cpp | 307 +++++++++++------- .../Core/VideoCommon/Assets/MaterialAsset.h | 22 +- .../Assets/TextureSamplerValue.cpp | 60 ++++ .../VideoCommon/Assets/TextureSamplerValue.h | 36 ++ Source/Core/VideoCommon/CMakeLists.txt | 2 + 6 files changed, 309 insertions(+), 120 deletions(-) create mode 100644 Source/Core/VideoCommon/Assets/TextureSamplerValue.cpp create mode 100644 Source/Core/VideoCommon/Assets/TextureSamplerValue.h 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