rendering: add keyword-aware shader variant selection

This commit is contained in:
2026-04-06 19:37:01 +08:00
parent a8b4da16a3
commit 261dd44fd5
26 changed files with 469 additions and 76 deletions

View File

@@ -144,6 +144,88 @@ TEST(Shader, FindsBackendSpecificVariantAndFallsBackToGeneric) {
EXPECT_EQ(openglVariant->sourceCode, "generic fragment");
}
TEST(Shader, FindsMostSpecificKeywordVariantAndPrefersExactBackend) {
Shader shader;
ShaderStageVariant baseFragment = {};
baseFragment.stage = ShaderType::Fragment;
baseFragment.language = ShaderLanguage::GLSL;
baseFragment.backend = ShaderBackend::Generic;
baseFragment.sourceCode = "generic base";
shader.AddPassVariant("ForwardLit", baseFragment);
ShaderStageVariant genericKeywordFragment = {};
genericKeywordFragment.stage = ShaderType::Fragment;
genericKeywordFragment.language = ShaderLanguage::GLSL;
genericKeywordFragment.backend = ShaderBackend::Generic;
genericKeywordFragment.requiredKeywords.enabledKeywords.PushBack("XC_DEBUG");
genericKeywordFragment.requiredKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST");
genericKeywordFragment.requiredKeywords.enabledKeywords.PushBack("XC_DEBUG");
genericKeywordFragment.sourceCode = "generic keyword";
shader.AddPassVariant("ForwardLit", genericKeywordFragment);
ShaderStageVariant d3d12KeywordFragment = {};
d3d12KeywordFragment.stage = ShaderType::Fragment;
d3d12KeywordFragment.language = ShaderLanguage::HLSL;
d3d12KeywordFragment.backend = ShaderBackend::D3D12;
d3d12KeywordFragment.requiredKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST");
d3d12KeywordFragment.sourceCode = "d3d12 keyword";
shader.AddPassVariant("ForwardLit", d3d12KeywordFragment);
ShaderKeywordSet enabledKeywords = {};
enabledKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST");
enabledKeywords.enabledKeywords.PushBack("XC_DEBUG");
const ShaderStageVariant* d3d12Variant =
shader.FindVariant(
"ForwardLit",
ShaderType::Fragment,
ShaderBackend::D3D12,
enabledKeywords);
ASSERT_NE(d3d12Variant, nullptr);
EXPECT_EQ(d3d12Variant->sourceCode, "d3d12 keyword");
const ShaderStageVariant* openglVariant =
shader.FindVariant(
"ForwardLit",
ShaderType::Fragment,
ShaderBackend::OpenGL,
enabledKeywords);
ASSERT_NE(openglVariant, nullptr);
EXPECT_EQ(openglVariant->sourceCode, "generic keyword");
ASSERT_EQ(openglVariant->requiredKeywords.enabledKeywords.Size(), 2u);
EXPECT_EQ(openglVariant->requiredKeywords.enabledKeywords[0], "XC_ALPHA_TEST");
EXPECT_EQ(openglVariant->requiredKeywords.enabledKeywords[1], "XC_DEBUG");
}
TEST(Shader, RejectsVariantWhenRequiredKeywordsAreMissing) {
Shader shader;
ShaderStageVariant keywordFragment = {};
keywordFragment.stage = ShaderType::Fragment;
keywordFragment.language = ShaderLanguage::GLSL;
keywordFragment.backend = ShaderBackend::OpenGL;
keywordFragment.requiredKeywords.enabledKeywords.PushBack("XC_CLIP");
keywordFragment.sourceCode = "clip fragment";
shader.AddPassVariant("ForwardLit", keywordFragment);
EXPECT_EQ(
shader.FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::OpenGL),
nullptr);
ShaderKeywordSet enabledKeywords = {};
enabledKeywords.enabledKeywords.PushBack("XC_CLIP");
const ShaderStageVariant* matchedVariant =
shader.FindVariant(
"ForwardLit",
ShaderType::Fragment,
ShaderBackend::OpenGL,
enabledKeywords);
ASSERT_NE(matchedVariant, nullptr);
EXPECT_EQ(matchedVariant->sourceCode, "clip fragment");
}
TEST(Shader, StoresPerPassTags) {
Shader shader;
shader.SetPassTag("ForwardLit", "LightMode", "ForwardBase");

View File

@@ -382,6 +382,66 @@ TEST(ShaderLoader, LoadLegacyBackendSplitShaderAuthoringBuildsRuntimeContract) {
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadShaderManifestParsesVariantKeywordsAndKeywordAwareLookup) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_manifest_variant_keywords";
const fs::path manifestPath = shaderRoot / "variant_keywords.shader";
fs::remove_all(shaderRoot);
fs::create_directories(shaderRoot);
{
std::ofstream manifest(manifestPath);
ASSERT_TRUE(manifest.is_open());
manifest << "{\n";
manifest << " \"name\": \"VariantKeywordShader\",\n";
manifest << " \"passes\": [\n";
manifest << " {\n";
manifest << " \"name\": \"ForwardLit\",\n";
manifest << " \"variants\": [\n";
manifest << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"sourceCode\": \"#version 430\\nvoid main() {}\\n\" },\n";
manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"sourceCode\": \"#version 430\\n// BASE_FRAGMENT\\nvoid main() {}\\n\" },\n";
manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"keywords\": [\"XC_ALPHA_TEST\", \"_\", \"XC_FOG\", \"XC_ALPHA_TEST\"], \"sourceCode\": \"#version 430\\n// KEYWORD_FRAGMENT\\nvoid main() {}\\n\" }\n";
manifest << " ]\n";
manifest << " }\n";
manifest << " ]\n";
manifest << "}\n";
}
ShaderLoader loader;
LoadResult result = loader.Load(manifestPath.string().c_str());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderStageVariant* baseFragment =
shader->FindVariant("ForwardLit", ShaderType::Fragment, ShaderBackend::OpenGL);
ASSERT_NE(baseFragment, nullptr);
EXPECT_NE(std::string(baseFragment->sourceCode.CStr()).find("BASE_FRAGMENT"), std::string::npos);
ShaderKeywordSet enabledKeywords = {};
enabledKeywords.enabledKeywords.PushBack("XC_FOG");
enabledKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST");
const ShaderStageVariant* keywordFragment =
shader->FindVariant(
"ForwardLit",
ShaderType::Fragment,
ShaderBackend::OpenGL,
enabledKeywords);
ASSERT_NE(keywordFragment, nullptr);
EXPECT_NE(std::string(keywordFragment->sourceCode.CStr()).find("KEYWORD_FRAGMENT"), std::string::npos);
ASSERT_EQ(keywordFragment->requiredKeywords.enabledKeywords.Size(), 2u);
EXPECT_EQ(keywordFragment->requiredKeywords.enabledKeywords[0], "XC_ALPHA_TEST");
EXPECT_EQ(keywordFragment->requiredKeywords.enabledKeywords[1], "XC_FOG");
delete shader;
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVariants) {
namespace fs = std::filesystem;
@@ -680,7 +740,8 @@ TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactAndLoaderReadsItBack) {
manifest << " ],\n";
manifest << " \"variants\": [\n";
manifest << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"lit.vert.glsl\" },\n";
manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"lit.frag.glsl\" }\n";
manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"lit.frag.glsl\" },\n";
manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"keywords\": [\"XC_ALPHA_TEST\", \"XC_FOG\"], \"sourceCode\": \"#version 430\\n// ARTIFACT_GL_PS_KEYWORD\\nvoid main() {}\\n\" }\n";
manifest << " ]\n";
manifest << " }\n";
manifest << " ]\n";
@@ -711,7 +772,7 @@ TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactAndLoaderReadsItBack) {
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->variants.Size(), 2u);
ASSERT_EQ(pass->variants.Size(), 3u);
ASSERT_EQ(pass->resources.Size(), 1u);
const ShaderStageVariant* fragmentVariant =
@@ -719,6 +780,22 @@ TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactAndLoaderReadsItBack) {
ASSERT_NE(fragmentVariant, nullptr);
EXPECT_NE(std::string(fragmentVariant->sourceCode.CStr()).find("ARTIFACT_GL_PS"), std::string::npos);
ShaderKeywordSet enabledKeywords = {};
enabledKeywords.enabledKeywords.PushBack("XC_FOG");
enabledKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST");
const ShaderStageVariant* keywordVariant =
shader->FindVariant(
"ForwardLit",
ShaderType::Fragment,
ShaderBackend::OpenGL,
enabledKeywords);
ASSERT_NE(keywordVariant, nullptr);
EXPECT_NE(std::string(keywordVariant->sourceCode.CStr()).find("ARTIFACT_GL_PS_KEYWORD"), std::string::npos);
ASSERT_EQ(keywordVariant->requiredKeywords.enabledKeywords.Size(), 2u);
EXPECT_EQ(keywordVariant->requiredKeywords.enabledKeywords[0], "XC_ALPHA_TEST");
EXPECT_EQ(keywordVariant->requiredKeywords.enabledKeywords[1], "XC_FOG");
delete shader;
database.Shutdown();
fs::remove_all(projectRoot);