Formalize material schema and constant layout contract
This commit is contained in:
@@ -12,6 +12,35 @@ using namespace XCEngine::Math;
|
||||
|
||||
namespace {
|
||||
|
||||
Shader* CreateMaterialSchemaShader() {
|
||||
auto* shader = new Shader();
|
||||
|
||||
ShaderPropertyDesc baseColor = {};
|
||||
baseColor.name = "_BaseColor";
|
||||
baseColor.displayName = "Base Color";
|
||||
baseColor.type = ShaderPropertyType::Color;
|
||||
baseColor.defaultValue = "(1.0,0.5,0.25,1.0)";
|
||||
baseColor.semantic = "BaseColor";
|
||||
shader->AddProperty(baseColor);
|
||||
|
||||
ShaderPropertyDesc metallic = {};
|
||||
metallic.name = "_Metallic";
|
||||
metallic.displayName = "Metallic";
|
||||
metallic.type = ShaderPropertyType::Float;
|
||||
metallic.defaultValue = "0.7";
|
||||
shader->AddProperty(metallic);
|
||||
|
||||
ShaderPropertyDesc baseMap = {};
|
||||
baseMap.name = "_MainTex";
|
||||
baseMap.displayName = "Base Map";
|
||||
baseMap.type = ShaderPropertyType::Texture2D;
|
||||
baseMap.defaultValue = "white";
|
||||
baseMap.semantic = "BaseColorTexture";
|
||||
shader->AddProperty(baseMap);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
TEST(Material, DefaultConstructor) {
|
||||
Material material;
|
||||
EXPECT_EQ(material.GetType(), ResourceType::Material);
|
||||
@@ -225,6 +254,28 @@ TEST(Material, SetTextureReplacesExistingBinding) {
|
||||
EXPECT_EQ(material.GetTexture("uDiffuse").Get(), secondTexture);
|
||||
}
|
||||
|
||||
TEST(Material, SetTextureAssetRefStoresStableBindingMetadata) {
|
||||
Material material;
|
||||
|
||||
AssetRef textureRef;
|
||||
textureRef.assetGuid = AssetGUID(1, 2);
|
||||
textureRef.localID = kMainAssetLocalID;
|
||||
textureRef.resourceType = ResourceType::Texture;
|
||||
|
||||
material.SetTextureAssetRef("uDiffuse", textureRef, "Assets/diffuse.bmp");
|
||||
|
||||
ASSERT_EQ(material.GetTextureBindingCount(), 1u);
|
||||
EXPECT_TRUE(material.HasProperty("uDiffuse"));
|
||||
EXPECT_EQ(material.GetTextureBindingName(0), "uDiffuse");
|
||||
EXPECT_EQ(material.GetTextureBindingPath(0), "Assets/diffuse.bmp");
|
||||
|
||||
const AssetRef storedRef = material.GetTextureBindingAssetRef(0);
|
||||
EXPECT_EQ(storedRef.assetGuid, textureRef.assetGuid);
|
||||
EXPECT_EQ(storedRef.localID, textureRef.localID);
|
||||
EXPECT_EQ(storedRef.resourceType, textureRef.resourceType);
|
||||
EXPECT_FALSE(material.GetTextureBindingLoadedTexture(0).IsValid());
|
||||
}
|
||||
|
||||
TEST(Material, ChangeVersionIncrementsWhenMaterialMutates) {
|
||||
Material material;
|
||||
const XCEngine::Core::uint64 initialVersion = material.GetChangeVersion();
|
||||
@@ -243,6 +294,24 @@ TEST(Material, UpdateConstantBufferPacksNumericPropertiesIntoStableSlots) {
|
||||
material.SetFloat4("beta", Vector4(1.0f, 2.0f, 3.0f, 4.0f));
|
||||
material.SetInt("gamma", 7);
|
||||
|
||||
const auto& constantLayout = material.GetConstantLayout();
|
||||
ASSERT_EQ(constantLayout.Size(), 3u);
|
||||
EXPECT_EQ(constantLayout[0].name, "alpha");
|
||||
EXPECT_EQ(constantLayout[0].offset, 0u);
|
||||
EXPECT_EQ(constantLayout[0].size, 4u);
|
||||
EXPECT_EQ(constantLayout[0].alignedSize, 16u);
|
||||
EXPECT_EQ(constantLayout[1].name, "beta");
|
||||
EXPECT_EQ(constantLayout[1].offset, 16u);
|
||||
EXPECT_EQ(constantLayout[1].size, 16u);
|
||||
EXPECT_EQ(constantLayout[2].name, "gamma");
|
||||
EXPECT_EQ(constantLayout[2].offset, 32u);
|
||||
EXPECT_EQ(constantLayout[2].size, 4u);
|
||||
|
||||
const MaterialConstantFieldDesc* betaField = material.FindConstantField("beta");
|
||||
ASSERT_NE(betaField, nullptr);
|
||||
EXPECT_EQ(betaField->offset, 16u);
|
||||
EXPECT_EQ(betaField->alignedSize, 16u);
|
||||
|
||||
const auto& constantBufferData = material.GetConstantBufferData();
|
||||
ASSERT_EQ(constantBufferData.Size(), 48u);
|
||||
|
||||
@@ -331,4 +400,138 @@ TEST(Material, ClearAllProperties) {
|
||||
EXPECT_FALSE(material.HasProperty("uIndex"));
|
||||
}
|
||||
|
||||
TEST(Material, SetShaderSeedsDefaultsAndRemovePropertyRestoresShaderDefault) {
|
||||
Material material;
|
||||
Shader* shader = CreateMaterialSchemaShader();
|
||||
|
||||
material.SetShader(ResourceHandle<Shader>(shader));
|
||||
|
||||
EXPECT_TRUE(material.HasProperty("_BaseColor"));
|
||||
EXPECT_TRUE(material.HasProperty("_Metallic"));
|
||||
EXPECT_TRUE(material.HasProperty("_MainTex"));
|
||||
EXPECT_EQ(material.GetFloat4("_BaseColor"), Vector4(1.0f, 0.5f, 0.25f, 1.0f));
|
||||
EXPECT_FLOAT_EQ(material.GetFloat("_Metallic"), 0.7f);
|
||||
EXPECT_EQ(material.GetTextureBindingCount(), 0u);
|
||||
|
||||
material.SetFloat4("_BaseColor", Vector4(0.2f, 0.3f, 0.4f, 0.5f));
|
||||
EXPECT_EQ(material.GetFloat4("_BaseColor"), Vector4(0.2f, 0.3f, 0.4f, 0.5f));
|
||||
|
||||
material.RemoveProperty("_BaseColor");
|
||||
EXPECT_EQ(material.GetFloat4("_BaseColor"), Vector4(1.0f, 0.5f, 0.25f, 1.0f));
|
||||
}
|
||||
|
||||
TEST(Material, ClearAllPropertiesWithShaderRestoresSchemaDefaults) {
|
||||
Material material;
|
||||
Shader* shader = CreateMaterialSchemaShader();
|
||||
|
||||
material.SetShader(ResourceHandle<Shader>(shader));
|
||||
material.SetFloat("_Metallic", 0.15f);
|
||||
material.SetTexture("_MainTex", ResourceHandle<Texture>(new Texture()));
|
||||
ASSERT_EQ(material.GetTextureBindingCount(), 1u);
|
||||
|
||||
material.ClearAllProperties();
|
||||
|
||||
EXPECT_TRUE(material.HasProperty("_BaseColor"));
|
||||
EXPECT_TRUE(material.HasProperty("_Metallic"));
|
||||
EXPECT_TRUE(material.HasProperty("_MainTex"));
|
||||
EXPECT_EQ(material.GetFloat4("_BaseColor"), Vector4(1.0f, 0.5f, 0.25f, 1.0f));
|
||||
EXPECT_FLOAT_EQ(material.GetFloat("_Metallic"), 0.7f);
|
||||
EXPECT_EQ(material.GetTextureBindingCount(), 0u);
|
||||
}
|
||||
|
||||
TEST(Material, ShaderSchemaRejectsUnknownAndTypeMismatchedAssignments) {
|
||||
Material material;
|
||||
Shader* shader = CreateMaterialSchemaShader();
|
||||
|
||||
material.SetShader(ResourceHandle<Shader>(shader));
|
||||
const Vector4 defaultBaseColor = material.GetFloat4("_BaseColor");
|
||||
const float defaultMetallic = material.GetFloat("_Metallic");
|
||||
|
||||
material.SetFloat("_BaseColor", 0.1f);
|
||||
material.SetFloat4("_Metallic", Vector4(1.0f, 2.0f, 3.0f, 4.0f));
|
||||
material.SetFloat("UnknownProperty", 5.0f);
|
||||
|
||||
EXPECT_EQ(material.GetFloat4("_BaseColor"), defaultBaseColor);
|
||||
EXPECT_FLOAT_EQ(material.GetFloat("_Metallic"), defaultMetallic);
|
||||
EXPECT_FALSE(material.HasProperty("UnknownProperty"));
|
||||
}
|
||||
|
||||
TEST(Material, SwitchingShaderResyncsPropertiesAgainstNewSchema) {
|
||||
Material material;
|
||||
|
||||
auto* shaderA = new Shader();
|
||||
ShaderPropertyDesc sharedA = {};
|
||||
sharedA.name = "Shared";
|
||||
sharedA.type = ShaderPropertyType::Float;
|
||||
sharedA.defaultValue = "1.0";
|
||||
shaderA->AddProperty(sharedA);
|
||||
ShaderPropertyDesc onlyA = {};
|
||||
onlyA.name = "OnlyA";
|
||||
onlyA.type = ShaderPropertyType::Float;
|
||||
onlyA.defaultValue = "2.0";
|
||||
shaderA->AddProperty(onlyA);
|
||||
|
||||
auto* shaderB = new Shader();
|
||||
ShaderPropertyDesc sharedB = {};
|
||||
sharedB.name = "Shared";
|
||||
sharedB.type = ShaderPropertyType::Float;
|
||||
sharedB.defaultValue = "4.0";
|
||||
shaderB->AddProperty(sharedB);
|
||||
ShaderPropertyDesc onlyB = {};
|
||||
onlyB.name = "OnlyB";
|
||||
onlyB.type = ShaderPropertyType::Color;
|
||||
onlyB.defaultValue = "(0.1,0.2,0.3,0.4)";
|
||||
shaderB->AddProperty(onlyB);
|
||||
|
||||
material.SetFloat("Legacy", 9.0f);
|
||||
material.SetShader(ResourceHandle<Shader>(shaderA));
|
||||
material.SetFloat("Shared", 5.0f);
|
||||
material.SetFloat("OnlyA", 8.0f);
|
||||
|
||||
material.SetShader(ResourceHandle<Shader>(shaderB));
|
||||
|
||||
EXPECT_FALSE(material.HasProperty("Legacy"));
|
||||
EXPECT_FALSE(material.HasProperty("OnlyA"));
|
||||
EXPECT_TRUE(material.HasProperty("Shared"));
|
||||
EXPECT_TRUE(material.HasProperty("OnlyB"));
|
||||
EXPECT_FLOAT_EQ(material.GetFloat("Shared"), 5.0f);
|
||||
EXPECT_EQ(material.GetFloat4("OnlyB"), Vector4(0.1f, 0.2f, 0.3f, 0.4f));
|
||||
}
|
||||
|
||||
TEST(Material, UpdateConstantBufferFollowsShaderSchemaOrderInsteadOfAlphabeticalOrder) {
|
||||
Material material;
|
||||
|
||||
auto* shader = new Shader();
|
||||
ShaderPropertyDesc beta = {};
|
||||
beta.name = "beta";
|
||||
beta.type = ShaderPropertyType::Float;
|
||||
beta.defaultValue = "0.0";
|
||||
shader->AddProperty(beta);
|
||||
|
||||
ShaderPropertyDesc alpha = {};
|
||||
alpha.name = "alpha";
|
||||
alpha.type = ShaderPropertyType::Float;
|
||||
alpha.defaultValue = "0.0";
|
||||
shader->AddProperty(alpha);
|
||||
|
||||
material.SetShader(ResourceHandle<Shader>(shader));
|
||||
material.SetFloat("alpha", 10.0f);
|
||||
material.SetFloat("beta", 20.0f);
|
||||
|
||||
const auto& constantLayout = material.GetConstantLayout();
|
||||
ASSERT_EQ(constantLayout.Size(), 2u);
|
||||
EXPECT_EQ(constantLayout[0].name, "beta");
|
||||
EXPECT_EQ(constantLayout[0].offset, 0u);
|
||||
EXPECT_EQ(constantLayout[1].name, "alpha");
|
||||
EXPECT_EQ(constantLayout[1].offset, 16u);
|
||||
|
||||
const auto& constantBufferData = material.GetConstantBufferData();
|
||||
ASSERT_EQ(constantBufferData.Size(), 32u);
|
||||
|
||||
const float* firstSlot = reinterpret_cast<const float*>(constantBufferData.Data());
|
||||
const float* secondSlot = reinterpret_cast<const float*>(constantBufferData.Data() + 16);
|
||||
EXPECT_FLOAT_EQ(firstSlot[0], 20.0f);
|
||||
EXPECT_FLOAT_EQ(secondSlot[0], 10.0f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user