457 lines
14 KiB
C++
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
|