rendering: generate single-source shader keyword variants

This commit is contained in:
2026-04-06 19:58:49 +08:00
parent 261dd44fd5
commit f16620afc6
2 changed files with 156 additions and 30 deletions

View File

@@ -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<ShaderKeywordSet> BuildShaderKeywordVariantSets(
const Containers::Array<ShaderKeywordDeclaration>& declarations) {
std::vector<ShaderKeywordSet> keywordSets(1);
for (const ShaderKeywordDeclaration& declaration : declarations) {
if (declaration.options.Empty()) {
continue;
}
std::vector<ShaderKeywordSet> nextKeywordSets;
std::unordered_set<std::string> 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<std::string>& 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<ShaderKeywordSet> 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);
}
}
}
}

View File

@@ -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<String> 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);