#include "ShaderAuthoringInternal.h" #include "../ShaderSourceUtils.h" #include #include #include 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(line[keywordString.size()])) != 0; } void SplitShaderAuthoringLines( const std::string& sourceText, std::vector& 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& 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( ("shader authoring 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; } 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& 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& 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(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& 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 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; } } // namespace Internal } // namespace Resources } // namespace XCEngine