Tighten material loader schema parsing

This commit is contained in:
2026-04-08 01:10:51 +08:00
parent 077a6b0a51
commit 6e6a98a022
2 changed files with 182 additions and 177 deletions

View File

@@ -1,7 +1,6 @@
#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>
@@ -572,6 +571,21 @@ bool TryParseFloatText(const std::string& text, float& outValue) {
return true;
}
bool TryParseFloatValue(const std::string& json, const char* key, float& outValue) {
size_t valuePos = 0;
if (!FindValueStart(json, key, valuePos)) {
return false;
}
std::string rawValue;
JsonRawValueType rawType = JsonRawValueType::Invalid;
if (!TryExtractRawValue(json, valuePos, rawValue, rawType) || rawType != JsonRawValueType::Number) {
return false;
}
return TryParseFloatText(rawValue, outValue);
}
bool TryParseIntText(const std::string& text, Core::int32& outValue) {
const std::string trimmed = TrimCopy(text);
if (trimmed.empty()) {
@@ -641,91 +655,14 @@ bool TryParseFloatListText(const std::string& text,
return outCount > 0;
}
Containers::String NormalizeMaterialLookupToken(const Containers::String& value) {
std::string normalized;
normalized.reserve(value.Length());
for (size_t index = 0; index < value.Length(); ++index) {
const unsigned char ch = static_cast<unsigned char>(value[index]);
if (std::isalnum(ch) != 0) {
normalized.push_back(static_cast<char>(std::tolower(ch)));
}
}
return Containers::String(normalized.c_str());
}
const ShaderPropertyDesc* FindShaderPropertyBySemantic(
const Shader* shader,
const Containers::String& semantic) {
if (shader == nullptr || semantic.Empty()) {
return nullptr;
}
const Containers::String normalizedSemantic = NormalizeMaterialLookupToken(semantic);
for (const ShaderPropertyDesc& property : shader->GetProperties()) {
if (NormalizeMaterialLookupToken(property.semantic) == normalizedSemantic) {
return &property;
}
}
return nullptr;
}
Containers::String ResolveLegacyMaterialSemanticAlias(
const Containers::String& propertyName,
JsonRawValueType rawType) {
const Containers::String normalizedName = NormalizeMaterialLookupToken(propertyName);
if (rawType == JsonRawValueType::Array ||
rawType == JsonRawValueType::Number) {
if (normalizedName == "basecolor" ||
normalizedName == "color") {
return Containers::String("BaseColor");
}
}
if (rawType == JsonRawValueType::String) {
if (normalizedName == "basecolortexture" ||
normalizedName == "maintex" ||
normalizedName == "maintexture" ||
normalizedName == "albedotexture" ||
normalizedName == "texture") {
return Containers::String("BaseColorTexture");
}
}
return Containers::String();
}
const ShaderPropertyDesc* ResolveShaderPropertyForMaterialKey(
const Shader* shader,
const Containers::String& propertyName,
JsonRawValueType rawType) {
const Containers::String& propertyName) {
if (shader == nullptr || propertyName.Empty()) {
return nullptr;
}
if (const ShaderPropertyDesc* property = shader->FindProperty(propertyName)) {
return property;
}
const Containers::String normalizedName = NormalizeMaterialLookupToken(propertyName);
for (const ShaderPropertyDesc& property : shader->GetProperties()) {
if (NormalizeMaterialLookupToken(property.name) == normalizedName) {
return &property;
}
}
if (const ShaderPropertyDesc* property = FindShaderPropertyBySemantic(shader, propertyName)) {
return property;
}
const Containers::String semanticAlias = ResolveLegacyMaterialSemanticAlias(propertyName, rawType);
if (!semanticAlias.Empty()) {
return FindShaderPropertyBySemantic(shader, semanticAlias);
}
return nullptr;
return shader->FindProperty(propertyName);
}
bool TryApplySchemaMaterialProperty(Material* material,
@@ -875,7 +812,7 @@ bool TryParseMaterialPropertiesObject(const std::string& objectText, Material* m
const Shader* shader = material->GetShader();
const ShaderPropertyDesc* shaderProperty =
ResolveShaderPropertyForMaterialKey(shader, propertyName, rawType);
ResolveShaderPropertyForMaterialKey(shader, propertyName);
if (shader != nullptr && shaderProperty == nullptr) {
return false;
}
@@ -1105,7 +1042,7 @@ bool TryApplyTexturePath(Material* material,
const Shader* shader = material->GetShader();
const ShaderPropertyDesc* shaderProperty =
ResolveShaderPropertyForMaterialKey(shader, textureName, JsonRawValueType::String);
ResolveShaderPropertyForMaterialKey(shader, textureName);
if (shader != nullptr && shaderProperty == nullptr) {
return false;
}
@@ -1332,6 +1269,123 @@ bool TryParseBlendOp(const Containers::String& value, MaterialBlendOp& outOp) {
return false;
}
bool TryParseStencilOp(const Containers::String& value, MaterialStencilOp& outOp) {
const Containers::String normalized = value.Trim().ToLower();
if (normalized == "keep") {
outOp = MaterialStencilOp::Keep;
return true;
}
if (normalized == "zero") {
outOp = MaterialStencilOp::Zero;
return true;
}
if (normalized == "replace") {
outOp = MaterialStencilOp::Replace;
return true;
}
if (normalized == "incrsat" || normalized == "incrementclamp" || normalized == "increment_clamp") {
outOp = MaterialStencilOp::IncrSat;
return true;
}
if (normalized == "decrsat" || normalized == "decrementclamp" || normalized == "decrement_clamp") {
outOp = MaterialStencilOp::DecrSat;
return true;
}
if (normalized == "invert") {
outOp = MaterialStencilOp::Invert;
return true;
}
if (normalized == "incrwrap" || normalized == "incr" || normalized == "incrementwrap" || normalized == "increment_wrap") {
outOp = MaterialStencilOp::IncrWrap;
return true;
}
if (normalized == "decrwrap" || normalized == "decr" || normalized == "decrementwrap" || normalized == "decrement_wrap") {
outOp = MaterialStencilOp::DecrWrap;
return true;
}
return false;
}
bool TryParseStencilStateObject(const std::string& objectText, MaterialStencilState& outState) {
if (objectText.empty() || objectText.front() != '{' || objectText.back() != '}') {
return false;
}
MaterialStencilState stencilState = outState;
stencilState.enabled = true;
bool boolValue = false;
if (HasKey(objectText, "enabled")) {
if (!TryParseBoolValue(objectText, "enabled", boolValue)) {
return false;
}
stencilState.enabled = boolValue;
}
Core::int32 intValue = 0;
auto parseMaskValue = [&](const char* key, Core::uint8& outValue) -> bool {
if (!HasKey(objectText, key)) {
return true;
}
if (!TryParseIntValue(objectText, key, intValue) || intValue < 0 || intValue > 0xFF) {
return false;
}
outValue = static_cast<Core::uint8>(intValue);
return true;
};
if (!parseMaskValue("readMask", stencilState.readMask) ||
!parseMaskValue("writeMask", stencilState.writeMask) ||
!parseMaskValue("ref", stencilState.reference) ||
!parseMaskValue("reference", stencilState.reference)) {
return false;
}
Containers::String enumValue;
auto parseFaceCompare = [&](const char* key, MaterialComparisonFunc& outFunc) -> bool {
if (!HasKey(objectText, key)) {
return true;
}
return TryParseStringValue(objectText, key, enumValue) &&
TryParseComparisonFunc(enumValue, outFunc);
};
auto parseFaceOp = [&](const char* key, MaterialStencilOp& outOp) -> bool {
if (!HasKey(objectText, key)) {
return true;
}
return TryParseStringValue(objectText, key, enumValue) &&
TryParseStencilOp(enumValue, outOp);
};
if (!parseFaceCompare("comp", stencilState.front.func) ||
!parseFaceCompare("comp", stencilState.back.func) ||
!parseFaceOp("pass", stencilState.front.passOp) ||
!parseFaceOp("pass", stencilState.back.passOp) ||
!parseFaceOp("fail", stencilState.front.failOp) ||
!parseFaceOp("fail", stencilState.back.failOp) ||
!parseFaceOp("zFail", stencilState.front.depthFailOp) ||
!parseFaceOp("zFail", stencilState.back.depthFailOp) ||
!parseFaceCompare("compFront", stencilState.front.func) ||
!parseFaceOp("passFront", stencilState.front.passOp) ||
!parseFaceOp("failFront", stencilState.front.failOp) ||
!parseFaceOp("zFailFront", stencilState.front.depthFailOp) ||
!parseFaceCompare("compBack", stencilState.back.func) ||
!parseFaceOp("passBack", stencilState.back.passOp) ||
!parseFaceOp("failBack", stencilState.back.failOp) ||
!parseFaceOp("zFailBack", stencilState.back.depthFailOp)) {
return false;
}
outState = stencilState;
return true;
}
bool TryParseRenderStateObject(const std::string& objectText, Material* material) {
if (material == nullptr || objectText.empty() || objectText.front() != '{' || objectText.back() != '}') {
return false;
@@ -1435,6 +1489,42 @@ bool TryParseRenderStateObject(const std::string& objectText, Material* material
return false;
}
}
if (HasKey(objectText, "offset")) {
std::string offsetArray;
if (!TryExtractArray(objectText, "offset", offsetArray)) {
return false;
}
float offsetValues[2] = {};
size_t offsetCount = 0;
if (!TryParseFloatListText(offsetArray, offsetValues, 2, offsetCount) || offsetCount != 2u) {
return false;
}
renderState.depthBiasFactor = offsetValues[0];
renderState.depthBiasUnits = static_cast<Core::int32>(offsetValues[1]);
}
if (HasKey(objectText, "offsetFactor")) {
float floatValue = 0.0f;
if (!TryParseFloatValue(objectText, "offsetFactor", floatValue)) {
return false;
}
renderState.depthBiasFactor = floatValue;
}
if (HasKey(objectText, "offsetUnits")) {
Core::int32 offsetUnits = 0;
if (!TryParseIntValue(objectText, "offsetUnits", offsetUnits)) {
return false;
}
renderState.depthBiasUnits = offsetUnits;
}
if (HasKey(objectText, "stencil")) {
std::string stencilObject;
if (!TryExtractObject(objectText, "stencil", stencilObject) ||
!TryParseStencilStateObject(stencilObject, renderState.stencil)) {
return false;
}
}
material->SetRenderState(renderState);
return true;
@@ -1473,18 +1563,6 @@ bool MaterialFileExists(const Containers::String& path) {
return std::filesystem::exists(std::filesystem::path(resourceRoot.CStr()) / inputPath);
}
void ApplyLegacyMaterialShaderPassHint(Material* material, const Containers::String& shaderPass) {
if (material == nullptr || shaderPass.Empty()) {
return;
}
if (Rendering::IsRedundantLegacyMaterialShaderPassHint(material->GetShader(), shaderPass)) {
return;
}
material->SetLegacyShaderPassHint(shaderPass);
}
ResourceHandle<Shader> LoadShaderHandle(const Containers::String& shaderPath);
template<typename T>
@@ -1572,11 +1650,9 @@ 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 == "XCMAT04" && fileHeader.schemaVersion == kMaterialArtifactSchemaVersion;
if (!isLegacySchema && !isSchemaV3 && !isCurrentSchema) {
magic == "XCMAT06" && fileHeader.schemaVersion == kMaterialArtifactSchemaVersion;
if (!isCurrentSchema) {
return LoadResult("Invalid material artifact magic: " + path);
}
@@ -1588,11 +1664,9 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
Containers::String materialName;
Containers::String materialSourcePath;
Containers::String shaderPath;
Containers::String shaderPass;
if (!ReadMaterialArtifactString(data, offset, materialName) ||
!ReadMaterialArtifactString(data, offset, materialSourcePath) ||
!ReadMaterialArtifactString(data, offset, shaderPath) ||
!ReadMaterialArtifactString(data, offset, shaderPass)) {
!ReadMaterialArtifactString(data, offset, shaderPath)) {
return LoadResult("Failed to parse material artifact strings: " + path);
}
@@ -1608,38 +1682,10 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
material->SetShader(shaderHandle);
}
}
ApplyLegacyMaterialShaderPassHint(material.get(), shaderPass);
MaterialArtifactHeader header = {};
if (isLegacySchema) {
MaterialArtifactHeaderV2 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.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 {
if (!ReadMaterialArtifactValue(data, offset, header)) {
return LoadResult("Failed to parse material artifact body: " + path);
}
if (!ReadMaterialArtifactValue(data, offset, header)) {
return LoadResult("Failed to parse material artifact body: " + path);
}
material->SetRenderQueue(header.renderQueue);
@@ -1794,19 +1840,6 @@ bool MaterialLoader::ParseMaterialData(const Containers::Array<Core::uint8>& dat
}
}
Containers::String shaderPass;
if (HasKey(jsonText, "shaderPass")) {
if (!TryParseStringValue(jsonText, "shaderPass", shaderPass)) {
return false;
}
ApplyLegacyMaterialShaderPassHint(material, shaderPass);
} else if (HasKey(jsonText, "pass")) {
if (!TryParseStringValue(jsonText, "pass", shaderPass)) {
return false;
}
ApplyLegacyMaterialShaderPassHint(material, shaderPass);
}
if (HasKey(jsonText, "renderQueue")) {
Core::int32 renderQueue = 0;
if (TryParseIntValue(jsonText, "renderQueue", renderQueue)) {

View File

@@ -217,7 +217,6 @@ TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
materialFile << "{\n";
materialFile << " \"shader\": \"" << shaderPath.generic_string() << "\",\n";
materialFile << " \"renderQueue\": \"Transparent\",\n";
materialFile << " \"shaderPass\": \"ForwardLit\",\n";
materialFile << " \"tags\": {\n";
materialFile << " \"LightMode\": \"ForwardBase\",\n";
materialFile << " \"RenderType\": \"Transparent\"\n";
@@ -243,7 +242,6 @@ TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
EXPECT_TRUE(material->IsValid());
EXPECT_NE(material->GetShader(), nullptr);
EXPECT_EQ(material->GetRenderQueue(), static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Transparent));
EXPECT_TRUE(material->GetLegacyShaderPassHint().Empty());
EXPECT_EQ(material->GetTag("LightMode"), "ForwardBase");
EXPECT_EQ(material->GetTag("RenderType"), "Transparent");
EXPECT_EQ(material->GetRenderState().cullMode, MaterialCullMode::Back);
@@ -380,7 +378,6 @@ TEST(MaterialLoader, LoadMaterialWithAuthoringShaderResolvesShaderPass) {
ASSERT_TRUE(materialFile.is_open());
materialFile << "{\n";
materialFile << " \"shader\": \"" << shaderPath.generic_string() << "\",\n";
materialFile << " \"shaderPass\": \"ForwardLit\",\n";
materialFile << " \"renderQueue\": \"Geometry\"\n";
materialFile << "}\n";
}
@@ -393,7 +390,6 @@ TEST(MaterialLoader, LoadMaterialWithAuthoringShaderResolvesShaderPass) {
Material* material = static_cast<Material*>(result.resource);
ASSERT_NE(material, nullptr);
ASSERT_NE(material->GetShader(), nullptr);
EXPECT_TRUE(material->GetLegacyShaderPassHint().Empty());
ASSERT_NE(material->GetShader()->FindPass("ForwardLit"), nullptr);
const ShaderStageVariant* vertexVariant =
material->GetShader()->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::OpenGL);
@@ -419,7 +415,6 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) {
materialPath,
"{\n"
" \"shader\": \"" + shaderPath.generic_string() + "\",\n"
" \"shaderPass\": \"ForwardLit\",\n"
" \"properties\": {\n"
" \"_BaseColor\": [0.2, 0.4, 0.6, 0.8],\n"
" \"_Metallic\": 0.15,\n"
@@ -435,7 +430,6 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) {
auto* material = static_cast<Material*>(result.resource);
ASSERT_NE(material, nullptr);
ASSERT_NE(material->GetShader(), nullptr);
EXPECT_TRUE(material->GetLegacyShaderPassHint().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);
@@ -446,7 +440,7 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) {
fs::remove_all(rootPath);
}
TEST(MaterialLoader, LoadBuiltinShaderMaterialDropsRedundantBuiltinShaderPassHint) {
TEST(MaterialLoader, LoadBuiltinShaderMaterialUsesShaderMetadataWithoutMaterialPassHint) {
namespace fs = std::filesystem;
ResourceManager& manager = ResourceManager::Get();
@@ -460,8 +454,7 @@ TEST(MaterialLoader, LoadBuiltinShaderMaterialDropsRedundantBuiltinShaderPassHin
WriteTextFile(
materialPath,
"{\n"
" \"shader\": \"" + std::string(GetBuiltinUnlitShaderPath().CStr()) + "\",\n"
" \"shaderPass\": \"Unlit\"\n"
" \"shader\": \"" + std::string(GetBuiltinUnlitShaderPath().CStr()) + "\"\n"
"}\n");
MaterialLoader loader;
@@ -472,7 +465,6 @@ TEST(MaterialLoader, LoadBuiltinShaderMaterialDropsRedundantBuiltinShaderPassHin
auto* material = static_cast<Material*>(result.resource);
ASSERT_NE(material, nullptr);
ASSERT_NE(material->GetShader(), nullptr);
EXPECT_TRUE(material->GetLegacyShaderPassHint().Empty());
EXPECT_NE(material->GetShader()->FindPass("Unlit"), nullptr);
delete material;
@@ -568,7 +560,7 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectPreservesShaderDefaultsForO
fs::remove_all(rootPath);
}
TEST(MaterialLoader, LoadMaterialMapsLegacySemanticKeysIntoShaderSchemaProperties) {
TEST(MaterialLoader, RejectsSemanticKeysWhenShaderSchemaRequiresExactPropertyNames) {
namespace fs = std::filesystem;
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_semantic_alias_test";
@@ -593,21 +585,7 @@ TEST(MaterialLoader, LoadMaterialMapsLegacySemanticKeysIntoShaderSchemaPropertie
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_FALSE(material->HasProperty("baseColor"));
EXPECT_EQ(material->GetFloat4("_BaseColor"), XCEngine::Math::Vector4(0.2f, 0.4f, 0.6f, 0.8f));
ASSERT_EQ(material->GetTextureBindingCount(), 1u);
EXPECT_EQ(material->GetTextureBindingName(0), "_MainTex");
EXPECT_EQ(
fs::path(material->GetTextureBindingPath(0).CStr()).lexically_normal().generic_string(),
(rootPath / "checker.bmp").lexically_normal().generic_string());
delete material;
EXPECT_FALSE(result);
fs::remove_all(rootPath);
}
@@ -624,7 +602,6 @@ TEST(MaterialLoader, LoadMaterialParsesKeywordArrayAgainstShaderSchema) {
materialPath,
"{\n"
" \"shader\": \"" + shaderPath.generic_string() + "\",\n"
" \"shaderPass\": \"ForwardLit\",\n"
" \"keywords\": [\"XC_MAIN_LIGHT_SHADOWS\", \"XC_ALPHA_TEST\", \"XC_ALPHA_TEST\", \"_\"]\n"
"}\n");
@@ -839,7 +816,6 @@ TEST(MaterialLoader, AssetDatabaseMaterialArtifactRoundTripsKeywords) {
materialPath,
"{\n"
" \"shader\": \"Assets/Shaders/keyword.shader\",\n"
" \"shaderPass\": \"ForwardLit\",\n"
" \"keywords\": [\"XC_MAIN_LIGHT_SHADOWS\", \"XC_ALPHA_TEST\"]\n"
"}\n");
@@ -873,7 +849,7 @@ TEST(MaterialLoader, AssetDatabaseMaterialArtifactRoundTripsKeywords) {
fs::remove_all(projectRoot);
}
TEST(MaterialLoader, AssetDatabaseMaterialArtifactStripsRedundantBuiltinShaderPassHint) {
TEST(MaterialLoader, AssetDatabaseMaterialArtifactRoundTripsBuiltinShaderWithoutMaterialPassHint) {
namespace fs = std::filesystem;
ResourceManager& manager = ResourceManager::Get();
@@ -890,7 +866,6 @@ TEST(MaterialLoader, AssetDatabaseMaterialArtifactStripsRedundantBuiltinShaderPa
materialPath,
"{\n"
" \"shader\": \"" + std::string(GetBuiltinUnlitShaderPath().CStr()) + "\",\n"
" \"shaderPass\": \"Unlit\",\n"
" \"properties\": {\n"
" \"_BaseColor\": [1.0, 1.0, 1.0, 1.0]\n"
" }\n"
@@ -914,7 +889,6 @@ TEST(MaterialLoader, AssetDatabaseMaterialArtifactStripsRedundantBuiltinShaderPa
auto* material = static_cast<Material*>(result.resource);
ASSERT_NE(material, nullptr);
ASSERT_NE(material->GetShader(), nullptr);
EXPECT_TRUE(material->GetLegacyShaderPassHint().Empty());
EXPECT_EQ(material->GetFloat4("_BaseColor"), XCEngine::Math::Vector4(1.0f, 1.0f, 1.0f, 1.0f));
delete material;
@@ -1065,7 +1039,6 @@ TEST(MaterialLoader, AssetDatabaseReimportsMaterialWhenShaderDependencyChanges)
ASSERT_TRUE(materialFile.is_open());
materialFile << "{\n";
materialFile << " \"shader\": \"Assets/Shaders/lit.shader\",\n";
materialFile << " \"shaderPass\": \"ForwardLit\",\n";
materialFile << " \"renderQueue\": \"geometry\"\n";
materialFile << "}\n";
}
@@ -1133,7 +1106,6 @@ TEST(MaterialLoader, LoadMaterialArtifactDefersTexturePayloadUntilRequested) {
WriteArtifactString(output, "LazyMaterial");
WriteArtifactString(output, "Assets/lazy.material");
WriteArtifactString(output, "");
WriteArtifactString(output, "");
MaterialArtifactHeader header;
header.renderQueue = static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Geometry);