Files
XCEngine/engine/src/Resources/Shader/Internal/LegacyShaderAuthoringParser.cpp

291 lines
10 KiB
C++

#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<std::string> lines;
SplitShaderAuthoringLines(sourceText, lines);
if (lines.empty()) {
return fail("shader file is empty", 0);
}
std::vector<BlockKind> 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<std::string> 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 = &currentSubShader->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<std::string> 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<std::string> 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<ShaderTagIR> 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<std::string> 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