rendering: strip redundant builtin material pass hints
This commit is contained in:
@@ -45,6 +45,23 @@ inline bool IsObjectIdPassName(const Containers::String& value) {
|
||||
normalized == Containers::String("editorobjectid");
|
||||
}
|
||||
|
||||
inline bool IsSkyboxPassName(const Containers::String& value) {
|
||||
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
|
||||
return normalized == Containers::String("skybox");
|
||||
}
|
||||
|
||||
inline bool IsPostProcessPassName(const Containers::String& value) {
|
||||
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
|
||||
return normalized == Containers::String("postprocess") ||
|
||||
normalized == Containers::String("colorscale");
|
||||
}
|
||||
|
||||
inline bool IsFinalColorPassName(const Containers::String& value) {
|
||||
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
|
||||
return normalized == Containers::String("finaloutput") ||
|
||||
normalized == Containers::String("finalcolor");
|
||||
}
|
||||
|
||||
inline bool MatchesBuiltinPassName(
|
||||
const Containers::String& value,
|
||||
BuiltinMaterialPass pass) {
|
||||
@@ -59,11 +76,61 @@ inline bool MatchesBuiltinPassName(
|
||||
return IsShadowCasterPassName(value);
|
||||
case BuiltinMaterialPass::ObjectId:
|
||||
return IsObjectIdPassName(value);
|
||||
case BuiltinMaterialPass::Skybox:
|
||||
return IsSkyboxPassName(value);
|
||||
case BuiltinMaterialPass::PostProcess:
|
||||
return IsPostProcessPassName(value);
|
||||
case BuiltinMaterialPass::FinalColor:
|
||||
return IsFinalColorPassName(value);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool TryResolveBuiltinMaterialPassHint(
|
||||
const Containers::String& value,
|
||||
BuiltinMaterialPass& outPass) {
|
||||
const Containers::String normalized = NormalizeBuiltinPassMetadataValue(value);
|
||||
if (normalized.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsForwardPassName(normalized)) {
|
||||
outPass = BuiltinMaterialPass::ForwardLit;
|
||||
return true;
|
||||
}
|
||||
if (IsUnlitPassName(normalized)) {
|
||||
outPass = BuiltinMaterialPass::Unlit;
|
||||
return true;
|
||||
}
|
||||
if (IsDepthOnlyPassName(normalized)) {
|
||||
outPass = BuiltinMaterialPass::DepthOnly;
|
||||
return true;
|
||||
}
|
||||
if (IsShadowCasterPassName(normalized)) {
|
||||
outPass = BuiltinMaterialPass::ShadowCaster;
|
||||
return true;
|
||||
}
|
||||
if (IsObjectIdPassName(normalized)) {
|
||||
outPass = BuiltinMaterialPass::ObjectId;
|
||||
return true;
|
||||
}
|
||||
if (IsSkyboxPassName(normalized)) {
|
||||
outPass = BuiltinMaterialPass::Skybox;
|
||||
return true;
|
||||
}
|
||||
if (IsPostProcessPassName(normalized)) {
|
||||
outPass = BuiltinMaterialPass::PostProcess;
|
||||
return true;
|
||||
}
|
||||
if (IsFinalColorPassName(normalized)) {
|
||||
outPass = BuiltinMaterialPass::FinalColor;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool ShaderPassHasExplicitBuiltinMetadata(const Resources::ShaderPass& shaderPass) {
|
||||
if (!shaderPass.name.Empty() &&
|
||||
shaderPass.name != Containers::String("Default")) {
|
||||
@@ -116,6 +183,27 @@ inline bool ShaderPassMatchesBuiltinPass(
|
||||
return hasMetadata;
|
||||
}
|
||||
|
||||
inline bool IsRedundantLegacyMaterialShaderPassHint(
|
||||
const Resources::Shader* shader,
|
||||
const Containers::String& shaderPass) {
|
||||
if (shader == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BuiltinMaterialPass builtinPass = BuiltinMaterialPass::ForwardLit;
|
||||
if (!TryResolveBuiltinMaterialPassHint(shaderPass, builtinPass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const Resources::ShaderPass& pass : shader->GetPasses()) {
|
||||
if (ShaderPassMatchesBuiltinPass(pass, builtinPass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic(
|
||||
const Resources::ShaderResourceBindingDesc& binding) {
|
||||
Containers::String semantic = NormalizeBuiltinPassMetadataValue(binding.semantic);
|
||||
@@ -143,11 +231,34 @@ inline BuiltinPassResourceSemantic ResolveBuiltinPassResourceSemantic(
|
||||
return BuiltinPassResourceSemantic::ShadowReceiver;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("environment") ||
|
||||
semantic == Containers::String("environmentconstants")) {
|
||||
return BuiltinPassResourceSemantic::Environment;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("passconstants") ||
|
||||
semantic == Containers::String("finalcolorconstants") ||
|
||||
semantic == Containers::String("postprocessconstants")) {
|
||||
return BuiltinPassResourceSemantic::PassConstants;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("basecolortexture") ||
|
||||
semantic == Containers::String("maintex")) {
|
||||
return BuiltinPassResourceSemantic::BaseColorTexture;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("sourcecolortexture")) {
|
||||
return BuiltinPassResourceSemantic::SourceColorTexture;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("skyboxpanoramictexture")) {
|
||||
return BuiltinPassResourceSemantic::SkyboxPanoramicTexture;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("skyboxtexture")) {
|
||||
return BuiltinPassResourceSemantic::SkyboxTexture;
|
||||
}
|
||||
|
||||
if (semantic == Containers::String("shadowmaptexture") ||
|
||||
semantic == Containers::String("shadowmap")) {
|
||||
return BuiltinPassResourceSemantic::ShadowMapTexture;
|
||||
@@ -175,8 +286,18 @@ inline const char* BuiltinPassResourceSemanticToString(BuiltinPassResourceSemant
|
||||
return "Lighting";
|
||||
case BuiltinPassResourceSemantic::ShadowReceiver:
|
||||
return "ShadowReceiver";
|
||||
case BuiltinPassResourceSemantic::Environment:
|
||||
return "Environment";
|
||||
case BuiltinPassResourceSemantic::PassConstants:
|
||||
return "PassConstants";
|
||||
case BuiltinPassResourceSemantic::BaseColorTexture:
|
||||
return "BaseColorTexture";
|
||||
case BuiltinPassResourceSemantic::SourceColorTexture:
|
||||
return "SourceColorTexture";
|
||||
case BuiltinPassResourceSemantic::SkyboxPanoramicTexture:
|
||||
return "SkyboxPanoramicTexture";
|
||||
case BuiltinPassResourceSemantic::SkyboxTexture:
|
||||
return "SkyboxTexture";
|
||||
case BuiltinPassResourceSemantic::ShadowMapTexture:
|
||||
return "ShadowMapTexture";
|
||||
case BuiltinPassResourceSemantic::LinearClampSampler:
|
||||
@@ -238,11 +359,16 @@ inline bool IsBuiltinPassResourceTypeCompatible(
|
||||
case BuiltinPassResourceSemantic::Material:
|
||||
case BuiltinPassResourceSemantic::Lighting:
|
||||
case BuiltinPassResourceSemantic::ShadowReceiver:
|
||||
case BuiltinPassResourceSemantic::Environment:
|
||||
case BuiltinPassResourceSemantic::PassConstants:
|
||||
return type == Resources::ShaderResourceType::ConstantBuffer;
|
||||
case BuiltinPassResourceSemantic::BaseColorTexture:
|
||||
case BuiltinPassResourceSemantic::SourceColorTexture:
|
||||
case BuiltinPassResourceSemantic::SkyboxPanoramicTexture:
|
||||
case BuiltinPassResourceSemantic::ShadowMapTexture:
|
||||
return type == Resources::ShaderResourceType::Texture2D ||
|
||||
type == Resources::ShaderResourceType::TextureCube;
|
||||
return type == Resources::ShaderResourceType::Texture2D;
|
||||
case BuiltinPassResourceSemantic::SkyboxTexture:
|
||||
return type == Resources::ShaderResourceType::TextureCube;
|
||||
case BuiltinPassResourceSemantic::LinearClampSampler:
|
||||
case BuiltinPassResourceSemantic::ShadowMapSampler:
|
||||
return type == Resources::ShaderResourceType::Sampler;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Debug/Logger.h>
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h>
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
@@ -424,6 +425,19 @@ Containers::String ResolveTextureBindingPath(
|
||||
return NormalizeArtifactPathString(material.GetTextureBindingPath(bindingIndex));
|
||||
}
|
||||
|
||||
Containers::String ResolveSerializedMaterialShaderPass(const Material& material) {
|
||||
const Containers::String& shaderPass = material.GetShaderPass();
|
||||
if (shaderPass.Empty()) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
if (Rendering::IsRedundantLegacyMaterialShaderPassHint(material.GetShader(), shaderPass)) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
return shaderPass;
|
||||
}
|
||||
|
||||
bool WriteMaterialArtifactFile(
|
||||
const fs::path& artifactPath,
|
||||
const Material& material,
|
||||
@@ -445,7 +459,7 @@ bool WriteMaterialArtifactFile(
|
||||
material.GetShader() != nullptr
|
||||
? material.GetShader()->GetPath()
|
||||
: Containers::String());
|
||||
WriteString(output, material.GetShaderPass());
|
||||
WriteString(output, ResolveSerializedMaterialShaderPass(material));
|
||||
|
||||
MaterialArtifactHeader header;
|
||||
header.renderQueue = material.GetRenderQueue();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
@@ -1472,6 +1473,18 @@ bool MaterialFileExists(const Containers::String& path) {
|
||||
return std::filesystem::exists(std::filesystem::path(resourceRoot.CStr()) / inputPath);
|
||||
}
|
||||
|
||||
void ApplyMaterialShaderPassHint(Material* material, const Containers::String& shaderPass) {
|
||||
if (material == nullptr || shaderPass.Empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Rendering::IsRedundantLegacyMaterialShaderPassHint(material->GetShader(), shaderPass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
material->SetShaderPass(shaderPass);
|
||||
}
|
||||
|
||||
ResourceHandle<Shader> LoadShaderHandle(const Containers::String& shaderPath);
|
||||
|
||||
template<typename T>
|
||||
@@ -1560,9 +1573,10 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
|
||||
|
||||
const std::string magic(fileHeader.magic, fileHeader.magic + 7);
|
||||
const bool isLegacySchema = magic == "XCMAT02" && fileHeader.schemaVersion == 2u;
|
||||
const bool isSchemaV3 = magic == "XCMAT03" && fileHeader.schemaVersion == 3u;
|
||||
const bool isCurrentSchema =
|
||||
magic == "XCMAT03" && fileHeader.schemaVersion == kMaterialArtifactSchemaVersion;
|
||||
if (!isLegacySchema && !isCurrentSchema) {
|
||||
magic == "XCMAT04" && fileHeader.schemaVersion == kMaterialArtifactSchemaVersion;
|
||||
if (!isLegacySchema && !isSchemaV3 && !isCurrentSchema) {
|
||||
return LoadResult("Invalid material artifact magic: " + path);
|
||||
}
|
||||
|
||||
@@ -1594,9 +1608,7 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
|
||||
material->SetShader(shaderHandle);
|
||||
}
|
||||
}
|
||||
if (!shaderPass.Empty()) {
|
||||
material->SetShaderPass(shaderPass);
|
||||
}
|
||||
ApplyMaterialShaderPassHint(material.get(), shaderPass);
|
||||
|
||||
MaterialArtifactHeader header = {};
|
||||
if (isLegacySchema) {
|
||||
@@ -1608,6 +1620,20 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
|
||||
header.renderQueue = legacyHeader.renderQueue;
|
||||
header.renderState = legacyHeader.renderState;
|
||||
header.tagCount = legacyHeader.tagCount;
|
||||
header.hasRenderStateOverride = 1u;
|
||||
header.propertyCount = legacyHeader.propertyCount;
|
||||
header.textureBindingCount = legacyHeader.textureBindingCount;
|
||||
} else if (isSchemaV3) {
|
||||
MaterialArtifactHeaderV3 legacyHeader = {};
|
||||
if (!ReadMaterialArtifactValue(data, offset, legacyHeader)) {
|
||||
return LoadResult("Failed to parse material artifact body: " + path);
|
||||
}
|
||||
|
||||
header.renderQueue = legacyHeader.renderQueue;
|
||||
header.renderState = legacyHeader.renderState;
|
||||
header.tagCount = legacyHeader.tagCount;
|
||||
header.hasRenderStateOverride = 1u;
|
||||
header.keywordCount = legacyHeader.keywordCount;
|
||||
header.propertyCount = legacyHeader.propertyCount;
|
||||
header.textureBindingCount = legacyHeader.textureBindingCount;
|
||||
} else {
|
||||
@@ -1618,6 +1644,7 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
|
||||
|
||||
material->SetRenderQueue(header.renderQueue);
|
||||
material->SetRenderState(header.renderState);
|
||||
material->SetRenderStateOverrideEnabled(header.hasRenderStateOverride != 0u);
|
||||
|
||||
for (Core::uint32 tagIndex = 0; tagIndex < header.tagCount; ++tagIndex) {
|
||||
Containers::String tagName;
|
||||
@@ -1772,12 +1799,12 @@ bool MaterialLoader::ParseMaterialData(const Containers::Array<Core::uint8>& dat
|
||||
if (!TryParseStringValue(jsonText, "shaderPass", shaderPass)) {
|
||||
return false;
|
||||
}
|
||||
material->SetShaderPass(shaderPass);
|
||||
ApplyMaterialShaderPassHint(material, shaderPass);
|
||||
} else if (HasKey(jsonText, "pass")) {
|
||||
if (!TryParseStringValue(jsonText, "pass", shaderPass)) {
|
||||
return false;
|
||||
}
|
||||
material->SetShaderPass(shaderPass);
|
||||
ApplyMaterialShaderPassHint(material, shaderPass);
|
||||
}
|
||||
|
||||
if (HasKey(jsonText, "renderQueue")) {
|
||||
|
||||
@@ -112,7 +112,6 @@ Material* CreateQuadMaterial(Texture* texture) {
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinUnlitShaderPath()));
|
||||
material->SetShaderPass("Unlit");
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
return material;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,6 @@ Material* CreateQuadMaterial(Texture* texture) {
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinUnlitShaderPath()));
|
||||
material->SetShaderPass("Unlit");
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
return material;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,6 @@ Material* CreateQuadMaterial(Texture* texture) {
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinUnlitShaderPath()));
|
||||
material->SetShaderPass("Unlit");
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
return material;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ Mesh* CreateQuadMesh() {
|
||||
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
|
||||
vertices[3].uv0 = Vector2(1.0f, 0.0f);
|
||||
|
||||
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
|
||||
const uint32_t indices[6] = { 0, 2, 1, 2, 3, 1 };
|
||||
mesh->SetVertexData(
|
||||
vertices,
|
||||
sizeof(vertices),
|
||||
@@ -117,7 +117,6 @@ Material* CreateMaterial(Texture* texture) {
|
||||
params.guid = ResourceGUID::Generate(params.path);
|
||||
material->Initialize(params);
|
||||
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinUnlitShaderPath()));
|
||||
material->SetShaderPass("Unlit");
|
||||
material->SetTexture("_MainTex", ResourceHandle<Texture>(texture));
|
||||
return material;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Core/Asset/AssetDatabase.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
@@ -251,12 +252,36 @@ TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
|
||||
EXPECT_EQ(material->GetRenderState().dstBlend, MaterialBlendFactor::InvSrcAlpha);
|
||||
EXPECT_FALSE(material->GetRenderState().depthWriteEnable);
|
||||
EXPECT_EQ(material->GetRenderState().depthFunc, MaterialComparisonFunc::LessEqual);
|
||||
EXPECT_TRUE(material->HasRenderStateOverride());
|
||||
|
||||
delete material;
|
||||
std::remove(materialPath.string().c_str());
|
||||
std::remove(shaderPath.string().c_str());
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, LoadMaterialWithoutRenderStateLeavesOverrideDisabled) {
|
||||
const std::filesystem::path materialPath =
|
||||
std::filesystem::current_path() / "material_loader_no_render_state.material";
|
||||
|
||||
{
|
||||
std::ofstream materialFile(materialPath);
|
||||
ASSERT_TRUE(materialFile.is_open());
|
||||
materialFile << "{ \"renderQueue\": \"Geometry\" }";
|
||||
}
|
||||
|
||||
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_FALSE(material->HasRenderStateOverride());
|
||||
|
||||
delete material;
|
||||
std::remove(materialPath.string().c_str());
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, LoadMaterialWithShaderManifestResolvesShaderPass) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -319,7 +344,7 @@ TEST(MaterialLoader, LoadMaterialWithShaderManifestResolvesShaderPass) {
|
||||
Material* material = static_cast<Material*>(result.resource);
|
||||
ASSERT_NE(material, nullptr);
|
||||
ASSERT_NE(material->GetShader(), nullptr);
|
||||
EXPECT_EQ(material->GetShaderPass(), "ForwardLit");
|
||||
EXPECT_TRUE(material->GetShaderPass().Empty());
|
||||
ASSERT_NE(material->GetShader()->FindPass("ForwardLit"), nullptr);
|
||||
const ShaderStageVariant* vertexVariant =
|
||||
material->GetShader()->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::OpenGL);
|
||||
@@ -361,7 +386,7 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) {
|
||||
auto* material = static_cast<Material*>(result.resource);
|
||||
ASSERT_NE(material, nullptr);
|
||||
ASSERT_NE(material->GetShader(), nullptr);
|
||||
EXPECT_EQ(material->GetShaderPass(), "ForwardLit");
|
||||
EXPECT_TRUE(material->GetShaderPass().Empty());
|
||||
EXPECT_EQ(material->GetFloat4("_BaseColor"), XCEngine::Math::Vector4(0.2f, 0.4f, 0.6f, 0.8f));
|
||||
EXPECT_FLOAT_EQ(material->GetFloat("_Metallic"), 0.15f);
|
||||
EXPECT_EQ(material->GetInt("_Mode"), 5);
|
||||
@@ -372,6 +397,40 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) {
|
||||
fs::remove_all(rootPath);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, LoadBuiltinShaderMaterialDropsRedundantBuiltinShaderPassHint) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_builtin_hint_strip_test";
|
||||
fs::remove_all(rootPath);
|
||||
fs::create_directories(rootPath);
|
||||
|
||||
const fs::path materialPath = rootPath / "builtin_unlit.material";
|
||||
WriteTextFile(
|
||||
materialPath,
|
||||
"{\n"
|
||||
" \"shader\": \"" + std::string(GetBuiltinUnlitShaderPath().CStr()) + "\",\n"
|
||||
" \"shaderPass\": \"Unlit\"\n"
|
||||
"}\n");
|
||||
|
||||
MaterialLoader loader;
|
||||
LoadResult result = loader.Load(materialPath.generic_string().c_str());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* material = static_cast<Material*>(result.resource);
|
||||
ASSERT_NE(material, nullptr);
|
||||
ASSERT_NE(material->GetShader(), nullptr);
|
||||
EXPECT_TRUE(material->GetShaderPass().Empty());
|
||||
EXPECT_NE(material->GetShader()->FindPass("Unlit"), nullptr);
|
||||
|
||||
delete material;
|
||||
manager.Shutdown();
|
||||
fs::remove_all(rootPath);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, RejectsUnknownPropertyAgainstShaderSchema) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -676,6 +735,43 @@ TEST(MaterialLoader, AssetDatabaseCreatesMaterialArtifact) {
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, AssetDatabaseMaterialArtifactPreservesImplicitRenderStateFlag) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_material_implicit_render_state_artifact";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
const fs::path materialPath = assetsDir / "implicit.material";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(assetsDir);
|
||||
|
||||
WriteTextFile(
|
||||
materialPath,
|
||||
"{\n"
|
||||
" \"renderQueue\": \"Geometry\"\n"
|
||||
"}\n");
|
||||
|
||||
AssetDatabase database;
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase::ResolvedAsset resolvedAsset;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/implicit.material", ResourceType::Material, resolvedAsset));
|
||||
ASSERT_TRUE(resolvedAsset.artifactReady);
|
||||
|
||||
MaterialLoader loader;
|
||||
LoadResult result = loader.Load(resolvedAsset.artifactMainPath.CStr());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* material = static_cast<Material*>(result.resource);
|
||||
ASSERT_NE(material, nullptr);
|
||||
EXPECT_FALSE(material->HasRenderStateOverride());
|
||||
|
||||
delete material;
|
||||
database.Shutdown();
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, AssetDatabaseMaterialArtifactRoundTripsKeywords) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -728,6 +824,57 @@ TEST(MaterialLoader, AssetDatabaseMaterialArtifactRoundTripsKeywords) {
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, AssetDatabaseMaterialArtifactStripsRedundantBuiltinShaderPassHint) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_material_artifact_builtin_hint_strip_test";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
const fs::path materialPath = assetsDir / "builtin_unlit.material";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(assetsDir);
|
||||
|
||||
WriteTextFile(
|
||||
materialPath,
|
||||
"{\n"
|
||||
" \"shader\": \"" + std::string(GetBuiltinUnlitShaderPath().CStr()) + "\",\n"
|
||||
" \"shaderPass\": \"Unlit\",\n"
|
||||
" \"properties\": {\n"
|
||||
" \"_BaseColor\": [1.0, 1.0, 1.0, 1.0]\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
manager.RefreshProjectAssets();
|
||||
|
||||
AssetDatabase database;
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase::ResolvedAsset resolvedAsset;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/builtin_unlit.material", ResourceType::Material, resolvedAsset));
|
||||
ASSERT_TRUE(resolvedAsset.artifactReady);
|
||||
|
||||
MaterialLoader loader;
|
||||
LoadResult result = loader.Load(resolvedAsset.artifactMainPath.CStr());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* material = static_cast<Material*>(result.resource);
|
||||
ASSERT_NE(material, nullptr);
|
||||
ASSERT_NE(material->GetShader(), nullptr);
|
||||
EXPECT_TRUE(material->GetShaderPass().Empty());
|
||||
EXPECT_EQ(material->GetFloat4("_BaseColor"), XCEngine::Math::Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
delete material;
|
||||
database.Shutdown();
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, ResourceManagerLoadsProjectMaterialTextureAsLazyDependency) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user