rendering: formalize shader keyword metadata contract
This commit is contained in:
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user