@ -11,6 +11,7 @@
# include <Common/JsonTestUtils.h>
# include <Common/ShaderAssetTestUtils.h>
# include <Common/ErrorMessageFinder.h>
# include <Common/SerializeTester.h>
# include <Material/MaterialAssetTestUtils.h>
# include <Atom/RPI.Reflect/Material/MaterialAsset.h>
@ -65,9 +66,29 @@ namespace UnitTest
m_testShaderAsset = CreateTestShaderAsset ( Uuid : : CreateRandom ( ) , m_testMaterialSrgLayout ) ;
m_assetSystemStub . RegisterSourceInfo ( " @exefolder@/Temp/test.shader " , m_testShaderAsset . GetId ( ) ) ;
// The MaterialSourceData relies on both MaterialTypeSourceData and MaterialTypeAsset. We have to make sure the
// .materialtype file is present on disk, and that the MaterialTypeAsset is available through the asset database stub...
m_testMaterialTypeAsset = CreateTestMaterialTypeAsset ( Uuid : : CreateRandom ( ) ) ;
// Since this test doesn't actually instantiate a Material, it won't need to instantiate this ImageAsset, so all we
// need is an asset reference with a valid ID.
m_testImageAsset = Data : : Asset < ImageAsset > { Data : : AssetId { Uuid : : CreateRandom ( ) , StreamingImageAsset : : GetImageAssetSubId ( ) } , azrtti_typeid < StreamingImageAsset > ( ) } ;
// Register the test assets with the AssetSystemStub so CreateMaterialAsset() can use AssetUtils.
m_assetSystemStub . RegisterSourceInfo ( " @exefolder@/Temp/test.materialtype " , m_testMaterialTypeAsset . GetId ( ) ) ;
m_assetSystemStub . RegisterSourceInfo ( " @exefolder@/Temp/test.streamingimage " , m_testImageAsset . GetId ( ) ) ;
}
void TearDown ( ) override
{
m_testMaterialTypeAsset . Reset ( ) ;
m_testMaterialSrgLayout = nullptr ;
m_testShaderAsset . Reset ( ) ;
m_testImageAsset . Reset ( ) ;
RPITestFixture : : TearDown ( ) ;
}
Data : : Asset < MaterialTypeAsset > CreateTestMaterialTypeAsset ( Data : : AssetId assetId )
{
const char * materialTypeJson = R " (
{
" version " : 10 ,
@ -122,29 +143,10 @@ namespace UnitTest
}
) " ;
AZ : : Utils : : WriteFile ( materialTypeJson , " @exefolder@/Temp/test.materialtype " ) ;
MaterialTypeSourceData materialTypeSourceData ;
LoadTestDataFromJson ( materialTypeSourceData , materialTypeJson ) ;
m_testMaterialTypeAsset = materialTypeSourceData . CreateMaterialTypeAsset ( Uuid : : CreateRandom ( ) ) . TakeValue ( ) ;
// Since this test doesn't actually instantiate a Material, it won't need to instantiate this ImageAsset, so all we
// need is an asset reference with a valid ID.
m_testImageAsset = Data : : Asset < ImageAsset > { Data : : AssetId { Uuid : : CreateRandom ( ) , StreamingImageAsset : : GetImageAssetSubId ( ) } , azrtti_typeid < StreamingImageAsset > ( ) } ;
// Register the test assets with the AssetSystemStub so CreateMaterialAsset() can use AssetUtils.
m_assetSystemStub . RegisterSourceInfo ( " @exefolder@/Temp/test.materialtype " , m_testMaterialTypeAsset . GetId ( ) ) ;
m_assetSystemStub . RegisterSourceInfo ( " @exefolder@/Temp/test.streamingimage " , m_testImageAsset . GetId ( ) ) ;
}
void TearDown ( ) override
{
m_testMaterialTypeAsset . Reset ( ) ;
m_testMaterialSrgLayout = nullptr ;
m_testShaderAsset . Reset ( ) ;
m_testImageAsset . Reset ( ) ;
RPITestFixture : : TearDown ( ) ;
return materialTypeSourceData . CreateMaterialTypeAsset ( assetId ) . TakeValue ( ) ;
}
} ;
@ -175,12 +177,110 @@ namespace UnitTest
AddProperty ( sourceData , " general " , " MyImage " , AZStd : : string ( " @exefolder@/Temp/test.streamingimage " ) ) ;
AddProperty ( sourceData , " general " , " MyEnum " , AZStd : : string ( " Enum1 " ) ) ;
auto materialAssetOutcome = sourceData . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , true ) ;
auto materialAssetOutcome = sourceData . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : PreBake , true ) ;
EXPECT_TRUE ( materialAssetOutcome . IsSuccess ( ) ) ;
Data : : Asset < MaterialAsset > materialAsset = materialAssetOutcome . GetValue ( ) ;
EXPECT_TRUE ( materialAsset - > WasPreFinalized ( ) ) ;
EXPECT_EQ ( 0 , materialAsset - > GetRawPropertyValues ( ) . size ( ) ) ; // A pre-baked material has no need for the original raw property names and values
// The order here is based on the order in the MaterialTypeSourceData, as added to the MaterialTypeAssetCreator.
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 0 ] . GetValue < bool > ( ) , true ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 1 ] . GetValue < int32_t > ( ) , - 10 ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 2 ] . GetValue < uint32_t > ( ) , 25u ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 3 ] . GetValue < float > ( ) , 1.5f ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 4 ] . GetValue < Vector2 > ( ) , Vector2 ( 2.1f , 2.2f ) ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 5 ] . GetValue < Vector3 > ( ) , Vector3 ( 3.1f , 3.2f , 3.3f ) ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 6 ] . GetValue < Vector4 > ( ) , Vector4 ( 4.1f , 4.2f , 4.3f , 4.4f ) ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 7 ] . GetValue < Color > ( ) , Color ( 0.1f , 0.2f , 0.3f , 0.4f ) ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 8 ] . GetValue < Data : : Asset < ImageAsset > > ( ) , m_testImageAsset ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 9 ] . GetValue < uint32_t > ( ) , 1u ) ;
}
TEST_F ( MaterialSourceDataTests , CreateMaterialAsset_DeferredBake )
{
// This test is similar to CreateMaterialAsset_BasicProperties but uses MaterialAssetProcessingMode::DeferredBake instead of PreBake.
Data : : AssetId materialTypeAssetId = Uuid : : CreateRandom ( ) ;
// This material type asset will be known by the asset system (stub) but doesn't exist in the AssetManager.
// This demonstrates that the CreateMaterialAsset does not attempt to access the MaterialTypeAsset data in MaterialAssetProcessingMode::DeferredBake.
m_assetSystemStub . RegisterSourceInfo ( " testDeferredBake.materialtype " , materialTypeAssetId ) ;
MaterialSourceData sourceData ;
sourceData . m_materialType = " testDeferredBake.materialtype " ;
AddPropertyGroup ( sourceData , " general " ) ;
AddProperty ( sourceData , " general " , " MyBool " , true ) ;
AddProperty ( sourceData , " general " , " MyInt " , - 10 ) ;
AddProperty ( sourceData , " general " , " MyUInt " , 25u ) ;
AddProperty ( sourceData , " general " , " MyFloat " , 1.5f ) ;
AddProperty ( sourceData , " general " , " MyColor " , AZ : : Color { 0.1f , 0.2f , 0.3f , 0.4f } ) ;
AddProperty ( sourceData , " general " , " MyFloat2 " , AZ : : Vector2 ( 2.1f , 2.2f ) ) ;
AddProperty ( sourceData , " general " , " MyFloat3 " , AZ : : Vector3 ( 3.1f , 3.2f , 3.3f ) ) ;
AddProperty ( sourceData , " general " , " MyFloat4 " , AZ : : Vector4 ( 4.1f , 4.2f , 4.3f , 4.4f ) ) ;
AddProperty ( sourceData , " general " , " MyImage " , AZStd : : string ( " @exefolder@/Temp/test.streamingimage " ) ) ;
AddProperty ( sourceData , " general " , " MyEnum " , AZStd : : string ( " Enum1 " ) ) ;
auto materialAssetOutcome = sourceData . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : DeferredBake , true ) ;
EXPECT_TRUE ( materialAssetOutcome . IsSuccess ( ) ) ;
Data : : Asset < MaterialAsset > materialAsset = materialAssetOutcome . GetValue ( ) ;
// The order here is based on the order in the MaterialTypeSourceData, as added to the the MaterialTypeAssetCreator.
EXPECT_FALSE ( materialAsset - > WasPreFinalized ( ) ) ;
// Note we avoid calling GetPropertyValues() because that will auto-finalize the material. We want to check its raw property values first.
auto findRawPropertyValue = [ materialAsset ] ( const char * propertyId )
{
auto iter = AZStd : : find_if ( materialAsset - > GetRawPropertyValues ( ) . begin ( ) , materialAsset - > GetRawPropertyValues ( ) . end ( ) , [ propertyId ] ( const AZStd : : pair < Name , MaterialPropertyValue > & pair )
{
return pair . first = = AZ : : Name { propertyId } ;
} ) ;
if ( iter = = materialAsset - > GetRawPropertyValues ( ) . end ( ) )
{
return MaterialPropertyValue { } ;
}
else
{
return iter - > second ;
}
} ;
auto checkRawPropertyValues = [ findRawPropertyValue , this ] ( )
{
EXPECT_EQ ( findRawPropertyValue ( " general.MyBool " ) . GetValue < bool > ( ) , true ) ;
EXPECT_EQ ( findRawPropertyValue ( " general.MyInt " ) . GetValue < int32_t > ( ) , - 10 ) ;
EXPECT_EQ ( findRawPropertyValue ( " general.MyUInt " ) . GetValue < uint32_t > ( ) , 25u ) ;
EXPECT_EQ ( findRawPropertyValue ( " general.MyFloat " ) . GetValue < float > ( ) , 1.5f ) ;
EXPECT_EQ ( findRawPropertyValue ( " general.MyFloat2 " ) . GetValue < Vector2 > ( ) , Vector2 ( 2.1f , 2.2f ) ) ;
EXPECT_EQ ( findRawPropertyValue ( " general.MyFloat3 " ) . GetValue < Vector3 > ( ) , Vector3 ( 3.1f , 3.2f , 3.3f ) ) ;
EXPECT_EQ ( findRawPropertyValue ( " general.MyFloat4 " ) . GetValue < Vector4 > ( ) , Vector4 ( 4.1f , 4.2f , 4.3f , 4.4f ) ) ;
EXPECT_EQ ( findRawPropertyValue ( " general.MyColor " ) . GetValue < Color > ( ) , Color ( 0.1f , 0.2f , 0.3f , 0.4f ) ) ;
EXPECT_EQ ( findRawPropertyValue ( " general.MyImage " ) . GetValue < Data : : Asset < ImageAsset > > ( ) , m_testImageAsset ) ;
// The raw value for an enum is the original string, not the numerical value, because the material type holds the necessary metadata to match the name to the value.
EXPECT_EQ ( findRawPropertyValue ( " general.MyEnum " ) . GetValue < AZStd : : string > ( ) , AZStd : : string ( " Enum1 " ) ) ;
} ;
// We check the raw property values before the material type asset is even available
checkRawPropertyValues ( ) ;
// Now we'll create the material type asset in memory so the material will have what it needs to finalize itself.
Data : : Asset < MaterialTypeAsset > testMaterialTypeAsset = CreateTestMaterialTypeAsset ( materialTypeAssetId ) ;
// The MaterialAsset is still holding an reference to an unloaded asset, so we run it through the serializer which causes the loaded MaterialAsset
// to have access to the testMaterialTypeAsset. This is similar to how the AP would save the MaterialAsset to the cache and the runtime would load it.
SerializeTester < RPI : : MaterialAsset > tester ( GetSerializeContext ( ) ) ;
tester . SerializeOut ( materialAsset . Get ( ) ) ;
materialAsset = tester . SerializeIn ( Uuid : : CreateRandom ( ) , ObjectStream : : FilterDescriptor { AZ : : Data : : AssetFilterNoAssetLoading } ) ;
// We check that the asset is still in the original un-finalized state after going through the serialization process.
EXPECT_FALSE ( materialAsset - > WasPreFinalized ( ) ) ;
checkRawPropertyValues ( ) ;
// Now all the property values should be available through the main GetPropertyValues() API.
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 0 ] . GetValue < bool > ( ) , true ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 1 ] . GetValue < int32_t > ( ) , - 10 ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 2 ] . GetValue < uint32_t > ( ) , 25u ) ;
@ -191,6 +291,10 @@ namespace UnitTest
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 7 ] . GetValue < Color > ( ) , Color ( 0.1f , 0.2f , 0.3f , 0.4f ) ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 8 ] . GetValue < Data : : Asset < ImageAsset > > ( ) , m_testImageAsset ) ;
EXPECT_EQ ( materialAsset - > GetPropertyValues ( ) [ 9 ] . GetValue < uint32_t > ( ) , 1u ) ;
// The raw property values are still available (because they are needed if a hot-reload of the MaterialTypeAsset occurs)
EXPECT_FALSE ( materialAsset - > WasPreFinalized ( ) ) ;
checkRawPropertyValues ( ) ;
}
void CheckEqual ( MaterialSourceData & a , MaterialSourceData & b )
@ -544,18 +648,21 @@ namespace UnitTest
AddPropertyGroup ( sourceDataLevel3 , " general " ) ;
AddProperty ( sourceDataLevel3 , " general " , " MyFloat " , 3.5f ) ;
auto materialAssetLevel1 = sourceDataLevel1 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , true ) ;
auto materialAssetLevel1 = sourceDataLevel1 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : PreBake , true ) ;
EXPECT_TRUE ( materialAssetLevel1 . IsSuccess ( ) ) ;
EXPECT_TRUE ( materialAssetLevel1 . GetValue ( ) - > WasPreFinalized ( ) ) ;
m_assetSystemStub . RegisterSourceInfo ( " level1.material " , materialAssetLevel1 . GetValue ( ) . GetId ( ) ) ;
auto materialAssetLevel2 = sourceDataLevel2 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , true ) ;
auto materialAssetLevel2 = sourceDataLevel2 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : PreBake , true ) ;
EXPECT_TRUE ( materialAssetLevel2 . IsSuccess ( ) ) ;
EXPECT_TRUE ( materialAssetLevel2 . GetValue ( ) - > WasPreFinalized ( ) ) ;
m_assetSystemStub . RegisterSourceInfo ( " level2.material " , materialAssetLevel2 . GetValue ( ) . GetId ( ) ) ;
auto materialAssetLevel3 = sourceDataLevel3 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , true ) ;
auto materialAssetLevel3 = sourceDataLevel3 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : PreBake , true ) ;
EXPECT_TRUE ( materialAssetLevel3 . IsSuccess ( ) ) ;
EXPECT_TRUE ( materialAssetLevel3 . GetValue ( ) - > WasPreFinalized ( ) ) ;
auto layout = m_testMaterialTypeAsset - > GetMaterialPropertiesLayout ( ) ;
MaterialPropertyIndex myFloat = layout - > FindPropertyIndex ( Name ( " general.MyFloat " ) ) ;
@ -582,6 +689,96 @@ namespace UnitTest
EXPECT_EQ ( properties [ myFloat2 . GetIndex ( ) ] . GetValue < Vector2 > ( ) , Vector2 ( 4.1f , 4.2f ) ) ;
EXPECT_EQ ( properties [ myColor . GetIndex ( ) ] . GetValue < Color > ( ) , Color ( 0.15f , 0.25f , 0.35f , 0.45f ) ) ;
}
TEST_F ( MaterialSourceDataTests , CreateMaterialAsset_MultiLevelDataInheritance_DeferredBake )
{
// This test is similar to CreateMaterialAsset_MultiLevelDataInheritance but uses MaterialAssetProcessingMode::DeferredBake instead of PreBake.
Data : : AssetId materialTypeAssetId = Uuid : : CreateRandom ( ) ;
// This material type asset will be known by the asset system (stub) but doesn't exist in the AssetManager.
// This demonstrates that the CreateMaterialAsset does not attempt to access the MaterialTypeAsset data in MaterialAssetProcessingMode::DeferredBake.
m_assetSystemStub . RegisterSourceInfo ( " testDeferredBake.materialtype " , materialTypeAssetId ) ;
MaterialSourceData sourceDataLevel1 ;
sourceDataLevel1 . m_materialType = " testDeferredBake.materialtype " ;
AddPropertyGroup ( sourceDataLevel1 , " general " ) ;
AddProperty ( sourceDataLevel1 , " general " , " MyFloat " , 1.5f ) ;
AddProperty ( sourceDataLevel1 , " general " , " MyColor " , AZ : : Color { 0.1f , 0.2f , 0.3f , 0.4f } ) ;
MaterialSourceData sourceDataLevel2 ;
sourceDataLevel2 . m_materialType = " testDeferredBake.materialtype " ;
sourceDataLevel2 . m_parentMaterial = " level1.material " ;
AddPropertyGroup ( sourceDataLevel2 , " general " ) ;
AddProperty ( sourceDataLevel2 , " general " , " MyColor " , AZ : : Color { 0.15f , 0.25f , 0.35f , 0.45f } ) ;
AddProperty ( sourceDataLevel2 , " general " , " MyFloat2 " , AZ : : Vector2 { 4.1f , 4.2f } ) ;
MaterialSourceData sourceDataLevel3 ;
sourceDataLevel3 . m_materialType = " testDeferredBake.materialtype " ;
sourceDataLevel3 . m_parentMaterial = " level2.material " ;
AddPropertyGroup ( sourceDataLevel3 , " general " ) ;
AddProperty ( sourceDataLevel3 , " general " , " MyFloat " , 3.5f ) ;
auto materialAssetLevel1Result = sourceDataLevel1 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : DeferredBake , true ) ;
EXPECT_TRUE ( materialAssetLevel1Result . IsSuccess ( ) ) ;
Data : : Asset < MaterialAsset > materialAssetLevel1 = materialAssetLevel1Result . TakeValue ( ) ;
EXPECT_FALSE ( materialAssetLevel1 - > WasPreFinalized ( ) ) ;
m_assetSystemStub . RegisterSourceInfo ( " level1.material " , materialAssetLevel1 . GetId ( ) ) ;
auto materialAssetLevel2Result = sourceDataLevel2 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : DeferredBake , true ) ;
EXPECT_TRUE ( materialAssetLevel2Result . IsSuccess ( ) ) ;
Data : : Asset < MaterialAsset > materialAssetLevel2 = materialAssetLevel2Result . TakeValue ( ) ;
EXPECT_FALSE ( materialAssetLevel2 - > WasPreFinalized ( ) ) ;
m_assetSystemStub . RegisterSourceInfo ( " level2.material " , materialAssetLevel2 . GetId ( ) ) ;
auto materialAssetLevel3Result = sourceDataLevel3 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : DeferredBake , true ) ;
EXPECT_TRUE ( materialAssetLevel3Result . IsSuccess ( ) ) ;
Data : : Asset < MaterialAsset > materialAssetLevel3 = materialAssetLevel3Result . TakeValue ( ) ;
EXPECT_FALSE ( materialAssetLevel3 - > WasPreFinalized ( ) ) ;
// Now we'll create the material type asset in memory so the materials will have what they need to finalize.
Data : : Asset < MaterialTypeAsset > testMaterialTypeAsset = CreateTestMaterialTypeAsset ( materialTypeAssetId ) ;
auto layout = testMaterialTypeAsset - > GetMaterialPropertiesLayout ( ) ;
MaterialPropertyIndex myFloat = layout - > FindPropertyIndex ( Name ( " general.MyFloat " ) ) ;
MaterialPropertyIndex myFloat2 = layout - > FindPropertyIndex ( Name ( " general.MyFloat2 " ) ) ;
MaterialPropertyIndex myColor = layout - > FindPropertyIndex ( Name ( " general.MyColor " ) ) ;
// The MaterialAsset is still holding an reference to an unloaded asset, so we run it through the serializer which causes the loaded MaterialAsset
// to have access to the testMaterialTypeAsset. This is similar to how the AP would save the MaterialAsset to the cache and the runtime would load it.
SerializeTester < RPI : : MaterialAsset > tester ( GetSerializeContext ( ) ) ;
tester . SerializeOut ( materialAssetLevel1 . Get ( ) ) ;
materialAssetLevel1 = tester . SerializeIn ( Uuid : : CreateRandom ( ) , ObjectStream : : FilterDescriptor { AZ : : Data : : AssetFilterNoAssetLoading } ) ;
tester . SerializeOut ( materialAssetLevel2 . Get ( ) ) ;
materialAssetLevel2 = tester . SerializeIn ( Uuid : : CreateRandom ( ) , ObjectStream : : FilterDescriptor { AZ : : Data : : AssetFilterNoAssetLoading } ) ;
tester . SerializeOut ( materialAssetLevel3 . Get ( ) ) ;
materialAssetLevel3 = tester . SerializeIn ( Uuid : : CreateRandom ( ) , ObjectStream : : FilterDescriptor { AZ : : Data : : AssetFilterNoAssetLoading } ) ;
// The properties will finalize automatically when we call GetPropertyValues()...
AZStd : : array_view < MaterialPropertyValue > properties ;
// Check level 1 properties
properties = materialAssetLevel1 - > GetPropertyValues ( ) ;
EXPECT_EQ ( properties [ myFloat . GetIndex ( ) ] . GetValue < float > ( ) , 1.5f ) ;
EXPECT_EQ ( properties [ myFloat2 . GetIndex ( ) ] . GetValue < Vector2 > ( ) , Vector2 ( 0.0f , 0.0f ) ) ;
EXPECT_EQ ( properties [ myColor . GetIndex ( ) ] . GetValue < Color > ( ) , Color ( 0.1f , 0.2f , 0.3f , 0.4f ) ) ;
// Check level 2 properties
properties = materialAssetLevel2 - > GetPropertyValues ( ) ;
EXPECT_EQ ( properties [ myFloat . GetIndex ( ) ] . GetValue < float > ( ) , 1.5f ) ;
EXPECT_EQ ( properties [ myFloat2 . GetIndex ( ) ] . GetValue < Vector2 > ( ) , Vector2 ( 4.1f , 4.2f ) ) ;
EXPECT_EQ ( properties [ myColor . GetIndex ( ) ] . GetValue < Color > ( ) , Color ( 0.15f , 0.25f , 0.35f , 0.45f ) ) ;
// Check level 3 properties
properties = materialAssetLevel3 - > GetPropertyValues ( ) ;
EXPECT_EQ ( properties [ myFloat . GetIndex ( ) ] . GetValue < float > ( ) , 3.5f ) ;
EXPECT_EQ ( properties [ myFloat2 . GetIndex ( ) ] . GetValue < Vector2 > ( ) , Vector2 ( 4.1f , 4.2f ) ) ;
EXPECT_EQ ( properties [ myColor . GetIndex ( ) ] . GetValue < Color > ( ) , Color ( 0.15f , 0.25f , 0.35f , 0.45f ) ) ;
}
TEST_F ( MaterialSourceDataTests , CreateMaterialAsset_MultiLevelDataInheritance_Error_MaterialTypesDontMatch )
{
@ -604,18 +801,18 @@ namespace UnitTest
sourceDataLevel3 . m_materialType = " @exefolder@/Temp/otherBase.materialtype " ;
sourceDataLevel3 . m_parentMaterial = " level2.material " ;
auto materialAssetLevel1 = sourceDataLevel1 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , true ) ;
auto materialAssetLevel1 = sourceDataLevel1 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : PreBake , true ) ;
EXPECT_TRUE ( materialAssetLevel1 . IsSuccess ( ) ) ;
m_assetSystemStub . RegisterSourceInfo ( " level1.material " , materialAssetLevel1 . GetValue ( ) . GetId ( ) ) ;
auto materialAssetLevel2 = sourceDataLevel2 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , true ) ;
auto materialAssetLevel2 = sourceDataLevel2 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : PreBake , true ) ;
EXPECT_TRUE ( materialAssetLevel2 . IsSuccess ( ) ) ;
m_assetSystemStub . RegisterSourceInfo ( " level2.material " , materialAssetLevel2 . GetValue ( ) . GetId ( ) ) ;
AZ_TEST_START_ASSERTTEST ;
auto materialAssetLevel3 = sourceDataLevel3 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , true ) ;
auto materialAssetLevel3 = sourceDataLevel3 . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : PreBake , true ) ;
AZ_TEST_STOP_ASSERTTEST ( 1 ) ;
EXPECT_FALSE ( materialAssetLevel3 . IsSuccess ( ) ) ;
}
@ -625,7 +822,7 @@ namespace UnitTest
// We use local functions to easily start a new MaterialAssetCreator for each test case because
// the AssetCreator would just skip subsequent operations after the first failure is detected.
auto expectWarning = [ ] ( AZStd : : function < void ( MaterialSourceData & materialSourceData ) > setOneBadInput , [[maybe_unused]] uint32_t expectedAsserts = 1 )
auto expectWarning = [ ] ( const char * expectedErrorMessage , AZStd : : function < void ( MaterialSourceData & materialSourceData ) > setOneBadInput , bool warningOccursBeforeFinalize = false )
{
MaterialSourceData sourceData ;
@ -635,233 +832,69 @@ namespace UnitTest
setOneBadInput ( sourceData ) ;
AZ_TEST_START_ASSERTTEST ;
auto materialAssetOutcome = sourceData . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , true ) ;
AZ_TEST_STOP_ASSERTTEST ( expectedAsserts ) ; // Usually just one for when End() is called
// Check with MaterialAssetProcessingMode::PreBake
{
ErrorMessageFinder errorFinder ;
errorFinder . AddExpectedErrorMessage ( expectedErrorMessage ) ;
errorFinder . AddIgnoredErrorMessage ( " Failed to build " , true ) ;
auto materialAssetOutcome = sourceData . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : PreBake , true ) ;
errorFinder . CheckExpectedErrorsFound ( ) ;
EXPECT_FALSE ( materialAssetOutcome . IsSuccess ( ) ) ;
EXPECT_FALSE ( materialAssetOutcome . IsSuccess ( ) ) ;
}
// Check with MaterialAssetProcessingMode::DeferredBake, no validation occurs because the MaterialTypeAsset cannot be used and so the MaterialAsset is not finalized
if ( ! warningOccursBeforeFinalize )
{
auto materialAssetOutcome = sourceData . CreateMaterialAsset ( Uuid : : CreateRandom ( ) , " " , MaterialAssetProcessingMode : : DeferredBake , true ) ;
EXPECT_TRUE ( materialAssetOutcome . IsSuccess ( ) ) ;
}
} ;
// Test property does not exist...
expectWarning ( [ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , true ) ;
} ) ;
expectWarning ( " \" general.DoesNotExist \" is not found in the material properties layout " ,
[ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , true ) ;
} ) ;
expectWarning ( [ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , - 10 ) ;
} ) ;
expectWarning ( " \" general.DoesNotExist \" is not found in the material properties layout " ,
[ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , - 10 ) ;
} ) ;
expectWarning ( [ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , 25u ) ;
} ) ;
expectWarning ( " \" general.DoesNotExist \" is not found in the material properties layout " ,
[ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , 25u ) ;
} ) ;
expectWarning ( [ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , 1.5f ) ;
} ) ;
expectWarning ( " \" general.DoesNotExist \" is not found in the material properties layout " ,
[ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , 1.5f ) ;
} ) ;
expectWarning ( [ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , AZ : : Color { 0.1f , 0.2f , 0.3f , 0.4f } ) ;
} ) ;
expectWarning ( " \" general.DoesNotExist \" is not found in the material properties layout " ,
[ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , AZ : : Color { 0.1f , 0.2f , 0.3f , 0.4f } ) ;
} ) ;
expectWarning ( [ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , AZStd : : string ( " @exefolder@/Temp/test.streamingimage " ) ) ;
} ) ;
expectWarning ( " \" general.DoesNotExist \" is not found in the material properties layout " ,
[ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " DoesNotExist " , AZStd : : string ( " @exefolder@/Temp/test.streamingimage " ) ) ;
} ) ;
// Missing image reference
expectWarning ( [ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " MyImage " , AZStd : : string ( " doesNotExist.streamingimage " ) ) ;
} ) ;
}
TEST_F ( MaterialSourceDataTests , Load_MaterialTypeVersionUpdate )
{
const AZStd : : string inputJson = R " (
{
" materialType " : " @exefolder@/Temp/test.materialtype " ,
" materialTypeVersion " : 1 ,
" properties " : {
" general " : {
" testColorNameA " : [ 0.1 , 0.2 , 0.3 ]
}
}
}
) " ;
MaterialSourceData material ;
JsonTestResult loadResult = LoadTestDataFromJson ( material , inputJson ) ;
EXPECT_EQ ( AZ : : JsonSerializationResult : : Tasks : : ReadField , loadResult . m_jsonResultCode . GetTask ( ) ) ;
EXPECT_EQ ( AZ : : JsonSerializationResult : : Processing : : Completed , loadResult . m_jsonResultCode . GetProcessing ( ) ) ;
// Initially, the loaded material data will match the .material file exactly. This gives us the accurate representation of
// what's actually saved on disk.
EXPECT_NE ( material . m_properties [ " general " ] . find ( " testColorNameA " ) , material . m_properties [ " general " ] . end ( ) ) ;
EXPECT_EQ ( material . m_properties [ " general " ] . find ( " testColorNameB " ) , material . m_properties [ " general " ] . end ( ) ) ;
EXPECT_EQ ( material . m_properties [ " general " ] . find ( " testColorNameC " ) , material . m_properties [ " general " ] . end ( ) ) ;
EXPECT_EQ ( material . m_properties [ " general " ] . find ( " MyColor " ) , material . m_properties [ " general " ] . end ( ) ) ;
AZ : : Color testColor = material . m_properties [ " general " ] [ " testColorNameA " ] . m_value . GetValue < AZ : : Color > ( ) ;
EXPECT_TRUE ( AZ : : Color ( 0.1f , 0.2f , 0.3f , 1.0f ) . IsClose ( testColor , 0.01 ) ) ;
EXPECT_EQ ( 1 , material . m_materialTypeVersion ) ;
// Then we force the material data to update to the latest material type version specification
ErrorMessageFinder warningFinder ; // Note this finds errors and warnings, and we're looking for a warning.
warningFinder . AddExpectedErrorMessage ( " Automatic updates are available. Consider updating the .material source file " ) ;
warningFinder . AddExpectedErrorMessage ( " This material is based on version '1' " ) ;
warningFinder . AddExpectedErrorMessage ( " material type is now at version '10' " ) ;
material . ApplyVersionUpdates ( ) ;
warningFinder . CheckExpectedErrorsFound ( ) ;
// Now the material data should match the latest material type.
// Look for the property under the latest name in the material type, not the name used in the .material file.
EXPECT_EQ ( material . m_properties [ " general " ] . find ( " testColorNameA " ) , material . m_properties [ " general " ] . end ( ) ) ;
EXPECT_EQ ( material . m_properties [ " general " ] . find ( " testColorNameB " ) , material . m_properties [ " general " ] . end ( ) ) ;
EXPECT_EQ ( material . m_properties [ " general " ] . find ( " testColorNameC " ) , material . m_properties [ " general " ] . end ( ) ) ;
EXPECT_NE ( material . m_properties [ " general " ] . find ( " MyColor " ) , material . m_properties [ " general " ] . end ( ) ) ;
testColor = material . m_properties [ " general " ] [ " MyColor " ] . m_value . GetValue < AZ : : Color > ( ) ;
EXPECT_TRUE ( AZ : : Color ( 0.1f , 0.2f , 0.3f , 1.0f ) . IsClose ( testColor , 0.01 ) ) ;
EXPECT_EQ ( 10 , material . m_materialTypeVersion ) ;
// Calling ApplyVersionUpdates() again should not report the warning again, since the material has already been updated.
warningFinder . Reset ( ) ;
material . ApplyVersionUpdates ( ) ;
}
TEST_F ( MaterialSourceDataTests , Load_MaterialTypeVersionUpdate_MovePropertiesToAnotherGroup )
{
const AZStd : : string inputJson = R " (
{
" materialType " : " @exefolder@/Temp/test.materialtype " ,
" materialTypeVersion " : 3 ,
" properties " : {
" oldGroup " : {
" MyFloat " : 1.2 ,
" MyIntOldName " : 5
}
}
}
) " ;
MaterialSourceData material ;
JsonTestResult loadResult = LoadTestDataFromJson ( material , inputJson ) ;
EXPECT_EQ ( AZ : : JsonSerializationResult : : Tasks : : ReadField , loadResult . m_jsonResultCode . GetTask ( ) ) ;
EXPECT_EQ ( AZ : : JsonSerializationResult : : Processing : : Completed , loadResult . m_jsonResultCode . GetProcessing ( ) ) ;
// Initially, the loaded material data will match the .material file exactly. This gives us the accurate representation of
// what's actually saved on disk.
EXPECT_NE ( material . m_properties [ " oldGroup " ] . find ( " MyFloat " ) , material . m_properties [ " oldGroup " ] . end ( ) ) ;
EXPECT_NE ( material . m_properties [ " oldGroup " ] . find ( " MyIntOldName " ) , material . m_properties [ " oldGroup " ] . end ( ) ) ;
EXPECT_EQ ( material . m_properties [ " general " ] . find ( " MyFloat " ) , material . m_properties [ " general " ] . end ( ) ) ;
EXPECT_EQ ( material . m_properties [ " general " ] . find ( " MyInt " ) , material . m_properties [ " general " ] . end ( ) ) ;
float myFloat = material . m_properties [ " oldGroup " ] [ " MyFloat " ] . m_value . GetValue < float > ( ) ;
EXPECT_EQ ( myFloat , 1.2f ) ;
int32_t myInt = material . m_properties [ " oldGroup " ] [ " MyIntOldName " ] . m_value . GetValue < int32_t > ( ) ;
EXPECT_EQ ( myInt , 5 ) ;
EXPECT_EQ ( 3 , material . m_materialTypeVersion ) ;
// Then we force the material data to update to the latest material type version specification
ErrorMessageFinder warningFinder ; // Note this finds errors and warnings, and we're looking for a warning.
warningFinder . AddExpectedErrorMessage ( " Automatic updates are available. Consider updating the .material source file " ) ;
warningFinder . AddExpectedErrorMessage ( " This material is based on version '3' " ) ;
warningFinder . AddExpectedErrorMessage ( " material type is now at version '10' " ) ;
material . ApplyVersionUpdates ( ) ;
warningFinder . CheckExpectedErrorsFound ( ) ;
// Now the material data should match the latest material type.
// Look for the property under the latest name in the material type, not the name used in the .material file.
EXPECT_EQ ( material . m_properties [ " oldGroup " ] . find ( " MyFloat " ) , material . m_properties [ " oldGroup " ] . end ( ) ) ;
EXPECT_EQ ( material . m_properties [ " oldGroup " ] . find ( " MyIntOldName " ) , material . m_properties [ " oldGroup " ] . end ( ) ) ;
EXPECT_NE ( material . m_properties [ " general " ] . find ( " MyFloat " ) , material . m_properties [ " general " ] . end ( ) ) ;
EXPECT_NE ( material . m_properties [ " general " ] . find ( " MyInt " ) , material . m_properties [ " general " ] . end ( ) ) ;
myFloat = material . m_properties [ " general " ] [ " MyFloat " ] . m_value . GetValue < float > ( ) ;
EXPECT_EQ ( myFloat , 1.2f ) ;
myInt = material . m_properties [ " general " ] [ " MyInt " ] . m_value . GetValue < int32_t > ( ) ;
EXPECT_EQ ( myInt , 5 ) ;
EXPECT_EQ ( 10 , material . m_materialTypeVersion ) ;
// Calling ApplyVersionUpdates() again should not report the warning again, since the material has already been updated.
warningFinder . Reset ( ) ;
material . ApplyVersionUpdates ( ) ;
}
TEST_F ( MaterialSourceDataTests , Load_MaterialTypeVersionPartialUpdate )
{
// This case is similar to Load_MaterialTypeVersionUpdate but we start at a later
// version so only some of the version updates are applied.
const AZStd : : string inputJson = R " (
{
" materialType " : " @exefolder@/Temp/test.materialtype " ,
" materialTypeVersion " : 3 ,
" properties " : {
" general " : {
" testColorNameB " : [ 0.1 , 0.2 , 0.3 ]
}
}
}
) " ;
MaterialSourceData material ;
JsonTestResult loadResult = LoadTestDataFromJson ( material , inputJson ) ;
EXPECT_EQ ( AZ : : JsonSerializationResult : : Tasks : : ReadField , loadResult . m_jsonResultCode . GetTask ( ) ) ;
EXPECT_EQ ( AZ : : JsonSerializationResult : : Processing : : Completed , loadResult . m_jsonResultCode . GetProcessing ( ) ) ;
material . ApplyVersionUpdates ( ) ;
AZ : : Color testColor = material . m_properties [ " general " ] [ " MyColor " ] . m_value . GetValue < AZ : : Color > ( ) ;
EXPECT_TRUE ( AZ : : Color ( 0.1f , 0.2f , 0.3f , 1.0f ) . IsClose ( testColor , 0.01 ) ) ;
EXPECT_EQ ( 10 , material . m_materialTypeVersion ) ;
}
TEST_F ( MaterialSourceDataTests , Load_Error_MaterialTypeVersionUpdateWithMismatchedVersion )
{
const AZStd : : string inputJson = R " (
{
" materialType " : " @exefolder@/Temp/test.materialtype " ,
" materialTypeVersion " : 3 , // At this version, the property should be testColorNameB not testColorNameA
" properties " : {
" general " : {
" testColorNameA " : [ 0.1 , 0.2 , 0.3 ]
}
}
}
) " ;
MaterialSourceData material ;
JsonTestResult loadResult = LoadTestDataFromJson ( material , inputJson ) ;
loadResult . ContainsMessage ( " /properties/general/testColorNameA " , " Property 'general.testColorNameA' not found in material type. " ) ;
EXPECT_FALSE ( material . m_properties [ " general " ] [ " testColorNameA " ] . m_value . IsValid ( ) ) ;
material . ApplyVersionUpdates ( ) ;
EXPECT_FALSE ( material . m_properties [ " general " ] [ " MyColor " ] . m_value . IsValid ( ) ) ;
expectWarning ( " Could not find the image 'doesNotExist.streamingimage' " ,
[ ] ( MaterialSourceData & materialSourceData )
{
AddProperty ( materialSourceData , " general " , " MyImage " , AZStd : : string ( " doesNotExist.streamingimage " ) ) ;
} , true ) ; // In this case, the warning does happen even when the asset is not finalized, because the image path is checked earlier than that
}
}