#include "ShaderAuthoringInternal.h" #include "../ShaderSourceUtils.h" namespace XCEngine { namespace Resources { namespace Internal { bool ParseLegacyBackendSplitShaderAuthoring( const Containers::String& path, const std::string& sourceText, ShaderIR& 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; ShaderSubShaderIR* currentSubShader = nullptr; ShaderPassIR* 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") { ShaderBackendVariantIR 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 ShaderSubShaderIR& subShader : outDesc.subShaders) { if (subShader.passes.empty()) { continue; } for (const ShaderPassIR& 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; } } // namespace Internal } // namespace Resources } // namespace XCEngine