Formalize renderer material contracts and harden backpack import

This commit is contained in:
2026-04-08 04:27:21 +08:00
parent 7be3b2cc45
commit 6113ed92b0
18 changed files with 534 additions and 326 deletions

View File

@@ -62,6 +62,18 @@ std::filesystem::path ResolveRuntimePath(const char* relativePath) {
return GetExecutableDirectory() / relativePath;
}
void NormalizeBackpackTestMaterials(Mesh& mesh) {
for (Material* material : mesh.GetMaterials()) {
if (material == nullptr) {
continue;
}
MaterialRenderState renderState = material->GetRenderState();
renderState.cullMode = MaterialCullMode::None;
material->SetRenderState(renderState);
}
}
const char* GetScreenshotFilename(RHIType backendType) {
switch (backendType) {
case RHIType::D3D12:
@@ -179,6 +191,7 @@ void BackpackSceneTest::LoadBackpackMesh() {
ASSERT_TRUE(mMesh->IsValid());
ASSERT_GT(mMesh->GetVertexCount(), 0u);
ASSERT_GT(mMesh->GetSections().Size(), 0u);
NormalizeBackpackTestMaterials(*mMesh);
}
void BackpackSceneTest::BuildScene() {

View File

@@ -9,6 +9,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -19,10 +20,13 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -66,6 +70,18 @@ std::filesystem::path ResolveRuntimePath(const char* relativePath) {
return GetExecutableDirectory() / relativePath;
}
void NormalizeBackpackTestMaterials(Mesh& mesh) {
for (Material* material : mesh.GetMaterials()) {
if (material == nullptr) {
continue;
}
MaterialRenderState renderState = material->GetRenderState();
renderState.cullMode = MaterialCullMode::None;
material->SetRenderState(renderState);
}
}
Mesh* CreateQuadMesh() {
auto* mesh = new Mesh();
IResource::ConstructParams params = {};
@@ -84,7 +100,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -137,7 +153,8 @@ Material* CreateQuadMaterial(Texture* texture) {
params.path = "Tests/Rendering/CameraStackQuad.material";
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
return material;
}
@@ -189,6 +206,13 @@ void CameraStackSceneTest::SetUp() {
mQuadMesh = CreateQuadMesh();
mQuadTexture = CreateCheckerTexture();
mQuadMaterial = CreateQuadMaterial(mQuadTexture);
ASSERT_NE(mQuadMaterial, nullptr);
ASSERT_NE(mQuadMaterial->GetShader(), nullptr);
ASSERT_TRUE(mQuadMaterial->HasProperty("_BaseColor"));
ASSERT_TRUE(mQuadMaterial->HasProperty("_MainTex"));
ASSERT_EQ(mQuadMaterial->GetTexture("_MainTex").Get(), mQuadTexture);
ASSERT_EQ(ResolveBuiltinBaseColorTexture(mQuadMaterial), mQuadTexture);
ASSERT_TRUE(ResolveSchemaMaterialConstantPayload(mQuadMaterial).IsValid());
BuildScene();
TextureDesc depthDesc = {};
@@ -270,6 +294,14 @@ void CameraStackSceneTest::LoadBackpackMesh() {
ASSERT_TRUE(mBackpackMesh->IsValid());
ASSERT_GT(mBackpackMesh->GetVertexCount(), 0u);
ASSERT_GT(mBackpackMesh->GetSections().Size(), 0u);
ASSERT_GT(mBackpackMesh->GetMaterials().Size(), 0u);
NormalizeBackpackTestMaterials(*mBackpackMesh);
for (Material* material : mBackpackMesh->GetMaterials()) {
ASSERT_NE(material, nullptr);
ASSERT_NE(material->GetShader(), nullptr);
ASSERT_TRUE(ResolveSchemaMaterialConstantPayload(material).IsValid());
}
}
void CameraStackSceneTest::BuildScene() {

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -110,7 +113,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(MaterialRenderQueue::Geometry);
material->SetRenderState(renderState);
return material;

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -61,7 +64,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -111,7 +114,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(renderQueue);
material->SetRenderState(renderState);
return material;

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -112,7 +115,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(renderQueue);
material->SetRenderState(renderState);
return material;

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -61,7 +64,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -111,7 +114,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(renderQueue);
material->SetRenderState(renderState);
return material;

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Vector2.h>
@@ -15,8 +16,11 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Rendering/Materials/RenderMaterialResolve.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -61,7 +65,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -114,7 +118,8 @@ Material* CreateMaterial(Texture* texture) {
params.path = "Tests/Rendering/Quad.material";
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
return material;
}
@@ -162,6 +167,14 @@ void TexturedQuadSceneTest::SetUp() {
mMesh = CreateQuadMesh();
mTexture = CreateCheckerTexture();
mMaterial = CreateMaterial(mTexture);
ASSERT_NE(mMaterial, nullptr);
ASSERT_NE(mMaterial->GetShader(), nullptr);
ASSERT_TRUE(mMaterial->HasProperty("_BaseColor"));
ASSERT_TRUE(mMaterial->HasProperty("_Cutoff"));
ASSERT_TRUE(mMaterial->HasProperty("_MainTex"));
ASSERT_EQ(mMaterial->GetTexture("_MainTex").Get(), mTexture);
ASSERT_EQ(ResolveBuiltinBaseColorTexture(mMaterial), mTexture);
ASSERT_TRUE(ResolveSchemaMaterialConstantPayload(mMaterial).IsValid());
GameObject* cameraObject = mScene->CreateGameObject("MainCamera");
auto* camera = cameraObject->AddComponent<CameraComponent>();

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Quaternion.h>
@@ -16,8 +17,10 @@
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
@@ -61,7 +64,7 @@ Mesh* CreateQuadMesh() {
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
@@ -111,7 +114,8 @@ Material* CreateMaterial(
params.path = path;
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
material->SetRenderQueue(renderQueue);
material->SetRenderState(renderState);
return material;

View File

@@ -54,7 +54,7 @@ Mesh* CreateSectionedTestMesh(const char* path, std::initializer_list<uint32_t>
return mesh;
}
Material* CreateTestMaterial(const char* path, int32_t renderQueue, const char* legacyShaderPassHint = nullptr, const char* lightMode = nullptr) {
Material* CreateTestMaterial(const char* path, int32_t renderQueue) {
auto* material = new Material();
IResource::ConstructParams params = {};
params.name = "TestMaterial";
@@ -62,12 +62,6 @@ Material* CreateTestMaterial(const char* path, int32_t renderQueue, const char*
params.guid = ResourceGUID::Generate(path);
material->Initialize(params);
material->SetRenderQueue(renderQueue);
if (legacyShaderPassHint != nullptr) {
material->SetLegacyShaderPassHint(legacyShaderPassHint);
}
if (lightMode != nullptr) {
material->SetTag("LightMode", lightMode);
}
return material;
}
@@ -331,14 +325,10 @@ TEST(RenderSceneExtractor_Test, ExtractsSectionLevelVisibleItemsAndSortsByRender
Mesh* mesh = CreateSectionedTestMesh("Meshes/sectioned.mesh", { 1u, 0u });
Material* opaqueMaterial = CreateTestMaterial(
"Materials/opaque.mat",
static_cast<int32_t>(MaterialRenderQueue::Geometry),
"ForwardLit",
"ForwardBase");
static_cast<int32_t>(MaterialRenderQueue::Geometry));
Material* transparentMaterial = CreateTestMaterial(
"Materials/transparent.mat",
static_cast<int32_t>(MaterialRenderQueue::Transparent),
"ForwardLit",
"ForwardBase");
static_cast<int32_t>(MaterialRenderQueue::Transparent));
meshFilter->SetMesh(mesh);
meshRenderer->SetMaterial(0, opaqueMaterial);
@@ -382,7 +372,7 @@ TEST(RenderSceneExtractor_Test, SortsOpaqueFrontToBackAndTransparentBackToFront)
auto* meshFilter = object->AddComponent<MeshFilterComponent>();
auto* meshRenderer = object->AddComponent<MeshRendererComponent>();
Mesh* mesh = CreateTestMesh(name);
Material* material = CreateTestMaterial(name, renderQueue, "ForwardLit", "ForwardBase");
Material* material = CreateTestMaterial(name, renderQueue);
meshFilter->SetMesh(mesh);
meshRenderer->SetMaterial(0, material);
return mesh;
@@ -440,9 +430,7 @@ TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasN
Mesh* mesh = CreateSectionedTestMesh("Meshes/embedded.mesh", { 0u });
Material* embeddedMaterial = CreateTestMaterial(
"Materials/embedded.mat",
static_cast<int32_t>(MaterialRenderQueue::Transparent),
"ForwardLit",
"ForwardBase");
static_cast<int32_t>(MaterialRenderQueue::Transparent));
mesh->AddMaterial(embeddedMaterial);
meshFilter->SetMesh(mesh);
meshRenderer->ClearMaterials();
@@ -459,38 +447,36 @@ TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasN
delete mesh;
}
TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsCanDriveBuiltinPassMatching) {
Material forwardMaterial;
forwardMaterial.SetLegacyShaderPassHint("ForwardLit");
forwardMaterial.SetTag("LightMode", "ForwardBase");
EXPECT_TRUE(MatchesBuiltinPass(&forwardMaterial, BuiltinMaterialPass::ForwardLit));
TEST(RenderMaterialUtility_Test, MaterialsWithoutShaderMetadataUseImplicitForwardFallback) {
Material noMetadataMaterial;
EXPECT_TRUE(MatchesBuiltinPass(&noMetadataMaterial, BuiltinMaterialPass::ForwardLit));
Material shadowMaterial;
shadowMaterial.SetLegacyShaderPassHint("ShadowCaster");
EXPECT_FALSE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::ForwardLit));
EXPECT_TRUE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::ShadowCaster));
Material depthOnlyMaterial;
depthOnlyMaterial.SetTag("LightMode", "DepthOnly");
EXPECT_FALSE(MatchesBuiltinPass(&depthOnlyMaterial, BuiltinMaterialPass::ForwardLit));
}
TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsSupportUnlitDepthAndObjectId) {
TEST(RenderMaterialUtility_Test, ShaderPassMetadataSupportsUnlitDepthAndObjectId) {
Material unlitMaterial;
unlitMaterial.SetLegacyShaderPassHint("Unlit");
auto* unlitShader = new Shader();
ShaderPass unlitPass = {};
unlitPass.name = "Unlit";
unlitShader->AddPass(unlitPass);
unlitMaterial.SetShader(ResourceHandle<Shader>(unlitShader));
EXPECT_TRUE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::Unlit));
EXPECT_FALSE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::ForwardLit));
Material depthMaterial;
depthMaterial.SetTag("LightMode", "DepthOnly");
auto* depthShader = new Shader();
ShaderPass depthPass = {};
depthPass.name = "DepthOnly";
depthShader->AddPass(depthPass);
depthMaterial.SetShader(ResourceHandle<Shader>(depthShader));
EXPECT_TRUE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::DepthOnly));
EXPECT_FALSE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::Unlit));
Material objectIdMaterial;
objectIdMaterial.SetLegacyShaderPassHint("ObjectId");
auto* objectIdShader = new Shader();
ShaderPass objectIdPass = {};
objectIdPass.name = "ObjectId";
objectIdShader->AddPass(objectIdPass);
objectIdMaterial.SetShader(ResourceHandle<Shader>(objectIdShader));
EXPECT_TRUE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ObjectId));
EXPECT_FALSE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ForwardLit));
}
@@ -525,7 +511,7 @@ TEST(RenderMaterialUtility_Test, ExplicitShaderPassMetadataDisablesImplicitForwa
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
}
TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingLegacyMaterialPassHints) {
TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingMaterialTags) {
Material material;
auto* shader = new Shader();
@@ -534,7 +520,6 @@ TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingLegacyMateria
shader->AddPass(forwardPass);
material.SetShader(ResourceHandle<Shader>(shader));
material.SetLegacyShaderPassHint("ShadowCaster");
material.SetTag("LightMode", "DepthOnly");
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
@@ -542,7 +527,7 @@ TEST(RenderMaterialUtility_Test, ShaderMetadataOverridesConflictingLegacyMateria
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::DepthOnly));
}
TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsRemainAvailableForShadersWithoutBuiltinMetadata) {
TEST(RenderMaterialUtility_Test, MaterialTagsDoNotOverrideImplicitForwardFallbackWithoutShaderMetadata) {
Material material;
auto* shader = new Shader();
@@ -551,10 +536,10 @@ TEST(RenderMaterialUtility_Test, LegacyMaterialPassHintsRemainAvailableForShader
shader->AddPass(defaultPass);
material.SetShader(ResourceHandle<Shader>(shader));
material.SetLegacyShaderPassHint("ShadowCaster");
material.SetTag("LightMode", "ShadowCaster");
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ShadowCaster));
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ShadowCaster));
}
TEST(RenderMaterialUtility_Test, ShadersWithoutBuiltinMetadataKeepImplicitForwardFallback) {
@@ -571,28 +556,24 @@ TEST(RenderMaterialUtility_Test, ShadersWithoutBuiltinMetadataKeepImplicitForwar
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
}
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromCanonicalNamesAndAliases) {
Material canonicalMaterial;
canonicalMaterial.SetFloat4("baseColor", Vector4(0.2f, 0.4f, 0.6f, 0.8f));
canonicalMaterial.SetFloat("_Cutoff", 0.3f);
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&canonicalMaterial), Vector4(0.2f, 0.4f, 0.6f, 0.8f));
EXPECT_FLOAT_EQ(ResolveBuiltinAlphaCutoff(&canonicalMaterial), 0.3f);
TEST(RenderMaterialUtility_Test, MaterialsWithoutFormalShaderMetadataResolveBuiltinDefaultsOnly) {
Material material;
material.SetFloat4("baseColor", Vector4(0.2f, 0.4f, 0.6f, 0.8f));
material.SetFloat("_Cutoff", 0.3f);
material.SetFloat("opacity", 0.35f);
Material aliasMaterial;
aliasMaterial.SetFloat4("_BaseColor", Vector4(0.7f, 0.6f, 0.5f, 0.4f));
aliasMaterial.SetFloat("_Cutoff", 0.42f);
Texture* baseColorTexture = new Texture();
IResource::ConstructParams textureParams = {};
textureParams.name = "AliasBaseColor";
textureParams.path = "Textures/alias_base_color.texture";
textureParams.guid = ResourceGUID::Generate(textureParams.path);
baseColorTexture->Initialize(textureParams);
aliasMaterial.SetTexture("_BaseColorTexture", ResourceHandle<Texture>(baseColorTexture));
material.SetTexture("_BaseColorTexture", ResourceHandle<Texture>(baseColorTexture));
const BuiltinForwardMaterialData materialData = BuildBuiltinForwardMaterialData(&aliasMaterial);
EXPECT_EQ(materialData.baseColorFactor, Vector4(0.7f, 0.6f, 0.5f, 0.4f));
EXPECT_FLOAT_EQ(materialData.alphaCutoff, 0.42f);
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&aliasMaterial), baseColorTexture);
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
EXPECT_FLOAT_EQ(ResolveBuiltinAlphaCutoff(&material), 0.5f);
EXPECT_EQ(ResolveBuiltinBaseColorTexture(&material), nullptr);
EXPECT_FALSE(ResolveSchemaMaterialConstantPayload(&material).IsValid());
}
TEST(RenderMaterialUtility_Test, ResolvesBuiltinForwardMaterialContractFromShaderSemanticMetadata) {
@@ -708,13 +689,13 @@ TEST(RenderMaterialUtility_Test, ExposesSchemaDrivenMaterialConstantPayload) {
EXPECT_FLOAT_EQ(cutoffValues[0], 0.6f);
}
TEST(RenderMaterialUtility_Test, UsesOpacityOnlyWhenBaseColorFactorIsMissing) {
TEST(RenderMaterialUtility_Test, DoesNotUseOpacityFallbackWithoutFormalShaderSemanticMetadata) {
Material material;
material.SetFloat("opacity", 0.35f);
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 0.35f));
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
material.SetFloat4("baseColor", Vector4(0.9f, 0.8f, 0.7f, 0.6f));
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(0.9f, 0.8f, 0.7f, 0.6f));
EXPECT_EQ(ResolveBuiltinBaseColorFactor(&material), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
}
TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
@@ -732,6 +713,20 @@ TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
renderState.depthTestEnable = true;
renderState.depthWriteEnable = false;
renderState.depthFunc = MaterialComparisonFunc::LessEqual;
renderState.depthBiasFactor = 1.25f;
renderState.depthBiasUnits = 4;
renderState.stencil.enabled = true;
renderState.stencil.reference = 9;
renderState.stencil.readMask = 0x3F;
renderState.stencil.writeMask = 0x1F;
renderState.stencil.front.func = MaterialComparisonFunc::Equal;
renderState.stencil.front.failOp = MaterialStencilOp::Replace;
renderState.stencil.front.passOp = MaterialStencilOp::IncrWrap;
renderState.stencil.front.depthFailOp = MaterialStencilOp::DecrSat;
renderState.stencil.back.func = MaterialComparisonFunc::NotEqual;
renderState.stencil.back.failOp = MaterialStencilOp::Invert;
renderState.stencil.back.passOp = MaterialStencilOp::DecrWrap;
renderState.stencil.back.depthFailOp = MaterialStencilOp::Zero;
material.SetRenderState(renderState);
const MaterialRenderState effectiveRenderState = ResolveEffectiveRenderState(nullptr, &material);
@@ -741,6 +736,8 @@ TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
EXPECT_EQ(rasterizerState.cullMode, static_cast<uint32_t>(XCEngine::RHI::CullMode::Back));
EXPECT_EQ(rasterizerState.frontFace, static_cast<uint32_t>(XCEngine::RHI::FrontFace::CounterClockwise));
EXPECT_FLOAT_EQ(rasterizerState.slopeScaledDepthBias, 1.25f);
EXPECT_EQ(rasterizerState.depthBias, 4);
EXPECT_TRUE(blendState.blendEnable);
EXPECT_EQ(blendState.srcBlend, static_cast<uint32_t>(XCEngine::RHI::BlendFactor::SrcAlpha));
@@ -754,6 +751,31 @@ TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {
EXPECT_TRUE(depthStencilState.depthTestEnable);
EXPECT_FALSE(depthStencilState.depthWriteEnable);
EXPECT_EQ(depthStencilState.depthFunc, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::LessEqual));
EXPECT_TRUE(depthStencilState.stencilEnable);
EXPECT_EQ(depthStencilState.stencilReadMask, 0x3F);
EXPECT_EQ(depthStencilState.stencilWriteMask, 0x1F);
EXPECT_EQ(depthStencilState.front.func, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::Equal));
EXPECT_EQ(depthStencilState.front.failOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Replace));
EXPECT_EQ(depthStencilState.front.passOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Incr));
EXPECT_EQ(depthStencilState.front.depthFailOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::DecrSat));
EXPECT_EQ(depthStencilState.back.func, static_cast<uint32_t>(XCEngine::RHI::ComparisonFunc::NotEqual));
EXPECT_EQ(depthStencilState.back.failOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Invert));
EXPECT_EQ(depthStencilState.back.passOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Decr));
EXPECT_EQ(depthStencilState.back.depthFailOp, static_cast<uint32_t>(XCEngine::RHI::StencilOp::Zero));
}
TEST(RenderMaterialUtility_Test, PipelineStateKeyStripsDynamicStencilReference) {
MaterialRenderState renderState = {};
renderState.stencil.enabled = true;
renderState.stencil.reference = 12;
renderState.stencil.front.func = MaterialComparisonFunc::Always;
renderState.stencil.back.func = MaterialComparisonFunc::Always;
const MaterialRenderState keyState = BuildStaticPipelineRenderStateKey(renderState);
EXPECT_TRUE(keyState.stencil.enabled);
EXPECT_EQ(keyState.stencil.reference, 0u);
EXPECT_EQ(keyState.stencil.front.func, MaterialComparisonFunc::Always);
EXPECT_EQ(keyState.stencil.back.func, MaterialComparisonFunc::Always);
}
TEST(RenderMaterialUtility_Test, ShaderPassFixedFunctionStateIsUsedBeforeLegacyMaterialOverride) {