516 lines
13 KiB
C++
516 lines
13 KiB
C++
#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(
|
|
("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<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;
|
|
}
|
|
|
|
} // namespace Internal
|
|
} // namespace Resources
|
|
} // namespace XCEngine
|