Close shader authoring pipeline and UsePass dependency tracking

This commit is contained in:
2026-04-07 18:49:37 +08:00
parent 2f9b1696cd
commit 901a6ecc26
12 changed files with 565 additions and 319 deletions

View File

@@ -67,64 +67,45 @@ void WriteTextFile(const std::filesystem::path& path, const std::string& content
ASSERT_TRUE(static_cast<bool>(output));
}
std::filesystem::path WriteSchemaMaterialShaderManifest(const std::filesystem::path& rootPath) {
std::filesystem::path WriteSchemaMaterialShaderAuthoring(const std::filesystem::path& rootPath) {
namespace fs = std::filesystem;
const fs::path shaderDir = rootPath / "Shaders";
fs::create_directories(shaderDir);
WriteTextFile(shaderDir / "schema.vert.glsl", "#version 430\nvoid main() {}\n");
WriteTextFile(shaderDir / "schema.frag.glsl", "#version 430\nvoid main() {}\n");
const fs::path manifestPath = shaderDir / "schema.shader";
std::ofstream manifest(manifestPath, std::ios::binary | std::ios::trunc);
EXPECT_TRUE(manifest.is_open());
if (!manifest.is_open()) {
const fs::path shaderPath = shaderDir / "schema.shader";
std::ofstream shader(shaderPath, std::ios::binary | std::ios::trunc);
EXPECT_TRUE(shader.is_open());
if (!shader.is_open()) {
return {};
}
manifest << "{\n";
manifest << " \"name\": \"SchemaMaterialShader\",\n";
manifest << " \"properties\": [\n";
manifest << " {\n";
manifest << " \"name\": \"_BaseColor\",\n";
manifest << " \"displayName\": \"Base Color\",\n";
manifest << " \"type\": \"Color\",\n";
manifest << " \"defaultValue\": \"(1,0.5,0.25,1)\",\n";
manifest << " \"semantic\": \"BaseColor\"\n";
manifest << " },\n";
manifest << " {\n";
manifest << " \"name\": \"_Metallic\",\n";
manifest << " \"displayName\": \"Metallic\",\n";
manifest << " \"type\": \"Float\",\n";
manifest << " \"defaultValue\": \"0.7\"\n";
manifest << " },\n";
manifest << " {\n";
manifest << " \"name\": \"_Mode\",\n";
manifest << " \"displayName\": \"Mode\",\n";
manifest << " \"type\": \"Int\",\n";
manifest << " \"defaultValue\": \"2\"\n";
manifest << " },\n";
manifest << " {\n";
manifest << " \"name\": \"_MainTex\",\n";
manifest << " \"displayName\": \"Main Tex\",\n";
manifest << " \"type\": \"Texture2D\",\n";
manifest << " \"defaultValue\": \"white\",\n";
manifest << " \"semantic\": \"BaseColorTexture\"\n";
manifest << " }\n";
manifest << " ],\n";
manifest << " \"passes\": [\n";
manifest << " {\n";
manifest << " \"name\": \"ForwardLit\",\n";
manifest << " \"variants\": [\n";
manifest << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"schema.vert.glsl\" },\n";
manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"schema.frag.glsl\" }\n";
manifest << " ]\n";
manifest << " }\n";
manifest << " ]\n";
manifest << "}\n";
EXPECT_TRUE(static_cast<bool>(manifest));
shader << R"(Shader "SchemaMaterialShader"
{
Properties
{
_BaseColor ("Base Color", Color) = (1,0.5,0.25,1) [Semantic(BaseColor)]
_Metallic ("Metallic", Float) = 0.7
_Mode ("Mode", Int) = 2
_MainTex ("Main Tex", 2D) = "white" [Semantic(BaseColorTexture)]
}
SubShader
{
Pass
{
Name "ForwardLit"
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
float4 MainVS() : SV_POSITION { return 0; }
float4 MainPS() : SV_TARGET { return 1; }
ENDHLSL
}
}
}
)";
EXPECT_TRUE(static_cast<bool>(shader));
return manifestPath;
return shaderPath;
}
std::filesystem::path WriteKeywordMaterialShaderAuthoring(const std::filesystem::path& rootPath) {
@@ -200,15 +181,34 @@ TEST(MaterialLoader, ResourceManagerRegistersMaterialAndShaderLoaders) {
}
TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
ResourceManager& manager = ResourceManager::Get();
manager.Initialize();
const std::filesystem::path shaderPath =
std::filesystem::current_path() / "material_loader_valid_shader.hlsl";
std::filesystem::current_path() / "material_loader_valid_shader.shader";
const std::filesystem::path materialPath =
std::filesystem::current_path() / "material_loader_valid.material";
{
std::ofstream shaderFile(shaderPath);
ASSERT_TRUE(shaderFile.is_open());
shaderFile << "float4 MainPS() : SV_TARGET { return float4(1, 1, 1, 1); }";
shaderFile << R"(Shader "Test/ValidMaterial"
{
SubShader
{
Pass
{
Name "ForwardLit"
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
float4 MainVS() : SV_POSITION { return 0; }
float4 MainPS() : SV_TARGET { return float4(1, 1, 1, 1); }
ENDHLSL
}
}
}
)";
}
{
@@ -243,7 +243,7 @@ TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
EXPECT_TRUE(material->IsValid());
EXPECT_NE(material->GetShader(), nullptr);
EXPECT_EQ(material->GetRenderQueue(), static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Transparent));
EXPECT_EQ(material->GetLegacyShaderPassHint(), "ForwardLit");
EXPECT_TRUE(material->GetLegacyShaderPassHint().Empty());
EXPECT_EQ(material->GetTag("LightMode"), "ForwardBase");
EXPECT_EQ(material->GetTag("RenderType"), "Transparent");
EXPECT_EQ(material->GetRenderState().cullMode, MaterialCullMode::Back);
@@ -255,6 +255,7 @@ TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
EXPECT_TRUE(material->HasRenderStateOverride());
delete material;
manager.Shutdown();
std::remove(materialPath.string().c_str());
std::remove(shaderPath.string().c_str());
}
@@ -282,55 +283,103 @@ TEST(MaterialLoader, LoadMaterialWithoutRenderStateLeavesOverrideDisabled) {
std::remove(materialPath.string().c_str());
}
TEST(MaterialLoader, LoadMaterialWithShaderManifestResolvesShaderPass) {
namespace fs = std::filesystem;
ResourceManager& manager = ResourceManager::Get();
manager.Initialize();
const fs::path shaderRoot = fs::temp_directory_path() / "xc_material_shader_manifest_test";
const fs::path shaderDir = shaderRoot / "Shaders";
const fs::path manifestPath = shaderDir / "lit.shader";
const fs::path materialPath = shaderRoot / "manifest.material";
fs::remove_all(shaderRoot);
fs::create_directories(shaderDir);
{
std::ofstream vertexFile(shaderDir / "lit.vert.glsl");
ASSERT_TRUE(vertexFile.is_open());
vertexFile << "#version 430\n// MATERIAL_MANIFEST_GL_VS\nvoid main() {}\n";
}
{
std::ofstream fragmentFile(shaderDir / "lit.frag.glsl");
ASSERT_TRUE(fragmentFile.is_open());
fragmentFile << "#version 430\n// MATERIAL_MANIFEST_GL_PS\nvoid main() {}\n";
}
{
std::ofstream manifestFile(manifestPath);
ASSERT_TRUE(manifestFile.is_open());
manifestFile << "{\n";
manifestFile << " \"name\": \"ManifestLit\",\n";
manifestFile << " \"passes\": [\n";
manifestFile << " {\n";
manifestFile << " \"name\": \"ForwardLit\",\n";
manifestFile << " \"tags\": { \"LightMode\": \"ForwardBase\" },\n";
manifestFile << " \"variants\": [\n";
manifestFile << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"lit.vert.glsl\" },\n";
manifestFile << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"lit.frag.glsl\" }\n";
manifestFile << " ]\n";
manifestFile << " }\n";
manifestFile << " ]\n";
manifestFile << "}\n";
}
TEST(MaterialLoader, LoadMaterialParsesOffsetAndStencilRenderState) {
const std::filesystem::path materialPath =
std::filesystem::current_path() / "material_loader_offset_stencil.material";
{
std::ofstream materialFile(materialPath);
ASSERT_TRUE(materialFile.is_open());
materialFile << "{\n";
materialFile << " \"shader\": \"" << manifestPath.generic_string() << "\",\n";
materialFile << " \"renderState\": {\n";
materialFile << " \"offset\": [1.5, 2],\n";
materialFile << " \"stencil\": {\n";
materialFile << " \"ref\": 7,\n";
materialFile << " \"readMask\": 63,\n";
materialFile << " \"writeMask\": 31,\n";
materialFile << " \"comp\": \"Equal\",\n";
materialFile << " \"pass\": \"Replace\",\n";
materialFile << " \"fail\": \"Keep\",\n";
materialFile << " \"zFail\": \"IncrSat\",\n";
materialFile << " \"compBack\": \"NotEqual\",\n";
materialFile << " \"passBack\": \"DecrWrap\",\n";
materialFile << " \"failBack\": \"Invert\",\n";
materialFile << " \"zFailBack\": \"Zero\"\n";
materialFile << " }\n";
materialFile << " }\n";
materialFile << "}\n";
}
MaterialLoader loader;
LoadResult result = loader.Load(materialPath.string().c_str());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* material = static_cast<Material*>(result.resource);
ASSERT_NE(material, nullptr);
EXPECT_FLOAT_EQ(material->GetRenderState().depthBiasFactor, 1.5f);
EXPECT_EQ(material->GetRenderState().depthBiasUnits, 2);
EXPECT_TRUE(material->GetRenderState().stencil.enabled);
EXPECT_EQ(material->GetRenderState().stencil.reference, 7u);
EXPECT_EQ(material->GetRenderState().stencil.readMask, 63u);
EXPECT_EQ(material->GetRenderState().stencil.writeMask, 31u);
EXPECT_EQ(material->GetRenderState().stencil.front.func, MaterialComparisonFunc::Equal);
EXPECT_EQ(material->GetRenderState().stencil.front.passOp, MaterialStencilOp::Replace);
EXPECT_EQ(material->GetRenderState().stencil.front.depthFailOp, MaterialStencilOp::IncrSat);
EXPECT_EQ(material->GetRenderState().stencil.back.func, MaterialComparisonFunc::NotEqual);
EXPECT_EQ(material->GetRenderState().stencil.back.passOp, MaterialStencilOp::DecrWrap);
EXPECT_EQ(material->GetRenderState().stencil.back.failOp, MaterialStencilOp::Invert);
EXPECT_EQ(material->GetRenderState().stencil.back.depthFailOp, MaterialStencilOp::Zero);
delete material;
std::remove(materialPath.string().c_str());
}
TEST(MaterialLoader, LoadMaterialWithAuthoringShaderResolvesShaderPass) {
namespace fs = std::filesystem;
ResourceManager& manager = ResourceManager::Get();
manager.Initialize();
const fs::path shaderRoot = fs::temp_directory_path() / "xc_material_shader_authoring_test";
const fs::path shaderDir = shaderRoot / "Shaders";
const fs::path shaderPath = shaderDir / "lit.shader";
const fs::path materialPath = shaderRoot / "authoring.material";
fs::remove_all(shaderRoot);
fs::create_directories(shaderDir);
WriteTextFile(
shaderPath,
R"(Shader "AuthoringLit"
{
SubShader
{
Pass
{
Name "ForwardLit"
Tags { "LightMode" = "ForwardBase" }
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
float4 MainVS() : SV_POSITION
{
return 0; // MATERIAL_AUTHORING_VS
}
float4 MainPS() : SV_TARGET
{
return 1; // MATERIAL_AUTHORING_PS
}
ENDHLSL
}
}
}
)");
{
std::ofstream materialFile(materialPath);
ASSERT_TRUE(materialFile.is_open());
materialFile << "{\n";
materialFile << " \"shader\": \"" << shaderPath.generic_string() << "\",\n";
materialFile << " \"shaderPass\": \"ForwardLit\",\n";
materialFile << " \"renderQueue\": \"Geometry\"\n";
materialFile << "}\n";
@@ -349,7 +398,7 @@ TEST(MaterialLoader, LoadMaterialWithShaderManifestResolvesShaderPass) {
const ShaderStageVariant* vertexVariant =
material->GetShader()->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::OpenGL);
ASSERT_NE(vertexVariant, nullptr);
EXPECT_NE(std::string(vertexVariant->sourceCode.CStr()).find("MATERIAL_MANIFEST_GL_VS"), std::string::npos);
EXPECT_NE(std::string(vertexVariant->sourceCode.CStr()).find("MATERIAL_AUTHORING_VS"), std::string::npos);
delete material;
manager.Shutdown();
@@ -362,7 +411,7 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) {
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_override_test";
fs::remove_all(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
ASSERT_FALSE(shaderPath.empty());
const fs::path materialPath = rootPath / "override.material";
@@ -437,7 +486,7 @@ TEST(MaterialLoader, RejectsUnknownPropertyAgainstShaderSchema) {
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_unknown_test";
fs::remove_all(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
ASSERT_FALSE(shaderPath.empty());
const fs::path materialPath = rootPath / "unknown_property.material";
@@ -463,7 +512,7 @@ TEST(MaterialLoader, RejectsTypeMismatchAgainstShaderSchema) {
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_mismatch_test";
fs::remove_all(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
ASSERT_FALSE(shaderPath.empty());
const fs::path materialPath = rootPath / "type_mismatch.material";
@@ -489,7 +538,7 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectPreservesShaderDefaultsForO
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_defaults_test";
fs::remove_all(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
ASSERT_FALSE(shaderPath.empty());
const fs::path materialPath = rootPath / "defaults.material";
@@ -526,7 +575,7 @@ TEST(MaterialLoader, LoadMaterialMapsLegacySemanticKeysIntoShaderSchemaPropertie
fs::remove_all(rootPath);
fs::create_directories(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
ASSERT_FALSE(shaderPath.empty());
const fs::path materialPath = rootPath / "semantic_alias.material";
@@ -604,7 +653,7 @@ TEST(MaterialLoader, RejectsUnknownTextureBindingAgainstShaderSchema) {
fs::remove_all(rootPath);
fs::create_directories(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
ASSERT_FALSE(shaderPath.empty());
const fs::path materialPath = rootPath / "unknown_texture.material";
@@ -985,31 +1034,31 @@ TEST(MaterialLoader, AssetDatabaseReimportsMaterialWhenShaderDependencyChanges)
const fs::path projectRoot = fs::temp_directory_path() / "xc_material_shader_dependency_reimport_test";
const fs::path assetsDir = projectRoot / "Assets";
const fs::path shaderDir = assetsDir / "Shaders";
const fs::path shaderManifestPath = shaderDir / "lit.shader";
const fs::path shaderPath = shaderDir / "lit.shader";
const fs::path materialPath = assetsDir / "textured.material";
fs::remove_all(projectRoot);
fs::create_directories(shaderDir);
WriteTextFile(shaderDir / "lit.vert.glsl", "#version 430\nvoid main() {}\n");
WriteTextFile(shaderDir / "lit.frag.glsl", "#version 430\nvoid main() {}\n");
WriteTextFile(
shaderPath,
R"(Shader "MaterialDependencyShader"
{
SubShader
{
std::ofstream manifest(shaderManifestPath);
ASSERT_TRUE(manifest.is_open());
manifest << "{\n";
manifest << " \"name\": \"MaterialDependencyShader\",\n";
manifest << " \"passes\": [\n";
manifest << " {\n";
manifest << " \"name\": \"ForwardLit\",\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 << " ]\n";
manifest << " }\n";
manifest << " ]\n";
manifest << "}\n";
Pass
{
Name "ForwardLit"
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
float4 MainVS() : SV_POSITION { return 0; }
float4 MainPS() : SV_TARGET { return 1; }
ENDHLSL
}
}
}
)");
{
std::ofstream materialFile(materialPath);
@@ -1034,10 +1083,10 @@ TEST(MaterialLoader, AssetDatabaseReimportsMaterialWhenShaderDependencyChanges)
std::this_thread::sleep_for(50ms);
{
std::ofstream manifest(shaderManifestPath, std::ios::app);
ASSERT_TRUE(manifest.is_open());
manifest << "\n";
ASSERT_TRUE(static_cast<bool>(manifest));
std::ofstream shaderFile(shaderPath, std::ios::app);
ASSERT_TRUE(shaderFile.is_open());
shaderFile << "\n// force shader dependency reimport\n";
ASSERT_TRUE(static_cast<bool>(shaderFile));
}
database.Initialize(projectRoot.string().c_str());

View File

@@ -10,8 +10,7 @@ namespace {
TEST(Shader, DefaultConstructor) {
Shader shader;
EXPECT_EQ(shader.GetShaderType(), ShaderType::Fragment);
EXPECT_EQ(shader.GetShaderLanguage(), ShaderLanguage::GLSL);
EXPECT_EQ(shader.GetPassCount(), 0u);
EXPECT_FALSE(shader.IsValid());
EXPECT_EQ(shader.GetMemorySize(), 0u);
}
@@ -21,64 +20,42 @@ TEST(Shader, GetType) {
EXPECT_EQ(shader.GetType(), ResourceType::Shader);
}
TEST(Shader, SetGetShaderType) {
TEST(Shader, AddPassVariantStoresStageLanguageAndPayload) {
Shader shader;
shader.SetShaderType(ShaderType::Vertex);
EXPECT_EQ(shader.GetShaderType(), ShaderType::Vertex);
shader.SetShaderType(ShaderType::Fragment);
EXPECT_EQ(shader.GetShaderType(), ShaderType::Fragment);
shader.SetShaderType(ShaderType::Geometry);
EXPECT_EQ(shader.GetShaderType(), ShaderType::Geometry);
shader.SetShaderType(ShaderType::Compute);
EXPECT_EQ(shader.GetShaderType(), ShaderType::Compute);
}
ShaderStageVariant variant = {};
variant.stage = ShaderType::Vertex;
variant.language = ShaderLanguage::HLSL;
variant.backend = ShaderBackend::D3D12;
variant.entryPoint = "MainVS";
variant.profile = "vs_5_0";
variant.sourceCode = "float4 MainVS() : SV_POSITION { return 0; }";
variant.compiledBinary = { 0x00, 0x01, 0x02, 0x03 };
TEST(Shader, SetGetShaderLanguage) {
Shader shader;
shader.SetShaderLanguage(ShaderLanguage::GLSL);
EXPECT_EQ(shader.GetShaderLanguage(), ShaderLanguage::GLSL);
shader.SetShaderLanguage(ShaderLanguage::HLSL);
EXPECT_EQ(shader.GetShaderLanguage(), ShaderLanguage::HLSL);
shader.SetShaderLanguage(ShaderLanguage::SPIRV);
EXPECT_EQ(shader.GetShaderLanguage(), ShaderLanguage::SPIRV);
}
shader.AddPassVariant("ForwardLit", variant);
TEST(Shader, SetGetSourceCode) {
Shader shader;
const char* source = "#version 330 core\nvoid main() {}";
shader.SetSourceCode(source);
EXPECT_EQ(shader.GetSourceCode(), source);
}
TEST(Shader, SetGetCompiledBinary) {
Shader shader;
XCEngine::Containers::Array<XCEngine::Core::uint8> binary = { 0x00, 0x01, 0x02, 0x03 };
shader.SetCompiledBinary(binary);
EXPECT_EQ(shader.GetCompiledBinary().Size(), 4u);
EXPECT_EQ(shader.GetCompiledBinary()[0], 0x00);
EXPECT_EQ(shader.GetCompiledBinary()[3], 0x03);
const ShaderStageVariant* storedVariant =
shader.FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12);
ASSERT_NE(storedVariant, nullptr);
EXPECT_EQ(storedVariant->stage, ShaderType::Vertex);
EXPECT_EQ(storedVariant->language, ShaderLanguage::HLSL);
EXPECT_EQ(storedVariant->entryPoint, "MainVS");
EXPECT_EQ(storedVariant->profile, "vs_5_0");
EXPECT_EQ(storedVariant->sourceCode, "float4 MainVS() : SV_POSITION { return 0; }");
ASSERT_EQ(storedVariant->compiledBinary.Size(), 4u);
EXPECT_EQ(storedVariant->compiledBinary[3], 0x03);
}
TEST(Shader, AddGetUniforms) {
Shader shader;
ShaderUniform uniform1;
uniform1.name = "uModelView";
uniform1.location = 0;
uniform1.size = 1;
uniform1.type = 4;
shader.AddUniform(uniform1);
const auto& uniforms = shader.GetUniforms();
EXPECT_EQ(uniforms.Size(), 1u);
EXPECT_EQ(uniforms[0].name, "uModelView");
@@ -86,36 +63,20 @@ TEST(Shader, AddGetUniforms) {
TEST(Shader, AddGetAttributes) {
Shader shader;
ShaderAttribute attr1;
attr1.name = "aPosition";
attr1.location = 0;
attr1.size = 1;
attr1.type = 3;
shader.AddAttribute(attr1);
const auto& attributes = shader.GetAttributes();
EXPECT_EQ(attributes.Size(), 1u);
EXPECT_EQ(attributes[0].name, "aPosition");
}
TEST(Shader, LegacySingleStageStateSyncsIntoDefaultPassVariant) {
Shader shader;
shader.SetShaderType(ShaderType::Vertex);
shader.SetShaderLanguage(ShaderLanguage::HLSL);
shader.SetSourceCode("float4 MainVS() : SV_POSITION { return 0; }");
ASSERT_EQ(shader.GetPassCount(), 1u);
const ShaderPass* pass = shader.FindPass("Default");
ASSERT_NE(pass, nullptr);
ASSERT_EQ(pass->variants.Size(), 1u);
EXPECT_EQ(pass->variants[0].stage, ShaderType::Vertex);
EXPECT_EQ(pass->variants[0].language, ShaderLanguage::HLSL);
EXPECT_EQ(pass->variants[0].backend, ShaderBackend::Generic);
EXPECT_EQ(pass->variants[0].sourceCode, "float4 MainVS() : SV_POSITION { return 0; }");
}
TEST(Shader, FindsBackendSpecificVariantAndFallsBackToGeneric) {
Shader shader;
@@ -309,7 +270,6 @@ TEST(Shader, StoresPassKeywordDeclarationsAndQueriesDeclaredKeywords) {
TEST(Shader, ReleaseClearsPassRuntimeData) {
Shader shader;
shader.SetSourceCode("void main() {}");
ShaderPropertyDesc property = {};
property.name = "_BaseColor";
property.type = ShaderPropertyType::Color;
@@ -323,8 +283,6 @@ TEST(Shader, ReleaseClearsPassRuntimeData) {
EXPECT_EQ(shader.GetProperties().Size(), 0u);
EXPECT_EQ(shader.GetPassCount(), 0u);
EXPECT_EQ(shader.GetSourceCode(), "");
EXPECT_EQ(shader.GetCompiledBinary().Size(), 0u);
EXPECT_FALSE(shader.IsValid());
}

View File

@@ -1193,6 +1193,171 @@ TEST(ShaderLoader, AssetDatabaseReimportsShaderWhenUsePassDependencyChanges) {
fs::remove_all(projectRoot);
}
TEST(ShaderLoader, AssetDatabaseReimportsShaderWhenBuiltinUsePassDependencyChanges) {
namespace fs = std::filesystem;
using namespace std::chrono_literals;
const fs::path sandboxRoot =
fs::temp_directory_path() / "xc_shader_authoring_builtin_usepass_reimport";
const fs::path projectRoot = sandboxRoot / "Project";
const fs::path shaderDir = projectRoot / "Assets" / "Shaders";
const fs::path mainShaderPath = shaderDir / "main.shader";
const fs::path builtinShaderAssetPath =
sandboxRoot / "engine" / "assets" / "builtin" / "shaders" / "shadow-caster.shader";
const fs::path previousPath = fs::current_path();
fs::remove_all(sandboxRoot);
fs::create_directories(shaderDir);
fs::create_directories(builtinShaderAssetPath.parent_path());
auto writeBuiltinShader = [&](const char* cullMode, const char* marker) {
WriteTextFile(
builtinShaderAssetPath,
std::string(R"(Shader "Builtin Shadow Caster"
{
SubShader
{
Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
Cull )") +
cullMode +
R"(
HLSLPROGRAM
#pragma vertex MainVS
#pragma fragment MainPS
float4 MainVS() : SV_POSITION { return 0; }
float4 MainPS() : SV_TARGET { return )" +
marker +
R"(; }
ENDHLSL
}
}
}
)");
};
writeBuiltinShader("Back", "float4(1.0, 0.0, 0.0, 1.0)");
WriteTextFile(
mainShaderPath,
R"(Shader "Builtin UsePass Dependency Shader"
{
SubShader
{
UsePass "Builtin Shadow Caster/ShadowCaster"
}
}
)");
fs::current_path(projectRoot);
ShaderLoader dependencyLoader;
Array<String> firstDependencies;
ASSERT_TRUE(dependencyLoader.CollectSourceDependencies(mainShaderPath.string().c_str(), firstDependencies));
ASSERT_EQ(firstDependencies.Size(), 1u);
EXPECT_EQ(
fs::path(firstDependencies[0].CStr()).lexically_normal(),
builtinShaderAssetPath.lexically_normal());
AssetDatabase database;
database.Initialize(projectRoot.string().c_str());
AssetDatabase::ResolvedAsset firstResolve;
ASSERT_TRUE(database.EnsureArtifact("Assets/Shaders/main.shader", ResourceType::Shader, firstResolve));
ASSERT_TRUE(firstResolve.artifactReady);
ShaderLoader loader;
LoadResult firstLoad = loader.Load(firstResolve.artifactMainPath.CStr());
ASSERT_TRUE(firstLoad);
auto* firstShader = static_cast<Shader*>(firstLoad.resource);
ASSERT_NE(firstShader, nullptr);
const ShaderPass* firstPass = firstShader->FindPass("ShadowCaster");
ASSERT_NE(firstPass, nullptr);
EXPECT_EQ(firstPass->fixedFunctionState.cullMode, MaterialCullMode::Back);
const ShaderStageVariant* firstFragmentVariant =
firstShader->FindVariant("ShadowCaster", ShaderType::Fragment, ShaderBackend::D3D12);
ASSERT_NE(firstFragmentVariant, nullptr);
EXPECT_NE(
std::string(firstFragmentVariant->sourceCode.CStr()).find("float4(1.0, 0.0, 0.0, 1.0)"),
std::string::npos);
delete firstShader;
const String firstArtifactPath = firstResolve.artifactMainPath;
database.Shutdown();
std::this_thread::sleep_for(50ms);
writeBuiltinShader("Front", "float4(0.0, 1.0, 0.0, 1.0)");
database.Initialize(projectRoot.string().c_str());
AssetDatabase::ResolvedAsset secondResolve;
ASSERT_TRUE(database.EnsureArtifact("Assets/Shaders/main.shader", ResourceType::Shader, secondResolve));
ASSERT_TRUE(secondResolve.artifactReady);
EXPECT_NE(secondResolve.artifactMainPath, firstArtifactPath);
LoadResult secondLoad = loader.Load(secondResolve.artifactMainPath.CStr());
ASSERT_TRUE(secondLoad);
auto* secondShader = static_cast<Shader*>(secondLoad.resource);
ASSERT_NE(secondShader, nullptr);
const ShaderPass* secondPass = secondShader->FindPass("ShadowCaster");
ASSERT_NE(secondPass, nullptr);
EXPECT_EQ(secondPass->fixedFunctionState.cullMode, MaterialCullMode::Front);
const ShaderStageVariant* secondFragmentVariant =
secondShader->FindVariant("ShadowCaster", ShaderType::Fragment, ShaderBackend::D3D12);
ASSERT_NE(secondFragmentVariant, nullptr);
EXPECT_NE(
std::string(secondFragmentVariant->sourceCode.CStr()).find("float4(0.0, 1.0, 0.0, 1.0)"),
std::string::npos);
delete secondShader;
database.Shutdown();
fs::current_path(previousPath);
fs::remove_all(sandboxRoot);
}
TEST(ShaderLoader, LoadShaderAuthoringRejectsCyclicProjectUsePass) {
namespace fs = std::filesystem;
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_authoring_usepass_cycle";
const fs::path shaderAPath = shaderRoot / "a.shader";
const fs::path shaderBPath = shaderRoot / "b.shader";
fs::remove_all(shaderRoot);
fs::create_directories(shaderRoot);
WriteTextFile(
shaderAPath,
R"(Shader "Cycle Shader A"
{
SubShader
{
UsePass "Cycle Shader B/PassB"
}
}
)");
WriteTextFile(
shaderBPath,
R"(Shader "Cycle Shader B"
{
SubShader
{
UsePass "Cycle Shader A/PassA"
}
}
)");
ShaderLoader loader;
LoadResult result = loader.Load(shaderAPath.string().c_str());
EXPECT_FALSE(result.success);
EXPECT_EQ(result.resource, nullptr);
EXPECT_NE(
std::string(result.errorMessage.CStr()).find("cyclic shader reference"),
std::string::npos);
fs::remove_all(shaderRoot);
}
TEST(ShaderLoader, LoadShaderAuthoringRejectsLegacyBackendPragma) {
namespace fs = std::filesystem;