Files
XCEngine/tests/Resources/Material/test_material.cpp

538 lines
18 KiB
C++

#include <gtest/gtest.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Core/Math/Vector2.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Vector4.h>
using namespace XCEngine::Resources;
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);
EXPECT_FALSE(material.IsValid());
EXPECT_EQ(material.GetMemorySize(), 0u);
}
TEST(Material, GetType) {
Material material;
EXPECT_EQ(material.GetType(), ResourceType::Material);
}
TEST(Material, SetGetShader) {
Material material;
Shader* shader = new Shader();
ResourceHandle<Shader> handle(shader);
material.SetShader(handle);
EXPECT_EQ(material.GetShader(), shader);
}
TEST(Material, DefaultRenderMetadata) {
Material material;
EXPECT_EQ(material.GetRenderQueue(), static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Geometry));
EXPECT_EQ(material.GetRenderState().cullMode, MaterialCullMode::None);
EXPECT_FALSE(material.GetRenderState().blendEnable);
EXPECT_TRUE(material.GetRenderState().depthTestEnable);
EXPECT_TRUE(material.GetRenderState().depthWriteEnable);
EXPECT_EQ(material.GetRenderState().depthFunc, MaterialComparisonFunc::Less);
EXPECT_TRUE(material.GetShaderPass().Empty());
EXPECT_EQ(material.GetTagCount(), 0u);
}
TEST(Material, SetGetRenderQueue) {
Material material;
material.SetRenderQueue(MaterialRenderQueue::Transparent);
EXPECT_EQ(material.GetRenderQueue(), static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Transparent));
material.SetRenderQueue(2600);
EXPECT_EQ(material.GetRenderQueue(), 2600);
}
TEST(Material, SetGetRenderState) {
Material material;
MaterialRenderState renderState;
renderState.cullMode = MaterialCullMode::Back;
renderState.blendEnable = true;
renderState.srcBlend = MaterialBlendFactor::SrcAlpha;
renderState.dstBlend = MaterialBlendFactor::InvSrcAlpha;
renderState.srcBlendAlpha = MaterialBlendFactor::One;
renderState.dstBlendAlpha = MaterialBlendFactor::InvSrcAlpha;
renderState.blendOp = MaterialBlendOp::Add;
renderState.blendOpAlpha = MaterialBlendOp::Add;
renderState.depthTestEnable = true;
renderState.depthWriteEnable = false;
renderState.depthFunc = MaterialComparisonFunc::LessEqual;
renderState.colorWriteMask = 0x7;
material.SetRenderState(renderState);
const MaterialRenderState& result = material.GetRenderState();
EXPECT_EQ(result.cullMode, MaterialCullMode::Back);
EXPECT_TRUE(result.blendEnable);
EXPECT_EQ(result.srcBlend, MaterialBlendFactor::SrcAlpha);
EXPECT_EQ(result.dstBlend, MaterialBlendFactor::InvSrcAlpha);
EXPECT_EQ(result.srcBlendAlpha, MaterialBlendFactor::One);
EXPECT_EQ(result.dstBlendAlpha, MaterialBlendFactor::InvSrcAlpha);
EXPECT_EQ(result.blendOp, MaterialBlendOp::Add);
EXPECT_EQ(result.blendOpAlpha, MaterialBlendOp::Add);
EXPECT_TRUE(result.depthTestEnable);
EXPECT_FALSE(result.depthWriteEnable);
EXPECT_EQ(result.depthFunc, MaterialComparisonFunc::LessEqual);
EXPECT_EQ(result.colorWriteMask, 0x7);
}
TEST(Material, SetGetShaderPass) {
Material material;
material.SetShaderPass("ForwardLit");
EXPECT_EQ(material.GetShaderPass(), "ForwardLit");
}
TEST(Material, SetGetTags) {
Material material;
material.SetTag("LightMode", "ForwardBase");
material.SetTag("RenderType", "Opaque");
EXPECT_TRUE(material.HasTag("LightMode"));
EXPECT_EQ(material.GetTag("LightMode"), "ForwardBase");
EXPECT_EQ(material.GetTag("RenderType"), "Opaque");
EXPECT_EQ(material.GetTagCount(), 2u);
}
TEST(Material, SetTagReplacesExistingValue) {
Material material;
material.SetTag("LightMode", "ForwardBase");
material.SetTag("LightMode", "ShadowCaster");
EXPECT_EQ(material.GetTagCount(), 1u);
EXPECT_EQ(material.GetTag("LightMode"), "ShadowCaster");
}
TEST(Material, RemoveTag) {
Material material;
material.SetTag("LightMode", "ForwardBase");
EXPECT_TRUE(material.HasTag("LightMode"));
material.RemoveTag("LightMode");
EXPECT_FALSE(material.HasTag("LightMode"));
EXPECT_TRUE(material.GetTag("LightMode").Empty());
}
TEST(Material, ClearTags) {
Material material;
material.SetTag("LightMode", "ForwardBase");
material.SetTag("RenderType", "Opaque");
ASSERT_EQ(material.GetTagCount(), 2u);
material.ClearTags();
EXPECT_EQ(material.GetTagCount(), 0u);
EXPECT_FALSE(material.HasTag("LightMode"));
EXPECT_FALSE(material.HasTag("RenderType"));
}
TEST(Material, SetGetFloat) {
Material material;
material.SetFloat("uTime", 1.5f);
EXPECT_FLOAT_EQ(material.GetFloat("uTime"), 1.5f);
}
TEST(Material, SetGetFloat2) {
Material material;
Vector2 value(1.0f, 2.0f);
material.SetFloat2("uUV", value);
Vector2 result = material.GetFloat2("uUV");
EXPECT_FLOAT_EQ(result.x, 1.0f);
EXPECT_FLOAT_EQ(result.y, 2.0f);
}
TEST(Material, SetGetFloat3) {
Material material;
Vector3 value(1.0f, 2.0f, 3.0f);
material.SetFloat3("uPosition", value);
Vector3 result = material.GetFloat3("uPosition");
EXPECT_FLOAT_EQ(result.x, 1.0f);
EXPECT_FLOAT_EQ(result.y, 2.0f);
EXPECT_FLOAT_EQ(result.z, 3.0f);
}
TEST(Material, SetGetFloat4) {
Material material;
Vector4 value(1.0f, 2.0f, 3.0f, 4.0f);
material.SetFloat4("uColor", value);
Vector4 result = material.GetFloat4("uColor");
EXPECT_FLOAT_EQ(result.x, 1.0f);
EXPECT_FLOAT_EQ(result.y, 2.0f);
EXPECT_FLOAT_EQ(result.z, 3.0f);
EXPECT_FLOAT_EQ(result.w, 4.0f);
}
TEST(Material, SetGetInt) {
Material material;
material.SetInt("uIndex", 42);
EXPECT_EQ(material.GetInt("uIndex"), 42);
}
TEST(Material, SetGetBool) {
Material material;
material.SetBool("uEnabled", true);
EXPECT_TRUE(material.GetBool("uEnabled"));
material.SetBool("uEnabled", false);
EXPECT_FALSE(material.GetBool("uEnabled"));
}
TEST(Material, SetGetTexture) {
Material material;
Texture* texture = new Texture();
ResourceHandle<Texture> handle(texture);
material.SetTexture("uDiffuse", handle);
EXPECT_EQ(material.GetTexture("uDiffuse").Get(), texture);
}
TEST(Material, SetTextureReplacesExistingBinding) {
Material material;
Texture* firstTexture = new Texture();
Texture* secondTexture = new Texture();
material.SetTexture("uDiffuse", ResourceHandle<Texture>(firstTexture));
material.SetTexture("uDiffuse", ResourceHandle<Texture>(secondTexture));
EXPECT_EQ(material.GetTextureBindingCount(), 1u);
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();
material.SetFloat("uTime", 1.0f);
const XCEngine::Core::uint64 afterFloatVersion = material.GetChangeVersion();
EXPECT_GT(afterFloatVersion, initialVersion);
material.SetTexture("uDiffuse", ResourceHandle<Texture>(new Texture()));
EXPECT_GT(material.GetChangeVersion(), afterFloatVersion);
}
TEST(Material, UpdateConstantBufferPacksNumericPropertiesIntoStableSlots) {
Material material;
material.SetFloat("alpha", 3.5f);
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);
const float* alphaSlot = reinterpret_cast<const float*>(constantBufferData.Data());
EXPECT_FLOAT_EQ(alphaSlot[0], 3.5f);
EXPECT_FLOAT_EQ(alphaSlot[1], 0.0f);
EXPECT_FLOAT_EQ(alphaSlot[2], 0.0f);
EXPECT_FLOAT_EQ(alphaSlot[3], 0.0f);
const float* betaSlot = reinterpret_cast<const float*>(constantBufferData.Data() + 16);
EXPECT_FLOAT_EQ(betaSlot[0], 1.0f);
EXPECT_FLOAT_EQ(betaSlot[1], 2.0f);
EXPECT_FLOAT_EQ(betaSlot[2], 3.0f);
EXPECT_FLOAT_EQ(betaSlot[3], 4.0f);
const XCEngine::Core::int32* gammaSlot =
reinterpret_cast<const XCEngine::Core::int32*>(constantBufferData.Data() + 32);
EXPECT_EQ(gammaSlot[0], 7);
EXPECT_EQ(gammaSlot[1], 0);
EXPECT_EQ(gammaSlot[2], 0);
EXPECT_EQ(gammaSlot[3], 0);
}
TEST(Material, RemoveTexturePropertyAlsoRemovesTextureBinding) {
Material material;
Texture* texture = new Texture();
material.SetTexture("uDiffuse", ResourceHandle<Texture>(texture));
ASSERT_EQ(material.GetTextureBindingCount(), 1u);
material.RemoveProperty("uDiffuse");
EXPECT_FALSE(material.HasProperty("uDiffuse"));
EXPECT_EQ(material.GetTextureBindingCount(), 0u);
EXPECT_EQ(material.GetTexture("uDiffuse").Get(), nullptr);
}
TEST(Material, ReplacingTexturePropertyWithScalarRemovesTextureBinding) {
Material material;
Texture* texture = new Texture();
material.SetTexture("uMain", ResourceHandle<Texture>(texture));
ASSERT_EQ(material.GetTextureBindingCount(), 1u);
material.SetFloat("uMain", 2.0f);
EXPECT_EQ(material.GetTextureBindingCount(), 0u);
EXPECT_EQ(material.GetTexture("uMain").Get(), nullptr);
EXPECT_FLOAT_EQ(material.GetFloat("uMain"), 2.0f);
}
TEST(Material, HasProperty) {
Material material;
EXPECT_FALSE(material.HasProperty("uTime"));
material.SetFloat("uTime", 1.0f);
EXPECT_TRUE(material.HasProperty("uTime"));
}
TEST(Material, RemoveProperty) {
Material material;
material.SetFloat("uTime", 1.0f);
EXPECT_TRUE(material.HasProperty("uTime"));
material.RemoveProperty("uTime");
EXPECT_FALSE(material.HasProperty("uTime"));
}
TEST(Material, ClearAllProperties) {
Material material;
material.SetFloat("uTime", 1.0f);
material.SetFloat3("uPosition", Vector3(1.0f, 2.0f, 3.0f));
material.SetInt("uIndex", 1);
EXPECT_TRUE(material.HasProperty("uTime"));
EXPECT_TRUE(material.HasProperty("uPosition"));
EXPECT_TRUE(material.HasProperty("uIndex"));
material.ClearAllProperties();
EXPECT_FALSE(material.HasProperty("uTime"));
EXPECT_FALSE(material.HasProperty("uPosition"));
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