#include "ShaderAuthoringParser.h" #include "ShaderSourceUtils.h" #include #include #include #include namespace XCEngine { namespace Resources { struct ExtractedProgramBlock { enum class Kind { SharedInclude, PassProgram }; Kind kind = Kind::PassProgram; Containers::String sourceText; size_t markerLine = 0; }; bool TryTokenizeQuotedArguments(const std::string& line, std::vector& outTokens); 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( ("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& lines) { for (const std::string& line : lines) { if (line.rfind("#pragma backend", 0) == 0) { return true; } } return false; } bool ContainsResourcesBlock(const std::vector& lines) { for (const std::string& line : lines) { if (line == "Resources" || StartsWithKeyword(line, "Resources")) { return true; } } return false; } bool ContainsSingleSourceAuthoringConstructs(const std::vector& 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; } MaterialRenderState BuildUnityDefaultFixedFunctionState() { MaterialRenderState state = {}; state.blendEnable = false; state.srcBlend = MaterialBlendFactor::One; state.dstBlend = MaterialBlendFactor::Zero; state.srcBlendAlpha = MaterialBlendFactor::One; state.dstBlendAlpha = MaterialBlendFactor::Zero; state.blendOp = MaterialBlendOp::Add; state.blendOpAlpha = MaterialBlendOp::Add; state.colorWriteMask = 0xF; state.depthTestEnable = true; state.depthWriteEnable = true; state.depthFunc = MaterialComparisonFunc::LessEqual; state.cullMode = MaterialCullMode::Back; return state; } void EnsureAuthoringFixedFunctionStateInitialized( bool& hasFixedFunctionState, MaterialRenderState& fixedFunctionState) { if (!hasFixedFunctionState) { hasFixedFunctionState = true; fixedFunctionState = BuildUnityDefaultFixedFunctionState(); } } bool TryParseUnityStyleBoolDirectiveToken(const std::string& token, bool& outValue) { const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower(); if (normalized == "on") { outValue = true; return true; } if (normalized == "off") { outValue = false; return true; } return false; } bool TryParseUnityStyleCullMode(const std::string& token, MaterialCullMode& outMode) { const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower(); if (normalized == "back") { outMode = MaterialCullMode::Back; return true; } if (normalized == "front") { outMode = MaterialCullMode::Front; return true; } if (normalized == "off") { outMode = MaterialCullMode::None; return true; } return false; } bool TryParseUnityStyleComparisonFunc(const std::string& token, MaterialComparisonFunc& outFunc) { const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower(); if (normalized == "never") { outFunc = MaterialComparisonFunc::Never; return true; } if (normalized == "less") { outFunc = MaterialComparisonFunc::Less; return true; } if (normalized == "equal") { outFunc = MaterialComparisonFunc::Equal; return true; } if (normalized == "lequal" || normalized == "lessequal" || normalized == "less_equal") { outFunc = MaterialComparisonFunc::LessEqual; return true; } if (normalized == "greater") { outFunc = MaterialComparisonFunc::Greater; return true; } if (normalized == "notequal" || normalized == "not_equal") { outFunc = MaterialComparisonFunc::NotEqual; return true; } if (normalized == "gequal" || normalized == "greaterequal" || normalized == "greater_equal") { outFunc = MaterialComparisonFunc::GreaterEqual; return true; } if (normalized == "always") { outFunc = MaterialComparisonFunc::Always; return true; } return false; } bool TryParseUnityStyleBlendFactor(const std::string& token, MaterialBlendFactor& outFactor) { const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower(); if (normalized == "zero") { outFactor = MaterialBlendFactor::Zero; return true; } if (normalized == "one") { outFactor = MaterialBlendFactor::One; return true; } if (normalized == "srccolor" || normalized == "src_color") { outFactor = MaterialBlendFactor::SrcColor; return true; } if (normalized == "oneminussrccolor" || normalized == "one_minus_src_color" || normalized == "invsrccolor") { outFactor = MaterialBlendFactor::InvSrcColor; return true; } if (normalized == "srcalpha" || normalized == "src_alpha") { outFactor = MaterialBlendFactor::SrcAlpha; return true; } if (normalized == "oneminussrcalpha" || normalized == "one_minus_src_alpha" || normalized == "invsrcalpha") { outFactor = MaterialBlendFactor::InvSrcAlpha; return true; } if (normalized == "dstalpha" || normalized == "dst_alpha") { outFactor = MaterialBlendFactor::DstAlpha; return true; } if (normalized == "oneminusdstalpha" || normalized == "one_minus_dst_alpha" || normalized == "invdstalpha") { outFactor = MaterialBlendFactor::InvDstAlpha; return true; } if (normalized == "dstcolor" || normalized == "dst_color") { outFactor = MaterialBlendFactor::DstColor; return true; } if (normalized == "oneminusdstcolor" || normalized == "one_minus_dst_color" || normalized == "invdstcolor") { outFactor = MaterialBlendFactor::InvDstColor; return true; } if (normalized == "srcalphasaturate" || normalized == "src_alpha_saturate" || normalized == "srcalphasat") { outFactor = MaterialBlendFactor::SrcAlphaSat; return true; } return false; } bool TryParseUnityStyleColorMask(const std::string& token, Core::uint8& outMask) { const Containers::String normalized = Containers::String(token.c_str()).Trim().ToUpper(); if (normalized == "0") { outMask = 0u; return true; } Core::uint8 mask = 0u; for (size_t index = 0; index < normalized.Length(); ++index) { switch (normalized[index]) { case 'R': mask |= 0x1u; break; case 'G': mask |= 0x2u; break; case 'B': mask |= 0x4u; break; case 'A': mask |= 0x8u; break; default: return false; } } outMask = mask; return true; } bool TryParseUnityStyleBlendDirective( const std::vector& tokens, MaterialRenderState& outState) { std::vector normalizedTokens; normalizedTokens.reserve(tokens.size()); for (const std::string& token : tokens) { if (token == ",") { continue; } std::string normalizedToken = token; while (!normalizedToken.empty() && normalizedToken.back() == ',') { normalizedToken.pop_back(); } if (!normalizedToken.empty()) { normalizedTokens.push_back(std::move(normalizedToken)); } } if (normalizedTokens.size() != 2u && normalizedTokens.size() != 3u && normalizedTokens.size() != 5u) { return false; } if (normalizedTokens.size() == 2u) { bool enabled = false; if (!TryParseUnityStyleBoolDirectiveToken(normalizedTokens[1], enabled)) { return false; } outState.blendEnable = enabled; if (!enabled) { outState.srcBlend = MaterialBlendFactor::One; outState.dstBlend = MaterialBlendFactor::Zero; outState.srcBlendAlpha = MaterialBlendFactor::One; outState.dstBlendAlpha = MaterialBlendFactor::Zero; } return true; } MaterialBlendFactor srcBlend = MaterialBlendFactor::One; MaterialBlendFactor dstBlend = MaterialBlendFactor::Zero; if (!TryParseUnityStyleBlendFactor(normalizedTokens[1], srcBlend) || !TryParseUnityStyleBlendFactor(normalizedTokens[2], dstBlend)) { return false; } outState.blendEnable = true; outState.srcBlend = srcBlend; outState.dstBlend = dstBlend; if (normalizedTokens.size() == 5u) { if (!TryParseUnityStyleBlendFactor(normalizedTokens[3], outState.srcBlendAlpha) || !TryParseUnityStyleBlendFactor(normalizedTokens[4], outState.dstBlendAlpha)) { return false; } } else { outState.srcBlendAlpha = srcBlend; outState.dstBlendAlpha = dstBlend; } return true; } void SetOrReplaceAuthoringTag( std::vector& tags, const Containers::String& name, const Containers::String& value) { for (AuthoringTagEntry& tag : tags) { if (tag.name == name) { tag.value = value; return; } } tags.push_back({ name, value }); } ShaderAuthoringStyle DetectShaderAuthoringStyle(const std::string& sourceText) { std::vector lines; SplitShaderAuthoringLines(sourceText, lines); if (lines.empty() || !StartsWithKeyword(lines.front(), "Shader")) { return ShaderAuthoringStyle::NotShaderAuthoring; } const bool hasBackendPragma = ContainsBackendPragma(lines); const bool hasSingleSourceConstructs = ContainsSingleSourceAuthoringConstructs(lines); if (hasBackendPragma && !hasSingleSourceConstructs) { return ShaderAuthoringStyle::LegacyBackendSplit; } if (ContainsResourcesBlock(lines)) { return ShaderAuthoringStyle::UnityStyleSingleSource; } if (hasSingleSourceConstructs) { return ShaderAuthoringStyle::UnityStyleSingleSource; } return ShaderAuthoringStyle::UnityStyleSingleSource; } void AppendAuthoringSourceBlock( Containers::String& target, const Containers::String& sourceBlock) { if (sourceBlock.Empty()) { return; } if (!target.Empty()) { target += '\n'; } target += sourceBlock; } void CollectQuotedIncludeDependencyPaths( const Containers::String& sourcePath, const Containers::String& sourceText, std::unordered_set& seenPaths, Containers::Array& outDependencies) { std::istringstream stream(ToStdString(sourceText)); std::string rawLine; while (std::getline(stream, rawLine)) { const std::string line = TrimCopy(StripAuthoringLineComment(rawLine)); if (line.rfind("#include", 0) != 0) { continue; } const size_t firstQuote = line.find('"'); if (firstQuote == std::string::npos) { continue; } const size_t secondQuote = line.find('"', firstQuote + 1); if (secondQuote == std::string::npos || secondQuote <= firstQuote + 1) { continue; } const Containers::String includePath(line.substr(firstQuote + 1, secondQuote - firstQuote - 1).c_str()); const Containers::String resolvedPath = ResolveShaderDependencyPath(includePath, sourcePath); const std::string key = ToStdString(resolvedPath); if (!key.empty() && seenPaths.insert(key).second) { outDependencies.PushBack(resolvedPath); } } } bool IsUnityStyleAuthoringPragmaDirective(const std::string& line) { std::vector pragmaTokens; if (!TryTokenizeQuotedArguments(line, pragmaTokens) || pragmaTokens.empty() || pragmaTokens[0] != "#pragma" || pragmaTokens.size() < 2u) { return false; } return pragmaTokens[1] == "vertex" || pragmaTokens[1] == "fragment" || pragmaTokens[1] == "target" || pragmaTokens[1] == "multi_compile" || pragmaTokens[1] == "multi_compile_local" || pragmaTokens[1] == "shader_feature" || pragmaTokens[1] == "shader_feature_local" || pragmaTokens[1] == "backend"; } Containers::String StripUnityStyleAuthoringPragmas(const Containers::String& sourceText) { std::istringstream stream(ToStdString(sourceText)); std::string rawLine; std::string strippedSource; while (std::getline(stream, rawLine)) { const std::string normalizedLine = TrimCopy(StripAuthoringLineComment(rawLine)); if (IsUnityStyleAuthoringPragmaDirective(normalizedLine)) { continue; } strippedSource += rawLine; strippedSource += '\n'; } return strippedSource.c_str(); } Containers::String BuildShaderKeywordSetSignature(const ShaderKeywordSet& keywordSet) { ShaderKeywordSet normalizedKeywords = keywordSet; NormalizeShaderKeywordSetInPlace(normalizedKeywords); Containers::String signature; for (size_t keywordIndex = 0; keywordIndex < normalizedKeywords.enabledKeywords.Size(); ++keywordIndex) { if (keywordIndex > 0) { signature += ";"; } signature += normalizedKeywords.enabledKeywords[keywordIndex]; } return signature; } std::vector BuildShaderKeywordVariantSets( const Containers::Array& declarations) { std::vector keywordSets(1); for (const ShaderKeywordDeclaration& declaration : declarations) { if (declaration.options.Empty()) { continue; } std::vector nextKeywordSets; std::unordered_set seenSignatures; nextKeywordSets.reserve(keywordSets.size() * declaration.options.Size()); for (const ShaderKeywordSet& currentKeywordSet : keywordSets) { for (const Containers::String& option : declaration.options) { ShaderKeywordSet nextKeywordSet = currentKeywordSet; const Containers::String normalizedKeyword = NormalizeShaderKeywordToken(option); if (!normalizedKeyword.Empty()) { nextKeywordSet.enabledKeywords.PushBack(normalizedKeyword); } NormalizeShaderKeywordSetInPlace(nextKeywordSet); const std::string signature = ToStdString(BuildShaderKeywordSetSignature(nextKeywordSet)); if (seenSignatures.insert(signature).second) { nextKeywordSets.push_back(std::move(nextKeywordSet)); } } } if (!nextKeywordSets.empty()) { keywordSets = std::move(nextKeywordSets); } } if (keywordSets.empty()) { keywordSets.emplace_back(); } return keywordSets; } Containers::String BuildKeywordVariantSource( const Containers::String& baseSource, const ShaderKeywordSet& requiredKeywords) { if (requiredKeywords.enabledKeywords.Empty()) { return baseSource; } std::string defineBlock; for (const Containers::String& keyword : requiredKeywords.enabledKeywords) { defineBlock += "#define "; defineBlock += ToStdString(keyword); defineBlock += " 1\n"; } if (baseSource.Empty()) { return Containers::String(defineBlock.c_str()); } const std::string sourceText = ToStdString(baseSource); size_t lineStart = 0; while (lineStart < sourceText.size()) { const size_t lineEnd = sourceText.find_first_of("\r\n", lineStart); const size_t lineLength = lineEnd == std::string::npos ? sourceText.size() - lineStart : lineEnd - lineStart; const std::string line = sourceText.substr(lineStart, lineLength); const std::string trimmedLine = TrimCopy(line); if (trimmedLine.empty() || trimmedLine.rfind("//", 0) == 0u || trimmedLine.rfind("/*", 0) == 0u || trimmedLine.rfind("*", 0) == 0u) { if (lineEnd == std::string::npos) { break; } lineStart = lineEnd + 1u; if (sourceText[lineEnd] == '\r' && lineStart < sourceText.size() && sourceText[lineStart] == '\n') { ++lineStart; } continue; } if (trimmedLine.rfind("#version", 0) != 0u) { break; } size_t insertionPos = lineEnd == std::string::npos ? sourceText.size() : lineEnd + 1u; if (lineEnd != std::string::npos && sourceText[lineEnd] == '\r' && insertionPos < sourceText.size() && sourceText[insertionPos] == '\n') { ++insertionPos; } std::string variantSource = sourceText.substr(0, insertionPos); variantSource += defineBlock; variantSource += sourceText.substr(insertionPos); return Containers::String(variantSource.c_str()); } std::string variantSource = defineBlock; variantSource += '\n'; variantSource += sourceText; return Containers::String(variantSource.c_str()); } bool TryParseShaderKeywordDeclarationPragma( const std::vector& pragmaTokens, ShaderKeywordDeclaration& outDeclaration) { outDeclaration = {}; if (pragmaTokens.size() < 3u) { return false; } if (pragmaTokens[1] == "multi_compile") { outDeclaration.type = ShaderKeywordDeclarationType::MultiCompile; } else if (pragmaTokens[1] == "multi_compile_local") { outDeclaration.type = ShaderKeywordDeclarationType::MultiCompileLocal; } else if (pragmaTokens[1] == "shader_feature") { outDeclaration.type = ShaderKeywordDeclarationType::ShaderFeature; } else if (pragmaTokens[1] == "shader_feature_local") { outDeclaration.type = ShaderKeywordDeclarationType::ShaderFeatureLocal; } else { return false; } for (size_t tokenIndex = 2; tokenIndex < pragmaTokens.size(); ++tokenIndex) { outDeclaration.options.PushBack(pragmaTokens[tokenIndex].c_str()); } return !outDeclaration.options.Empty(); } 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; } 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 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(std::stoul(parts[1])); outBinding.binding = static_cast(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; } bool ParseLegacyBackendSplitShaderAuthoring( const Containers::String& path, const std::string& sourceText, AuthoringShaderDesc& outDesc, Containers::String* outError) { (void)path; outDesc = {}; enum class BlockKind { None, Shader, Properties, SubShader, Pass, Resources }; auto fail = [&outError](const std::string& message, size_t lineNumber) -> bool { if (outError != nullptr) { *outError = Containers::String( ("Legacy shader parse error at line " + std::to_string(lineNumber) + ": " + message).c_str()); } return false; }; std::vector lines; SplitShaderAuthoringLines(sourceText, lines); if (lines.empty()) { return fail("shader file is empty", 0); } std::vector blockStack; BlockKind pendingBlock = BlockKind::None; AuthoringSubShaderEntry* currentSubShader = nullptr; AuthoringPassEntry* currentPass = nullptr; bool inProgram = false; auto currentBlock = [&blockStack]() -> BlockKind { return blockStack.empty() ? BlockKind::None : blockStack.back(); }; for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex) { const std::string& line = lines[lineIndex]; const size_t humanLine = lineIndex + 1; if (inProgram) { if (line == "ENDHLSL" || line == "ENDCG") { inProgram = false; continue; } std::vector pragmaTokens; if (!TryTokenizeQuotedArguments(line, pragmaTokens) || pragmaTokens.empty()) { continue; } if (pragmaTokens[0] != "#pragma") { continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "vertex") { currentPass->vertexEntryPoint = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "fragment") { currentPass->fragmentEntryPoint = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 6u && pragmaTokens[1] == "backend") { AuthoringBackendVariantEntry backendVariant = {}; if (!TryParseShaderBackend(pragmaTokens[2].c_str(), backendVariant.backend)) { return fail("invalid backend pragma backend name", humanLine); } if (!TryParseShaderLanguage(pragmaTokens[3].c_str(), backendVariant.language)) { return fail("invalid backend pragma language name", humanLine); } backendVariant.vertexSourcePath = pragmaTokens[4].c_str(); backendVariant.fragmentSourcePath = pragmaTokens[5].c_str(); if (pragmaTokens.size() >= 7u) { backendVariant.vertexProfile = pragmaTokens[6].c_str(); } if (pragmaTokens.size() >= 8u) { backendVariant.fragmentProfile = pragmaTokens[7].c_str(); } currentPass->backendVariants.push_back(std::move(backendVariant)); continue; } if (pragmaTokens.size() >= 2u && (pragmaTokens[1] == "multi_compile" || pragmaTokens[1] == "multi_compile_local" || pragmaTokens[1] == "shader_feature" || pragmaTokens[1] == "shader_feature_local")) { ShaderKeywordDeclaration declaration = {}; if (!TryParseShaderKeywordDeclarationPragma(pragmaTokens, declaration)) { return fail("keyword pragma must declare at least one option", humanLine); } currentPass->keywordDeclarations.PushBack(declaration); continue; } continue; } if (line == "{") { switch (pendingBlock) { case BlockKind::Shader: blockStack.push_back(BlockKind::Shader); break; case BlockKind::Properties: blockStack.push_back(BlockKind::Properties); break; case BlockKind::SubShader: outDesc.subShaders.emplace_back(); currentSubShader = &outDesc.subShaders.back(); blockStack.push_back(BlockKind::SubShader); break; case BlockKind::Pass: if (currentSubShader == nullptr) { return fail("pass block must be inside a SubShader", humanLine); } currentSubShader->passes.emplace_back(); currentPass = ¤tSubShader->passes.back(); blockStack.push_back(BlockKind::Pass); break; case BlockKind::Resources: if (currentPass == nullptr) { return fail("resources block must be inside a Pass", humanLine); } blockStack.push_back(BlockKind::Resources); break; case BlockKind::None: default: return fail("unexpected opening brace", humanLine); } pendingBlock = BlockKind::None; continue; } if (line == "}") { if (blockStack.empty()) { return fail("unexpected closing brace", humanLine); } const BlockKind closingBlock = blockStack.back(); blockStack.pop_back(); if (closingBlock == BlockKind::Pass) { currentPass = nullptr; } else if (closingBlock == BlockKind::SubShader) { currentSubShader = nullptr; } continue; } if (StartsWithKeyword(line, "Shader")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("Shader declaration is missing a name", humanLine); } outDesc.name = tokens[1].c_str(); pendingBlock = BlockKind::Shader; continue; } if (line == "Properties") { pendingBlock = BlockKind::Properties; continue; } if (currentBlock() == BlockKind::Shader && StartsWithKeyword(line, "Fallback")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("Fallback directive is missing a value", humanLine); } outDesc.fallback = tokens[1].c_str(); continue; } if (StartsWithKeyword(line, "SubShader")) { pendingBlock = BlockKind::SubShader; continue; } if (StartsWithKeyword(line, "Pass")) { pendingBlock = BlockKind::Pass; continue; } if (line == "Resources") { pendingBlock = BlockKind::Resources; continue; } if (StartsWithKeyword(line, "Tags")) { std::vector parsedTags; if (!TryParseInlineTagAssignments(line, parsedTags)) { return fail("Tags block must use inline key/value pairs", humanLine); } if (currentPass != nullptr) { currentPass->tags.insert(currentPass->tags.end(), parsedTags.begin(), parsedTags.end()); } else if (currentSubShader != nullptr) { currentSubShader->tags.insert(currentSubShader->tags.end(), parsedTags.begin(), parsedTags.end()); } else { return fail("Tags block is only supported inside SubShader or Pass", humanLine); } continue; } if (currentBlock() == BlockKind::Properties) { ShaderPropertyDesc property = {}; if (!TryParseAuthoringPropertyLine(line, property)) { return fail("invalid Properties entry", humanLine); } outDesc.properties.PushBack(property); continue; } if (currentBlock() == BlockKind::Resources) { ShaderResourceBindingDesc resourceBinding = {}; if (!TryParseAuthoringResourceLine(line, resourceBinding)) { return fail("invalid Resources entry", humanLine); } currentPass->resources.PushBack(resourceBinding); continue; } if (currentBlock() == BlockKind::Pass && currentPass != nullptr) { if (StartsWithKeyword(line, "Name")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("pass Name directive is missing a value", humanLine); } currentPass->name = tokens[1].c_str(); continue; } if (line == "HLSLPROGRAM" || line == "CGPROGRAM") { inProgram = true; continue; } } return fail("unsupported authoring statement: " + line, humanLine); } if (inProgram) { return fail("program block was not closed", lines.size()); } if (!blockStack.empty()) { return fail("one or more blocks were not closed", lines.size()); } if (outDesc.name.Empty()) { return fail("shader name is missing", 0); } if (outDesc.subShaders.empty()) { return fail("shader does not declare any SubShader blocks", 0); } for (const AuthoringSubShaderEntry& subShader : outDesc.subShaders) { if (subShader.passes.empty()) { continue; } for (const AuthoringPassEntry& pass : subShader.passes) { if (pass.name.Empty()) { return fail("a Pass is missing a Name directive", 0); } if (pass.backendVariants.empty()) { return fail("a Pass is missing backend variants", 0); } } } return true; } bool ParseUnityStyleSingleSourceShaderAuthoring( const Containers::String& path, const std::string& sourceText, AuthoringShaderDesc& outDesc, Containers::String* outError) { (void)path; outDesc = {}; enum class BlockKind { None, Shader, Properties, SubShader, Pass }; auto fail = [&outError](const std::string& message, size_t lineNumber) -> bool { if (outError != nullptr) { *outError = Containers::String( ("Unity-style shader parse error at line " + std::to_string(lineNumber) + ": " + message).c_str()); } return false; }; std::vector lines; SplitShaderAuthoringLines(sourceText, lines); if (lines.empty()) { return fail("shader file is empty", 0); } std::vector extractedBlocks; Containers::String extractionError; if (!TryExtractProgramBlocks(sourceText, extractedBlocks, &extractionError)) { if (outError != nullptr) { *outError = extractionError; } return false; } size_t nextExtractedBlock = 0; std::vector blockStack; BlockKind pendingBlock = BlockKind::None; AuthoringSubShaderEntry* currentSubShader = nullptr; AuthoringPassEntry* currentPass = nullptr; bool inProgramBlock = false; bool inSharedIncludeBlock = false; auto currentBlock = [&blockStack]() -> BlockKind { return blockStack.empty() ? BlockKind::None : blockStack.back(); }; auto consumeExtractedBlock = [&](ExtractedProgramBlock::Kind expectedKind, Containers::String& destination, bool append, size_t humanLine) -> bool { if (nextExtractedBlock >= extractedBlocks.size()) { return fail("program block source extraction is out of sync", humanLine); } const ExtractedProgramBlock& block = extractedBlocks[nextExtractedBlock++]; if (block.kind != expectedKind) { return fail("program block source extraction mismatched block kind", humanLine); } if (append) { AppendAuthoringSourceBlock(destination, block.sourceText); } else { destination = block.sourceText; } return true; }; for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex) { const std::string& line = lines[lineIndex]; const size_t humanLine = lineIndex + 1; if (inSharedIncludeBlock || inProgramBlock) { if (line == "ENDHLSL" || line == "ENDCG") { inSharedIncludeBlock = false; inProgramBlock = false; continue; } std::vector pragmaTokens; if (!TryTokenizeQuotedArguments(line, pragmaTokens) || pragmaTokens.empty()) { continue; } if (pragmaTokens[0] != "#pragma") { continue; } if (pragmaTokens.size() >= 2u && pragmaTokens[1] == "backend") { return fail("Unity-style single-source shaders must not use #pragma backend", humanLine); } if (inSharedIncludeBlock) { continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "vertex") { currentPass->vertexEntryPoint = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "fragment") { currentPass->fragmentEntryPoint = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 3u && pragmaTokens[1] == "target") { currentPass->targetProfile = pragmaTokens[2].c_str(); continue; } if (pragmaTokens.size() >= 2u && (pragmaTokens[1] == "multi_compile" || pragmaTokens[1] == "multi_compile_local" || pragmaTokens[1] == "shader_feature" || pragmaTokens[1] == "shader_feature_local")) { ShaderKeywordDeclaration declaration = {}; if (!TryParseShaderKeywordDeclarationPragma(pragmaTokens, declaration)) { return fail("keyword pragma must declare at least one option", humanLine); } currentPass->keywordDeclarations.PushBack(declaration); continue; } return fail("unsupported pragma in Unity-style single-source shader", humanLine); } if (line == "{") { switch (pendingBlock) { case BlockKind::Shader: blockStack.push_back(BlockKind::Shader); break; case BlockKind::Properties: blockStack.push_back(BlockKind::Properties); break; case BlockKind::SubShader: outDesc.subShaders.emplace_back(); currentSubShader = &outDesc.subShaders.back(); blockStack.push_back(BlockKind::SubShader); break; case BlockKind::Pass: if (currentSubShader == nullptr) { return fail("pass block must be inside a SubShader", humanLine); } currentSubShader->passes.emplace_back(); currentPass = ¤tSubShader->passes.back(); currentPass->hasFixedFunctionState = true; currentPass->fixedFunctionState = BuildUnityDefaultFixedFunctionState(); if (currentSubShader->hasFixedFunctionState) { currentPass->fixedFunctionState = currentSubShader->fixedFunctionState; } blockStack.push_back(BlockKind::Pass); break; case BlockKind::None: default: return fail("unexpected opening brace", humanLine); } pendingBlock = BlockKind::None; continue; } if (line == "}") { if (blockStack.empty()) { return fail("unexpected closing brace", humanLine); } const BlockKind closingBlock = blockStack.back(); blockStack.pop_back(); if (closingBlock == BlockKind::Pass) { currentPass = nullptr; } else if (closingBlock == BlockKind::SubShader) { currentSubShader = nullptr; } continue; } if (StartsWithKeyword(line, "Shader")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("Shader declaration is missing a name", humanLine); } outDesc.name = tokens[1].c_str(); pendingBlock = BlockKind::Shader; continue; } if (line == "Properties") { pendingBlock = BlockKind::Properties; continue; } if (currentBlock() == BlockKind::Shader && StartsWithKeyword(line, "Fallback")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("Fallback directive is missing a value", humanLine); } outDesc.fallback = tokens[1].c_str(); continue; } if (StartsWithKeyword(line, "SubShader")) { pendingBlock = BlockKind::SubShader; continue; } if (StartsWithKeyword(line, "Pass")) { pendingBlock = BlockKind::Pass; continue; } if (line == "Resources" || StartsWithKeyword(line, "Resources")) { return fail("Unity-style single-source shaders must not declare Resources blocks", humanLine); } if (StartsWithKeyword(line, "Tags")) { std::vector parsedTags; if (!TryParseInlineTagAssignments(line, parsedTags)) { return fail("Tags block must use inline key/value pairs", humanLine); } if (currentPass != nullptr) { currentPass->tags.insert(currentPass->tags.end(), parsedTags.begin(), parsedTags.end()); } else if (currentSubShader != nullptr) { currentSubShader->tags.insert(currentSubShader->tags.end(), parsedTags.begin(), parsedTags.end()); } else { return fail("Tags block is only supported inside SubShader or Pass", humanLine); } continue; } if (currentBlock() == BlockKind::Properties) { ShaderPropertyDesc property = {}; if (!TryParseAuthoringPropertyLine(line, property)) { return fail("invalid Properties entry", humanLine); } outDesc.properties.PushBack(property); continue; } if (line == "HLSLINCLUDE" || line == "CGINCLUDE") { if (currentPass != nullptr) { return fail("HLSLINCLUDE is not supported inside a Pass block", humanLine); } Containers::String* destination = nullptr; if (currentBlock() == BlockKind::Shader) { destination = &outDesc.sharedProgramSource; } else if (currentBlock() == BlockKind::SubShader && currentSubShader != nullptr) { destination = ¤tSubShader->sharedProgramSource; } else { return fail( "HLSLINCLUDE is only supported directly inside Shader or SubShader in the new authoring mode", humanLine); } inSharedIncludeBlock = true; if (!consumeExtractedBlock( ExtractedProgramBlock::Kind::SharedInclude, *destination, true, humanLine)) { return false; } continue; } if (currentBlock() == BlockKind::SubShader && StartsWithKeyword(line, "LOD")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) { return fail("LOD directive must provide a numeric value", humanLine); } try { const Core::uint32 lodValue = static_cast(std::stoul(tokens[1])); SetOrReplaceAuthoringTag(currentSubShader->tags, "LOD", std::to_string(lodValue).c_str()); } catch (...) { return fail("LOD directive must provide a numeric value", humanLine); } continue; } if (currentBlock() == BlockKind::SubShader && currentSubShader != nullptr) { if (StartsWithKeyword(line, "Cull")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) { return fail("Cull directive must use Front, Back, or Off", humanLine); } EnsureAuthoringFixedFunctionStateInitialized( currentSubShader->hasFixedFunctionState, currentSubShader->fixedFunctionState); if (!TryParseUnityStyleCullMode(tokens[1], currentSubShader->fixedFunctionState.cullMode)) { return fail("Cull directive must use Front, Back, or Off", humanLine); } continue; } if (StartsWithKeyword(line, "ZWrite")) { std::vector tokens; bool enabled = false; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u || !TryParseUnityStyleBoolDirectiveToken(tokens[1], enabled)) { return fail("ZWrite directive must use On or Off", humanLine); } EnsureAuthoringFixedFunctionStateInitialized( currentSubShader->hasFixedFunctionState, currentSubShader->fixedFunctionState); currentSubShader->fixedFunctionState.depthWriteEnable = enabled; continue; } if (StartsWithKeyword(line, "ZTest")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) { return fail("ZTest directive uses an unsupported compare function", humanLine); } EnsureAuthoringFixedFunctionStateInitialized( currentSubShader->hasFixedFunctionState, currentSubShader->fixedFunctionState); if (!TryParseUnityStyleComparisonFunc(tokens[1], currentSubShader->fixedFunctionState.depthFunc)) { return fail("ZTest directive uses an unsupported compare function", humanLine); } currentSubShader->fixedFunctionState.depthTestEnable = true; continue; } if (StartsWithKeyword(line, "Blend")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens)) { return fail("Blend directive could not be tokenized", humanLine); } for (size_t tokenIndex = 1; tokenIndex < tokens.size(); ++tokenIndex) { if (!tokens[tokenIndex].empty() && tokens[tokenIndex].back() == ',') { tokens[tokenIndex].pop_back(); } } EnsureAuthoringFixedFunctionStateInitialized( currentSubShader->hasFixedFunctionState, currentSubShader->fixedFunctionState); if (!TryParseUnityStyleBlendDirective(tokens, currentSubShader->fixedFunctionState)) { return fail("Blend directive uses an unsupported factor combination", humanLine); } continue; } if (StartsWithKeyword(line, "ColorMask")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || (tokens.size() != 2u && tokens.size() != 3u)) { return fail("ColorMask directive uses an unsupported channel mask", humanLine); } EnsureAuthoringFixedFunctionStateInitialized( currentSubShader->hasFixedFunctionState, currentSubShader->fixedFunctionState); if (!TryParseUnityStyleColorMask(tokens[1], currentSubShader->fixedFunctionState.colorWriteMask)) { return fail("ColorMask directive uses an unsupported channel mask", humanLine); } continue; } } if (currentBlock() == BlockKind::Pass && currentPass != nullptr) { if (StartsWithKeyword(line, "Name")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() < 2u) { return fail("pass Name directive is missing a value", humanLine); } currentPass->name = tokens[1].c_str(); continue; } if (StartsWithKeyword(line, "Cull")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) { return fail("Cull directive must use Front, Back, or Off", humanLine); } EnsureAuthoringFixedFunctionStateInitialized( currentPass->hasFixedFunctionState, currentPass->fixedFunctionState); if (!TryParseUnityStyleCullMode(tokens[1], currentPass->fixedFunctionState.cullMode)) { return fail("Cull directive must use Front, Back, or Off", humanLine); } continue; } if (StartsWithKeyword(line, "ZWrite")) { std::vector tokens; bool enabled = false; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u || !TryParseUnityStyleBoolDirectiveToken(tokens[1], enabled)) { return fail("ZWrite directive must use On or Off", humanLine); } EnsureAuthoringFixedFunctionStateInitialized( currentPass->hasFixedFunctionState, currentPass->fixedFunctionState); currentPass->fixedFunctionState.depthWriteEnable = enabled; continue; } if (StartsWithKeyword(line, "ZTest")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) { return fail("ZTest directive uses an unsupported compare function", humanLine); } EnsureAuthoringFixedFunctionStateInitialized( currentPass->hasFixedFunctionState, currentPass->fixedFunctionState); if (!TryParseUnityStyleComparisonFunc(tokens[1], currentPass->fixedFunctionState.depthFunc)) { return fail("ZTest directive uses an unsupported compare function", humanLine); } currentPass->fixedFunctionState.depthTestEnable = true; continue; } if (StartsWithKeyword(line, "Blend")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens)) { return fail("Blend directive could not be tokenized", humanLine); } for (size_t tokenIndex = 1; tokenIndex < tokens.size(); ++tokenIndex) { if (!tokens[tokenIndex].empty() && tokens[tokenIndex].back() == ',') { tokens[tokenIndex].pop_back(); } } EnsureAuthoringFixedFunctionStateInitialized( currentPass->hasFixedFunctionState, currentPass->fixedFunctionState); if (!TryParseUnityStyleBlendDirective(tokens, currentPass->fixedFunctionState)) { return fail("Blend directive uses an unsupported factor combination", humanLine); } continue; } if (StartsWithKeyword(line, "ColorMask")) { std::vector tokens; if (!TryTokenizeQuotedArguments(line, tokens) || (tokens.size() != 2u && tokens.size() != 3u)) { return fail("ColorMask directive uses an unsupported channel mask", humanLine); } EnsureAuthoringFixedFunctionStateInitialized( currentPass->hasFixedFunctionState, currentPass->fixedFunctionState); if (!TryParseUnityStyleColorMask(tokens[1], currentPass->fixedFunctionState.colorWriteMask)) { return fail("ColorMask directive uses an unsupported channel mask", humanLine); } continue; } if (line == "HLSLPROGRAM" || line == "CGPROGRAM") { inProgramBlock = true; if (!consumeExtractedBlock( ExtractedProgramBlock::Kind::PassProgram, currentPass->programSource, false, humanLine)) { return false; } continue; } } return fail("unsupported authoring statement: " + line, humanLine); } if (inSharedIncludeBlock || inProgramBlock) { return fail("program block was not closed", lines.size()); } if (!blockStack.empty()) { return fail("one or more blocks were not closed", lines.size()); } if (outDesc.name.Empty()) { return fail("shader name is missing", 0); } if (outDesc.subShaders.empty()) { return fail("shader does not declare any SubShader blocks", 0); } for (AuthoringSubShaderEntry& subShader : outDesc.subShaders) { if (subShader.passes.empty()) { continue; } for (AuthoringPassEntry& pass : subShader.passes) { if (pass.name.Empty()) { return fail("a Pass is missing a Name directive", 0); } if (pass.programSource.Empty()) { return fail("a Pass is missing an HLSLPROGRAM block", 0); } if (pass.vertexEntryPoint.Empty()) { return fail("a Pass is missing a #pragma vertex directive", 0); } if (pass.fragmentEntryPoint.Empty()) { return fail("a Pass is missing a #pragma fragment directive", 0); } } } return true; } } // namespace Resources } // namespace XCEngine