From 0761079b4c75be96a06e010a913b050d239c9ac6 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 6 Apr 2026 20:09:28 +0800 Subject: [PATCH] resources: expand legacy shader keyword variants --- engine/src/Resources/Shader/ShaderLoader.cpp | 48 ++-- tests/Resources/Shader/test_shader_loader.cpp | 206 ++++++++++++++++++ 2 files changed, 241 insertions(+), 13 deletions(-) diff --git a/engine/src/Resources/Shader/ShaderLoader.cpp b/engine/src/Resources/Shader/ShaderLoader.cpp index 443192ef..76c2a938 100644 --- a/engine/src/Resources/Shader/ShaderLoader.cpp +++ b/engine/src/Resources/Shader/ShaderLoader.cpp @@ -837,10 +837,7 @@ bool ContainsSingleSourceAuthoringConstructs(const std::vector& lin return true; } - if (line.rfind("#pragma target", 0) == 0 || - line.rfind("#pragma multi_compile", 0) == 0 || - line.rfind("#pragma shader_feature", 0) == 0 || - line.rfind("#pragma shader_feature_local", 0) == 0) { + if (line.rfind("#pragma target", 0) == 0) { return true; } } @@ -1012,8 +1009,8 @@ std::vector BuildShaderKeywordVariantSets( return keywordSets; } -Containers::String BuildSingleSourceVariantSource( - const Containers::String& strippedCombinedSource, +Containers::String BuildKeywordVariantSource( + const Containers::String& baseSource, const ShaderKeywordSet& requiredKeywords) { Containers::String variantSource; for (const Containers::String& keyword : requiredKeywords.enabledKeywords) { @@ -1022,11 +1019,11 @@ Containers::String BuildSingleSourceVariantSource( variantSource += " 1\n"; } - if (!variantSource.Empty() && !strippedCombinedSource.Empty()) { + if (!variantSource.Empty() && !baseSource.Empty()) { variantSource += '\n'; } - variantSource += strippedCombinedSource; + variantSource += baseSource; return variantSource; } @@ -1549,6 +1546,19 @@ bool ParseLegacyBackendSplitShaderAuthoring( } currentPass->backendVariants.push_back(std::move(backendVariant)); + continue; + } + if (pragmaTokens.size() >= 2u && + (pragmaTokens[1] == "multi_compile" || + pragmaTokens[1] == "shader_feature" || + pragmaTokens[1] == "shader_feature_local")) { + ShaderKeywordDeclaration declaration = {}; + if (!TryParseShaderKeywordDeclarationPragma(pragmaTokens, declaration)) { + return fail("keyword pragma must declare at least one option", humanLine); + } + + currentPass->keywordDeclarations.PushBack(declaration); + continue; } continue; } @@ -2074,7 +2084,10 @@ LoadResult BuildShaderFromAuthoringDesc( } if (!pass.backendVariants.empty()) { + const std::vector keywordSets = + BuildShaderKeywordVariantSets(pass.keywordDeclarations); for (const AuthoringBackendVariantEntry& backendVariant : pass.backendVariants) { + Containers::String vertexSourceCode; ShaderStageVariant vertexVariant = {}; vertexVariant.stage = ShaderType::Vertex; vertexVariant.backend = backendVariant.backend; @@ -2089,11 +2102,11 @@ LoadResult BuildShaderFromAuthoringDesc( const Containers::String resolvedVertexPath = ResolveShaderDependencyPath(backendVariant.vertexSourcePath, path); - if (!ReadTextFile(resolvedVertexPath, vertexVariant.sourceCode)) { + if (!ReadTextFile(resolvedVertexPath, vertexSourceCode)) { return LoadResult("Failed to read shader authoring vertex source: " + resolvedVertexPath); } - shader->AddPassVariant(pass.name, vertexVariant); + Containers::String fragmentSourceCode; ShaderStageVariant fragmentVariant = {}; fragmentVariant.stage = ShaderType::Fragment; fragmentVariant.backend = backendVariant.backend; @@ -2108,10 +2121,19 @@ LoadResult BuildShaderFromAuthoringDesc( const Containers::String resolvedFragmentPath = ResolveShaderDependencyPath(backendVariant.fragmentSourcePath, path); - if (!ReadTextFile(resolvedFragmentPath, fragmentVariant.sourceCode)) { + if (!ReadTextFile(resolvedFragmentPath, fragmentSourceCode)) { return LoadResult("Failed to read shader authoring fragment source: " + resolvedFragmentPath); } - shader->AddPassVariant(pass.name, fragmentVariant); + + for (const ShaderKeywordSet& keywordSet : keywordSets) { + vertexVariant.requiredKeywords = keywordSet; + vertexVariant.sourceCode = BuildKeywordVariantSource(vertexSourceCode, keywordSet); + shader->AddPassVariant(pass.name, vertexVariant); + + fragmentVariant.requiredKeywords = keywordSet; + fragmentVariant.sourceCode = BuildKeywordVariantSource(fragmentSourceCode, keywordSet); + shader->AddPassVariant(pass.name, fragmentVariant); + } } } else if (!pass.programSource.Empty()) { Containers::String combinedSource; @@ -2126,7 +2148,7 @@ LoadResult BuildShaderFromAuthoringDesc( for (const ShaderKeywordSet& keywordSet : keywordSets) { const Containers::String variantSource = - BuildSingleSourceVariantSource(strippedCombinedSource, keywordSet); + BuildKeywordVariantSource(strippedCombinedSource, keywordSet); ShaderStageVariant vertexVariant = {}; vertexVariant.stage = ShaderType::Vertex; diff --git a/tests/Resources/Shader/test_shader_loader.cpp b/tests/Resources/Shader/test_shader_loader.cpp index 03ffe865..8b54f868 100644 --- a/tests/Resources/Shader/test_shader_loader.cpp +++ b/tests/Resources/Shader/test_shader_loader.cpp @@ -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(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(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;