rendering: formalize shader keyword metadata contract

This commit is contained in:
2026-04-06 18:55:26 +08:00
parent 7acc397714
commit a8b4da16a3
16 changed files with 795 additions and 16 deletions

View File

@@ -300,6 +300,13 @@ bool TryDecodeAssetRef(const Containers::String& value, AssetRef& outRef) {
return outRef.IsValid();
}
bool TryExtractDelimitedText(const std::string& text,
size_t valuePos,
char openChar,
char closeChar,
std::string& outValue,
size_t* nextPos);
bool TryExtractObject(const std::string& json, const char* key, std::string& outObject) {
size_t valuePos = 0;
if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != '{') {
@@ -345,6 +352,15 @@ bool TryExtractObject(const std::string& json, const char* key, std::string& out
return false;
}
bool TryExtractArray(const std::string& json, const char* key, std::string& outArray) {
size_t valuePos = 0;
if (!FindValueStart(json, key, valuePos) || valuePos >= json.size() || json[valuePos] != '[') {
return false;
}
return TryExtractDelimitedText(json, valuePos, '[', ']', outArray, nullptr);
}
std::string TrimCopy(const std::string& text) {
const size_t first = SkipWhitespace(text, 0);
if (first >= text.size()) {
@@ -359,6 +375,17 @@ std::string TrimCopy(const std::string& text) {
return text.substr(first, last - first);
}
Containers::String NormalizeMaterialKeywordToken(const Containers::String& keyword) {
const Containers::String normalized = keyword.Trim();
if (normalized.Empty() ||
normalized == Containers::String("_") ||
normalized == Containers::String("__")) {
return Containers::String();
}
return normalized;
}
bool IsJsonValueTerminator(char ch) {
return std::isspace(static_cast<unsigned char>(ch)) != 0 ||
ch == ',' ||
@@ -887,6 +914,58 @@ bool TryParseMaterialPropertiesObject(const std::string& objectText, Material* m
return false;
}
bool TryParseMaterialKeywordsArray(const std::string& arrayText, Material* material) {
if (material == nullptr || arrayText.empty() || arrayText.front() != '[' || arrayText.back() != ']') {
return false;
}
size_t pos = 1;
while (pos < arrayText.size()) {
pos = SkipWhitespace(arrayText, pos);
if (pos >= arrayText.size()) {
return false;
}
if (arrayText[pos] == ']') {
return true;
}
std::string rawValue;
JsonRawValueType rawType = JsonRawValueType::Invalid;
if (!TryExtractRawValue(arrayText, pos, rawValue, rawType, &pos) ||
rawType != JsonRawValueType::String) {
return false;
}
const Containers::String normalizedKeyword =
NormalizeMaterialKeywordToken(Containers::String(rawValue.c_str()));
if (!normalizedKeyword.Empty()) {
material->EnableKeyword(normalizedKeyword);
if (!material->IsKeywordEnabled(normalizedKeyword)) {
return false;
}
}
pos = SkipWhitespace(arrayText, pos);
if (pos >= arrayText.size()) {
return false;
}
if (arrayText[pos] == ',') {
++pos;
continue;
}
if (arrayText[pos] == ']') {
return true;
}
return false;
}
return false;
}
bool TryParseRenderQueueName(const Containers::String& queueName, Core::int32& outQueue) {
const Containers::String normalized = queueName.ToLower();
if (normalized == "background") {
@@ -1487,7 +1566,10 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
}
const std::string magic(fileHeader.magic, fileHeader.magic + 7);
if (magic != "XCMAT02") {
const bool isLegacySchema = magic == "XCMAT02" && fileHeader.schemaVersion == 2u;
const bool isCurrentSchema =
magic == "XCMAT03" && fileHeader.schemaVersion == kMaterialArtifactSchemaVersion;
if (!isLegacySchema && !isCurrentSchema) {
return LoadResult("Invalid material artifact magic: " + path);
}
@@ -1523,9 +1605,22 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
material->SetShaderPass(shaderPass);
}
MaterialArtifactHeader header;
if (!ReadMaterialArtifactValue(data, offset, header)) {
return LoadResult("Failed to parse material artifact body: " + path);
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.propertyCount = legacyHeader.propertyCount;
header.textureBindingCount = legacyHeader.textureBindingCount;
} else {
if (!ReadMaterialArtifactValue(data, offset, header)) {
return LoadResult("Failed to parse material artifact body: " + path);
}
}
material->SetRenderQueue(header.renderQueue);
@@ -1542,6 +1637,21 @@ LoadResult LoadMaterialArtifact(const Containers::String& path) {
material->SetTag(tagName, tagValue);
}
for (Core::uint32 keywordIndex = 0; keywordIndex < header.keywordCount; ++keywordIndex) {
Containers::String keyword;
if (!ReadMaterialArtifactString(data, offset, keyword)) {
return LoadResult("Failed to read material artifact keywords: " + path);
}
const Containers::String normalizedKeyword = NormalizeMaterialKeywordToken(keyword);
if (!normalizedKeyword.Empty()) {
material->EnableKeyword(normalizedKeyword);
if (!material->IsKeywordEnabled(normalizedKeyword)) {
return LoadResult("Material artifact references undeclared shader keyword: " + path);
}
}
}
for (Core::uint32 propertyIndex = 0; propertyIndex < header.propertyCount; ++propertyIndex) {
Containers::String propertyName;
MaterialPropertyArtifact propertyArtifact;
@@ -1692,6 +1802,14 @@ bool MaterialLoader::ParseMaterialData(const Containers::Array<Core::uint8>& dat
}
}
if (HasKey(jsonText, "keywords")) {
std::string keywordsArray;
if (!TryExtractArray(jsonText, "keywords", keywordsArray) ||
!TryParseMaterialKeywordsArray(keywordsArray, material)) {
return false;
}
}
if (HasKey(jsonText, "tags")) {
std::string tagObject;
if (!TryExtractObject(jsonText, "tags", tagObject) || !TryParseTagMap(tagObject, material)) {