Files
XCEngine/engine/Runtime/Resources/Shader/Internal/ShaderAuthoringShared.cpp

457 lines
14 KiB
C++

#include "ShaderAuthoringInternal.h"
#include "../ShaderSourceUtils.h"
#include "ShaderFileUtils.h"
#include <sstream>
#include <string>
#include <unordered_set>
#include <vector>
namespace XCEngine {
namespace Resources {
namespace Internal {
namespace {
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;
}
void SetIncludeResolveError(
const Containers::String& sourcePath,
const Containers::String& includePath,
const Containers::String& includeChain,
Containers::String* outError) {
if (outError == nullptr) {
return;
}
*outError = BuildShaderDependencyResolutionDiagnostic(
includePath,
sourcePath,
includeChain);
}
Containers::String NormalizeIncludePathSeparators(const Containers::String& path) {
std::string normalized(path.CStr());
for (char& ch : normalized) {
if (ch == '\\') {
ch = '/';
}
}
return Containers::String(normalized.c_str());
}
bool IsShaderPackageIncludePath(const Containers::String& includePath) {
return NormalizeIncludePathSeparators(includePath).StartsWith("Packages/");
}
bool TryParseQuotedIncludePath(
const std::string& rawLine,
Containers::String& outIncludePath) {
const std::string line = TrimCopy(StripAuthoringLineComment(rawLine));
if (line.rfind("#include", 0) != 0) {
return false;
}
const size_t firstQuote = line.find('"');
if (firstQuote == std::string::npos) {
return false;
}
const size_t secondQuote = line.find('"', firstQuote + 1);
if (secondQuote == std::string::npos || secondQuote <= firstQuote + 1) {
return false;
}
outIncludePath = line.substr(firstQuote + 1, secondQuote - firstQuote - 1).c_str();
return true;
}
Containers::String BuildIncludeChainText(
const std::vector<Containers::String>& includeChain,
const Containers::String& requestedIncludePath) {
Containers::String text;
for (const Containers::String& chainEntry : includeChain) {
if (chainEntry.Empty()) {
continue;
}
if (!text.Empty()) {
text += " -> ";
}
text += chainEntry;
}
if (!requestedIncludePath.Empty()) {
if (!text.Empty()) {
text += " -> ";
}
text += requestedIncludePath;
}
return text;
}
bool ExpandShaderPackageIncludeSourceRecursive(
const Containers::String& sourcePath,
const Containers::String& sourceText,
std::unordered_set<std::string>& activeIncludePaths,
std::vector<Containers::String>& includeChain,
Containers::String& outSourceText,
Containers::String* outError) {
outSourceText.Clear();
std::istringstream stream(ToStdString(sourceText));
std::string rawLine;
while (std::getline(stream, rawLine)) {
Containers::String includePath;
if (!TryParseQuotedIncludePath(rawLine, includePath) ||
!IsShaderPackageIncludePath(includePath)) {
outSourceText += rawLine.c_str();
outSourceText += '\n';
continue;
}
const Containers::String resolvedPath = ResolveShaderDependencyPath(includePath, sourcePath);
if (resolvedPath.Empty()) {
SetIncludeResolveError(
sourcePath,
includePath,
BuildIncludeChainText(includeChain, includePath),
outError);
return false;
}
const std::string activePathKey = ToStdString(resolvedPath);
if (!activeIncludePaths.insert(activePathKey).second) {
if (outError != nullptr) {
*outError =
Containers::String("Cyclic shader package include: requested=") +
includePath +
", source=" +
sourcePath +
", resolved=" +
resolvedPath +
", includeChain=" +
BuildIncludeChainText(includeChain, includePath);
}
return false;
}
Containers::String includedSourceText;
if (!ReadShaderTextFile(resolvedPath, includedSourceText)) {
activeIncludePaths.erase(activePathKey);
SetIncludeResolveError(
sourcePath,
includePath,
BuildIncludeChainText(includeChain, includePath),
outError);
return false;
}
Containers::String expandedIncludedSource;
includeChain.push_back(resolvedPath);
if (!ExpandShaderPackageIncludeSourceRecursive(
resolvedPath,
includedSourceText,
activeIncludePaths,
includeChain,
expandedIncludedSource,
outError)) {
includeChain.pop_back();
activeIncludePaths.erase(activePathKey);
return false;
}
includeChain.pop_back();
activeIncludePaths.erase(activePathKey);
outSourceText += expandedIncludedSource;
if (!expandedIncludedSource.Empty() &&
expandedIncludedSource[expandedIncludedSource.Length() - 1] != '\n') {
outSourceText += '\n';
}
}
return true;
}
bool CollectQuotedIncludeDependencyPathsRecursive(
const Containers::String& sourcePath,
const Containers::String& sourceText,
std::unordered_set<std::string>& seenPaths,
std::vector<Containers::String>& includeChain,
Containers::Array<Containers::String>& outDependencies,
Containers::String* outError) {
std::istringstream stream(ToStdString(sourceText));
std::string rawLine;
while (std::getline(stream, rawLine)) {
Containers::String includePath;
if (!TryParseQuotedIncludePath(rawLine, includePath)) {
continue;
}
const Containers::String resolvedPath = ResolveShaderDependencyPath(includePath, sourcePath);
if (resolvedPath.Empty()) {
SetIncludeResolveError(
sourcePath,
includePath,
BuildIncludeChainText(includeChain, includePath),
outError);
return false;
}
const std::string key = ToStdString(resolvedPath);
if (key.empty() || !seenPaths.insert(key).second) {
continue;
}
outDependencies.PushBack(resolvedPath);
Containers::String includedSourceText;
if (!ReadShaderTextFile(resolvedPath, includedSourceText)) {
SetIncludeResolveError(
sourcePath,
includePath,
BuildIncludeChainText(includeChain, includePath),
outError);
return false;
}
includeChain.push_back(resolvedPath);
if (!CollectQuotedIncludeDependencyPathsRecursive(
resolvedPath,
includedSourceText,
seenPaths,
includeChain,
outDependencies,
outError)) {
includeChain.pop_back();
return false;
}
includeChain.pop_back();
}
return true;
}
} // namespace
void AppendAuthoringSourceBlock(
Containers::String& target,
const Containers::String& sourceBlock) {
if (sourceBlock.Empty()) {
return;
}
if (!target.Empty()) {
target += '\n';
}
target += sourceBlock;
}
bool CollectQuotedIncludeDependencyPaths(
const Containers::String& sourcePath,
const Containers::String& sourceText,
std::unordered_set<std::string>& seenPaths,
Containers::Array<Containers::String>& outDependencies,
Containers::String* outError) {
std::vector<Containers::String> includeChain;
includeChain.push_back(sourcePath);
return CollectQuotedIncludeDependencyPathsRecursive(
sourcePath,
sourceText,
seenPaths,
includeChain,
outDependencies,
outError);
}
bool ExpandShaderPackageIncludeSource(
const Containers::String& sourcePath,
const Containers::String& sourceText,
Containers::String& outSourceText,
Containers::String* outError) {
if (outError != nullptr) {
outError->Clear();
}
std::unordered_set<std::string> activeIncludePaths;
std::vector<Containers::String> includeChain;
includeChain.push_back(sourcePath);
return ExpandShaderPackageIncludeSourceRecursive(
sourcePath,
sourceText,
activeIncludePaths,
includeChain,
outSourceText,
outError);
}
bool IsShaderAuthoringPragmaDirective(const std::string& line) {
std::vector<std::string> pragmaTokens;
if (!TryTokenizeQuotedArguments(line, pragmaTokens) ||
pragmaTokens.empty() ||
pragmaTokens[0] != "#pragma" ||
pragmaTokens.size() < 2u) {
return false;
}
return pragmaTokens[1] == "vertex" ||
pragmaTokens[1] == "fragment" ||
pragmaTokens[1] == "compute" ||
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 StripShaderAuthoringPragmas(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 (IsShaderAuthoringPragmaDirective(normalizedLine)) {
continue;
}
strippedSource += rawLine;
strippedSource += '\n';
}
return strippedSource.c_str();
}
std::vector<ShaderKeywordSet> BuildShaderKeywordVariantSets(
const Containers::Array<ShaderKeywordDeclaration>& declarations) {
std::vector<ShaderKeywordSet> keywordSets(1);
for (const ShaderKeywordDeclaration& declaration : declarations) {
if (declaration.options.Empty()) {
continue;
}
std::vector<ShaderKeywordSet> nextKeywordSets;
std::unordered_set<std::string> 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());
}
} // namespace Internal
} // namespace Resources
} // namespace XCEngine