rendering: formalize unity-style shader pass contracts
This commit is contained in:
@@ -24,6 +24,20 @@ void WriteTextFile(const std::filesystem::path& path, const std::string& content
|
||||
ASSERT_TRUE(static_cast<bool>(output));
|
||||
}
|
||||
|
||||
const ShaderPassTagEntry* FindPassTag(const ShaderPass* pass, const char* name) {
|
||||
if (pass == nullptr || name == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const ShaderPassTagEntry& tag : pass->tags) {
|
||||
if (tag.name == name) {
|
||||
return &tag;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TEST(ShaderLoader, GetResourceType) {
|
||||
ShaderLoader loader;
|
||||
EXPECT_EQ(loader.GetResourceType(), ResourceType::Shader);
|
||||
@@ -640,11 +654,13 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVar
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
ASSERT_EQ(pass->tags.Size(), 2u);
|
||||
ASSERT_EQ(pass->tags.Size(), 3u);
|
||||
EXPECT_EQ(pass->tags[0].name, "Queue");
|
||||
EXPECT_EQ(pass->tags[0].value, "Geometry");
|
||||
EXPECT_EQ(pass->tags[1].name, "LightMode");
|
||||
EXPECT_EQ(pass->tags[1].value, "ForwardLit");
|
||||
EXPECT_EQ(pass->tags[1].name, "LOD");
|
||||
EXPECT_EQ(pass->tags[1].value, "200");
|
||||
EXPECT_EQ(pass->tags[2].name, "LightMode");
|
||||
EXPECT_EQ(pass->tags[2].value, "ForwardLit");
|
||||
EXPECT_TRUE(pass->resources.Empty());
|
||||
ASSERT_EQ(pass->keywordDeclarations.Size(), 2u);
|
||||
EXPECT_EQ(pass->keywordDeclarations[0].type, ShaderKeywordDeclarationType::MultiCompile);
|
||||
@@ -708,6 +724,134 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringBuildsGenericHlslVar
|
||||
fs::remove_all(shaderRoot);
|
||||
}
|
||||
|
||||
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringParsesPassStateAndFallback) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_single_source_pass_state";
|
||||
const fs::path shaderPath = shaderRoot / "single_source_state.shader";
|
||||
|
||||
fs::remove_all(shaderRoot);
|
||||
fs::create_directories(shaderRoot);
|
||||
|
||||
WriteTextFile(
|
||||
shaderPath,
|
||||
R"(Shader "SingleSourceStateful"
|
||||
{
|
||||
Fallback "Legacy/Diffuse"
|
||||
SubShader
|
||||
{
|
||||
Pass
|
||||
{
|
||||
Name "ForwardLit"
|
||||
Cull Front
|
||||
ZWrite Off
|
||||
ZTest LEqual
|
||||
Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha
|
||||
ColorMask RGB
|
||||
HLSLPROGRAM
|
||||
#pragma vertex Vert
|
||||
#pragma fragment Frag
|
||||
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<Shader*>(result.resource);
|
||||
ASSERT_NE(shader, nullptr);
|
||||
EXPECT_EQ(shader->GetFallback(), "Legacy/Diffuse");
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
EXPECT_TRUE(pass->hasFixedFunctionState);
|
||||
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Front);
|
||||
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
||||
EXPECT_TRUE(pass->fixedFunctionState.depthTestEnable);
|
||||
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
||||
EXPECT_TRUE(pass->fixedFunctionState.blendEnable);
|
||||
EXPECT_EQ(pass->fixedFunctionState.srcBlend, MaterialBlendFactor::SrcAlpha);
|
||||
EXPECT_EQ(pass->fixedFunctionState.dstBlend, MaterialBlendFactor::InvSrcAlpha);
|
||||
EXPECT_EQ(pass->fixedFunctionState.srcBlendAlpha, MaterialBlendFactor::One);
|
||||
EXPECT_EQ(pass->fixedFunctionState.dstBlendAlpha, MaterialBlendFactor::InvSrcAlpha);
|
||||
EXPECT_EQ(pass->fixedFunctionState.colorWriteMask, 0x7u);
|
||||
|
||||
delete shader;
|
||||
fs::remove_all(shaderRoot);
|
||||
}
|
||||
|
||||
TEST(ShaderLoader, AssetDatabaseCreatesShaderArtifactFromSingleSourceAuthoringPreservesPassStateAndFallback) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_shader_single_source_artifact_pass_state";
|
||||
const fs::path shaderDir = projectRoot / "Assets" / "Shaders";
|
||||
const fs::path shaderPath = shaderDir / "single_source_state.shader";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(shaderDir);
|
||||
|
||||
WriteTextFile(
|
||||
shaderPath,
|
||||
R"(Shader "ArtifactSingleSourceStateful"
|
||||
{
|
||||
Fallback "Legacy/Cutout"
|
||||
SubShader
|
||||
{
|
||||
Pass
|
||||
{
|
||||
Name "ForwardLit"
|
||||
Cull Off
|
||||
ZWrite Off
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ColorMask RGBA
|
||||
HLSLPROGRAM
|
||||
#pragma vertex Vert
|
||||
#pragma fragment Frag
|
||||
float4 Vert() : SV_POSITION { return 0; }
|
||||
float4 Frag() : SV_TARGET { return 1; }
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
AssetDatabase database;
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase::ResolvedAsset resolvedAsset;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/Shaders/single_source_state.shader", ResourceType::Shader, resolvedAsset));
|
||||
ASSERT_TRUE(resolvedAsset.artifactReady);
|
||||
|
||||
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);
|
||||
EXPECT_EQ(shader->GetFallback(), "Legacy/Cutout");
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
EXPECT_TRUE(pass->hasFixedFunctionState);
|
||||
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
|
||||
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
||||
EXPECT_TRUE(pass->fixedFunctionState.blendEnable);
|
||||
EXPECT_EQ(pass->fixedFunctionState.srcBlend, MaterialBlendFactor::SrcAlpha);
|
||||
EXPECT_EQ(pass->fixedFunctionState.dstBlend, MaterialBlendFactor::InvSrcAlpha);
|
||||
EXPECT_EQ(pass->fixedFunctionState.colorWriteMask, 0xFu);
|
||||
|
||||
delete shader;
|
||||
database.Shutdown();
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringParsesMultiCompileLocalKeywords) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -770,6 +914,82 @@ TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringParsesMultiCompileLo
|
||||
fs::remove_all(shaderRoot);
|
||||
}
|
||||
|
||||
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringParsesFallbackAndFixedFunctionStateInheritance) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_single_source_fixed_state";
|
||||
const fs::path shaderPath = shaderRoot / "single_source_fixed_state.shader";
|
||||
|
||||
fs::remove_all(shaderRoot);
|
||||
fs::create_directories(shaderRoot);
|
||||
|
||||
WriteTextFile(
|
||||
shaderPath,
|
||||
R"(Shader "SingleSourceFixedState"
|
||||
{
|
||||
Fallback "Legacy Shaders/Diffuse"
|
||||
SubShader
|
||||
{
|
||||
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
|
||||
LOD 310
|
||||
Cull Front
|
||||
ZWrite Off
|
||||
Pass
|
||||
{
|
||||
Name "ForwardLit"
|
||||
Tags { "LightMode" = "ForwardLit" }
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ColorMask RGB
|
||||
HLSLPROGRAM
|
||||
#pragma vertex Vert
|
||||
#pragma fragment Frag
|
||||
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<Shader*>(result.resource);
|
||||
ASSERT_NE(shader, nullptr);
|
||||
EXPECT_EQ(shader->GetFallback(), "Legacy Shaders/Diffuse");
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
EXPECT_TRUE(pass->hasFixedFunctionState);
|
||||
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Front);
|
||||
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
||||
EXPECT_TRUE(pass->fixedFunctionState.depthTestEnable);
|
||||
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
||||
EXPECT_TRUE(pass->fixedFunctionState.blendEnable);
|
||||
EXPECT_EQ(pass->fixedFunctionState.srcBlend, MaterialBlendFactor::SrcAlpha);
|
||||
EXPECT_EQ(pass->fixedFunctionState.dstBlend, MaterialBlendFactor::InvSrcAlpha);
|
||||
EXPECT_EQ(pass->fixedFunctionState.srcBlendAlpha, MaterialBlendFactor::SrcAlpha);
|
||||
EXPECT_EQ(pass->fixedFunctionState.dstBlendAlpha, MaterialBlendFactor::InvSrcAlpha);
|
||||
EXPECT_EQ(pass->fixedFunctionState.colorWriteMask, 0x7);
|
||||
|
||||
const ShaderPassTagEntry* queueTag = FindPassTag(pass, "Queue");
|
||||
ASSERT_NE(queueTag, nullptr);
|
||||
EXPECT_EQ(queueTag->value, "Transparent");
|
||||
|
||||
const ShaderPassTagEntry* lodTag = FindPassTag(pass, "LOD");
|
||||
ASSERT_NE(lodTag, nullptr);
|
||||
EXPECT_EQ(lodTag->value, "310");
|
||||
|
||||
const ShaderPassTagEntry* lightModeTag = FindPassTag(pass, "LightMode");
|
||||
ASSERT_NE(lightModeTag, nullptr);
|
||||
EXPECT_EQ(lightModeTag->value, "ForwardLit");
|
||||
|
||||
delete shader;
|
||||
fs::remove_all(shaderRoot);
|
||||
}
|
||||
|
||||
TEST(ShaderLoader, LoadUnityStyleSingleSourceShaderAuthoringRejectsBackendPragma) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user