From f16620afc6c20dea2a8ce105d547fc7288e210ba Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 6 Apr 2026 19:58:49 +0800 Subject: [PATCH] rendering: generate single-source shader keyword variants --- engine/src/Resources/Shader/ShaderLoader.cpp | 144 ++++++++++++++---- tests/Resources/Shader/test_shader_loader.cpp | 42 ++++- 2 files changed, 156 insertions(+), 30 deletions(-) diff --git a/engine/src/Resources/Shader/ShaderLoader.cpp b/engine/src/Resources/Shader/ShaderLoader.cpp index 8cd0cf60..443192ef 100644 --- a/engine/src/Resources/Shader/ShaderLoader.cpp +++ b/engine/src/Resources/Shader/ShaderLoader.cpp @@ -954,6 +954,82 @@ Containers::String StripUnityStyleAuthoringPragmas(const Containers::String& sou return strippedSource.c_str(); } +Containers::String BuildShaderKeywordSetSignature(const ShaderKeywordSet& keywordSet) { + ShaderKeywordSet normalizedKeywords = keywordSet; + NormalizeShaderKeywordSetInPlace(normalizedKeywords); + + Containers::String signature; + for (size_t keywordIndex = 0; keywordIndex < normalizedKeywords.enabledKeywords.Size(); ++keywordIndex) { + if (keywordIndex > 0) { + signature += ";"; + } + + signature += normalizedKeywords.enabledKeywords[keywordIndex]; + } + + return signature; +} + +std::vector BuildShaderKeywordVariantSets( + const Containers::Array& declarations) { + std::vector keywordSets(1); + + for (const ShaderKeywordDeclaration& declaration : declarations) { + if (declaration.options.Empty()) { + continue; + } + + std::vector nextKeywordSets; + std::unordered_set seenSignatures; + nextKeywordSets.reserve(keywordSets.size() * declaration.options.Size()); + + for (const ShaderKeywordSet& currentKeywordSet : keywordSets) { + for (const Containers::String& option : declaration.options) { + ShaderKeywordSet nextKeywordSet = currentKeywordSet; + const Containers::String normalizedKeyword = NormalizeShaderKeywordToken(option); + if (!normalizedKeyword.Empty()) { + nextKeywordSet.enabledKeywords.PushBack(normalizedKeyword); + } + + NormalizeShaderKeywordSetInPlace(nextKeywordSet); + const std::string signature = + ToStdString(BuildShaderKeywordSetSignature(nextKeywordSet)); + if (seenSignatures.insert(signature).second) { + nextKeywordSets.push_back(std::move(nextKeywordSet)); + } + } + } + + if (!nextKeywordSets.empty()) { + keywordSets = std::move(nextKeywordSets); + } + } + + if (keywordSets.empty()) { + keywordSets.emplace_back(); + } + + return keywordSets; +} + +Containers::String BuildSingleSourceVariantSource( + const Containers::String& strippedCombinedSource, + const ShaderKeywordSet& requiredKeywords) { + Containers::String variantSource; + for (const Containers::String& keyword : requiredKeywords.enabledKeywords) { + variantSource += "#define "; + variantSource += keyword; + variantSource += " 1\n"; + } + + if (!variantSource.Empty() && !strippedCombinedSource.Empty()) { + variantSource += '\n'; + } + + variantSource += strippedCombinedSource; + return variantSource; +} + bool TryParseShaderKeywordDeclarationPragma( const std::vector& pragmaTokens, ShaderKeywordDeclaration& outDeclaration) { @@ -2043,37 +2119,47 @@ LoadResult BuildShaderFromAuthoringDesc( AppendAuthoringSourceBlock(combinedSource, subShader.sharedProgramSource); AppendAuthoringSourceBlock(combinedSource, pass.sharedProgramSource); AppendAuthoringSourceBlock(combinedSource, pass.programSource); - combinedSource = StripUnityStyleAuthoringPragmas(combinedSource); + const Containers::String strippedCombinedSource = + StripUnityStyleAuthoringPragmas(combinedSource); + const std::vector keywordSets = + BuildShaderKeywordVariantSets(pass.keywordDeclarations); - ShaderStageVariant vertexVariant = {}; - vertexVariant.stage = ShaderType::Vertex; - vertexVariant.backend = ShaderBackend::Generic; - vertexVariant.language = ShaderLanguage::HLSL; - vertexVariant.entryPoint = - !pass.vertexEntryPoint.Empty() - ? pass.vertexEntryPoint - : GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Vertex); - vertexVariant.profile = GetDefaultProfile( - ShaderLanguage::HLSL, - ShaderBackend::Generic, - ShaderType::Vertex); - vertexVariant.sourceCode = combinedSource; - shader->AddPassVariant(pass.name, vertexVariant); + for (const ShaderKeywordSet& keywordSet : keywordSets) { + const Containers::String variantSource = + BuildSingleSourceVariantSource(strippedCombinedSource, keywordSet); - ShaderStageVariant fragmentVariant = {}; - fragmentVariant.stage = ShaderType::Fragment; - fragmentVariant.backend = ShaderBackend::Generic; - fragmentVariant.language = ShaderLanguage::HLSL; - fragmentVariant.entryPoint = - !pass.fragmentEntryPoint.Empty() - ? pass.fragmentEntryPoint - : GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Fragment); - fragmentVariant.profile = GetDefaultProfile( - ShaderLanguage::HLSL, - ShaderBackend::Generic, - ShaderType::Fragment); - fragmentVariant.sourceCode = combinedSource; - shader->AddPassVariant(pass.name, fragmentVariant); + ShaderStageVariant vertexVariant = {}; + vertexVariant.stage = ShaderType::Vertex; + vertexVariant.backend = ShaderBackend::Generic; + vertexVariant.language = ShaderLanguage::HLSL; + vertexVariant.requiredKeywords = keywordSet; + vertexVariant.entryPoint = + !pass.vertexEntryPoint.Empty() + ? pass.vertexEntryPoint + : GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Vertex); + vertexVariant.profile = GetDefaultProfile( + ShaderLanguage::HLSL, + ShaderBackend::Generic, + ShaderType::Vertex); + vertexVariant.sourceCode = variantSource; + shader->AddPassVariant(pass.name, vertexVariant); + + ShaderStageVariant fragmentVariant = {}; + fragmentVariant.stage = ShaderType::Fragment; + fragmentVariant.backend = ShaderBackend::Generic; + fragmentVariant.language = ShaderLanguage::HLSL; + fragmentVariant.requiredKeywords = keywordSet; + fragmentVariant.entryPoint = + !pass.fragmentEntryPoint.Empty() + ? pass.fragmentEntryPoint + : GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Fragment); + fragmentVariant.profile = GetDefaultProfile( + ShaderLanguage::HLSL, + ShaderBackend::Generic, + ShaderType::Fragment); + fragmentVariant.sourceCode = variantSource; + shader->AddPassVariant(pass.name, fragmentVariant); + } } } } diff --git a/tests/Resources/Shader/test_shader_loader.cpp b/tests/Resources/Shader/test_shader_loader.cpp index 9cf67eb4..03ffe865 100644 --- a/tests/Resources/Shader/test_shader_loader.cpp +++ b/tests/Resources/Shader/test_shader_loader.cpp @@ -532,7 +532,7 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVar EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_MAIN_LIGHT_SHADOWS")); EXPECT_TRUE(shader->DeclaresKeyword("XC_ALPHA_TEST")); EXPECT_FALSE(shader->DeclaresKeyword("_")); - ASSERT_EQ(pass->variants.Size(), 2u); + ASSERT_EQ(pass->variants.Size(), 8u); const ShaderStageVariant* vertexVariant = shader->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12); @@ -543,6 +543,8 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVar EXPECT_EQ(vertexVariant->profile, "vs_5_0"); EXPECT_NE(std::string(vertexVariant->sourceCode.CStr()).find("#include \"shaderlib/shared.hlsl\""), std::string::npos); EXPECT_NE(std::string(vertexVariant->sourceCode.CStr()).find("XC_SINGLE_SOURCE_FRAG_BODY"), std::string::npos); + EXPECT_EQ(vertexVariant->requiredKeywords.enabledKeywords.Size(), 0u); + EXPECT_EQ(std::string(vertexVariant->sourceCode.CStr()).find("#pragma multi_compile"), std::string::npos); const ShaderStageVariant* fragmentVariant = shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::D3D12); @@ -552,6 +554,24 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVar EXPECT_EQ(fragmentVariant->entryPoint, "Frag"); EXPECT_EQ(fragmentVariant->profile, "ps_5_0"); + 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_EQ(keywordFragmentVariant->requiredKeywords.enabledKeywords[0], "XC_ALPHA_TEST"); + EXPECT_EQ(keywordFragmentVariant->requiredKeywords.enabledKeywords[1], "XC_MAIN_LIGHT_SHADOWS"); + EXPECT_NE(std::string(keywordFragmentVariant->sourceCode.CStr()).find("#define XC_ALPHA_TEST 1"), std::string::npos); + EXPECT_NE(std::string(keywordFragmentVariant->sourceCode.CStr()).find("#define XC_MAIN_LIGHT_SHADOWS 1"), std::string::npos); + EXPECT_EQ(std::string(keywordFragmentVariant->sourceCode.CStr()).find("#pragma shader_feature_local"), std::string::npos); + Array dependencies; ASSERT_TRUE(loader.CollectSourceDependencies(shaderPath.string().c_str(), dependencies)); ASSERT_EQ(dependencies.Size(), 1u); @@ -855,9 +875,29 @@ TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactFromSingleSourceAuthoringPr const ShaderPass* pass = shader->FindPass("ForwardLit"); ASSERT_NE(pass, nullptr); ASSERT_EQ(pass->keywordDeclarations.Size(), 2u); + ASSERT_EQ(pass->variants.Size(), 8u); EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::MultiCompile); EXPECT_EQ(pass->keywordDeclarations[1].type, ShaderKeywordDeclarationType::ShaderFeatureLocal); + 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); + delete shader; database.Shutdown(); fs::remove_all(projectRoot);