diff --git a/engine/include/XCEngine/Resources/Shader/ShaderKeywordTypes.h b/engine/include/XCEngine/Resources/Shader/ShaderKeywordTypes.h index a3ea8790..bad4af7d 100644 --- a/engine/include/XCEngine/Resources/Shader/ShaderKeywordTypes.h +++ b/engine/include/XCEngine/Resources/Shader/ShaderKeywordTypes.h @@ -13,7 +13,8 @@ namespace Resources { enum class ShaderKeywordDeclarationType : Core::uint8 { MultiCompile = 0, ShaderFeature, - ShaderFeatureLocal + ShaderFeatureLocal, + MultiCompileLocal = 3 }; struct ShaderKeywordDeclaration { @@ -21,7 +22,8 @@ struct ShaderKeywordDeclaration { Containers::Array options; bool IsLocal() const { - return type == ShaderKeywordDeclarationType::ShaderFeatureLocal; + return type == ShaderKeywordDeclarationType::ShaderFeatureLocal || + type == ShaderKeywordDeclarationType::MultiCompileLocal; } }; diff --git a/engine/src/Resources/Shader/ShaderLoader.cpp b/engine/src/Resources/Shader/ShaderLoader.cpp index 76c2a938..b0374aa2 100644 --- a/engine/src/Resources/Shader/ShaderLoader.cpp +++ b/engine/src/Resources/Shader/ShaderLoader.cpp @@ -928,6 +928,7 @@ bool IsUnityStyleAuthoringPragmaDirective(const std::string& line) { pragmaTokens[1] == "fragment" || pragmaTokens[1] == "target" || pragmaTokens[1] == "multi_compile" || + pragmaTokens[1] == "multi_compile_local" || pragmaTokens[1] == "shader_feature" || pragmaTokens[1] == "shader_feature_local" || pragmaTokens[1] == "backend"; @@ -1037,6 +1038,8 @@ bool TryParseShaderKeywordDeclarationPragma( if (pragmaTokens[1] == "multi_compile") { outDeclaration.type = ShaderKeywordDeclarationType::MultiCompile; + } else if (pragmaTokens[1] == "multi_compile_local") { + outDeclaration.type = ShaderKeywordDeclarationType::MultiCompileLocal; } else if (pragmaTokens[1] == "shader_feature") { outDeclaration.type = ShaderKeywordDeclarationType::ShaderFeature; } else if (pragmaTokens[1] == "shader_feature_local") { @@ -1550,6 +1553,7 @@ bool ParseLegacyBackendSplitShaderAuthoring( } if (pragmaTokens.size() >= 2u && (pragmaTokens[1] == "multi_compile" || + pragmaTokens[1] == "multi_compile_local" || pragmaTokens[1] == "shader_feature" || pragmaTokens[1] == "shader_feature_local")) { ShaderKeywordDeclaration declaration = {}; @@ -1842,6 +1846,7 @@ bool ParseUnityStyleSingleSourceShaderAuthoring( } if (pragmaTokens.size() >= 2u && (pragmaTokens[1] == "multi_compile" || + pragmaTokens[1] == "multi_compile_local" || pragmaTokens[1] == "shader_feature" || pragmaTokens[1] == "shader_feature_local")) { ShaderKeywordDeclaration declaration = {}; diff --git a/tests/Resources/Shader/test_shader_loader.cpp b/tests/Resources/Shader/test_shader_loader.cpp index a37997c3..ff89ae87 100644 --- a/tests/Resources/Shader/test_shader_loader.cpp +++ b/tests/Resources/Shader/test_shader_loader.cpp @@ -478,7 +478,7 @@ TEST(ShaderLoader, LoadLegacyBackendSplitShaderAuthoringExpandsKeywordVariantsPe #pragma vertex MainVS #pragma fragment MainPS #pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS - #pragma shader_feature_local _ XC_ALPHA_TEST + #pragma multi_compile_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 @@ -501,7 +501,8 @@ TEST(ShaderLoader, LoadLegacyBackendSplitShaderAuthoringExpandsKeywordVariantsPe 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_EQ(pass->keywordDeclarations[1].type, ShaderKeywordDeclarationType::MultiCompileLocal); + EXPECT_TRUE(pass->keywordDeclarations[1].IsLocal()); EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_MAIN_LIGHT_SHADOWS")); EXPECT_TRUE(shader->PassDeclaresKeyword("ForwardLit", "XC_ALPHA_TEST")); ASSERT_EQ(pass->variants.Size(), 16u); @@ -702,6 +703,68 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVar fs::remove_all(shaderRoot); } +TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringParsesMultiCompileLocalKeywords) { + namespace fs = std::filesystem; + + const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_single_source_multi_compile_local"; + const fs::path shaderPath = shaderRoot / "single_source_multi_compile_local.shader"; + + fs::remove_all(shaderRoot); + fs::create_directories(shaderRoot); + + WriteTextFile( + shaderPath, + R"(Shader "SingleSourceLocalKeywords" +{ + SubShader + { + Pass + { + Name "ForwardLit" + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment Frag + #pragma multi_compile_local _ XC_LOCAL_FOG + float4 Vert() : SV_POSITION { return 0; } + float4 Frag() : SV_TARGET { return 1; } + 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); + + const ShaderPass* pass = shader->FindPass("ForwardLit"); + ASSERT_NE(pass, nullptr); + ASSERT_EQ(pass->keywordDeclarations.Size(), 1u); + EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::MultiCompileLocal); + EXPECT_TRUE(pass->keywordDeclarations[0].IsLocal()); + ASSERT_EQ(pass->variants.Size(), 4u); + + ShaderKeywordSet enabledKeywords = {}; + enabledKeywords.enabledKeywords.PushBack("XC_LOCAL_FOG"); + const ShaderStageVariant* keywordFragmentVariant = + shader->FindVariant( + "ForwardLit", + ShaderType::Fragment, + ShaderBackend::D3D12, + enabledKeywords); + ASSERT_NE(keywordFragmentVariant, nullptr); + EXPECT_NE( + std::string(keywordFragmentVariant->sourceCode.CStr()).find("#define XC_LOCAL_FOG 1"), + std::string::npos); + + delete shader; + fs::remove_all(shaderRoot); +} + TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringRejectsBackendPragma) { namespace fs = std::filesystem; @@ -1051,7 +1114,7 @@ TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactFromLegacyBackendSplitAutho #pragma vertex MainVS #pragma fragment MainPS #pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS - #pragma shader_feature_local _ XC_ALPHA_TEST + #pragma multi_compile_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 } @@ -1080,6 +1143,9 @@ TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactFromLegacyBackendSplitAutho 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::MultiCompileLocal); + EXPECT_TRUE(pass->keywordDeclarations[1].IsLocal()); ASSERT_EQ(pass->variants.Size(), 8u); ShaderKeywordSet enabledKeywords = {};