resources: expand legacy shader keyword variants
This commit is contained in:
@@ -837,10 +837,7 @@ bool ContainsSingleSourceAuthoringConstructs(const std::vector<std::string>& 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<ShaderKeywordSet> 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<ShaderKeywordSet> 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;
|
||||
|
||||
@@ -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<Shader*>(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<Shader*>(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;
|
||||
|
||||
Reference in New Issue
Block a user