resources: split shader authoring parser internals
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
#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 = ¤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<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
|
||||
Reference in New Issue
Block a user