291 lines
10 KiB
C++
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 = ¤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
|