Close shader authoring pipeline and UsePass dependency tracking
This commit is contained in:
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user