Formalize material schema and constant layout contract
This commit is contained in:
@@ -479,6 +479,55 @@ TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShade
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), texture);
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShaderSemanticDefaults) {
|
||||
auto* shader = new Shader();
|
||||
|
||||
ShaderPropertyDesc colorProperty = {};
|
||||
colorProperty.name = "TintColor";
|
||||
colorProperty.displayName = "Tint";
|
||||
colorProperty.type = ShaderPropertyType::Color;
|
||||
colorProperty.defaultValue = "(0.11,0.22,0.33,0.44)";
|
||||
colorProperty.semantic = "BaseColor";
|
||||
shader->AddProperty(colorProperty);
|
||||
|
||||
Material material;
|
||||
material.SetShader(ResourceHandle<Shader>(shader));
|
||||
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(0.11f, 0.22f, 0.33f, 0.44f));
|
||||
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), nullptr);
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, ExposesSchemaDrivenMaterialConstantPayload) {
|
||||
auto* shader = new Shader();
|
||||
|
||||
ShaderPropertyDesc colorProperty = {};
|
||||
colorProperty.name = "_BaseColor";
|
||||
colorProperty.type = ShaderPropertyType::Color;
|
||||
colorProperty.defaultValue = "(0.25,0.5,0.75,1.0)";
|
||||
colorProperty.semantic = "BaseColor";
|
||||
shader->AddProperty(colorProperty);
|
||||
|
||||
Material material;
|
||||
material.SetShader(ResourceHandle<Shader>(shader));
|
||||
|
||||
const MaterialConstantPayloadView payload = ResolveSchemaMaterialConstantPayload(&material);
|
||||
ASSERT_TRUE(payload.IsValid());
|
||||
ASSERT_EQ(payload.size, 16u);
|
||||
ASSERT_TRUE(payload.layout.IsValid());
|
||||
ASSERT_EQ(payload.layout.count, 1u);
|
||||
EXPECT_EQ(payload.layout.size, 16u);
|
||||
EXPECT_EQ(payload.layout.fields[0].name, "_BaseColor");
|
||||
EXPECT_EQ(payload.layout.fields[0].offset, 0u);
|
||||
EXPECT_EQ(payload.layout.fields[0].size, 16u);
|
||||
EXPECT_EQ(payload.layout.fields[0].alignedSize, 16u);
|
||||
|
||||
const float* values = static_cast<const float*>(payload.data);
|
||||
EXPECT_FLOAT_EQ(values[0], 0.25f);
|
||||
EXPECT_FLOAT_EQ(values[1], 0.5f);
|
||||
EXPECT_FLOAT_EQ(values[2], 0.75f);
|
||||
EXPECT_FLOAT_EQ(values[3], 1.0f);
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, UsesOpacityOnlyWhenBaseColorFactorIsMissing) {
|
||||
Material material;
|
||||
material.SetFloat("opacity", 0.35f);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -66,6 +66,66 @@ void WriteTextFile(const std::filesystem::path& path, const std::string& content
|
||||
ASSERT_TRUE(static_cast<bool>(output));
|
||||
}
|
||||
|
||||
std::filesystem::path WriteSchemaMaterialShaderManifest(const std::filesystem::path& rootPath) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path shaderDir = rootPath / "Shaders";
|
||||
fs::create_directories(shaderDir);
|
||||
|
||||
WriteTextFile(shaderDir / "schema.vert.glsl", "#version 430\nvoid main() {}\n");
|
||||
WriteTextFile(shaderDir / "schema.frag.glsl", "#version 430\nvoid main() {}\n");
|
||||
|
||||
const fs::path manifestPath = shaderDir / "schema.shader";
|
||||
std::ofstream manifest(manifestPath, std::ios::binary | std::ios::trunc);
|
||||
EXPECT_TRUE(manifest.is_open());
|
||||
if (!manifest.is_open()) {
|
||||
return {};
|
||||
}
|
||||
manifest << "{\n";
|
||||
manifest << " \"name\": \"SchemaMaterialShader\",\n";
|
||||
manifest << " \"properties\": [\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"_BaseColor\",\n";
|
||||
manifest << " \"displayName\": \"Base Color\",\n";
|
||||
manifest << " \"type\": \"Color\",\n";
|
||||
manifest << " \"defaultValue\": \"(1,0.5,0.25,1)\",\n";
|
||||
manifest << " \"semantic\": \"BaseColor\"\n";
|
||||
manifest << " },\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"_Metallic\",\n";
|
||||
manifest << " \"displayName\": \"Metallic\",\n";
|
||||
manifest << " \"type\": \"Float\",\n";
|
||||
manifest << " \"defaultValue\": \"0.7\"\n";
|
||||
manifest << " },\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"_Mode\",\n";
|
||||
manifest << " \"displayName\": \"Mode\",\n";
|
||||
manifest << " \"type\": \"Int\",\n";
|
||||
manifest << " \"defaultValue\": \"2\"\n";
|
||||
manifest << " },\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"_MainTex\",\n";
|
||||
manifest << " \"displayName\": \"Main Tex\",\n";
|
||||
manifest << " \"type\": \"Texture2D\",\n";
|
||||
manifest << " \"defaultValue\": \"white\",\n";
|
||||
manifest << " \"semantic\": \"BaseColorTexture\"\n";
|
||||
manifest << " }\n";
|
||||
manifest << " ],\n";
|
||||
manifest << " \"passes\": [\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"ForwardLit\",\n";
|
||||
manifest << " \"variants\": [\n";
|
||||
manifest << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"schema.vert.glsl\" },\n";
|
||||
manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"schema.frag.glsl\" }\n";
|
||||
manifest << " ]\n";
|
||||
manifest << " }\n";
|
||||
manifest << " ]\n";
|
||||
manifest << "}\n";
|
||||
EXPECT_TRUE(static_cast<bool>(manifest));
|
||||
|
||||
return manifestPath;
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, GetResourceType) {
|
||||
MaterialLoader loader;
|
||||
EXPECT_EQ(loader.GetResourceType(), ResourceType::Material);
|
||||
@@ -235,6 +295,135 @@ TEST(MaterialLoader, LoadMaterialWithShaderManifestResolvesShaderPass) {
|
||||
fs::remove_all(shaderRoot);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_override_test";
|
||||
fs::remove_all(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "override.material";
|
||||
|
||||
WriteTextFile(
|
||||
materialPath,
|
||||
"{\n"
|
||||
" \"shader\": \"" + shaderPath.generic_string() + "\",\n"
|
||||
" \"shaderPass\": \"ForwardLit\",\n"
|
||||
" \"properties\": {\n"
|
||||
" \"_BaseColor\": [0.2, 0.4, 0.6, 0.8],\n"
|
||||
" \"_Metallic\": 0.15,\n"
|
||||
" \"_Mode\": 5\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
|
||||
MaterialLoader loader;
|
||||
LoadResult result = loader.Load(materialPath.generic_string().c_str());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* material = static_cast<Material*>(result.resource);
|
||||
ASSERT_NE(material, nullptr);
|
||||
ASSERT_NE(material->GetShader(), nullptr);
|
||||
EXPECT_EQ(material->GetShaderPass(), "ForwardLit");
|
||||
EXPECT_EQ(material->GetFloat4("_BaseColor"), XCEngine::Math::Vector4(0.2f, 0.4f, 0.6f, 0.8f));
|
||||
EXPECT_FLOAT_EQ(material->GetFloat("_Metallic"), 0.15f);
|
||||
EXPECT_EQ(material->GetInt("_Mode"), 5);
|
||||
EXPECT_TRUE(material->HasProperty("_MainTex"));
|
||||
EXPECT_EQ(material->GetTextureBindingCount(), 0u);
|
||||
|
||||
delete material;
|
||||
fs::remove_all(rootPath);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, RejectsUnknownPropertyAgainstShaderSchema) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_unknown_test";
|
||||
fs::remove_all(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "unknown_property.material";
|
||||
|
||||
WriteTextFile(
|
||||
materialPath,
|
||||
"{\n"
|
||||
" \"shader\": \"" + shaderPath.generic_string() + "\",\n"
|
||||
" \"properties\": {\n"
|
||||
" \"_Unknown\": 1.0\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
|
||||
MaterialLoader loader;
|
||||
LoadResult result = loader.Load(materialPath.generic_string().c_str());
|
||||
EXPECT_FALSE(result);
|
||||
|
||||
fs::remove_all(rootPath);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, RejectsTypeMismatchAgainstShaderSchema) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_mismatch_test";
|
||||
fs::remove_all(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "type_mismatch.material";
|
||||
|
||||
WriteTextFile(
|
||||
materialPath,
|
||||
"{\n"
|
||||
" \"shader\": \"" + shaderPath.generic_string() + "\",\n"
|
||||
" \"properties\": {\n"
|
||||
" \"_BaseColor\": 1.0\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
|
||||
MaterialLoader loader;
|
||||
LoadResult result = loader.Load(materialPath.generic_string().c_str());
|
||||
EXPECT_FALSE(result);
|
||||
|
||||
fs::remove_all(rootPath);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, LoadMaterialWithPropertiesObjectPreservesShaderDefaultsForOmittedValues) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_defaults_test";
|
||||
fs::remove_all(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "defaults.material";
|
||||
|
||||
WriteTextFile(
|
||||
materialPath,
|
||||
"{\n"
|
||||
" \"shader\": \"" + shaderPath.generic_string() + "\",\n"
|
||||
" \"properties\": {\n"
|
||||
" \"_Metallic\": 0.33\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
|
||||
MaterialLoader loader;
|
||||
LoadResult result = loader.Load(materialPath.generic_string().c_str());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* material = static_cast<Material*>(result.resource);
|
||||
ASSERT_NE(material, nullptr);
|
||||
EXPECT_EQ(material->GetFloat4("_BaseColor"), XCEngine::Math::Vector4(1.0f, 0.5f, 0.25f, 1.0f));
|
||||
EXPECT_FLOAT_EQ(material->GetFloat("_Metallic"), 0.33f);
|
||||
EXPECT_EQ(material->GetInt("_Mode"), 2);
|
||||
EXPECT_TRUE(material->HasProperty("_MainTex"));
|
||||
EXPECT_EQ(material->GetTextureBindingCount(), 0u);
|
||||
|
||||
delete material;
|
||||
fs::remove_all(rootPath);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, RejectsUnknownRenderQueueName) {
|
||||
const std::filesystem::path materialPath =
|
||||
std::filesystem::current_path() / "material_loader_invalid_queue.material";
|
||||
@@ -380,6 +569,7 @@ TEST(MaterialLoader, ResourceManagerLoadsProjectMaterialTextureAsLazyDependency)
|
||||
ASSERT_TRUE(materialHandle.IsValid());
|
||||
ASSERT_EQ(materialHandle->GetTextureBindingCount(), 1u);
|
||||
EXPECT_EQ(materialHandle->GetTextureBindingName(0), "baseColorTexture");
|
||||
EXPECT_TRUE(materialHandle->GetTextureBindingAssetRef(0).IsValid());
|
||||
EXPECT_EQ(
|
||||
fs::path(materialHandle->GetTextureBindingPath(0).CStr()).lexically_normal().generic_string(),
|
||||
(projectRoot / "Assets" / "checker.bmp").lexically_normal().generic_string());
|
||||
@@ -542,6 +732,9 @@ TEST(MaterialLoader, LoadMaterialArtifactDefersTexturePayloadUntilRequested) {
|
||||
assetsDir / "checker.bmp",
|
||||
fs::copy_options::overwrite_existing);
|
||||
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
manager.RefreshProjectAssets();
|
||||
|
||||
{
|
||||
std::ofstream output(materialArtifactPath, std::ios::binary | std::ios::trunc);
|
||||
ASSERT_TRUE(output.is_open());
|
||||
@@ -558,11 +751,18 @@ TEST(MaterialLoader, LoadMaterialArtifactDefersTexturePayloadUntilRequested) {
|
||||
header.textureBindingCount = 1;
|
||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
|
||||
WriteArtifactString(output, "baseColorTexture");
|
||||
WriteArtifactString(output, "Assets/checker.bmp");
|
||||
}
|
||||
AssetRef textureRef;
|
||||
ASSERT_TRUE(manager.TryGetAssetRef("Assets/checker.bmp", ResourceType::Texture, textureRef));
|
||||
ASSERT_TRUE(textureRef.IsValid());
|
||||
const String encodedTextureRef =
|
||||
textureRef.assetGuid.ToString() + "," +
|
||||
String(std::to_string(textureRef.localID).c_str()) + "," +
|
||||
String(std::to_string(static_cast<int>(textureRef.resourceType)).c_str());
|
||||
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
WriteArtifactString(output, "baseColorTexture");
|
||||
WriteArtifactString(output, encodedTextureRef);
|
||||
WriteArtifactString(output, "");
|
||||
}
|
||||
|
||||
MaterialLoader loader;
|
||||
LoadResult result = loader.Load("Library/Manual/test.xcmat");
|
||||
@@ -572,9 +772,8 @@ TEST(MaterialLoader, LoadMaterialArtifactDefersTexturePayloadUntilRequested) {
|
||||
auto* material = static_cast<Material*>(result.resource);
|
||||
ASSERT_NE(material, nullptr);
|
||||
EXPECT_EQ(material->GetTextureBindingCount(), 1u);
|
||||
EXPECT_EQ(
|
||||
fs::path(material->GetTextureBindingPath(0).CStr()).lexically_normal().generic_string(),
|
||||
(projectRoot / "Assets" / "checker.bmp").lexically_normal().generic_string());
|
||||
EXPECT_TRUE(material->GetTextureBindingAssetRef(0).IsValid());
|
||||
EXPECT_TRUE(material->GetTextureBindingPath(0).Empty());
|
||||
|
||||
const ResourceHandle<Texture> initialTexture = material->GetTexture("baseColorTexture");
|
||||
EXPECT_FALSE(initialTexture.IsValid());
|
||||
@@ -585,6 +784,9 @@ TEST(MaterialLoader, LoadMaterialArtifactDefersTexturePayloadUntilRequested) {
|
||||
ASSERT_TRUE(loadedTexture.IsValid());
|
||||
EXPECT_EQ(loadedTexture->GetWidth(), 2u);
|
||||
EXPECT_EQ(loadedTexture->GetHeight(), 2u);
|
||||
EXPECT_EQ(
|
||||
fs::path(material->GetTextureBindingPath(0).CStr()).lexically_normal().generic_string(),
|
||||
fs::path("Assets/checker.bmp").lexically_normal().generic_string());
|
||||
|
||||
delete material;
|
||||
manager.SetResourceRoot("");
|
||||
|
||||
Reference in New Issue
Block a user