/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace JsonSerializationTests { class MaterialPropertyValueSourceDataSerializerTestDescription : public JsonSerializerConformityTestDescriptor { public: void Reflect(AZStd::unique_ptr& context) override { AZ::RPI::MaterialPropertyValueSourceData::Reflect(context.get()); } void Reflect(AZStd::unique_ptr& context) override { context->Serializer()->HandlesType(); } AZStd::shared_ptr CreateSerializer() override { return AZStd::make_shared(); } AZStd::shared_ptr CreateDefaultInstance() override { return AZStd::make_shared(); } AZStd::shared_ptr CreateFullySetInstance() override { auto result = AZStd::make_shared(); result->SetValue(42); return result; } AZStd::string_view GetJsonForFullySetInstance() override { return R"(42)"; } void ConfigureFeatures(JsonSerializerConformityTestDescriptorFeatures& features) override { features.EnableJsonType(rapidjson::kFalseType); features.EnableJsonType(rapidjson::kTrueType); features.EnableJsonType(rapidjson::kArrayType); features.EnableJsonType(rapidjson::kStringType); features.EnableJsonType(rapidjson::kNumberType); features.m_fixedSizeArray = true; features.m_supportsPartialInitialization = false; features.m_supportsInjection = false; } bool AreEqual( const AZ::RPI::MaterialPropertyValueSourceData& lhs, const AZ::RPI::MaterialPropertyValueSourceData& rhs) override { return AZ::RPI::MaterialPropertyValueSourceData::AreSimilar(lhs, rhs); } }; using MaterialPropertyValueSourceDataSerializerTestTypes = ::testing::Types; INSTANTIATE_TYPED_TEST_CASE_P(MaterialPropertyValueSourceDataTests, JsonSerializerConformityTests, MaterialPropertyValueSourceDataSerializerTestTypes); } // namespace JsonSerializationTests namespace UnitTest { using namespace AZ; using namespace RPI; class MaterialPropertyValueSourceDataTests : public RPITestFixture { protected: //! A dummy material creator filled with necessary data. //! It is kept alive between SetUp and TearDown, so that MaterialPropertyValueSourceData can have access to the layout and resolve its value. MaterialTypeAssetCreator m_materialTypeCreator; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Samples used for testing MaterialFunctor class ValueFunctor final : public MaterialFunctor { public: AZ_RTTI(MaterialPropertyValueSourceDataTests::ValueFunctor, "{07CE498C-6E97-45C9-8B2D-18BC03724055}", AZ::RPI::MaterialFunctor); static void Reflect(ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(1) ->Field("propertyIndex", &ValueFunctor::m_propertyIndex) ->Field("propertyValue", &ValueFunctor::m_propertyValue) ; } } MaterialPropertyIndex m_propertyIndex; MaterialPropertyValue m_propertyValue; }; class ValueFunctorSourceData final : public MaterialFunctorSourceData { public: AZ_RTTI(ValueFunctorSourceData, "{777CE7A5-3023-4C63-BA43-5763DF51D82D}", AZ::RPI::MaterialFunctorSourceData); static void Reflect(ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(1) ->Field("propertyName", &ValueFunctorSourceData::m_propertyName) ->Field("propertyValue", &ValueFunctorSourceData::m_propertyValue) ; } } AZStd::string m_propertyName; MaterialPropertyValueSourceData m_propertyValue; using MaterialFunctorSourceData::CreateFunctor; FunctorResult CreateFunctor(const RuntimeContext& context) const override { Ptr functor = aznew ValueFunctor; functor->m_propertyIndex = context.FindMaterialPropertyIndex(Name(m_propertyName)); EXPECT_TRUE(functor->m_propertyIndex.IsValid()); m_propertyValue.Resolve(*context.GetMaterialPropertiesLayout(), AZ::Name(m_propertyName)); functor->m_propertyValue = m_propertyValue.GetValue(); return Success(Ptr(functor)); } }; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Reflect(ReflectContext* context) override { RPITestFixture::Reflect(context); MaterialPropertyValueSourceData::Reflect(context); MaterialFunctorSourceDataHolder::Reflect(context); ValueFunctorSourceData::Reflect(context); ValueFunctor::Reflect(context); } void SetUp() override { RPITestFixture::SetUp(); m_materialTypeCreator.Begin(Data::AssetId(Uuid::CreateRandom())); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{"general.Bool"}, MaterialPropertyDataType::Bool); m_materialTypeCreator.EndMaterialProperty(); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{"general.Int"}, MaterialPropertyDataType::Int); m_materialTypeCreator.EndMaterialProperty(); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{"general.UInt"}, MaterialPropertyDataType::UInt); m_materialTypeCreator.EndMaterialProperty(); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{"general.Float"}, MaterialPropertyDataType::Float); m_materialTypeCreator.EndMaterialProperty(); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{"general.Vector2"}, MaterialPropertyDataType::Vector2); m_materialTypeCreator.EndMaterialProperty(); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{"general.Vector3"}, MaterialPropertyDataType::Vector3); m_materialTypeCreator.EndMaterialProperty(); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{"general.Vector4"}, MaterialPropertyDataType::Vector4); m_materialTypeCreator.EndMaterialProperty(); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{"general.Color"}, MaterialPropertyDataType::Color); m_materialTypeCreator.EndMaterialProperty(); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{"general.Image"}, MaterialPropertyDataType::Image); m_materialTypeCreator.EndMaterialProperty(); m_materialTypeCreator.BeginMaterialProperty(AZ::Name{ "general.Enum" }, MaterialPropertyDataType::Enum); m_materialTypeCreator.SetMaterialPropertyEnumNames(AZStd::vector({ "DummyEnum" })); m_materialTypeCreator.EndMaterialProperty(); } void TearDown() override { { Data::Asset testAsset; m_materialTypeCreator.End(testAsset); } RPITestFixture::TearDown(); } }; TEST_F(MaterialPropertyValueSourceDataTests, MaterialFunctorTest) { AZStd::unordered_map typeValue = { {MaterialPropertyDataType::Bool, "true"}, {MaterialPropertyDataType::Int, "-42"}, {MaterialPropertyDataType::UInt, "42"}, {MaterialPropertyDataType::Float, "42.0"}, {MaterialPropertyDataType::Vector2, "[42.0, 42.0]"}, {MaterialPropertyDataType::Vector3, "[42.0, 42.0, 42.0]"}, {MaterialPropertyDataType::Vector4, "[42.0, 42.0, 42.0, 42.0]"}, {MaterialPropertyDataType::Color, "[0.0, 0.0, 0.0, 1.0]"}, {MaterialPropertyDataType::Image, "\"DummyImagePath.png\""}, {MaterialPropertyDataType::Enum, "\"DummyEnum\""}, }; AZStd::array, static_cast(MaterialPropertyDataType::Count)> valueFunctors; valueFunctors.fill(nullptr); char inputJson[2048]; AZStd::string outputJson; uint32_t propertyTypeCount = static_cast(MaterialPropertyDataType::Count); static_assert(static_cast(MaterialPropertyDataType::Invalid) == 0u, "If MaterialPropertyDataType::Invalid has changed its order, the following logic should change accordingly."); // Test through each type, serialization and deserialization. // Collect the functors and test the serialized value later. for (uint32_t i = static_cast(MaterialPropertyDataType::Invalid) + 1u; i < propertyTypeCount; ++i) { MaterialPropertyDataType type = static_cast(i); azsprintf(inputJson, R"( { "propertyName": "general.%s", "propertyValue": %s } )", ToString(type), typeValue[type] ); // Stored as base class. Ptr functorData = aznew ValueFunctorSourceData; JsonTestResult loadResult = LoadTestDataFromJson(*functorData, inputJson); MaterialNameContext nameContext; // Where type resolving happens. MaterialFunctorSourceData::FunctorResult functorResult = functorData->CreateFunctor( MaterialFunctorSourceData::RuntimeContext( "Dummy.materialtype", m_materialTypeCreator.GetMaterialPropertiesLayout(), m_materialTypeCreator.GetMaterialShaderResourceGroupLayout(), m_materialTypeCreator.GetShaderCollection(), &nameContext ) ); valueFunctors[i] = functorResult.GetValue(); // Store back to json after type is resolved. outputJson.clear(); JsonTestResult storeResult = StoreTestDataToJson(*functorData, outputJson); ExpectSimilarJson(inputJson, outputJson); } MaterialPropertyValue& value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::Bool)].get())->m_propertyValue; EXPECT_TRUE(value == true); value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::Int)].get())->m_propertyValue; EXPECT_TRUE(value == -42); value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::UInt)].get())->m_propertyValue; EXPECT_TRUE(value == 42u); value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::Float)].get())->m_propertyValue; EXPECT_TRUE(value == 42.0f); value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::Vector2)].get())->m_propertyValue; EXPECT_TRUE(value == Vector2(42.0f, 42.0f)); value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::Vector3)].get())->m_propertyValue; EXPECT_TRUE(value == Vector3(42.0f, 42.0f, 42.0f)); value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::Vector4)].get())->m_propertyValue; EXPECT_TRUE(value == Vector4(42.0f, 42.0f, 42.0f, 42.0f)); value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::Color)].get())->m_propertyValue; EXPECT_TRUE(value == Color(0.0f, 0.0f, 0.0f, 1.0f)); value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::Image)].get())->m_propertyValue; EXPECT_TRUE(value == AZStd::string("DummyImagePath.png")); value = static_cast(valueFunctors[static_cast(MaterialPropertyDataType::Enum)].get())->m_propertyValue; EXPECT_TRUE(value == AZStd::string("DummyEnum")); } TEST_F(MaterialPropertyValueSourceDataTests, DataSimilarityTest) { MaterialPropertyValueSourceData emptyValue; MaterialPropertyValueSourceData onlyResolvedValue[2]; MaterialPropertyValueSourceData onlyPossibleValue[2]; MaterialPropertyValueSourceData fullValue[2]; // Load data static const char* source[] = { R"(42)", R"(43)" }; constexpr int32_t value[] = { 42, 43 }; for (int32_t i = 0; i < 2; ++i) { LoadTestDataFromJson(onlyPossibleValue[i], source[i]); LoadTestDataFromJson(fullValue[i], source[i]); onlyResolvedValue[i].SetValue(value[i]); fullValue[i].SetValue(value[i]); } // Group them into 2 based on the source data. // Within each set, AreSimilar should return true. // Between two sets, assuming different source data, AreSimilar should return false. MaterialPropertyValueSourceData* setA[] = { &onlyResolvedValue[0], &onlyPossibleValue[0], &fullValue[0] }; MaterialPropertyValueSourceData* setB[] = { &onlyResolvedValue[1], &onlyPossibleValue[1], &fullValue[1] }; EXPECT_TRUE(AZ::RPI::MaterialPropertyValueSourceData::AreSimilar(emptyValue, emptyValue)); for (int32_t i = 0; i < 3; ++i) { for (int32_t j = 0; j < 3; ++j) { // Test within each set and between them, including comparing to the element itself. EXPECT_TRUE(AZ::RPI::MaterialPropertyValueSourceData::AreSimilar(*setA[i], *setA[j])); EXPECT_TRUE(AZ::RPI::MaterialPropertyValueSourceData::AreSimilar(*setB[i], *setB[j])); EXPECT_FALSE(AZ::RPI::MaterialPropertyValueSourceData::AreSimilar(*setA[i], *setB[j])); } // Test against empty. EXPECT_FALSE(AZ::RPI::MaterialPropertyValueSourceData::AreSimilar(*setA[i], emptyValue)); EXPECT_FALSE(AZ::RPI::MaterialPropertyValueSourceData::AreSimilar(*setB[i], emptyValue)); } } }