resources: split shader authoring parser internals
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user