Tighten material loader schema parsing
This commit is contained in:
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user