Formalize renderer material contracts and harden backpack import
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user