refactor: formalize builtin pass matching

This commit is contained in:
2026-04-02 16:26:20 +08:00
parent 70ced2d91f
commit 33f16597fa
3 changed files with 181 additions and 20 deletions

View File

@@ -11,11 +11,20 @@ namespace XCEngine {
namespace Rendering {
enum class BuiltinMaterialPass : Core::uint32 {
Forward = 0
ForwardLit = 0,
Unlit,
DepthOnly,
ShadowCaster,
ObjectId,
Forward = ForwardLit
};
inline Containers::String NormalizeBuiltinPassMetadataValue(const Containers::String& value) {
return value.Trim().ToLower();
}
inline bool IsForwardPassName(const Containers::String& value) {
const Containers::String normalized = value.Trim().ToLower();
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
return normalized.Empty() ||
normalized == Containers::String("forward") ||
normalized == Containers::String("forwardbase") ||
@@ -23,6 +32,92 @@ inline bool IsForwardPassName(const Containers::String& value) {
normalized == Containers::String("forwardonly");
}
inline bool IsUnlitPassName(const Containers::String& value) {
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
return normalized == Containers::String("unlit") ||
normalized == Containers::String("forwardunlit") ||
normalized == Containers::String("srpdefaultunlit");
}
inline bool IsDepthOnlyPassName(const Containers::String& value) {
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
return normalized == Containers::String("depthonly") ||
normalized == Containers::String("depth");
}
inline bool IsShadowCasterPassName(const Containers::String& value) {
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
return normalized == Containers::String("shadowcaster") ||
normalized == Containers::String("shadow");
}
inline bool IsObjectIdPassName(const Containers::String& value) {
const Containers::String normalized = value.Trim().ToLower();
return normalized == Containers::String("objectid") ||
normalized == Containers::String("editorobjectid");
}
inline bool MatchesBuiltinPassName(
const Containers::String& value,
BuiltinMaterialPass pass) {
switch (pass) {
case BuiltinMaterialPass::ForwardLit:
return IsForwardPassName(value);
case BuiltinMaterialPass::Unlit:
return IsUnlitPassName(value);
case BuiltinMaterialPass::DepthOnly:
return IsDepthOnlyPassName(value);
case BuiltinMaterialPass::ShadowCaster:
return IsShadowCasterPassName(value);
case BuiltinMaterialPass::ObjectId:
return IsObjectIdPassName(value);
default:
return false;
}
}
inline bool ShaderPassHasExplicitBuiltinMetadata(const Resources::ShaderPass& shaderPass) {
if (!shaderPass.name.Empty() &&
shaderPass.name != Containers::String("Default")) {
return true;
}
for (const Resources::ShaderPassTagEntry& tag : shaderPass.tags) {
if (NormalizeBuiltinPassMetadataValue(tag.name) == Containers::String("lightmode")) {
return true;
}
}
return false;
}
inline bool ShaderPassMatchesBuiltinPass(
const Resources::ShaderPass& shaderPass,
BuiltinMaterialPass pass) {
bool hasMetadata = false;
if (!shaderPass.name.Empty() &&
shaderPass.name != Containers::String("Default")) {
hasMetadata = true;
if (!MatchesBuiltinPassName(shaderPass.name, pass)) {
return false;
}
}
for (const Resources::ShaderPassTagEntry& tag : shaderPass.tags) {
if (NormalizeBuiltinPassMetadataValue(tag.name) != Containers::String("lightmode")) {
continue;
}
hasMetadata = true;
if (!MatchesBuiltinPassName(tag.value, pass)) {
return false;
}
}
return hasMetadata;
}
inline const Resources::Material* ResolveMaterial(
const Components::MeshRendererComponent* meshRenderer,
const Resources::Mesh* mesh,
@@ -72,28 +167,46 @@ inline bool IsTransparentRenderQueue(Core::int32 renderQueue) {
inline bool MatchesBuiltinPass(const Resources::Material* material, BuiltinMaterialPass pass) {
if (material == nullptr) {
return true;
return pass == BuiltinMaterialPass::ForwardLit;
}
switch (pass) {
case BuiltinMaterialPass::Forward: {
const Containers::String shaderPass = material->GetShaderPass();
if (!shaderPass.Empty() && !IsForwardPassName(shaderPass)) {
return false;
}
const Containers::String lightMode = material->GetTag("LightMode");
if (!lightMode.Empty() && !IsForwardPassName(lightMode)) {
const bool hasMaterialShaderPass = !NormalizeBuiltinPassMetadataValue(shaderPass).Empty();
const bool hasMaterialLightMode = !NormalizeBuiltinPassMetadataValue(lightMode).Empty();
if (hasMaterialShaderPass || hasMaterialLightMode) {
if (hasMaterialShaderPass &&
!MatchesBuiltinPassName(shaderPass, pass)) {
return false;
}
if (hasMaterialLightMode &&
!MatchesBuiltinPassName(lightMode, pass)) {
return false;
}
return true;
}
const Resources::Shader* shader = material->GetShader();
if (shader != nullptr) {
bool shaderHasExplicitBuiltinMetadata = false;
for (const Resources::ShaderPass& shaderPassEntry : shader->GetPasses()) {
if (ShaderPassMatchesBuiltinPass(shaderPassEntry, pass)) {
return true;
}
default:
return true;
if (ShaderPassHasExplicitBuiltinMetadata(shaderPassEntry)) {
shaderHasExplicitBuiltinMetadata = true;
}
}
if (shaderHasExplicitBuiltinMetadata) {
return false;
}
}
return pass == BuiltinMaterialPass::ForwardLit;
}
inline RHI::CullMode ToRHICullMode(Resources::MaterialCullMode mode) {
switch (mode) {
case Resources::MaterialCullMode::Front:

View File

@@ -439,7 +439,7 @@ bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(const RenderPassContext& p
RHI::RHIPipelineState* currentPipelineState = nullptr;
for (const VisibleRenderItem& visibleItem : sceneData.visibleItems) {
const Resources::Material* material = ResolveMaterial(visibleItem);
if (!MatchesBuiltinPass(material, BuiltinMaterialPass::Forward)) {
if (!MatchesBuiltinPass(material, BuiltinMaterialPass::ForwardLit)) {
continue;
}

View File

@@ -13,6 +13,7 @@
#include <XCEngine/Rendering/RenderSceneExtractor.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Scene/Scene.h>
using namespace XCEngine::Components;
@@ -359,22 +360,69 @@ TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasN
delete mesh;
}
TEST(RenderMaterialUtility_Test, MatchesBuiltinForwardPassMetadata) {
TEST(RenderMaterialUtility_Test, MatchesBuiltinForwardLitPassMetadata) {
Material forwardMaterial;
forwardMaterial.SetShaderPass("ForwardLit");
forwardMaterial.SetTag("LightMode", "ForwardBase");
EXPECT_TRUE(MatchesBuiltinPass(&forwardMaterial, BuiltinMaterialPass::Forward));
EXPECT_TRUE(MatchesBuiltinPass(&forwardMaterial, BuiltinMaterialPass::ForwardLit));
Material noMetadataMaterial;
EXPECT_TRUE(MatchesBuiltinPass(&noMetadataMaterial, BuiltinMaterialPass::Forward));
EXPECT_TRUE(MatchesBuiltinPass(&noMetadataMaterial, BuiltinMaterialPass::ForwardLit));
Material shadowMaterial;
shadowMaterial.SetShaderPass("ShadowCaster");
EXPECT_FALSE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::Forward));
EXPECT_FALSE(MatchesBuiltinPass(&shadowMaterial, BuiltinMaterialPass::ForwardLit));
Material depthOnlyMaterial;
depthOnlyMaterial.SetTag("LightMode", "DepthOnly");
EXPECT_FALSE(MatchesBuiltinPass(&depthOnlyMaterial, BuiltinMaterialPass::Forward));
EXPECT_FALSE(MatchesBuiltinPass(&depthOnlyMaterial, BuiltinMaterialPass::ForwardLit));
}
TEST(RenderMaterialUtility_Test, MatchesBuiltinUnlitDepthAndObjectIdPassMetadata) {
Material unlitMaterial;
unlitMaterial.SetShaderPass("Unlit");
EXPECT_TRUE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::Unlit));
EXPECT_FALSE(MatchesBuiltinPass(&unlitMaterial, BuiltinMaterialPass::ForwardLit));
Material depthMaterial;
depthMaterial.SetTag("LightMode", "DepthOnly");
EXPECT_TRUE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::DepthOnly));
EXPECT_FALSE(MatchesBuiltinPass(&depthMaterial, BuiltinMaterialPass::Unlit));
Material objectIdMaterial;
objectIdMaterial.SetShaderPass("ObjectId");
EXPECT_TRUE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ObjectId));
EXPECT_FALSE(MatchesBuiltinPass(&objectIdMaterial, BuiltinMaterialPass::ForwardLit));
}
TEST(RenderMaterialUtility_Test, ShaderPassMetadataCanDriveBuiltinPassMatching) {
Material material;
auto* shader = new Shader();
ShaderPass forwardPass = {};
forwardPass.name = "ForwardLit";
shader->AddPass(forwardPass);
ResourceHandle<Shader> shaderHandle(shader);
material.SetShader(shaderHandle);
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
}
TEST(RenderMaterialUtility_Test, ExplicitShaderPassMetadataDisablesImplicitForwardFallback) {
Material material;
auto* shader = new Shader();
ShaderPass unlitPass = {};
unlitPass.name = "Unlit";
shader->AddPass(unlitPass);
ResourceHandle<Shader> shaderHandle(shader);
material.SetShader(shaderHandle);
EXPECT_FALSE(MatchesBuiltinPass(&material, BuiltinMaterialPass::ForwardLit));
EXPECT_TRUE(MatchesBuiltinPass(&material, BuiltinMaterialPass::Unlit));
}
TEST(RenderMaterialUtility_Test, MapsMaterialRenderStateToRhiDescriptors) {