resources: expand legacy shader keyword variants

This commit is contained in:
2026-04-06 20:09:28 +08:00
parent 2a61f0b20a
commit 0761079b4c
2 changed files with 241 additions and 13 deletions

View File

@@ -442,6 +442,125 @@ TEST(ShaderLoader, LoadShaderManifestParsesVariantKeywordsAndKeywordAwareLookup)
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadLegacyBackendSplitShaderAuthoringExpandsKeywordVariantsPerBackend) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_legacy_keyword_variants";
const fs::path stageRoot = shaderRoot / "stages";
const fs::path shaderPath = shaderRoot / "keyword_variants.shader";
fs::remove_all(shaderRoot);
fs::create_directories(stageRoot);
WriteTextFile(
stageRoot / "keyword_test.vs.hlsl",
"float4 MainVS() : SV_POSITION { return 0; } // LEGACY_KEYWORD_D3D12_VS\n");
WriteTextFile(
stageRoot / "keyword_test.ps.hlsl",
"float4 MainPS() : SV_TARGET { return 1; } // LEGACY_KEYWORD_D3D12_PS\n");
WriteTextFile(
stageRoot / "keyword_test.vert.glsl",
"#version 430\n// LEGACY_KEYWORD_GL_VS\nvoid main() {}\n");
WriteTextFile(
stageRoot / "keyword_test.frag.glsl",
"#version 430\n// LEGACY_KEYWORD_GL_PS\nvoid main() {}\n");
WriteTextFile(
shaderPath,
R"(Shader "LegacyKeywordVariants"
{
SubShader
{
Pass
{
Name "ForwardLit"
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
#pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS
#pragma shader_feature_local _ XC_ALPHA_TEST
#pragma backend D3D12 HLSL "stages/keyword_test.vs.hlsl" "stages/keyword_test.ps.hlsl" vs_5_0 ps_5_0
#pragma backend OpenGL GLSL "stages/keyword_test.vert.glsl" "stages/keyword_test.frag.glsl"
ENDHLSL
}
}
}
)");
ShaderLoader loader;
LoadResult result = loader.Load(shaderPath.string().c_str());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
ASSERT_TRUE(shader->IsValid());
EXPECT_EQ(shader->GetName(), "LegacyKeywordVariants");
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->keywordDeclarations.Size(), 2u);
EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::MultiCompile);
EXPECT_EQ(pass->keywordDeclarations[1].type, ShaderKeywordDeclarationType::ShaderFeatureLocal);
EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_MAIN_LIGHT_SHADOWS"));
EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_ALPHA_TEST"));
ASSERT_EQ(pass->variants.Size(), 16u);
const ShaderStageVariant* baseD3D12Fragment =
shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12);
ASSERT_NE(baseD3D12Fragment, nullptr);
EXPECT_EQ(baseD3D12Fragment->requiredKeywords.enabledKeywords.Size(), 0u);
EXPECT_NE(
std::string(baseD3D12Fragment->sourceCode.CStr()).find("LEGACY_KEYWORD_D3D12_PS"),
std::string::npos);
EXPECT_EQ(
std::string(baseD3D12Fragment->sourceCode.CStr()).find("#define XC_MAIN_LIGHT_SHADOWS 1"),
std::string::npos);
ShaderKeywordSet enabledKeywords = {};
enabledKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST");
enabledKeywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS");
const ShaderStageVariant* keywordD3D12Fragment =
shader->FindVariant(
"ForwardLit",
ShaderType::Fragment,
ShaderBackend::D3D12,
enabledKeywords);
ASSERT_NE(keywordD3D12Fragment, nullptr);
ASSERT_EQ(keywordD3D12Fragment->requiredKeywords.enabledKeywords.Size(), 2u);
EXPECT_EQ(keywordD3D12Fragment->requiredKeywords.enabledKeywords[0], "XC_ALPHA_TEST");
EXPECT_EQ(keywordD3D12Fragment->requiredKeywords.enabledKeywords[1], "XC_MAIN_LIGHT_SHADOWS");
EXPECT_NE(
std::string(keywordD3D12Fragment->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"),
std::string::npos);
EXPECT_NE(
std::string(keywordD3D12Fragment->sourceCode.CStr()).find("#define XC_MAIN_LIGHT_SHADOWS 1"),
std::string::npos);
EXPECT_NE(
std::string(keywordD3D12Fragment->sourceCode.CStr()).find("LEGACY_KEYWORD_D3D12_PS"),
std::string::npos);
const ShaderStageVariant* keywordOpenGLFragment =
shader->FindVariant(
"ForwardLit",
ShaderType::Fragment,
ShaderBackend::OpenGL,
enabledKeywords);
ASSERT_NE(keywordOpenGLFragment, nullptr);
EXPECT_EQ(keywordOpenGLFragment->entryPoint, "main");
EXPECT_EQ(keywordOpenGLFragment->profile, "fs_4_30");
EXPECT_NE(
std::string(keywordOpenGLFragment->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"),
std::string::npos);
EXPECT_NE(
std::string(keywordOpenGLFragment->sourceCode.CStr()).find("LEGACY_KEYWORD_GL_PS"),
std::string::npos);
delete shader;
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVariants) {
namespace fs = std::filesystem;
@@ -903,6 +1022,93 @@ TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactFromSingleSourceAuthoringPr
fs::remove_all(projectRoot);
}
TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactFromLegacyBackendSplitAuthoringPreservesKeywords) {
namespace fs = std::filesystem;
const fs::path projectRoot = fs::temp_directory_path() / "xc_shader_legacy_keyword_artifact";
const fs::path shaderDir = projectRoot / "Assets" / "Shaders";
const fs::path stageDir = shaderDir / "stages";
const fs::path shaderPath = shaderDir / "legacy_keywords.shader";
fs::remove_all(projectRoot);
fs::create_directories(stageDir);
WriteTextFile(stageDir / "legacy_keywords.vs.hlsl", "float4 MainVS() : SV_POSITION { return 0; }\n");
WriteTextFile(
stageDir / "legacy_keywords.ps.hlsl",
"float4 MainPS() : SV_TARGET { return float4(1.0, 0.0, 0.0, 1.0); } // LEGACY_ARTIFACT_PS\n");
WriteTextFile(
shaderPath,
R"(Shader "LegacyArtifactKeywords"
{
SubShader
{
Pass
{
Name "ForwardLit"
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
#pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS
#pragma shader_feature_local _ XC_ALPHA_TEST
#pragma backend D3D12 HLSL "stages/legacy_keywords.vs.hlsl" "stages/legacy_keywords.ps.hlsl" vs_5_0 ps_5_0
ENDHLSL
}
}
}
)");
AssetDatabase database;
database.Initialize(projectRoot.string().c_str());
AssetDatabase::ResolvedAsset resolvedAsset;
ASSERT_TRUE(database.EnsureArtifact("Assets/Shaders/legacy_keywords.shader", ResourceType::Shader, resolvedAsset));
ASSERT_TRUE(resolvedAsset.artifactReady);
EXPECT_TRUE(fs::exists(resolvedAsset.artifactMainPath.CStr()));
ShaderLoader loader;
LoadResult result = loader.Load(resolvedAsset.artifactMainPath.CStr());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
ASSERT_TRUE(shader->DeclaresKeyword("XC_MAIN_LIGHT_SHADOWS"));
ASSERT_TRUE(shader->DeclaresKeyword("XC_ALPHA_TEST"));
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->keywordDeclarations.Size(), 2u);
ASSERT_EQ(pass->variants.Size(), 8u);
ShaderKeywordSet enabledKeywords = {};
enabledKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST");
enabledKeywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS");
const ShaderStageVariant* keywordFragmentVariant =
shader->FindVariant(
"ForwardLit",
ShaderType::Fragment,
ShaderBackend::D3D12,
enabledKeywords);
ASSERT_NE(keywordFragmentVariant, nullptr);
ASSERT_EQ(keywordFragmentVariant->requiredKeywords.enabledKeywords.Size(), 2u);
EXPECT_NE(
std::string(keywordFragmentVariant->sourceCode.CStr()).find("#define XC_MAIN_LIGHT_SHADOWS 1"),
std::string::npos);
EXPECT_NE(
std::string(keywordFragmentVariant->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"),
std::string::npos);
EXPECT_NE(
std::string(keywordFragmentVariant->sourceCode.CStr()).find("LEGACY_ARTIFACT_PS"),
std::string::npos);
delete shader;
database.Shutdown();
fs::remove_all(projectRoot);
}
TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactFromUnityLikeAuthoringAndTracksStageDependencies) {
namespace fs = std::filesystem;
using namespace std::chrono_literals;