Add shader package include dependency pipeline
This commit is contained in:
@@ -71,11 +71,18 @@ void AppendAuthoringSourceBlock(
|
||||
Containers::String& target,
|
||||
const Containers::String& sourceBlock);
|
||||
|
||||
void CollectQuotedIncludeDependencyPaths(
|
||||
bool CollectQuotedIncludeDependencyPaths(
|
||||
const Containers::String& sourcePath,
|
||||
const Containers::String& sourceText,
|
||||
std::unordered_set<std::string>& seenPaths,
|
||||
Containers::Array<Containers::String>& outDependencies);
|
||||
Containers::Array<Containers::String>& outDependencies,
|
||||
Containers::String* outError = nullptr);
|
||||
|
||||
bool ExpandShaderPackageIncludeSource(
|
||||
const Containers::String& sourcePath,
|
||||
const Containers::String& sourceText,
|
||||
Containers::String& outSourceText,
|
||||
Containers::String* outError = nullptr);
|
||||
|
||||
bool IsShaderAuthoringPragmaDirective(const std::string& line);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../ShaderAuthoringParser.h"
|
||||
#include "../ShaderSourceUtils.h"
|
||||
#include "ShaderAuthoringInternal.h"
|
||||
#include "ShaderFileUtils.h"
|
||||
#include "ShaderRuntimeBuildUtils.h"
|
||||
|
||||
@@ -22,7 +23,8 @@ bool CollectShaderAuthoringDependencyPathsRecursive(
|
||||
const std::string& sourceText,
|
||||
std::unordered_set<std::string>& seenShaderPaths,
|
||||
std::unordered_set<std::string>& seenDependencyPaths,
|
||||
Containers::Array<Containers::String>& outDependencies) {
|
||||
Containers::Array<Containers::String>& outDependencies,
|
||||
Containers::String* outError) {
|
||||
const fs::path normalizedShaderPath = fs::path(path.CStr()).lexically_normal();
|
||||
const std::string shaderKey = normalizedShaderPath.generic_string();
|
||||
if (!shaderKey.empty() && !seenShaderPaths.insert(shaderKey).second) {
|
||||
@@ -32,16 +34,45 @@ bool CollectShaderAuthoringDependencyPathsRecursive(
|
||||
ShaderIR shaderIR = {};
|
||||
Containers::String parseError;
|
||||
if (!ParseShaderAuthoring(path, sourceText, shaderIR, &parseError)) {
|
||||
if (outError != nullptr) {
|
||||
*outError = parseError;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CollectQuotedIncludeDependencyPaths(path, shaderIR.sharedProgramSource, seenDependencyPaths, outDependencies);
|
||||
if (!::XCEngine::Resources::Internal::CollectQuotedIncludeDependencyPaths(
|
||||
path,
|
||||
shaderIR.sharedProgramSource,
|
||||
seenDependencyPaths,
|
||||
outDependencies,
|
||||
outError)) {
|
||||
return false;
|
||||
}
|
||||
for (const ShaderSubShaderIR& subShader : shaderIR.subShaders) {
|
||||
CollectQuotedIncludeDependencyPaths(path, subShader.sharedProgramSource, seenDependencyPaths, outDependencies);
|
||||
if (!::XCEngine::Resources::Internal::CollectQuotedIncludeDependencyPaths(
|
||||
path,
|
||||
subShader.sharedProgramSource,
|
||||
seenDependencyPaths,
|
||||
outDependencies,
|
||||
outError)) {
|
||||
return false;
|
||||
}
|
||||
for (const ShaderPassIR& pass : subShader.passes) {
|
||||
if (!pass.isUsePass) {
|
||||
CollectQuotedIncludeDependencyPaths(path, pass.sharedProgramSource, seenDependencyPaths, outDependencies);
|
||||
CollectQuotedIncludeDependencyPaths(path, pass.programSource, seenDependencyPaths, outDependencies);
|
||||
if (!::XCEngine::Resources::Internal::CollectQuotedIncludeDependencyPaths(
|
||||
path,
|
||||
pass.sharedProgramSource,
|
||||
seenDependencyPaths,
|
||||
outDependencies,
|
||||
outError) ||
|
||||
!::XCEngine::Resources::Internal::CollectQuotedIncludeDependencyPaths(
|
||||
path,
|
||||
pass.programSource,
|
||||
seenDependencyPaths,
|
||||
outDependencies,
|
||||
outError)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -53,6 +84,13 @@ bool CollectShaderAuthoringDependencyPathsRecursive(
|
||||
resolvedUsePassPath)) {
|
||||
Containers::String builtinShaderPath;
|
||||
if (!TryGetBuiltinShaderPathByShaderName(pass.usePassShaderName, builtinShaderPath)) {
|
||||
if (outError != nullptr) {
|
||||
*outError =
|
||||
Containers::String("Failed to resolve shader UsePass: source=") +
|
||||
path +
|
||||
", requested=" +
|
||||
pass.usePassShaderName;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -70,6 +108,13 @@ bool CollectShaderAuthoringDependencyPathsRecursive(
|
||||
if (IsBuiltinShaderPath(resolvedUsePassPath)) {
|
||||
Containers::String builtinAssetPath;
|
||||
if (!TryResolveBuiltinShaderAssetPath(resolvedUsePassPath, builtinAssetPath)) {
|
||||
if (outError != nullptr) {
|
||||
*outError =
|
||||
Containers::String("Failed to resolve builtin shader UsePass asset: source=") +
|
||||
path +
|
||||
", requested=" +
|
||||
resolvedUsePassPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -84,6 +129,13 @@ bool CollectShaderAuthoringDependencyPathsRecursive(
|
||||
|
||||
Containers::String referencedSourceText;
|
||||
if (!ReadShaderTextFile(builtinAssetPath, referencedSourceText)) {
|
||||
if (outError != nullptr) {
|
||||
*outError =
|
||||
Containers::String("Failed to read shader UsePass source: source=") +
|
||||
path +
|
||||
", requested=" +
|
||||
builtinAssetPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -92,7 +144,8 @@ bool CollectShaderAuthoringDependencyPathsRecursive(
|
||||
referencedSourceText.CStr(),
|
||||
seenShaderPaths,
|
||||
seenDependencyPaths,
|
||||
outDependencies)) {
|
||||
outDependencies,
|
||||
outError)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@@ -109,6 +162,13 @@ bool CollectShaderAuthoringDependencyPathsRecursive(
|
||||
|
||||
Containers::String referencedSourceText;
|
||||
if (!ReadShaderTextFile(resolvedUsePassPath, referencedSourceText)) {
|
||||
if (outError != nullptr) {
|
||||
*outError =
|
||||
Containers::String("Failed to read shader UsePass source: source=") +
|
||||
path +
|
||||
", requested=" +
|
||||
resolvedUsePassPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -117,7 +177,8 @@ bool CollectShaderAuthoringDependencyPathsRecursive(
|
||||
referencedSourceText.CStr(),
|
||||
seenShaderPaths,
|
||||
seenDependencyPaths,
|
||||
outDependencies)) {
|
||||
outDependencies,
|
||||
outError)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -132,7 +193,18 @@ bool CollectShaderAuthoringDependencyPaths(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText,
|
||||
Containers::Array<Containers::String>& outDependencies) {
|
||||
return CollectShaderAuthoringDependencyPaths(path, sourceText, outDependencies, nullptr);
|
||||
}
|
||||
|
||||
bool CollectShaderAuthoringDependencyPaths(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText,
|
||||
Containers::Array<Containers::String>& outDependencies,
|
||||
Containers::String* outError) {
|
||||
outDependencies.Clear();
|
||||
if (outError != nullptr) {
|
||||
outError->Clear();
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> seenShaderPaths;
|
||||
std::unordered_set<std::string> seenDependencyPaths;
|
||||
@@ -141,7 +213,8 @@ bool CollectShaderAuthoringDependencyPaths(
|
||||
sourceText,
|
||||
seenShaderPaths,
|
||||
seenDependencyPaths,
|
||||
outDependencies);
|
||||
outDependencies,
|
||||
outError);
|
||||
}
|
||||
|
||||
LoadResult LoadShaderAuthoring(
|
||||
|
||||
@@ -14,6 +14,12 @@ bool CollectShaderAuthoringDependencyPaths(
|
||||
const std::string& sourceText,
|
||||
Containers::Array<Containers::String>& outDependencies);
|
||||
|
||||
bool CollectShaderAuthoringDependencyPaths(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText,
|
||||
Containers::Array<Containers::String>& outDependencies,
|
||||
Containers::String* outError);
|
||||
|
||||
LoadResult LoadShaderAuthoring(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ShaderAuthoringInternal.h"
|
||||
|
||||
#include "../ShaderSourceUtils.h"
|
||||
#include "ShaderFileUtils.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -29,6 +30,225 @@ Containers::String BuildShaderKeywordSetSignature(const ShaderKeywordSet& keywor
|
||||
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(
|
||||
@@ -44,36 +264,42 @@ void AppendAuthoringSourceBlock(
|
||||
target += sourceBlock;
|
||||
}
|
||||
|
||||
void CollectQuotedIncludeDependencyPaths(
|
||||
bool CollectQuotedIncludeDependencyPaths(
|
||||
const Containers::String& sourcePath,
|
||||
const Containers::String& sourceText,
|
||||
std::unordered_set<std::string>& seenPaths,
|
||||
Containers::Array<Containers::String>& outDependencies) {
|
||||
std::istringstream stream(ToStdString(sourceText));
|
||||
std::string rawLine;
|
||||
while (std::getline(stream, rawLine)) {
|
||||
const std::string line = TrimCopy(StripAuthoringLineComment(rawLine));
|
||||
if (line.rfind("#include", 0) != 0) {
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
const size_t firstQuote = line.find('"');
|
||||
if (firstQuote == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t secondQuote = line.find('"', firstQuote + 1);
|
||||
if (secondQuote == std::string::npos || secondQuote <= firstQuote + 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Containers::String includePath(line.substr(firstQuote + 1, secondQuote - firstQuote - 1).c_str());
|
||||
const Containers::String resolvedPath = ResolveShaderDependencyPath(includePath, sourcePath);
|
||||
const std::string key = ToStdString(resolvedPath);
|
||||
if (!key.empty() && seenPaths.insert(key).second) {
|
||||
outDependencies.PushBack(resolvedPath);
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -395,10 +395,15 @@ void AppendScannedAuthoringResourceBindings(
|
||||
}
|
||||
}
|
||||
|
||||
ShaderPass BuildConcretePass(
|
||||
bool BuildConcretePass(
|
||||
const Containers::String& path,
|
||||
const ShaderIR& shaderIR,
|
||||
const ShaderSubShaderIR& subShader,
|
||||
const ShaderPassIR& pass) {
|
||||
const ShaderPassIR& pass,
|
||||
ShaderPass& outShaderPass,
|
||||
Containers::String& outError) {
|
||||
outError.Clear();
|
||||
|
||||
ShaderPass shaderPass = {};
|
||||
shaderPass.name = pass.name;
|
||||
shaderPass.hasFixedFunctionState = pass.hasFixedFunctionState;
|
||||
@@ -426,7 +431,8 @@ ShaderPass BuildConcretePass(
|
||||
}
|
||||
}
|
||||
|
||||
return shaderPass;
|
||||
outShaderPass = std::move(shaderPass);
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String combinedSource;
|
||||
@@ -435,7 +441,16 @@ ShaderPass BuildConcretePass(
|
||||
AppendAuthoringSourceBlock(combinedSource, pass.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, pass.programSource);
|
||||
|
||||
const Containers::String strippedCombinedSource = StripShaderAuthoringPragmas(combinedSource);
|
||||
Containers::String expandedCombinedSource;
|
||||
if (!Internal::ExpandShaderPackageIncludeSource(
|
||||
path,
|
||||
combinedSource,
|
||||
expandedCombinedSource,
|
||||
&outError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Containers::String strippedCombinedSource = StripShaderAuthoringPragmas(expandedCombinedSource);
|
||||
if (shaderPass.resources.Empty()) {
|
||||
Containers::Array<ShaderResourceBindingDesc> defaultBindings;
|
||||
if (::XCEngine::Rendering::TryBuildBuiltinPassDefaultResourceBindings(shaderPass, defaultBindings)) {
|
||||
@@ -504,7 +519,8 @@ ShaderPass BuildConcretePass(
|
||||
shaderPass.variants.PushBack(fragmentVariant);
|
||||
}
|
||||
|
||||
return shaderPass;
|
||||
outShaderPass = std::move(shaderPass);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryLoadReferencedUsePassShader(
|
||||
@@ -764,7 +780,17 @@ LoadResult BuildShaderFromIRInternal(
|
||||
continue;
|
||||
}
|
||||
|
||||
ShaderPass concretePass = BuildConcretePass(shaderIR, subShader, pass);
|
||||
ShaderPass concretePass = {};
|
||||
Containers::String concretePassError;
|
||||
if (!BuildConcretePass(
|
||||
path,
|
||||
shaderIR,
|
||||
subShader,
|
||||
pass,
|
||||
concretePass,
|
||||
concretePassError)) {
|
||||
return LoadResult(concretePassError);
|
||||
}
|
||||
const std::string passKey = ToStdString(concretePass.name);
|
||||
if (passKey.empty()) {
|
||||
return LoadResult("Shader authoring produced a pass with an empty name");
|
||||
|
||||
@@ -70,7 +70,17 @@ ImportSettings* ShaderLoader::GetDefaultSettings() const {
|
||||
bool ShaderLoader::CollectSourceDependencies(
|
||||
const Containers::String& path,
|
||||
Containers::Array<Containers::String>& outDependencies) const {
|
||||
return CollectSourceDependencies(path, outDependencies, nullptr);
|
||||
}
|
||||
|
||||
bool ShaderLoader::CollectSourceDependencies(
|
||||
const Containers::String& path,
|
||||
Containers::Array<Containers::String>& outDependencies,
|
||||
Containers::String* outError) const {
|
||||
outDependencies.Clear();
|
||||
if (outError != nullptr) {
|
||||
outError->Clear();
|
||||
}
|
||||
|
||||
if (IsBuiltinShaderPath(path)) {
|
||||
return true;
|
||||
@@ -83,15 +93,23 @@ bool ShaderLoader::CollectSourceDependencies(
|
||||
|
||||
const Containers::Array<Core::uint8> data = ReadShaderFileData(path);
|
||||
if (data.Empty()) {
|
||||
if (outError != nullptr) {
|
||||
*outError = Containers::String("Failed to read shader file: ") + path;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string sourceText = ToStdStringFromBytes(data);
|
||||
if (!LooksLikeShaderAuthoring(sourceText)) {
|
||||
if (outError != nullptr) {
|
||||
*outError =
|
||||
Containers::String("Shader authoring file must start with a Shader declaration: ") +
|
||||
path;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return CollectShaderAuthoringDependencyPaths(path, sourceText, outDependencies);
|
||||
return CollectShaderAuthoringDependencyPaths(path, sourceText, outDependencies, outError);
|
||||
}
|
||||
|
||||
ShaderType ShaderLoader::DetectShaderType(const Containers::String& path, const Containers::String& source) {
|
||||
|
||||
@@ -14,10 +14,66 @@ std::string ToStdString(const Containers::String& value) {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kBuiltinShaderPackagePrefix = "Packages/";
|
||||
constexpr const char* kBuiltinShaderPackageRootRelativePath =
|
||||
"engine/assets/builtin/shaders/Packages";
|
||||
|
||||
Containers::String NormalizeDependencyPathSeparators(const Containers::String& path) {
|
||||
std::string normalized(path.CStr());
|
||||
for (char& ch : normalized) {
|
||||
if (ch == '\\') {
|
||||
ch = '/';
|
||||
}
|
||||
}
|
||||
|
||||
return Containers::String(normalized.c_str());
|
||||
}
|
||||
|
||||
Containers::String NormalizePathString(const std::filesystem::path& path) {
|
||||
return Containers::String(path.lexically_normal().generic_string().c_str());
|
||||
}
|
||||
|
||||
bool TryResolvePathFromAnchor(
|
||||
const std::filesystem::path& anchor,
|
||||
const std::filesystem::path& relativePath,
|
||||
std::filesystem::path& outPath) {
|
||||
if (anchor.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::path current = anchor.lexically_normal();
|
||||
if (std::filesystem::is_regular_file(current, ec)) {
|
||||
current = current.parent_path();
|
||||
}
|
||||
|
||||
while (!current.empty()) {
|
||||
const std::filesystem::path candidate = (current / relativePath).lexically_normal();
|
||||
if (std::filesystem::exists(candidate, ec)) {
|
||||
outPath = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::filesystem::path parent = current.parent_path();
|
||||
if (parent == current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ContainsParentTraversal(const std::filesystem::path& path) {
|
||||
for (const std::filesystem::path& part : path) {
|
||||
if (part == "..") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t SkipWhitespace(const std::string& text, size_t pos) {
|
||||
@@ -229,9 +285,16 @@ Containers::String ResolveShaderDependencyPath(
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
const std::filesystem::path dependencyFsPath(dependencyPath.CStr());
|
||||
const Containers::String normalizedDependencyPath =
|
||||
NormalizeDependencyPathSeparators(dependencyPath);
|
||||
|
||||
if (normalizedDependencyPath.StartsWith(kBuiltinShaderPackagePrefix)) {
|
||||
return ResolveBuiltinShaderPackageDependencyPath(normalizedDependencyPath);
|
||||
}
|
||||
|
||||
const std::filesystem::path dependencyFsPath(normalizedDependencyPath.CStr());
|
||||
if (dependencyFsPath.is_absolute()) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
const std::filesystem::path sourceFsPath(sourcePath.CStr());
|
||||
@@ -250,5 +313,110 @@ Containers::String ResolveShaderDependencyPath(
|
||||
return NormalizePathString(sourceFsPath.parent_path() / dependencyFsPath);
|
||||
}
|
||||
|
||||
Containers::String BuildShaderDependencyResolutionDiagnostic(
|
||||
const Containers::String& dependencyPath,
|
||||
const Containers::String& sourcePath,
|
||||
const Containers::String& includeChain) {
|
||||
const Containers::String normalizedDependencyPath =
|
||||
NormalizeDependencyPathSeparators(dependencyPath);
|
||||
|
||||
Containers::String diagnostic =
|
||||
Containers::String("Failed to resolve shader include: requested=") +
|
||||
dependencyPath +
|
||||
", normalized=" +
|
||||
normalizedDependencyPath +
|
||||
", source=" +
|
||||
sourcePath +
|
||||
", builtinPackageRoot=" +
|
||||
kBuiltinShaderPackageRootRelativePath;
|
||||
|
||||
if (!includeChain.Empty()) {
|
||||
diagnostic += ", includeChain=";
|
||||
diagnostic += includeChain;
|
||||
}
|
||||
|
||||
std::filesystem::path normalizedCandidate;
|
||||
if (normalizedDependencyPath.StartsWith(kBuiltinShaderPackagePrefix)) {
|
||||
const std::filesystem::path dependencyFsPath(normalizedDependencyPath.CStr());
|
||||
const std::filesystem::path packageRelativePath =
|
||||
dependencyFsPath.lexically_relative("Packages");
|
||||
if (!packageRelativePath.empty() &&
|
||||
!packageRelativePath.is_absolute() &&
|
||||
!ContainsParentTraversal(packageRelativePath)) {
|
||||
normalizedCandidate =
|
||||
(std::filesystem::path(kBuiltinShaderPackageRootRelativePath) /
|
||||
packageRelativePath).lexically_normal();
|
||||
}
|
||||
} else {
|
||||
const std::filesystem::path dependencyFsPath(normalizedDependencyPath.CStr());
|
||||
const std::filesystem::path sourceFsPath(sourcePath.CStr());
|
||||
if (!dependencyFsPath.is_absolute()) {
|
||||
normalizedCandidate =
|
||||
(sourceFsPath.parent_path() / dependencyFsPath).lexically_normal();
|
||||
}
|
||||
}
|
||||
|
||||
diagnostic += ", normalizedCandidate=";
|
||||
diagnostic += normalizedCandidate.empty()
|
||||
? Containers::String("<none>")
|
||||
: NormalizePathString(normalizedCandidate);
|
||||
|
||||
diagnostic += ", searchedAnchors=[sourceRoot=";
|
||||
diagnostic += NormalizePathString(std::filesystem::path(__FILE__));
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
diagnostic += ", resourceRoot=";
|
||||
diagnostic += resourceRoot.Empty() ? Containers::String("<empty>") : resourceRoot;
|
||||
|
||||
std::error_code ec;
|
||||
diagnostic += ", currentPath=";
|
||||
diagnostic += ec
|
||||
? Containers::String("<unavailable>")
|
||||
: NormalizePathString(std::filesystem::current_path(ec));
|
||||
diagnostic += "]";
|
||||
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
Containers::String ResolveBuiltinShaderPackageDependencyPath(
|
||||
const Containers::String& dependencyPath) {
|
||||
const Containers::String normalizedDependencyPath =
|
||||
NormalizeDependencyPathSeparators(dependencyPath);
|
||||
|
||||
if (!normalizedDependencyPath.StartsWith(kBuiltinShaderPackagePrefix)) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
const std::filesystem::path dependencyFsPath(normalizedDependencyPath.CStr());
|
||||
const std::filesystem::path packageRelativePath = dependencyFsPath.lexically_relative("Packages");
|
||||
if (packageRelativePath.empty() ||
|
||||
packageRelativePath.is_absolute() ||
|
||||
ContainsParentTraversal(packageRelativePath)) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
const std::filesystem::path relativePath =
|
||||
std::filesystem::path(kBuiltinShaderPackageRootRelativePath) /
|
||||
packageRelativePath;
|
||||
|
||||
std::filesystem::path resolvedPath;
|
||||
if (TryResolvePathFromAnchor(std::filesystem::path(__FILE__), relativePath, resolvedPath)) {
|
||||
return NormalizePathString(resolvedPath);
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (!resourceRoot.Empty() &&
|
||||
TryResolvePathFromAnchor(std::filesystem::path(resourceRoot.CStr()), relativePath, resolvedPath)) {
|
||||
return NormalizePathString(resolvedPath);
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
if (TryResolvePathFromAnchor(std::filesystem::current_path(ec), relativePath, resolvedPath)) {
|
||||
return NormalizePathString(resolvedPath);
|
||||
}
|
||||
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -29,5 +29,13 @@ Containers::String ResolveShaderDependencyPath(
|
||||
const Containers::String& dependencyPath,
|
||||
const Containers::String& sourcePath);
|
||||
|
||||
Containers::String BuildShaderDependencyResolutionDiagnostic(
|
||||
const Containers::String& dependencyPath,
|
||||
const Containers::String& sourcePath,
|
||||
const Containers::String& includeChain = Containers::String());
|
||||
|
||||
Containers::String ResolveBuiltinShaderPackageDependencyPath(
|
||||
const Containers::String& dependencyPath);
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
|
||||
Reference in New Issue
Block a user