resources: split shader authoring parser internals

This commit is contained in:
2026-04-07 13:00:30 +08:00
parent 3cf4adf181
commit e7669dc8c3
8 changed files with 2082 additions and 1852 deletions

View File

@@ -0,0 +1,597 @@
#include "ShaderAuthoringInternal.h"
#include "../ShaderSourceUtils.h"
#include <sstream>
#include <string>
#include <vector>
namespace XCEngine {
namespace Resources {
namespace Internal {
std::string StripAuthoringLineComment(const std::string& line) {
bool inString = false;
bool escaped = false;
for (size_t index = 0; index + 1 < line.size(); ++index) {
const char ch = line[index];
if (escaped) {
escaped = false;
continue;
}
if (ch == '\\') {
escaped = true;
continue;
}
if (ch == '"') {
inString = !inString;
continue;
}
if (!inString && ch == '/' && line[index + 1] == '/') {
return line.substr(0, index);
}
}
return line;
}
bool StartsWithKeyword(const std::string& line, const char* keyword) {
const std::string keywordString(keyword);
if (line.size() < keywordString.size() ||
line.compare(0, keywordString.size(), keywordString) != 0) {
return false;
}
return line.size() == keywordString.size() ||
std::isspace(static_cast<unsigned char>(line[keywordString.size()])) != 0;
}
void SplitShaderAuthoringLines(
const std::string& sourceText,
std::vector<std::string>& outLines) {
outLines.clear();
std::istringstream stream(sourceText);
std::string rawLine;
while (std::getline(stream, rawLine)) {
std::string line = TrimCopy(StripAuthoringLineComment(rawLine));
if (line.empty()) {
continue;
}
if (!StartsWithKeyword(line, "Tags") &&
line.size() > 1 &&
line.back() == '{') {
line.pop_back();
const std::string prefix = TrimCopy(line);
if (!prefix.empty()) {
outLines.push_back(prefix);
}
outLines.push_back("{");
continue;
}
if (line.size() > 1 && line.front() == '}') {
outLines.push_back("}");
line = TrimCopy(line.substr(1));
if (!line.empty()) {
outLines.push_back(line);
}
continue;
}
outLines.push_back(line);
}
}
bool TryExtractProgramBlocks(
const std::string& sourceText,
std::vector<ExtractedProgramBlock>& outBlocks,
Containers::String* outError) {
outBlocks.clear();
std::istringstream stream(sourceText);
std::string rawLine;
bool insideBlock = false;
ExtractedProgramBlock currentBlock = {};
std::string blockSource;
size_t lineNumber = 0;
auto fail = [&outError](const std::string& message, size_t humanLine) -> bool {
if (outError != nullptr) {
*outError = Containers::String(
("Unity-style shader parse error at line " + std::to_string(humanLine) + ": " + message).c_str());
}
return false;
};
while (std::getline(stream, rawLine)) {
++lineNumber;
const std::string normalizedLine = TrimCopy(StripAuthoringLineComment(rawLine));
if (!insideBlock) {
if (normalizedLine == "HLSLINCLUDE" || normalizedLine == "CGINCLUDE") {
insideBlock = true;
currentBlock = {};
currentBlock.kind = ExtractedProgramBlock::Kind::SharedInclude;
currentBlock.markerLine = lineNumber;
blockSource.clear();
} else if (normalizedLine == "HLSLPROGRAM" || normalizedLine == "CGPROGRAM") {
insideBlock = true;
currentBlock = {};
currentBlock.kind = ExtractedProgramBlock::Kind::PassProgram;
currentBlock.markerLine = lineNumber;
blockSource.clear();
}
continue;
}
if (normalizedLine == "ENDHLSL" || normalizedLine == "ENDCG") {
currentBlock.sourceText = blockSource.c_str();
outBlocks.push_back(std::move(currentBlock));
insideBlock = false;
blockSource.clear();
continue;
}
blockSource += rawLine;
blockSource += '\n';
}
if (insideBlock) {
return fail("program block was not closed", lineNumber);
}
return true;
}
bool ContainsBackendPragma(const std::vector<std::string>& lines) {
for (const std::string& line : lines) {
if (line.rfind("#pragma backend", 0) == 0) {
return true;
}
}
return false;
}
bool ContainsResourcesBlock(const std::vector<std::string>& lines) {
for (const std::string& line : lines) {
if (line == "Resources" || StartsWithKeyword(line, "Resources")) {
return true;
}
}
return false;
}
bool ContainsSingleSourceAuthoringConstructs(const std::vector<std::string>& lines) {
for (const std::string& line : lines) {
if (line == "HLSLINCLUDE" || line == "CGINCLUDE") {
return true;
}
if (line.rfind("#pragma target", 0) == 0) {
return true;
}
}
return false;
}
size_t FindMatchingDelimiter(
const std::string& text,
size_t openPos,
char openChar,
char closeChar) {
if (openPos >= text.size() || text[openPos] != openChar) {
return std::string::npos;
}
bool inString = false;
bool escaped = false;
int depth = 0;
for (size_t pos = openPos; pos < text.size(); ++pos) {
const char ch = text[pos];
if (escaped) {
escaped = false;
continue;
}
if (ch == '\\') {
escaped = true;
continue;
}
if (ch == '"') {
inString = !inString;
continue;
}
if (inString) {
continue;
}
if (ch == openChar) {
++depth;
} else if (ch == closeChar) {
--depth;
if (depth == 0) {
return pos;
}
}
}
return std::string::npos;
}
size_t FindFirstTopLevelChar(const std::string& text, char target) {
bool inString = false;
bool escaped = false;
int roundDepth = 0;
int squareDepth = 0;
for (size_t pos = 0; pos < text.size(); ++pos) {
const char ch = text[pos];
if (escaped) {
escaped = false;
continue;
}
if (ch == '\\') {
escaped = true;
continue;
}
if (ch == '"') {
inString = !inString;
continue;
}
if (inString) {
continue;
}
if (ch == target && roundDepth == 0 && squareDepth == 0) {
return pos;
}
if (ch == '(') {
++roundDepth;
continue;
}
if (ch == ')') {
--roundDepth;
continue;
}
if (ch == '[') {
++squareDepth;
continue;
}
if (ch == ']') {
--squareDepth;
continue;
}
}
return std::string::npos;
}
bool SplitCommaSeparatedAuthoring(
const std::string& text,
std::vector<std::string>& outParts) {
outParts.clear();
bool inString = false;
bool escaped = false;
int roundDepth = 0;
int squareDepth = 0;
size_t partStart = 0;
for (size_t pos = 0; pos < text.size(); ++pos) {
const char ch = text[pos];
if (escaped) {
escaped = false;
continue;
}
if (ch == '\\') {
escaped = true;
continue;
}
if (ch == '"') {
inString = !inString;
continue;
}
if (inString) {
continue;
}
if (ch == '(') {
++roundDepth;
continue;
}
if (ch == ')') {
--roundDepth;
continue;
}
if (ch == '[') {
++squareDepth;
continue;
}
if (ch == ']') {
--squareDepth;
continue;
}
if (ch == ',' && roundDepth == 0 && squareDepth == 0) {
outParts.push_back(TrimCopy(text.substr(partStart, pos - partStart)));
partStart = pos + 1;
}
}
outParts.push_back(TrimCopy(text.substr(partStart)));
return true;
}
Containers::String UnquoteAuthoringValue(const std::string& text) {
const std::string trimmed = TrimCopy(text);
if (trimmed.size() >= 2 &&
trimmed.front() == '"' &&
trimmed.back() == '"') {
Containers::String parsed;
if (ParseQuotedString(trimmed, 0, parsed)) {
return parsed;
}
}
return Containers::String(trimmed.c_str());
}
bool TryTokenizeQuotedArguments(
const std::string& line,
std::vector<std::string>& outTokens) {
outTokens.clear();
std::string current;
bool inString = false;
bool escaped = false;
for (size_t pos = 0; pos < line.size(); ++pos) {
const char ch = line[pos];
if (escaped) {
current.push_back(ch);
escaped = false;
continue;
}
if (ch == '\\' && inString) {
escaped = true;
continue;
}
if (ch == '"') {
inString = !inString;
continue;
}
if (!inString && std::isspace(static_cast<unsigned char>(ch)) != 0) {
if (!current.empty()) {
outTokens.push_back(current);
current.clear();
}
continue;
}
current.push_back(ch);
}
if (inString) {
return false;
}
if (!current.empty()) {
outTokens.push_back(current);
}
return !outTokens.empty();
}
bool TryParseInlineTagAssignments(
const std::string& line,
std::vector<ShaderTagIR>& outTags) {
const size_t openBrace = line.find('{');
const size_t closeBrace = line.rfind('}');
if (openBrace == std::string::npos ||
closeBrace == std::string::npos ||
closeBrace <= openBrace) {
return false;
}
const std::string content = line.substr(openBrace + 1, closeBrace - openBrace - 1);
size_t pos = 0;
while (pos < content.size()) {
pos = SkipWhitespace(content, pos);
while (pos < content.size() && content[pos] == ',') {
++pos;
pos = SkipWhitespace(content, pos);
}
if (pos >= content.size()) {
break;
}
Containers::String key;
if (!ParseQuotedString(content, pos, key, &pos)) {
return false;
}
pos = SkipWhitespace(content, pos);
if (pos >= content.size() || content[pos] != '=') {
return false;
}
pos = SkipWhitespace(content, pos + 1);
Containers::String value;
if (!ParseQuotedString(content, pos, value, &pos)) {
return false;
}
outTags.push_back({ key, value });
}
return true;
}
bool TryParseSemanticAttributes(
const std::string& attributesText,
Containers::String& outSemantic) {
outSemantic.Clear();
size_t pos = 0;
while (pos < attributesText.size()) {
pos = attributesText.find('[', pos);
if (pos == std::string::npos) {
break;
}
const size_t closePos = FindMatchingDelimiter(attributesText, pos, '[', ']');
if (closePos == std::string::npos) {
return false;
}
const std::string attributeBody = TrimCopy(attributesText.substr(pos + 1, closePos - pos - 1));
if (attributeBody.size() > 10 &&
attributeBody.compare(0, 9, "Semantic(") == 0 &&
attributeBody.back() == ')') {
outSemantic = UnquoteAuthoringValue(attributeBody.substr(9, attributeBody.size() - 10));
}
pos = closePos + 1;
}
return true;
}
bool TryParseAuthoringPropertyLine(
const std::string& line,
ShaderPropertyDesc& outProperty) {
outProperty = {};
const size_t openParen = line.find('(');
if (openParen == std::string::npos) {
return false;
}
const size_t closeParen = FindMatchingDelimiter(line, openParen, '(', ')');
if (closeParen == std::string::npos) {
return false;
}
outProperty.name = Containers::String(TrimCopy(line.substr(0, openParen)).c_str());
if (outProperty.name.Empty()) {
return false;
}
std::vector<std::string> headerParts;
if (!SplitCommaSeparatedAuthoring(line.substr(openParen + 1, closeParen - openParen - 1), headerParts) ||
headerParts.size() < 2u) {
return false;
}
outProperty.displayName = UnquoteAuthoringValue(headerParts[0]);
std::string propertyTypeName = headerParts[1];
for (size_t index = 2; index < headerParts.size(); ++index) {
propertyTypeName += ",";
propertyTypeName += headerParts[index];
}
propertyTypeName = TrimCopy(propertyTypeName);
const size_t rangePos = propertyTypeName.find('(');
if (rangePos != std::string::npos &&
TrimCopy(propertyTypeName.substr(0, rangePos)) == "Range") {
propertyTypeName = "Range";
}
if (!TryParseShaderPropertyType(propertyTypeName.c_str(), outProperty.type)) {
return false;
}
const size_t equalsPos = line.find('=', closeParen + 1);
if (equalsPos == std::string::npos) {
return false;
}
const std::string tail = TrimCopy(line.substr(equalsPos + 1));
const size_t attributePos = FindFirstTopLevelChar(tail, '[');
const std::string defaultValueText =
attributePos == std::string::npos ? tail : TrimCopy(tail.substr(0, attributePos));
if (defaultValueText.empty()) {
return false;
}
outProperty.defaultValue = UnquoteAuthoringValue(defaultValueText);
if (attributePos != std::string::npos &&
!TryParseSemanticAttributes(tail.substr(attributePos), outProperty.semantic)) {
return false;
}
return true;
}
bool TryParseAuthoringResourceLine(
const std::string& line,
ShaderResourceBindingDesc& outBinding) {
outBinding = {};
const size_t openParen = line.find('(');
if (openParen == std::string::npos) {
return false;
}
const size_t closeParen = FindMatchingDelimiter(line, openParen, '(', ')');
if (closeParen == std::string::npos) {
return false;
}
outBinding.name = Containers::String(TrimCopy(line.substr(0, openParen)).c_str());
if (outBinding.name.Empty()) {
return false;
}
std::vector<std::string> parts;
if (!SplitCommaSeparatedAuthoring(line.substr(openParen + 1, closeParen - openParen - 1), parts) ||
parts.size() != 3u) {
return false;
}
if (!TryParseShaderResourceType(parts[0].c_str(), outBinding.type)) {
return false;
}
try {
outBinding.set = static_cast<Core::uint32>(std::stoul(parts[1]));
outBinding.binding = static_cast<Core::uint32>(std::stoul(parts[2]));
} catch (...) {
return false;
}
const size_t attributePos = FindFirstTopLevelChar(line.substr(closeParen + 1), '[');
if (attributePos != std::string::npos) {
const std::string attributesText = line.substr(closeParen + 1 + attributePos);
if (!TryParseSemanticAttributes(attributesText, outBinding.semantic)) {
return false;
}
}
return true;
}
} // namespace Internal
} // namespace Resources
} // namespace XCEngine